UCDL Accessibility Use Case Definition Language
Contents

Testing the Way Users Actually Experience the Web

An introduction to a tutorial series on @afixt/usecase-runner.

Most web test suites answer one question: does the page do what the developer built it to do? They find the button by its CSS class, click it at its coordinates, and check that the right div appeared. Every one of those steps reaches around the interface a person would use and pokes the DOM directly. The suite can be entirely green while a keyboard user can't reach the button, a screen-reader user can't tell what it's called, and nobody hears the confirmation.

@afixt/usecase-runner asks a different question: can a user accomplish this goal? You write what a person does — find the link, reach it, activate it, confirm where they ended up — in a small YAML syntax that a QA tester can author and a product owner can read. The tool turns that into Playwright tests, or runs it directly against a browser. And it targets every element the way assistive technology does: by ARIA role and accessible name, never by CSS selector or XPath.

That one constraint carries the whole philosophy. If an element isn't exposed in the accessibility tree, the test can't find it, and it fails. That failure isn't a limitation to work around — it's the finding. A flow written this way is a functional test and an accessibility test at once, and it can't be one without being the other.

A few convictions show up in every design decision, and each post in this series demonstrates one of them with a runnable example:

  • A failure is a finding. Every red step maps to something a real user can't do and to a concrete fix in the page, not in the test.
  • The test plan and the test are the same artifact. The YAML carries preconditions, expected results, and prose intent, and the report comes out in the same step-and-comments table a manual tester would deliver.
  • Verbs are contracts about outcomes. focus means focus was received. toggle means the state flipped. verify: alert means a role="alert" announced it. None of them describes a gesture; all of them assert what the user experienced.
  • Strictness over silence. A step the parser doesn't fully understand is rejected, not quietly trimmed — because an assertion that silently asserts nothing is indistinguishable from a pass, and that's the most expensive defect a test tool can have.
  • Report what actually ran. Whether a profile was enforced, whether a virtual or real screen reader spoke, which engine audited the page — the result always says how it was produced.

The series is ordered so it reads as a progressive tutorial, but each post stands alone with its own complete .uc.yaml.

For people already using it

  • Part 8: One Flow, Many Outcomes — extension cases, negative paths, and the checklist every error state should pass.
  • Part 9: Generate for CI, Run for Right Now — the two execution modes, why they must agree, and the three-value score.
  • Part 10: Beyond the Step — audit, contrast, lang_check, read_image, sr_says, and iterating a case over every element of a kind.
npm install @afixt/usecase-runner @playwright/test
npx playwright install
npx usecase-runner init

init drops a sample use case and config into the current directory. Point it at something you own and read the failures as a list of things a real user can't do. That's the habit the whole series is trying to build: don't test the page you built; test the page your users get.