Playwright, Microsoft's open-source automation library, is a modern solution for reliable cross-browser testing, solving common flakiness issues often found in older tools. It supports Chromium, Firefox, and WebKit. A simple TypeScript code example demonstrates using Playwright to automate a music search on Spotify.
Getting Started with Playwright
article
I’ve been testing software for almost two decades now, and I still love many aspects of it. The job role and its tasks have changed during these years, with automation becoming critical for ensuring performance, stability, frequent and continuous development, and a smooth user experience.
While Selenium has long (maybe too long, since 2004—so for more than 20 years) been the go-to tool for browser automation, during the last few years, newer tools have emerged with more reliable and more tester-friendly solutions. One such tool is Playwright, an open-source automation library for browser testing and web scraping developed by Microsoft that enables end-to-end testing for modern web apps.
Similarly to my article about pytest and Selenium, I found it worth summing up a new approach using Playwright. Playwright was introduced by Microsoft in early 2020. It was created by the same team that initially worked on Google’s Puppeteer, another browser automation tool. The goal was to create a testing library that supports all modern rendering engines (Chromium, Firefox, WebKit), and solves long-standing pain points in browser testing like flakiness, timing issues, and poor multi-browser coverage. It is free and open-source, so anybody can use it for cross-browser test automation.
Just like pytest, Playwright also has vast documentation and effective integration with Microsoft’s Visual Studio Code (VS Code) with a good number of extensions and plugins available.
After a quick installation, one can easily set up a test. I’ll use a simple code snippet written in TypeScript to open a Chrome browser, hit the popular music streaming application Spotify, and search for my favorite British alternative rock band, Oasis. The expected result will be the band’s name found as the first result on the page. The repository of my test code can be found on GitHub, along with a setup readme if you want to try it out. I also use this script as a conversation starter during QA interviews so there are plenty of enhancement tips and opportunities embedded into this example.
The configuration file only contains one setting: the headed mode, which means the test runner will open up a browser while the test case executes for the tester to see what is going on step-by-step. This file can later be extended to contain the other test data used for this test: every URL to open, any login information, and so on. This allows you to easily modify your tests without having to directly edit your playwright test code itself.
3 test('Search for Oasis and verify entity title', async ({ page }) => {
4 // Navigate to Spotify
5 await page.goto('https://open.spotify.com/'); 7 // Perform search
8 await page.getByTestId('search-input').click();
9 await page.getByTestId('search-input').fill('Oasis');
10 await page.goto('https://open.spotify.com/search/Oasis');
11 await page.getByRole('checkbox', { name: 'Artists' }).click();
12 await page.getByTestId('search-category-card-0').getByRole('link', { name: 'Oasis' }).click(); 14 // Verify entity title
15 const entityTitle = await page.getByTestId('entityTitle').textContent();
16 expect(entityTitle).toContain('Oasis'); // Adjust assertion to match expected result
17 });
The async ({ page } part at line 3, the beginning of the test might feel unusual at first: this means we have an asynchronous test function in the body. The { page } syntax explains to Playwright to automatically provide a fresh browser page for each test. The async keyword lets you wait for asynchronous operations to complete before the next step begins.
After the test starts its WebDriver which is a web test automation library, it searches on the user interface for the search field with the ID search-input in line 8, and types the test data from the fill function. Even though Playwright provides a wide range of locator functions like getByRole, getByTitle and so on, finding these web elements on the UI is still best by their IDs—if Spotify designers keep them like that. But this is not necessarily the case, just as with every mainstream web application. They are not interested in having scripts or bots running on their UI, so this example will probably run until a UI change only.
This issue also is an example of the work needed to continuously maintain all kinds of UI scripts, independent of the testing framework you are using. Keeping up with UI changes is a constant challenge in automated testing, as even minor updates to the application can break existing scripts. To maintain the Playwright tests effectively, it’s important to use robust selectors: prefer data attributes or stable IDs over classes or text of the web elements, which are more likely to change.
The await commands in the code are the Playwright’s built-in waiting mechanisms, making sure the elements are present and ready before interacting with them. Additionally, Playwright supports automatic retries for failed tests, which can be enabled in the configuration to help catch intermittent failures. This can be simply set in the defineConfig like this: retries: 2.
As an exercise, I recorded this test script with the smooth test recording feature of Playwright using the command npx playwright codegen <url>. This is why it has the next URL, https://open.spotify.com/search/Oasis hard-coded in line 10—another exercise to enhance it would be to store the search result in a variable and use it to go forward to the next URI.
You have to make sure you verify your expected result at the end of your test. This is what the expect function in line 16 is for: it will pass the test if its expression is true, and will fail if the result is false. If the rights owner of this rock band removes the whole Oasis catalog from Spotify, like Taylor Swift did a few years ago, we would get an ugly red failing test.
You can run the test with the npx playwright test command. It will identify and run all your tests (functions starting with the test(...) prefix). Just make sure you execute this command in the folder you have your test script in, log off at the end so as not to overload the system to be tested, and quit the driver at the end of the tests.
Playwright is designed to run tests in parallel by default, which means each test file is executed in a separate worker process, allowing multiple browser instances to run simultaneously. You can control the level of parallelism by setting the workers option in the same configuration file—for example, the setting workers: 4 will run up to four test files at the same time.
By default, Playwright runs tests using the Chromium browser, which is the engine behind Google Chrome and Microsoft Edge. However, Playwright also supports testing in Firefox and WebKit (the engine behind Safari), allowing you to ensure cross-browser compatibility. To run tests in a different browser, specify the browserName option in the test configuration file like: browserName: 'firefox',or use the command line to overwrite the default setting: npx playwright test --project=firefox.
This browser setting can also be parametrized by defining multiple projects in the configuration file. Each project can use a different browser engine:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
projects: [
{
name: 'Chromium',
use: { ...devices['Desktop Chrome'], headless: false },
},
{
name: 'Firefox',
use: { ...devices['Desktop Firefox'], headless: false },
},
{
name: 'WebKit',
use: { ...devices['Desktop Safari'], headless: false },
},
],
});
As a next level exercise, you can try building a login function into this script, then adding some artists to a new playlist or simply playing your Release Radar on a Friday.
The learning curve of Playwright is quite fast; you can write (or record and polish) simple tests in a few hours. Luckily, the Playground documentation is proven to be precise, and the widely available LLM code generators like Copilot also provide great help while working with it. Just make sure you select the test cases to be automated consciously, keeping the test pyramid in mind.
Lets Hang!