effidevFlutter · Cloudflare edge · Cloud cost optimization
English

Automating E2E and Visual Regression Testing with Playwright, Cloudflare Pages, and GitHub Actions

Playwright Cloudflare Pages E2E CI CD Visual Regression Guide

Refactoring frontend code often comes with the anxiety of unintended visual regressions. Manually verifying responsive layouts and multi-language pages across multiple browsers is unsustainable.

Playwright has emerged as the leading modern E2E testing framework due to its 3x speed over Cypress, native cross-browser support (Chromium, Firefox, WebKit), and powerful Trace Viewer.

This article details how to build an automated CI/CD pipeline combining Playwright, Cloudflare Pages preview deployments, and GitHub Actions to run E2E and visual snapshot tests on every Pull Request.

Key Takeaways

  • Why Playwright: Auto-waiting, multi-tab support, and native network mocking eliminate flaky test failures.
  • Cloudflare Pages Preview Integration: Tests run against real edge deployment URLs (https://<hash>.pages.dev) for exact production parity.
  • Visual Regression: Use native toHaveScreenshot() to catch pixel-level UI regressions and automatically upload diff reports to GitHub Actions artifacts.

1. Playwright Test Configuration (playwright.config.ts)

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

const BASE_URL = process.env.PREVIEW_URL || 'http://localhost:4321';

export default defineConfig({
  testDir: './e2e',
  fullyParallel: true,
  use: {
    baseURL: BASE_URL,
    trace: 'on-first-retry',
  },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'mobile-safari', use: { ...devices['iPhone 14'] } },
  ],
});

2. Visual Regression Code (e2e/visual.spec.ts)

import { test, expect } from '@playwright/test';

test('Homepage visual snapshot comparison', async ({ page }) => {
  await page.goto('/');
  await page.waitForLoadState('networkidle');
  await expect(page).toHaveScreenshot('homepage-baseline.png', {
    maxDiffPixelRatio: 0.01,
    fullPage: true,
  });
});