Making your UI testable
For frontend devs. If an automated test can't reliably find your element, the fix is almost always a small, additive change in the FE — not a brittle selector in the test. This page is the contract: expose semantics, not styling.
In the frontend repo there is a Claude skill for a11y auditing, use this to find the gaps in your frontend code or for an app repository.

The rule: target meaning, not appearance
Tests should locate elements by what they are, not how they look. Hashed CSS-module classes and FontAwesome icon classes change without warning and break tests silently. A stable role, label, or data-testid does not.
Locator preference order (tests follow this; make the top options possible):
- Role + accessible name —
get_by_role("button", name="Save") - Label / text —
get_by_label(...),get_by_text(...) data-testid— when there's no good semantic handle- CSS / XPath — last resort, brittle
What to add (with real examples)
aria-labelon repeated/ambiguous elements so the accessible name is unique. #801 — tree items gotaria-label+menuitem-<id>testids soget_by_role("treeitem", name="Files")resolves to exactly one row.- Expose state as a
data-attribute, not a colour class. #1023 — addeddata-status="RUNNING|COMPLETED|…"instead of leaving status in a hashed CSS-module class. - Use the right ARIA state for the right concept. #866 —
aria-currentfor the active workspace, instead of tests reverse-engineering a computedbox-shadow. - Give text a real role when it acts like one. #832 — a plain
<span>becamerole="heading"; without it,get_by_role("heading", …)timed out for 50s even though the UI had rendered fine. - Apply a naming convention across a whole surface. #917 / #924 —
composer-*testids + ARIA across every Composer surface.
Why it's worth it
These are small, additive, non-visual changes. They make the product more accessible and the tests durable — the same investment buys both. A missing role isn't just an a11y gap; #832 shows it's a real test failure.