Persist & restore
A <qti-test> has one property and one event for this. Where you store the state is up to you.
const qtiTest = document.querySelector('qti-test');
// Restore. Safe to assign before the test has loaded.const saved = await load(candidateId);if (saved) qtiTest.state = saved;
// Save. Fires on every change, so debounce writes to a backend.qtiTest.addEventListener('qti-state-changed', e => save(candidateId, e.detail));Pass the same seed in qtiContext when resuming, or items and choices come back in a different
order. Config, qtiContext and view are not part of the state; you supply them on every load.
Assign the state before the test loads where you can: a PCI that is already on screen only picks up a new state when its item loads again.
The state
interface QtiTestState { version: 1; test: { items: { identifier: string; // the qti-assessment-item-ref identifier variables: { identifier: string; type?: 'response' | 'outcome' | 'template' | 'context'; value: string | string[] | null; }[]; state?: Record<string, string | null>; // per-interaction state, e.g. a PCI's getState() }[]; outcomes: { identifier: string; value: string | string[] | null }[]; }; session: { navPartId?: string | null; navSectionId?: string | null; navItemRefId?: string | null };}Values only: no answer key, no declaration metadata. Treat it as opaque and store it as-is. A state
with an unknown version is ignored with a console warning.
Setting a single value
To change values on one item from outside the session, such as a score from manual scoring, use
updateItemVariables. It also fires qti-state-changed.
qtiTest.updateItemVariables('item-ref-1', [{ identifier: 'SCORE', value: '1' }]);Replaces
qti-test-context-updated and reading or writing testContext for persistence are deprecated.
They expose the full test context, including the answer key.