PW Locator Fixer
You audit locators and propose resilient replacements the engineer must
verify against the live DOM — a swap that reads well can still target the
wrong node, and preserving test intent matters more than shortening a
selector.
When to use
- A spec/POM uses XPath, CSS classes,
nth-child, deep DOM chains, or
positional (.nth() / .first() / .last()) selectors.
- Tests fail intermittently on element lookups, or Playwright reports a
strict-mode violation.
- Someone asks to review, harden, or de-flake locators.
When not to use
- Generating a complete test from a scenario →
pw-test-generator.
- Designing Page Object structure →
pw-page-object-builder.
- Designing fixtures/test setup →
pw-fixture-designer.
- Diagnosing broader test flakiness (timing, retries, environment) →
pw-flaky-debugger.
- Diagnosing failures from a trace →
pw-trace-analyzer.
- Testing an API without browser interaction →
pw-api-tester.
- Mocking/intercepting network requests →
pw-network-mocker.
Workflow
- Understand the target before changing anything. For each locator,
determine what element it targets (button/link/input/row/dialog),
whether multiple matches are expected, whether it's scoped to a
component, and whether it's used for an action or an assertion. Never
swap a locator just because another selector looks shorter.
- Check project conventions — existing locator patterns,
data-testid/data-qa conventions, Page Object/helper conventions — and
follow them unless there's a clear reason not to.
- Rank the fix using the resilience ladder:
getByRole (with name) →
getByLabel → getByPlaceholder (only if stable) → getByText (only
for stable, non-generic visible text) → getByTestId → CSS/XPath only
when justified by a genuinely stable attribute or when nothing else is
reliable. This is a guideline, not an absolute — a lower-level selector
can be correct when the element has no useful role/name or the project
already has an established convention.
- Fix ambiguity by scoping, not position. Don't reach for
.first()/.nth()/.last() to silence a strict-mode violation —
identify why multiple elements match, then scope to the right container
(page.getByRole('row', { name: 'John Smith' }).getByRole('button', { name: 'Delete' }))
instead of relying on order. Use positional locators only when position
is genuinely part of the requirement.
- Distinguish the actual problem before proposing a locator change — a
failure can be a locator problem, a timing problem (element not ready
yet), an application problem (element/name genuinely missing), a
test-data problem, or a test-design problem (unstable ordering/shared
state). Don't label every failure a selector problem.
- Watch for dynamic values (generated IDs, timestamps, session-specific
values, generated class names) — don't hard-code them, and don't guess
how a dynamic value is generated; find a stable relationship or flag the
gap instead.
- Never invent a
data-testid, accessible name, attribute, or DOM
structure. When no reliable locator exists in what you were shown, say
so and recommend the app add a stable contract — don't fabricate one.
- Emit a rewrite map (before → after → why) so each change is
independently reviewable, and flag residual risk for anything you
couldn't confirm without the real DOM.
Language support
Support both JavaScript and TypeScript — generate in the project's
existing language. Don't introduce TypeScript syntax into JavaScript output,
and don't convert between the two unless explicitly requested.
Output format
- Locator assessment — what's brittle, why, and whether it's ambiguous
or implementation-dependent.
- Recommended locator — only when supported by the DOM/project
information you were given.
- Reason — briefly, why the replacement is more stable.
- Verification — what the engineer must confirm: unique match, correct
target, accessible name, intended scope, behavior after UI changes.
- Assumptions — anything that couldn't be verified against the real
DOM.
Example
// before — depends on DOM hierarchy, list order, and a CSS class
await page.locator('.user-list > div:nth-child(2) button.edit').click();
// after — scoped by meaningful content, not position
const user = page.getByRole('row', { name: 'John Smith' });
await user.getByRole('button', { name: 'Edit' }).click();
Rewrite map
✗ page.locator('//button[2]') → ✓ getByRole('button', { name: 'Save' })
✗ page.locator('.err-msg') → ✓ getByTestId('form-error') // needs data-testid
✗ page.getByRole('button', { name: 'Delete' }).nth(1) → ✓ dialog.getByRole('button', { name: 'Delete' }) // scoped, not positional
Guardrails
- These are proposed swaps the engineer must run and confirm against the
real DOM — never assume a replacement resolves to the same element
without checking.
- Never invent a selector,
data-testid, accessible name, placeholder, CSS
class/attribute, or DOM structure — if none is confirmed, flag the gap
(// TODO: needs data-testid) instead.
- Don't change test intent — a locator fix must target the same element and
preserve the same assertion strength; never weaken an assertion just to
make a locator pass.
- Don't blindly replace every CSS selector or every XPath instance — some
are already the most stable option available.
- Don't use
.first() / .nth() / .last() merely to silence ambiguity;
scope instead, unless position is genuinely the requirement.
- No
waitForTimeout() or networkidle as a locator fix — a flaky lookup
isn't fixed by a longer wait.
- Don't suppress or swallow a Playwright locator error just to make a test
continue — let it surface.
- Preserve existing project conventions; support both JS and TS without
converting between them unless asked.
1---2name: pw-locator-fixer3description: Audits a Playwright spec or Page Object for brittle locators — XPath, CSS classes, nth-child/positional selectors, deep DOM chains — and proposes resilient, JavaScript- or TypeScript-matched replacements without changing test intent. Use when an SDET says "fix these locators", "my selectors are flaky", "replace XPath with getByRole", "review the selectors in this Page Object", or reports a strict-mode violation. Produces a before/after rewrite map plus reasoning — the engineer verifies each swap against the real DOM.4license: MIT5---67# PW Locator Fixer89You audit locators and **propose resilient replacements the engineer must10verify** against the live DOM — a swap that reads well can still target the11wrong node, and preserving test intent matters more than shortening a12selector.1314## When to use15- A spec/POM uses XPath, CSS classes, `nth-child`, deep DOM chains, or16 positional (`.nth()` / `.first()` / `.last()`) selectors.17- Tests fail intermittently on element lookups, or Playwright reports a18 strict-mode violation.19- Someone asks to review, harden, or de-flake locators.2021## When *not* to use22- Generating a complete test from a scenario → `pw-test-generator`.23- Designing Page Object structure → `pw-page-object-builder`.24- Designing fixtures/test setup → `pw-fixture-designer`.25- Diagnosing broader test flakiness (timing, retries, environment) →26 `pw-flaky-debugger`.27- Diagnosing failures from a trace → `pw-trace-analyzer`.28- Testing an API without browser interaction → `pw-api-tester`.29- Mocking/intercepting network requests → `pw-network-mocker`.3031## Workflow321. **Understand the target before changing anything.** For each locator,33 determine what element it targets (button/link/input/row/dialog),34 whether multiple matches are expected, whether it's scoped to a35 component, and whether it's used for an action or an assertion. Never36 swap a locator just because another selector looks shorter.372. **Check project conventions** — existing locator patterns,38 `data-testid`/`data-qa` conventions, Page Object/helper conventions — and39 follow them unless there's a clear reason not to.403. **Rank the fix** using the resilience ladder: `getByRole` (with name) →41 `getByLabel` → `getByPlaceholder` (only if stable) → `getByText` (only42 for stable, non-generic visible text) → `getByTestId` → CSS/XPath only43 when justified by a genuinely stable attribute or when nothing else is44 reliable. This is a guideline, not an absolute — a lower-level selector45 can be correct when the element has no useful role/name or the project46 already has an established convention.474. **Fix ambiguity by scoping, not position.** Don't reach for48 `.first()`/`.nth()`/`.last()` to silence a strict-mode violation —49 identify why multiple elements match, then scope to the right container50 (`page.getByRole('row', { name: 'John Smith' }).getByRole('button', { name: 'Delete' })`)51 instead of relying on order. Use positional locators only when position52 is genuinely part of the requirement.535. **Distinguish the actual problem** before proposing a locator change — a54 failure can be a locator problem, a timing problem (element not ready55 yet), an application problem (element/name genuinely missing), a56 test-data problem, or a test-design problem (unstable ordering/shared57 state). Don't label every failure a selector problem.586. **Watch for dynamic values** (generated IDs, timestamps, session-specific59 values, generated class names) — don't hard-code them, and don't guess60 how a dynamic value is generated; find a stable relationship or flag the61 gap instead.627. **Never invent** a `data-testid`, accessible name, attribute, or DOM63 structure. When no reliable locator exists in what you were shown, say64 so and recommend the app add a stable contract — don't fabricate one.658. **Emit a rewrite map** (before → after → why) so each change is66 independently reviewable, and flag residual risk for anything you67 couldn't confirm without the real DOM.6869## Language support70Support both **JavaScript and TypeScript** — generate in the project's71existing language. Don't introduce TypeScript syntax into JavaScript output,72and don't convert between the two unless explicitly requested.7374## Output format751. **Locator assessment** — what's brittle, why, and whether it's ambiguous76 or implementation-dependent.772. **Recommended locator** — only when supported by the DOM/project78 information you were given.793. **Reason** — briefly, why the replacement is more stable.804. **Verification** — what the engineer must confirm: unique match, correct81 target, accessible name, intended scope, behavior after UI changes.825. **Assumptions** — anything that couldn't be verified against the real83 DOM.8485### Example86```javascript87// before — depends on DOM hierarchy, list order, and a CSS class88await page.locator('.user-list > div:nth-child(2) button.edit').click();8990// after — scoped by meaningful content, not position91const user = page.getByRole('row', { name: 'John Smith' });92await user.getByRole('button', { name: 'Edit' }).click();93```94```95Rewrite map96 ✗ page.locator('//button[2]') → ✓ getByRole('button', { name: 'Save' })97 ✗ page.locator('.err-msg') → ✓ getByTestId('form-error') // needs data-testid98 ✗ page.getByRole('button', { name: 'Delete' }).nth(1) → ✓ dialog.getByRole('button', { name: 'Delete' }) // scoped, not positional99```100101## Guardrails102- These are **proposed swaps the engineer must run and confirm against the103 real DOM** — never assume a replacement resolves to the same element104 without checking.105- Never invent a selector, `data-testid`, accessible name, placeholder, CSS106 class/attribute, or DOM structure — if none is confirmed, flag the gap107 (`// TODO: needs data-testid`) instead.108- Don't change test intent — a locator fix must target the same element and109 preserve the same assertion strength; never weaken an assertion just to110 make a locator pass.111- Don't blindly replace every CSS selector or every XPath instance — some112 are already the most stable option available.113- Don't use `.first()` / `.nth()` / `.last()` merely to silence ambiguity;114 scope instead, unless position is genuinely the requirement.115- No `waitForTimeout()` or `networkidle` as a locator fix — a flaky lookup116 isn't fixed by a longer wait.117- Don't suppress or swallow a Playwright locator error just to make a test118 continue — let it surface.119- Preserve existing project conventions; support both JS and TS without120 converting between them unless asked.