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

6.3 KiB

ADDED Requirements

Requirement: NetworkMonitor MUST assert absence of missing assets

NetworkMonitor (returned by attachNetworkMonitor(page)) SHALL expose assertNoMissingAssets() that fails when any 404 was observed for an image, font, script, or stylesheet resource type. The monitor SHALL also expose allow404(predicate: (entry) => boolean) to opt-out specific URLs from this assertion. Other 404s (e.g. for xhr/fetch resource types) are NOT subject to this assertion and remain governed by assertNoFailures().

Scenario: Missing image fails the assertion

  • WHEN the page references <img src="/missing.png"> and the request returns 404
  • AND monitor.assertNoMissingAssets() is called
  • THEN the call throws MissingAssetError with code === "TESTING_E2E.NETWORK.MISSING_ASSET" and context.url containing /missing.png

Scenario: Allow-listed 404 does not fail

  • WHEN monitor.allow404((e) => e.url.endsWith("/beacon.gif")) is called before navigation
  • AND /beacon.gif returns 404
  • AND monitor.assertNoMissingAssets() is called
  • THEN the call returns without throwing

Scenario: Non-asset 404 does not fail this assertion

  • WHEN an xhr request to /api/foo returns 404
  • AND monitor.assertNoMissingAssets() is called
  • THEN the call returns without throwing (the failure is reported by assertNoFailures() instead)

Requirement: NetworkMonitor MUST assert absence of mixed content

NetworkMonitor SHALL expose assertNoMixedContent() that fails when the page is loaded over https:// AND any subresource was fetched over http://, OR when the browser reports a mixed-content blocking event. The assertion is a no-op when the page is loaded over http://.

Scenario: HTTP subresource on HTTPS page fails

  • WHEN the page is https://example.test/
  • AND it includes <script src="http://cdn.test/lib.js">
  • AND monitor.assertNoMixedContent() is called
  • THEN the call throws MixedContentError with code === "TESTING_E2E.NETWORK.MIXED_CONTENT" and context.url containing http://cdn.test/lib.js

Scenario: Plain HTTP page is exempt

  • WHEN the page is http://localhost:3000/
  • AND monitor.assertNoMixedContent() is called
  • THEN the call returns without throwing

Requirement: NetworkMonitor MUST assert absence of CORS errors

NetworkMonitor SHALL expose assertNoCorsErrors() that fails on any of: a request that failed with a net::ERR_CORS_* error code, a preflight (OPTIONS) response that did not satisfy the subsequent request, or a response missing a required Access-Control-Allow-Origin header that the browser blocked.

Scenario: CORS preflight failure fails the assertion

  • WHEN an OPTIONS preflight returns without Access-Control-Allow-Origin and the browser cancels the follow-up POST
  • AND monitor.assertNoCorsErrors() is called
  • THEN the call throws CorsError with code === "TESTING_E2E.NETWORK.CORS_ERROR"

Scenario: Same-origin requests are unaffected

  • WHEN all requests are same-origin and complete normally
  • AND monitor.assertNoCorsErrors() is called
  • THEN the call returns without throwing

Requirement: NetworkMonitor MUST assert request graph against an allow-list

NetworkMonitor SHALL expose assertNoUnexpectedRequests({ allowed: (url: string) => boolean }) that fails when any observed request URL fails the allowed predicate. This assertion is opt-in and intended for tests requiring strict request-graph control.

Scenario: Unexpected request fails the assertion

  • WHEN the page made a request to https://tracker.test/pixel
  • AND monitor.assertNoUnexpectedRequests({ allowed: (u) => u.startsWith("https://example.test") }) is called
  • THEN the call throws UnexpectedRequestError with code === "TESTING_E2E.NETWORK.UNEXPECTED_REQUEST" and context.url === "https://tracker.test/pixel"

Scenario: All allowed requests pass

  • WHEN every request URL satisfies the predicate
  • AND monitor.assertNoUnexpectedRequests({ allowed }) is called
  • THEN the call returns without throwing

The package SHALL export crawlInternalLinks(page, options?: { maxDepth?: number; sameOriginOnly?: boolean; ignore?: (url: string) => boolean }): Promise<CrawlReport> that BFS-walks all <a href> links from the entry page, follows internal links up to maxDepth (default 2), and asserts each link returns 2xx via HEAD with GET fallback. sameOriginOnly defaults to true. The returned CrawlReport SHALL list visited URLs, broken links, and any circular redirects encountered.

  • WHEN the page links to /missing which returns 404
  • AND crawlInternalLinks(page) is called
  • THEN the returned report contains an entry whose error.code === "TESTING_E2E.CRAWL.BROKEN_LINK" and whose url ends in /missing

Scenario: Crawler honours sameOriginOnly default

  • WHEN the page links to https://external.test/foo
  • AND crawlInternalLinks(page) is called with default options
  • THEN https://external.test/foo is NOT visited and is NOT reported as broken

Scenario: Crawler reports circular redirect

  • WHEN /a redirects to /b and /b redirects to /a
  • AND crawlInternalLinks(page) encounters this chain
  • THEN the report contains an entry whose error.code === "TESTING_E2E.CRAWL.CIRCULAR_REDIRECT"

Scenario: Crawler reports max-depth exceeded

  • WHEN the link graph contains paths deeper than maxDepth
  • AND crawlInternalLinks(page, { maxDepth: 1 }) is called
  • THEN unvisited deeper links are reported with error.code === "TESTING_E2E.CRAWL.MAX_DEPTH_EXCEEDED"

Requirement: Network and crawl errors MUST follow the structured-error contract

All errors thrown or reported by the new NetworkMonitor methods and crawlInternalLinks MUST be subclasses of TestingE2eError carrying { code, cause?, context? }. Codes MUST match ^TESTING_E2E\.(NETWORK|CRAWL)\.[A-Z_]+$.

Scenario: Code shape

  • WHEN any of MissingAssetError, MixedContentError, CorsError, UnexpectedRequestError, BrokenLinkError, CircularRedirectError, MaxDepthExceededError is thrown
  • THEN err instanceof TestingE2eError is true AND err.code matches the regex