Automated End-to-End (E2E) testing is the ultimate safety net for modern web deployment. However, poorly optimized CI pipelines often suffer from single-worker bottlenecks, flaky third-party script overlays, and slow execution times.
This post covers how we engineered a bulletproof Playwright verification suite for blog.induslevel.com that completes 12 rigorous tests across 4 browser projects in under 30 seconds.
Core Pipeline Optimizations
1. 4x Parallel Execution
We eliminated the standard single-worker CI bottleneck to unlock maximum hardware performance.
- Multi-Worker Scaling: Updating
playwright.config.tsto utilizeworkers: process.env.CI ? 4 : undefined. - Runner Utilization: Fully saturating GitHub Actions' hosted 4-core runners to execute Chrome, Safari, Mobile Chrome, and Mobile Safari simultaneously.
2. Containerized Execution Speed
Bypassing lengthy package installation overhead during CI runs.
- Pre-baked Containers: Running the job inside Microsoft's official Docker image (
mcr.microsoft.com/playwright:v1.59.1-noble). - Zero-Install Overhead: Eliminating 5-minute
apt-getand browser binary download steps entirely.
3. Bypassing Flaky Third-Party Overlays
Third-party trackers and cookie banners (like Silktide) frequently block automated clicks and pollute DOM state.
- Network Interception: Using
page.route('**/silktide-consent-manager.js', route => route.abort())to block consent scripts at the network layer. - Pure State Testing: Ensuring tests execute against pure application code without lingering animation backdrops or script blocking.
4. Bulletproof Test Assertions
Engineering resilient locators that adapt to dynamic viewports and hydration cycles.
- Viewport Awareness: Automatically switching between desktop
<nav>links and mobile hamburger menu buttons (button[aria-label="Toggle menu"]). - Event Delegation: Using document-level event delegation (
e.target.closest('#theme-toggle')) to ensure theme buttons function flawlessly across Astro View Transition DOM swaps. - Image Decoding Verification: Verifying search thumbnails via
img.naturalWidth > 0to guarantee images decode successfully rather than rendering as broken icons.
5. On-Demand Commit Triggering
Testing should be rigorous on development branches but never obstruct rapid hotfixes.
- Tag Filtering: Configuring
.github/workflows/e2e-tests.ymlto execute only when commit messages contain the[run-e2e]tag. - Branch Isolation: Keeping the
mainbranch deployment pipeline 100% streamlined and independent.
The Result
Running 12 tests using 4 workers
············
12 passed (28.3s)
By combining parallel CPU workers, network-level script blocking, and containerized runners, we achieved a lightning-fast, 100% reliable testing pipeline.
Complete Production Configurations
For fellow developers looking to replicate this setup, here are the complete, production-ready configuration files powering our CI/CD pipeline.
1. Playwright Configuration (playwright.config.ts)
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
timeout: 30 * 1000,
expect: { timeout: 5000 },
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 4 : undefined,
reporter: process.env.CI ? 'github' : 'html',
use: {
baseURL: 'http://localhost:4321',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{
name: 'Desktop Chrome',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'Desktop Safari',
use: { ...devices['Desktop Safari'] },
},
{
name: 'Mobile Chrome',
use: { ...devices['Pixel 7'] },
},
{
name: 'Mobile Safari',
use: { ...devices['iPhone 14'] },
},
],
webServer: {
command: 'npm run preview',
port: 4321,
timeout: 120 * 1000,
reuseExistingServer: !process.env.CI,
},
});
2. GitHub Actions CI Workflow (.github/workflows/e2e-tests.yml)
name: End-to-End Automated CI Verification
on:
push:
branches:
- dev
pull_request:
branches:
- dev
workflow_dispatch:
jobs:
test:
runs-on: ubuntu-latest
env:
NODE_OPTIONS: "--dns-result-order=ipv4first"
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
container:
image: mcr.microsoft.com/playwright:v1.59.1-noble
if: contains(github.event.head_commit.message, '[run-e2e]') || github.event_name == 'workflow_dispatch'
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
- name: Install Dependencies
run: npm ci
- name: Build Statically for Preview Verification
run: ASTRO_TELEMETRY_DISABLED=1 npm run build
- name: Run Playwright E2E & Visual Regression Tests
run: npm run test:e2e
- name: Upload Playwright Artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
retention-days: 14
3. Bulletproof Test Suite (e2e/blog.spec.ts)
import { test, expect } from '@playwright/test';
test.describe('Blog E2E & Visual Regression Verification', () => {
test.beforeEach(async ({ page }) => {
// Block Silktide consent manager to prevent overlays and script blocking during tests
await page.route('**/assets/silktide/silktide-consent-manager.js', route => route.abort());
});
test('Homepage renders correct heading and layout', async ({ page }) => {
await page.goto('/');
// Verify main site title is visible
await expect(page.locator('text=Indus LeveL Blog')).toBeVisible();
// On desktop viewports, verify nav link is visible. On mobile viewports, verify mobile menu toggle is visible
const navContact = page.locator('nav >> text=Contact');
if (await navContact.isVisible()) {
await expect(navContact).toBeVisible();
} else {
await expect(page.locator('button[aria-label="Toggle menu"]')).toBeVisible();
}
});
test('Theme toggle correctly switches dark and light modes', async ({ page }) => {
await page.goto('/');
// Wait exactly for client-side JavaScript hydration to complete
const themeToggle = await page.waitForSelector('#theme-toggle[data-theme-init="true"]', { state: 'attached', timeout: 10000 });
if (themeToggle) {
// Click toggle
await page.locator('#theme-toggle').click({ force: true });
// Verify html tag class list updates
const htmlTag = page.locator('html');
await expect(htmlTag).toHaveAttribute('class', /dark|light/);
}
});
test('Contact Us page loads and renders Turnstile challenge container', async ({ page }) => {
await page.goto('/contact');
// Verify form container heading is visible
await expect(page.locator('text=Send a message')).toBeVisible();
});
test('Search functionality works and displays image thumbnails', async ({ page }) => {
await page.goto('/search');
// Wait for Pagefind UI to render the input
const searchInput = page.locator('#search input');
await expect(searchInput).toBeVisible();
// Type search query
await searchInput.fill('Astro');
// Wait for results to appear (Pagefind UI renders results in a list)
const firstResult = page.locator('.pagefind-ui__result').first();
await expect(firstResult).toBeVisible({ timeout: 10000 });
// Verify search result has an image thumbnail
const resultImg = firstResult.locator('img');
await expect(resultImg).toBeVisible();
// Verify the image loads successfully (naturalWidth > 0)
const isImageLoaded = await resultImg.evaluate((img: HTMLImageElement) => img.naturalWidth > 0);
expect(isImageLoaded).toBe(true);
});
});