Initial Commit
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
## 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
|
||||
|
||||
### Requirement: `crawlInternalLinks` MUST walk and verify internal links
|
||||
|
||||
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.
|
||||
|
||||
#### Scenario: Crawler reports broken internal link
|
||||
|
||||
- **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
|
||||
Reference in New Issue
Block a user