Playwright
What Is Playwright?
Playwright is a relative freshman in the world of automated testing, rapidly gaining popularity for its robust features and capabilities. Developed by Microsoft, it's designed to simplify the process of writing reliable and robust end-to-end tests, especially for complex modern web applications. Its growing community and frequent updates make it a compelling option for teams looking to streamline their testing processes.
What Are the Playwright’s Advantages?
Let’s now take a closer look at the benefits that the Playwright automation testing tool offers:
- Testing across multiple browsers: Playwright supports testing across multiple browsers, including Chrome, Firefox, and Safari, as well as different browser versions, ensuring comprehensive cross-browser compatibility checks;
- Headless mode support: It provides the ability to run tests in headless mode, which is particularly useful for continuous integration environments where you don't need a UI for each test;
- Native mobile environments emulation: Unlike Selenium, Playwright can emulate mobile environments natively, offering more accurate testing scenarios for mobile web applications;
- Rich API for automation: Playwright comes with a rich set of APIs that make it easier to deal with modern web app features, including complex AJAX-based interactions, shadow DOM, and more;
- Handling of modern web features: It can efficiently handle modern web features like single-page-applications (SPAs), making it a suitable choice for testing advanced web applications;
- Impressive language support: Playwright supports multiple programming languages including JavaScript, Python, C#, and Java, offering flexibility in terms of development language choices;
- Auto-wait feature: One of the standout features of Playwright is its auto-wait functionality. This feature automatically waits for elements to be ready before executing actions, reducing the number of explicit waits and sleep commands in test scripts;
- A set of debugging tools: Playwright provides a rich set of debugging tools that make it easier to diagnose and fix issues in tests.
As you can see, Playwright also offers a rich set of testing features, and at some points, it’s even better than Selenium, for example, it offers mobile environment emulation and modern web features handling. But, of course, it also has some drawbacks, which we’ll consider in the next paragraph.
What Are the Playwright’s Disadvantages?
So, what are the cons of using the Playwright automation tool for testing? Here we go:
- Relative newness: Being relatively new in the market, Playwright might lack the extensive community support and breadth of resources available for more established tools like Selenium. This can be a challenge when looking for solutions to specific problems or when requiring detailed documentation;
- Limited browser support: Despite supporting multiple major browsers, Playwright’s coverage is not as extensive as Selenium's. This might be a limitation when working with less common browsers or specific browser versions;
- Steep learning curve: For teams already familiar with Selenium, transitioning to Playwright can require a significant learning curve. The difference in architecture and API design means existing knowledge and scripts may not be directly transferable;
- Ecosystem integration: Integrating Playwright into existing testing ecosystems that were built around other tools can be challenging. This includes adapting to different reporting tools, CI/CD pipelines, and other integrations;
- Limited mobile testing: While Playwright offers native mobile emulation, it might not fully replace the need for dedicated mobile testing tools, especially for complex mobile applications;
- Less extensive plugin ecosystem: Compared to Selenium, Playwright’s plugin and extension ecosystem is less developed. This can be a drawback for teams relying on specific plugins or extensions for enhanced testing capabilities.
We’d like to mention that despite these disadvantages, Playwright remains a powerful automation tool, especially for testing modern web applications. Teams considering Playwright should weigh these challenges against its benefits to determine if it aligns with their specific testing requirements and infrastructure.
Playwright vs. Selenium: A Comparison Table
When it comes to choosing the right automation tool for web application testing, both Playwright and Selenium offer unique features and capabilities. Let's compare these two prominent testing tools across various criteria:
Thus, both tools have their strengths and drawbacks, so the best choice may involve using a combination of both to leverage the unique benefits they offer.
Which One to Choose?
Deciding between Playwright and Selenium automated testing is not a straightforward choice. It primarily depends on your specific project requirements, team skill set, and the type of application you are testing. Here are some considerations to help you make an informed decision:
- If your project is heavily JavaScript or TypeScript-based, Playwright might be a more natural fit due to its native support for these languages. For projects using other languages like Java, Python, or C#, Selenium could be more suitable due to its broader language support.
- If you're looking for a tool with a straightforward setup, Playwright offers an easier installation process. Selenium might require more configuration but is a more familiar choice for many teams.
- Playwright is ideal for modern web applications that require testing in Chromium, Firefox, and WebKit. For broader browser support, including older browser versions, Selenium is the go-to tool.
- Playwright offers a faster execution speed due to its more integrated architecture. Selenium might experience latency issues but is robust for diverse testing scenarios.
- If testing on real devices is a critical requirement, Selenium’s integration with device farms is advantageous. Playwright primarily focuses on emulators and simulators.
Driving the line, both tools have their merits. For teams aligned with modern JavaScript development and seeking faster performance, the Playwright automation tool is an excellent choice. Conversely, Selenium is unmatched in its versatility and broad language support, making it suitable for diverse and complex testing environments. Ultimately, the decision may involve considering a combination of both, depending on the varied needs of different projects.
Which Is Faster: Playwright or Selenium?
Playwright offers faster execution speeds compared to Selenium. This is due to Playwright’s more modern architecture and direct communication with browsers, which reduces latency.
Is Playwright better than Selenium?
Whether Playwright is better than Selenium depends on the specific needs of a project. Playwright is often preferred for its speed, native support for modern JavaScript frameworks, and straightforward setup. However, Selenium's broader language support and established ecosystem make it a better choice for some projects.
Why Is Playwright so Popular?
Playwright has gained popularity for several reasons: it offers native end-to-end test support for all major web browsers; its API is straightforward, making test writing more accessible; it provides advanced features like network interception, emulation capabilities, and cross-browser testing out of the box.
Playwright Architecture:
Client: In the client side our automation test code will be written in different languages such as javascript, typescript, C#.net, python and Java .
Server: The server communicates between client and web browser engines such as chromium, firefox and webkit etc..
playwright uses CDP to communicate with chromium or chrome browser similarly playwright has implemented CDP to communicate with other browser such as Firefox and webkit.
WebSocket:Websocket connection is established to the server from client by using process called websocket handshake. Websocket sends response as soon as it gets request in real time. Connection will be not terminated untill unless connection is closed by either client or server.
CDP(Chrome Dev Tools Protocal):
Client & Server Communication:When test is triggered Client converts automation test into json Format and sends to server over websocket protocal connection.
-Test Failure & Test flakiness is less as because executing all the tests over single websocket connection.
===========================
Test generator
Introduction
Playwright comes with the ability to generate tests for you as you perform actions in the browser and is a great way to quickly get started with testing. Playwright will look at your page and figure out the best locator, prioritizing role, text and test id locators. If the generator finds multiple elements matching the locator, it will improve the locator to make it resilient that uniquely identify the target element.
Generate tests with the Playwright Inspector
When running the codegen
command two windows will be opened, a browser window where you interact with the website you wish to test and the Playwright Inspector window where you can record your tests and then copy them into your editor.
Running Codegen
Use the codegen
command to run the test generator followed by the URL of the website you want to generate tests for. The URL is optional and you can always run the command without it and then add the URL directly into the browser window instead.
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="codegen demo.playwright.dev/todomvc"
Recording a test
Run the codegen
command and perform actions in the browser window. Playwright will generate the code for the user interactions which you can see in the Playwright Inspector window. Once you have finished recording your test stop the recording and press the copy button to copy your generated test into your editor.
With the test generator you can record:
- Actions like click or fill by simply interacting with the page
- Assertions by clicking on one of the icons in the toolbar and then clicking on an element on the page to assert against. You can choose:
'assert visibility'
to assert that an element is visible'assert text'
to assert that an element contains specific text'assert value'
to assert that an element has a specific value
When you have finished interacting with the page, press the record button to stop the recording and use the copy button to copy the generated code to your editor.
Use the clear button to clear the code to start recording again. Once finished close the Playwright inspector window or stop the terminal command.
To learn more about generating tests check out or detailed guide on Codegen.
Generating locators
You can generate locators with the test generator.
- Press the
'Record'
button to stop the recording and the'Pick Locator'
button will appear. - Click on the
'Pick Locator'
button and then hover over elements in the browser window to see the locator highlighted underneath each element. - To choose a locator click on the element you would like to locate and the code for that locator will appear in the field next to the Pick Locator button.
- You can then edit the locator in this field to fine tune it or use the copy button to copy it and paste it into your code.
===========================
2nd way to generate the tests
create a codegen.bat file in project directory and paste below command in the codegen.bat file
call mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="codegen %1"
in the command prompt give below command:
codegen.bat https://www.saucedemo.com/
then application will open and do the actions on application, codegen will generate the testscript.
============================
3rd way to Generate the tests:
Right click on project -->go to runconfigurations-->maven build-->enter the command: exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="codegen demo.playwright.dev/todomvc" in goal field and name: codegencmd
==========================
Trace viewer
Introduction
Playwright Trace Viewer is a GUI tool that lets you explore recorded Playwright traces of your tests meaning you can go back and forward though each action of your test and visually see what was happening during each action.
You will learn
- How to record a trace
- How to open the trace viewer
Recording a trace
Traces can be recorded using the BrowserContext.tracing() API as follows:
Browser browser = browserType.launch();
BrowserContext context = browser.newContext();
// Start tracing before creating / navigating a page.
context.tracing().start(new Tracing.StartOptions()
.setScreenshots(true)
.setSnapshots(true)
.setSources(true));
Page page = context.newPage();
page.navigate("https://playwright.dev");
// Stop tracing and export it into a zip archive.
context.tracing().stop(new Tracing.StopOptions()
.setPath(Paths.get("trace.zip")));
This will record the trace and place it into the file named trace.zip
.
Opening the trace
You can open the saved trace using the Playwright CLI or in your browser on trace.playwright.dev
. Make sure to add the full path to where your trace's zip file is located. Once opened you can click on each action or use the timeline to see the state of the page before and after each action. You can also inspect the log, source and network during each step of the test. The trace viewer creates a DOM snapshot so you can fully interact with it, open devtools etc.
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="show-trace trace.zip"
========================
Locators
Introduction
Locators are the central piece of Playwright's auto-waiting and retry-ability. In a nutshell, locators represent a way to find element(s) on the page at any moment.
Quick Guide
These are the recommended built in locators.
- Page.getByRole() to locate by explicit and implicit accessibility attributes.
- Page.getByText() to locate by text content.
- Page.getByLabel() to locate a form control by associated label's text.
- Page.getByPlaceholder() to locate an input by placeholder.
- Page.getByAltText() to locate an element, usually image, by its text alternative.
- Page.getByTitle() to locate an element by its title attribute.
- Page.getByTestId() to locate an element based on its
data-testid
attribute (other attributes can be configured).
page.getByLabel("User Name").fill("John");
page.getByLabel("Password").fill("secret-password");
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign in"))
.click();
assertThat(page.getByText("Welcome, John!")).isVisible();
Locating elements
Playwright comes with multiple built-in locators. To make tests resilient, we recommend prioritizing user-facing attributes and explicit contracts such as Page.getByRole().
For example, consider the following DOM structure.
<button>Sign in</button>
Locate the element by its role of button
with name "Sign in".
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign in"))
.click();
Use the code generator 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.
Locator locator = page.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Sign in"))
locator.hover();
locator.click();
Note that all methods that create a locator, such as Page.getByLabel(), are also available on the Locator and FrameLocator classes, so you can chain them and iteratively narrow down your locator.
Locator locator = page
.frameLocator("#my-frame")
.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign in"));
locator.click();
Locate by role
The 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.
Sign up
<h3>Sign up</h3>
<label>
<input type="checkbox" /> Subscribe
</label>
<br/>
<button>Submit</button>
You can locate each element by its implicit role:
assertThat(page
.getByRole(AriaRole.HEADING,
new Page.GetByRoleOptions().setName("Sign up")))
.isVisible();
page.getByRole(AriaRole.CHECKBOX,
new Page.GetByRoleOptions().setName("Subscribe"))
.check();
page.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName(
Pattern.compile("submit", Pattern.CASE_INSENSITIVE)))
.click();
Role locators include buttons, checkboxes, headings, links, lists, tables, and many more and follow W3C specifications for ARIA role, ARIA attributes and accessible name. Note that many html elements like <button>
have an implicitly defined role that is recognized by the role locator.
Note that role locators do not replace accessibility audits and conformance tests, but rather give early feedback about the ARIA guidelines.
We recommend prioritizing role locators to locate elements, as it is the closest way to how users and assistive technology perceive the page.
Locate by label
Most form controls usually have dedicated labels that could be conveniently used to interact with the form. In this case, you can locate the control by its associated label using Page.getByLabel().
For example, consider the following DOM structure.
<label>Password <input type="password" /></label>
You can fill the input after locating it by the label text:
page.getByLabel("Password").fill("secret");
Use this locator when locating form fields.
Locate by placeholder
Inputs may have a placeholder attribute to hint to the user what value should be entered. You can locate such an input using Page.getByPlaceholder().
For example, consider the following DOM structure.
<input type="email" placeholder="name@example.com" />
You can fill the input after locating it by the placeholder text:
page.getByPlaceholder("name@example.com").fill("playwright@microsoft.com");
Use this locator when locating form elements that do not have labels but do have placeholder texts.
Locate by text
Find an element by the text it contains. You can match by a substring, exact string, or a regular expression when using Page.getByText().
For example, consider the following DOM structure.
<span>Welcome, John</span>
You can locate the element by the text it contains:
assertThat(page.getByText("Welcome, John")).isVisible();
Set an exact match:
assertThat(page
.getByText("Welcome, John", new Page.GetByTextOptions().setExact(true)))
.isVisible();
Match with a regular expression:
assertThat(page
.getByText(Pattern.compile("welcome, john$", Pattern.CASE_INSENSITIVE)))
.isVisible();
Matching by text always normalizes whitespace, even with exact match. For example, it turns multiple spaces into one, turns line breaks into spaces and ignores leading and trailing whitespace.
We recommend using text locators to find non interactive elements like div
, span
, p
, etc. For interactive elements like button
, a
, input
, etc. use role locators.
You can also filter by text which can be useful when trying to find a particular item in a list.
Locate by alt text
All images should have an alt
attribute that describes the image. You can locate an image based on the text alternative using Page.getByAltText().
For example, consider the following DOM structure.
<img alt="playwright logo" src="/img/playwright-logo.svg" width="100" />
You can click on the image after locating it by the text alternative:
page.getByAltText("playwright logo").click();
Use this locator when your element supports alt text such as img
and area
elements.
Locate by title
Locate an element with a matching title attribute using Page.getByTitle().
For example, consider the following DOM structure.
<span title='Issues count'>25 issues</span>
You can check the issues count after locating it by the title text:
assertThat(page.getByTitle("Issues count")).hasText("25 issues");
Use this locator when your element has the title
attribute.
Locate by test id
Testing by test ids is the most resilient way of testing as even if your text or role of the attribute changes the test will still pass. QA's and developers should define explicit test ids and query them with Page.getByTestId(). However testing by test ids is not user facing. If the role or text value is important to you then consider using user facing locators such as role and text locators.
For example, consider the following DOM structure.
<button data-testid="directions">Itinéraire</button>
You can locate the element by its test id:
page.getByTestId("directions").click();
Set a custom test id attribute
By default, Page.getByTestId() will locate elements based on the data-testid
attribute, but you can configure it in your test config or by calling Selectors.setTestIdAttribute().
Set the test id to use a custom data attribute for your tests.
playwright.selectors().setTestIdAttribute("data-pw");
In your html you can now use data-pw
as your test id instead of the default data-testid
.
<button data-pw="directions">Itinéraire</button>
And then locate the element as you would normally do:
page.getByTestId("directions").click();
Locate by CSS or XPath
If you absolutely must use CSS or XPath locators, you can use Page.locator() to create a locator that takes a selector describing how to find an element in the page. Playwright supports CSS and XPath selectors, and auto-detects them if you omit css=
or xpath=
prefix.
page.locator("css=button").click();
page.locator("xpath=//button").click();
page.locator("button").click();
page.locator("//button").click();
XPath and CSS selectors can be tied to the DOM structure or implementation. These selectors can break when the DOM structure changes. Long CSS or XPath chains below are an example of a bad practice that leads to unstable tests:
page.locator(
"#tsf > div:nth-child(2) > div.A8SBwf > div.RNNXgb > div > div.a4bIc > input"
).click();
page.locator("//*[@id='tsf']/div[2]/div[1]/div[1]/div/div[2]/input").click();
CSS and XPath are not recommended as the DOM can often change leading to non resilient tests. Instead, try to come up with a locator that is close to how the user perceives the page such as role locators or define an explicit testing contract using test ids.
Locate in Shadow DOM
All locators in Playwright by default work with elements in Shadow DOM. The exceptions are:
- Locating by XPath does not pierce shadow roots.
- Closed-mode shadow roots are not supported.
Consider the following example with a custom web component:
<x-details role=button aria-expanded=true aria-controls=inner-details>
<div>Title</div>
#shadow-root
<div id=inner-details>Details</div>
</x-details>
You can locate in the same way as if the shadow root was not present at all.
To click <div>Details</div>
:
page.getByText("Details").click();
<x-details role=button aria-expanded=true aria-controls=inner-details>
<div>Title</div>
#shadow-root
<div id=inner-details>Details</div>
</x-details>
To click <x-details>
:
page.locator("x-details", new Page.LocatorOptions().setHasText("Details"))
.click();
<x-details role=button aria-expanded=true aria-controls=inner-details>
<div>Title</div>
#shadow-root
<div id=inner-details>Details</div>
</x-details>
To ensure that <x-details>
contains the text "Details":
assertThat(page.locator("x-details")).containsText("Details");
Filtering Locators
Consider the following DOM structure where we want to click on the buy button of the second product card. We have a few options in order to filter the locators to get the right one.
Product 1
Product 2
<ul>
<li>
<h3>Product 1</h3>
<button>Add to cart</button>
</li>
<li>
<h3>Product 2</h3>
<button>Add to cart</button>
</li>
</ul>
Filter by text
Locators can be filtered by text with the Locator.filter() method. It will search for a particular string somewhere inside the element, possibly in a descendant element, case-insensitively. You can also pass a regular expression.
page.getByRole(AriaRole.LISTITEM)
.filter(new Locator.FilterOptions().setHasText("Product 2"))
.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Add to cart"))
.click();
Use a regular expression:
page.getByRole(AriaRole.LISTITEM)
.filter(new Locator.FilterOptions()
.setHasText(Pattern.compile("Product 2")))
.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Add to cart"))
.click();
Filter by not having text
Alternatively, filter by not having text:
// 5 in-stock items
assertThat(page.getByRole(AriaRole.LISTITEM)
.filter(new Locator.FilterOptions().setHasNotText("Out of stock")))
.hasCount(5);
Filter by child/descendant
Locators support an option to only select elements that have or have not a descendant matching another locator. You can therefore filter by any other locator such as a Locator.getByRole(), Locator.getByTestId(), Locator.getByText() etc.
Product 1
Product 2
<ul>
<li>
<h3>Product 1</h3>
<button>Add to cart</button>
</li>
<li>
<h3>Product 2</h3>
<button>Add to cart</button>
</li>
</ul>
page.getByRole(AriaRole.LISTITEM)
.filter(new Locator.FilterOptions()
.setHas(page.GetByRole(AriaRole.HEADING, new Page.GetByRoleOptions()
.setName("Product 2"))))
.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Add to cart"))
.click()
We can also assert the product card to make sure there is only one:
assertThat(page
.getByRole(AriaRole.LISTITEM)
.filter(new Locator.FilterOptions()
.setHas(page.GetByRole(AriaRole.HEADING,
new Page.GetByRoleOptions().setName("Product 2"))))
.hasCount(1);
The filtering locator must be relative to the original locator and is queried starting with the original locator match, not the document root. Therefore, the following will not work, because the filtering locator starts matching from the <ul>
list element that is outside of the <li>
list item matched by the original locator:
// ✖ WRONG
assertThat(page
.getByRole(AriaRole.LISTITEM)
.filter(new Locator.FilterOptions()
.setHas(page.GetByRole(AriaRole.LIST)
.GetByRole(AriaRole.HEADING,
new Page.GetByRoleOptions().setName("Product 2"))))
.hasCount(1);
Filter by not having child/descendant
We can also filter by not having a matching element inside.
assertThat(page
.getByRole(AriaRole.LISTITEM)
.filter(new Locator.FilterOptions().setHasNot(page.getByText("Product 2")))
.hasCount(1);
Note that the inner locator is matched starting from the outer one, not from the document root.
Locator operators
Matching inside a locator
You can chain methods that create a locator, like Page.getByText() or Locator.getByRole(), to narrow down the search to a particular part of the page.
In this example we first create a locator called product by locating its role of listitem
. We then filter by text. We can use the product locator again to get by role of button and click it and then use an assertion to make sure there is only one product with the text "Product 2".
Locator product = page
.getByRole(AriaRole.LISTITEM)
.filter(new Locator.FilterOptions().setHasText("Product 2"));
product
.getByRole(AriaRole.BUTTON,
new Locator.GetByRoleOptions().setName("Add to cart"))
.click();
You can also chain two locators together, for example to find a "Save" button inside a particular dialog:
Locator saveButton = page.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Save"));
// ...
Locator dialog = page.getByTestId("settings-dialog");
dialog.locator(saveButton).click();
Matching two locators simultaneously
Method Locator.and() narrows down an existing locator by matching an additional locator. For example, you can combine Page.getByRole() and Page.getByTitle() to match by both role and title.
Locator button = page.getByRole(AriaRole.BUTTON).and(page.getByTitle("Subscribe"));
Matching one of the two alternative locators
If you'd like to target one of the two or more elements, and you don't know which one it will be, use Locator.or() to create a locator that matches all of the alternatives.
For example, consider a scenario where you'd like to click on a "New email" button, but sometimes a security settings dialog shows up instead. In this case, you can wait for either a "New email" button, or a dialog and act accordingly.
If both "New email" button and security dialog appear on screen, the "or" locator will match both of them, possibly throwing the "strict mode violation" error. In this case, you can use Locator.first() to only match one of them.
Locator newEmail = page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("New"));
Locator dialog = page.getByText("Confirm security settings");
assertThat(newEmail.or(dialog).first()).isVisible();
if (dialog.isVisible())
page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Dismiss")).click();
newEmail.click();
Matching only visible elements
It's usually better to find a more reliable way to uniquely identify the element instead of checking the visibility.
Consider a page with two buttons, the first invisible and the second visible.
<button style='display: none'>Invisible</button>
<button>Visible</button>
This will find both buttons and throw a strictness violation error:
page.locator("button").click();
This will only find a second button, because it is visible, and then click it.
page.locator("button").locator("visible=true").click();
Lists
Count items in a list
You can assert locators in order to count the items in a list.
For example, consider the following DOM structure:
- apple
- banana
- orange
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
Use the count assertion to ensure that the list has 3 items.
assertThat(page.getByRole(AriaRole.LISTITEM).hasCount(3);
Assert all text in a list
You can assert locators in order to find all the text in a list.
For example, consider the following DOM structure:
- apple
- banana
- orange
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
Use assertThat(locator).hasText() to ensure that the list has the text "apple", "banana" and "orange".
assertThat(page
.getByRole(AriaRole.LISTITEM))
.hasText(new String[] { "apple", "banana", "orange" });
Get a specific item
There are many ways to get a specific item in a list.
Get by text
Use the Page.getByText() method to locate an element in a list by its text content and then click on it.
For example, consider the following DOM structure:
- apple
- banana
- orange
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
Locate an item by its text content and click it.
page.getByText("orange").click();
- apple
- banana
- orange
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
Filter by text
Use the Locator.filter() to locate a specific item in a list.
For example, consider the following DOM structure:
- apple
- banana
- orange
<ul>
<li>apple</li>
<li>banana</li>
<li>orange</li>
</ul>
Locate an item by the role of "listitem" and then filter by the text of "orange" and then click it.
page.getByRole(AriaRole.LISTITEM)
.filter(new Locator.FilterOptions().setHasText("orange"))
.click();
Get by test id
Use the Page.getByTestId() method to locate an element in a list. You may need to modify the html and add a test id if you don't already have a test id.
For example, consider the following DOM structure:
- apple
- banana
- orange
<ul>
<li data-testid='apple'>apple</li>
<li data-testid='banana'>banana</li>
<li data-testid='orange'>orange</li>
</ul>
Locate an item by it's test id of "orange" and then click it.
page.getByTestId("orange").click();
Get by nth item
If you have a list of identical elements, and the only way to distinguish between them is the order, you can choose a specific element from a list with Locator.first(), Locator.last() or Locator.nth().
Locator banana = page.getByRole(AriaRole.LISTITEM).nth(1);
However, use this method with caution. Often times, the page might change, and the locator will point to a completely different element from the one you expected. Instead, try to come up with a unique locator that will pass the strictness criteria.
Chaining filters
When you have elements with various similarities, you can use the Locator.filter() method to select the right one. You can also chain multiple filters to narrow down the selection.
For example, consider the following DOM structure:
- John
- Mary
- John
- Mary
<ul>
<li>
<div>John</div>
<div><button>Say hello</button></div>
</li>
<li>
<div>Mary</div>
<div><button>Say hello</button></div>
</li>
<li>
<div>John</div>
<div><button>Say goodbye</button></div>
</li>
<li>
<div>Mary</div>
<div><button>Say goodbye</button></div>
</li>
</ul>
To take a screenshot of the row with "Mary" and "Say goodbye":
Locator rowLocator = page.getByRole(AriaRole.LISTITEM);
rowLocator
.filter(new Locator.FilterOptions().setHasText("Mary"))
.filter(new Locator.FilterOptions()
.setHas(page.getByRole(
AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Say goodbye"))))
.screenshot(new Page.ScreenshotOptions().setPath("screenshot.png"));
You should now have a "screenshot.png" file in your project's root directory.
Rare use cases
Do something with each element in the list
Iterate elements:
for (Locator row : page.getByRole(AriaRole.LISTITEM).all())
System.out.println(row.textContent());
Iterate using regular for loop:
Locator rows = page.getByRole(AriaRole.LISTITEM);
int count = rows.count();
for (int i = 0; i < count; ++i)
System.out.println(rows.nth(i).textContent());
Evaluate in the page
The code inside Locator.evaluateAll() runs in the page, you can call any DOM apis there.
Locator rows = page.getByRole(AriaRole.LISTITEM);
Object texts = rows.evaluateAll(
"list => list.map(element => element.textContent)");
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. For example, the following call throws if there are several buttons in the DOM:
Throws an error if more than one
page.getByRole(AriaRole.BUTTON).click();
On the other hand, Playwright understands when you perform a multiple-element operation, so the following call works perfectly fine when the locator resolves to multiple elements.
Works fine with multiple elements
page.getByRole(AriaRole.BUTTON).count();
You can explicitly opt-out from strictness check by telling Playwright which element to use when multiple elements match, through Locator.first(), Locator.last(), and 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 above to create a locator that uniquely identifies the target element.
==============================
Other locators
Introduction
Check out the main locators guide for most common and recommended locators.
In addition to recommended locators like Page.getByRole() and Page.getByText(), Playwright supports a variety of other locators described in this guide.
CSS locator
We recommend prioritizing user-visible locators like text or accessible role instead of using CSS that is tied to the implementation and could break when the page changes.
Playwright can locate an element by CSS selector.
page.locator("css=button").click();
Playwright augments standard CSS selectors in two ways:
- CSS selectors pierce open shadow DOM.
- Playwright adds custom pseudo-classes like
:visible
,:has-text()
,:has()
,:is()
,:nth-match()
and more.
CSS: matching by text
Playwright include a number of CSS pseudo-classes to match elements by their text content.
article:has-text("Playwright")
- the:has-text()
matches any element containing specified text somewhere inside, possibly in a child or a descendant element. Matching is case-insensitive, trims whitespace and searches for a substring.For example,
article:has-text("Playwright")
matches<article><div>Playwright</div></article>
.Note that
:has-text()
should be used together with other CSS specifiers, otherwise it will match all the elements containing specified text, including the<body>
.// Wrong, will match many elements including <body>
page.locator(":has-text(\"Playwright\")").click();
// Correct, only matches the <article> element
page.locator("article:has-text(\"Playwright\")").click();#nav-bar :text("Home")
- the:text()
pseudo-class matches the smallest element containing specified text. Matching is case-insensitive, trims whitespace and searches for a substring.For example, this will find an element with text "Home" somewhere inside the
#nav-bar
element:page.locator("#nav-bar :text('Home')").click();
#nav-bar :text-is("Home")
- the:text-is()
pseudo-class 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<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<button>
contains a text node" Log "
.Similarly,
:text-is("Download")
will not match<button>download</button>
because it is case-sensitive.
#nav-bar :text-matches("reg?ex", "i")
- the:text-matches()
pseudo-class matches the smallest element with text content matching the JavaScript-like regex.For example,
:text-matches("Log\s*in", "i")
matches<button>Login</button>
and<button>log IN</button>
.
Text matching always normalizes whitespace. For example, it turns multiple spaces into one, turns line breaks into spaces and ignores leading and trailing whitespace.
Input elements of the type button
and submit
are matched by their value
instead of text content. For example, :text("Log in")
matches <input type=button value="Log in">
.
CSS: matching only visible elements
Playwright supports the :visible
pseudo class in CSS selectors. For example, css=button
matches all the buttons on the page, while css=button:visible
only matches visible buttons. This is useful to distinguish elements that are very similar but differ in visibility.
Consider a page with two buttons, first invisible and second visible.
<button style='display: none'>Invisible</button>
<button>Visible</button>
This will find both buttons and throw a strictness violation error:
page.locator("button").click();
This will only find a second button, because it is visible, and then click it.
page.locator("button:visible").click();
CSS: elements that contain other elements
The :has()
pseudo-class is an experimental CSS pseudo-class. It returns an element if any of the selectors passed as parameters relative to the :scope
of the given element match at least one element.
Following snippet returns text content of an <article>
element that has a <div class=promo>
inside.
page.locator("article:has(div.promo)").textContent();
CSS: elements matching one of the conditions
Comma-separated list of CSS selectors will match all elements that can be selected by one of the selectors in that list.
// Clicks a <button> that has either a "Log in" or "Sign in" text.
page.locator("button:has-text(\"Log in\"), button:has-text(\"Sign in\")").click();
The :is()
pseudo-class is an experimental CSS pseudo-class that may be useful for specifying a list of extra conditions on an element.
CSS: matching elements based on layout
Matching based on layout may produce unexpected results. For example, a different element could be matched when layout changes by one pixel.
Sometimes, it is hard to come up with a good selector to the target element when it lacks distinctive features. In this case, using Playwright layout CSS pseudo-classes could help. These can be combined with regular CSS to pinpoint one of the multiple choices.
For example, input:right-of(:text("Password"))
matches an input field that is to the right of text "Password" - useful when the page has multiple inputs that are hard to distinguish between each other.
Note that layout pseudo-classes are useful in addition to something else, like input
. If you use a layout pseudo-class alone, like :right-of(:text("Password"))
, most likely you'll get not the input you are looking for, but some empty element in between the text and the target input.
Layout pseudo-classes use bounding client rect to compute distance and relative position of the elements.
:right-of(div > button)
- Matches elements that are to the right of any element matching the inner selector, at any vertical position.:left-of(div > button)
- Matches elements that are to the left of any element matching the inner selector, at any vertical position.:above(div > button)
- Matches elements that are above any of the elements matching the inner selector, at any horizontal position.:below(div > button)
- Matches elements that are below any of the elements matching the inner selector, at any horizontal position.:near(div > button)
- Matches elements that are near (within 50 CSS pixels) any of the elements matching the inner selector.
Note that resulting matches are sorted by their distance to the anchor element, so you can use Locator.first() to pick the closest one. This is only useful if you have something like a list of similar elements, where the closest is obviously the right one. However, using Locator.first() in other cases most likely won't work as expected - it will not target the element you are searching for, but some other element that happens to be the closest like a random empty <div>
, or an element that is scrolled out and is not currently visible.
// Fill an input to the right of "Username".
page.locator("input:right-of(:text(\"Username\"))").fill("value");
// Click a button near the promo card.
page.locator("button:near(.promo-card)").click();
// Click the radio input in the list closest to the "Label 3".
page.locator("[type=radio]:left-of(:text(\"Label 3\"))").first().click();
All layout pseudo-classes support 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 the text "Username".
CSS: pick n-th match from the query result
It is usually possible to distinguish elements by some attribute or text content, which is more resilient to page changes.
Sometimes page contains a number of similar elements, and it is hard to select a particular one. For example:
<section> <button>Buy</button> </section>
<article><div> <button>Buy</button> </div></article>
<div><div> <button>Buy</button> </div></div>
In this case, :nth-match(:text("Buy"), 3)
will select the third button from the snippet above. Note that index is one-based.
// Click the third "Buy" button
page.locator(":nth-match(:text('Buy'), 3)").click();
:nth-match()
is also useful to wait until a specified number of elements appear, using Locator.waitFor().
// Wait until all three buttons are visible
page.locator(":nth-match(:text('Buy'), 3)").waitFor();
Unlike :nth-child()
, elements do not have to be siblings, they could be anywhere on the page. In the snippet above, all three buttons match :text("Buy")
selector, and :nth-match()
selects the third button.
N-th element locator
You can narrow down query to the n-th match using the nth=
locator passing a zero-based index.
// Click first button
page.locator("button").locator("nth=0").click();
// Click last button
page.locator("button").locator("nth=-1").click();
Parent element locator
When you need to target a parent element of some other element, most of the time you should Locator.filter() by the child locator. For example, consider the following DOM structure:
<li><label>Hello</label></li>
<li><label>World</label></li>
If you'd like to target the parent <li>
of a label with text "Hello"
, using Locator.filter() works best:
Locator child = page.getByText("Hello");
Locator parent = page.getByRole(AriaRole.LISTITEM).filter(new Locator.FilterOptions().setHas(child));
Alternatively, if you cannot find a suitable locator for the parent element, use xpath=..
. Note that this method is not as reliable, because any changes to the DOM structure will break your tests. Prefer Locator.filter() when possible.
Locator parent = page.getByText("Hello").locator("xpath=..");
React locator
React locator is experimental and prefixed with _
. The functionality might change in future.
React locator allows finding elements by their component name and property values. The syntax is very similar to CSS attribute selectors and supports all CSS attribute selector operators.
In React locator, component names are transcribed with CamelCase.
page.locator("_react=BookItem").click();
More examples:
- match by component:
_react=BookItem
- match by component and exact property value, case-sensitive:
_react=BookItem[author = "Steven King"]
- match by property value only, case-insensitive:
_react=[author = "steven king" i]
- match by component and truthy property value:
_react=MyButton[enabled]
- match by component and boolean value:
_react=MyButton[enabled = false]
- match by property value substring:
_react=[author *= "King"]
- match by component and multiple properties:
_react=BookItem[author *= "king" i][year = 1990]
- match by nested property value:
_react=[some.nested.value = 12]
- match by component and property value prefix:
_react=BookItem[author ^= "Steven"]
- match by component and property value suffix:
_react=BookItem[author $= "Steven"]
- match by component and key:
_react=BookItem[key = '2']
- match by property value regex:
_react=[author = /Steven(\\s+King)?/i]
To find React element names in a tree use React DevTools.
React locator supports React 15 and above.
React locator, as well as React DevTools, only work against unminified application builds.
Vue locator
Vue locator is experimental and prefixed with _
. The functionality might change in future.
Vue locator allows finding elements by their component name and property values. The syntax is very similar to CSS attribute selectors and supports all CSS attribute selector operators.
In Vue locator, component names are transcribed with kebab-case.
page.locator("_vue=book-item").click();
More examples:
- match by component:
_vue=book-item
- match by component and exact property value, case-sensitive:
_vue=book-item[author = "Steven King"]
- match by property value only, case-insensitive:
_vue=[author = "steven king" i]
- match by component and truthy property value:
_vue=my-button[enabled]
- match by component and boolean value:
_vue=my-button[enabled = false]
- match by property value substring:
_vue=[author *= "King"]
- match by component and multiple properties:
_vue=book-item[author *= "king" i][year = 1990]
- match by nested property value:
_vue=[some.nested.value = 12]
- match by component and property value prefix:
_vue=book-item[author ^= "Steven"]
- match by component and property value suffix:
_vue=book-item[author $= "Steven"]
- match by property value regex:
_vue=[author = /Steven(\\s+King)?/i]
To find Vue element names in a tree use Vue DevTools.
Vue locator supports Vue2 and above.
Vue locator, as well as Vue DevTools, only work against unminified application builds.
XPath locator
We recommend prioritizing user-visible locators like text or accessible role instead of using XPath that is tied to the implementation and easily break when the page changes.
XPath locators are equivalent to calling Document.evaluate
.
page.locator("xpath=//button").click();
Any selector string starting with //
or ..
are assumed to be an xpath selector. For example, Playwright converts '//html/body'
to 'xpath=//html/body'
.
XPath does not pierce shadow roots.
XPath union
Pipe operator (|
) can be used to specify multiple selectors in XPath. It will match all elements that can be selected by one of the selectors in that list.
// Waits for either confirmation dialog or load spinner.
page.locator("//span[contains(@class, 'spinner__loading')]|//div[@id='confirmation']").waitFor();
Label to form control retargeting
We recommend locating by label text instead of relying to label-to-control retargeting.
Targeted input actions in Playwright automatically distinguish between labels and controls, so you can target the label to perform an action on the associated control.
For example, consider the following DOM structure: <label for="password">Password:</label><input id="password" type="password">
. You can target the label by it's "Password" text using Page.getByText(). However, the following actions will be performed on the input instead of the label:
- Locator.click() will click the label and automatically focus the input field;
- Locator.fill() will fill the input field;
- Locator.inputValue() will return the value of the input field;
- Locator.selectText() will select text in the input field;
- Locator.setInputFiles() will set files for the input field with
type=file
; - Locator.selectOption() will select an option from the select box.
// Fill the input by targeting the label.
page.getByText("Password").fill("secret");
However, other methods will target the label itself, for example assertThat(locator).hasText() will assert the text content of the label, not the input field.
// Fill the input by targeting the label.
assertThat(page.locator("label")).hasText("Password");
Legacy text locator
We recommend the modern text locator instead.
Legacy text locator matches elements that contain passed text.
page.locator("text=Log in").click();
Legacy text locator has a few variations:
text=Log in
- default matching is case-insensitive, trims whitespace and searches for a substring. For example,text=Log
matches<button>Log in</button>
.page.locator("text=Log in").click();
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.For example,
text="Log"
does not match<button>Log in</button>
because<button>
contains a single text node"Log in"
that is not equal to"Log"
. However,text="Log"
matches<button> Log <span>in</span></button>
, because<button>
contains a text node" Log "
. This exact mode implies case-sensitive matching, sotext="Download"
will not match<button>download</button>
.Quoted body follows the usual escaping rules, e.g. use
\"
to escape double quote in a double-quoted string:text="foo\"bar"
.page.locator("text='Log in'").click();
/Log\s*in/i
- body can be a JavaScript-like regex wrapped in/
symbols. For example,text=/Log\s*in/i
matches<button>Login</button>
and<button>log IN</button>
.page.locator("text=/Log\\s*in/i").click();
String selectors starting and ending with a quote (either "
or '
) are assumed to be a legacy text locators. For example, "Log in"
is converted to text="Log in"
internally.
Matching always normalizes whitespace. For example, it turns multiple spaces into one, turns line breaks into spaces and ignores leading and trailing whitespace.
Input elements of the type button
and submit
are matched by their value
instead of text content. For example, text=Log in
matches <input type=button value="Log in">
.
id, data-testid, data-test-id, data-test selectors
We recommend locating by test id instead.
Playwright supports shorthand for selecting elements using certain attributes. Currently, only the following attributes are supported:
id
data-testid
data-test-id
data-test
// Fill an input with the id "username"
page.locator("id=username").fill("value");
// Click an element with data-test-id "submit"
page.locator("data-test-id=submit").click();
Attribute selectors are not CSS selectors, so anything CSS-specific like :enabled
is not supported. For more features, use a proper css selector, e.g. css=[data-test="login"]:enabled
.
Chaining selectors
We recommend chaining locators instead.
Selectors defined as engine=body
or in short-form can be combined with the >>
token, e.g. selector1 >> selector2 >> selectors3
. When selectors are chained, the next one is queried relative to the previous one's result.
For example,
css=article >> css=.bar > .baz >> css=span[attr=value]
is equivalent to
document
.querySelector('article')
.querySelector('.bar > .baz')
.querySelector('span[attr=value]');
If a selector needs to include >>
in the body, it should be escaped inside a string to not be confused with chaining separator, e.g. text="some >> text"
.
Intermediate matches
We recommend filtering by another locator to locate elements that contain other elements.
By default, chained selectors resolve to an element queried by the last selector. A selector can be prefixed with *
to capture elements that are queried by an intermediate selector.
For example, css=article >> text=Hello
captures the element with the text Hello
, and *css=article >> text=Hello
(note the *
) captures the article
element that contains some element with the text Hello
=========================================================
Resource: github link:https://github.com/vitalets/playwright-bdd/tree/main
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.