Codegen locator recommendation strategy
Codegen analyzes the rendered page and recommends the best locator, prioritizing role, text, and test id locators in that order. When multiple elements match a locator, the generator improves it to uniquely identify the target element, reducing test failures and flakiness.
Using custom selector engines in tests
After registration, custom selector engines can be used with the prefix specified during registration (e.g., 'tag=button'). Custom selectors can be combined with built-in locators like getByText(). They can be used in any methods supporting selectors, such as locator(), expect(), and count().
Custom selector engines registration
Playwright supports custom selector engines registered with the Selectors.register method. Selector engines must have a query function to query the first element matching a selector relative to the root, and a queryAll function to query all elements matching a selector relative to the root.
Custom selector engine content script mode
Custom selector engines can be registered with {contentScript: true} option to isolate the engine from any JavaScript in the frame while leaving access to the DOM. Content script engines are safer because they are protected from tampering with global objects, such as altering Node.prototype methods. All built-in selector engines run as content scripts. Running as a content script is not guaranteed when the engine is used together with other custom engines.
Custom selector engines must be registered before page creation
Selectors must be registered before creating the page.
Custom selector engine example with fixture
Example showing how to create and register a custom selector engine for tag-based queries using a fixture with worker scope: const createTagNameEngine = () => ({ query(root, selector) { return root.querySelector(selector); }, queryAll(root, selector) { return Array.from(root.querySelectorAll(selector)); } }); export const test = base.extend<{}, { selectorRegistration: void }>({ selectorRegistration: [async ({ playwright }, use) => { await playwright.selectors.register('tag', createTagNameEngine); await use(); }, { scope: 'worker', auto: true }], });
Interacting with frame objects directly
Once a Frame object is obtained via page.frame(), it can be used to interact with elements directly using methods like fill(). For example, frame.fill('#username-input', 'John') fills an input element in the frame.
Page can have one or more Frame objects
A Page can have one or more Frame objects attached to it. Each page has a main frame and page-level interactions (like click) are assumed to operate in the main frame.
Frames created with iframe HTML tag
A page can have additional frames attached with the iframe HTML tag. These frames can be accessed for interactions inside the frame.
frameLocator() method to locate elements in frames
The frameLocator() method is used to locate a frame and then interact with elements inside it. It returns a locator that operates within the frame context. For example, page.frameLocator('.frame-class').getByLabel('User Name') locates an element with a label inside a frame with class 'frame-class'.
Frame objects accessed via Page.frame() API
Frame objects can be accessed using the Page.frame() API. The frame can be retrieved by the frame's name attribute using page.frame('name') or by the frame's URL using page.frame({ url: /pattern/ }).
frameLocator vs frame object - locator vs accessor
frameLocator() is used to locate elements inside a frame and returns a locator, while page.frame() returns the actual Frame object which can be used for interactions. Both approaches work but serve different purposes.
Live debugging with locators in VS Code
With 'Show Browsers' enabled, click on a locator in your code. Playwright highlights the corresponding element in the browser, making it easy to verify locators.
Pick locator tool in VS Code
Use the 'Pick locator' tool to click on any element in the opened browser. Playwright determines the best locator and copies it to your clipboard, ready to be pasted into your code.
Use codegen command to generate locators
Run the codegen command followed by a URL to open a browser window and the Playwright inspector. Click the 'Record' button to stop recording, then the 'Pick Locator' button becomes available. Hover over elements to see locators highlighted below your cursor. Click an element to add its locator to the inspector. You can copy the locator or edit it in the inspector to explore variations.
Use VS Code extension to generate locators
The VS Code Extension can generate locators and record tests. It also gives a great developer experience when writing, running, and debugging tests.
Use locators for finding elements
Use Playwright's built-in locators to find elements on webpages. Locators come with auto-waiting and retry-ability. Auto-waiting means Playwright performs actionability checks such as ensuring the element is visible and enabled before performing actions. Prioritize user-facing attributes and explicit contracts over implementation details.
Prefer role locators over CSS selectors and XPath
Use page.getByRole() locators like page.getByRole('button', { name: 'submit' }) instead of CSS class selectors like 'button.buttonIcon.episode-actions-later' or XPath. Role-based locators are resilient to DOM structure changes because they rely on user-visible attributes rather than implementation details.
Chain and filter locators to narrow searches
Locators can be chained to narrow down searches to a particular part of the page. For example: page.getByRole('listitem').filter({ hasText: 'Product 2' }).getByRole('button', { name: 'Add to cart' }).click(). You can filter locators by text or by another locator.
codegen command examples
npm: npx playwright codegen playwright.dev
yarn: yarn playwright codegen playwright.dev
pnpm: pnpm exec playwright codegen playwright.dev
Page object model: querying multiple elements with complex selectors
Locators can select multiple elements using complex CSS selectors. For example, page.locator('article div.markdown ul > li > a') selects all anchor elements that are direct children of list items within an unordered list that is a child of a div with class markdown inside an article element.
Page object model: Locator definition with hasText option
Locators in page objects can be defined using options like hasText to filter elements. For example, page.locator('a', { hasText: 'Get started' }) selects an anchor element containing the text 'Get started'. Locators can also be chained: page.locator('li', { hasText: 'Guides' }).locator('a', { hasText: 'Page Object Model' }).
Page object model: using first() to get single element
When a locator selector matches multiple elements, use the first() method to get the first matching element. For example, this.getStartedLink.first().click() clicks the first element matching the getStartedLink selector.
Protractor ElementFinder to Playwright locators equivalence
Protractor's ElementFinder API maps to Playwright Test Locator. Key migrations: element(by.buttonText('...')) becomes page.locator('button, input[type="button"], input[type="submit"] >> text="..."'), element(by.css('...')) becomes page.locator('...'), element(by.cssContainingText('..1..', '..2..')) becomes page.locator('..1.. >> text=..2..'), element(by.id('...')) becomes page.locator('#...'), element(by.model('...')) becomes page.locator('[ng-model="..."]'), element(by.repeater('...')) becomes page.locator('[ng-repeat="..."]'), element(by.xpath('...')) becomes page.locator('xpath=...'), and element.all becomes page.locator.
Protractor browser methods to Playwright page methods
Protractor's browser.get(url) becomes await page.goto(url) in Playwright Test. Protractor's browser.getCurrentUrl() becomes page.url().
Playwright locator nth() method replaces element.all().get()
In Protractor, element.all(by.repeater(...)).get(2) retrieves the element at index 2. In Playwright Test, this becomes page.locator('[ng-repeat="..."]').nth(2).
Locators are strict by default
Locators in Playwright are strict, meaning that all operations on locators that imply some target DOM element will throw an exception if more than one element matches the given selector.
Locator creation is synchronous
Locator creation with page.locator() is one of the few methods that is synchronous and does not require await.
CSS :near() pseudo-class behavior
:near(div > button) matches elements that are near (within 50 CSS pixels) any element matching the inner selector. Resulting matches are sorted by their distance to the anchor element.
CSS locator pierce open shadow DOM
Playwright CSS selectors pierce open shadow DOM automatically. Standard CSS selectors alone do not pierce shadow DOM, but Playwright augments them to do so.
CSS custom pseudo-classes available
Playwright adds custom pseudo-classes to CSS selectors: :visible, :has-text(), :has(), :is(), :nth-match(), :right-of(), :left-of(), :above(), :below(), :near(), :text(), :text-is(), and :text-matches().
CSS :has-text() pseudo-class behavior
:has-text() matches any element containing specified text somewhere inside, possibly in a child or descendant element. Matching is case-insensitive, trims whitespace, and searches for a substring. Must be used together with other CSS specifiers, otherwise it matches all elements containing the text including <body>. For example, 'article:has-text("Playwright")' is correct, but ':has-text("Playwright")' is incorrect.
CSS :text() pseudo-class behavior
:text() matches the smallest element containing specified text. Matching is case-insensitive, trims whitespace, and searches for a substring. For example, '#nav-bar :text("Home")' finds an element with text 'Home' inside the #nav-bar element.
CSS :text-is() pseudo-class behavior
:text-is() matches the smallest element with exact text. Exact matching is case-sensitive, trims whitespace, and searches for the full string. For example, :text-is("Log") does not match <button>Log in</button> because the button contains a single text node "Log in" that is not equal to "Log". However, :text-is("Log") matches <button> Log <span>in</span></button> because the button contains a text node " Log ".
CSS :text-matches() pseudo-class behavior
:text-matches() matches the smallest element with text content matching a JavaScript-like regex. For example, :text-matches("Log\s*in", "i") matches <button>Login</button> and <button>log IN</button>.
Input button and submit matched by value not text
Input elements of the type 'button' and 'submit' are matched by their 'value' attribute instead of text content. For example, :text("Log in") matches <input type=button value="Log in">.
CSS :visible pseudo-class behavior
:visible pseudo-class in CSS selectors matches only visible elements. For example, 'css=button' matches all buttons, while 'css=button:visible' only matches visible buttons.
CSS :has() pseudo-class behavior
:has() is a CSS pseudo-class that returns an element if any of the selectors passed as parameters relative to the :scope of the given element match at least one element. For example, 'article:has(div.promo)' returns the article element that contains a div with class promo.
CSS comma-separated selector list behavior
Comma-separated list of CSS selectors matches all elements that can be selected by one of the selectors in that list. For example, 'button:has-text("Log in"), button:has-text("Sign in")' matches buttons with either text.
Layout CSS pseudo-classes deprecated
Layout CSS pseudo-classes like :right-of(), :left-of(), :above(), :below(), :near() are deprecated and may be removed in the future. They may produce unexpected results as a different element could be matched when layout changes by one pixel.
CSS :right-of() pseudo-class behavior
:right-of(div > button) matches elements that are to the right of any element matching the inner selector, at any vertical position. For example, 'input:right-of(:text("Username"))' fills an input to the right of 'Username'.
CSS :left-of() pseudo-class behavior
:left-of(div > button) matches elements that are to the left of any element matching the inner selector, at any vertical position.
CSS :below() pseudo-class behavior
:below(div > button) matches elements that are below any element matching the inner selector, at any horizontal position.
Layout pseudo-class distance parameter
All layout pseudo-classes support an optional maximum pixel distance as the last argument. For example, 'button:near(:text("Username"), 120)' matches a button that is at most 120 CSS pixels away from the element with text 'Username'.
Layout pseudo-class distance uses bounding client rect
Layout pseudo-classes use bounding client rect to compute distance and relative position of elements.
Layout pseudo-class use with Locator.first()
Layout pseudo-classes result matches are sorted by distance to the anchor element, so Locator.first() can be used to pick the closest one. This is only useful if there is a list of similar elements where the closest is obviously the right one. Using Locator.first() in other cases most likely won't work as expected.
CSS :nth-match() pseudo-class behavior
:nth-match(:text("Buy"), 3) selects the nth element matching a selector. Index is one-based. Elements do not have to be siblings; they can be anywhere on the page. For example, :nth-match(:text("Buy"), 3) selects the third button with text 'Buy' anywhere on the page.
CSS :nth-match() with waitFor example
:nth-match() is useful to wait until a specified number of elements appear. For example, page.locator(':nth-match(:text("Buy"), 3)').waitFor() waits until all three buttons are visible.
nth= locator uses zero-based index
The 'nth=' locator is used to narrow down a query to the n-th match using a zero-based index. Positive index selects from the start (0 is first), negative index selects from the end (-1 is last). For example, page.locator('button').locator('nth=0').click() clicks the first button, and page.locator('button').locator('nth=-1').click() clicks the last button.
Parent element locator with filter example
To target a parent element of another element, use Locator.filter() with a child locator. For example, const child = page.getByText('Hello'); const parent = page.getByRole('listitem').filter({ has: child }); targets the parent <li> of a label with text 'Hello'.
Parent element locator with xpath
Alternatively, use 'xpath=..' to locate a parent element. However, this method is not reliable because any changes to DOM structure will break tests. Use Locator.filter() when possible. For example, page.getByText('Hello').locator('xpath=..') locates the parent element.
XPath locator syntax
XPath locators are equivalent to calling Document.evaluate. Use 'xpath=' prefix, for example 'xpath=//button'. Any selector string starting with '//' or '..' is assumed to be an xpath selector, so Playwright converts '//html/body' to 'xpath=//html/body' automatically.
XPath does not pierce shadow DOM
XPath selectors do not pierce shadow roots.
XPath union with pipe operator
Pipe operator (|) in XPath can be used to specify multiple selectors. It matches all elements that can be selected by one of the selectors in the list. For example, '//span[contains(@class, 'spinner__loading')]|//div[@id='confirmation']' matches either a confirmation dialog or a load spinner.
Label-to-form control retargeting behavior
Targeted input actions in Playwright automatically distinguish between labels and controls. When targeting a label, certain actions perform on the associated control instead. Locator.click() clicks the label and focuses the input; Locator.fill() fills the input field; Locator.inputValue() returns the input value; Locator.selectText() selects text in the input; Locator.setInputFiles() sets files for file input; Locator.selectOption() selects an option from select box.
Label-to-form control retargeting assertion behavior
When using assertions on a label locator, other methods like LocatorAssertions.toHaveText() target the label itself, not the associated control. For example, expect(page.locator('label')).toHaveText('Password') asserts the text content of the label, not the input field.
Legacy text locator syntax
Legacy text locator matches elements that contain passed text. Use 'text=Log in' format. Any string selector starting and ending with a quote is assumed to be a legacy text locator. For example, '"Log in"' is converted to 'text="Log in"' internally.
Legacy text locator default matching
'text=Log in' - default matching is case-insensitive, trims whitespace, and searches for a substring. For example, 'text=Log' matches <button>Log in</button>.
Legacy text locator exact matching with quotes
'text="Log in"' - text body can be escaped with single or double quotes to search for a text node with exact content after trimming whitespace. Quoted body is case-sensitive. For example, 'text="Log"' does not match <button>Log in</button> but matches <button> Log <span>in</span></button>. Use backslash to escape quotes in quoted string: 'text="foo\"bar"'.
Legacy text locator regex matching
Legacy text locator body can be a JavaScript-like regex wrapped in forward slashes. For example, 'text=/Log\s*in/i' matches <button>Login</button> and <button>log IN</button>.