playwright-testid-attributes
Why It Exists
This skill was one of four introduced together in commit 306857c
("skills: add playwright testid-catalog/attributes/waits/page-objects
family"), whose message says the split "generalize[s] a Playwright
instrumentation convention found undocumented-but-pervasive in a client
engagement's codebase." Rather than write one large skill, the pattern was
divided along a producer/catalog/consumer/structure axis, with
docs/PLAYWRIGHT-TESTING-RUNBOOK.md added as a thin index tying the four
together — that runbook spells out the real dependency order: the typed
catalog (playwright-testid-catalog) comes first, this skill's reactive
state-marker attributes come second (pulling data-testid from the catalog
and adding data-state/data-load-state/aria-busy/epoch attributes on
top), the wait helpers (playwright-attribute-waits) come third, and Page
Objects (playwright-page-objects) come last.
The skill wasn't static after that initial commit. A follow-up fix,
94613cb ("fix(skills): quote-safe colon in playwright-testid-attributes
description"), corrected a mid-line colon-space in this exact skill's YAML
frontmatter description that broke frontmatter parsing — skill loaders were
silently dropping this skill from discovery entirely until that fix landed.
That failure mode is a concrete reminder of why the frontmatter description
field carries the trigger phrases verbatim: if it fails to parse, the skill
doesn't just render badly, it never activates at all.
This skill's authority extends beyond its own directory. .omc/plans/quality-loop-skills.md
(local research artifact, not tracked in this repo) explicitly defers to it
rather than re-deriving equivalent guidance: its Lens I item 4, "DOM State
Anchor: data-load-state/data-state," is noted as "already this family's
own playwright-testid-attributes skill, invariant-for-invariant." The same
plan also maps a Playwright-rules lens directly onto this skill (among its
three siblings) instead of writing a parallel checklist. A separate plan,
.omc/plans/appium-rn-testing-skills.md (also local-only), used this
family's producer/catalog/consumer/structure split as the explicit template
for a parallel React Native/Appium skill family, naming this skill's
sibling-disclaimer style and invariant-verifiability bar as the precedent to
match.
What It Does
playwright-testid-attributes is the producer half of a three-skill family:
it teaches how to instrument a component's markup with data-*/aria-*
attributes that describe visual, loading, and interaction state, derived
from the exact same variable that drives the component's render — never a
parallel computation that can drift from what's actually on screen. It
covers the data-state/data-load-state/aria-busy baseline pair for
async-gated containers, data-<action>-epoch counters for repeatable
actions like delete/refresh/retry, the four-stage debounced-search attribute
pattern, JSON.stringify-ing identity attributes when a test needs to know
which items are selected (not just how many), hidden sentinel elements for
content that unmounts on close, and preferring standard ARIA attributes over
bespoke data-* equivalents when an ARIA one already exists.
It does not define the typed data-testid catalog mechanics — that's the
sibling skill playwright-testid-catalog — or how test code reads and waits
on these attributes — that's playwright-attribute-waits. This skill only
covers the producer side: instrumenting the component itself.
How To Use It
Triggers on: "add data-testid to this component", "make this Sheet Playwright-testable", "async loading state attributes", "epoch counter for retries", "debounced search test attributes", "data-load-state", "aria-busy", "hidden sentinel for closed Dialog", or reviewing whether a component's state attributes will race Playwright.
skills add git@github.com:catesandrew/next-starters.git --skill skills/playwright-testid-attributes -g
npm install @next-starters/skill-playwright-testid-attributes
/plugin marketplace add catesandrew/next-starters
/plugin install playwright-testid-attributes@next-starters
Gotchas & Invariants
- Derive
data-state/data-load-statefrom the exact same variable that decides what JSX renders — never a second, parallel computation that can disagree after a fast update or a refactor that only touches one of them. - Expose
data-state('open' | 'closed') anddata-load-state('loading' | 'ready' | 'error' | 'closed') as a baseline pair on any async-gated container — collapsing them into one attribute forces a test to guess whether "closed" means "not mounted" or "mounted but errored." - Set
aria-busyfrom the exact same boolean that flipsdata-load-state === 'loading', so test code has a generic, enum-agnostic "is this settled yet" gate. - Repeatable async actions (delete, refresh, save, retry) need a
monotonically-incrementing
data-<action>-epochcounter that increments exactly once per completed attempt, success or failure, never while pending — a terminal state string alone can repeat across two separate attempts and hide a stale result. - A debounced search input tracks three pipeline stages (raw typed value,
debounce-settled value, applied-filter value) as a four-state
idle | pending | loading | readymachine, never a single boolean. JSON.stringifythe full value (e.g.data-selected-ids) when a test needs to know which items are involved, not just a count.- Components that unmount their content on close (Radix/shadcn-style
Dialog/Sheet) need a permanently mounted, visually hidden sentinel carrying the samedata-state/data-load-statepair, so a test can assert across a close-then-reopen transition without racing the unmount. - Never hand-write a
data-testidstring literal — every value comes from the typed catalog (playwright-testid-catalogowns that mechanism). - Prefer the standard ARIA attribute (
aria-expanded,aria-pressed,aria-selected) over a bespokedata-*equivalent when one already exists — reinventing it can disagree with the ARIA value it duplicates. - Static, non-reactive labels (analytics/RUM action names, onboarding-tour anchors) are a separate concern from the reactive state markers above — don't apply render-derivation reasoning to a fixed string set once at author time.
Related Skills
- playwright-testid-catalog — owns
the typed
data-testidregistry every id this skill's components bind comes from. - playwright-attribute-waits — the consumer-side sibling that reads and waits on the exact attributes this skill produces.
- playwright-page-objects — Page Object classes wrap components instrumented by this skill, using the catalog and wait helpers one layer up.
Sourced from: skills/playwright-testid-attributes/metadata.json, skills/playwright-testid-attributes/SKILL.md, docs/PLAYWRIGHT-TESTING-RUNBOOK.md, git log --follow -- skills/playwright-testid-attributes (commits 306857c, 94613cb, ddeb5f5), .omc/plans/quality-loop-skills.md (local research artifact, not tracked in this repo), .omc/plans/appium-rn-testing-skills.md (local research artifact, not tracked in this repo)