Automated Testing: The Foundation of Engineering Speed
Manual testing is slow, expensive, and unreliable. How to build a robust End-to-End (E2E) testing strategy using Playwright and GitHub Actions.
The Fear of Deployment
Every software engineer knows the feeling. It is Friday, 4:00 PM. You have just merged a Pull Request. The deployment pipeline turns green. You should be happy. But you are sweating. “Did I break the checkout flow?” “Did I break the password reset?” “Did I test the mobile menu on Safari?” If you rely on Manual Testing (clicking around the website yourself), you are living in a constant state of fear. Humans are terrible at regression testing. We get bored. We miss things. We test the “Happy Path” and forget the edge cases. As the codebase grows, the time required to manually test it grows linearly. Eventually, you stop testing. That is when the bugs leak into Production. And when bugs leak, you lose revenue. You lose trust. Automated Testing is the cure for this fear. It turns deployment from a “High Risk Event” into a “Non-Event”.
Why Maison Code Discusses This
At Maison Code, we work with high-stakes luxury brands and heavy-traffic e-commerce platforms. Our clients generate thousands of euros per minute during peak sales (Black Friday, Christmas). A “minor” bug in the checkout flow is not an inconvenience; it is a financial catastrophe. We have seen brands lose $50k in an hour because a “AddToCart” button was covered by a Z-Index error on iPhone 12. We talk about Automated Testing because Stability is Revenue. We do not sell “code”. We sell “Reliability”. Implementing a robust E2E suite is often the first thing we do when auditing a client’s legacy codebase. It stops the bleeding and allows us to refactor with confidence.
1. The Testing Pyramid: A Strategic Framework
Mike Cohn popularized the “Testing Pyramid” concept, and it remains the gold standard for testing strategy. It defines the ideal distribution of tests in your suite.
-
Unit Tests (70%):
- Scope: Individual functions or classes.
- Speed: Milliseconds.
- Cost: Cheap.
- Tool: Jest, Vitest.
- Example:
add(2, 2) === 4.
-
Integration Tests (20%):
- Scope: Interactions between components (e.g., Parent passing props to Child).
- Speed: Seconds.
- Cost: Medium.
- Tool: React Testing Library.
-
End-to-End (E2E) Tests (10%):
- Scope: Full user flows in a real browser.
- Speed: Minutes.
- Cost: Expensive (Compute heavy).
- Tool: Playwright, Cypress.
In this article, we will focus on the top of the pyramid: E2E Testing.
Why? Because it provides the highest Confidence Coefficient.
A unit test can pass even if the “Submit” button is hidden by a CSS z-index bug.
An E2E test will fail, because it tries to click the button.
If an E2E test says “User purchased item”, you can be 99.9% sure that users can purchase items.
2. The Tool of Choice: Playwright
For a decade, Selenium was the standard. It was slow, Java-based, and notoriously flaky. Then came Cypress. It was a massive improvement, with a great developer experience, but it had architectural limitations (running inside the browser sandbox, limited multi-tab support). Today, the industry standard is Playwright (by Microsoft). Why Playwright?
- Speed: It runs tests in parallel across multiple worker processes.
- Reliability: It has “Auto-Waiting”. It waits for elements to be actionable before interacting. No more
sleep(1000). - Multi-Browser: It tests natively on Chromium, Firefox, and WebKit (Safari).
- Tracing: It records a full video and DOM trace of every failure, making debugging trivial.
3. Implementation: Testing a Checkout Flow
Let’s look at a real-world example. We want to test that a guest user can purchase a product.
// tests/checkout.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Checkout Flow', () => {
// Isolate the test environment
test.beforeEach(async ({ page }) => {
// Reset cookies/storage
await page.context().clearCookies();
});
test('Guest user can purchase an item', async ({ page }) => {
// 1. Navigation
console.log('Navigating to Product Page...');
await page.goto('/products/silk-shirt');
await expect(page).toHaveTitle(/Silk Shirt/);
// 2. Add to Cart
// Use user-facing locators (Role, Label, Text)
// Avoid CSS selectors like '.btn-primary' (fragile)
await page.getByRole('button', { name: 'Add to Cart' }).click();
// 3. Verify Cart Drawer
const cartDrawer = page.getByTestId('cart-drawer');
await expect(cartDrawer).toBeVisible();
await expect(cartDrawer).toContainText('Silk Shirt');
// 4. Proceed to Checkout
await page.getByRole('link', { name: 'Checkout' }).click();
await expect(page).toHaveURL(/.*\/checkout/);
// 5. Fill Form (Mocking Payment)
await page.getByLabel('Email').fill('test-bot@maisoncode.paris');
await page.getByLabel('First Name').fill('Test');
await page.getByLabel('Last Name').fill('Bot');
await page.getByLabel('Address').fill('123 Test St');
// 6. Payment (Stripe Mock)
// In strict E2E, we might use a Stripe Test Card.
await page.getByLabel('Card number').fill('4242 4242 4242 4242');
await page.getByLabel('Expiry').fill('12/30');
await page.getByLabel('CVC').fill('123');
// 7. Submit
await page.getByRole('button', { name: 'Pay Now' }).click();
// 8. Assert Success
// Increase timeout because payment processing takes time
await expect(page.getByText('Thank you for your order')).toBeVisible({ timeout: 15000 });
});
});
4. Handling “Flakiness” (The Silent Killer)
A Flaky Test is a test that passes 90% of the time and fails 10% of the time, without any code changes. Flakiness is the enemy. If developers stop trusting the tests (“Oh, just re-run it, it’s flaky”), the testing suite becomes useless.
Common Causes:
- Network Latency: API taking 5.1s when timeout is 5s.
- Animation: Clicking a button while it is still sliding in.
- Data Contamination: Test A deletes a user that Test B needs.
Solutions:
- Mocking (Network Interception):
Instead of calling the real Contentful API (which might be slow), intercept the request and return a static JSON.
This makes the test 10x faster and 100% deterministic.await page.route('**/api/products', route => { route.fulfill({ path: 'mock-data/products.json' }); }); - Retries:
Configure CI to retry failed tests automatically.
retries: 2. If it passes on retry, it’s flaky, but at least it doesn’t block deployment.
5. Visual Regression Testing (Pixel Perfect)
Playwright checks functionality (“Is the button clickable?”).
It does not check aesthetics (“Is the button pink?”).
If you accidentally delete main.css, Playwright might still pass (the button is clickable, just invisible).
Visual Regression Testing (Percy / Chromatic / Playwright Snapshots) fixes this.
await expect(page).toHaveScreenshot();
- Take a screenshot of the page.
- Compare it to the “Golden Image” (Baseline).
- If pixels differ by > 1%, fail the test. This catches “CSS Regressions” that no functional test ever could.
6. GitHub Actions: The Automated Gatekeeper
You don’t run tests on your laptop. You run them on CI (Continuous Integration). Every time you push to GitHub, a workflow runs.
# .github/workflows/e2e.yml
name: Playwright E2E
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Install Deps
run: npm ci
- name: Install Browsers
run: npx playwright install --with-deps
- name: Run Playwright
run: npx playwright test
env:
CI: true
BASE_URL: ${{ github.event.deployment.payload.web_url }} // Test the Vercel Preview URL
- name: Upload Report
uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: playwright-report/
7. The Cost of CI (Optimization)
Running 500 E2E tests on every commit is expensive (GitHub Actions minutes). Optimization Strategies:
- Sharding: Split tests across 10 machines causing them to run 10x faster.
npx playwright test --shard=1/10. - Affected Projects Only: Use Nx or Turbo to only run tests for apps that changed.
- Smoke Tests for PRs: Run a small subset (Critical Path) on PRs. Run the full suite on Main.
8. Conclusion
Automated Testing is not “extra work”. It is the work. It is the difference between a prototype and a product. It is the difference between “I hope it works” and “I know it works”. Start today. Write ONE test. The Login test. Then the Checkout test. Soon, you will sleep better at night.
Releases causing panic?
We architect automated E2E testing suites using Playwright that catch bugs before users do.