UCDL Accessibility Use Case Definition Language
Contents

Accessibility Use Case Definition Language (UCDL) and Runner

This section is normative.

A conforming processor SHOULD implement at least one of the following modes and MAY implement both. A processor that implements both MUST produce behaviorally equivalent runtime results in either mode (modulo timing).

The processor MUST emit one Playwright .spec.ts file per use case. The file structure is:

// Auto-generated by @afixt/usecase-runner from <source>.uc.yaml
// Do not edit manually — changes will be overwritten on next generation.

import { test, expect } from '@playwright/test';
// (only if any audit step is present:)
const { AccessibilityEngine } = require('@afixt/afixt-engine');

test.describe('<title>', () => {
  test.describe.configure({ mode: 'serial' });

  test('<id>: <title> (<type>)', async ({ page }) => {
    // Preconditions: …
    // Step 1: Access start location
    await page.goto('<start_location>');

    // Step 2: <comment>
    <generated code>

    // Step 3: <comment>
    <generated code>
    …
  });
});

The auto-generation banner is REQUIRED. The serial mode declaration is REQUIRED.

The generated await page.goto(...) MUST be step 1; user-authored steps begin at step 2. This invariant is observable in the report (see §10).

In direct execution, the processor:

  1. Launches a Playwright browser (browser type, headed/headless, slow-mo, viewport, default timeout — all per configuration).
  2. Navigates to start_location.
  3. Iterates over the resolved steps. For each step, it executes the step's action against the live page, captures duration, and produces a StepResult.
  4. On step failure with screenshot_on_failure: true, captures a full-page screenshot to <screenshot_dir>/<use-case-id>/step-<N>-fail.png, or to <screenshot_dir>/<use-case-id>/iter-<i>/step-<N>-fail.png for step N of iteration i (1-based) of an iterated case (§5.8), or to <screenshot_dir>/<use-case-id>/before/step-<N>-fail.png for entry N of the before block (§4.6), whose numbering also starts at 1. The use case id is reduced to the characters A–Z a–z 0–9 . _ - for the directory name, every other character becoming _. Step numbers restart per use case and per iteration, so a path without both components lets one case's evidence overwrite another's; the report's screenshot_path (§10.1) records the path actually written.
  5. On step failure with continue_on_failure: false (non-default), short-circuits remaining steps to status skip.
  6. Closes the browser and computes the overall score.

In direct mode, step indexing starts at 1 for the first user-authored step; goto is performed before the loop and is not counted. (This is the inverse of codegen mode — see §10 for how reports normalize this.)

Running the same conforming use case document through codegen and then through npx playwright test MUST produce the same pass/fail outcome as running it through direct execution (modulo flakiness and timing). The canonical equivalence is defined by §6 and §5; any divergence is a defect in the implementation.

There are no exceptions. Aggregate assertions (§5.8.4) and continuation past a failing iteration (§5.8.6) hold in both modes: a generated test collects per-iteration outcomes and asserts the aggregate after the loop, so both modes reach the same pass/fail outcome for an iterated document as for any other.

An interaction profile names a set of interaction affordances the page under test MUST NOT require in order to complete the flow. A profile does not change how the page is driven — it changes which verbs the run permits. Omitting it leaves the run unrestricted, which is the default and the behaviour of every run that does not ask for a profile.

The profile is selected per run: RunOptions.profile in the programmatic API (§13) and --profile <name> on the command line (§14).

One profile is defined:

Profile Forbidden forms
no-pointer hover, hover_out, toggle, activate without via keyboard, select on an option target

The set is every form that dispatches a pointer event, not every keyword that can. activate ... via keyboard presses Enter and stays available; select on a checkbox, radio or native select drives state without a click and stays available. Under the profile, activate ... via keyboard is the only activation form, and keyboard: drives options and toggles.

A profile name describes the prohibition, not a simulation: no-pointer forbids the pointer forms, it does not drive the page by keyboard in their place. A processor MUST NOT interpret a profile as a request to emulate an alternative input modality.

What a passing run establishes. That the script as written requires no pointer — which is the author's assertion about the flow, not proof that the application requires one. An author who wrote hover may simply have chosen that route where a keyboard route also exists. The profile makes the script's claim explicit and checkable; it does not discover the application's requirements.

When a violation is reported. A profile violation is a property of the document rather than of the page, so a processor MUST be able to report it without a browser: validate SHOULD accept the profile and name the offending steps. The runtime failure below is retained for processors that only see the document at run time, and for a violation reached through a path validation could not resolve.

A processor MUST reject an unrecognised profile name rather than ignoring it. Silently dropping an unknown name would report a clean run that enforced nothing, which is the precise failure the profile exists to prevent.

When the active profile forbids a step's keyword:

  1. The step MUST fail without executing. The processor MUST NOT dispatch the underlying interaction — the assertion is that the flow requires the affordance at all, so attempting it would defeat the check.
  2. The step result MUST carry status: "fail" and failure_reason: "profile_forbidden" (§10.1).
  3. The failure MUST be a failure and not a skip. A flow that cannot proceed without the forbidden affordance is a genuine defect for anyone who lacks it, not a test that turned out to be inapplicable. Scoring treats it like any other failing step (§11), and it gates the exit code accordingly (§14.1).
  4. The enforced profile MUST be recorded on the result (profile, §10.1), taken from what was actually enforced rather than what was requested, so a report can never claim a profile the steps did not run under.

Both execution modes MUST enforce the profile identically: direct execution fails the step as above, and code generation MUST emit a step that fails with the same message rather than one that performs the interaction. This is required by §8.3 — a profile enforced in only one mode would make the two modes disagree on the same document.