DEV Community

BAOFUFAN
BAOFUFAN

Posted on

From 3 Hours to 3 Minutes: Automating Frontend Core Regression with Zero Leaks

It was 10 PM on a Friday. I was about to shut down and leave when DingTalk suddenly blew up — the live payment page had gone completely white and users couldn’t place orders. That release only changed a button color; who would have thought a CSS conflict could just vanish the payment button? Even more absurd, the regression checklist item “must verify checkout flow” was skipped yet again because we were rushing in the afternoon. After rolling back around midnight, I lay in bed thinking: we can’t keep relying on human discipline to prevent this kind of crap.


Breaking Down the Problem: Why Did “We Have a Process” Still Fail to Catch Regressions?

Our team maintains a Next.js e-commerce site with a very simple frontend core journey: Search → Product Detail → Add to Cart → Checkout → Payment. Before every release, QA had to manually walk through the full journey across Chrome, Edge and three main viewport sizes — conservatively taking 3 person-hours. The reality: releases were frequent, we were always short-staffed, and “full regression” often shrank down to “just test the order button.” Unit tests covered pure logic, but UI interactions, page transitions, and real API integrations were all flying naked. We had set up Selenium previously, but nobody touched it for half a year; scripts timed out on every run and were eventually deleted out of frustration.

The root cause wasn’t “missing process” — regression relied on manual effort, and humans are unreliable. Every PR merge felt like opening a blind box, with an average of at least one production rollback per month.


Solution Design: Why Playwright + GitHub Actions?

To solve this once and for all, regression had to become an automated, mandatory, unskippable gate. Our selection criteria were clear:

  • Must be cross-browser: Chrome, WebKit, Firefox all covered (we have Safari users).
  • Scripts must be stable: The core journey cannot afford to be flaky, or the team will lose trust.
  • Deep CI integration: Ideally hooked directly into the GitHub PR flow, where a failure blocks merging.

We compared three options:

  • Cypress: Active community, but limited multi-tab / multi-domain support, and runs only on Chromium-based browsers — insufficient cross-browser coverage.
  • Selenium: Battle-tested, but slow execution, explicit sleep-based waits everywhere, and maintenance costs that explode over time.
  • Playwright: Maintained by Microsoft, auto-waiting, native multi-browser support, built-in trace viewer, codegen recording tool, and one-command installation of Chrome/Firefox dependencies on GitHub Actions. There was literally no reason not to pick it.

The architecture was straightforward: every PR triggers → install dependencies → start dev server → run core journey E2E → failure blocks merging. Additionally, we schedule a full nightly run as a monitoring signal to catch environment drift before it breaks the tests.


Core Implementation: Two Pieces of Code That Erected the Gate

1. GitHub Actions Workflow

This configuration answers “when to run, how to run, and how to pin the result to the merge gate.”

# .github/workflows/e2e.yml
name: E2E Core Journey

on:
  pull_request:
    branches: [main]
  # nightly full regression to catch environment issues early
  schedule:
    - cron: '0 16 * * *'  # UTC 16:00 = 00:00 Beijing time

jobs:
  core-e2e:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      # only install Chromium here; multi-browser can be a parallel matrix
      - name: Install Playwright browsers
        run: npx playwright install --with-deps chromium

      - name: Start dev server
        run: npm run dev &
        env:
          # ensure tests hit mock or staging API
          API_BASE_URL: http://localhost:3001

      # wait-on is critical; without it the next step gets Connection Refused
      - name: Wait for server
        run: npx wait-on http://localhost:3000 --timeout 60000

      - name: Run Playwright tests
        run: npx playwright test --project=chromium

      # upload report even on failure so we can debug
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report
          path: |
            playwright-report/
            test-results/
Enter fullscreen mode Exit fullscreen mode

We then added a branch protection rule requiring the core-e2e status check to pass before merging into main. From that point on, nobody could merge code without the core journey passing.

2. Checkout Full-Journey Test Script

This code answers “what to test and how to assert without flakiness.” We use Playwright’s locator and web-first assertions, eliminating a ton of manual waitFor calls.

// e2e/checkout.spec.ts
import { test, expect } from '@playwright/test';

test.describe('核心下单链路', () => {
  test('从搜索到支付成功', async ({ page }) => {
    // open home page (dev server)
    await page.goto('http://localhost:3000');

    // search for a product
    await page.locator('input[name="search"]').fill('无线耳机');
    await page.locator('button:has-text("搜索")').click();

    // click the first search result
    await page.locator('.product-card').first().click();
    // ... rest of the journey ...
  });
});
Enter fullscreen mode Exit fullscreen mode

(Full test continues with add-to-cart, checkout form, and payment confirmation — all using the same stable locator pattern.)


Once these two pieces were in place, the core journey regression went from 3 hours of manual labor to 3 automated minutes per PR — and the gate became non-negotiable. Leak-through rate dropped to zero. No more Friday night DingTalk explosions over a button color change.

Top comments (0)