Delivery: SCORM 1.2 and the LMS emulator
On this page
Package
elabora build produces dist/<id>-<version>-scorm12.zip, .report.json (machine-readable build report) and .proof.md (proof dossier). The ZIP is deterministic and contains:
imsmanifest.xml SCORM 1.2, one SCO, schemaversion 1.2, masteryscore if set
index.html launch file
elabora/player.js|css player (Apache-2.0)
elabora/course.js compiled course (window.ELABORA_COURSE)
elabora/object-frame.js sandbox bridge for free objects
elabora/vendor/three-r186.js three.js r186 (MIT) when a free object needs it
assets/… media referenced by blocks only
objects/<id>/… compiled free objects and their frames
Runtime behaviour
| Topic | Behaviour | LMS checklist |
|---|---|---|
| API discovery | Parent frames (up to 12 levels), then top.opener, top.opener.opener and their parents. The content never opens a window. |
6 |
| Initialise | Once. Never after LMSFinish (back-forward cache restore shows a message instead). |
5 |
| Commits | After every significant change (coalesced in the same tick), on visibilitychange: hidden, on pagehide, every heartbeatSeconds. Identical values are not re-sent. |
1, 12 |
| Exit | cmi.core.exit = suspend from the start; "" after completion only if exitAfterCompletion: normal. Never logout. |
1 |
| Quit | Visible button: commit, LMSFinish, end message. |
1 |
| Status | reportAs pair; final status sent when decided; no regression; score 0–100 sent when every scored item is decided. |
13 |
| Interactions | Quizzes and decisions as choice with letters; fill-ins one per blank (fill-in typed, choice from menus and words to place), sorts as matching (1.a,2.c) or sequencing (b,a,c), reflections as fill-in (255 characters, one line) or likert; free-object events as declared (choices with non-letter responses become performance). |
— |
| Time | cmi.core.session_time (HHHH:MM:SS.SS) at every commit. |
14 |
| State | Compact keyed JSON; worst case checked at build against the strictest profile. | 3, 7 |
Not in 0.1: cmi.objectives (SuccessFactors requires pre-existing ids), SCORM 2004, cmi5, xAPI, LTI, dispatch/proxy mode.
LMS profiles
profiles/*.json (schema lms-profile) document, with sources and dates, what each LMS accepts: standards, launch topology, suspend_data limits and enforcement, mastery override, package limits, update family, cookies. Values marked unknown are listed in toMeasure and printed as warnings: they must be measured in a real instance. Run elabora profiles.
Emulator (elabora test)
For each profile × launch mode × browser variant, a fake LMS page (scorm-again Scorm12API plus the profile quirks) launches the package and runs:
- API found in this topology; one
LMSInitialize; nounload/beforeunloadlistener and no synchronous XHR in the content. - Navigation with the real Next button; abrupt close (no beforeunload) — the LMS must already hold
lesson_location,suspend_dataandexit = suspend. - Relaunch: the learner is back on the same screen.
- Completion through the real UI (correct answers): expected status and score 100.
- Quit:
LMSFinishcalled; noLMSSetValuerejected (e.g. suspend data above the profile limit). - Review session after completion: the status does not regress.
- axe-core (WCAG 2.2 A/AA) on every screen before and after answering (first run only).
Chromium runs twice: with unload deprecated and with it allowed. Firefox and WebKit run with --browser firefox,webkit after npx playwright install firefox webkit. Any SCORM 1.2 ZIP (Storyline, Rise…) can be checked with elabora test --package file.zip (generic checks: initialise, unload listeners, rejected values, data committed before close).
Preview (elabora preview)
The preview serves the course standalone and in the emulator’s page, which plays the course’s LMS (delivery.lms, the first; --profile for another) and launches it in a frame as that LMS does (an LMS that opens a window of its own is played in a frame too). Pages reload after each rebuild: in the emulator, its page reloads, as a learner relaunching, back on the screen shown and its step, at the same scroll (a screen taken out of the course, or renamed without its key: the one now at its place). The preview keeps its own lock while it runs: elabora.lock.json as it was, then the keys of what is added since. It writes no lock (only a delivery does), and a screen added between two rebuilds, before others that have no key in the project’s lock yet, leaves their keys, and the learner’s place and answers, as they were. --navigation free builds it with every screen open, whatever the course’s rules (the web target’s free navigation, D41); a running preview switches with setNavigation (the desktop app’s « Déverrouiller la progression »).
Shown in a frame (the desktop app’s Aperçu), the emulator’s page tells its parent page what happens, with postMessage messages { type: 'elabora/lms', kind, … }:
kind |
When | What |
|---|---|---|
session |
the page starts | session: the registration, launch mode, profile (id, name, its own launch mode), suspend_data limit and enforcement, entry (ab-initio or resume) and what the LMS gave back (location, status, suspendData, or null) |
call |
every API call | call: its time, method, arguments and result, as window.__lmsLog keeps them |
course |
each course the page shows | course: the course data (window.__elabora.course) |
state |
every commit, and when asked | state: window.__elabora.state() (screen, step, status, score, suspendData, the variables’ values) |
events |
events run | entries: new entries of window.__elabora.trace (the trigger’s type and what happened, the screen, and each event whose trigger matched, with whether its conditions held) |
The parent sets a variable with { type: 'elabora/lms-ask', ask: 'set-variable', id, value }: its « when it changes » events run as if an event had set it (__elabora.setVariable).
What the emulator cannot prove
It reproduces documented behaviour on top of scorm-again; it is not the LMS. Keep the last two levels of the test pyramid: a smoke test in SCORM Cloud and a test learner in the client’s LMS, then update the profile’s toMeasure fields with what you measured.