GitLab CI pipelines for Playwright require one design decision upfront: whether to install browsers during the pipeline or use the official Playwright Docker image. The Docker image approach is faster and avoids the most common failure mode, where a version mismatch between the image tag and @playwright/test in package.json breaks the pipeline with an executable doesn't exist error. This guide covers the minimal working .gitlab-ci.yml, artifact uploads, secrets, caching, parallel sharding, and a multi-environment setup for staging and production.

Prerequisites

  • Playwright project working locally with npx playwright test
  • GitLab repository (gitlab.com or self-hosted)
  • .gitlab-ci.yml doesn't exist yet (or you're adding to it)

The Minimal Working Pipeline

Create .gitlab-ci.yml in your project root:

image: mcr.microsoft.com/playwright:v1.44.0-jammy

stages:
  - test

playwright-tests:
  stage: test
  script:
    - npm ci
    - npx playwright test
  artifacts:
    when: always
    paths:
      - playwright-report/
    expire_in: 7 days

That's it. Push this file and GitLab CI will run your tests on every commit.

Let's understand what each part does.

Line-by-Line Explanation

image: mcr.microsoft.com/playwright:v1.44.0-jammy

This is the Docker container your pipeline runs inside. Playwright's official Docker image comes with:

  • Node.js
  • All Playwright browser dependencies pre-installed
  • Linux environment

This is the recommended approach — no browser installation needed in the pipeline.

Pin the version (v1.44.0, not latest) so your pipeline doesn't break when Playwright releases a new version. Update it manually when you upgrade Playwright locally.

npm ci vs. npm install

In CI, always use npm ci instead of npm install:

  • npm ci installs exactly what's in package-lock.json (deterministic)
  • npm ci is faster in CI environments
  • npm ci fails if package-lock.json is missing or inconsistent

artifacts

Playwright generates an HTML report in playwright-report/ after each run. The artifacts section tells GitLab to save this folder so you can download and view it.

  • when: always — save artifacts even if tests fail (you need the report most when tests fail!)
  • expire_in: 7 days — GitLab automatically deletes artifacts after 7 days

To view the report: in GitLab CI → the pipeline → the job → "Download artifacts" → open playwright-report/index.html.

A More Complete Pipeline

For real projects, you'll want more structure:

image: mcr.microsoft.com/playwright:v1.44.0-jammy

stages:
  - test

variables:
  # Cache node_modules between runs for speed
  npm_config_cache: "$CI_PROJECT_DIR/.npm"

cache:
  key: "$CI_COMMIT_REF_SLUG"
  paths:
    - .npm/
    - node_modules/

playwright-tests:
  stage: test
  
  # Run on merge requests and main branch
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == "main"
  
  script:
    - npm ci
    - npx playwright test --reporter=html,junit
  
  artifacts:
    when: always
    paths:
      - playwright-report/
      - test-results/
    reports:
      junit: test-results/results.xml
    expire_in: 7 days
  
  # Timeout for the whole job
  timeout: 20 minutes

What's new here:

variables: Set environment variables for the job. npm_config_cache tells npm to use a specific cache directory. cache: Node modules are cached between pipeline runs. First run: slow (downloads everything). Subsequent runs: fast (reads from cache). Key is the branch name so different branches have separate caches. rules: Control when this job runs. The config above runs on:
  • Merge requests (so you see test results before merging)
  • Pushes to main (after merging)
--reporter=html,junit: Generate both HTML report and JUnit XML. JUnit format is understood by GitLab and shows test results directly in the pipeline UI. reports: junit: This tells GitLab where the JUnit XML is. GitLab then shows a "Tests" tab in the pipeline with pass/fail per test.

Using Environment Variables (Secrets)

Never put passwords or API keys in .gitlab-ci.yml. Use GitLab CI/CD variables:

Setting variables in GitLab:

1. Project → Settings → CI/CD → Variables

2. Add: BASE_URL, ADMIN_EMAIL, ADMIN_PASSWORD, API_KEY

3. Mark sensitive ones as "Masked" (hidden in logs)

4. Mark environment-specific ones as "Protected" (only runs on protected branches)

Using in your pipeline:

playwright-tests:
  script:
    - npm ci
    - npx playwright test
  variables:
    BASE_URL: $BASE_URL              # From GitLab CI variables
    ADMIN_EMAIL: $ADMIN_EMAIL        # From GitLab CI variables

