Back to Testing guides

Playwright Testing Essentials: A Beginner's Guide

Ayooluwa Isaiah
Updated on January 28, 2024

End-to-end (E2E) testing is a methodology used to verify whether the flow of an application is performing as designed from start to finish. This type of testing aims to simulate the effects of real user inputs on the system under test and validate that the results are as expected.

For example, an end-to-end test for an e-commerce website should cover the entire purchase process, including product page functions (like 'add to cart'), customer data validation, payment processing, successful transaction confirmation, and the accuracy of confirmation emails.

Several tools have been developed to facilitate and automate end-to-end testing in web environments, yet Playwright distinguishes itself as a particularly feature-rich, reliable, scalable, and efficient option in this domain.

In this article, we'll examine Playwright's capabilities and architecture, then explore some basic examples of automating and debugging end-to-end tests for web applications.

Let's get started!

What is Playwright?

Screenshot from 2024-01-28 17-12-24.png

Playwright is an open-source end-to-end test automation framework developed by Microsoft for the modern web. It allows you to write scripts that facilitate cross-browser and cross-platform web interactions using a unified API.

The framework is capable of automating a variety of browser tasks, including clicks, typing, and scrolling, as well as more intricate actions like drag-and-drop and multi-touch gestures.

Playwright supports major browser engines like Chromium, Firefox, and WebKit, and it can also emulate mobile devices to test across various screen sizes and resolutions. It runs these tests in headless mode by default for use in CI pipelines, but also supports headed and UI modes for a better development experience.

playwright-ui-mode.png

Other key features include the ability to simulate different user interactions, intercept network traffic to analyze application behavior under various conditions, and capture screenshots and videos for debugging and visual regression tests.

At the time of writing, Playwright's API is compatible with JavaScript, TypeScript, Python, .NET (C#), and Java, and it fully supports Debian-based Linux distributions (like Ubuntu), macOS, and Windows.

Use cases of Playwright

The capabilities of Playwright make it immensely useful in many scenarios, particularly when developing and testing web applications. Some common Playwright use cases include:

1. Test automation

With Playwright, you can automate user interactions in a web application to validate its functionality and performance. This includes testing user flows, form submissions, and multi-page navigation.

You can also use it for regression testing to ensure new updates work as expected without breaking existing functionality. Since the test suite runs on the three major browser engines, you can easily verify that your applications work consistently across different browsers and platforms.

Playwright's mobile emulation capabilities are also helpful for testing UI responsiveness and UX consistency across devices. It can also assess web app performance, responsiveness, and accessibility to verify compliance with accepted standards.

2. Web scraping

For web scraping, Playwright offers a robust solution that comes in handy where conventional tools are inadequate.

By operating like a real browser, it can scrape data from sites that dynamically load content using client-side JavaScript. This makes it effective for extracting information from modern, interactive websites.

Despite some websites employing anti-scraping measures like CAPTCHAs or authentication, Playwright can often navigate these obstacles by mimicking human interactions more accurately than traditional scraping techniques.

3. Automating repetitive web tasks

Another significant use case of Playwright is its ability to automate repetitive web tasks, which could help save time and improve efficiency in various scenarios. For example, you can automate data entry or form submissions, routine administrative tasks, and many other repetitive actions.

It even offers a feature where you can record the actions in your browser to a Playwright script file, and then run it on schedule or as needed.

A key feature of Playwright in these scenarios is its ability to interact with web pages as a real user would, which makes complex user interactions automatable.

Understanding Playwright's architecture

Playwright's architecture is closely aligned with modern browsers and operates out-of-process, avoiding the limitations of in-process test runners like Cypress. It uses a WebSocket connection for bi-directional client-server communication, which is faster and more efficient than communicating over HTTP.

Here's a concise breakdown:

  • Client: Playwright provides API bindings for interacting with the Playwright framework in various programming languages. These APIs provide a set of high-level commands which are translated into concise browser actions.

  • Server: The Playwright Node.js server is what facilitates communication between the client scripts and supported browser engines.

  • Client-server communication: For each script, Playwright establishes a low-latency WebSocket-based communication channel over a single TCP connection between the client and server that remains in place until the test is completed.

Since commands are sent on a single connection, chances of test failure or flakiness is reduced, and commands are executed much quicker than in alternative tools which use HTTP connection idioms.

Getting started with Playwright

Now that you have a better understanding of what Playwright is and what it can do, we'll proceed to get a feel of how it works for the remainder of this article.

