From Setup to Success: Creating Scalable Tests with Playwright, Java and Page-Object Model

Mohsen Nasiri
14 min readNov 14, 2023

Introduction

Embracing Modern Web Automation: Playwright with Java and the Page Object Model

Web Automation Revolutionized: The Emergence of Playwright

In the dynamic world of web development, automated testing has become indispensable. The advent of Playwright marks a significant advancement in this realm. Playwright, a cross-browser automation library, enables developers and testers to write reliable and robust tests for web applications. Its ability to interact with multiple browsers, including Chrome, Firefox, and Safari, under real-world conditions, sets it apart from its predecessors. This capability ensures comprehensive coverage and compatibility across different browsing environments, a critical aspect of today’s diverse web ecosystem.

You can also check out this article for a comprehensive comparison between Cypress and Playwright, since both of these automation frameworks are widely used nowadays and are on top of their game.

Java and Playwright: A Potent Combination

Java, renowned for its stability, scalability, and robust ecosystem, is an excellent choice for implementing Playwright-based test automation. Java’s extensive libraries, strong community support, and platform-independent nature make it a go-to language for many organizations. When combined with Playwright’s capabilities, Java provides a powerful framework for creating sophisticated and efficient automated test suites.

Introducing the Page Object Model (POM) for Maintainable Test Automation

The Page Object Model (POM) is a design pattern that has become a cornerstone in modern test automation strategies. POM promotes the creation of separate objects for each web page, encapsulating the page’s structure and behaviors. This separation yields numerous benefits:

  1. Enhanced Maintainability: Changes in the web application’s UI can be managed by updating the page objects, minimizing changes in the test scripts.
  2. Increased Readability: Test scripts become more readable and understandable, as they use methods that reflect user actions rather than direct interactions with UI elements.
  3. Reusability: Page objects can be reused across different tests, reducing code duplication.
  4. Improved Collaboration: A clear structure makes it easier for teams to understand and contribute to the test code, enhancing collaboration.

Exploring Playwrights’s Codegen:

After setting up Playwright, one of the first features you should explore is Playwright’s code generation tool, commonly referred to as ‘codegen’. Codegen is a revolutionary feature that assists in creating test scripts by recording your interactions with a web application and generating corresponding Playwright code.

Benefits of Codegen:

  • Rapid Script Creation: It significantly speeds up the process of writing test scripts, especially for complex interactions.
  • Learning Tool: For those new to Playwright, it serves as an excellent learning aid, demonstrating how Playwright scripts are structured.
  • Accuracy: Helps in capturing precise selectors, reducing the chances of errors that might occur when writing selectors manually.

Using Codegen:

To use codegen, run Playwright in codegen mode using the command line. This will open a browser where you can manually perform the actions you wish to automate. Playwright will record these actions and generate the corresponding code in real-time. Here’s a simple example of how to start codegen:

npx playwright codegen example.com

This command launches a browser window where you can interact with ‘example.com’. As you navigate and interact with the site, Playwright generates the code for these actions:

Integrating with POM:

While codegen is a powerful tool, it’s essential to integrate the generated code into the Page Object Model structure for maintainability. Use the generated code as a basis, and refine it to fit into your page classes and test structure.

In the upcoming chapters, we will delve into setting up a Playwright project with Java, leveraging the Page Object Model to create a robust, maintainable, and scalable automation framework.

To see these concepts in action and have access to the full code of the Google search example in this article, feel free to check out this comprehensive repository: PlaywrightWithJava. It serves as a ready-to-use boilerplate for implementing Playwright tests in Java using the Page-Object model.

Chapter 1: Getting Started with Playwright and Java

Laying the Foundation: Setting Up Java, Maven, and Playwright

In this chapter, we guide you through the initial setup of your Playwright project using Java and Maven. This setup is the first step towards creating an efficient and scalable web automation framework.

1. Setting Up the Java and Maven Environment:

Before diving into Playwright, ensure that you have Java and Maven installed on your system. Java is the programming language you’ll use, while Maven is a powerful build tool that simplifies dependency management and project builds.

  • Install Java: You need Java JDK installed on your system. You can download it from the Oracle website or use OpenJDK.
  • Install Maven: Download and install Maven from the Apache Maven Project. Follow the installation instructions provided on their website to set it up on your system.