Using in Playwright config:

// playwright.config.ts
import dotenv from 'dotenv';
dotenv.config();

export default defineConfig({
  use: {
    baseURL: process.env.BASE_URL || 'http://localhost:3000',
  },
});

GitLab CI variables are available as environment variables in the job — process.env.BASE_URL works.

Parallel Execution and Sharding

For large test suites, run tests in parallel:

playwright-tests:
  stage: test
  
  parallel:
    matrix:
      - SHARD: ["1/4", "2/4", "3/4", "4/4"]
  
  script:
    - npm ci
    - npx playwright test --shard=$SHARD
  
  artifacts:
    when: always
    paths:
      - playwright-report/
    expire_in: 7 days

This creates 4 parallel jobs, each running 25% of the tests. Total test time is roughly divided by 4.

Note: Merging the split HTML reports requires additional steps (Playwright's merge-reports command).

Running in Headed Mode (Debugging)

By default, Playwright runs headless in CI. If you need to debug visually:

playwright-debug:
  stage: test
  script:
    - npm ci
    - npx playwright test --headed --video=on
  when: manual  # Only run when manually triggered
  artifacts:
    when: always
    paths:
      - test-results/  # Videos are saved here
    expire_in: 1 day

when: manual means this job doesn't run automatically — you trigger it manually from the GitLab UI when you need to debug.

Handling Flaky Tests

Playwright has built-in retry logic. Configure it for CI:

// playwright.config.ts
export default defineConfig({
  retries: process.env.CI ? 2 : 0,  // Retry 2 times in CI only
});

Or in .gitlab-ci.yml, allow the job itself to retry on failure:

playwright-tests:
  retry:
    max: 1  # Retry the whole job once if it fails
    when:
      - runner_system_failure
      - stuck_or_timeout_failure

Note: Job-level retry (GitLab) is for infrastructure failures. Test-level retry (Playwright config) is for flaky tests. Use both.

Full Example with Multiple Environments

image: mcr.microsoft.com/playwright:v1.44.0-jammy

stages:
  - test

.playwright-base:
  script:
    - npm ci
    - npx playwright test --reporter=html,junit
  artifacts:
    when: always
    paths:
      - playwright-report/
    reports:
      junit: test-results/results.xml
    expire_in: 7 days
  timeout: 20 minutes

playwright-staging:
  extends: .playwright-base
  variables:
    BASE_URL: https://staging.yourapp.com
  rules:
    - if: $CI_COMMIT_BRANCH == "develop"
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

playwright-production:
  extends: .playwright-base
  variables:
    BASE_URL: https://yourapp.com
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
  environment:
    name: production

This runs staging tests on develop branch and PRs, and production tests only when merging to main.

Troubleshooting Common Issues

"Executable doesn't exist" error:

You're not using the Playwright Docker image, or it's the wrong version. Stick with mcr.microsoft.com/playwright:v1.44.0-jammy and the version must match your @playwright/test version exactly.

Tests time out in CI but pass locally:

CI machines are slower. Increase timeouts in playwright.config.ts:

export default defineConfig({
  timeout: 60000,        // 60s per test (up from 30s default)
  actionTimeout: 15000,  // 15s per action
});

"Cannot find module" in CI:

You have a dependency that's not in package.json. Run npm install locally and commit the updated package.json and package-lock.json.

Pipeline slow due to npm install:

Add caching (shown in the complete example above). First run will be slow; subsequent runs will be fast.

Quick Checklist

Before pushing your first pipeline:

  • [ ] .gitlab-ci.yml is in the project root
  • [ ] Using the Playwright Docker image with pinned version
  • [ ] npm ci (not npm install)
  • [ ] Artifacts configured to save playwright-report/
  • [ ] Secrets in GitLab CI variables, not in the YAML file
  • [ ] Playwright retries configured for CI environment
  • [ ] timeout set in playwright.config.ts (CI machines are slower)
→ See also: CI/CD for QA: GitHub Actions, Jenkins, and GitLab Compared | GitHub Actions for Playwright Tests: The Complete Setup (2026) | Playwright Config File Explained: Every Option You Need to Know

With this setup, every push to your repository will automatically run your test suite and make results available in the GitLab pipeline view.