Skip to main content

playwright-attribute-waits

Why It Exists

The introducing commit (306857c, "skills: add playwright testid-catalog/attributes/waits/page-objects family") is explicit about where this skill came from: it "generalizes a Playwright instrumentation convention found undocumented-but-pervasive in a client engagement's codebase" — typed data-testid catalogs, reactive data-*/aria-* state-marker attributes, the canonical wait helpers that consume them, and Page Object structure. The pattern existed as real, working code in that engagement before it existed as a skill; this skill is the extraction and documentation of the consumer half of that convention, not a design invented from scratch.

That extraction then became the reference other planning work defers to rather than re-deriving. .omc/plans/quality-loop-skills.md (local research artifact, not tracked in this repo), while scoping a separate frontend-quality-loop skill, maps an entire category of review findings — "raw process.env in a spec, route-intercept path mismatch, missing status-check before .data" — directly onto this skill family and says so explicitly: those Playwright rules "map directly onto this family's own playwright-testid-catalog/playwright-testid-attributes/playwright-attribute-waits/playwright-page-objects skills," and the plan chooses to defer to them rather than duplicate the checklist.

The same proven-pattern status made this skill the template for a second platform. .omc/plans/appium-rn-testing-skills.md (local research artifact, not tracked in this repo) explicitly set out to "mirror the existing playwright-testid-attributes/playwright-testid-catalog/playwright-attribute-waits/playwright-page-objects family's producer/catalog/consumer/structure split exactly," and its own ADR is candid that the resulting appium-attribute-waits counterpart is "honestly thinner" than this skill, since the RN/Appium stack has no standalone helper module analogous to this skill's references/readiness.ts — waits there are inherited NativeBase methods instead. That gap is itself evidence of how load-bearing the canonical-helper-module invariant is here: a sibling skill exists specifically to explain why it doesn't fully apply on another platform.

What It Does

playwright-attribute-waits is the consumer-side half of a producer/consumer pair: a component exposes its async state through data-*/aria-* attributes, and this skill is the discipline for waiting on those attributes correctly instead of guessing with waitForTimeout or reimplementing a slightly different polling loop per file. It centers on one canonical helper module (references/readiness.ts) exporting waitForReady (load-state gate that checks the error case first), waitForClosed (close/unmount gate with a visibility fallback), waitForEpochAdvance (capture-then-poll-for-strict-advance fencing around an action), waitForSearchSettled (one combined expect.poll over every tracked search attribute), and parseAttributeJson (guarded JSON parsing).

It assumes the attributes already exist on the page — producing them is the sibling skill playwright-testid-attributes' job — and it does not define Page Object class structure, which is the sibling skill playwright-page-objects' job. This skill is purely "how do I wait correctly," once the attributes and the class shape already exist.

How To Use It

Triggers on: "wait for element ready", "flaky Playwright test", "toHaveAttribute data-load-state", "wait for modal to close", "epoch fencing", "search debounce settle", "expect.poll multiple attributes", "parse JSON from a data attribute in a test", "known bug sentinel in an e2e assertion", or writing/reviewing a Playwright wait against instrumented async UI state.

skills add git@github.com:catesandrew/next-starters.git --skill skills/playwright-attribute-waits -g
npm install @next-starters/skill-playwright-attribute-waits
/plugin marketplace add catesandrew/next-starters
/plugin install playwright-attribute-waits@next-starters

Gotchas & Invariants

  • Exactly one canonical readiness/locators helper module per test suite (references/readiness.ts) — every spec and page-object method imports it; nothing re-derives its own polling loop inline. This is called out as the single most important invariant the other eight assume holds.
  • Load-state gate checks the negative case first: assert not.toHaveAttribute('data-load-state', 'error') before asserting toHaveAttribute('data-load-state', 'ready') (waitForReady) — checking only the positive case can miss a real error-then-recover cycle entirely.
  • Close/unmount gate waits for the documented "closed" attribute value (waitForClosed), not just toBeHidden() — an element can be hidden for transient reasons unrelated to the intended close. Falls back to a visibility-only check for older/uninstrumented components.
  • Epoch-fencing captures the epoch value before the action, then polls for strict advance after (waitForEpochAdvance) — never trust a terminal state string alone, since it can repeat identically across two separate action cycles.
  • Search-settle polls every tracked attribute together in one expect.poll(...).toEqual(...) (waitForSearchSettled), never one attribute in isolation — a partial check can read "ready" while a sibling attribute still lags the just-typed query.
  • Every JSON-in-attribute parse is wrapped in try/catch plus a shape guard (parseAttributeJson) — never assume the attribute is well-formed. When both a visible and hidden-sentinel copy of the same JSON attribute exist, read both and union the results.
  • Known-bug outcomes get a named sentinel constant in a distinct branch — never an inline magic string, and never silently folded into a plain pass/fail.
  • Degrade gracefully against uninstrumented components: fall back to a coarser check (visibility wait, bounded sleep) and log why, rather than hard-throwing when expected attributes are absent.
  • Prefer an existing ARIA attribute (aria-expanded, aria-pressed) over a bespoke data-* re-derivation of the same state — two attributes tracking one state is two places that can drift.
  • Packaging note: metadata.json's references entry for this skill is repo-relative (skills/playwright-attribute-waits/references/...) and is slated for rewriting to package-relative form as part of npm/plugin distribution — not yet done as of the distribution-channels plan.
  • playwright-page-objects — calls this skill's readiness helpers around every interaction but doesn't define them.
  • playwright-testid-attributes — the producer-side sibling of these consumer-side wait helpers; emits the data-*/aria-* attributes this skill waits on.
  • playwright-testid-catalog — owns the typed data-testid registry that the producer side pulls ids from before this skill's helpers ever see the page.

Sourced from: skills/playwright-attribute-waits/metadata.json, skills/playwright-attribute-waits/SKILL.md, .omc/plans/quality-loop-skills.md (local research artifact, not tracked in this repo), .omc/plans/skill-distribution-channels.md (local research artifact, not tracked in this repo), .omc/plans/appium-rn-testing-skills.md (local research artifact, not tracked in this repo), git log --follow -- skills/playwright-attribute-waits (commit 306857c)