playwright-page-objects
Why It Exists
playwright-page-objects is one of four Playwright skills (alongside
playwright-testid-catalog, playwright-testid-attributes, and
playwright-attribute-waits) added in commit 306857c to generalize "a
Playwright instrumentation convention found undocumented-but-pervasive in a
client engagement's codebase: typed data-testid catalogs, reactive
data-*/aria-* state-marker attributes (load-state, epoch-fencing,
debounced-search), the canonical wait helpers that consume them, and Page
Object structure/selector priority." The same commit redacted
client-identifying references (envmgr-ui, @ad-infrastructure) from
README.md and the docs/*-RUNBOOK.md files, replacing them with a neutral
"a client engagement" phrasing — the skill's structure and invariants were
lifted from real production code, not invented from a style guide.
The class-structure/selector-discipline split this skill owns (as opposed to
the wait-helper implementations or the testid catalog mechanics) later became
the reference pattern other skill families were told to mirror exactly.
.omc/plans/appium-rn-testing-skills.md (local research artifact, not
tracked in this repo) opens its principles with "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 appium-page-objects
acceptance criteria explicitly copy this skill's own carve-out — that element
discovery ("browser-exploration" here, screen-exploration there) is a
tool-agnostic concern owned by neither skill — once an Appium-capable MCP
server (@wdio/mcp) was confirmed to exist, "matching playwright-page-objects'
own carve-out for its discovery step."
What It Does
playwright-page-objects defines the output shape of a Playwright Page
Object: a typed façade over one route that owns the locators and the small
actions/assertions that touch them, so specs read as intent rather than raw
selector plumbing. It fixes the class contract (a shared BasePage with
route, constructor(page: Page), and navigate()), the selector priority
order, the wait discipline around interactions, the one-file-per-route naming
rule, and the boundary between page structure and business/domain flow.
It deliberately does not define how you discover which elements belong in the
class in the first place — that stays a tool-agnostic concern — nor the
readiness-wait helper implementations a page object's action methods call
into (that's playwright-attribute-waits) or the typed data-testid catalog
those selectors pull from (that's playwright-testid-catalog).
How To Use It
Triggers on: "new page object", "Page Object Model class", "extend BasePage", "page object structure", "expand a stub page object", "locator selector priority", "role vs testid selector", "verify method pattern", "one page object per route", or "business logic leaking into a page object".
skills add git@github.com:catesandrew/next-starters.git --skill skills/playwright-page-objects -g
npm install @next-starters/skill-playwright-page-objects
/plugin marketplace add catesandrew/next-starters
/plugin install playwright-page-objects@next-starters
Gotchas & Invariants
- Every page object extends a shared
BasePageexposingroute, aconstructor(page: Page), andnavigate(). Locators aregetaccessors (page.searchInput, notpage.searchInput()), andverify*assertion methods assert internally withexpectand returnvoidrather than handing a boolean back to the caller. - Selector priority ladder: role-based first (
getByRole()with an accessible name), thengetByLabel()/getByText(), thengetByTestId()pulled from the typed catalog, then a CSS class selector last. A role/label-based selector doubles as a live accessibility check — if it can't find the element by role and accessible name, that's often a real accessibility bug, not just test flakiness.data-testidfinds an element regardless of whether it's exposed to assistive tech, so it papers over a missingaria-labelif used as the first choice. - Action methods call a named readiness-wait helper from
playwright-attribute-waitsaround every interaction, never a barewaitForTimeout(). If a rawwaitForTimeout()is genuinely unavoidable, it must carry an inline comment justifying the exact millisecond budget (e.g.// 400ms matches the search debounce window). - One page-object file per route, with the file path and class name derived
mechanically from the route —
/items/listbecomesItemsListPagein a file that mirrors the segment path — and grouped to match the target app's own routing/feature directory structure. - Page objects encode structure, not business/domain flow. Multi-step flows ("sign up, then verify, then onboard") compose one or more page objects at a higher-level test/flow layer instead of living inside a single page object's methods.
- A stub page object containing only
routeand a title/heading locator is a legitimate first commit. Expanding it with real locators, actions, and assertions must be purely additive — it must never break or remove the stub's original publicroute/title contract, so specs already written against the stub keep passing unmodified.
Related Skills
- playwright-attribute-waits — supplies the readiness-wait helpers this skill's action methods call into but never redefine.
- playwright-testid-attributes — instruments components with the state-marker attributes this skill's selectors and waits ultimately depend on.
- playwright-testid-catalog — owns the typed
data-testidregistry this skill's selector-priority ladder falls back to when no reliable role/label exists.
Sourced from: skills/playwright-page-objects/metadata.json, skills/playwright-page-objects/SKILL.md, .omc/plans/appium-rn-testing-skills.md (local research artifact, not tracked in this repo), git log --follow -- skills/playwright-page-objects (commit 306857c)