End-to-end Testing with Playwright
Learning Objectives
- You know how to add Playwright to a project for end-to-end testing.
- You know how to run Playwright tests using Docker.
At this point, we have a walking skeleton that provides an user interface that interacts with a database through a server-side application. Next, we add end-to-end tests to the project using Playwright.
Setting up Playwright
Next, as the last step for the walking skeleton, we’ll add end-to-end tests to the project. End-to-end tests are used to test the whole application, including the user interface, the server-side application, and the database.
We use Playwright, which is a tool for automating browsers and testing web applications.
Inside the walking-skeleton folder in a terminal, run the command deno run -A npm:create-playwright@latest e2e-tests in the root folder of the project. The command creates a folder called e2e-tests and initiates a project for running Playwright tests. The command asks for a few options — use the following:
If the command does not work as expected, skip to the section “Creating the e2e-tests folder manually”.
- Pick “JavaScript when asked whether you want to use TypeScript or JavaScript.
- Pick “tests” when asked where to put the end-to-end tests.
- Pick “false” when asked whether to add a GitHub Actions workflow.
- Pick “false” when asked whether to install Playwright browsers.
- Pick “false” when asked whether to install Playwright operating system dependencies.
Once done, the project has a new folder e2e-tests with the following contents (omitting node_modules).
cd e2e-tests
tree --dirsfirst
.
├── node_modules
│ └── ...
├── tests
│ └── example.spec.js
├── tests-examples
│ └── demo-todo-app.spec.js
├── package.json
├── package-lock.json
└── playwright.config.js
Creating the e2e-tests folder manually
If the command deno run -A npm:create-playwright@latest e2e-tests does not work as expected, you can create the folder manually. To do this, create a folder e2e-tests in the root folder of the project and do the following steps:
- Create a folder
testsunder the foldere2e-tests, and create a file calledexample.spec.jsto the folder. Copy the following contents to the file.
// @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/);
});
-
Create a file called
playwright.config.jsto the foldere2e-tests. Copy the text “TODO” to the file. The actual contents needed will be shown in the section “Cleaning up the Playwright configuration” in a second. -
Create a file called
package.jsonto the foldere2e-testsand copy the following contents to the file.
{
"name": "e2e-tests",
"version": "1.0.0",
"main": "index.js",
"scripts": {},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"devDependencies": {
"@playwright/test": "^1.54.2",
"@types/node": "^24.2.0"
}
}
Remember to save the files!
Once ready, run the command deno install --allow-scripts in the folder. Now, you should be able to proceed.
Cleaning up the Playwright configuration
The playwright.config.js provides the configuration for Playwright. For our purpose, the configuration is a bit excessive, so let’s cut it down. Modify the configuration to match the following.
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
testDir: "./tests",
reporter: "list",
use: {
baseURL: "http://localhost:5173",
trace: "off",
},
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
],
});
In effect, we declare the directory for the tests and ask for the test results to be printed in a list format. Furthermore, we use http://localhost:5173 for the starting point for the tests, do not collect test traces, and run the tests using Chromium.
We’ll be running the tests with Docker and will download the necessary components there.
Containerizing Playwright
To run the tests with Docker, we need to create a Dockerfile for Playwright.
Before starting, remove the
tests-examplesandnode_modulesfolders from thee2e-testsfolder, and remove also thepackage-lock.jsonfile. If you created thee2e-testsfolder manually, these folders / files do not exist.
Create a file called Dockerfile into the folder e2e-tests with the following contents.
FROM mcr.microsoft.com/playwright:v1.54.2-jammy
WORKDIR /app
COPY package*.json .
COPY *config.js .
RUN npm install
RUN npx playwright install chromium
COPY . .
CMD [ "npx", "playwright", "test" ]
The above configuration uses the image mcr.microsoft.com/playwright:v1.54.2-jammy as the base image, creates a folder /app to the image and copies the configuration files to the folder. Then, we install the dependencies and the Chromium browser, which are used to run the tests. This is followed by copying the rest of the contents (i.e., the tests) to the image and finally, at the last line, the image is configured to run the tests using the command npx playwright test.
Next, we wish to add Playwright to the Docker Compose configuration to allow us to run the tests using Docker Compose. To do this, we add the following service to the compose.yml file.
e2e-tests:
entrypoint: "/bin/true"
build: e2e-tests
network_mode: host
depends_on:
- client
volumes:
- ./e2e-tests/tests:/app/tests
The above configuration creates a service called e2e-tests that uses the Dockerfile we just created. The entrypoint is used to indicate that we do not wish to run the tests by default (i.e., whenever we run docker compose up). The service is configured to use the host network, which allows the service to access applications running on the host machine. The service depends on the client service, which is the user interface of the application. Finally, the service is configured to map the tests in the folder e2e-tests/tests to the container.
At this point, the whole compose.yaml file is as follows.
services:
client:
build: client
restart: unless-stopped
volumes:
- ./client/src:/app/src
ports:
- 5173:5173
depends_on:
- server
server:
build: server
restart: unless-stopped
volumes:
- ./server:/app
ports:
- 8000:8000
env_file:
- project.env
depends_on:
- database
database:
container_name: postgresql_database
image: postgres:17.5
restart: unless-stopped
env_file:
- project.env
database-migrations:
image: flyway/flyway:11.10
env_file:
- project.env
depends_on:
- database
volumes:
- ./database-migrations:/flyway/db/migration
command: -connectRetries=60 -baselineOnMigrate=true migrate
e2e-tests:
entrypoint: "/bin/true"
build: e2e-tests
network_mode: host
depends_on:
- client
volumes:
- ./e2e-tests/tests:/app/tests
With the adjustments to the configuration, run the command docker compose up --build to start the services and to create the Playwright image — this takes a while on the first.
Running Playwright tests
With the project running, we can run the tests.
The tests are run using the command docker compose run --rm --entrypoint=npx e2e-tests playwright test in the root folder of the project. Note! Keep the project running in a separate terminal window.
The command runs the tests and prints the results in the terminal, removing a created test container after it has finished.
docker compose run --rm --entrypoint=npx e2e-tests playwright test
[+] Creating 2/0
✔ Container wsd-walking-skeleton-api-1 Running
✔ Container wsd-walking-skeleton-client-1 Running
Running 2 tests using 1 worker
✓ 1 [chromium] › example.spec.js:4:1 › has title (879ms)
✓ 2 [chromium] › example.spec.js:11:1 › get started link (1.1s)
2 passed (2.8s)
If you created the
e2e-testsfolder manually, there’s just one test instead of two.
Testing our application
The tests that come with Playwright by default test the Playwright website. Rename the test file example.spec.js in the folder tests of the Playwright project to todos.spec.js and modify the contents of the file to match the following.
import { expect, test } from "@playwright/test";
test('Page at / has heading "Todos".', async ({ page }) => {
await page.goto("/");
await expect(page.getByText("Todos")).toBeVisible();
});
Now, when you run the tests, you see the following output.
$ docker compose run --rm --entrypoint=npx e2e-tests playwright test
(...)
Running 1 test using 1 worker
✓ 1 [chromium] › tests/todos.spec.js:3:5 › Page at / has heading "Todos". (334ms)
1 passed (2.3s)
The simple test passes, yay!
Summary
In summary:
- End-to-end tests are used to test the whole application, including the user interface, the server-side application, and the database. Playwright is a tool for automating browsers and testing web applications.
- We added Playwright to the project for end-to-end testing.
- We created a Dockerfile for Playwright and added Playwright to the Docker Compose configuration.
- We ran the Playwright tests using Docker Compose.
- We created a simple test that checks that the user interface has the heading “Todos”.
At this point, the walking skeleton should have four folders, client, server, database-migrations, and e2e-tests, and two files, compose.yaml and project.env. The structure of the project should be similar to the following (the contents of almost all of the folders are omitted).
$ tree --dirsfirst
.
├── client
│ └── ...
├── database-migrations
│ └── ...
├── e2e-tests
│ ├── tests
│ │ └── todos.spec.js
│ └── ...
├── server
│ └── ...
├── compose.yaml
└── project.env
Now, you have a walking skeleton that includes a client-side application, a server-side application, a database, and end-to-end tests. Use this as the foundation for the rest of the course!