Execution Guide

Tool versions

ToolVersion used
Node.js23.11.0
playwright-core1.62.1
Chrome for Testing149.0.7827.55 (arm64)

Quick start

cd fe-04-html-as-a-platform/src
npm install
npm run a11y-tree   # 1. semantic vs div soup vs ARIA, real AX tree   (~10s)
npm run dialog      # 2. native <dialog> vs custom, behaviourally     (~15s)
npm run forms       # 3. constraint validation + dirty-value trap     (~10s)
npm run all

Manual lab: npm run servehttp://localhost:8084/ (see steps/04-replace-a-component.md).

Why CDP for the accessibility tree

Accessibility.getFullAXTree returns the browser's computed accessibility tree — the same data a screen reader consumes. Everything else is inference:

ApproachProblem
Reading the markuptells you what you wrote, not what the browser computed
axe/lighthousefinds violations of specific rules; does not show the tree
DevTools Accessibility paneshows one node at a time, manually
Accessibility.getFullAXTreethe whole tree, scriptable, diffable

That distinction is the point of experiment 1: div soup passes many automated audits while exposing zero landmarks and zero controls. Only the tree shows it.

Why keyboard input rather than assertions on markup

Experiment 2 does not check "is there a focus trap in the code". It presses Tab eight times through CDP and asks where focus ended up; presses Escape and asks whether the dialog closed; checks whether the background can be focused at all. Behaviour is the requirement, so behaviour is what is measured.

Note that page.focus('#trigger') + keyboard.press('Enter') is used rather than page.click() — activating a control the way a keyboard user does, which is the population the experiment is about.

Reproducibility notes

  • Markup byte counts are whitespace-normalised so indentation style does not affect the comparison. They measure structure, not minified transfer size.
  • AX node totals vary slightly with browser version and internal nodes. The landmark, heading, interactive and focusable counts are the stable signals; compare those.
  • ::backdrop is reported as a capability, not measured visually — it exists only for modal <dialog>, which is the architectural point.
  • Experiment 3's dirty-value section uses page.type() (real keystrokes). Replacing it with page.fill() or .value = will make the trap disappear, which is itself worth trying once.