Getting woken up at 3 AM by a pager alert is never fun – especially when it’s all payment callbacks timing out, dozens of orders stuck in “Pending Payment”, and the ops channel exploding. I groggily dug in and discovered the culprit: a new promotion logic shipped earlier that day had subtly changed the order state machine transitions, and nobody caught it. That night I made a hard promise: every release that touches money must run a complete, end‑to‑end regression, and it must never rely on manual clicking again.
Breaking Down the Problem
We maintain an e‑commerce platform. Its core flow is “Login → Browse products → Add to cart → Place order → Payment callback → Inventory deduction → Order status transition.” Long chain, many states, and any change can trigger a domino effect. The worst part? Plenty of bugs only surface inside a real browser: front‑end duplicate submission prevention, callback handling after redirecting to Alipay’s cashier page, page redirects when the session expires… Postman or integration tests simply can’t catch those.
Our previous manual regression looked like this: a QA engineer would open Chrome, switch between different role accounts, click all the way from login to payment completion, then check the database for correct order status and inventory. A full run took at least two hours. When releases were frequent, we could only spot‑check, and the scenarios we missed were ticking time bombs.
Someone will ask, “Why not just use Selenium?” We tried. Scripts failed constantly because of slow page loads or element rendering delays. The maintenance cost was so high the team abandoned it. We needed a tool purpose‑built for modern web apps, one that runs reliably in CI and makes debugging failed cases a breeze.
The Design
After some research, the real solution turned out to be Playwright + GitHub Actions.
The biggest advantage of Playwright isn’t simply “it can automate a browser”. It’s a handful of design choices that dramatically boost CI stability:
-
Auto‑waiting: When you click a button, it automatically waits for the element to be visible and actionable – no more sprinkling
sleep(2)everywhere. - Trace Viewer: On failure, you get a complete recording of actions, network requests, screenshots, and DOM snapshots. No more guessing.
- Multi‑browser support: The same scripts run on Chromium, Firefox, and WebKit – one regression pass covers the majority of your users.
- Built‑in test framework: The Test Runner gives you concurrency, hooks, retries, and parameterisation out of the box. It’s much lighter than wrapping Jest around a separate tool.
Why not Cypress? Cypress struggles with multiple tabs, file downloads, and cross‑origin restrictions – and our payment flow includes redirects to third‑party pages. Playwright makes these scenarios almost invisible to the test code.
Architecturally, we keep the test scripts in the e2e/ directory of the project repository, maintained alongside the main codebase. GitHub Actions runs the full suite automatically every morning at 4 AM, as well as on every pull request merged into the main branch. After the run finishes, the test results and any failure traces are uploaded as Action Artifacts, and our WeCom bot pushes a summary to the team channel.
Core Implementation
First, build a Page Object Model to reduce duplication. Here’s an example for the Login and Order flows.
This snippet encapsulates the login page so all test cases reuse the same login logic instead of scattering selectors everywhere.
// e2e/pages/login.page.ts
import { Page, expect } from '@playwright/test';
export class LoginPage {
constructor(private page: Page) {}
async goto() {
await this.page.goto('/login');
await expect(this.page).toHaveURL(/\/login/);
}
async loginAs(role: 'buyer' | 'seller') {
const users = {
buyer: { email: 'buyer@test.com', password: 'test123' },
seller: { email: 'seller@test.com', password: 'test123' },
};
const user = users[role];
await this.page.fill('input[name="email"]', user.email);
await this.page.fill('input[name="password"]', user.password);
await this.page.click('button[type="submit"]');
// 等跳转完成,确认已登录
await expect(this.page.locator('.user-avatar')).toBeVisible({ timeout: 10000 });
}
}
This test is the heart of the regression – it simulates the full flow from product detail page to payment completion, then hits the database to verify the order state, ensuring the business loop is closed.
// e2e/tests/order-flow.spec.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/login.page';
import { ProductPage } from '../pages/product.page'; // 省略实现,类似封装
import { CheckoutPage } from '../pages/checkout.page';
import { query } from '../db-helper'; // 自己封装的数据库查询辅助
test.describe('关键业务流程回归:下单到支付完成', () => {
test('全链路回归 - 支付宝担保交易', async ({ page }) => {
// 1. 登录
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.loginAs('buyer');
// 2. 浏览商品,加入购物车
const productPage = new ProductPage(page);
await productPage.openProduct('SKU-TEST-001');
await productPage.addToCart();
// 3. 进入结算,选择支付宝支付
const checkoutPage = new CheckoutPage(page);
await checkoutPage.gotoFromCart();
await checkoutPage.selectPayment('alipay');
await checkoutPage.placeOrder();
// 4. 抓取订单号,通常出现在成功页 URL 或元素中
const orderId = await pag
Top comments (0)