Chapter 99: Capstone 2 — Production-Grade SaaS Web Application

End-to-End SaaS Testing & CI/CD Deployment

Automating enterprise accessibility auditing with Playwright, `@axe-core/playwright`, Lighthouse CI budgets, and containerized production deployment.

LEARNING OBJECTIVES
  • Author automated End-to-End (E2E) integration test suites using Playwright to verify keyboard navigation, modal focus traps, and form state transitions.
  • Automate zero-tolerance accessibility regression tests using @axe-core/playwright to enforce WCAG 2.2 AAA compliance.
  • Configure Lighthouse CI assertions (.lighthouserc.json) to enforce 100% scores across Performance, Accessibility, and Best Practices.
  • Package the static SaaS application into a multi-stage production Docker container served by a hardened Nginx instance with automated security headers.
🎬 INTERACTIVE VISUAL PIPELINE Core Architecture Simulation
🌐
1. Input
Directives & Tags
⚙️
2. Parse
Tokenizer & AST
🌳
3. Layout
Box Model & Flow
🎨
4. Render
GPU Paint & Composite
PHASE 1: INPUT & DIRECTIVES
Browser receives declarative markup stream, parsing tag tokens and initializing component state.

📖 The Mental Model & Story (Intuitive Foundation)

Imagine manufacturing high-performance commercial jetliners. Before any aircraft rolls out to an airline, it undergoes two rigorous phases of verification:

  1. The Automated Wind Tunnel & Stress Rig (Playwright & Axe-Core): Robotic arms cycle the landing gear 10,000 times, hydraulic pumps test cabin pressure at extreme altitudes, and optical sensors inspect every rivet for structural micro-fractures. No human subjective opinion is involved—if a single sensor fails tolerances, the assembly line halts.
  2. The FAA Airworthiness Certification (Lighthouse CI & Docker Packaging): Once all automated mechanical tests pass, the plane is sealed in a certified flight-ready container, accompanied by cryptographic maintenance manifests, and signed off for commercial deployment worldwide.

In software engineering, shipping an enterprise SaaS dashboard without automated accessibility and performance assertions is like flying an uninspected aircraft. Manual testing misses subtle keyboard focus traps, broken ARIA bindings, and slow rendering cascades.

By integrating automated Playwright tests, axe-core scans, Lighthouse CI budgets, and Docker containerization, we ensure our CloudMetrics Pro application deploys with 100% confidence, zero regressions, and enterprise-grade reliability.


Technical Deep Dive & Specifications

1. Enterprise CI/CD Quality Pipeline Architecture

+----------------------------------------------------------------------------------------------------+
| GIT PUSH (main branch)                                                                             |
+----------------------------------------------------------------------------------------------------+
                                      |
                                      v
+----------------------------------------------------------------------------------------------------+
| GITHUB ACTIONS CI RUNNER                                                                           |
|  ├── STAGE 1: HTML & ARIA Linting (HTMLHint, W3C Validator)                                        |
|  ├── STAGE 2: Playwright E2E & Keyboard Navigation Test Suite                                      |
|  │    ├── Test 1: Skip link focus traversal                                                       |
|  │    ├── Test 2: Native <dialog> focus trap & Escape dismissal                                    |
|  │    └── Test 3: Multi-step wizard constraint validation                                          |
|  ├── STAGE 3: Automated WCAG 2.2 AAA Audit (@axe-core/playwright)                                   |
|  │    └── Assert 0 violations in color-contrast, aria-roles, label-associations                    |
|  ├── STAGE 4: Lighthouse CI Budget Assertions                                                      |
|  │    └── Assert: Performance >= 95, Accessibility = 100, Best-Practices = 100                     |
|  └── STAGE 5: Multi-Stage Docker Container Build                                                   |
|       └── Hardened Nginx 1.25 Alpine with CSP Nonce & Security Headers Injected                    |
+----------------------------------------------------------------------------------------------------+
                                      |
                                      v
+----------------------------------------------------------------------------------------------------+
| PRODUCTION CLOUD CLUSTER (Kubernetes / AWS ECS)                                                    |
|  └── Live High-Availability Multi-Tenant SaaS App                                                  |
+----------------------------------------------------------------------------------------------------+

