diff --git a/docs/src/locators.md b/docs/src/locators.md index f27c8676e9..bfb4564138 100644 --- a/docs/src/locators.md +++ b/docs/src/locators.md @@ -10,13 +10,13 @@ a way to find element(s) on the page at any moment. These are the recommended built in locators. -- [`method: Page.getByRole`](#locate-based-on-accessible-attributes) to locate by explicit and implicit accessibility attributes. +- [`method: Page.getByRole`](#locate-by-role) to locate by explicit and implicit accessibility attributes. - [`method: Page.getByText`](#locate-by-text) to locate by text content. -- [`method: Page.getByLabel`](#locate-by-label-text) to locate a form control by associated label's text. -- [`method: Page.getByPlaceholder`](#locate-by-placeholder-text) to locate an input by placeholder. +- [`method: Page.getByLabel`](#locate-by-label) to locate a form control by associated label's text. +- [`method: Page.getByPlaceholder`](#locate-by-placeholder) to locate an input by placeholder. - [`method: Page.getByAltText`](#locate-by-alt-text) to locate an element, usually image, by its text alternative. -- [`method: Page.getByTitle`](#locate-by-title) to locate an element by its title. -- [`method: Page.getByTestId`](#define-explicit-contract-and-use-a-data-testid-attribute) to locate an element based on its `data-testid` attribute (other attribute can be configured). +- [`method: Page.getByTitle`](#locate-by-title) to locate an element by its title attribute. +- [`method: Page.getByTestId`](#locate-by-testid) to locate an element based on its `data-testid` attribute (other attributes can be configured). ```js await page.getByLabel('User Name').fill('John'); @@ -63,195 +63,211 @@ await page.GetByLabel("User Name").FillAsync("John"); await page.GetByLabel("Password").FillAsync("secret-password"); -await page.GetByRole("button", new() { Name = "Sign in" }).ClickAsync(); +await page.GetByRole("button", new() { NameString = "Sign in" }).ClickAsync(); await Expect(page.GetByText("Welcome, John!")).ToBeVisibleAsync(); ``` -Every time locator is used for some action, up-to-date DOM element is located in the page. So in the snippet -below, underlying DOM element is going to be located twice, prior to every action. This means that if the +## Locating elements + +Playwright comes with multiple built-in locators. To make tests resilient, we recommend prioritizing user-facing attributes and explicit contracts such as [`method: Page.getByRole`]. + +For example, consider the following DOM structure. + +```html + +``` +Locate the element by its role of `button` with name "Sign in". + +```js +await page.getByRole('button', { name: 'Sign in' }) + .click(); +``` +```java +page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign in")) + .click(); +``` +```python async +await page.get_by_role("button", name="Sign in").click() +``` +```python sync +page.get_by_role("button", name="Sign in").click() +``` +```csharp +await page.GetByRole("button", new() { NameString = "Sign in" }) + .ClickAsync(); +``` + +:::tip +Use the [code generator](./codegen.md) to generate a locator, and then edit it as you'd like. +::: + +Every time a locator is used for an action, an up-to-date DOM element is located in the page. In the snippet +below, the underlying DOM element will be located twice, once prior to every action. This means that if the DOM changes in between the calls due to re-render, the new element corresponding to the locator will be used. ```js -const locator = page.getByText('Submit'); -// ... +const locator = page.getByRole('button', { name: 'Sign in' }) + await locator.hover(); await locator.click(); ``` ```java -Locator locator = page.getByText("Submit"); +Locator locator = page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign in")) + locator.hover(); locator.click(); ``` ```python async -locator = page.get_by_text("Submit") +locator = page.get_by_role("button", name="Sign in") + await locator.hover() await locator.click() ``` ```python sync -locator = page.get_by_text("Submit") +locator = page.get_by_role("button", name="Sign in") + locator.hover() locator.click() ``` ```csharp -var locator = page.GetByText("Submit"); +var locator = page.GetByRole("button", new() { NameString = "Sign in" }) + await locator.HoverAsync(); await locator.ClickAsync(); ``` -## Strictness - -Locators are strict. This means that all operations on locators that imply -some target DOM element will throw an exception if more than one element matches -given selector. For example, the following call throws if there are several buttons in the DOM: - -```js -await page.getByRole('button').click(); -``` - -```python async -await page.get_by_role("button").click() -``` - -```python sync -page.get_by_role("button").click() -``` - -```java -page.getByRole("button").click(); -``` - -```csharp -await page.GetByRole("button").ClickAsync(); -``` - -On the other hand, Playwright understands when you perform a multiple-element operation, -so the following call works perfectly fine when locator resolves to multiple elements. - -```js -await page.getByRole('button').count(); -``` - -```python async -await page.get_by_role("button").count() -``` - -```python sync -page.get_by_role("button").count() -``` - -```java -page.getByRole("button").count(); -``` - -```csharp -await page.GetByRole("button").CountAsync(); -``` - -You can explicitly opt-out from strictness check by telling Playwright which element to use when multiple element match, through [`method: Locator.first`], [`method: Locator.last`], and [`method: Locator.nth`]. These methods are **not recommended** because when your page changes, Playwright may click on an element you did not intend. Instead, follow best practices below to create a locator that uniquely identifies the target element. - - -## Locating elements - -Playwright comes with multiple built-in ways to create a locator. To make tests resilient, we recommend prioritizing user-facing attributes and explicit contracts, and provide dedicated methods for them, such as [`method: Page.getByText`]. It is often convenient to use the [code generator](./codegen.md) to generate a locator, and then edit it as you'd like. - -```js -await page.getByText('Log in').click(); -``` -```java -page.getByText("Log in").click(); -``` -```python async -await page.get_by_text("Log in").click() -``` -```python sync -page.get_by_text("Log in").click() -``` -```csharp -await page.GetByText("Log in").ClickAsync(); -``` - -If you absolutely must use CSS or XPath locators, you can use [`method: Page.locator`] to create a locator that takes a [selector](./selectors.md) describing how to find an element in the page. - Note that all methods that create a locator, such as [`method: Page.getByLabel`], are also available on the [Locator] and [FrameLocator] classes, so you can chain them and iteratively narrow down your locator. ```js -const locator = page.frameLocator('#my-frame').getByText('Submit'); +const locator = page.frameLocator('#my-frame') + .getByRole('button', { name: 'Sign in' }); + await locator.click(); ``` ```java -Locator locator = page.frameLocator("#my-frame").getByText("Submit"); +Locator locator = page.frameLocator("#my-frame") + .getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign in")); + locator.click(); ``` ```python async -locator = page.frame_locator("#my-frame").get_by_text("Submit") +locator = page.frame_locator("#my-frame") + .get_by_role("button", name="Sign in") + await locator.click() ``` ```python sync -locator = page.frame_locator("my-frame").get_by_text("Submit") +locator = page.frame_locator("my-frame") + .get_by_role("button", name="Sign in") + locator.click() ``` ```csharp -var locator = page.FrameLocator("#my-frame").GetByText("Submit"); +var locator = page.FrameLocator("#my-frame") + .GetByRole("button", new() { NameString = "Sign in" }); + await locator.ClickAsync(); ``` -### Locate based on accessible attributes +### Locate by role -The [`method: Page.getByRole`] locator reflects how users and assistive technology perceive the page, for example whether some element is a button or a checkbox. When locating by role, you should usually pass the accessible name as well, so that locator pinpoints the exact element. +The [`method: Page.getByRole`] locator reflects how users and assistive technology perceive the page, for example whether some element is a button or a checkbox. When locating by role, you should usually pass the accessible name as well, so that the locator pinpoints the exact element. + +For example, consider the following DOM structure. + +```html +