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.
 
Next, we setup Playwright for end-to-end testing. Playwright is a tool for automating browsers, and it can be used to test web applications.
Setting up e2e-tests directory for Playwright
To create a directory e2e-tests and to setup Playwright, 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.48.2",
    "@types/node": "^22.8.6"
  }
}
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.
const { defineConfig, devices } = require('@playwright/test');
/**
 * @see https://playwright.dev/docs/test-configuration
 */
module.exports = 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.
Playwright and Docker
To run the tests with Docker, we need to create a Dockerfile for Playwright.
Before starting, remove the
tests-examplesandnode_modulesfolders from 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.48.1-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.48.1-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
  e2e-tests:
    entrypoint: "/bin/true"
    build: e2e-tests
    network_mode: host
    depends_on:
      - client
    volumes:
      - ./e2e-tests/tests:/app/tests
  server:
    build: server
    restart: unless-stopped
    volumes:
      - ./server:/app
    ports:
      - 8000:8000
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 website. Modify the test file example.spec.js in the folder tests of the Playwright project to match the following.
const { test, expect } = require("@playwright/test");
test('Pressing "Fetch message" shows message.', async ({ page }) => {
  await page.goto("/");
  // hydration hack, more on this later on in the course
  await page.waitForTimeout(1000);
  await page.getByRole("button", { name: "Fetch message" }).click();
  await expect(page.getByText("Message is: Hello world!")).toBeVisible();
});
Now, when you run the tests, you see the following output.
$ docker compose run --rm --entrypoint=npx e2e-tests playwright test
[+] Creating 2/0
 ✔ Container wsd-walking-skeleton-api-1     Running                                                                                                  0.0s
 ✔ Container wsd-walking-skeleton-client-1  Running                                                                                                  0.0s
Running 1 test using 1 worker
  ✓  1 [chromium] › example.spec.js:4:1 › Pressing "Fetch message" shows message. (1.4s)
  1 passed (2.9s)
The test passes. At this point, our walking skeleton has a server that responds to messages, a client that can be used to fetch a message from the server and to show it, and end-to-end tests that can be used to verify that the client works as expected.