For a new project, use this command to install Playwright and its dependencies:

 
npm init playwright@latest <project_dir>

For example:

 
npm init playwright@latest playwright-tutorial

In an existing project, navigate to the root directory and run without <project_dir>:

 
npm init playwright@latest

During setup, choose these options:

 
Initializing project in 'playwright-tutorial'
✔ Do you want to use TypeScript or JavaScript? · JavaScript
✔ Where to put your end-to-end tests? · tests
✔ Add a GitHub Actions workflow? (y/N) · false
✔ Install Playwright browsers (can be done manually via 'npx playwright install')? (Y/n) · true
✔ Install Playwright operating system dependencies (requires sudo / root - can be done manually via 'sudo npx playwright install-deps')? (y/N) · true

playwright-init.png

Once these options are selected, the installation process will continue until the necessary components, including Chromium, Firefox, and WebKit browser engines are downloaded.

Once the installation is done, explore the new project directory:

 
cd playwright-tutorial
 
tree -I node_modules

You'll see the directory's contents with node_modules excluded:

Output
.
├── package.json
├── playwright.config.js
├── package-lock.json
├── test-results
├── tests
│   └── example.spec.js
└── tests-examples
    └── demo-todo-app.spec.js

In playwright.config.js, you'll find the configuration for Playwright, including the selected browsers for tests. The tests folder contains a basic test to verify that Playwright is set up correctly, while tests-examples provides more detailed example tests for a todo app.

The anatomy of a Playwright test

To understand how Playwright tests work, let's look at the sample test provided in the initial setup. Open tests/example.spec.js in your editor:

 
code tests/example.spec.js
tests/example.spec.js
// @ts-check
const { test, expect } = require('@playwright/test');

test('has title', async ({ page }) => {
  await page.goto('https://playwright.dev/');

  // Expect a title "to contain" a substring.
  await expect(page).toHaveTitle(/Playwright/);
});

test('get started link', async ({ page }) => {
  await page.goto('https://playwright.dev/');

  // Click the get started link.
  await page.getByRole('link', { name: 'Get started' }).click();

  // Expects page to have a heading with the name of Installation.
  await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
});

This script consists of two test cases to validate certain aspects of Playwright's documentation website.

The first test is named has title, and it navigates to https://playwright.dev/ using the page.goto() method (similar to entering a URL in the browser).

The expect() method is subsequently used to make assertions on the page's contents and attributes. Here, /Playwright/ is a regular expression pattern, and toHaveTitle() asserts that the contents of the page's <title> tag matches this pattern.

playwright-website-title.png

In the second test, the Playwright homepage is also visited. Once the page loads, the GET STARTED link is located and clicked.

playwright-getting-started.png

The resulting page is then asserted to have an Installation heading that is visible on the page.

playwright-installation-heading.png

The screenshots above demonstrate that the page and its interactions align with the requirements of both tests. However, to confirm their effectiveness, we must execute the tests through Playwright as detailed in the following section.

Running Playwright tests

To execute your test scripts, use the playwright test command. It runs all the scripts in the tests folder across all browsers specified in the playwright.config.js file.

By default, tests are conducted in headless mode, which means that no browser window is opened and the outcomes are displayed in the terminal.

 
npx playwright test
Output
Running 6 tests using 6 workers
  6 passed (20.1s)

To open last HTML report run:

  npx playwright show-report

The output shows six tests passing because both tests in the tests/example.spec.js run in Chromium, Firefox, and WebKit respectively as specified in the projects key of your playwright.config.js file:

playwright.config.js
. . .
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },

    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },

    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },

    . . .
],

. . .

The tests were also executed concurrently using six worker processes. Each of these workers represent a separate OS process, all equipped with identical environments.

Before exploring the test results, let's take a look at some options available for running tests.

Running specific test scripts

To run specific test files among many, specify the desired files like this:

 
npx playwright test <file1> <file2> . . .

You can also run all the tests contained in different directories like this:

 
npx playwright test tests/<dir1>/ tests/<dir2>/

Running a subset of your tests

Run tests matching a regular expression with the -g flag. For instance, to execute only the has title test, run:

 
npx playwright test -g 'has title'

Running tests in certain browsers

Use the --project flag to run tests on specified browsers, matching the name in your Playwright config's projects array.

 
npx playwright test --project 'chromium' 'firefox' # only run the tests in Chromium and Firefox

Running tests in headed mode

By default, Playwright tests run in headless mode to improve execution speed and compatibility with server environments. To watch the tests in a browser UI, use the --headed flag.

