Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
# testing-e2e: async-span + apiContext fixture hardening
**Package**: `@suaveplan/testing-e2e`
**Status**: PROPOSED
**Type**: bug-fix
## Why
Two latent defects:
1. **`extendedTest.apiContext` requests a browser it never uses.** `src/fixtures/extended-test.ts` destructures `{ browser: _browser }` from Playwright's fixture deps. Playwright's fixture system spins up the requested deps eagerly; declaring `browser` triggers a Chromium launch even for API-only test files. API-only suites pay hundreds of ms per worker for nothing.
2. **`instrumented()` ends spans synchronously even for async fns.** Same defect as in `@suaveplan/testing-dom`. Every histogram emitted by an async-wrapped helper records ~0 ms.
## What changes
- MODIFIED: `src/fixtures/extended-test.ts#apiContext` — changes the destructure from `{ browser: _browser }` to `{}` so Playwright doesn't materialise a browser for API-only tests.
- MODIFIED: `src/observability/instrumented.ts` — detects PromiseLike return values and ends the OTel span (and records the histogram) only after the promise settles.
- MODIFIED: `openspec/specs/testing-e2e/spec.md` — coverage gate raised from ≥95% to 100% to match `CLAUDE.md`'s pre-publish gate. Path reference updated from `packages/universal/testing-e2e/` to `packages/dev/testing-e2e/`.
## Out of scope
- Refactoring `instrumented()` away from `tracer.startActiveSpan` — keeping the same OTel surface, only fixing span lifetime.
- Adding new fixtures to `extendedTest`.
## Verification approach
- `src/fixtures/extended-test.test.ts` — source-level guard that the apiContext fixture body destructures an empty object, NOT one containing `browser`.
- `src/observability/instrumented.test.ts::"ends span only after async fn settles (resolved)"` — observable timing assertion (`elapsed >= 10ms`).
- `src/observability/instrumented.test.ts::"ends span only after async fn settles (rejected)"` and `"… (rejected with non-Error)"` — pin the rejection branch including `recordException` for non-Error throws.
## Migration impact
None — bug-fix-only behavioural change. Consumers' existing tests continue to pass.

View File

@@ -0,0 +1,58 @@
# 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)

View File

@@ -0,0 +1,47 @@
# Tasks — testing-e2e: async-span + apiContext fixture hardening
## 1. Spec authoring
- [x] 1.1 Read `openspec/specs/testing-e2e/spec.md`
- [x] 1.2 Draft `proposal.md`
- [x] 1.3 Draft `specs/testing-e2e/spec.md` delta
- [x] 1.4 Every requirement carries a `**Verified by:**` line
## 2. Implementation
- [x] 2.1 Drop `{ browser: _browser }` destructure in `apiContext` fixture
- [x] 2.2 Add async-aware span lifecycle in `src/observability/instrumented.ts`
- [x] 2.3 Update spec coverage gate to 100% and fix package path reference
## 3. Tests
- [x] 3.1 `src/fixtures/extended-test.test.ts` — source-level guard added
- [x] 3.2 `src/observability/instrumented.test.ts` — added 3 async-span tests
- [x] 3.3 Every `it(...)` carries `{ timeout: 5000 }`
## 4. Documentation
- [x] 4.1 Sibling `.md` updated for `extended-test.md`, `instrumented.md`
## 5. Pre-archive verification (the 5-gate triple)
- [ ] 5.1 `bun run check` — Biome 0/0/0
- [ ] 5.2 `bunx turbo lint typecheck test --filter=@suaveplan/testing-e2e` — green
- [ ] 5.3 `bunx turbo test:coverage --filter=@suaveplan/testing-e2e` — 100/100/100/100
- [ ] 5.4 `bun scripts/openspec-conformance.ts @suaveplan/testing-e2e` — exit 0
- [ ] 5.5 `bunx turbo build --filter=@suaveplan/testing-e2e` — green
## 6. Pre-archive 7-row checklist
- [x] 6.1 Generics threaded
- [x] 6.2 No new boundary inputs
- [x] 6.3 No new error codes
- [x] 6.4 Strict-mode N/A
- [x] 6.5 OTel span lifetime now asserted via observable elapsed time
- [x] 6.6 N/A
- [x] 6.7 No retroactive tick
## 7. Archive
- [ ] 7.1 Fold spec delta into `openspec/specs/testing-e2e/spec.md`
- [ ] 7.2 Move folder → `openspec/archive/2026-04-27-testing-e2e-async-and-fixtures-hardening/`