Skip to content

DIAGNOSTICS AND DELIVERY · LESSON 20 OF 24

Screenshots and visual regression checks

Capture evidence and compare reviewed baselines in a stable environment.

  • screenshot
  • toHaveScreenshot
  • baselines
  • 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

A screenshot attachment records what happened; a visual comparison checks the image against an approved baseline. These are different tests. A screenshot existing does not prove the interface looks correct.

Visual results depend on operating system, fonts, browser build, viewport and rendering. Generate and compare baselines in the same controlled environment. Avoid copying a baseline from one platform and assuming it is universal.

The first test always captures and attaches the task section. The visual comparison is opt-in through the visual project. No universal image baseline is shipped. First run npx playwright test --project=visual --update-snapshots, inspect the generated image, then rerun without the update flag.

Never accept new screenshots automatically just to turn a red build green. Review differences against the intended design. Mask volatile content only when that content is not what the test is meant to verify.

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 -- --project=chromium
# Optional: generate and review a visual baseline
npx playwright test --project=visual --update-snapshots
npx playwright test --project=visual

tests/lesson.spec.ts

tests/lesson.spec.ts
import { test, expect } from '@playwright/test';
test('attach a screenshot as evidence', async ({ page }, testInfo) => {
  await page.goto('/');
  const section = page.getByRole('region', { name: 'Tasks', exact: true });
  const image = await section.screenshot({ animations: 'disabled' });
  expect(image.length).toBeGreaterThan(0);
  await testInfo.attach('task-section', { body: image, contentType: 'image/png' });
});
test('compare a reviewed visual baseline', async ({ page }, testInfo) => {
  test.skip(testInfo.project.name !== 'visual', 'Run the visual project after reviewing its baseline');
  await page.goto('/');
  await expect(page.getByRole('region', { name: 'Tasks', exact: true })).toHaveScreenshot('tasks.png', { animations: 'disabled' });
});

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'] } }, { name: 'visual', use: { ...devices['Desktop Chrome'] }, testMatch: '**/lesson.spec.ts' }],
  webServer: { command: 'node server.mjs', url: 'http://127.0.0.1:4174', reuseExistingServer: false, timeout: 15000 }
});

package.json

package.json
{
  "name": "edupoly-playwright-screenshots-and-visual-comparisons",
  "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. A named region scopes the screenshot to the task interface.
  2. The explicit skip prevents an accidental missing-baseline failure in the default course run.
  3. toHaveScreenshot performs comparison, not just image capture.

Verify your result

  • The Chromium project attaches a screenshot and skips the opt-in comparison.
  • Generate, review and rerun the visual baseline in the same environment.

4. Practice independently

Change a button color after approving a baseline and inspect the failure.

Show the explanation

Run the visual project without --update-snapshots. Inspect expected, actual and diff images. Restore the color or approve the change only if it is intended.

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 ↗