Sitemap

Pixel-by-Pixel Visual Testing Using Playwright with GitHub Actions

4 min readMay 28, 2025

--

UI bugs are sneaky — a one-pixel shift, a color mismatch, or a disappearing icon. Visual testing helps catch what functional tests miss. In this tutorial, you’ll learn how to use Playwright + Pixelmatch to compare two screenshots pixel-by-pixel and detect UI regressions — complete with working code and GitHub Actions integration.

Press enter or click to view image in full size

Prerequisites

  • Node.js ≥ 16
  • GitHub repository
  • Basic knowledge of Playwright

Step 1: Set up the Project

Create a new project folder:

mkdir playwright-pixelmatch && cd playwright-pixelmatch
npm init -y
npm install playwright pixelmatch pngjs fs-extra --save-dev
npx playwright install

Update your package.json:

{
"name": "playwright-pixelmatch",
"version": "1.0.0",
"type": "module",
"scripts": {
"test": "npx playwright test",
"compare": "node compare-images.mjs"
}
}

Folder Structure

playwright-pixelmatch/
├── screenshots/
│ ├── baseline/
│ ├── actual/
│ └── diff/
├── tests/
│ └── visual.spec.js
├── compare-images.mjs
├── package.json

Step 2: Capture Screenshots in Playwright

Create tests/visual.spec.js:

import { test, expect } from '@playwright/test';
import * as fs from 'fs';
import fsExtra from 'fs-extra';
test('visual test for homepage', async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 800 });
await page.goto('https://example.com');
const actualPath = 'screenshots/actual/example.png';
const baselinePath = 'screenshots/baseline/example.png';
await fsExtra.ensureDir('screenshots/actual');
await page.screenshot({ path: actualPath, fullPage: true });
if (!fs.existsSync(baselinePath)) {
console.warn(`⚠️ No baseline found. Creating at ${baselinePath}`);
await fsExtra.ensureDir('screenshots/baseline');
await fsExtra.copy(actualPath, baselinePath);
return;
}
expect(fs.existsSync(baselinePath)).toBe(true);
});

Run your first test to generate the baseline:

npx playwright test

Once the test is executed, move the image from screenshots/actual folder to screenshots/baseline folder

Step 3: Compare Screenshots with Pixelmatch

Create compare-images.mjs:

import * as fs from 'fs';
import { PNG } from 'pngjs';
import pixelmatch from 'pixelmatch';
import fsExtra from 'fs-extra';
const baselinePath = 'screenshots/baseline/example.png';
const actualPath = 'screenshots/actual/example.png';
const diffPath = 'screenshots/diff/example-diff.png';
const img1 = PNG.sync.read(fs.readFileSync(baselinePath));
const img2 = PNG.sync.read(fs.readFileSync(actualPath));
const { width, height } = img1;
const diff = new PNG({ width, height });
await fsExtra.ensureDir('screenshots/diff');
const mismatchedPixels = pixelmatch(
img1.data,
img2.data,
diff.data,
width,
height,
{
threshold: 0.15,
includeAA: true,
alpha: 0.8
}
);
fs.writeFileSync(diffPath, PNG.sync.write(diff));
const totalPixels = width * height;
const mismatchPercentage = (mismatchedPixels / totalPixels) * 100;
console.log(`🧪 Mismatched Pixels: ${mismatchedPixels}`);
console.log(`📊 Mismatch Percentage: ${mismatchPercentage.toFixed(2)}%`);
if (mismatchPercentage > 0.5) {
console.error(`❌ Visual test failed: ${mismatchPercentage.toFixed(2)}% > 0.5%`);
process.exit(1);
} else {
console.log(`✅ Visual test passed.`);
}

Then run:

npm run compare

🚀 Step 4: Automate with GitHub Actions

Create .github/workflows/visual-test.yml:

name: Visual Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
visual-test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: 18
- name: Install dependencies
run: npm install
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npm run test
- name: Compare screenshots
run: npm run compare
- name: Upload diff artifact
uses: actions/upload-artifact@v3
with:
name: diff-screenshot
path: screenshots/diff/

Successful Execution

Press enter or click to view image in full size

Artifacts of the results in case of failures

Press enter or click to view image in full size

Here are the artifacts.

Press enter or click to view image in full size

This will:

  • Run visual tests
  • Compare images
  • Upload any diffs for you to inspect visually

Conclusion

Pixelmatch + Playwright provides a powerful visual testing workflow with pixel-level precision. By integrating into CI with GitHub Actions, you can catch UI changes before users do.

⭐ If you found this helpful, follow me on Medium and check out my other testing articles.

I have created a project on GitHub and added the code here. Also, I have added the pipeline link to view the results here.

Feel free to hit clap if you like the content. Happy Automation Testing :) Cheers. 👏

If you guys want to support my work, then leave me a good rating for my Chrome plugin here and Firefox plugin here.

--

--

Pradap Pandiyan
Pradap Pandiyan

Written by Pradap Pandiyan

I’m a passionate QA Engineer. I’m a motovlogger, content creator, off-roader and freelancer. Buy me a coffee here https://www.buymeacoffee.com/pradappandiyan