Deterministic replay testing
Record normalized lifecycle events, replay them through a ManualClock, and assert announcement or diagnostic transcripts with optional Vitest matchers.
Use the testing entry from core
Testing helpers ship with @generative-a11y/core. Import the testing entry only in test code, and install Vitest only when the project uses the semantic matchers.
npm install @generative-a11y/core
npm install --save-dev vitestRecord and replay normalized events
recordRuntime captures only events sent through the dispatch target it returns. Fixtures use a versioned JSON envelope, relative timestamps, and array order for events with the same time.
import { ManualClock, createRuntime } from "@generative-a11y/core";
import { recordRuntime, replayEvents } from "@generative-a11y/core/testing";
const clock = new ManualClock(0);
const runtime = createRuntime({ clock });
const recording = recordRuntime({ runtime, clock });
recording.runtime.dispatch({
type: "response.started",
responseId: "report",
});
clock.advanceBy(100);
recording.runtime.dispatch({
type: "response.interrupted",
responseId: "report",
});
const fixture = recording.fixture();
const replayClock = new ManualClock(fixture.startAt);
const replayRuntime = createRuntime({ clock: replayClock });
replayEvents(replayRuntime, replayClock, fixture);
replayClock.runUntilIdle();
runtime.dispose();
replayRuntime.dispose();recording.runtime is the dispatch target that captures events; calls sent
directly to the original runtime are not recorded. runUntilIdle() settles
delayed output; omit it when asserting an intermediate state. Keep fixture data
under your application’s test-data rules because normalized events can contain
host content.
Run and step events use the same fixture envelope. Preserve their IDs, parent relationships, and instance IDs to test nested or concurrent work, retry boundaries, late stale events, and parent completion races deterministically.
Use semantic Vitest matchers
installVitestMatchers adds transcript, announcement, and diagnostic assertions. Expected objects use semantic partial fields, which keeps tests focused on the behavior under review.
import { expect, test } from "vitest";
import { createRecorder } from "@generative-a11y/core";
import { installVitestMatchers } from "@generative-a11y/core/testing";
const accessibilityExpect = installVitestMatchers(expect);
test("announces a confirmed stop", () => {
const recorder = createRecorder();
recorder.runtime.dispatch({ type: "response.started", responseId: "r1" });
recorder.runtime.dispatch({ type: "response.interrupted", responseId: "r1" });
recorder.clock.runUntilIdle();
accessibilityExpect(recorder).toHaveAnnounced({
sourceType: "response.interrupted",
});
recorder.runtime.dispose();
});Use toHaveAnnouncementTranscript for ordered output and toHaveDiagnostic for
a matching runtime decision. See the testing reference for
matcher fields and fixture limits.
Keep the evidence boundary clear
Replay proves that normalized events produce repeatable runtime output under a controlled clock. It does not prove browser delivery or screen-reader speech. Keep browser fixtures and hands-on assistive-technology tests as separate release evidence.
Debug AI accessibility with the trace explorer
Inspect bounded, redacted runtime decisions and browser delivery evidence with the optional generative-a11y devtools package.
Troubleshooting
Fix missing, repeated, late, delayed, or noisy announcements by checking each step from the framework event to the browser update.