Execution Guide
Tool versions
| Tool | Version used |
|---|---|
| Node.js | 23.11.0 |
| playwright-core | 1.62.1 |
| Chrome for Testing | 149.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 serve → http://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:
| Approach | Problem |
|---|---|
| Reading the markup | tells you what you wrote, not what the browser computed |
axe/lighthouse | finds violations of specific rules; does not show the tree |
| DevTools Accessibility pane | shows one node at a time, manually |
Accessibility.getFullAXTree | the 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.
::backdropis 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 withpage.fill()or.value =will make the trap disappear, which is itself worth trying once.