2. Installing Playwright for Java:

Once Java and Maven are set up, the next step is to add Playwright to your project. You do this by adding Playwright as a dependency in your Maven project.

  • Create a New Maven Project: If you’re starting a new project, create a Maven project in your favorite IDE or from the command line.
  • Add Playwright Dependency: In your pom.xml, add the following dependency to include Playwright:
<dependencies>
<!-- Playwright -->
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>[latest_version]</version>
<scope>test</scope>
</dependency>
<!-- Test runner and assertions -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.10.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Add other dependencies as needed -->
</dependencies>
  • Replace [latest_version] with the latest version of Playwright for Java, which you can find on the Maven Repository.

3. Verifying the Setup:

After setting up your project with the Playwright dependency, it’s crucial to verify that everything works correctly.

  • Create a Simple Test: Write a small script to open a browser and navigate to a webpage. Here’s an example:
package com.example;

import com.microsoft.playwright.*;

public class TestExample {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch();
Page page = browser.newPage();
page.navigate("https://example.com");
System.out.println(page.title());
}
}
}
  • Run Your Test: Execute this script using Maven to ensure that Playwright can launch the browser and perform actions.

With Java, Maven, and Playwright successfully set up, you’re now ready to delve into the world of automated web testing. The next chapters will cover the structure of your project and how to effectively implement the Page Object Model for maintainable and scalable tests.

Chapter 2: Project Structure for Maintainability

Architecting Your Test Framework: Optimal Project Layout with Java and Maven

Creating a well-organized project structure is crucial for maintaining a scalable and efficient automation framework. This chapter outlines an optimal layout for a Playwright project using Java and Maven, emphasizing maintainability and ease of use.

1. Overview of the Recommended Project Layout:

A structured approach helps in managing code efficiently, especially as the project grows. Here’s a recommended project structure:

YourProject
├── src
| ├── main
| | ├── java
| | | └── com
| | | ├── pages
| | | └── utils
| | └── resources
| | └── translations
| └── test
| ├── java/
| | └── com
| | └── tests
| └── resources
| ├── config
| └── utils
└── pom.xml
  • src/main/java: Contains the core of your test framework, including page objects and utility classes.
  • src/test/java: Houses your test scripts, organized in a manner reflecting the application’s structure.
  • src/test/resources: Stores external resources like selectors and test data, keeping them separate from the test logic.

2. Explanation of Each Component:

  • Page Classes (src/main/java/.../pages/): Each web page in your application should have a corresponding page class. This class encapsulates all the functionalities of the web page, following the Page Object Model.
  • Utility Classes (src/main/java/.../utils/): These classes can include various utilities like configuration readers, helper methods for common tasks, etc.
  • Test Classes (src/test/java/.../tests/): Here, you write your actual test cases, utilizing the page classes.
  • Test Data Files (src/test/resources/testdata/): Keep your test data, like usernames and passwords, in external files for easy management and to avoid hardcoding.
  • Maven Configuration (pom.xml): This file manages project dependencies, plugins, and other configurations.

3. Sample Page Class:

Here’s an example of a simple page class representing a login page:

package com.yourcompany.yourproject.pages;

import com.microsoft.playwright.Page;

public class LoginPage {
private final Page page;

// Locators
private final String USERNAME_INPUT = "#username";
private final String PASSWORD_INPUT = "#password";
private final String LOGIN_BUTTON = "#login";
public LoginPage(Page page) {
this.page = page;
}

public void login(String username, String password) {
page.fill(USERNAME_INPUT, username);
page.fill(PASSWORD_INPUT, password);
page.click(LOGIN_BUTTON);
}
}

A well-structured project layout is essential for the long-term maintainability and scalability of your test framework. By organizing your code into logical sections and externalizing selectors and test data, you create a robust foundation for your test automation efforts. In the next chapter, we’ll delve into how to create effective and maintainable page classes.

Chapter 3: Creating Page Classes

Crafting Robust Page Objects: A Guide to Building Page Classes in Playwright

