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

93 lines
5.6 KiB
Markdown

## 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