Rafał Podraza
Back to blog

July 6, 2026

How to Run Playwright Tests in GitHub Actions

Writing automated tests is one thing, but the real value starts when those tests run automatically after every code change. In this article, I will show you how to connect Playwright with GitHub Actions and run end-to-end tests as part of your CI/CD pipeline.

How to Run Playwright Tests in GitHub Actions

If you write automated tests with Playwright, you will quickly reach a point where running them only on your local machine is not enough.

At the beginning, everything looks simple.

You run the tests locally, everything passes, the result is green, and it feels like the topic is covered. The problem usually appears later, when someone changes something in the application, creates a pull request, or deploys a new version, and suddenly an important process stops working.

That is why automated tests should not run only locally. They should also run as part of the CI/CD process.

In this article, I will show you how to run Playwright tests in GitHub Actions.

Why run Playwright tests in GitHub Actions?

The simplest answer is: so you do not have to check everything manually after every change.

GitHub Actions can automatically run tests after a push to the repository or when a pull request is created. This helps you detect much faster whether a change has broken login, registration, shopping cart, payment, contact form, or any other important flow in your application.

In practice, the process looks like this:

  1. A developer makes a change in the code.
  2. The change is pushed to GitHub.
  3. GitHub Actions starts the workflow.
  4. Dependencies are installed.
  5. Playwright tests are executed.
  6. At the end, you can see whether the tests passed or failed.

This is the moment where automation starts to make much more sense.

Tests are no longer something that a tester runs locally from time to time. They become part of the software delivery process.

Example Playwright project

I assume that you already have a Playwright project.

If not, you can create one with the following command:

npm init playwright@latest

After installation, your project should contain files and folders such as:

tests/
playwright.config.ts
package.json

You can run your tests locally with:

npx playwright test

If everything works locally, we can move on to the GitHub Actions configuration.

Where to add the GitHub Actions configuration?

In your repository, create the following directory:

.github/workflows

Inside it, create a file, for example:

playwright.yml

The final path should look like this:

.github/workflows/playwright.yml

This is the file where we define when and how our tests should run.

Basic Playwright workflow

For the beginning, we can use a simple configuration:

name: Playwright Tests

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  test:
    name: Run Playwright tests
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 22

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright browsers
        run: npx playwright install --with-deps

      - name: Run Playwright tests
        run: npx playwright test

      - name: Upload Playwright report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 7

This is already a working foundation.

After adding this file to your repository, GitHub Actions will run your tests after a push to the main branch and for pull requests targeting main.

What do these steps do?

Let’s go through this file calmly, because YAML may look a bit strange at first.

name: Playwright Tests

This is the name of the workflow. It will be visible in the Actions tab on GitHub.

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

Here we define when the workflow should start.

In this case, tests will run after a push to main and for pull requests targeting main.

runs-on: ubuntu-latest

This means that the job will run on an Ubuntu machine. In most cases, this is enough for Playwright tests.

uses: actions/checkout@v4

This step checks out the repository code into the GitHub Actions environment.

uses: actions/setup-node@v4

Here we install Node.js. In this example, I use version 22.

run: npm ci

This command installs project dependencies based on the package-lock.json file.

In CI, it is usually better to use npm ci instead of npm install, because it is more predictable and fits automated environments better.

run: npx playwright install --with-deps

This step installs the browsers and system dependencies required by Playwright.

This is important because on a fresh GitHub Actions machine we cannot assume that everything is already installed.

run: npx playwright test

This is the step where the tests are executed.

if: always()

This part in the report upload step means that the report should be saved even if the tests fail.

And this is very important.

When tests fail, that is exactly when we need reports, screenshots, or traces the most.

HTML report from Playwright tests

Playwright can generate an HTML report by default. After the tests are executed, this report can be saved as an artifact in GitHub Actions.

In our workflow, this part is responsible for that:

- name: Upload Playwright report
  uses: actions/upload-artifact@v4
  if: always()
  with:
    name: playwright-report
    path: playwright-report/
    retention-days: 7

After the workflow finishes, you can open the specific run in GitHub Actions and download the report from the artifacts section.

This is very useful when analyzing failures.

Instead of guessing what happened, you can check which test failed, at which step the problem occurred, and what the browser actually saw.

Trace Viewer as help with difficult failures

One thing I really like about Playwright is the Trace Viewer.

If a test fails, the trace allows you to analyze the test execution step by step. You can see actions, assertions, screenshots, logs, requests, and responses.

In practice, this is much more comfortable than reading only the error message in the console.

In the playwright.config.ts file, it is worth having a configuration like this:

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  retries: process.env.CI ? 2 : 0,
  reporter: 'html',
  use: {
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
  ],
});

In this example:

  • trace is saved on the first retry,
  • screenshot is saved only when a test fails,
  • video is kept only when a test fails,
  • in CI, tests are retried two times.

Of course, you do not always have to use retries. Well-written tests should be stable. But in real projects, retries sometimes help separate temporary environment problems from actual application defects.

What if the application must be started before the tests?

Very often, end-to-end tests require a running application.

