Series Navigation
← 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.