Playwright selector strategies and patterns
Generates resilient Playwright element selectors using role-based, test ID, and accessibility-focused strategies.
/plugin marketplace add the-answerai/alphaagent-team/plugin install aai-stack-playwright@alphaagent-teamThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Strategies for selecting elements in Playwright tests.
// Buttons
await page.getByRole('button', { name: 'Submit' })
await page.getByRole('button', { name: /submit/i })
// Links
await page.getByRole('link', { name: 'Home' })
await page.getByRole('link', { name: 'Learn more' }).first()
// Form elements
await page.getByRole('textbox', { name: 'Email' })
await page.getByRole('checkbox', { name: 'Accept terms' })
await page.getByRole('combobox', { name: 'Country' })
await page.getByRole('radio', { name: 'Option A' })
// Headings
await page.getByRole('heading', { name: 'Welcome' })
await page.getByRole('heading', { level: 1 })
// Navigation
await page.getByRole('navigation')
await page.getByRole('main')
await page.getByRole('dialog')
// Lists
await page.getByRole('list')
await page.getByRole('listitem')
// Tables
await page.getByRole('table')
await page.getByRole('row')
await page.getByRole('cell', { name: 'Value' })
// Single element
await page.getByTestId('submit-button')
await page.getByTestId('user-profile')
// In HTML
<button data-testid="submit-button">Submit</button>
// Configure attribute name
// playwright.config.ts
export default defineConfig({
use: {
testIdAttribute: 'data-test-id',
},
})
// Text inputs
await page.getByLabel('Email')
await page.getByLabel('Password')
// With exact match
await page.getByLabel('Email', { exact: true })
// Associated label
<label for="email">Email</label>
<input id="email" type="email" />
await page.getByPlaceholder('Enter your email')
await page.getByPlaceholder('Search...')
// Contains text
await page.getByText('Welcome')
// Exact match
await page.getByText('Welcome', { exact: true })
// Regex
await page.getByText(/welcome/i)
// In specific element
await page.locator('h1').getByText('Welcome')
// Images
await page.getByAltText('Company logo')
await page.getByAltText(/product image/i)
await page.getByTitle('Close dialog')
await page.getByTitle(/tooltip/i)
// By class
await page.locator('.submit-button')
await page.locator('.card.featured')
// By ID
await page.locator('#main-content')
// By attribute
await page.locator('[data-state="active"]')
await page.locator('input[type="email"]')
await page.locator('[aria-expanded="true"]')
// Descendant
await page.locator('.card .title')
await page.locator('#form input')
// Child
await page.locator('.list > li')
// Sibling
await page.locator('.label + input')
// First/last
await page.locator('.item:first-child')
await page.locator('.item:last-child')
await page.locator('.item:nth-child(2)')
// State
await page.locator('button:enabled')
await page.locator('input:disabled')
await page.locator('input:focus')
await page.locator('input:checked')
// Content
await page.locator('button:has-text("Submit")')
await page.locator('div:has(.icon)')
// Basic XPath
await page.locator('xpath=//button[@id="submit"]')
await page.locator('xpath=//div[contains(@class, "card")]')
// Text content
await page.locator('xpath=//button[text()="Submit"]')
await page.locator('xpath=//h1[contains(text(), "Welcome")]')
// Axes
await page.locator('xpath=//div[@class="card"]//button')
await page.locator('xpath=//label[text()="Email"]/following-sibling::input')
// Chain methods
const row = page.getByRole('row', { name: 'John' })
const editButton = row.getByRole('button', { name: 'Edit' })
// Nested locators
const card = page.locator('.card').filter({ hasText: 'Featured' })
const title = card.locator('.title')
// Filter by text
await page.locator('.item').filter({ hasText: 'Active' })
// Filter by not having text
await page.locator('.item').filter({ hasNotText: 'Deleted' })
// Filter by child element
await page.locator('.card').filter({ has: page.locator('.badge') })
// Filter by not having child
await page.locator('.card').filter({ hasNot: page.locator('.error') })
// First match
await page.locator('.item').first()
// Last match
await page.locator('.item').last()
// By index
await page.locator('.item').nth(2)
// Count
const count = await page.locator('.item').count()
// All elements
const items = await page.locator('.item').all()
for (const item of items) {
await item.click()
}
// By name/id
const frame = page.frame('frame-name')
// By URL
const frame = page.frame({ url: /embedded/ })
// Frame locator
const frameLocator = page.frameLocator('iframe.embed')
await frameLocator.getByRole('button').click()
// Nested frames
const nestedFrame = page.frameLocator('iframe').frameLocator('iframe')
// Playwright automatically pierces open shadow DOM
await page.locator('custom-element').locator('.inner-element')
// Explicit shadow piercing
await page.locator('custom-element >> .inner-element')
// Position-based
await page.locator('.button').locator(':near(.icon)')
await page.locator('.button').locator(':left-of(.other)')
await page.locator('.button').locator(':right-of(.other)')
await page.locator('.button').locator(':above(.other)')
await page.locator('.button').locator(':below(.other)')
// Auto-waiting (built-in)
await page.getByRole('button').click() // Waits for element
// Custom wait
await page.getByRole('button').waitFor()
await page.getByRole('button').waitFor({ state: 'visible' })
await page.getByRole('button').waitFor({ state: 'hidden' })
await page.getByRole('button').waitFor({ state: 'attached' })
await page.getByRole('button').waitFor({ state: 'detached' })
// With timeout
await page.getByRole('button').waitFor({ timeout: 5000 })
1. getByRole() - Most resilient, tests accessibility
2. getByTestId() - Explicit, decoupled from UI
3. getByLabel() - Good for forms
4. getByPlaceholder()- When label not available
5. getByText() - For unique visible text
6. CSS selectors - When semantic selectors not available
7. XPath - Complex traversal only
// BAD: Brittle selectors
await page.locator('.btn-primary.ml-2.mt-3') // Styling classes
await page.locator('div > div > div > button') // Structure-dependent
await page.locator('[class*="Button-module"]') // Generated classes
// GOOD: Resilient selectors
await page.getByRole('button', { name: 'Submit' })
await page.getByTestId('submit-form')
await page.getByLabel('Email address')
Used by:
frontend-developer agentfullstack-developer agentCreating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.
Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.
Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations.