Files
2026-05-27 23:29:53 +01:00

59 lines
3.1 KiB
Markdown

# Capability Spec Delta: testing-e2e (testing-e2e-async-and-fixtures-hardening)
**Package**: `@suaveplan/testing-e2e`
**Status**: PROPOSED
## ADDED Requirements
### Requirement: `extendedTest.apiContext` is browser-free
The `apiContext` fixture in `extendedTest` SHALL NOT request the `browser` fixture from Playwright's dependency graph. Playwright spins up requested deps eagerly, so destructuring `browser` would launch a Chromium instance for every API-only test, blowing the per-suite startup budget.
#### Scenario: Source-level guard
- **WHEN** `src/fixtures/extended-test.ts` is parsed
- **THEN** the apiContext fixture body destructures an empty object AND its destructure list contains no `browser` token
#### Scenario: extendedTest export shape
- **WHEN** a consumer imports `extendedTest` from `@suaveplan/testing-e2e/fixtures`
- **THEN** the value is a callable Playwright test extension exposing `.extend(...)`
**Verified by:** `src/fixtures/extended-test.test.ts::"apiContext fixture does not destructure \`browser\`"` AND `src/fixtures/extended-test.test.ts::"extendedTest is exported as a Playwright test extension"`
---
### Requirement: `instrumented()` ends spans only after async fns settle
`instrumented(name, fn)` SHALL detect when the wrapped function returns a PromiseLike value and SHALL defer `span.end()` and the duration histogram record until the returned promise settles. On rejection, the span SHALL `recordException` (wrapping non-Error rejections in `new Error(String(reason))`) before ending. On resolution, the duration recorded SHALL reflect the wall-clock interval between invocation and settlement.
#### Scenario: async resolve
- **WHEN** the wrapped fn awaits ≥10ms before returning
- **THEN** the awaited result equals the inner return value AND the elapsed wall-clock time is ≥10ms
#### Scenario: async reject
- **WHEN** the wrapped async fn throws an `Error`
- **THEN** the returned promise rejects with the same error
#### Scenario: async reject with non-Error reason
- **WHEN** the wrapped async fn rejects with a string
- **THEN** the returned promise rejects with the same string AND `recordException` is invoked with a wrapped `Error`
**Verified by:** `src/observability/instrumented.test.ts::"ends span only after async fn settles (resolved)"` AND `src/observability/instrumented.test.ts::"ends span only after async fn settles (rejected)"` AND `src/observability/instrumented.test.ts::"ends span only after async fn settles (rejected with non-Error)"`
## MODIFIED Requirements
### Requirement: Coverage and docs gates
Istanbul statement + branch + function + line coverage across `packages/dev/testing-e2e/src/` (excluding `*.bench.ts`, `*.schemas.ts`, barrel `index.ts` files) MUST be 100% on every dimension. Every implementation file MUST have a sibling `.md` (≥ 200 words, simple + advanced examples).
#### Scenario: CI coverage gate
- **WHEN** `bunx turbo test:coverage --filter=@suaveplan/testing-e2e` runs
- **THEN** statement / branch / function / line coverage all = 100%
**Verified by:** CI gate (`bunx turbo test:coverage --filter=@suaveplan/testing-e2e` exits non-zero on regression)