JavaScript testing #17. Introduction to End-to-End testing with Playwright

JavaScript Testing

This entry is part 17 of 18 in the JavaScript testing tutorial

With End-to-End testing (E2E), we test a fully working application where different parts of our application work together in real-life scenarios from start to finish. The End-to-End tests might also act as regression tests that check if our latest changes haven’t broken any previous features. We might also use them as smoke tests that ensure that the new version of our application is functional and that the crucial functionalities work as expected.

Thet term “smoke testing” is an analogy to electronics. It refers to the first moments of powering up the circuit and making sure that no smoke is coming from the device.

Introducing Playwright

When performing End-to-End tests on a frontend application, we simulate how real users interact with its interface. To do that, we can use Playwright, a framework that allows us to write automated tests that interact with our application through a web browser.

The most straightforward way to start working with Playwright is to run the command in a new, empty directory. It will create a new project with Playwright, but it will first ask us a few questions. For example, it will ask if we want to install the dependencies necessary to run the tests in the simulated browser environment.

If you have the latest version of Ubuntu (23.10), you’re out of luck – Playwright does not support it. I had to manually download the libicu70 and libffi7 deb packages and even compile libx264-163 from source code.

The Playwright framework also asks us what the name of the directory is. By default, it is called .

Writing our first test

Playwright is built to resemble other JavaScript testing frameworks and provides functions such as and that let us structure our tests.

homepage.test.ts

For each test, Playwright creates an instance of a page that provides methods to interact with a single tab in the browser.

In our test, we start by navigating to a particular page using the function. Since it interacts with the browser, it is asynchronous. Therefore, we should wait for it to finish using the keyword.

Then, we use the function to check if the page meets a particular condition. The function is asynchronous and waits until the page has a specific title. Because of that, we need to use the keyword.

Using environment variables

If all of our tests focus on a particular web page, we can add its URL to the Playwright configuration generated when we ran the command.

playwright.config.ts

Thanks to that, we don’t need to type it explicitly in our tests. Instead, we can provide a path relative to the we provided in the configuration.

homepage.test.ts

There is a good chance that we will want to run our tests in various environments. When developing the application locally, we might want to point to . In other cases, we will want to use a real, deployed application. To achieve this, we should use environment variables that we can configure per environment.

First, let’s create the file containing our application’s URL.

.env

A good practice is to avoid commiting the file to the repository because it might sometimes contain sensitive invormation.

A very straightforward way to ensure our application loads the above file is to use the dotenv library.

playwright.config.ts

Now, whenever we want to change the URL of the application we are testing, we can modify the file.

Running our tests

One way to run our tests is through the UI mode. It lets us choose which tests to run and in what browsers. We can also see Playwright interact with our website in real-time and ensure the tests run as expected.

We can also run our tests in the headless mode. With this approach, no browser windows are opened, and we can see the results in the terminal. By default, the tests will run on multiple browsers.

Interacting with the page

Playwright can interact with the website in many different ways. To interact with an element on our page, we should first find it using the Locators API built into Playwright. Let’s find the search input on .

Now, we fill the input with text. It’s asynchronous because Playwright waits for the element to be actionable before interacting with it.

We can submit the form by pressing the enter key.

Finally, we can check if submitting the form caused the search results to appear.

search.test.ts

The function is asynchronous because Playwright waits until the element we are looking for appears on the page.

Playwright waits a maximum of 5 seconds by default.

 

Besides checking if there is a particular piece of text on the page, there are multiple other assertions we can peform.

Organizing our tests

If we have multiple related tests in one file, there is a high chance that they will become repetitive.

search.test.ts

To deal with that, we can create hooks that run before each test in a particular group of tests marked with .

search.test.ts

Thanks to this, our tests are easier to read and more scalable.

Summary

In this article, we’ve gone through the basics of End-to-End (E2E) tests and how to perform them with Playwright. To do that, we learned about different options for executing our tests with Playwright. We also used environment variables, interacted with our website in various ways, and learned how to organize our tests better.

Thanks to automating browser interactions, Playwright is a helpful tool for ensuring that our applications work as expected in real-life scenarios. It can be a very valuable asset for helping us make our software more reliable and bulletproof.

Series Navigation<< JavaScript testing #16. Snapshot testing with React, Jest, and VitestJavaScript testing #18. E2E Playwright tests for uploading and downloading files >>
Subscribe
Notify of
guest
1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Wad*
Wad*
8 months ago

Hi Marcin, thanks for the helpful article.

How would you compare Playwright to Cypress regarding E2E testing? Where do they overlap and differ? Do you have any recommendations on when to use which framework? Is one of them better suited for CI/CD pipelines?