Files
suaveplan/openspec/changes/enterprise-testing-integration/specs/testing-e2e/spec.md
2026-05-27 23:29:53 +01:00

5.6 KiB

ADDED Requirements

Requirement: ConsoleMonitor

The @suaveplan/testing-e2e package SHALL export attachConsoleMonitor(page, options?) returning an object exposing errors: ConsoleMessage[], warnings: ConsoleMessage[], assertNoErrors(): void, and assertNoWarnings(): void. The default behaviour MUST forbid all console errors and warnings; the options.failOn field MAY relax to ["error"] only. The options.ignore field MAY accept regex or string filters that exempt matching messages.

assertNoErrors() and assertNoWarnings() MUST throw TestingE2eError subclasses (code === "TESTING_E2E.CONSOLE.ERRORS_OBSERVED" / "TESTING_E2E.CONSOLE.WARNINGS_OBSERVED") carrying the captured messages on context.

Scenario: No errors observed

  • WHEN a page emits only info-level console output and monitor.assertNoErrors() is called
  • THEN the call returns without throwing

Scenario: Error observed

  • WHEN a page logs console.error("boom") and monitor.assertNoErrors() is called
  • THEN the call throws with code === "TESTING_E2E.CONSOLE.ERRORS_OBSERVED" AND context.messages includes "boom"

Scenario: Filter exempts known noise

  • WHEN attachConsoleMonitor(page, { ignore: [/Download the React DevTools/] }) is used and a matching warning fires
  • THEN monitor.warnings does not include the matching message AND assertNoWarnings() does not throw

Requirement: NetworkMonitor with full failure-class assertions

attachNetworkMonitor(page) SHALL return an object exposing failures: Request[] and four assertion methods, each throwing a coded TestingE2eError subclass:

  • assertNoFailures() — fails on any request that returned 4xx, 5xx, network error, or aborted (code TESTING_E2E.NETWORK.FAILURES_OBSERVED).
  • assertNoMissingAssets() — fails on any 404 for an asset request whose resourceType is image | font | script | stylesheet (code TESTING_E2E.NETWORK.MISSING_ASSETS).
  • assertNoMixedContent() — fails when the page is HTTPS and any subresource is HTTP (code TESTING_E2E.NETWORK.MIXED_CONTENT).
  • assertNoCorsErrors() — fails on any request blocked by the browser's CORS rules (code TESTING_E2E.NETWORK.CORS_BLOCKED).

Scenario: 404 image fails missing-assets check

  • WHEN a <img src="/missing.png"> is rendered and the response is 404
  • THEN monitor.assertNoMissingAssets() throws with code === "TESTING_E2E.NETWORK.MISSING_ASSETS" AND context.urls includes the image URL

Scenario: HTTPS page loading HTTP script triggers mixed-content

  • WHEN an HTTPS page loads a <script src="http://…">
  • THEN monitor.assertNoMixedContent() throws with code === "TESTING_E2E.NETWORK.MIXED_CONTENT"

Scenario: CORS-blocked request

  • WHEN a fetch is blocked by CORS
  • THEN monitor.assertNoCorsErrors() throws with code === "TESTING_E2E.NETWORK.CORS_BLOCKED"

Requirement: Multi-session WebRTC fixture

The package SHALL export createMultiSessionFixture(opts) returning an object that owns at least two isolated browser contexts sharing a signaling URL but with separated cookies and storage. The fixture MUST expose:

  • sessions: { context: BrowserContext; page: Page }[] — at least two entries
  • assertMessage(from: number, to: number, predicate: (data: unknown) => boolean): Promise<void> — asserts a peer-to-peer message arrives
  • disableSignalingRoute(): Promise<void> — chaos hook that 5xx-responses to the signaling URL until re-enabled

Scenario: Two sessions exchange a message

  • WHEN session 0 sends a payload and assertMessage(0, 1, p => p === "hello") is awaited
  • THEN the assertion resolves once session 1 observes the payload

Scenario: Disabling signaling produces a deterministic failure

  • WHEN disableSignalingRoute() is called and a new session attempts to register
  • THEN the registration fails with a 5xx from the signaling URL

Requirement: Extension fixture

The package SHALL export extendTestWithExtension(extPath: string) that loads a Chromium extension into the persistent context and exposes popup, background, and contentScript accessors on the test fixture. The accessors MUST return Playwright Page (popup), Worker or Page (background — service worker or background page depending on manifest version), and an array of Page (one per content-script-injected frame) respectively.

Scenario: Background service worker accessible

  • WHEN an MV3 extension is loaded and the test reads background
  • THEN the returned value is a Playwright Worker whose URL begins with chrome-extension://

Requirement: Default-deny network sandbox

createPlaywrightConfig SHALL default to blocking external origins by registering page.route('**/*', …) that aborts requests whose origin is not in options.allowedOrigins. Same-origin requests to the configured baseURL MUST always be allowed. Callers MAY supply allowedOrigins: string[] to extend the allow-list. Setting options.allowedOrigins = "*" disables the sandbox (escape hatch for fully-online specs).

Scenario: External fetch blocked by default

  • WHEN a page running under createPlaywrightConfig({ baseURL: "http://localhost:3000" }) issues a fetch("https://api.external.com/x")
  • THEN the request is aborted by Playwright before it leaves the browser

Scenario: Allow-list grants access

  • WHEN the same config is constructed with allowedOrigins: ["https://api.external.com"]
  • THEN the same fetch resolves normally

Scenario: Escape hatch disables sandbox

  • WHEN allowedOrigins === "*"
  • THEN no page.route block is registered AND every request proceeds