Skip to content

DIAGNOSTICS AND DELIVERY · LESSON 23 OF 24

Continuous integration and reports

Run the same tests in a clean CI environment.

  • GitHub Actions
  • npm ci
  • browser dependencies
  • artifacts

Your runnable checkpoint

Extract into a new practice folder. Each download contains tests, configuration, the complete local practice app and an offline lesson. Use Node.js 22 or 24. Each checkpoint is independent.

Install dependencies and browser binaries using the commands below. The test runner starts the practice server on port 4174. Tests target localhost, never the production EduPoly site.

1. Understand the concepts

CI should reproduce a known local workflow: install locked dependencies, install matching browser binaries and OS dependencies, type-check, then run tests. The included workflow targets a dedicated repository containing this extracted checkpoint at its root.

Run npm install once and commit package-lock.json before using npm ci or setup-node’s npm cache. The ZIP intentionally contains no node_modules or browser downloads. A CI machine starts without your local browser cache.

The --with-deps option installs Linux browser system dependencies as well as the browser. This workflow runs Chromium; expand the project configuration and installation command together if you want a multi-browser matrix.

Upload the HTML report even when tests fail, but limit retention and avoid secrets in test artifacts. Store real credentials in CI secrets, use dedicated test accounts and do not expose production reset endpoints.

For larger suites, sharding divides tests across separate jobs. Each shard needs independent data and an artifact-merging strategy. Begin with a reliable single job before optimizing execution time.

2. Build and run

These are complete test files and configuration. The download also includes app/ and server.mjs; preserve their paths. Stop any manually started practice server before running the suite.

Terminal
npm install
npx playwright install chromium
npm run check
npm test

tests/lesson.spec.ts

tests/lesson.spec.ts
import { test, expect } from '@playwright/test';
test('CI smoke check', async ({ page }) => {
  await page.goto('/');
  await expect(page.getByRole('heading', { name: 'Playwright Lab', exact: true })).toBeVisible();
  await expect(page.getByRole('button', { name: 'Add task', exact: true })).toBeEnabled();
});

.github/workflows/playwright.yml

.github/workflows/playwright.yml
name: Playwright checks
on: [push, pull_request]
permissions:
  contents: read
jobs:
  browser-tests:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      - run: npm run check
      - run: npm test
      - uses: actions/upload-artifact@v4
        if: ${{ !cancelled() }}
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 7

playwright.config.ts

playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 1 : 0,
  workers: process.env.CI ? 2 : undefined,
  timeout: 30000,
  expect: { timeout: 5000 },
  reporter: [['list'], ['html', { open: 'never' }]],
  use: { baseURL: 'http://127.0.0.1:4174', trace: 'retain-on-failure', screenshot: 'only-on-failure' },
  projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
  webServer: { command: 'node server.mjs', url: 'http://127.0.0.1:4174', reuseExistingServer: false, timeout: 15000 }
});

package.json

package.json
{
  "name": "edupoly-playwright-continuous-integration",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "test": "playwright test",
    "test:headed": "playwright test --headed",
    "test:ui": "playwright test --ui",
    "check": "tsc --noEmit",
    "report": "playwright show-report",
    "serve": "node server.mjs"
  },
  "devDependencies": {
    "@playwright/test": "1.56.1",
    "typescript": "5.9.3",
    "@types/node": "22.20.5"
  }
}

tsconfig.json

tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "CommonJS",
    "moduleResolution": "Node",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "types": [
      "node"
    ],
    "noEmit": true
  },
  "include": [
    "tests/**/*.ts",
    "pages/**/*.ts",
    "playwright.config.ts"
  ]
}

.gitignore

.gitignore
node_modules/
playwright-report/
test-results/
playwright/.auth/
.env

3. Trace the code and check the result

  1. The workflow uses npm ci rather than silently changing dependency resolution.
  2. Browser OS dependencies are installed explicitly on the Linux runner.
  3. The report artifact is retained for seven days.

Verify your result

  • The local smoke test passes.
  • After committing a lockfile in a dedicated repository, the workflow can install and run it.

4. Practice independently

Make CI reject a committed test.only.

Show the explanation

The supplied config sets forbidOnly when CI is present. Temporarily add test.only, observe the CI failure, then remove it before merging.

When something goes wrong

If a browser executable is missing, run npx playwright install chromium in the project folder. If port 4174 is occupied, stop your manual practice server. Read assertion call logs for mismatched locators or expectations. Use npm run report to inspect failures; do not add fixed sleeps or broad resets to hide them.

Read the related official Playwright documentation ↗