Formation SCORM pour DĂ©veloppeurs Full-Stack¶
🎯 Objectifs de la Formation¶
Cette formation technique s'adresse aux développeurs full-stack souhaitant maîtriser la création de contenus SCORM, avec un focus particulier sur : - La création de packs multi-SCO (Sharable Content Objects) - L'export STL (SCORM Test and Launch) - L'intégration avec les LMS (Learning Management Systems) - La conformité aux standards SCORM 1.2 et 2004
📚 Table des Matières¶
- Introduction au SCORM
- Architecture et Standards
- Création de Packs Multi-SCO
- Export STL et Conformité
- Intégration LMS
- Exemples Pratiques
- Bonnes Pratiques
- Troubleshooting
Introduction au SCORM¶
Qu'est-ce que le SCORM ?¶
SCORM (Sharable Content Object Reference Model) est un ensemble de standards techniques qui permettent aux contenus d'apprentissage d'être partagés entre différents systèmes de gestion de l'apprentissage (LMS).
Versions du SCORM¶
SCORM 1.2 (2001)¶
- Avantages : Simplicité, large compatibilité
- Inconvénients : Fonctionnalités limitées
- Utilisation : Projets simples, compatibilité maximale
SCORM 2004 (4ème Ă©dition - 2009)¶
- Avantages : Fonctionnalités avancées, séquençage
- Inconvénients : Complexité, compatibilité variable
- Utilisation : Projets complexes, parcours adaptatifs
Composants ClĂ©s¶
SCO (Sharable Content Object)¶
// Structure d'un SCO
const sco = {
identifier: "sco_001",
title: "Introduction au JavaScript",
launch: "index.html",
resources: ["assets/", "scripts/", "styles/"],
metadata: {
description: "Module d'introduction au JavaScript",
duration: "PT30M",
difficulty: "beginner"
}
};
Asset¶
- Ressources statiques (images, CSS, JS, vidéos)
- Non traçables individuellement
- Réutilisables entre SCOs
Manifest¶
- Fichier XML décrivant le package
- Structure et organisation du contenu
- Métadonnées et prérequis
Architecture et Standards¶
Structure d'un Package SCORM¶
mon_package_scorm/
├── imsmanifest.xml # Manifest principal
├── sco1/ # Premier SCO
│ ├── index.html
│ ├── assets/
│ └── scripts/
├── sco2/ # Deuxième SCO
│ ├── index.html
│ └── assets/
└── shared/ # Assets partagés
├── styles/
└── images/
Manifest XML (SCORM 1.2)¶
<?xml version="1.0" encoding="UTF-8"?>
<manifest identifier="package_001" version="1.0"
xmlns="http://www.imsproject.org/xsd/imscp_rootv1p1p2"
xmlns:adlcp="http://www.adlnet.org/xsd/adlcp_rootv1p2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.imsproject.org/xsd/imscp_rootv1p1p2 imscp_rootv1p1p2.xsd
http://www.adlnet.org/xsd/adlcp_rootv1p2 adlcp_rootv1p2.xsd">
<metadata>
<schema>ADL SCORM</schema>
<schemaversion>1.2</schemaversion>
<adlcp:location>metadata.xml</adlcp:location>
</metadata>
<organizations default="default_org">
<organization identifier="default_org">
<title>Formation Développement Web</title>
<item identifier="sco1" identifierref="sco1_ref">
<title>Introduction HTML</title>
<adlcp:masteryscore>80</adlcp:masteryscore>
</item>
<item identifier="sco2" identifierref="sco2_ref">
<title>Introduction CSS</title>
<adlcp:masteryscore>75</adlcp:masteryscore>
</item>
</organization>
</organizations>
<resources>
<resource identifier="sco1_ref" type="webcontent"
adlcp:scormtype="sco" href="sco1/index.html">
<file href="sco1/index.html"/>
<file href="sco1/scripts/scorm_api.js"/>
<file href="sco1/styles/main.css"/>
</resource>
<resource identifier="sco2_ref" type="webcontent"
adlcp:scormtype="sco" href="sco2/index.html">
<file href="sco2/index.html"/>
<file href="sco2/scripts/scorm_api.js"/>
<file href="sco2/styles/main.css"/>
</resource>
</resources>
</manifest>
API SCORM¶
Initialisation de l'API¶
class SCORMAPI {
constructor() {
this.api = null;
this.connected = false;
this.init();
}
init() {
// Recherche de l'API SCORM
if (window.parent && window.parent.API_1484_11) {
this.api = window.parent.API_1484_11;
} else if (window.parent && window.parent.API) {
this.api = window.parent.API;
} else if (window.API_1484_11) {
this.api = window.API_1484_11;
} else if (window.API) {
this.api = window.API;
}
if (this.api) {
this.connected = this.api.Initialize("");
}
}
// Méthodes de base
getValue(element) {
if (this.connected) {
return this.api.GetValue(element);
}
return null;
}
setValue(element, value) {
if (this.connected) {
return this.api.SetValue(element, value);
}
return false;
}
commit() {
if (this.connected) {
return this.api.Commit("");
}
return false;
}
terminate() {
if (this.connected) {
this.api.Terminate("");
this.connected = false;
}
}
}
CrĂ©ation de Packs Multi-SCO¶
Architecture Multi-SCO¶
Un pack multi-SCO permet de créer des parcours d'apprentissage complexes avec : - Séquençage : Ordre de présentation des SCOs - Prérequis : Conditions d'accès aux SCOs - Progression : Suivi de l'avancement global - Navigation : Contrôle du parcours
Structure d'un Pack Multi-SCO¶
// Configuration du pack multi-SCO
const multiSCOConfig = {
packageId: "formation_dev_web",
version: "1.0",
title: "Formation Développement Web Complète",
scos: [
{
id: "html_basics",
title: "HTML - Les Bases",
launch: "html/index.html",
prerequisites: [],
masteryScore: 80,
maxTimeAllowed: "PT45M"
},
{
id: "css_basics",
title: "CSS - Les Bases",
launch: "css/index.html",
prerequisites: ["html_basics"],
masteryScore: 75,
maxTimeAllowed: "PT60M"
},
{
id: "javascript_basics",
title: "JavaScript - Les Bases",
launch: "js/index.html",
prerequisites: ["html_basics", "css_basics"],
masteryScore: 85,
maxTimeAllowed: "PT90M"
}
],
sequencing: {
controlMode: "choice",
flowMode: "forward_only",
rollupRules: {
rollupObjectiveSatisfied: true,
rollupProgressCompletion: true
}
}
};
Gestion de la Progression¶
class MultiSCOManager {
constructor(config) {
this.config = config;
this.currentSCO = null;
this.completedSCOs = [];
this.progress = 0;
}
// Vérification des prérequis
canAccessSCO(scoId) {
const sco = this.config.scos.find(s => s.id === scoId);
if (!sco) return false;
return sco.prerequisites.every(prereq =>
this.completedSCOs.includes(prereq)
);
}
// Calcul de la progression globale
calculateProgress() {
const totalSCOs = this.config.scos.length;
const completed = this.completedSCOs.length;
this.progress = Math.round((completed / totalSCOs) * 100);
return this.progress;
}
// Sauvegarde de la progression
saveProgress() {
const progressData = {
completedSCOs: this.completedSCOs,
progress: this.progress,
lastAccessed: new Date().toISOString()
};
localStorage.setItem('scorm_progress', JSON.stringify(progressData));
this.api.setValue('cmi.core.lesson_status', 'incomplete');
this.api.setValue('cmi.core.score.raw', this.progress);
this.api.commit();
}
// Chargement de la progression
loadProgress() {
const saved = localStorage.getItem('scorm_progress');
if (saved) {
const progressData = JSON.parse(saved);
this.completedSCOs = progressData.completedSCOs || [];
this.progress = progressData.progress || 0;
}
}
}
Navigation entre SCOs¶
class SCONavigator {
constructor(manager) {
this.manager = manager;
this.createNavigation();
}
createNavigation() {
const nav = document.createElement('div');
nav.className = 'sco-navigation';
this.config.scos.forEach((sco, index) => {
const button = document.createElement('button');
button.textContent = sco.title;
button.disabled = !this.manager.canAccessSCO(sco.id);
if (this.manager.completedSCOs.includes(sco.id)) {
button.classList.add('completed');
}
button.addEventListener('click', () => {
this.launchSCO(sco.id);
});
nav.appendChild(button);
});
document.body.appendChild(nav);
}
launchSCO(scoId) {
const sco = this.config.scos.find(s => s.id === scoId);
if (sco && this.manager.canAccessSCO(scoId)) {
// Sauvegarde avant changement
this.manager.saveProgress();
// Lancement du SCO
window.location.href = sco.launch;
}
}
}
Export STL et ConformitĂ©¶
Qu'est-ce que l'Export STL ?¶
L'Export STL (SCORM Test and Launch) est un processus de validation et de test qui garantit : - La conformité aux standards SCORM - La compatibilité avec les LMS - La qualité technique du package - La traçabilité des données
Outils de Validation¶
SCORM Cloud (Rustici)¶
# Installation de l'outil de validation
npm install -g scorm-cloud-validator
# Validation d'un package
scorm-validate ./mon_package_scorm/
ADL Conformance Test Suite¶
# Test de conformité SCORM 1.2
adl-test-suite --version 1.2 --package ./mon_package_scorm/
# Test de conformité SCORM 2004
adl-test-suite --version 2004 --package ./mon_package_scorm/
Script d'Export STL AutomatisĂ©¶
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
class SCORMExporter {
constructor(packagePath, outputPath) {
this.packagePath = packagePath;
this.outputPath = outputPath;
this.validationResults = [];
}
// Validation du manifest
validateManifest() {
const manifestPath = path.join(this.packagePath, 'imsmanifest.xml');
if (!fs.existsSync(manifestPath)) {
throw new Error('Manifest non trouvé');
}
// Validation XML
const manifest = fs.readFileSync(manifestPath, 'utf8');
const xmlValidation = this.validateXML(manifest);
// Validation SCORM
const scormValidation = this.validateSCORMStructure(manifest);
return {
xml: xmlValidation,
scorm: scormValidation,
valid: xmlValidation.valid && scormValidation.valid
};
}
// Test de lancement
testLaunch() {
const scos = this.extractSCOs();
const results = [];
scos.forEach(sco => {
try {
// Test de chargement
const launchTest = this.testSCOLaunch(sco);
results.push({
sco: sco.id,
launch: launchTest.success,
errors: launchTest.errors
});
} catch (error) {
results.push({
sco: sco.id,
launch: false,
errors: [error.message]
});
}
});
return results;
}
// Génération du rapport STL
generateSTLReport() {
const manifestValidation = this.validateManifest();
const launchTests = this.testLaunch();
const report = {
timestamp: new Date().toISOString(),
package: this.packagePath,
manifest: manifestValidation,
launchTests: launchTests,
overallStatus: this.calculateOverallStatus(manifestValidation, launchTests),
recommendations: this.generateRecommendations(manifestValidation, launchTests)
};
const reportPath = path.join(this.outputPath, 'stl-report.json');
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
return report;
}
// Export du package validé
exportValidatedPackage() {
const report = this.generateSTLReport();
if (report.overallStatus === 'PASS') {
const exportPath = path.join(this.outputPath, 'validated-package.zip');
this.createZipPackage(exportPath);
console.log(`Package validé exporté vers : ${exportPath}`);
} else {
console.log('Package non conforme, voir le rapport STL pour les détails');
}
return report;
}
}
MĂ©tadonnĂ©es de ConformitĂ©¶
<!-- Exemple de métadonnées complètes pour l'export STL -->
<metadata>
<schema>ADL SCORM</schema>
<schemaversion>1.2</schemaversion>
<!-- Informations générales -->
<title>Formation Développement Web - Pack Multi-SCO</title>
<description>Formation complète en développement web avec HTML, CSS et JavaScript</description>
<keywords>développement, web, HTML, CSS, JavaScript, SCORM</keywords>
<!-- Informations techniques -->
<technical>
<format>text/html</format>
<size>15728640</size>
<location>index.html</location>
<requirement>
<type>browser</type>
<name>Chrome</name>
<minimum_version>90.0</minimum_version>
</requirement>
</technical>
<!-- Informations pédagogiques -->
<educational>
<interactivitytype>active</interactivitytype>
<learningresourcetype>course</learningresourcetype>
<interactivitylevel>high</interactivitylevel>
<semanticdensity>medium</semanticdensity>
<intendedenduserrole>learner</intendedenduserrole>
<context>professional</context>
<typicalagerange>18-65</typicalagerange>
<difficulty>intermediate</difficulty>
<typicallearningtime>
<datetime>PT180M</datetime>
</typicallearningtime>
</educational>
<!-- Informations de conformité -->
<conformance>
<scorm_version>1.2</scorm_version>
<api_support>true</api_support>
<sequencing_support>false</sequencing_support>
<rollup_support>true</rollup_support>
<stl_validated>true</stl_validated>
<validation_date>2024-01-15T10:30:00Z</validation_date>
</conformance>
</metadata>
IntĂ©gration LMS¶
Configuration LMS¶
Moodle¶
// Configuration SCORM dans Moodle
$scorm_config = [
'scormtype' => 'local',
'packageurl' => 'https://lms.example.com/scorm/package.zip',
'maxgrade' => 100,
'grademethod' => SCORM_GRADEHIGHEST,
'maxattempt' => 3,
'whatgrade' => SCORM_GRADEHIGHEST,
'forcenewattempt' => SCORM_FORCENEWATTEMPT,
'lastattemptlock' => SCORM_LOCKONFINAL,
'masteryoverride' => SCORM_MASTERYOVERRIDE,
'displaycoursestructure' => 1,
'displayattemptstatus' => 1,
'displaycoursestructure' => 1,
'popup' => 1,
'width' => 1000,
'height' => 600,
'window' => 'popup'
];
Canvas¶
// Configuration SCORM dans Canvas
const canvasSCORMConfig = {
external_tool: {
name: "SCORM Package",
url: "https://canvas.example.com/scorm/launch",
consumer_key: "scorm_consumer_key",
shared_secret: "scorm_shared_secret",
privacy_level: "public",
custom_fields: {
scorm_package_id: "package_001",
scorm_version: "1.2",
max_attempts: 3,
mastery_score: 80
}
}
};
API d'IntĂ©gration¶
class LMSIntegration {
constructor(lmsType, config) {
this.lmsType = lmsType;
this.config = config;
this.api = this.initializeAPI();
}
initializeAPI() {
switch(this.lmsType) {
case 'moodle':
return new MoodleAPI(this.config);
case 'canvas':
return new CanvasAPI(this.config);
case 'blackboard':
return new BlackboardAPI(this.config);
default:
throw new Error('LMS non supporté');
}
}
// Upload du package SCORM
async uploadPackage(packagePath) {
const formData = new FormData();
formData.append('package', fs.createReadStream(packagePath));
formData.append('scorm_version', '1.2');
formData.append('auto_launch', 'true');
const response = await this.api.uploadSCORM(formData);
return response.package_id;
}
// Configuration du cours
async configureCourse(packageId, courseConfig) {
const courseData = {
package_id: packageId,
title: courseConfig.title,
description: courseConfig.description,
max_attempts: courseConfig.maxAttempts || 3,
mastery_score: courseConfig.masteryScore || 80,
due_date: courseConfig.dueDate,
available_from: courseConfig.availableFrom,
available_until: courseConfig.availableUntil
};
return await this.api.createCourse(courseData);
}
// Récupération des rapports
async getReports(courseId) {
const reports = await this.api.getSCORMReports(courseId);
return this.formatReports(reports);
}
formatReports(rawReports) {
return rawReports.map(report => ({
learner: report.learner_name,
sco: report.sco_title,
score: report.score,
status: report.completion_status,
timeSpent: report.total_time,
attempts: report.attempt_count,
lastAccess: report.last_access_date
}));
}
}
Exemples Pratiques¶
Exemple 1 : Pack Multi-SCO Simple¶
<!-- sco1/index.html - Introduction HTML -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Introduction HTML</title>
<script src="../shared/scripts/scorm_api.js"></script>
<link rel="stylesheet" href="../shared/styles/main.css">
</head>
<body>
<div class="sco-container">
<header>
<h1>Introduction au HTML</h1>
<div class="progress-bar">
<div class="progress" id="progress"></div>
</div>
</header>
<main>
<section class="content">
<h2>Qu'est-ce que le HTML ?</h2>
<p>Le HTML (HyperText Markup Language) est le langage de balisage standard pour créer des pages web.</p>
<div class="interactive-content">
<h3>Exercice Pratique</h3>
<div class="code-editor">
<textarea id="html-code" placeholder="Écrivez votre code HTML ici...">
<!DOCTYPE html>
<html>
<head>
<title>Ma première page</title>
</head>
<body>
<h1>Bonjour le monde !</h1>
</body>
</html>
</textarea>
</div>
<button onclick="validateHTML()">Valider</button>
<div id="validation-result"></div>
</div>
</section>
<section class="quiz">
<h3>Quiz de Compréhension</h3>
<div class="question">
<p>Quelle balise HTML est utilisée pour créer un titre principal ?</p>
<label><input type="radio" name="q1" value="a"> <title></label>
<label><input type="radio" name="q1" value="b"> <h1></label>
<label><input type="radio" name="q1" value="c"> <header></label>
</div>
<button onclick="submitQuiz()">Soumettre</button>
</section>
</main>
<footer>
<button onclick="previousSCO()" id="prev-btn" disabled>Précédent</button>
<button onclick="nextSCO()" id="next-btn">Suivant</button>
</footer>
</div>
<script>
const scorm = new SCORMAPI();
let currentProgress = 0;
let quizScore = 0;
// Initialisation
window.onload = function() {
loadProgress();
updateProgress();
};
// Sauvegarde avant fermeture
window.onbeforeunload = function() {
saveProgress();
};
function loadProgress() {
const saved = scorm.getValue('cmi.core.lesson_status');
if (saved === 'completed') {
currentProgress = 100;
document.getElementById('next-btn').disabled = false;
}
}
function updateProgress() {
const progressBar = document.getElementById('progress');
progressBar.style.width = currentProgress + '%';
}
function validateHTML() {
const code = document.getElementById('html-code').value;
const result = document.getElementById('validation-result');
// Validation simple (à améliorer avec une vraie validation HTML)
if (code.includes('<h1>') && code.includes('</h1>')) {
result.innerHTML = '<span class="success">âś“ Code HTML valide !</span>';
currentProgress = Math.max(currentProgress, 50);
updateProgress();
} else {
result.innerHTML = '<span class="error">✗ Code HTML invalide. Vérifiez vos balises.</span>';
}
}
function submitQuiz() {
const answer = document.querySelector('input[name="q1"]:checked');
const result = document.getElementById('validation-result');
if (answer && answer.value === 'b') {
result.innerHTML = '<span class="success">✓ Bonne réponse !</span>';
quizScore = 100;
currentProgress = Math.max(currentProgress, 100);
updateProgress();
document.getElementById('next-btn').disabled = false;
} else {
result.innerHTML = '<span class="error">✗ Mauvaise réponse. La bonne réponse est <h1>.</span>';
}
}
function saveProgress() {
scorm.setValue('cmi.core.lesson_status', currentProgress === 100 ? 'completed' : 'incomplete');
scorm.setValue('cmi.core.score.raw', currentProgress);
scorm.setValue('cmi.core.total_time', 'PT15M');
scorm.commit();
}
function nextSCO() {
saveProgress();
window.location.href = '../css/index.html';
}
function previousSCO() {
// Pas de SCO précédent dans cet exemple
}
</script>
</body>
</html>
Exemple 2 : Gestionnaire de Progression AvancĂ©¶
// shared/scripts/progress_manager.js
class AdvancedProgressManager {
constructor() {
this.scorm = new SCORMAPI();
this.scoData = {
currentSCO: this.getCurrentSCO(),
startTime: new Date(),
interactions: [],
objectives: [],
progress: 0
};
}
// Suivi des interactions
trackInteraction(id, type, description, response, result) {
const interaction = {
id: id,
type: type,
description: description,
response: response,
result: result,
timestamp: new Date().toISOString()
};
this.scoData.interactions.push(interaction);
this.saveInteraction(interaction);
}
// Gestion des objectifs
setObjective(objectiveId, score, status) {
const objective = {
id: objectiveId,
score: score,
status: status,
timestamp: new Date().toISOString()
};
this.scoData.objectives.push(objective);
this.saveObjective(objective);
}
// Calcul de la progression
calculateProgress() {
const totalObjectives = this.scoData.objectives.length;
const completedObjectives = this.scoData.objectives.filter(obj =>
obj.status === 'completed' || obj.status === 'passed'
).length;
this.scoData.progress = totalObjectives > 0 ?
Math.round((completedObjectives / totalObjectives) * 100) : 0;
return this.scoData.progress;
}
// Sauvegarde complète
saveCompleteProgress() {
const progress = this.calculateProgress();
const timeSpent = this.calculateTimeSpent();
// Sauvegarde SCORM
this.scorm.setValue('cmi.core.lesson_status',
progress === 100 ? 'completed' : 'incomplete');
this.scorm.setValue('cmi.core.score.raw', progress);
this.scorm.setValue('cmi.core.total_time', timeSpent);
this.scorm.setValue('cmi.core.entry', 'resume');
// Sauvegarde des interactions
this.scoData.interactions.forEach((interaction, index) => {
this.scorm.setValue(`cmi.interactions.${index}.id`, interaction.id);
this.scorm.setValue(`cmi.interactions.${index}.type`, interaction.type);
this.scorm.setValue(`cmi.interactions.${index}.description`, interaction.description);
this.scorm.setValue(`cmi.interactions.${index}.learner_response`, interaction.response);
this.scorm.setValue(`cmi.interactions.${index}.result`, interaction.result);
});
this.scorm.commit();
}
calculateTimeSpent() {
const now = new Date();
const diff = now - this.scoData.startTime;
const minutes = Math.floor(diff / 60000);
return `PT${minutes}M`;
}
}
Bonnes Pratiques¶
DĂ©veloppement SCORM¶
1. Structure du Code¶
// Organisation recommandée des fichiers
const scormStructure = {
'shared/': {
'scripts/': [
'scorm_api.js', // API SCORM de base
'progress_manager.js', // Gestionnaire de progression
'navigation.js', // Navigation entre SCOs
'utils.js' // Utilitaires communs
],
'styles/': [
'main.css', // Styles principaux
'scorm.css', // Styles spécifiques SCORM
'responsive.css' // Responsive design
],
'assets/': [
'images/', // Images partagées
'fonts/', // Polices
'data/' // Données statiques
]
},
'sco1/': {
'index.html', // Point d'entrée
'content.html', // Contenu principal
'quiz.html', // Quiz spécifique
'scripts/': [
'sco1-specific.js' // Scripts spécifiques au SCO
]
}
};
2. Gestion des Erreurs¶
class SCORMErrorHandler {
static handleAPIError(error, context) {
console.error(`Erreur SCORM dans ${context}:`, error);
// Fallback local
this.saveToLocalStorage(context, error);
// Notification utilisateur
this.showUserNotification('Une erreur est survenue. Vos progrès sont sauvegardés localement.');
}
static saveToLocalStorage(context, data) {
const key = `scorm_fallback_${context}`;
const existing = JSON.parse(localStorage.getItem(key) || '[]');
existing.push({
timestamp: new Date().toISOString(),
data: data
});
localStorage.setItem(key, JSON.stringify(existing));
}
}
3. Performance et Optimisation¶
// Optimisation des appels API SCORM
class OptimizedSCORMAPI extends SCORMAPI {
constructor() {
super();
this.pendingChanges = new Map();
this.commitTimeout = null;
}
setValue(element, value) {
// Mise en cache des changements
this.pendingChanges.set(element, value);
// Commit différé (évite trop d'appels API)
this.scheduleCommit();
}
scheduleCommit() {
if (this.commitTimeout) {
clearTimeout(this.commitTimeout);
}
this.commitTimeout = setTimeout(() => {
this.commitPendingChanges();
}, 1000); // Commit toutes les secondes
}
commitPendingChanges() {
for (const [element, value] of this.pendingChanges) {
super.setValue(element, value);
}
this.pendingChanges.clear();
super.commit();
}
}
Tests et Validation¶
1. Tests Unitaires¶
// Tests pour les fonctionnalités SCORM
describe('SCORM API Tests', () => {
let scormAPI;
beforeEach(() => {
scormAPI = new SCORMAPI();
});
test('should initialize API connection', () => {
expect(scormAPI.connected).toBe(true);
});
test('should save progress correctly', () => {
scormAPI.setValue('cmi.core.lesson_status', 'completed');
scormAPI.setValue('cmi.core.score.raw', 85);
expect(scormAPI.getValue('cmi.core.lesson_status')).toBe('completed');
expect(scormAPI.getValue('cmi.core.score.raw')).toBe('85');
});
test('should handle API errors gracefully', () => {
// Simulation d'erreur API
window.parent.API = null;
const newAPI = new SCORMAPI();
expect(newAPI.connected).toBe(false);
expect(newAPI.setValue('test', 'value')).toBe(false);
});
});
2. Tests d'IntĂ©gration¶
// Tests d'intégration multi-SCO
describe('Multi-SCO Integration Tests', () => {
test('should navigate between SCOs correctly', async () => {
const manager = new MultiSCOManager(testConfig);
const navigator = new SCONavigator(manager);
// Test de navigation
await navigator.launchSCO('sco1');
expect(manager.currentSCO).toBe('sco1');
await navigator.launchSCO('sco2');
expect(manager.currentSCO).toBe('sco2');
});
test('should respect prerequisites', () => {
const manager = new MultiSCOManager(testConfig);
// SCO2 nécessite SCO1
expect(manager.canAccessSCO('sco2')).toBe(false);
// Marquer SCO1 comme terminé
manager.completedSCOs.push('sco1');
expect(manager.canAccessSCO('sco2')).toBe(true);
});
});
Troubleshooting¶
Problèmes Courants¶
1. API SCORM Non DĂ©tectĂ©e¶
// Diagnostic de l'API SCORM
function diagnoseSCORMAPI() {
const diagnosis = {
apiFound: false,
apiType: null,
errors: []
};
try {
if (window.parent && window.parent.API_1484_11) {
diagnosis.apiFound = true;
diagnosis.apiType = 'SCORM 2004';
} else if (window.parent && window.parent.API) {
diagnosis.apiFound = true;
diagnosis.apiType = 'SCORM 1.2';
} else {
diagnosis.errors.push('Aucune API SCORM détectée');
}
} catch (error) {
diagnosis.errors.push(`Erreur de détection: ${error.message}`);
}
return diagnosis;
}
2. Problèmes de Sauvegarde¶
// Diagnostic des problèmes de sauvegarde
function diagnoseSaveIssues() {
const issues = [];
// Vérification de la connexion API
if (!scorm.connected) {
issues.push('API SCORM non connectée');
}
// Vérification des valeurs
const status = scorm.getValue('cmi.core.lesson_status');
if (!status) {
issues.push('Impossible de récupérer le statut');
}
// Test de sauvegarde
const testSave = scorm.setValue('cmi.core.lesson_status', 'incomplete');
if (!testSave) {
issues.push('Impossible de sauvegarder les données');
}
return issues;
}
3. Problèmes de Navigation¶
// Diagnostic des problèmes de navigation
function diagnoseNavigationIssues() {
const issues = [];
// Vérification des SCOs
const scos = document.querySelectorAll('[data-sco-id]');
if (scos.length === 0) {
issues.push('Aucun SCO trouvé dans le DOM');
}
// Vérification des prérequis
const manager = new MultiSCOManager(config);
config.scos.forEach(sco => {
if (!manager.canAccessSCO(sco.id)) {
issues.push(`SCO ${sco.id} non accessible (prérequis manquants)`);
}
});
return issues;
}
Solutions RecommandĂ©es¶
1. Fallback Local¶
// Système de fallback pour la sauvegarde locale
class LocalFallback {
static saveProgress(data) {
try {
localStorage.setItem('scorm_progress', JSON.stringify(data));
return true;
} catch (error) {
console.error('Erreur de sauvegarde locale:', error);
return false;
}
}
static loadProgress() {
try {
const data = localStorage.getItem('scorm_progress');
return data ? JSON.parse(data) : null;
} catch (error) {
console.error('Erreur de chargement local:', error);
return null;
}
}
static syncWithSCORM(scormAPI) {
const localData = this.loadProgress();
if (localData && scormAPI.connected) {
// Synchronisation des données locales avec SCORM
Object.keys(localData).forEach(key => {
scormAPI.setValue(key, localData[key]);
});
scormAPI.commit();
}
}
}
2. Monitoring et Logs¶
// Système de monitoring pour le debugging
class SCORMMonitor {
constructor() {
this.logs = [];
this.startMonitoring();
}
startMonitoring() {
// Monitoring des appels API
const originalSetValue = SCORMAPI.prototype.setValue;
SCORMAPI.prototype.setValue = function(element, value) {
this.log('API_SET_VALUE', { element, value });
return originalSetValue.call(this, element, value);
};
// Monitoring des erreurs
window.addEventListener('error', (event) => {
this.log('ERROR', {
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno
});
});
}
log(type, data) {
this.logs.push({
timestamp: new Date().toISOString(),
type: type,
data: data
});
}
getLogs() {
return this.logs;
}
exportLogs() {
const logs = this.getLogs();
const blob = new Blob([JSON.stringify(logs, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'scorm-logs.json';
a.click();
}
}
🎯 Conclusion¶
Cette formation vous a fourni les connaissances techniques nécessaires pour :
- Comprendre l'architecture SCORM et ses standards
- Créer des packs multi-SCO complexes et fonctionnels
- Exporter des packages conformes avec validation STL
- Intégrer vos contenus avec les LMS principaux
- Déboguer et résoudre les problèmes courants
Prochaines Étapes¶
- Pratique : Créez votre premier pack multi-SCO
- Test : Validez avec les outils STL
- Déploiement : Intégrez dans un LMS de test
- Optimisation : Améliorez les performances et l'UX
Ressources SupplĂ©mentaires¶
- Documentation officielle SCORM
- SCORM Cloud - Outils de test
- Moodle SCORM Documentation
- Canvas SCORM Integration
Cette formation est un guide technique complet pour les développeurs souhaitant maîtriser la création de contenus SCORM professionnels.