One of the key components of a maintainable test automation framework using the Page Object Model (POM) is the creation of page classes. This chapter focuses on how to construct these classes effectively, using Playwright and Java.

1. The Role of Page Classes in POM:

Page classes serve as an interface to a web page’s UI elements. Each page class corresponds to a page in your application, encapsulating the actions that can be performed on that page. This approach simplifies maintenance, enhances readability, and reduces code duplication.

2. Building a Page Class:

Here’s a step-by-step guide to creating a page class:

  • Identify the UI Elements: Determine all the elements on the web page that your test will interact with, such as text boxes, buttons, and links.
  • Define Selectors: Store the selectors for these elements in your page class. It’s a best practice to externalize these selectors, but for simplicity, we’ll define them directly in the class.
  • Implement Actions: Create methods for each action that can be performed on the page, like entering text, clicking buttons, etc.

3. Example: Creating a SearchPage Class:

Let’s create a SearchPage class for the Google search page:

package org.pages;

import com.microsoft.playwright.Locator;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.options.AriaRole;
import org.util.TranslationReader;

public class SearchPage {
private final Page page;
/* To avoid harcoding texts, we read them from translation files
so we can use them in defining out selectors.
You can see the content of TranslationReader.java here:
https://shorturl.at/vRST3 */
TranslationReader reader = new TranslationReader();

public SearchPage(Page page) {
this.page = page;
}

// Selectors: To return seletors for certain elements of the page
public Locator getCookieSelector() {
return page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(
reader.getTranslation("cookie.accept")
));
}

public Locator getSearchField() {
return page.getByRole(AriaRole.COMBOBOX, new Page.GetByRoleOptions().setName(
reader.getTranslation("search")
));
}

/* ...and more selectors */

// Methods: to perform certain action on the page
public void clickCookieAcceptButton(){
getCookieSelector().click();
}

public void performSearch(String searchKeyword) {
getMainSearchField().fill(searchKeyword);
getSearchButton().click();
}
/* ...and more methods */
}

In this example, the SearchPage class represents a first page when we land on Google. It includes methods to interact with the page, like searching with a given keywords and navigating to different result tabs.

4. Best Practices for Designing Page Classes:

  • One Class per Page: Create a separate class for each page of your application.
  • Descriptive Method Names: Use names that clearly describe the action, like performSearch()or navigateToImageResults().
  • Modularity: Design your methods to be small, focused, and reusable.

Page classes are fundamental in building a scalable and maintainable test automation framework using Playwright and Java. By encapsulating the interactions with a web page, you create a robust and reusable structure that simplifies your test scripts and makes them more resilient to changes in the UI.

Chapter 4: Writing Test Classes

Implementing Effective Test Cases: Integrating Page Objects in Your Tests

With your page classes set up, the next step is to write the test classes. These are the scripts that will use the methods defined in your page classes to interact with the web application. This chapter covers how to create these test cases using Playwright and Java.

1. Basics of Writing Test Classes:

Test classes should be clear, concise, and focus on the testing logic rather than the details of interacting with the web application. These tests will use the methods provided by your page classes.

2. Structuring Your Test Classes:

A typical test class includes:

  • Setup Method: Initializes the Playwright browser and other prerequisites before each test.
  • Test Methods: Individual test cases, each representing a different scenario.
  • Teardown Method: Closes the browser and performs any cleanup after each test.

3. Utilizing PlaywrightExtension for Efficient Test Setup and Teardown

