Learn Playwright in 10 DaysDay 10: CI/CD and Best Practices

Day 10: CI/CD and Best Practices

What You Will Learn Today

  • Why run Playwright in CI/CD
  • Setting up GitHub Actions
  • Test artifacts (reports, screenshots, videos, traces)
  • Running Playwright in Docker
  • Authentication state reuse (storageState)
  • Test design best practices
  • Dealing with flaky tests
  • Test strategy: E2E vs unit/integration
  • 10-day journey summary

Why Run Playwright in CI/CD

Running tests manually on your local machine is not enough to maintain quality over time.

flowchart LR
    subgraph Manual["Manual Testing"]
        M1["Easy to forget"]
        M2["Environment differences"]
        M3["Time-consuming"]
    end

    subgraph CICD["CI/CD Automated Testing"]
        C1["Runs every time"]
        C2["Consistent environment"]
        C3["Fast feedback"]
    end

    style Manual fill:#ef4444,color:#fff
    style CICD fill:#22c55e,color:#fff

By integrating Playwright into your CI/CD pipeline, you can automatically verify that code changes do not break existing functionality.


Setting Up GitHub Actions

Basic Workflow

# .github/workflows/playwright.yml
name: Playwright Tests

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest

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

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - 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 HTML report
        uses: actions/upload-artifact@v4
        if: ${{ !cancelled() }}
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 30

Workflow Overview

flowchart TB
    subgraph CI["GitHub Actions Workflow"]
        A["Checkout code"] --> B["Set up Node.js"]
        B --> C["Install dependencies"]
        C --> D["Install browsers"]
        D --> E["Run tests (headless)"]
        E --> F{"Pass?"}
        F -->|"Yes"| G["Done"]
        F -->|"No"| H["Save report & traces"]
        H --> I["Report failure"]
    end

    style CI fill:#8b5cf6,color:#fff
    style G fill:#22c55e,color:#fff
    style I fill:#ef4444,color:#fff

Caching Browsers

Browser installation takes time. Using a cache can speed up your workflow significantly.

      - name: Cache Playwright browsers
        uses: actions/cache@v4
        id: playwright-cache
        with:
          path: ~/.cache/ms-playwright
          key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }}

      - name: Install Playwright browsers
        if: steps.playwright-cache.outputs.cache-hit != 'true'
        run: npx playwright install --with-deps

      - name: Install system dependencies
        if: steps.playwright-cache.outputs.cache-hit == 'true'
        run: npx playwright install-deps

Test Artifacts

When tests fail in CI, artifacts are essential for investigating the cause.

Configuration in playwright.config.ts

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

export default defineConfig({
  reporter: [
    ['html', { open: 'never' }],
    ['junit', { outputFile: 'results.xml' }],
  ],
  use: {
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
    trace: 'retain-on-failure',
  },
});

Types of Artifacts

Artifact Configuration Purpose
HTML Report reporter: 'html' Visualize test results
Screenshots screenshot: 'only-on-failure' Capture screen on failure
Videos video: 'retain-on-failure' Replay failed tests
Traces trace: 'retain-on-failure' Detailed debugging information
JUnit Report reporter: 'junit' Integration with CI/CD tools

Uploading in GitHub Actions

      - name: Upload test results
        uses: actions/upload-artifact@v4
        if: ${{ !cancelled() }}
        with:
          name: playwright-report
          path: |
            playwright-report/
            test-results/
          retention-days: 30

The test-results/ directory contains screenshots, videos, and trace files.


Running Playwright in Docker

Playwright provides official Docker images with all browsers pre-installed.

Using the Official Image

FROM mcr.microsoft.com/playwright:v1.50.0-noble

WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .

CMD ["npx", "playwright", "test"]

Using Docker in GitHub Actions

jobs:
  test:
    runs-on: ubuntu-latest
    container:
      image: mcr.microsoft.com/playwright:v1.50.0-noble

    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npx playwright test
        env:
          HOME: /root

Using the container image eliminates the need for browser installation steps and guarantees environment consistency.


Authentication State Reuse (storageState)

When many tests require login, performing UI login for each test is inefficient. Playwright's storageState allows you to save and reuse authentication state.

Defining a Setup Project

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  projects: [
    {
      name: 'setup',
      testMatch: /.*\.setup\.ts/,
    },
    {
      name: 'chromium',
      use: {
        storageState: 'playwright/.auth/user.json',
      },
      dependencies: ['setup'],
    },
  ],
});

Authentication Setup File

// tests/auth.setup.ts
import { test as setup, expect } from '@playwright/test';

const authFile = 'playwright/.auth/user.json';

