Chapitre 4.5 — Débuguer le runtime
⏱️ TL;DR — Le symptôme est toujours le même : « le module ne remonte rien ». Derrière, une poignée de causes récurrentes — le SCO muet (API jamais trouvée), la session jamais initialisée, le statut oublié, le
Terminatemanquant, une valeur rejetée silencieusement. La bonne nouvelle : SCORM te donne de quoi savoir précisément ce qui cloche, à condition de le demander. Après chaque appel,GetLastError/LMSGetLastErrorrenvoie un code ;GetErrorStringle traduit en message ;GetDiagnosticdonne le détail propre au LMS. Ce chapitre te donne un wrapper qui vérifie l’erreur à chaque appel, une checklist de diagnostic, les outils (console, SCORM Cloud, extensions), et déroule un cas concret pas à pas.
🎯 Objectifs
- Reconnaître le « SCO muet » et ses causes (API introuvable, jamais initialisé).
- Interroger l’erreur après chaque appel :
GetLastError+GetErrorString+GetDiagnostic. - Écrire un wrapper qui journalise et détecte les échecs silencieux.
- Dérouler une checklist « mon module ne remonte rien » et un cas concret de bout en bout.
Le SCO muet
Un « SCO muet », c’est un module qui s’affiche mais ne communique pas avec le LMS. Deux grandes familles :
- Il n’a jamais trouvé l’API (chapitre 4.1) : cross-domain, iframe imbriquée non gérée, pop-up bloquée,
findAPI()naïf. Rien ne partira jamais, car il n’y a personne à qui parler. - Il a trouvé l’API mais ne s’en sert pas correctement : jamais
Initialize, statut jamais posé,Commit/Terminateoubliés, valeurs rejetées. L’API est là, mais le contrat n’est pas honoré.
Le drame du SCO muet, c’est qu’il échoue en silence. Les méthodes SCORM ne lèvent pas d’exception quand elles échouent : elles renvoient "false" (ou une chaîne vide) et posent un code d’erreur que, par défaut, personne ne lit. D’où la règle de survie du débogage SCORM :
💡 Réflexe — Vérifie l’erreur après CHAQUE appel. Un
SetValuequi renvoie"false"n’a rien fait — mais il ne te le dira que si tu appellesGetLastErrorjuste après. Le code qui « a l’air de marcher » sans jamais lire les erreurs est exactement celui qui livre des SCO muets en production.
Lire l’erreur : les trois méthodes
Après chaque appel au runtime, trois méthodes te renseignent :
| Méthode (2004 / 1.2) | Renvoie |
|---|---|
GetLastError() / LMSGetLastError() | Le code d’erreur du dernier appel ("0" = pas d’erreur). |
GetErrorString(code) / LMSGetErrorString(code) | Le libellé standard du code (message court). |
GetDiagnostic(code) / LMSGetDiagnostic(code) | Un détail propre au LMS (souvent le plus utile). |
Le principe : GetLastError te dit s’il y a eu une erreur et laquelle ; GetErrorString la nomme ; GetDiagnostic explique (au niveau de détail que le LMS veut bien fournir).
Quelques codes SCORM 1.2 à reconnaître (la liste complète est dans la spec) :
| Code | Signification |
|---|---|
0 | Pas d’erreur — l’appel a réussi. |
101 | Exception générale (souvent : appel avant LMSInitialize). |
201 | Argument invalide (nom d’élément mal formé). |
301 | Non initialisé (lecture/écriture hors session). |
401 | Élément non implémenté par ce LMS. |
403 | Élément en lecture seule (tu as tenté d’écrire un RO). |
404 | Élément en écriture seule (tu as tenté de lire un WO). |
405 | Type de donnée incorrect (mauvais format de valeur). |
⚠️ Piège — Ignorer le retour des méthodes et le code d’erreur.
LMSSetValue(...)qui renvoie"false"n’a rien écrit : peut-être un403(champ RO), un301(pas initialisé), un405(mauvais type). Sans lireGetLastError/GetDiagnostic, tu passes des heures à chercher « pourquoi le score ne remonte pas » alors que le LMS te l’a dit au premier appel.
Un wrapper qui parle
Le meilleur investissement de débogage : n’appeler jamais le runtime en direct, mais à travers un wrapper qui journalise l’appel, vérifie le code d’erreur et crie en cas d’échec.
// scorm-debug.js — wrapper qui vérifie l'erreur après chaque appel
// (version SCORM 1.2 ; adapter les noms de méthodes pour 2004)
function lastError(api) {
const code = api.LMSGetLastError();
if (code === "0") return null;
return {
code,
label: api.LMSGetErrorString(code), // libellé standard
detail: api.LMSGetDiagnostic(code), // détail du LMS
};
}
function setValue(api, elt, val) {
const ok = api.LMSSetValue(elt, String(val));
const err = lastError(api);
if (ok !== "true" || err) {
console.error(
`[SCORM] SetValue("${elt}", "${val}") a ÉCHOUÉ`,
err || "(retour false sans code)"
);
return false;
}
console.debug(`[SCORM] SetValue("${elt}", "${val}") OK`);
return true;
}
function getValue(api, elt) {
const val = api.LMSGetValue(elt);
const err = lastError(api);
if (err) {
console.error(`[SCORM] GetValue("${elt}") a ÉCHOUÉ`, err);
return null;
}
return val;
}
function commit(api) {
const ok = api.LMSCommit("");
const err = lastError(api);
if (ok !== "true" || err) console.error("[SCORM] Commit a ÉCHOUÉ", err);
return ok === "true";
}Avec ce wrapper, un champ mal orthographié, un statut posé avant Initialize, un score au mauvais format : tout apparaît immédiatement dans la console, avec le code et le message. Tu passes du « ça ne marche pas » au « SetValue("cmi.core.score.raw", "85%") → code 405, type incorrect » — un diagnostic, pas une devinette.
Les outils
| Outil | À quoi il sert |
|---|---|
| Console navigateur | Voir tes logs, inspecter la chaîne de frames, tester getScormAPI() à la main. Attention : ouvre la console dans l’iframe du SCO, pas dans la page LMS (sélecteur de contexte / frame). |
| SCORM Cloud (Rustici) | LMS de test de référence : importe ton paquet, lance-le, et regarde le journal des appels runtime (chaque SetValue, chaque valeur, chaque erreur). L’étalon-or pour valider un SCO hors de tout LMS client. |
| Extension de debug SCORM | Des extensions navigateur affichent en direct les appels API et le contenu du modèle CMI, sans toucher au code. Pratiques pour auditer un module compilé (Storyline, Rise) dont tu n’as pas la source. |
| Suite de tests ADL | Vérifie la conformité formelle du SCO au standard — utile avant de livrer/certifier. |
| Logs applicatifs | Ton wrapper console.debug/console.error : garde-les (désactivables) pour diagnostiquer sur le terrain, chez le client. |
🔌 Côté intégration — Avant d’envoyer un paquet à une école, passe-le par SCORM Cloud. Si la complétion et le score y remontent proprement, tu as éliminé 90 % des tickets « ça ne marche pas chez nous » : le problème sera alors du côté de la configuration du LMS client (mode d’ouverture, cross-domain), pas de ton contenu. Un SCO validé sur SCORM Cloud est un argument commercial : « conforme, testé, ça s’importe partout ».
La checklist « mon module ne remonte rien »
Déroule dans l’ordre — chaque étape élimine une famille de causes.
En clair, les six questions à se poser :
getScormAPI()renvoie-t-il un objet ? Non → SCO muet par API introuvable (chapitre 4.1).Initialize/LMSInitializea-t-il renvoyé"true"? Non → session jamais ouverte ; tous lesSetValuesuivants échoueront en301/101.- Le statut est-il posé à
completed/passedà la fin ? Non → piège n°1 (chapitre 4.4). CommitpuisTerminate/LMSFinishsont-ils appelés, y compris en fermeture d’onglet ? Non → données perdues (chapitre 4.2).GetLastErrorest-il"0"après chaqueSetValue? Non → une valeur est rejetée (mauvais nom, mauvais type, champ RO) ; lireGetDiagnostic.- Tout est vert côté SCO mais ça ne remonte toujours pas ? Regarder la config du LMS client (mode d’ouverture, domaine).
Cas concret : le module FormaCampus, pas à pas
Reprenons le fil rouge et déroulons la checklist sur le module qui ne remontait pas la complétion dans le Moodle de l’école.
Étape 1 — API trouvée ? Dans la console (contexte : iframe du SCO), on tape getScormAPI(). Retour : un objet, version: "1.2". ✅ L’API est trouvée. On élimine le cross-domain et la pop-up bloquée (chapitre 4.1).
Étape 2 — Session ouverte ? On instrumente : LMSInitialize("") renvoie "true", GetLastError = "0". ✅ Session ouverte. Le SCO n’est pas muet au sens « jamais initialisé ».
Étape 3 — Statut posé ? On trace les SetValue. On voit :
[SCORM] SetValue("cmi.core.lesson_status", "incomplete") OK ← à l'ouverture
... (aucun autre SetValue sur lesson_status)❌ Le statut n’est jamais repassé à completed. Le code posait incomplete au départ et l’écran final n’appelait aucune méthode SCORM (juste une animation de félicitations). Bug n°1 confirmé (chapitre 4.4).
Étape 4 — Clôture ? On cherche LMSFinish. Il est câblé sur un bouton « Quitter » que personne ne clique ; à la fermeture d’onglet, rien. ❌ Bug n°2 confirmé (chapitre 4.2).
La correction, minimale et ciblée :
// À la validation du dernier écran (avant l'animation de félicitations) :
setValue(api, "cmi.core.lesson_status", "completed"); // wrapper qui vérifie l'erreur
setValue(api, "cmi.core.score.raw", String(scoreFinal));
setValue(api, "cmi.core.exit", "");
commit(api);
// Clôture garantie, quelle que soit la façon de quitter :
function closeSession() {
if (closeSession.done) return;
closeSession.done = true;
commit(api);
api.LMSFinish("");
}
window.addEventListener("pagehide", closeSession);
window.addEventListener("beforeunload", closeSession);Vérification : on rejoue le module sur SCORM Cloud. Le journal montre SetValue("cmi.core.lesson_status", "completed") suivi d’un Commit et d’un LMSFinish, tous en code "0". La complétion remonte. On réimporte dans le Moodle de l’école : les stagiaires passent en « terminé », les scores apparaissent. Ticket clos.
🧭 Sur FormaCampus — Bilan de la Partie 4 : le module « valide » n’était pas invalide au packaging (Partie 3) — il était incomplet au runtime. Les deux bugs (statut jamais
completed,LMSFinishjamais appelé) sont les deux pièges les plus courants du e-learning, et le wrapper de debug les a rendus visibles en trois lignes de console. Leçon d’équipe adoptée chez FormaCampus : aucun SetValue en direct (tout passe par le wrapper qui lit l’erreur) et tout module validé sur SCORM Cloud avant livraison. Zéro ticket « ça ne remonte pas » depuis.
📚 La spec —
GetLastError/LMSGetLastError,GetErrorString,GetDiagnosticet la table des codes d’erreur sont définis dans le livre RTE de chaque version SCORM (ADL,adlnet.gov). Les codes exacts diffèrent entre 1.2 et 2004 : ne pas coder « en dur » un tableau de codes, mais lireGetErrorString(code)etGetDiagnostic(code)renvoyés par le LMS, qui font autorité pour la version en cours.
✏️ Exercices
Exercice 1 — Le score refusé. Un dev jure que « le score ne remonte pas » malgré ce code 1.2 :
api.LMSSetValue("cmi.core.score.raw", "85%");Sans même lancer, dis ce que renverrait LMSGetLastError juste après, et corrige.
✅ Solution
"85%" n’est pas un nombre : cmi.core.score.raw attend une valeur numérique (ici 0–100). Le LMS rejette la valeur ; LMSSetValue renvoie "false" et LMSGetLastError renvoie 405 (type de donnée incorrect). Rien n’est écrit — d’où « le score ne remonte pas ».
Correction : envoyer un nombre en chaîne, sans symbole.
api.LMSSetValue("cmi.core.score.raw", "85"); // et poser min/max pour lever l'ambiguïté
api.LMSSetValue("cmi.core.score.min", "0");
api.LMSSetValue("cmi.core.score.max", "100");Morale : un wrapper qui lit GetLastError après chaque appel aurait affiché « code 405 » immédiatement.
Exercice 2 — Diagnostiquer un muet. Un module Storyline compilé (pas de source) ne trace rien chez un client. Décris, dans l’ordre, comment tu poses le diagnostic sans modifier le contenu.
✅ Solution
- Console dans l’iframe du SCO : taper
getScormAPI()(ou testerwindow.parent.API/API_1484_11en remontant). S’il n’y a pas d’objet → API introuvable (cross-domain, pop-up, iframe) : on regarde le domaine du SCO vs celui du LMS et le mode d’ouverture. - Si l’API est là, extension de debug SCORM ou journal SCORM Cloud : rejouer le module et regarder les appels — y a-t-il un
Initialize? unSetValuesur le statut ? unTerminate? avec quels codes d’erreur ? - Selon ce qu’on voit, on conclut : jamais initialisé, statut jamais posé, valeur rejetée, ou clôture absente.
Tout ça sans toucher au .zip : on observe le dialogue runtime, on ne le réécrit pas. Si le module est fautif, on renvoie le diagnostic à l’auteur (ou on recompile côté source).
🧠 Quiz de révision
1. Qu’est-ce qu’un « SCO muet » et quelles en sont les deux grandes causes ?
Un module qui s’affiche mais ne communique pas avec le LMS. Deux familles : (1) API jamais trouvée (cross-domain, iframe, pop-up), (2) API trouvée mais mal utilisée (jamais Initialize, statut oublié, Terminate manquant, valeurs rejetées).
2. Pourquoi les méthodes SCORM sont-elles « silencieuses » en cas d’échec ?
Elles ne lèvent pas d’exception : elles renvoient "false"/chaîne vide et posent un code d’erreur qu’il faut aller lire avec GetLastError. Un code qui ne vérifie jamais l’erreur croit « que ça marche » et livre des SCO muets.
3. Quelles trois méthodes utilise-t-on pour diagnostiquer une erreur ?
GetLastError() (le code ; "0" = pas d’erreur), GetErrorString(code) (le libellé standard), GetDiagnostic(code) (le détail propre au LMS). Préfixées LMS en SCORM 1.2.
4. Où faut-il ouvrir la console pour inspecter un SCO ?
Dans le contexte de l’iframe du SCO, pas dans la page LMS : sélectionner la bonne frame dans les outils de développement. Sinon window.parent, getScormAPI() et les logs du SCO ne correspondent pas à ce qu’on croit inspecter.
5. Pourquoi tester un paquet sur SCORM Cloud avant de le livrer ?
C’est un LMS de test de référence qui journalise chaque appel runtime et ses erreurs. Si complétion et score y remontent, on a éliminé l’essentiel des tickets « ça ne marche pas » ; un éventuel problème restant sera alors du côté configuration du LMS client, pas du contenu.
Partie suivante : Partie 5 — SCORM 2004 : séquencement — au-delà d’un SCO isolé, comment SCORM 2004 orchestre plusieurs activités : objectifs, règles de séquencement, rollup et navigation.