Locally, your application may run at:

http://localhost:3000

But in GitHub Actions, it has to be started first.

There are several ways to handle this. One convenient option is the webServer configuration in Playwright.

Example:

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  webServer: {
    command: 'npm run dev',
    url: 'http://127.0.0.1:3000',
    reuseExistingServer: !process.env.CI,
    timeout: 120 * 1000,
  },
  use: {
    baseURL: 'http://127.0.0.1:3000',
    trace: 'on-first-retry',
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
  ],
});

With this configuration, Playwright starts the application before the tests, waits until it is available at the given address, and only then runs the tests.

In your tests, you can then use relative paths:

import { test, expect } from '@playwright/test';

test('homepage should load', async ({ page }) => {
  await page.goto('/');
  await expect(page).toHaveTitle(/Example/);
});

This is much more convenient than writing the full address in every test.

Running tests only for pull requests

You do not always have to run tests after every push to every branch.

A common approach is to run tests for pull requests targeting main.

Example:

on:
  pull_request:
    branches:
      - main

This way, the change is checked before it gets merged into the main branch.

In team projects, this makes a lot of sense, because a pull request should usually not be merged if automated tests do not pass.

Running tests on multiple browsers

Playwright allows you to test an application in different browsers, such as Chromium, Firefox, and WebKit.

In the playwright.config.ts file, the configuration can look like this:

projects: [
  {
    name: 'chromium',
    use: { ...devices['Desktop Chrome'] },
  },
  {
    name: 'firefox',
    use: { ...devices['Desktop Firefox'] },
  },
  {
    name: 'webkit',
    use: { ...devices['Desktop Safari'] },
  },
],

Should you always run tests in three browsers?

It depends.

If the project is small, Chromium may be enough at the beginning. If the application is used by many customers and must work correctly across different browsers, then broader browser coverage is worth considering.

I usually start with the most important scenarios in Chromium and add other browsers later, only where it really makes sense for the project.

Common problems in GitHub Actions

During the first Playwright configuration in GitHub Actions, similar problems often appear.

1. Browsers are not installed

If you see an error related to a missing browser, check whether your workflow contains this step:

- name: Install Playwright browsers
  run: npx playwright install --with-deps

Without this step, Playwright may not have the environment needed to run the tests.

2. The application did not start before the tests

If tests try to open localhost, but the application is not running, they will obviously fail.

In this case, it is worth using webServer in the Playwright configuration or adding a separate workflow step that starts the application before the tests.

3. The test works locally but fails in CI

This is a classic problem.

The most common causes are:

  • dependency on local data,
  • timeouts that are too strict,
  • weak selectors,
  • missing waits for elements,
  • differences between environments,
  • tests depending on execution order.

In such cases, trace, screenshots, and the HTML report help a lot.

4. Tests take too long

When the number of tests grows, the workflow may eventually take too long.

At the beginning, this is not a major problem. Later, it is worth thinking about:

  • splitting tests,
  • running only the most important tests on each pull request,
  • using a separate workflow for full regression,
  • test sharding,
  • running the full test set once a day or before a larger release.

Should Playwright tests block a merge?

In my opinion: yes, but with common sense.

If you have a few stable tests that check the most important flows in the application, they can definitely block a merge.

For example:

  • login,
  • registration,
  • shopping cart,
  • payment,
  • form submission,
  • basic purchase flow,
  • the most important user journey.

I would not start by blocking merges with one hundred unstable tests that randomly pass and fail.

It is better to start with a small but stable test suite. Then you can expand it step by step.

Good practice: separate workflow for smoke tests

In many projects, splitting tests works very well.

For example:

  • smoke tests run on every pull request,
  • full regression runs manually or on a schedule,
  • cross-browser tests run before a release.

This way, we do not block the team with a very long pipeline, but we still have automatic quality control.

Smoke tests can check only the most important features of the application:

npx playwright test --grep @smoke

And in the tests, we can mark scenarios in the test name:

test('@smoke user can open homepage', async ({ page }) => {
  await page.goto('/');
  await expect(page).toBeVisible();
});

This is a simple way to separate fast tests from a larger regression suite.

Summary

Running Playwright tests in GitHub Actions is one of those steps that really increases the value of test automation.

As long as tests are run only locally, they are easy to forget. Only when they become part of the pipeline do they truly support the team.

A well-configured workflow allows you to:

  • run tests after a push or pull request,
  • detect bugs faster,
  • save HTML reports,
  • analyze traces and screenshots,
  • block a merge if important scenarios fail.

You do not need a perfect configuration at the beginning.

A simple workflow, a few stable tests, and a report saved as an artifact are enough to start. Later, you can gradually add more elements: retries, traces, smoke tests, sharding, tests in multiple browsers, or full regression.

The most important thing is this: automated tests should run where the team actually makes decisions about code quality.

And in many projects, that place is GitHub Actions.

Get 15% OFF

Sign up for the newsletter and receive a 15% discount code for any course in our shop.

By signing up, you accept the privacy policy.

Most popular courses

If this topic is useful to you, these courses are a strong next step.