Playwright E2E Testing Setup with Traffic Tracking and OpenTelemetry
Session Date: 2026-01-19 Project: schema-org-file-system Focus: Implement comprehensive E2E testing infrastructure with observability Session Type: Implementation
Executive Summary
Implemented a complete Playwright E2E testing framework for the schema-org-file-system dashboard. The setup includes traffic tracking headers for request correlation, OpenTelemetry integration for distributed tracing, HAR recording for network analysis, and Core Web Vitals measurement for performance monitoring.
Created 14 files including 4 test fixtures, 4 test spec files covering all dashboard pages, global setup/teardown for OTEL initialization, and configuration files. Installed 199 npm packages and 3 Playwright browsers (Chromium, Firefox, WebKit).
Key Metrics: | Metric | Value | |——–|——-| | Files Created | 14 | | Test Spec Files | 4 | | Fixture Modules | 5 | | NPM Packages Added | 199 | | Browsers Installed | 3 | | Build Errors | 0 |
Problem Statement
The schema-org-file-system project has a dashboard (_site/) with multiple HTML pages but no automated E2E testing infrastructure. Key requirements:
- Traffic Tracking: Ability to identify test traffic in production logs
- Distributed Tracing: OpenTelemetry integration for observability
- Network Analysis: HAR file recording for debugging
- Performance Monitoring: Core Web Vitals measurement (LCP, FCP, CLS, TTFB)
- Multi-Browser Testing: Chromium, Firefox, WebKit, and mobile viewports
Implementation Details
Configuration Files
File: playwright.config.ts
export default defineConfig({
testDir: './tests/e2e',
fullyParallel: true,
reporter: [
['html', { outputFolder: 'playwright-report' }],
['json', { outputFile: 'results/e2e-results.json' }],
['list']
],
globalSetup: './tests/e2e/global-setup.ts',
globalTeardown: './tests/e2e/global-teardown.ts',
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
{ name: 'mobile-safari', use: { ...devices['iPhone 12'] } },
],
webServer: {
command: 'npm run serve',
url: 'http://localhost:3000',
},
});
Traffic Tracking Fixture
File: tests/e2e/fixtures/traffic-tracking.ts
Injects tracking headers on all requests:
await page.route('**/*', async (route) => {
const headers = {
...route.request().headers(),
'X-Test-Traffic': 'playwright-e2e',
'X-Request-ID': uuidv4(),
'X-Correlation-ID': correlationId,
'X-Test-Name': testName,
'X-Test-File': testFile,
};
await route.continue({ headers });
});
Headers Injected: | Header | Description | |——–|————-| | X-Test-Traffic | Identifies traffic as E2E test (always playwright-e2e) | | X-Request-ID | UUID per request for individual request tracking | | X-Correlation-ID | UUID per test run for correlating all requests | | X-Test-Name | Test title for debugging | | X-Test-File | Spec filename for traceability |
OpenTelemetry Integration
File: tests/e2e/fixtures/otel-tracing.ts
Creates spans for tests and network requests:
page.on('request', (request: Request) => {
const span = tracer.startSpan(`HTTP ${request.method()} ${pathname}`, {
attributes: {
'http.method': request.method(),
'http.url': request.url(),
'resource.type': request.resourceType(),
},
});
requestSpans.set(key, span);
});
page.on('response', (response: Response) => {
span.setAttributes({ 'http.status_code': response.status() });
span.setStatus({ code: response.status() >= 400 ? ERROR : OK });
span.end();
});
Core Web Vitals Measurement
File: tests/e2e/fixtures/performance.ts
Measures key performance metrics:
export interface CoreWebVitals {
lcp: number | null; // Largest Contentful Paint
fcp: number | null; // First Contentful Paint
cls: number | null; // Cumulative Layout Shift
ttfb: number | null; // Time to First Byte
}
export const DEFAULT_THRESHOLDS: PerformanceThresholds = {
lcp: 2500, // 2.5s for "good"
fcp: 1800, // 1.8s for "good"
cls: 0.1, // 0.1 for "good"
ttfb: 800, // 800ms for "good"
};
HAR Recording
File: tests/e2e/fixtures/har-recording.ts
Records network traffic per test:
const context = await browser.newContext({
recordHar: {
path: harPath,
mode: 'full',
content: 'embed',
},
});
Output: test-results/har/<test-file>-<test-name>.har
Global Setup/Teardown
File: tests/e2e/global-setup.ts
Initializes OTEL SDK and creates test run metadata:
const metadata: TestRunMetadata = {
runId: uuidv4(),
correlationId: uuidv4(),
startTime: new Date().toISOString(),
playwright: { version, projects },
otel: { enabled, endpoint },
environment: { nodeVersion, platform, ci },
};
fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));
Test Specifications
Created 4 comprehensive test spec files:
| Spec File | Tests | Coverage |
|---|---|---|
dashboard.spec.ts | 14 | Hero, stats, cards, navigation, performance |
metadata-viewer.spec.ts | 17 | Filters, search, dark mode, large JSON |
timeline.spec.ts | 12 | Visualization, interactions, FPS |
correction-interface.spec.ts | 14 | Forms, validation, export |
Example Test (dashboard.spec.ts):
test('should meet Core Web Vitals thresholds', async ({ page }) => {
await page.goto('/');
await page.waitForLoadState('load');
const metrics = await measurePagePerformance(page);
console.log(formatMetrics(metrics));
const violations = checkThresholds(metrics, DEFAULT_THRESHOLDS);
if (metrics.lcp !== null) {
expect(metrics.lcp).toBeLessThan(2500);
}
});
Files Created
Configuration (3 files)
| File | Lines | Purpose | |——|——-|———| | package.json | 32 | Updated with Playwright + OTEL dependencies | | tsconfig.json | 26 | TypeScript configuration for E2E tests | | playwright.config.ts | 55 | Multi-browser, HAR, webServer configuration |
Test Fixtures (5 files)
| File | Lines | Purpose | |——|——-|———| | tests/e2e/fixtures/traffic-tracking.ts | 95 | X-Test-Traffic header injection | | tests/e2e/fixtures/har-recording.ts | 135 | HAR file recording per test | | tests/e2e/fixtures/otel-tracing.ts | 165 | OpenTelemetry spans for tests | | tests/e2e/fixtures/performance.ts | 195 | Core Web Vitals measurement | | tests/e2e/fixtures/index.ts | 35 | Merged fixture export |
Global Setup/Teardown (2 files)
| File | Lines | Purpose | |——|——-|———| | tests/e2e/global-setup.ts | 85 | OTEL SDK init, test metadata | | tests/e2e/global-teardown.ts | 70 | Flush spans, update metadata |
Test Specs (4 files)
| File | Tests | Purpose | |——|——-|———| | tests/e2e/dashboard.spec.ts | 14 | Main dashboard tests | | tests/e2e/metadata-viewer.spec.ts | 17 | Metadata viewer tests | | tests/e2e/timeline.spec.ts | 12 | Timeline visualization tests | | tests/e2e/correction-interface.spec.ts | 14 | Correction form tests |
Dependencies Installed
NPM Packages (199 total)
Core Testing:
@playwright/test^1.40.0typescript^5.3.0@types/node^20.10.0
OpenTelemetry:
@opentelemetry/api^1.7.0@opentelemetry/sdk-node^0.46.0@opentelemetry/sdk-trace-node^1.19.0@opentelemetry/exporter-trace-otlp-http^0.46.0@opentelemetry/resources^1.19.0@opentelemetry/semantic-conventions^1.19.0
Utilities:
uuid^9.0.1serve^14.2.0
Playwright Browsers
| Browser | Version | Build |
|---|---|---|
| Chromium | (cached) | - |
| Firefox | 144.0.2 | v1497 |
| WebKit | 26.0 | v2227 |
NPM Scripts
{
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"test:e2e:report": "playwright show-report",
"test:e2e:debug": "playwright test --debug",
"test:e2e:headed": "playwright test --headed",
"test:e2e:trace": "playwright test --trace on",
"serve": "serve _site -l 3000"
}
Output Directory Structure
test-results/
├── har/ # HAR files per test
└── telemetry/
└── test-run-metadata.json
playwright-report/ # HTML test report
results/
└── e2e-results.json # JSON test results
Environment Variables
| Variable | Description |
|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT | OTLP collector endpoint |
OTEL_DEBUG | Enable OTEL debug logging |
BASE_URL | Override base URL (default: http://localhost:3000) |
Verification
# Install dependencies
npm install
# Output: added 199 packages in 7s
# Install Playwright browsers
npx playwright install
# Output: Firefox 144.0.2, WebKit 26.0 downloaded
# Run tests
npm run test:e2e
# View report
npm run test:e2e:report
Key Decisions and Trade-offs
Decision 1: Merged Fixtures vs Separate Test Files
Choice: Merged fixtures with selective imports available Rationale: Provides convenience while maintaining flexibility Alternative Considered: Separate test files per fixture Trade-off: Slightly larger test context for simpler usage
Decision 2: HAR Recording Per Test vs Per Suite
Choice: Per test recording Rationale: Easier debugging of specific test failures Alternative Considered: Single HAR per test suite Trade-off: More files, but better isolation
Decision 3: Default OTEL Disabled Unless Endpoint Set
Choice: Opt-in OTEL via environment variable Rationale: No overhead for local development Alternative Considered: Always enabled with console exporter Trade-off: Requires setup for OTEL benefits
Next Steps
Immediate
- Run
npm run test:e2eto verify all tests pass - Configure OTEL endpoint for trace collection
Short-term
- Add visual regression testing with Playwright snapshots
- Integrate with CI/CD pipeline (GitHub Actions)
Medium-term
- Add accessibility testing (axe-core integration)
- Create custom reporters for metrics aggregation
References
Code Files
playwright.config.ts- Main configurationtests/e2e/fixtures/index.ts- Merged fixturestests/e2e/global-setup.ts- OTEL initialization
Documentation
Dashboard Pages Tested
_site/index.html- Main dashboard_site/metadata_viewer.html- Metadata viewer_site/timeline.html- Timeline visualization_site/correction_interface.html- Correction interface