level 14 / architecture-and-scalability
Architecture & Scalability
Answer senior-level architecture questions about scaling test suites, parallelism, sharding, reporting, and multi-team test ownership.
The Scalability Interview
Senior and lead roles get questions about what happens when a test suite grows from 50 to 5,000 tests. They want to know:
- How do you keep CI feedback loops short?
- How do you manage test ownership across teams?
- How do you handle shared infrastructure (DBs, auth, environments)?
- How do you measure and improve test reliability?
Execution Scalability
Parallelism vs Sharding
// Parallelism: multiple workers on ONE machine
// playwright.config.ts
export default defineConfig({
workers: process.env.CI ? 4 : 2, // 4 CPUs on CI agent
});
// Sharding: split suite across MULTIPLE machines
// Machine 1: npx playwright test --shard=1/4
// Machine 2: npx playwright test --shard=2/4
// Machine 3: npx playwright test --shard=3/4
// Machine 4: npx playwright test --shard=4/4
When to shard:
- Suite takes >15 minutes on a single machine
- CI costs allow multiple parallel agents
- Tests are independent (no shared mutation)
Blob Reporter + Merge
# GitHub Actions matrix sharding
jobs:
test:
strategy:
matrix:
shard: [1, 2, 3, 4]
steps:
- run: npx playwright test --shard=${{ matrix.shard }}/4 --reporter=blob
- uses: actions/upload-artifact@v4
with:
name: blob-report-${{ matrix.shard }}
path: blob-report/
merge-reports:
needs: test
steps:
- uses: actions/download-artifact@v4
with: { path: all-blob-reports/, pattern: blob-report-* }
- run: npx playwright merge-reports --reporter=html ./all-blob-reports
Test Suite Architecture at Scale
Layered Test Strategy
Suite layers (each has different run frequency):
Layer 1: Smoke (10-20 tests) → every commit, <5 min
Layer 2: Regression (200-500) → every PR, <20 min (sharded)
Layer 3: Full suite (1000+) → nightly, <60 min (sharded)
Layer 4: Performance/Security → weekly or release
// Tag-based filtering
test('critical path checkout @smoke @regression', async ({ page }) => { ... });
test('edge case empty cart @regression', async ({ page }) => { ... });
// playwright.config.ts projects
projects: [
{ name: 'smoke', grep: /@smoke/ },
{ name: 'regression', grep: /@regression/ },
{ name: 'full', grepInvert: /@skip/ },
]
Multi-Team Ownership
Mono-repo with team namespacing:
e2e/
├── core/ → platform team owns
├── payments/ → payments team owns
├── catalog/ → catalog team owns
└── shared/ → shared fixtures, utilities (governance required)
playwright.config.ts: each team has a project config
payments team PR: only runs e2e/payments/ tests in PR, full suite nightly
Reliability at Scale
Flakiness Tracking
// Custom reporter: track flaky tests
class FlakinesReporter implements Reporter {
onTestEnd(test: TestCase, result: TestResult) {
if (result.status === 'flaky') {
// POST to metrics service: test title, file, duration, retry count
reportFlakiness({
title: test.title,
file: test.location.file,
retries: result.retry,
duration: result.duration,
});
}
}
}
Flakiness SLO: Teams own their test reliability. A test that flakes >5% of runs in 7 days is auto-filed as a P2 bug and quarantined (tagged @flaky, excluded from smoke).
Quarantine Pattern
// quarantine.ts — shared list
export const QUARANTINED = [
'checkout > payment > should handle timeout',
'catalog > search > should filter by price',
];
// playwright.config.ts
grepInvert: new RegExp(QUARANTINED.map(t => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'))
Environment Architecture
Environments:
- local: developer machine, real services via docker-compose
- preview: per-PR ephemeral environment (Vercel/Railway preview deploy)
- staging: shared, seeded weekly, integration test target
- production: smoke only (never mutate, never create test data)
// playwright.config.ts — environment-aware baseURL
const ENV = process.env.TEST_ENV ?? 'staging';
const BASE_URLS = {
local: 'http://localhost:3000',
preview: process.env.PREVIEW_URL!,
staging: 'https://staging.example.com',
production: 'https://example.com',
};
export default defineConfig({
use: { baseURL: BASE_URLS[ENV] },
});