Locators
An ElementLocator describes how to find elements. Creating one finds nothing: each action, read, or expectation looks the element up again when it runs, so a locator stays correct after the page changes, and an element that is replaced while an action waits for it is found again.
Page, Frame, and ElementLocator all have the methods below. On a page they search its main frame; on a locator they search within the elements it finds.
Finding Elements as a User Sees Them
Prefer locators that describe what a user sees, which change less often than a page's structure:
await page.GetByRole("button", "Sign in").ClickAsync();
await page.GetByLabel("Email address").FillAsync("ada@example.com");
await page.GetByPlaceholder("Search").FillAsync("lovelace");
await page.GetByText("Forgot your password?").ClickAsync();
await page.GetByAltText("Company logo").HoverAsync();
await page.GetByTitle("Close").ClickAsync();
await page.GetByTestId("checkout").ClickAsync();
| Method | Finds elements by | Matching |
|---|---|---|
GetByRole(role, name, states) |
The accessibility role the browser computes, such as button, link, heading, or checkbox, and optionally the accessible name |
The name must match exactly |
GetByLabel(text) |
The element aria-labelledby names; else aria-label; else a form control's <label> |
Contains the text, ignoring case and runs of whitespace |
GetByText(text) |
Rendered text | Contains the text, ignoring case |
GetByPlaceholder(text) |
The placeholder attribute |
Contains the text, ignoring case |
GetByAltText(text) |
The alt attribute |
Contains the text, ignoring case |
GetByTitle(text) |
The title attribute |
Contains the text, ignoring case |
GetByTestId(id) |
The test ID attribute, data-testid unless configured |
Exact |
Each method that takes text also takes exact: true, for a whole match with case.
GetByRole matches the role and name the browser computes. Browsers call the image role image, its name since ARIA 1.3; GetByRole asks for img as image, so either finds images. The names in an accessibility snapshot are computed in the page and can differ from the browser's; act on an element from a snapshot through its ref.
GetByRole also takes the ARIA states an element must have:
await page.GetByRole("checkbox", "Remember me", new RoleStates() { Checked = ToggleState.Off }).CheckAsync();
await page.GetByRole("heading", states: new RoleStates() { Level = 2 }).First().ClickAsync();
await page.GetByRole("button", "Menu", new RoleStates() { Expanded = false }).ClickAsync();
Firefox:
GetByTextuses WebDriver BiDi'sinnerTextlocator, which Firefox does not yet support (bug 1869538). In Firefox, find the element another way, such as by its role and name.
CSS and XPath
WebDriver BiDi's own locators can be used directly:
await page.Locate(new CssLocator("form#login button[type=submit]")).ClickAsync();
await page.Locate(new XPathLocator("//table/tbody/tr[1]/td[2]")).ClickAsync();
CssLocator, XPathLocator, InnerTextLocator, and AccessibilityLocator are in the WebDriverBiDi.BrowsingContext namespace.
Chaining and Filtering
A locator's methods create a new locator that searches within what it finds. Filter keeps the elements that contain, or do not contain, some text or another element. And and Or combine two locators:
// Within the dialog, the button named Save.
ElementLocator dialog = page.GetByRole("dialog");
await dialog.GetByRole("button", "Save").ClickAsync();
// The row that mentions Ada and has a Delete button, then that button.
ElementLocator row = page.GetByRole("row").Filter(hasText: "Ada", has: page.GetByRole("button", "Delete"));
await row.GetByRole("button", "Delete").ClickAsync();
// Elements both locators find, and elements either finds.
ElementLocator enabledSubmit = page.GetByRole("button").And(page.Locate(new CssLocator("[type=submit]:enabled")));
ElementLocator signInOrUp = page.GetByRole("link", "Sign in").Or(page.GetByRole("link", "Sign up"));
Filter's text conditions compare rendered text, ignoring case and runs of whitespace. Its element conditions search within each element found, so they cost a lookup for each. The locators given to Filter, And, and Or must search the same frame.
One Element or Many
An action, a read, or an expectation about one element requires its locator to find exactly one, and throws AmbiguousElementException at once if it finds several. Narrow the locator, or choose an element:
ElementLocator items = page.GetByRole("listitem");
int count = await items.CountAsync();
await items.First().ClickAsync();
await items.Nth(2).ClickAsync();
await items.Last().ClickAsync();
Nth counts from zero, in document order. CountAsync, ToHaveCountAsync, and the expectations about a list of texts work with every element a locator finds.
Shadow DOM and Frames
ShadowRoot() continues a search inside the shadow roots, open or closed, of the elements found. With PierceShadowRoots set, lookups also search open shadow roots automatically. Browsers do not evaluate XPath within a shadow root, and Chrome's role lookups reach every shadow root, closed ones included.
Each frame is searched separately. ContentFrameAsync returns the frame of an iframe or frame element, once its document has loaded:
// A closed shadow root is reached from its host.
await page.Locate(new CssLocator("date-picker")).ShadowRoot().GetByRole("button", "Next month").ClickAsync();
// A frame is found from the element that holds it.
Frame payment = await page.Locate(new CssLocator("iframe[name=payment]")).ContentFrameAsync();
await payment.GetByLabel("Card number").FillAsync("4242 4242 4242 4242");
A Frame is fixed to one document: if the iframe's document is replaced, the frame reports IsDetached, and ContentFrameAsync finds the new one.
Waiting for a State
Actions wait for their element. To wait for an element itself, such as for a spinner to go away, use WaitForAsync, or an expectation:
await page.GetByRole("progressbar").WaitForAsync(ElementState.Detached);
ElementState is Attached, Detached, Visible (the default), or Hidden; an element that does not exist is hidden.