← Blog

"From Playwright Studio Recording to CI/CD Pipeline — Complete Integration Guide"

Take the test code generated by Playwright Studio and wire it into GitHub Actions, GitLab CI, or Jenkins. Covers project setup, parallel execution, HTML reports, and failure screenshots.

reading now
views
comments

Series Navigation

Self-Healing Locators

Getting Started with Playwright Studio


The Path from Extension to CI

Playwright Studio (browser)
        │ generates TypeScript test code
        ▼
Your Playwright Project (local)
        │ npx playwright test ✓
        ▼
GitHub / GitLab (push)
        │
        ▼
CI Pipeline runs tests automatically
        │
        ├── Pass → deploy
        └── Fail → notify team, show report

Step 1 — Set Up a Playwright Project

If you don't have one yet:

npm init playwright@latest

# Prompts:
# ✔ Where to put your end-to-end tests? · tests
# ✔ Add a GitHub Actions workflow? · true
# ✔ Install Playwright browsers? · true

# Structure created:
playwright-tests/
├── tests/
│   └── example.spec.ts
├── playwright.config.ts
├── package.json
└── .github/workflows/playwright.yml

Step 2 — Paste Your Recorded Tests

Copy code from Playwright Studio and save it in the tests/ folder:

// tests/login.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Authentication', () => {

  test('successful login redirects to dashboard', async ({ page }) => {
    await page.goto('https://your-app.com/login');
    await page.getByTestId('email-input').fill('user@example.com');
    await page.getByTestId('password-input').fill('password123');
    await page.getByTestId('login-btn').click();
    await expect(page).toHaveURL(/dashboard/);
    await expect(page.getByTestId('welcome-message')).toBeVisible();
  });

  test('invalid credentials shows error', async ({ page }) => {
    await page.goto('https://your-app.com/login');
    await page.getByTestId('email-input').fill('wrong@example.com');
    await page.getByTestId('password-input').fill('wrongpassword');
    await page.getByTestId('login-btn').click();
    await expect(page.getByTestId('error-banner')).toBeVisible();
    await expect(page.getByTestId('error-banner')).toContainText('Invalid credentials');
  });

});

Step 3 — Configure playwright.config.ts

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

export default defineConfig({
  testDir: './tests',
  timeout: 30000,

  expect: { timeout: 10000 },

  // Run tests in parallel
  fullyParallel: true,
  workers: process.env.CI ? 4 : 2,

  // Retry flaky tests in CI
  retries: process.env.CI ? 2 : 0,

  // Reports
  reporter: [
    ['html', { outputFolder: 'playwright-report', open: 'never' }],
    ['junit', { outputFile: 'test-results/results.xml' }],
    ['line'],  // console output
  ],

  use: {
    baseURL: process.env.BASE_URL || 'https://staging.your-app.com',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
    trace: 'on-first-retry',
  },

  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox',  use: { ...devices['Desktop Firefox'] } },
    { name: 'webkit',   use: { ...devices['Desktop Safari'] } },
    { name: 'mobile',   use: { ...devices['iPhone 14'] } },
  ],
});

GitHub Actions — Complete Workflow

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

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 6 * * 1-5'  # 6am UTC weekdays

jobs:
  test:
    name: Playwright (${{ matrix.project }})
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        project: [chromium, firefox, webkit]

    steps:
      - uses: actions/checkout@v4

      - 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 ${{ matrix.project }}

      - name: Run Playwright tests
        run: npx playwright test --project=${{ matrix.project }}
        env:
          BASE_URL: ${{ vars.STAGING_URL }}
          CI: true

      - name: Upload HTML report
        uses: actions/upload-artifact@v4
        if: always()  # upload even on failure
        with:
          name: playwright-report-${{ matrix.project }}
          path: playwright-report/
          retention-days: 14

      - name: Upload test results (JUnit)
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: test-results-${{ matrix.project }}
          path: test-results/

      - name: Publish Test Results
        uses: EnricoMi/publish-unit-test-result-action@v2
        if: always()
        with:
          files: test-results/*.xml

GitLab CI — Complete Pipeline

# .gitlab-ci.yml
image: mcr.microsoft.com/playwright:v1.45.0-jammy

stages:
  - test

variables:
  BASE_URL: $STAGING_URL
  CI: "true"

playwright-tests:
  stage: test
  script:
    - npm ci
    - npx playwright test
  parallel:
    matrix:
      - PROJECT: [chromium, firefox, webkit]
  script:
    - npx playwright test --project=$PROJECT
  artifacts:
    when: always
    paths:
      - playwright-report/
      - test-results/
    reports:
      junit: test-results/results.xml
    expire_in: 14 days
  only:
    - main
    - merge_requests

Reading the HTML Report

After a run, open playwright-report/index.html:

Test Results
├── ✓ 48 passed
├── ✗ 2 failed
└── ⊘ 1 skipped

Failed tests:
  ✗ checkout › complete purchase
    Error: Locator not found: [data-testid="pay-now-btn"]
    Screenshot: attached
    Trace: attached
    
    Retried 2 times (CI retry enabled)

Click any failed test to see:

  • The exact step that failed
  • A screenshot of the page at failure time
  • A trace file you can open in Playwright Trace Viewer for full step-by-step replay

Tips for Reliable CI Tests

// 1. Use baseURL — never hardcode URLs
await page.goto('/login');  // resolves to baseURL + /login

// 2. Set up test data via API, not UI
test.beforeAll(async ({ request }) => {
  await request.post('/api/test-users', {
    data: { email: 'ci-test@example.com', password: 'CI_Password123' }
  });
});

// 3. Clean up after tests
test.afterAll(async ({ request }) => {
  await request.delete('/api/test-users/ci-test@example.com');
});

// 4. Use storage state for authenticated tests — login once, reuse session
// In global setup:
const authFile = 'playwright/.auth/user.json';
await page.context().storageState({ path: authFile });

// In config:
use: { storageState: 'playwright/.auth/user.json' }

Built with Playwright Studio by AIQEAcademy — the free Chrome extension for Playwright test automation.

Discussion

Loading...

Leave a Comment

All comments are reviewed before appearing. No links please.

0 / 1000