Automation QA Testing Course Content

Playwright

 

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:

Parameter

Selenium

Playwright

Language Support

Supports multiple languages including Java, C#, Python, Ruby, and JavaScript.

Primarily supports JavaScript and TypeScript, with Python, Java, and C# versions available.

Ease of Installation

Requires separate installations for each component like WebDriver and Grid, which can be complex.

Offers a simpler installation process with a single package that includes all necessary components.

Test Runner Frameworks Supported

Compatible with various test runners like JUnit, TestNG, NUnit, and Mocha.

Works seamlessly with Jest, Mocha, and other JavaScript-based test runners.

Prerequisites

Needs browser drivers for each browser you intend to test.

No separate browser drivers are required as it comes with built-in browser binaries.

Operating Systems Supported

Support all major OS like Windows, macOS, and Linux.

Open Source

Open-source tool available for use and contribution by the community.

Architecture Used

Operates on a client-server model which can lead to latency issues.

Uses a more integrated approach which tends to be faster and more reliable.

The number of browsers Supported

Supports numerous browsers including older versions.

Focuses on modern browsers, including Chromium, Firefox, and WebKit.

Community Support

Benefits from a large community and extensive support resources.

The community is growing but has fewer resources compared to Selenium.

Real Devices Support

Can be integrated with device farms for testing on real devices.

Primarily focused on emulators and simulators, with limited support for real device testing.

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.


Selenium Vs Playwright Vs Cypress differences:


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.

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

recording a test

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.

picking a locator

===========================

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"

playwright trace viewer

========================

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.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.

http://localhost:3000
<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();
NOTE

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.

http://localhost:3000

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 roleARIA 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.

WHEN TO USE ROLE LOCATORS

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.

http://localhost:3000
<label>Password <input type="password" /></label>

You can fill the input after locating it by the label text:

page.getByLabel("Password").fill("secret");
WHEN TO USE LABEL LOCATORS

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.

http://localhost:3000
<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");
WHEN TO USE PLACEHOLDER LOCATORS

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.

http://localhost:3000
Welcome, John
<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();
NOTE

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.

WHEN TO USE TEXT LOCATORS

We recommend using text locators to find non interactive elements like divspanp, etc. For interactive elements like buttonainput, 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.

http://localhost:3000
playwright logo
<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();
WHEN TO USE ALT LOCATORS

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.

http://localhost:3000
25 issues
<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");
WHEN TO USE TITLE LOCATORS

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.

http://localhost:3000
<button data-testid="directions">Itinéraire</button>

You can locate the element by its test id:

page.getByTestId("directions").click();
WHEN TO USE TESTID LOCATORS

You can also use test ids when you choose to use the test id methodology or when you can't locate by role or text.

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.

http://localhost:3000
<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();
WHEN TO USE THIS

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:

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.

http://localhost:3000
  • 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.

http://localhost:3000
  • 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.

NOTE

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

NOTE

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:

http://localhost:3000
  • 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:

http://localhost:3000
  • 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:

http://localhost:3000
  • 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();
http://localhost:3000
  • 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:

http://localhost:3000
  • 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:

http://localhost:3000
  • 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:

http://localhost:3000
  • 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

NOTE

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

NOTE

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>.

NOTE

Text matching always normalizes whitespace. For example, it turns multiple spaces into one, turns line breaks into spaces and ignores leading and trailing whitespace.

NOTE

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

NOTE

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

NOTE

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();
NOTE

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

NOTE

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.

NOTE

React locator supports React 15 and above.

NOTE

React locator, as well as React DevTools, only work against unminified application builds.

Vue locator

NOTE

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.

NOTE

Vue locator supports Vue2 and above.

NOTE

Vue locator, as well as Vue DevTools, only work against unminified application builds.

XPath locator

WARNING

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();
NOTE

Any selector string starting with // or .. are assumed to be an xpath selector. For example, Playwright converts '//html/body' to 'xpath=//html/body'.

NOTE

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

WARNING

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:

// 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

WARNING

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, so text="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();
NOTE

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.

NOTE

Matching always normalizes whitespace. For example, it turns multiple spaces into one, turns line breaks into spaces and ignores leading and trailing whitespace.

NOTE

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

WARNING

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();
NOTE

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

WARNING

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

WARNING

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.