Introduction
Playwright, developed by Microsoft and launched in early 2020, is a powerful framework for web testing and automation. It offers a range of benefits:
- Cross-browser: Supports Chromium, Firefox, and WebKit with a single API, in both headed and headless modes.
- Cross-platform: Compatible with Windows, Linux, and macOS, suitable for local and CI/CD pipeline integration.
- Cross-language: Supports JavaScript, TypeScript, Python, .NET, and Java.
- Native Mobile Emulation
- Increased Test Robustness: Features built-in auto-wait for elements, web-first assertions, and tracing to minimize flaky tests.
- Test Isolation: Achieved through browser contexts.
- Support for multiple tabs, origins, and users
- Event Hooks
- Seamless automation for IFrame and Shadow DOM
In this post, we will explore the most commonly used features of Playwright using Java.
What Does a Playwright Test Look Like?
Setup
To begin writing tests with Playwright, add the library as a dependency to your project using Maven or Gradle. Additionally, consider adding a testing framework like JUnit or TestNG as a dependency.
Test
package tests;
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.AriaRole;
import org.junit.jupiter.api.Test;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
public class PlaywrightTests {
@Test
public void simplePlaywrightTest() {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch(new BrowserType.LaunchOptions().setHeadless(false));
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate("https://www.automatetheplanet.com");
Locator heading = page.getByRole(AriaRole.HEADING).first();
assertThat(page).hasTitle("Home Page - Automate The Planet");
assertThat(heading).hasText("Your Ultimate Test Automation Partner");
}
}
}
Analysis
The first thing we might notice are the essential objects in the Playwright API: Playwright, Browser, BrowserContext, Page, and Locator.
- Playwright: Provides methods to launch a Browser instance. Closing it will terminate all associated browsers.
- Browser: Represents the launched browser.
- BrowserContext: Allows multiple independent browser sessions.
- Page: Provides methods to interact with the browser tab.
- Locator: Represents a way to find elements on the page but is not a direct reference to the HTML elements.
In the following sections, we will explore these objects in detail.
Another key point is that the Playwright object is initialized in a try-with-resources statement. Java’s try-with-resources ensures that resources like files, database connections, or sockets are closed automatically after use, even if an exception occurs. This is done by implementing the AutoCloseable interface, which includes the close() method.
Both Playwright and Browser implement this interface. If they are not closed properly, the opened browser won’t close after the test. While you can manually call the close() method, it is error-prone and can lead to resource leaks, especially in complex code with multiple exit points or exceptions. Thus, using try-with-resources is the preferred and safer practice.
You don’t need to manually close the Browser in Playwright. Closing the Playwright object automatically handles closure of all associated Browser objects, including their BrowserContexts and Pages.
Finally, Playwright provides its own assertions. To use them, you can either:
- Write PlaywrightAssertions.assertThat and import com.microsoft.playwright.assertions.PlaywrightAssertions.
- Write assertThat directly and statically import com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat.
We will discuss the assertions in detail later.
Writing tests with Playwright is intuitive and simple, yet very powerful.
Launching Playwright Browser
As mentioned already, launching Playwright happens with a few steps: Create a Playwright instance, from that instance create a Browser instance, from which BrowserContext and Page objects are created.
When launching browsers, there are 3 basic options: Chromium, Firefox, and WebKit.
browser = playwright.chromium().launch();
browser = playwright.firefox().launch();
browser = playwright.webkit().launch();
Additionally, one can pass an argument to the launch method which is the launching options.
Let’s take a look at them.
var launchOptions = new BrowserType.LaunchOptions();
You can customize the browser by defining many different settings, such as: headless, channel, chromiumSandbox, devtools, executablePath, proxy, slowMo (slow motion), timeout, and tracesDir.
Maybe you are wondering how to launch Chrome or Edge browser, not just Chromium. It is done by setting the channel option to either “chrome” or “msedge”, when launching chromium.
playwright.chromium().launch(new BrowserType.LaunchOptions().setChannel("chrome"));
playwright.chromium().launch(new BrowserType.LaunchOptions().setChannel("msedge"));
Other available channels for chromium include: chrome-beta, msedge-beta, and msedge-dev.
As you might know, Chrome and Edge are based on Chromium. Testing on the latest Chromium keeps you ahead of stable browser releases, ensuring upcoming updates won’t break your website. However, regression testing often requires testing against the latest publicly available browsers. Therefore, it’s crucial to know how to set the channel in Chromium launch options.
After initializing the Browser object, there are two methods to initialize a Page object for testing.
- Using BrowserContext:
BrowserContext browserContext = browser.newContext();
Page page = browserContext.newPage();
This approach first creates a new BrowserContext using browser.newContext(), and then initializes a Page object using browserContext.newPage().
- Directly from Browser:
Page page = browser.newPage();
This method directly initializes a Page object from the Browser object. Internally, Playwright automatically creates a BrowserContext and then initializes the Page from that context.
Both methods ultimately provide a Page object that can be used to interact with and automate actions on a web page during testing. The choice between these methods depends on whether additional control over the BrowserContext settings is needed or if a simple default context is sufficient for the test scenario.
Locators and ElementHandles
Locators encapsulate methods to find and interact with elements on the page. Each time a locator is utilised for an action, it dynamically locates the most current DOM element on the page. This approach is fundamental to reducing test flakiness.
Locating
- getByRole()
This locator reflects what function the element represents, for example, a button, or a checkbox, or an input field. This method accepts 1 or 2 arguments, the first being a value from the enum AriaRole (e.g., AriaRole.HEADING), and the second being optional is the options for locating by role.
getByRole(AriaRole.HEADING)
getByRole(AriaRole.TEXTBOX, new Page.GetByRoleOptions().setName("billing address"));
getByRole(AriaRole.CHECKBOX, new Page.GetByRoleOptions().setChecked(true));
getByRole(AriaRole.RADIO, new Page.GetByRoleOptions().setDisabled(true));
- getByLabel()
This locator is used to find input elements with a label attached to them. It is useful to have a direct locator by label when dealing with forms for example.
getByLabel("Password");
getByLabel(Pattern.compile("(?i).*name.*"));
- getByPlaceholder()
Many inputs may have a placeholder attribute which will hint to the user what they are supposed to write.
getByPlaceholder("name@example.com");
getByPlaceholder("^[\\w.%+-]+@[\\w.-]+\\.[a-zA-Z]{2,}$");
- getByText()
When dealing with non-interactive elements which have the role of simply conveying information in the form of a text, searching by it is straightforward. The interesting part is that Playwright normalises whitespace, so you don’t have to worry about that!
getByText("Your Ultimate Test Automation Partner");
getByText("Ultimate Test Automation", new Page.GetByTextOptions().setExact(false));
getByText(Pattern.compile("Partner$"));
- getByAltText()
It’s generally recommended to include an ‘alt’ attribute when adding images to HTML. This attribute provides text that displays if the image fails to load. Searching for or elements using this attribute is highly reliable.
getByAltText("Parallel on Agents", new Page.GetByAltTextOptions().setExact(true));
getByAltText(Pattern.compile("^people"));
- getByTitle()
This locator searches by the title attribute of elements.
getByTitle("Automate the Planet Logo");
getByTitle(Pattern.compile("Logo$"));
- getByTestId()
In an ideal scenario where test automation is integrated into the development process and both test engineers and developers collaborate by adding test IDs to elements for automated tests, Playwright provides a straightforward method to search by these IDs. Even if the page content changes, the test ID remains consistent.
Before using test IDs for locating elements, it’s important to configure Playwright to recognize the attribute that qualifies as a test ID on your website. By default, Playwright uses “data-testid”.
playwright.selectors().setTestIdAttribute("bellatrix-testid");
After you have instructed Playwright which attribute is the test id, you can easily locate elements by it as follows:
page.getByTestId("username-input");
page.getByTestId(Pattern.compile("^username"));
- locate()
While basic methods of locating elements are straightforward, test automation often requires writing complex CSS or XPath selectors to handle more challenging scenarios.
The method for these selectors in Playwright is used like this:
locator("//img[@alt='people_image' and contains(@src, 'Forbes.jpeg')]");
locator("[id='logo']");
You might want to add in the beginning of the locator “xpath=” or “css=” accordingly, as even though Playwright often guesses correctly which is which, it might fail.
It’s often discouraged to copy-paste CSS Selectors or XPath directly from the devtools inspector, as they can generate unreliable locators. For more information on creating reliable CSS and XPath selectors, check out these blog posts:
Most Exhaustive CSS Locators Cheat Sheet
Most Exhaustive XPath Locators Cheat Sheet
Shadow DOM
With Playwright, you don’t need to worry about elements being unreachable due to being within a shadow root. Whether using CSS selectors or the getBy methods provided, Playwright locators can find elements even within the shadow DOM seamlessly.
Interaction
When using Locator objects to interact with elements on a page in Playwright, you will not encounter errors like ‘StaleElementReferenceException’. This is because Locators are not direct references to elements; instead, they are strategies to find elements. Each time you interact with an element through a Locator, Playwright re-finds the element, creates an ElementHandle (which acts as a pointer to the element), performs the action, and then disposes of the ElementHandle. This ensures that interactions are always with the most current instance of the element.
Now, let’s take a look at some of the most common methods of Locator and what they do.
- check() and uncheck()
Used with checkboxes and radio buttons.
check() will ensure it’s checked, while uncheck() will ensure it’s unchecked.
- clear()
Used with inputs, it will clear the input field.
- click()
Might be used with any element, but most notably buttons and anchors.
Via the ClickOptions which are optional one can:
Set the mouse button (left, right, middle):
button.click(new Locator.ClickOptions().setButton(MouseButton.RIGHT));
Set the click count:
button.click(new Locator.ClickOptions().setClickCount(5));
Set the delay between mousedown and mouseup events:
button.click(new Locator.ClickOptions().setDelay(300));
Set keyboard modifiers (e.g., Alt, Control, Shift):
button.click(new Locator.ClickOptions().setModifiers(List.of(KeyboardModifier.SHIFT)));
- evaluate()
Used to perform JavaScript against the element.
element.evaluate("el => el.innerHTML;");
- fill()
Sets the value of an input field.
- focus()
Focuses on the element.
- hover()
Hovers the element.
- selectOption()
Used with select elements.
// selects either by label or by value
element.selectOption("lamborghini");
// explicitly selects by label
element.selectOption(new SelectOption().setLabel("Lamborghini"));
// explicitly selects by value
element.selectOption(new SelectOption().setValue("lamborghini"));
// explicitly selects by index
element.selectOption(new SelectOption().setIndex(1));
// selects multiple
element.selectOption(new String[] { "lamborghini", "jaguar", "mustang"});
And some information methods include:
- boundingBox()
This method will return a BoundingBox object, which has the following values: X, Y, Width, and Height.
- innerHTML()
Returns the inner HTML as String.
- innerText()
Returns the inner text as String.
- isChecked()
If the corresponding element is either a checkbox or a radio button, it will return a boolean value. Otherwise it will throw an exception.
- isDisabled()
If the corresponding element is interactable and thus can be disabled, it will return a boolean value.
- isEditable()
Returns a boolean value if the corresponding element is editable.
- isEnabled()
If the corresponding element is interactable and thus enabled, it will return a boolean value.
- isHidden()
Returns whether the element is hidden.
- isVisible()
Returns whether the element is visible.
Auto-Waiting
One of the core benefits of Playwright is its auto-waiting feature for elements. Before performing an action on an element, Playwright checks if the element meets specific criteria to receive the action.
For example, when clicking an element, Playwright validates if the element is visible, stable, capable of receiving events, and enabled. Similarly, when setting text in an input field, it checks if the element is visible, enabled, and editable.
While this auto-waiting generally enhances reliability and reduces flakiness, there are situations where you may want to disable it. Most methods in the Locator class provide an option in their corresponding Options object to force the action, thereby bypassing non-essential checks for actionability.
Let’s briefly look at the checks and what they do:
Visible
Elements will be considered visible if they:
- Have non-empty bounding box
- They don’t have computed visibility:hidden style
- Their size is not zero
- There is no display:none
- The opacity is not zero
Stable
Elements will be considered stable if they maintain the same bounding box for two consecutive animation frames.
Enabled
Elements will be considered enabled unless they are interactable (buttons, selects, inputs) and they have a disabled attribute.
Editable
Elements are considered editable if they are enabled and don’t have a readonly attribute.
Receives Events
Element is considered receiving pointer events when there is no other element on top of it which will capture the click.
Playwright Assertions
Playwright includes a comprehensive set of assertions for elements, pages, and API responses. Let’s explore some of these assertions and how they are used!
The PlaywrightAssertions.assertThat() method takes one parameter, which should be an object implementing either the Page interface, APIResponse interface, or Locator interface. Depending on the type of object passed, it returns an instance of PageAssertions, APIResponseAssertions, or LocatorAssertions.
Each assertion type provides a not() method, which negates the assertion. This allows validation of conditions to be either true or false, depending on testing requirements.
By default, all assertions include a retry mechanism with a timeout until the condition is met. To customize this timeout, simply pass an Options object of the corresponding type as an argument and specify the desired timeout for the assertion method.
assertThat(page).hasURL("https://www.automatetheplanet.com", new PageAssertions.HasURLOptions().setTimeout(30_000));
Another way to customize the timeout is to directly set it from PlaywrightAssertions which will influence all assertions:
PlaywrightAssertions.setDefaultAssertionTimeout(15_000);
Page Assertions
- hasURL()
Asserts the URL of the page and accepts 1 argument in the form of either a string or a regex pattern. The Options class has an option to perform a case-insensitive match.
- hasTitle()
Asserts the title of the page and accepts 1 argument of either type String or type Pattern. It ensures that the page has or does not have the specified title.
API Response Assertions
- isOK()
When testing, it’s crucial to monitor the API requests and responses alongside visible elements. Even though they aren’t seen by the user, they provide vital validation steps. If an API assertion fails, it helps pinpoint issues during test result analysis.
As expected, this method validates whether the status code falls within the 200-299 range.
Locator Assertions
Compared to other Assertion classes, the assertions for elements on the page are the most extensive in Playwright. Since the primary focus of testing is often on the elements visible to users, there are numerous states and conditions we can validate for these elements.
The most common assertion methods are:
- containsText()
This validation ensures that the element contains the given text. This method also accepts in the form of an Options object an interesting setting: useInnerText which determines whether to use element.innerText or element.textContent.
Playwright normalizes whitespace when asserting the text.
- hasText()
Similarly to the previously mentioned validation, this too ensures the element’s text. This time, it is the whole text. This method too has the userInnerText option.
- hasValue()
Used with input fields, it will ensure that the value is the same as expected.
- isDisabled()
When dealing with HTML elements such as buttons, inputs, select elements, textarea elements, etc. This assertion method will ensure that the element is disabled by checking if the “disabled” attribute is present. Note that this method won’t work with other elements.
- isEnabled()
The opposite of the above method, works on the same interactable elements and will ensure the element is enabled.
- isVisible()
Ensures that the element is visible by the criteria mentioned previously in the section about auto-waiting.
Summary
Discover Playwright, a robust tool for test automation with immense potential. We covered writing typical tests, navigating Locators, interacting with page elements, leveraging Playwright’s auto-wait feature for stability, and asserting website behavior.
For deeper insights, explore Playwright’s official documentation.