setup('authenticate', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('Email').fill('test@example.com');
  await page.getByLabel('Password').fill('password');
  await page.getByRole('button', { name: 'Sign in' }).click();

  await page.waitForURL('/dashboard');
  await expect(page.getByText('Welcome')).toBeVisible();

  // Save authentication state
  await page.context().storageState({ path: authFile });
});
flowchart TB
    subgraph Auth["Authentication State Reuse"]
        A["Setup project"] --> B["Perform login"]
        B --> C["Save storageState"]
        C --> D["Chromium project"]
        D --> E["Load saved state"]
        E --> F["Run tests as logged-in user"]
    end

    style Auth fill:#3b82f6,color:#fff

This approach eliminates the need to log in for each test, significantly reducing overall execution time.


Test Design Best Practices

Project Structure

project-root/
β”œβ”€β”€ tests/
β”‚   β”œβ”€β”€ auth.setup.ts          # Authentication setup
β”‚   β”œβ”€β”€ auth/
β”‚   β”‚   β”œβ”€β”€ login.spec.ts
β”‚   β”‚   └── register.spec.ts
β”‚   β”œβ”€β”€ dashboard/
β”‚   β”‚   β”œβ”€β”€ overview.spec.ts
β”‚   β”‚   └── settings.spec.ts
β”‚   └── products/
β”‚       β”œβ”€β”€ list.spec.ts
β”‚       └── detail.spec.ts
β”œβ”€β”€ pages/                      # Page Object Model
β”‚   β”œβ”€β”€ LoginPage.ts
β”‚   β”œβ”€β”€ DashboardPage.ts
β”‚   └── ProductPage.ts
β”œβ”€β”€ fixtures/                   # Test data
β”‚   └── test-data.json
β”œβ”€β”€ playwright.config.ts
└── package.json

Writing Maintainable Tests

// BAD: Fragile selectors
await page.locator('.btn-primary.mt-4.px-6').click();
await page.locator('#root > div > div:nth-child(3) > button').click();

// GOOD: Role-based locators
await page.getByRole('button', { name: 'Submit' }).click();
await page.getByLabel('Email').fill('test@example.com');
await page.getByTestId('checkout-button').click();

Locator Priority

Priority Locator Reason
1 getByRole() Based on accessibility, most stable
2 getByLabel() Best for form elements
3 getByText() Identify by visible text
4 getByTestId() Test-specific attribute
5 locator('.class') Last resort

Test Isolation

// BAD: Tests depend on each other
test('create item', async ({ page }) => {
  // ...create item...
});

test('edit item', async ({ page }) => {
  // Assumes 'create item' test ran first
});

// GOOD: Each test is independent
test('edit item', async ({ page }) => {
  // Prepare data via API
  const item = await createTestItem(page);
  await page.goto(`/items/${item.id}/edit`);
  // ...
});

Dealing with Flaky Tests

A flaky test is an unstable test that sometimes passes and sometimes fails with the same code.

flowchart TB
    subgraph Causes["Causes of Flakiness"]
        C1["Timing dependencies"]
        C2["Shared state between tests"]
        C3["External service dependencies"]
        C4["Animations"]
        C5["Dynamic content"]
    end

    subgraph Solutions["Solutions"]
        S1["Trust auto-waiting"]
        S2["Ensure test isolation"]
        S3["Mock APIs"]
        S4["Disable animations"]
        S5["Explicit waits"]
    end

    C1 --> S1
    C2 --> S2
    C3 --> S3
    C4 --> S4
    C5 --> S5

    style Causes fill:#ef4444,color:#fff
    style Solutions fill:#22c55e,color:#fff

Configuring Retries

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

Detecting Flaky Tests

# Repeat the same test 10 times to check stability
npx playwright test --repeat-each=10 tests/checkout.spec.ts

Troubleshooting Checklist

Check What to Look For
Fixed waitForTimeout() Can auto-waiting replace it?
Test order dependency Does the test pass when run in isolation?
External API dependency Are you using route.fulfill() to mock?
Date/time dependency Are you using page.clock?
Random data Are you using fixed test data?

Test Strategy: What to Test with E2E

You do not need to cover everything with E2E tests. Keep the testing pyramid in mind.

flowchart TB
    subgraph Pyramid["Testing Pyramid"]
        E2E["E2E Tests<br/>Few, critical flows"]
        INT["Integration Tests<br/>Component interactions"]
        UNIT["Unit Tests<br/>Many, fast"]
    end

    E2E --> INT --> UNIT

    style E2E fill:#ef4444,color:#fff
    style INT fill:#f59e0b,color:#000
    style UNIT fill:#22c55e,color:#fff

Choosing the Right Test Type

