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.

MethodFinds
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

OptionTypeDescription
namestring | RegExpAccessible name (case-insensitive string match by default)
exactbooleanRequire exact string match
levelnumberHeading level 1–6
checkedbooleanCheckbox/radio state
disabledbooleanDisabled state
expandedbooleanExpanded state
pressedbooleanPressed state (toggle buttons)
selectedbooleanSelected state (tabs, options)
includeHiddenbooleanInclude 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)

SelectorMeaning
: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
:visibleOnly visible elements
:enabledOnly enabled form controls
:disabledOnly disabled form controls
:checkedChecked 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 over page.$() / 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 with expect(locator).toHaveCount(1) first.