Setting up a Walking Skeleton

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 tests under the folder e2e-tests, and create a file called example.spec.js to 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.js to the folder e2e-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.json to the folder e2e-tests and 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-examples and node_modules folders from from the e2e-tests folder, and remove also the package-lock.json file. If you created the e2e-tests folder 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-tests folder 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.