Puppeteer Page Object

A Puppeteer Page Object is a design pattern that creates an abstraction layer representing a web page within automated tests, encapsulating its elements and interactions for cleaner, maintainable, and reusable code.

Detailed explanation

The Page Object Model (POM) is a widely adopted design pattern in test automation, particularly beneficial when working with UI-based testing frameworks like Puppeteer. It aims to reduce code duplication and improve test maintainability by creating a separate class (the "Page Object") for each web page or significant section of a web application's UI. This class contains all the locators (e.g., CSS selectors, XPath expressions) and methods that interact with the elements on that page.

In the context of Puppeteer, a Page Object typically encapsulates the Puppeteer Page instance and provides methods to perform actions on the page, such as clicking buttons, filling forms, navigating to specific URLs, and retrieving data. This abstraction allows tests to interact with the UI through well-defined methods, hiding the underlying implementation details of how the page is structured and how Puppeteer interacts with it.

Benefits of using Puppeteer Page Objects:

  • Improved Code Readability: Tests become more readable because they interact with the UI through semantic methods rather than directly manipulating DOM elements.
  • Reduced Code Duplication: Locators and interactions are defined in a single place, eliminating the need to repeat them across multiple tests.
  • Enhanced Maintainability: Changes to the UI only require modifications to the corresponding Page Object, rather than updating every test that interacts with that page.
  • Increased Reusability: Page Objects can be reused across multiple tests, promoting consistency and reducing development effort.
  • Abstraction: Hides the complexity of the underlying UI structure from the test logic.

Practical Implementation:

Let's consider a simple example of a login page with fields for username and password, and a submit button.

First, create a LoginPage.js file:

// LoginPage.js
const puppeteer = require('puppeteer');
 
class LoginPage {
    /**
     * @param {puppeteer.Page} page
     */
    constructor(page) {
        this.page = page;
        this.usernameSelector = '#username';
        this.passwordSelector = '#password';
        this.loginButtonSelector = '#login-button';
        this.successMessageSelector = '#success-message';
    }
 
    async navigate() {
        await this.page.goto('https://example.com/login'); // Replace with your login page URL
    }
 
    async login(username, password) {
        await this.page.type(this.usernameSelector, username);
        await this.page.type(this.passwordSelector, password);
        await this.page.click(this.loginButtonSelector);
        await this.page.waitForSelector(this.successMessageSelector);
    }
 
    async getSuccessMessage() {
        return await this.page.$eval(this.successMessageSelector, el => el.textContent);
    }
}
 
module.exports = LoginPage;

In this example:

  • The LoginPage class encapsulates the Puppeteer Page instance.
  • It defines selectors for the username field, password field, login button, and success message.
  • The navigate() method navigates to the login page.
  • The login() method enters the username and password, clicks the login button, and waits for the success message to appear.
  • The getSuccessMessage() method retrieves the text of the success message.

Now, you can use this Page Object in your test:

// login.test.js
const puppeteer = require('puppeteer');
const LoginPage = require('./LoginPage');
 
describe('Login Page', () => {
    let browser;
    let page;
    let loginPage;
 
    beforeAll(async () => {
        browser = await puppeteer.launch();
        page = await browser.newPage();
        loginPage = new LoginPage(page);
    });
 
    afterAll(async () => {
        await browser.close();
    });
 
    it('should login successfully with valid credentials', async () => {
        await loginPage.navigate();
        await loginPage.login('valid_user', 'valid_password');
        const successMessage = await loginPage.getSuccessMessage();
        expect(successMessage).toContain('Login successful');
    });
 
    it('should display an error message with invalid credentials', async () => {
        // Implement test for invalid credentials
    });
});

In this test:

  • We create instances of Browser and Page from Puppeteer.
  • We create an instance of the LoginPage Page Object, passing the Page instance to its constructor.
  • The test interacts with the login page through the methods provided by the LoginPage object.

Best Practices:

  • Keep Page Objects focused: Each Page Object should represent a single page or a logical section of a page.
  • Expose only necessary methods: Page Objects should only expose methods that are relevant to the tests that will use them. Avoid exposing internal implementation details.
  • Use descriptive method names: Method names should clearly indicate the action they perform on the page.
  • Handle dynamic content: If the page contains dynamic content, use appropriate selectors and waiting strategies to ensure that the tests are reliable.
  • Consider using a base Page Object class: Create a base class that provides common functionality, such as navigation and error handling, and have all other Page Objects inherit from it.
  • Use data-testid attributes: Add data-testid attributes to your HTML elements. These attributes are specifically for testing and are less likely to change than CSS classes or IDs, leading to more stable tests.

Common Tools and Libraries:

  • Puppeteer: The core library for controlling Chrome or Chromium programmatically.
  • Jest or Mocha: Popular JavaScript testing frameworks.
  • Chai or Expect: Assertion libraries for verifying expected outcomes.
  • dotenv: For managing environment variables, such as URLs and credentials.

By following these guidelines and utilizing the Page Object Model with Puppeteer, you can create robust, maintainable, and scalable automated tests for your web applications. This approach not only simplifies test development but also reduces the effort required to maintain tests as the application evolves.

Further reading