From playwright-e2e
Framework-aware locator strategy for Playwright. Defines locator priority, per-framework selector tables for React, Angular, Vue, and plain HTML, anti-patterns to avoid, and DO/DON'T examples.
How this skill is triggered — by the user, by Claude, or both
Slash command
/playwright-e2e:e2e-selectorsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Always prefer locators that reflect how users perceive and interact with the UI. Apply them in this order — use the first that uniquely identifies the element:
Always prefer locators that reflect how users perceive and interact with the UI. Apply them in this order — use the first that uniquely identifies the element:
getByRole, getByLabel, getByPlaceholder, getByTextgetByTestId (when semantic locators are ambiguous or unstable)locator('[data-testid="..."]') as a fallback for custom attribute schemesNever use XPath or CSS class selectors tied to styling (e.g., .btn-primary, .MuiButton-root).
| Element type | Preferred locator | Example |
|---|---|---|
| Button | getByRole('button', { name }) | page.getByRole('button', { name: 'Submit' }) |
| Link | getByRole('link', { name }) | page.getByRole('link', { name: 'Dashboard' }) |
| Text input | getByLabel('Label text') | page.getByLabel('Email') |
| Checkbox | getByRole('checkbox', { name }) | page.getByRole('checkbox', { name: 'Remember me' }) |
| Heading | getByRole('heading', { name }) | page.getByRole('heading', { name: 'Welcome' }) |
| Any text | getByText('text') | page.getByText('No results found') |
| Ambiguous element | getByTestId('test-id') | page.getByTestId('product-card-42') |
Add data-testid attributes to React components only when getByRole or getByLabel cannot uniquely identify the element.
| Element type | Preferred locator | Example |
|---|---|---|
| Button | getByRole('button', { name }) | page.getByRole('button', { name: 'Save' }) |
| Form field | getByLabel('Label text') | page.getByLabel('Username') |
| Navigation link | getByRole('link', { name }) | page.getByRole('link', { name: 'Profile' }) |
| Any text | getByText('text') | page.getByText('Loading...') |
| Ambiguous / no ARIA | locator('[data-testid="id"]') | page.locator('[data-testid="user-menu"]') |
| Angular component root | locator('app-user-card') | page.locator('app-user-card').first() |
Use Angular component element selectors (app-*) only when querying the component root itself, not for interacting with its internal elements.
| Element type | Preferred locator | Example |
|---|---|---|
| Button | getByRole('button', { name }) | page.getByRole('button', { name: 'Confirm' }) |
| Form field | getByLabel('Label text') | page.getByLabel('Search') |
| Any text | getByText('text') | page.getByText('Cart is empty') |
| Ambiguous element | locator('[data-test="id"]') | page.locator('[data-test="checkout-btn"]') |
Vue projects often use data-test (without -id). Check the project's test-attribute convention in .playwright-agent.json (testIdAttribute key) before defaulting to data-testid.
| Element type | Preferred locator | Example |
|---|---|---|
| Button / submit | getByRole('button', { name }) | page.getByRole('button', { name: 'Login' }) |
| Input with label | getByLabel('Label text') | page.getByLabel('Password') |
| Placeholder input | getByPlaceholder('text') | page.getByPlaceholder('Search...') |
| Semantic text | getByText('text') | page.getByText('Welcome back') |
| ID attribute | locator('#element-id') | page.locator('#main-nav') |
| CSS — structural only | locator('table > tbody > tr') | page.locator('table > tbody > tr').nth(0) |
Never use these locator strategies:
| Anti-pattern | Why | Alternative |
|---|---|---|
| XPath | Brittle, tied to DOM structure | getByRole, getByLabel |
| CSS class selectors | Break on styling refactors | getByRole, getByTestId |
| CSS-in-JS generated classes | Unstable hash suffixes | getByRole, getByTestId |
nth-child / nth-of-type | Positional, fragile | Add data-testid to the specific element |
| Text matching translated strings | Breaks on locale changes | getByTestId or getByRole |
| Internal component state attributes | Implementation detail | Observable user-facing attributes |
// DO — semantic role selector
await page.getByRole('button', { name: 'Add to cart' }).click();
// DON'T — CSS class tied to styling
await page.locator('.btn-primary').click();
// DO — label-based input
await page.getByLabel('Email address').fill('[email protected]');
// DON'T — brittle CSS ID (fragile if ID changes)
await page.locator('#email-input-v2').fill('[email protected]');
// DO — test ID for ambiguous elements
await page.getByTestId('product-thumbnail-42').click();
// DON'T — XPath
await page.locator('//div[@class="product"]/img').click();
// DO — scoped locator to avoid false matches
const card = page.getByTestId('user-card').filter({ hasText: 'Alice' });
await card.getByRole('button', { name: 'Edit' }).click();
// DON'T — global text match that could hit multiple elements
await page.getByRole('button', { name: 'Edit' }).click(); // ambiguous if multiple Edit buttons
| API | Use case |
|---|---|
page.getByRole(role, options) | ARIA role + accessible name |
page.getByLabel(text) | Form inputs associated with a <label> |
page.getByPlaceholder(text) | Inputs with a placeholder attribute |
page.getByText(text) | Visible text content |
page.getByAltText(text) | Images with an alt attribute |
page.getByTitle(text) | Elements with a title attribute |
page.getByTestId(id) | Elements with the configured test-ID attribute |
locator.filter({ hasText }) | Narrow a locator set by contained text |
locator.nth(index) | Select the Nth match (use sparingly) |
locator.first() / .last() | First or last match in a list |
npx claudepluginhub gagandeepp/software-agent-teams --plugin playwright-e2eGuides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Enforces test-driven development: write failing test first, then minimal code to pass. Use when implementing features or bugfixes.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.