Playwright Cheatsheet
Locators
Use this Playwright reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
Core locator methods
All locators are lazy — nothing is queried until an action or assertion is called.
| Method | Finds |
|---|---|
page.getByRole(role, opts?) | ARIA role + accessible name |
page.getByText(text, opts?) | visible text (partial by default) |
page.getByLabel(text, opts?) | form field by <label> text |
page.getByPlaceholder(text, opts?) | input by placeholder attr |
page.getByAltText(text, opts?) | image by alt attr |
page.getByTitle(text, opts?) | element by title attr |
page.getByTestId(id) | element by data-testid (configurable) |
page.locator(selector) | CSS / XPath / Playwright combos |
getByRole — role list
page.getByRole('button', { name: 'Submit' }) page.getByRole('link', { name: /home/i }) page.getByRole('heading', { name: 'Dashboard', level: 2 }) page.getByRole('checkbox', { checked: true }) page.getByRole('textbox', { name: 'Email' }) page.getByRole('combobox') // <select> or ARIA combobox page.getByRole('listitem') page.getByRole('dialog') page.getByRole('img', { name: 'avatar' }) page.getByRole('tab', { name: 'Settings', selected: true })
getByRole options
| Option | Type | Description |
|---|---|---|
name | string | RegExp | Accessible name (case-insensitive string match by default) |
exact | boolean | Require exact string match |
level | number | Heading level 1–6 |
checked | boolean | Checkbox/radio state |
disabled | boolean | Disabled state |
expanded | boolean | Expanded state |
pressed | boolean | Pressed state (toggle buttons) |
selected | boolean | Selected state (tabs, options) |
includeHidden | boolean | Include ARIA-hidden elements |
getByText options
page.getByText('Sign in') // partial match page.getByText('Sign in', { exact: true }) // whole text page.getByText(/sign in/i) // regex
getByTestId — custom attribute
// Default attribute: data-testid page.getByTestId('submit-button') // Change attribute globally in playwright.config.ts: // use: { testIdAttribute: 'data-cy' } page.getByTestId('login-form') // now matches data-cy="login-form"
locator() — CSS & XPath
page.locator('button') // tag page.locator('.nav-link') // class page.locator('#main-content') // id page.locator('[data-testid="card"]') // attribute page.locator('input[type="email"]') // attribute + tag page.locator('//button[@aria-label="Close"]')// XPath page.locator(':text("Submit")') // Playwright text pseudo-class page.locator(':has-text("Items")') // element containing text page.locator(':nth-match(li, 3)') // nth match across DOM
CSS pseudo-classes (Playwright-specific)
| Selector | Meaning |
|---|---|
:text("foo") | Element whose text content is "foo" |
:text-is("foo") | Exact text match |
:text-matches("foo", "i") | Regex text match |
:has-text("foo") | Element that contains "foo" (including children) |
:has(selector) | Element that contains a matching child |
:is(sel1, sel2) | Either selector |
:visible | Only visible elements |
:enabled | Only enabled form controls |
:disabled | Only disabled form controls |
:checked | Checked checkbox/radio |
:nth-match(tag, n) | nth matched element globally |
:right-of(sel) | Visually to the right |
:left-of(sel) | Visually to the left |
:above(sel) | Visually above |
:below(sel) | Visually below |
:near(sel) | Within 50px proximity |
Chaining locators
// Scope to a subtree const nav = page.locator('nav'); nav.getByRole('link', { name: 'Home' }); // Equivalent shorthand page.locator('nav').getByRole('link', { name: 'Home' });
Filtering locators
// .filter() — narrow an existing locator page.getByRole('listitem').filter({ hasText: 'Product A' }); page.getByRole('listitem').filter({ has: page.getByRole('button', { name: 'Delete' }) }); page.getByRole('listitem').filter({ hasNotText: 'Out of stock' }); // Combine filters page.getByRole('listitem') .filter({ hasText: /in stock/i }) .filter({ has: page.getByRole('checkbox', { checked: true }) });
Locator operators
// and() — element must match BOTH page.getByRole('button').and(page.getByTitle('Submit')); // or() — element matches EITHER page.getByRole('button', { name: 'OK' }).or(page.getByRole('button', { name: 'Confirm' })); // not() — negate page.getByRole('button').filter({ hasNotText: 'Cancel' });
Picking by index
page.getByRole('listitem').first() page.getByRole('listitem').last() page.getByRole('listitem').nth(2) // 0-based
Within a frame
const frame = page.frameLocator('#my-iframe'); await frame.getByRole('button', { name: 'Submit' }).click(); // Named frame const frame2 = page.frameLocator('[name="checkout"]');
Locator in a shadow DOM
Playwright automatically pierces open shadow roots with all locators. No special syntax needed.
Getting DOM elements / values
const el = await locator.elementHandle(); // legacy; prefer locator API const text = await locator.textContent(); const inner = await locator.innerHTML(); const val = await locator.inputValue(); const attr = await locator.getAttribute('href'); const count = await locator.count(); const visible = await locator.isVisible(); const enabled = await locator.isEnabled(); const checked = await locator.isChecked(); const box = await locator.boundingBox(); // { x, y, width, height }
Waiting for a locator state
await locator.waitFor(); // default: 'visible' await locator.waitFor({ state: 'attached' }); await locator.waitFor({ state: 'detached' }); await locator.waitFor({ state: 'hidden' }); await locator.waitFor({ timeout: 5000 });
Common gotchas
Avoid
nth(0)when you want a specific element — prefer semantic locators (getByRole,filter) so tests don't break on reorder.
page.locator()is preferred overpage.$()/page.$$()/elementHandle— the locator API retries on DOM changes automatically.
Strict mode: if a locator matches multiple elements, Playwright throws on actions. Use
.first(),.nth(), or.filter()to disambiguate, or assert withexpect(locator).toHaveCount(1)first.