pw-locator-fixer · v1.0.0 · 2026-10-10 · sha256 5a8fba07bc1a443d
pw-locator-fixer v1.0.0A
Immutable. This exact content never changes and is served at /api/v1/blob/5a8fba07bc1a443d.
---
name: pw-locator-fixer
description: >-
Scans a Playwright spec or Page Object for brittle locators and rewrites them
to resilient ones. Use when an SDET says "fix these locators", "my selectors
are flaky", "replace XPath with getByRole", "make these locators resilient",
or pastes code full of nth-child/CSS-class/text selectors. Produces a before/
after rewrite map plus patched code — the engineer verifies each swap.
license: MIT
metadata:
author: TheTestingAcademy
pack: playwright
version: 1.0.0
---
# 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.
## When to use
- A spec/POM uses XPath, `nth-child`, CSS-class, or raw-text selectors.
- Tests fail intermittently on element lookups.
- Someone says "fix/harden/de-flake these locators".
## Workflow
1. **Scan** the provided code and flag every brittle locator:
- XPath (`//div[...]`), `page.locator('.some-class')`, `:nth-child`, deep CSS
descendant chains, index-based `.nth(3)`, and unanchored text matches.
2. **Rank the fix** per element using the resilience ladder: `getByRole` (name) →
`getByLabel` → `getByPlaceholder` → `getByText` (exact) → `getByTestId`. Reach
for `getByTestId` when semantics are weak, not as the first choice.
3. **Rewrite** each locator, preserving intent. Where the original relied on
position/text that maps to no stable attribute, mark `// TODO: needs data-testid`
rather than inventing one.
4. **Emit a rewrite map** (before → after → why) so the change is reviewable.
5. **Note residual risk** — any swap you couldn't confirm without the real DOM.
## Output shape
```
Rewrite map
✗ page.locator('//button[2]') → ✓ getByRole('button', { name: 'Save' })
✗ page.locator('.err-msg') → ✓ getByTestId('form-error') // needs data-testid
✗ page.locator('tr:nth-child(3) td') → ✓ getByRole('row', { name: /Order 1042/ })
```
```typescript
// before
await page.locator('.login-form input.username').fill('ada');
// after
await page.getByLabel('Username').fill('ada');
```
## Guardrails
- These are **proposed swaps the engineer must run and confirm** — never assume the
new locator resolves to the same element without checking the real DOM.
- Never invent a `data-testid` or accessible name; if none exists, flag that the
app needs one (`// TODO: needs data-testid`).
- Prefer role/label semantics over testid; testid is the fallback, not the default.
- Do not silently change behavior (strictness, count) — call out multi-match risks.