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:

MetricValue
Files Created14
Test Spec Files4
Fixture Modules5
NPM Packages Added199
Browsers Installed3
Build Errors0

Problem Statement

The schema-org-file-system project has a dashboard (_site/) with multiple HTML pages but no automated E2E testing infrastructure. Key requirements:

  1. Traffic Tracking: Ability to identify test traffic in production logs
  2. Distributed Tracing: OpenTelemetry integration for observability
  3. Network Analysis: HAR file recording for debugging
  4. Performance Monitoring: Core Web Vitals measurement (LCP, FCP, CLS, TTFB)
  5. 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:

HeaderDescription
X-Test-TrafficIdentifies traffic as E2E test (always playwright-e2e)
X-Request-IDUUID per request for individual request tracking
X-Correlation-IDUUID per test run for correlating all requests
X-Test-NameTest title for debugging
X-Test-FileSpec 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 FileTestsCoverage
dashboard.spec.ts14Hero, stats, cards, navigation, performance
metadata-viewer.spec.ts17Filters, search, dark mode, large JSON
timeline.spec.ts12Visualization, interactions, FPS
correction-interface.spec.ts14Forms, 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)

FileLinesPurpose
package.json32Updated with Playwright + OTEL dependencies
tsconfig.json26TypeScript configuration for E2E tests
playwright.config.ts55Multi-browser, HAR, webServer configuration

Test Fixtures (5 files)

FileLinesPurpose
tests/e2e/fixtures/traffic-tracking.ts95X-Test-Traffic header injection
tests/e2e/fixtures/har-recording.ts135HAR file recording per test
tests/e2e/fixtures/otel-tracing.ts165OpenTelemetry spans for tests
tests/e2e/fixtures/performance.ts195Core Web Vitals measurement
tests/e2e/fixtures/index.ts35Merged fixture export

Global Setup/Teardown (2 files)

FileLinesPurpose
tests/e2e/global-setup.ts85OTEL SDK init, test metadata
tests/e2e/global-teardown.ts70Flush spans, update metadata

Test Specs (4 files)

FileTestsPurpose
tests/e2e/dashboard.spec.ts14Main dashboard tests
tests/e2e/metadata-viewer.spec.ts17Metadata viewer tests
tests/e2e/timeline.spec.ts12Timeline visualization tests
tests/e2e/correction-interface.spec.ts14Correction form tests

Dependencies Installed

NPM Packages (199 total)

Core Testing:

  • @playwright/test ^1.40.0
  • typescript ^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.1
  • serve ^14.2.0

Playwright Browsers

BrowserVersionBuild
Chromium(cached)-
Firefox144.0.2v1497
WebKit26.0v2227

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

VariableDescription
OTEL_EXPORTER_OTLP_ENDPOINTOTLP collector endpoint
OTEL_DEBUGEnable OTEL debug logging
BASE_URLOverride base URL (default: http://localhost:3000)

Verification

npm install

npx playwright install

npm run test:e2e

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

  1. Run npm run test:e2e to verify all tests pass
  2. Configure OTEL endpoint for trace collection

Short-term

  1. Add visual regression testing with Playwright snapshots
  2. Integrate with CI/CD pipeline (GitHub Actions)

Medium-term

  1. Add accessibility testing (axe-core integration)
  2. Create custom reporters for metrics aggregation

References

Code Files

  • playwright.config.ts - Main configuration
  • tests/e2e/fixtures/index.ts - Merged fixtures
  • tests/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