Test Type Scope Examples
Unit tests Individual functions/classes Validation logic, calculations
Integration Component interactions API + DB integration, UI components
E2E tests Full user flows Login -> purchase -> checkout

Scenarios to Prioritize for E2E

  1. Critical paths: Login, checkout, registration
  2. Cross-browser verification: Layout issues, compatibility
  3. User journeys: End-to-end operation flows
  4. Regression prevention: Preventing recurrence of past bugs

10-Day Journey Summary

flowchart TB
    subgraph Journey["Your 10-Day Learning Journey"]
        D1["Day 1<br/>What is Playwright"]
        D2["Day 2<br/>Setup"]
        D3["Day 3<br/>Locators"]
        D4["Day 4<br/>Assertions"]
        D5["Day 5<br/>Page Interactions"]
        D6["Day 6<br/>Networking"]
        D7["Day 7<br/>Page Objects"]
        D8["Day 8<br/>Fixtures"]
        D9["Day 9<br/>Debugging"]
        D10["Day 10<br/>CI/CD"]

        D1 --> D2 --> D3 --> D4 --> D5
        D5 --> D6 --> D7 --> D8 --> D9 --> D10
    end

    style D1 fill:#3b82f6,color:#fff
    style D2 fill:#3b82f6,color:#fff
    style D3 fill:#8b5cf6,color:#fff
    style D4 fill:#8b5cf6,color:#fff
    style D5 fill:#8b5cf6,color:#fff
    style D6 fill:#f59e0b,color:#000
    style D7 fill:#f59e0b,color:#000
    style D8 fill:#f59e0b,color:#000
    style D9 fill:#22c55e,color:#fff
    style D10 fill:#22c55e,color:#fff
Day Topic What You Learned
1 What is Playwright Overview of E2E testing, Playwright features and architecture
2 Setup Installation, configuration, writing your first test
3 Locators and DOM getByRole, getByText, locator usage
4 Assertions expect, toBeVisible, toHaveText
5 Page Interactions Form handling, file uploads, dialogs
6 Network Control route.fulfill, waitForResponse, API mocking
7 Page Object Model Designing reusable page classes
8 Fixtures and Data Custom fixtures, test data management
9 Debugging and Traces Trace Viewer, UI Mode, debugging techniques
10 CI/CD and Best Practices GitHub Actions, Docker, test strategy

Summary

Concept Description
GitHub Actions Automate test execution in CI/CD
Browser caching Speed up CI execution time
Artifacts Reports, screenshots, videos, traces
Docker Consistent environments with official images
storageState Save and reuse authentication state
Retries Handle flaky tests in CI
Testing pyramid Balance E2E, integration, and unit tests

Key Takeaways

  1. CI/CD automates testing to safeguard quality
  2. storageState makes authentication efficient
  3. Flaky tests should be fixed at the root cause, not ignored
  4. Testing pyramid helps keep E2E test scope appropriate
  5. Role-based locators produce resilient, maintainable tests

Exercises

Basics

  1. Create a GitHub Actions workflow file that automatically runs Playwright tests on push.
  2. Configure screenshots, videos, and traces in playwright.config.ts.
  3. Add settings to upload the HTML report as an artifact.

Intermediate

  1. Create an authentication setup project using storageState to reduce test execution time.
  2. Run Playwright tests using the official Docker image.
  3. Use --repeat-each=10 to check existing tests for flakiness.

Challenge

  1. Design a GitHub Actions workflow that runs tests in parallel across multiple browsers (Chromium, Firefox, WebKit). Include configuration to upload trace files on failure.

References


Congratulations!

You have completed 10 days of learning Playwright!

What You Learned

  1. Day 1: What Playwright is and why E2E testing matters
  2. Day 2: Setting up your environment and writing your first test
  3. Day 3: Locators and DOM interaction
  4. Day 4: Validating behavior with assertions
  5. Day 5: Page interactions and navigation
  6. Day 6: Controlling network requests
  7. Day 7: Page Object Model for reusability
  8. Day 8: Fixtures and test data management
  9. Day 9: Debugging techniques and traces
  10. Day 10: CI/CD and best practices

The Road Ahead

You now have a solid grasp of Playwright fundamentals. From here, the most important thing is to apply what you have learned in real projects and build experience through practice.

Playwright is an actively developed tool with new features added in every release. Check the official documentation regularly to stay up to date with the latest capabilities and best practices.

Writing tests does more than protect software quality -- it gives you the confidence to make changes and ship faster. Take the CI/CD knowledge you gained today and integrate Playwright into your team's development workflow.

Your testing journey starts here. Keep learning, and build web applications you can trust!