In the realm of automated testing, efficiency and reusability are key. This is where PlaywrightExtension comes into play, a custom JUnit extension designed to streamline your test setup and teardown processes. PlaywrightExtension significantly reduces boilerplate code, ensuring that each test class adheres to DRY (Don't Repeat Yourself) principles.

What is PlaywrightExtension?

PlaywrightExtension is a custom JUnit extension that automates the initialization and cleanup of Playwright resources for each test. It manages the lifecycle of browser instances, pages, and contexts, ensuring that each test starts with a clean state. This approach eliminates the need for repetitive setup and teardown code in each test class, making your tests cleaner and more maintainable.

To implement PlaywrightExtension, define it as a JUnit 5 extension and annotate your test classes with @ExtendWith(PlaywrightExtension.class). This tells JUnit to manage the test environment using the methods defined in PlaywrightExtension.

import org.junit.jupiter.api.extension.ExtendWith;
import com.microsoft.playwright.*;

@ExtendWith(PlaywrightExtension.class)
public class YourTest {
// Test methods go here
}

And the PlaywrightExtension class itself would look like:

package resources.config;

import java.nio.file.Paths;
import com.microsoft.playwright.*;
import org.junit.jupiter.api.extension.*;

public class PlaywrightExtension implements BeforeEachCallback, AfterEachCallback {
private static Browser browser;
private static BrowserContext context;
private static Page page;

@Override
public void beforeEach(ExtensionContext context) throws Exception {
// Override and globally define the beforeEach
}

@Override
public void afterEach(ExtensionContext context) throws Exception {
// Override and globally define the afterEach
}

public static Page getPage() {
return page;
}

public static Browser getBrowser() {
return browser;
}
}

This way we can manage to achieve the following:

  1. Reduced Boilerplate: Automatically handles common setup and teardown tasks.
  2. Consistency: Ensures a consistent testing environment for every test case.
  3. Resource Management: Efficiently manages and disposes of Playwright resources, preventing resource leaks.
  4. Enhanced Readability: Simplifies test classes, making them more readable and maintainable.

In your existing test classes, where you’ve used @BeforeEach and @AfterEach for setup and teardown, replace these with the PlaywrightExtension. Here’s a revised example of how a test class would look:

package org;

import com.microsoft.playwright.*;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.pages.SearchPage;

@ExtendWith(PlaywrightExtension.class)
public class SearchTests {
static Browser browser;
static SearchPage searchPage;
static Page page;

@BeforeEach
public void createContextAndPage() {
// Here you can access the page from PlaywrightExtension
page = PlaywrightExtension.getPage();
page.navigate("https://www.google.com");
}

@Test
public void shouldSearch() {
// Test implementation using SearchPage
}

@AfterAll
public static void tearDown() {
page.close();
// Here you can access the browser from PlaywrightExtension
PlaywrightExtension.getBrowser().close();
}
}

In this example, PlaywrightExtension handles the creation and closure of the Page and Browser instances, thereby simplifying the test structure.

4. Example: Creating a Search Test Class:

Let’s create a SearchTest class to test the search functionality of Google using the SearchPage class:

package org;

import com.microsoft.playwright.*;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.pages.SearchPage;
import resources.config.PlaywrightExtension;

import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;

@ExtendWith(PlaywrightExtension.class)
public class SearchTests {
static Browser browser;
static SearchPage searchPage;
static Page page;

@BeforeEach
public void createContextAndPage() {
page = PlaywrightExtension.getPage();
searchPage = new SearchPage(page);
page.navigate("https://www.google.com");
}

@Test
public void shouldSearch() {
searchPage.clickCookieAcceptButton();
searchPage.performSearch("Potato");
assertThat(searchPage.getSearchField()).containsText("Potato");
searchPage.navigateToImageResults();
// More assertions...
searchPage.navigateToAllResults();
searchPage.navigateToVideoResults();
// More assertions...
}

@AfterAll
public static void tearDown() {
page.close();
PlaywrightExtension.getBrowser().close();
}
}

In this example, shouldSearch is a test method that uses the SearchPage to perform a Google seach. It demonstrates how the test logic becomes more readable and concise with the use of page objects.

5. Tips for Clean and Readable Test Code

  • Descriptive Test Names: Use meaningful names for test methods that describe what the test is verifying.
  • Modular Tests: Write small, focused tests that verify one aspect of the application at a time.
  • Reusable Setup and Teardown: Use @BeforeEach and @AfterEach annotations to handle common setup and cleanup tasks.
  • Assertions: Include assertions to validate the outcome of your tests, ensuring they are checking the right conditions.

Test classes are where you bring together the functionality of your page classes to interact with your application. By following these guidelines, you can create tests that are not only effective in catching bugs but are also easy to read and maintain.

And finally to give an idea on how the project structure looks like so far:

Project structure for the Google search test

Chapter 5: Externalizing Selectors and Test Data

Enhancing Test Flexibility and Maintenance: Strategies for Managing Selectors and Test Data

Efficient management of selectors and test data is pivotal in maintaining a robust and adaptable test automation framework. This chapter explores how to externalize selectors and test data in your Playwright project, enhancing flexibility and reducing the need for frequent code changes.

1. The Importance of Externalizing Selectors:

Hard-coding selectors in your test scripts or page classes can lead to a maintenance nightmare, especially when the UI changes. By externalizing these selectors, you can easily update them without altering the core test logic.

2. Using JSON for Selector Management:

One effective way to manage selectors is to store them in JSON files. This allows you to change selectors without touching the Java code. Here’s an example:

  • JSON Selector File (loginSelectors.json):
{
"usernameInput": "#username",
"passwordInput": "#password",
"loginButton": "#login"
}
  • Reading Selectors in a Page Class: Modify your page classes to read selectors from these JSON files. You can use Java’s built-in libraries or third-party libraries like Jackson or Gson to parse JSON files.

3. Externalizing Test Data:

Similar to selectors, test data like usernames, passwords, or any input data should be externalized. This practice not only makes your tests more readable but also simplifies data management and enhances security.

  • JSON Test Data File (loginTestData.json):
{
"validUser": {
"username": "user1",
"password": "pass123"
},
"invalidUser": {
"username": "user2",
"password": "wrongpass"
}
}
  • Utilizing Test Data in Tests: Load this data in your test classes, and use it to drive your tests. This approach is especially useful for data-driven testing.

4. Best Practices for Selector and Test Data Management:

  • Keep It Organized: Store your selectors and test data in an organized manner, ideally in a dedicated directory within your project.
  • Version Control: Include these JSON files in your version control system to track changes and maintain a history of updates.
  • Security Considerations: Be cautious with sensitive data. Use environment variables or secure vaults for highly sensitive data like real passwords.

Externalizing selectors and test data enhances the maintainability and scalability of your test automation framework. It allows for easier updates and modifications, making your tests more resilient to changes in the application UI and data sets.

Conclusion

Embracing the Future of Automated Testing: Key Takeaways and Next Steps

If you want to dive deeper and see a complete setup, the PlaywrightWithJava repository that I have prepaired provides a comprehensive example, demonstrating the concepts discussed in this article.

As we conclude our guide to setting up Playwright with Java using the Page Object Model, it’s important to reflect on the key takeaways and consider the next steps in your journey towards mastering web automation.

1. Recap of Key Points:

  • Playwright and Java: We explored the powerful combination of Playwright with Java, highlighting the benefits of this duo in automated testing.
  • Project Structure: An optimal project layout was discussed, emphasizing maintainability and organization.
  • Page Object Model (POM): The implementation of POM was outlined, showcasing its importance in creating scalable and maintainable test scripts.
  • Page Classes: We delved into the creation of page classes, a core component of POM, and provided examples to illustrate their structure and functionality.
  • Test Classes: The structure and best practices for writing effective test classes were covered, ensuring clarity and efficiency in your tests.
  • Externalizing Selectors and Data: The significance of externalizing selectors and test data was emphasized, highlighting its role in enhancing the flexibility and maintainability of your tests.

2. Encouraging Exploration and Adaptation:

The world of web automation is vast and constantly evolving. While this guide provides a strong foundation, it’s crucial to continue exploring and adapting. Stay updated with the latest developments in Playwright, Java, and test automation. Experiment with different approaches and tools to find what works best for your specific needs.

3. Community and Continued Learning:

Engaging with the community is invaluable. Participate in forums, attend webinars, and contribute to open-source projects. This interaction fosters learning and keeps you abreast of industry best practices.

4. Next Steps:

  • Integrate Advanced Features: Explore advanced features of Playwright and Java, like handling dynamic content, working with iframes, and more.
  • CI/CD Integration: Consider integrating your tests into a CI/CD pipeline for automated test execution.
  • Performance and Security Testing: Expand your testing scope to include performance and security aspects.

5. Final Thoughts:

Automated testing is a journey of continuous learning and improvement. The setup and practices discussed in this guide are just the beginning. Embrace the challenges and opportunities that come with automated testing, and you’ll contribute significantly to the quality and success of your web applications.

--

--