generative-a11y
Testing

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.

Terminal
npm install @generative-a11y/core
npm install --save-dev vitest

Record 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.

Record and replay a response
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.

interruption.test.ts
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.