2. CI/CD Assertion & Tooling Matrix

Test Suite / Tool Test Target Pass/Fail Criteria Enterprise Requirement
Playwright E2E User workflows, modal lifecycles, keyboard events. 100% assertion pass rate. Mandatory for all release pull requests.
@axe-core/playwright WCAG 2.2 AA & AAA automated rules. expect(accessibilityScanResults.violations).toEqual([]) Zero accessibility violations.
Lighthouse CI Core Web Vitals, SEO, PWA readiness, contrast. categories:accessibility: 1.0, categories:performance: 0.95 Blocks deployment if budget fails.
Multi-Stage Docker Minimal production footprint, asset compression. Alpine Linux image < 25MB, non-root user. SOC2 & ISO 27001 deployment compliance.

3. Production Nginx Security Headers Configuration

# /etc/nginx/conf.d/default.conf
server {
    listen 80;
    listen [::]:80;
    server_name cloudmetrics.io;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name cloudmetrics.io;

    root /usr/share/nginx/html;
    index index.html;

    # Security Headers
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

    # Gzip & Brotli Compression
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;

    location / {
        try_files $uri $uri/ /index.html;
    }
}

💻 Interactive Code Playground

Starter Code

Line-by-Line Code Breakdown

  • Lines 28–34 (new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa', ...]).analyze()): Executes an automated accessibility audit against the live Chromium DOM, evaluating color contrast, ARIA landmarks, form labels, and heading orders.
  • Line 33 (expect(accessibilityScanResults.violations).toEqual([])): Enforces zero tolerance for accessibility defects in CI/CD builds.
  • Lines 36–48 (Keyboard Skiplink Test): Simulates real keyboard tabbing to ensure power users can bypass navigation menus to reach main content immediately.
  • Lines 50–65 (Native Modal Test): Verifies that opening <dialog> correctly traps focus on the primary action and that pressing Escape closes the dialog and restores focus to the trigger button.
  • Lines 67–82 (Constraint Validation Test): Tests client-side form validation state machines, asserting that incomplete steps cannot advance until inputs satisfy all constraint attributes.

Expected Browser Render Output


// playwright.config.ts - Enterprise Playwright Configuration
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 2 : undefined,
  reporter: [['html'], ['list']],
  use: {
    baseURL: 'http://localhost:8080',
    trace: 'on-first-retry',
  },
  webServer: {
    command: 'npx http-server ./dist -p 8080',
    port: 8080,
    reuseExistingServer: !process.env.CI,
  },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
    { name: 'webkit', use: { ...devices['Desktop Safari'] } },
  ],
});
// tests/capstone-saas.spec.ts - Full E2E & Accessibility Test Suite
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test.describe('CloudMetrics Pro Capstone Suite', () => {
  
  test.beforeEach(async ({ page }) => {
    await page.goto('/');
  });

  test('1. Automated WCAG 2.2 AAA Accessibility Audit', async ({ page }) => {
    // Run automated Axe scan across entire rendered document
    const accessibilityScanResults = await new AxeBuilder({ page })
      .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22aa'])
      .analyze();

    expect(accessibilityScanResults.violations).toEqual([]);
  });

  test('2. Keyboard Skiplink Navigation', async ({ page }) => {
    // Press Tab from clean page state
    await page.keyboard.press('Tab');
    
    // Ensure skiplink receives focus and is visible
    const skipLink = page.locator('.skip-link');
    await expect(skipLink).toBeFocused();
    await expect(skipLink).toBeVisible();

    // Trigger skiplink
    await page.keyboard.press('Enter');
    const mainHeading = page.locator('main h1');
    await expect(mainHeading).toBeVisible();
  });

  test('3. Native Modal Dialog Lifecycle & Focus Trap', async ({ page }) => {
    const openModalBtn = page.locator('#open-modal-btn');
    await openModalBtn.click();

    const dialog = page.locator('#terminate-dialog');
    await expect(dialog).toBeVisible();

    // Verify focus moved into dialog
    const confirmBtn = dialog.locator('button[value="terminate"]');
    await expect(confirmBtn).toBeFocused();

    // Verify Escape key dismissal
    await page.keyboard.press('Escape');
    await expect(dialog).not.toBeVisible();
    await expect(openModalBtn).toBeFocused();
  });

  test('4. Onboarding Wizard Constraint Validation', async ({ page }) => {
    // Attempt to proceed with empty required fields
    const nextBtn = page.locator('#next-btn');
    await nextBtn.click();

    // Step 1 should remain visible because inputs are invalid
    const step1 = page.locator('#step-1');
    await expect(step1).toBeVisible();

    // Fill valid data
    await page.fill('#org-name', 'Acme Observability Corp');
    await page.fill('#admin-email', '[email protected]');
    await nextBtn.click();

    // Should transition to Step 2
    const step2 = page.locator('#step-2');
    await expect(step2).toBeVisible();
    await expect(step1).toBeHidden();
  });
});
Running 4 tests using 3 workers

  ✓  [chromium] › capstone-saas.spec.ts:27:7 › 1. Automated WCAG 2.2 AAA Accessibility Audit (412ms)
  ✓  [chromium] › capstone-saas.spec.ts:36:7 › 2. Keyboard Skiplink Navigation (185ms)
  ✓  [chromium] › capstone-saas.spec.ts:50:7 › 3. Native Modal Dialog Lifecycle & Focus Trap (240ms)
  ✓  [chromium] › capstone-saas.spec.ts:67:7 › 4. Onboarding Wizard Constraint Validation (310ms)

  4 passed (1.8s)