For example, combine --headed with --project to see tests in the Chromium browser:

 
npx playright test --headed --project 'chromium'

The browser will open, run the tests, and close afterward.

Examining Playwright test results

Once a test run is completed, an HTML report is saved in the playwright-report directory. Open it with the following command:

 
npx playwright show-report
Output
Serving HTML report at http://localhost:9323. Press Ctrl+C to quit.

This report, accessible at http://localhost:9323, should launch in your default browser. It displays all test cases, their results in the different browsers, and their execution times. The report allows filtering by status or searching for specific tests.

Screenshot 2024-01-19 at 10-44-28 Playwright Test Report.png

To demonstrate a test failure, alter your has title test like so:

tests/example.spec.js
const { test, expect } = require('@playwright/test');

test('has title', async ({ page }) => {
  await page.goto('https://playwright.dev/');

  // Expect a title "to contain" a substring.
await expect(page).toHaveTitle(/Wikipedia/);
});

This change will cause a failure, as the Playwright homepage title doesn't include Wikipedia. Run the test:

 
npx playwright test

The test report automatically opens in your default browser when a failure is detected:

Screenshot from 2024-01-19 11-06-35.png

If you click on the failing test, you'll see more details such as the execution times for each action and the action that lead to the failure.

In this case, the test fails due to the mismatch between the expected and actual title.

Debugging Playwright tests

To conclude this article, let's examine how to debug Playwright tests using the Playwright Inspector. To engage the Inspector for test runs, use the --debug flag:

 
npx playwright test --debug

For example, let's debug the get started link test in Chromium alone by combining the --project and -g flags as follows:

 
npx playwright test --debug --project=chromium -g 'get started'

This will open both a Chromium browser and the Playwright Inspector window. Initially, the test is paused, so the browser window appears blank.

playwright-debug.png

To move to the next action in the test, you can press F10 or click the Step over button. You'll see the current action highlighted in the test code, and matching elements highlighted in the browser window.

playwright-debug-2.png

You'll have to repeat this for as many actions in the test until you get to the debugging point.

If your test contains many actions, you can add the page.pause() method to pause it just before the action you're interested in. For instance, to pause after clicking the 'Get started' link but before the assertions, you can write:

tests/example.spec.js
. . .

test('get started link', async ({ page }) => {
  await page.goto('https://playwright.dev/');

  // Click the get started link.
  await page.getByRole('link', { name: 'Get started' }).click();

await page.pause();
// Expects page to have a heading with the name of Installation. await expect( page.getByRole('heading', { name: 'Installation' }) ).toBeVisible(); });

With this, the test initially pauses, but you can jump directly to the page.pause() line by pressing F8 or the Resume button.

playwright-debug-3.png

Without page.pause(), resuming runs the test to completion.

While paused, use the Pick locator tool to select any page element. The code for the element will appear in the Locator tab.

playwright-debug-4.png

Playwright uses a unique combination of values to identify the target element so it's safe to copy the locator and use it in your tests as is.

Final thoughts

In this article, we only focused on Playwright's basic test automation capabilities and how you can get started with running and debugging test scripts.

In the next article, you'll automate the end-to-end testing of a web application and learn many of Playwright's advanced features along the way.

Thanks for reading, and happy coding!

Author's avatar
Article by
Ayooluwa Isaiah
Ayo is the Head of Content at Better Stack. His passion is simplifying and communicating complex technical ideas effectively. His work was featured on several esteemed publications including LWN.net, Digital Ocean, and CSS-Tricks. When he’s not writing or coding, he loves to travel, bike, and play tennis.
Got an article suggestion? Let us know
Next article
Playwright End-to-End Testing: A Step-by-Step Guide
Learn to learn to develop and execute Playwright test scripts, utilize its time travel debugging capabilities, and proactively identify visual regressions in this hands-on guide
Licensed under CC-BY-NC-SA

This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

Make your mark

Join the writer's program

Are you a developer and love writing and sharing your knowledge with the world? Join our guest writing program and get paid for writing amazing technical guides. We'll get them to the right readers that will appreciate them.

Write for us
Writer of the month
Marin Bezhanov
Marin is a software engineer and architect with a broad range of experience working...
Build on top of Better Stack

Write a script, app or project on top of Better Stack and share it with the world. Make a public repository and share it with us at our email.

community@betterstack.com

or submit a pull request and help us build better products for everyone.

See the full list of amazing projects on github