DEV Community

rico
rico

Posted on

How to Run Playwright in CI Pipeline

Why We Needed End-to-End CI Tests

For a long time, our team relied on unit tests and a handful of manual checks to validate frontend changes. It felt “good enough” until reality proved otherwise.

We were regularly delivering new features without any guarantee that the UI wouldn’t break elsewhere.

First of all, unit tests only covered logic, not real user interactions. They didn’t click buttons, fill forms, navigate pages, or handle permission side.

Secondly, manual testing wasn’t good either: slow, repetitive, incomplete, and easily skipped.

And then a critical bug hit production. The backend CI was green and the deployment went out smoothly, but users immediately faced broken UI behavior.

That incident made it clear: our CI pipeline was blind to the frontend.
We needed a reliable way to catch frontend issues before they reached production.

Why We Choose Playwright ?

We needed a way to test the frontend exactly as users experience it, automatically, on every code change.

Our requirements were simple but non-negotiable:

  • Fully reproducible and automated

  • Fast and reliable execution, suitable for CI pipelines

  • Easy to maintain and extend as the app grows

  • Cross-browser support (Chromium, Firefox )

  • Provides detailed failure artifacts (screenshots, videos, traces)

Able to simulate real user behavior: clicks, navigation, form inputs, and async interactions.

After evaluating options, we chose Playwright. It runs tests in real browsers (Chromium, Firefox…), screenshots, videos, and traces on failure, and fits naturally into CI workflows.

With this setup, we finally had confidence that every release would behave correctly for our users, before it ever reached production.

Implementing Playwright in Our CI Pipeline

Once we decided on Playwright, the next challenge was integrating it into our CI workflow. The goal: run end-to-end tests automatically on every code change.

First, install Playwright:

npm init playwright@latest
Enter fullscreen mode Exit fullscreen mode

This command generates a ready to use test structure, including:

  • Sample tests

  • Test runner configuration (playwright.config.ts)

  • Browser binaries (Chromium, Firefox … )

Next, verify that the tests run locally:

npx playwright test
Enter fullscreen mode Exit fullscreen mode

This ensures everything works before integrating into CI.


CI Pipeline Integration

Once Playwright was working locally, the final step was integrating it into our CI pipeline. This important because without automated browser tests in CI, bugs would still slip into production. We needed the pipeline to behave like a real user:

  • Launch the full stack (frontend, backend, database, cache) via Docker

  • Run Playwright tests against that running environment

  • Collect screenshots and traces when something breaks

At the top of the workflow, we define when the CI should run Playwright tests:

This means the workflow will run on any pull request made on the project:

name: Playwright Tests

on:
    pull_request:
        branches:
            - "*"
    workflow_dispatch:
Enter fullscreen mode Exit fullscreen mode

With this part, we ask a runner that runs on ubuntu-22.04 and configure the trigger workflow condition. We exclude PRs with the draft label.

jobs:
    playwright:
        name: Playwright test CI
        runs-on: ubuntu-22.04
        environment: ci
        if: ${{!contains(github.event.pull_request.labels.*.name, 'draft')}}
Enter fullscreen mode Exit fullscreen mode

As a monorepo, we need to checkout the main repository.

- name: Checkout repository
  uses: actions/checkout@v4
  with:
      token: ${{ secrets.GITHUB_PAT }}
Enter fullscreen mode Exit fullscreen mode

This step guarantees that both the frontend (where Playwright tests run) and the backend (required for the API) are present in the runner.


Installing Dependencies

Next, we install project dependencies. To speed up CI runs, we cache npm, so we can restore it:

- name: Cache npm dependencies
  uses: actions/cache@v4
  with:
      path: ~/.npm
      key: ${{ runner.os }}-node-${{ hashFiles('backend/package-lock.json') }}
Enter fullscreen mode Exit fullscreen mode

Then we install dependencies for both backend and frontend using npm ci to ensure clean installs:

- name: Install dependencies
  run: |
      cd backend/
      npm ci
      cd ../frontend/
      npm ci 
Enter fullscreen mode Exit fullscreen mode

Caching & Installing Playwright Browsers

Playwright uses real browser binaries (Chromium, Firefox).

These can take time to download, so we cache them:

- name: Cache Playwright Browsers
  id: playwright-cache
  uses: actions/cache@v3
  with:
      path: ~/.cache/ms-playwright
      key: playwright-${{ runner.os }}-${{ hashFiles('frontend/package-lock.json') }}
Enter fullscreen mode Exit fullscreen mode

If the cache is empty, Playwright will install the browsers:

- name: Install Playwright Browsers
  if: steps.playwright-cache.outputs.cache-hit != 'true'
  run: |
      cd frontend/
      npx playwright install --with-deps
Enter fullscreen mode Exit fullscreen mode

Starting the Application With Docker Compose

Before running Playwright tests, we must start the entire stack.

In our case, the frontend depends on:

  • The backend API

  • PostgreSQL database

GitHub Actions starts everything via Docker Compose:

- name: Build & run docker Compose
  run: |
      docker compose up -d database frontend backend
  env:
      DATABASE_URL: postgresql://postgres:root@database:5432/db?schema=public
      CLIENT_HOST: localhost
      API_URL: "http://localhost:8080"
      CLIENT_PORT: 3000
Enter fullscreen mode Exit fullscreen mode

Running Playwright Tests

Once the application is up and running, we execute the test suite:

- name: Run Playwright Tests
  working-directory: frontend/
  run: npx playwright test
Enter fullscreen mode Exit fullscreen mode

Playwright will:

  • Launch a headless browser

  • Navigate through the app

  • Perform real user actions

  • Screenshot failures

  • Generate traces for debugging

This converts the PR into a full end-to-end test session.


Uploading Artifacts on Failure

When a test fails, Playwright produces:

  • Full-page screenshots

  • HTML trace logs

We upload these artifacts so developers can inspect what went wrong:

- name: Upload screenshots
  if: always() && failure()
  uses: actions/upload-artifact@v4
  with:
      name: test-screenshots
      path: frontend/test-results/
      retention-days: 1

Enter fullscreen mode Exit fullscreen mode

For Debugging Purpose

Locally, you can simulate or run the test and see what Playwright is doing.

This one will launch the browser and run the tests:

npx playwright test --headed
Enter fullscreen mode Exit fullscreen mode

The second one will launch the UI mode of Playwright to have more tools and control on the process:

npx playwright test --ui
Enter fullscreen mode Exit fullscreen mode

Top comments (0)