🏋️ Hands-On Exercise

🎯 The Challenge: Production Multi-Stage Dockerfile & Lighthouse Budget

Write a production-grade multi-stage Dockerfile and a .lighthouserc.json configuration file that guarantees high-performance static asset serving and asserts strict Lighthouse performance budgets.

Instructions:

  1. Create a Dockerfile that:
    • Uses node:20-alpine as the build stage to build static HTML/CSS/JS assets.
    • Copies static output to nginx:1.25-alpine.
    • Runs as a non-privileged user and exposes port 80.
  2. Create .lighthouserc.json asserting:
    • categories:performance: 0.95
    • categories:accessibility: 1.0
    • categories:best-practices: 1.0

🏁 Starter Code Sandbox

⚠️ Common Pitfalls

  1. Testing Accessibility Only Manually: Manual audits catch only a fraction of regressions over time. Automated axe-core tests in CI prevent accidental ARIA breakage across continuous feature deployments.
  2. Flaky E2E Tests from Arbitrary Timeouts: Avoid await page.waitForTimeout(3000). Always wait for deterministic DOM assertions like await expect(locator).toBeVisible().
  3. Deploying Nginx without Gzip/Brotli: Forgetting static compression increases initial HTML/CSS payload transfer sizes by up to 80%, degrading First Contentful Paint (FCP) scores.

💡 Pro Tips

  1. Visual Regression Testing with Playwright: Use await expect(page).toHaveScreenshot('dashboard-dark.png') to capture pixel-perfect visual diffs across Chromium, Firefox, and WebKit rendering engines.
  2. GitHub Actions Matrix Builds: Run your Playwright test matrix concurrently across multiple OS environments (Ubuntu, macOS, Windows) to guarantee cross-platform user experience parity.

📌 Key Takeaways

  • Automated end-to-end testing with Playwright verifies real user keyboard workflows, modal focus traps, and form state transitions.
  • @axe-core/playwright enables continuous, zero-tolerance accessibility regression testing in CI/CD pipelines.
  • Lighthouse CI enforces quantitative performance, accessibility, and SEO budgets before deployment.
  • Production Nginx containers must inject essential security headers (X-Frame-Options, X-Content-Type-Options, HSTS, CSP).
  • Multi-stage Docker builds produce minimal, hardened container images suitable for enterprise multi-tenant cloud architectures.
  • --
⭐ LEARN: HTML 🌟 ⚔️ QUIZ BATTLE ARENA // ACTIVE
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 1 / 3

What is the primary benefit of integrating @axe-core/playwright into a Continuous Integration (CI) pipeline?

Question 1 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 2 / 3

In Playwright testing, why should you avoid await page.waitForTimeout(3000)?

Question 2 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 3 / 3

What does the Lighthouse CI assertion "categories:accessibility": ["error", { "minScore": 1.0 }] enforce?

Question 3 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP