mirror of
https://github.com/azaion/ui.git
synced 2026-06-21 11:41:11 +00:00
[AZ-471] [AZ-473] [AZ-478] [AZ-479] Batch 7 - canvas/photo-mode/network/perf tests
ci/woodpecker/push/build-arm Pipeline was successful
ci/woodpecker/push/build-arm Pipeline was successful
- AZ-471 CanvasEditor draw + 8-handle resize PASS (FT-P-39 fast + e2e + FT-P-40 8 sub-tests). Three drifts pinned via it.fails(): Ctrl+click multi-select (FT-P-41), Ctrl+wheel zoom-around-cursor (FT-P-42), Ctrl+drag empty-canvas pan (FT-P-43) — all rooted in handleMouseDown's early Ctrl-gate and handleWheel's pan-not-adjusted bug. - AZ-473 PhotoMode 3 ACs all PASS in fast + e2e (FT-P-48 switch filter, FT-P-49 auto-select, FT-P-50 yoloId wire across modes P=0/20/40 — outbound classNum == classId + photoModeOffset). - AZ-478 fast 7 + e2e 2: AC-1 user-visible offline indicator, AC-2 tainted-canvas fallback, AC-3 SSE disconnect banner — all drift today (it.fails fast + test.fail e2e + control PASS for each). Service-worker negative check passes. - AZ-479 AC-1 (bundle <= 2 MB gzipped) promoted from on-demand perf script to per-commit static profile via new STC-PERF01 row + static_check_bundle_size in run-tests.sh. AC-2 (mission-planner exclusion) already covered by STC-S5. AC-3 FCP /flights <= 3 s median (chromium suite-e2e) and AC-4 30-min annotation soak (RUN_LONG_RUNNING=1, chromium) scaffolded as e2e tests. Code review: PASS (0 findings). Fast: 25/25 files, 150 passed / 13 skipped. Static: 25/25 PASS (incl. new STC-PERF01). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
# Test — Canvas Editor (Bounding-Box + Multi-Select + Zoom + Pan)
|
||||
|
||||
**Task**: AZ-471_test_canvas_bbox
|
||||
**Name**: CanvasEditor manual draw + 8-handle resize + Ctrl+click multi-select + Ctrl+wheel zoom + Ctrl+drag pan
|
||||
**Description**: Implement the 5 blackbox tests covering the core canvas interactions: draw bbox, resize via 8 handles, multi-select via Ctrl+click, zoom-around-cursor via Ctrl+wheel, pan via Ctrl+drag.
|
||||
**Complexity**: 5 points
|
||||
**Dependencies**: AZ-456_test_infrastructure
|
||||
**Component**: 06_annotations (CanvasEditor) (Blackbox Tests)
|
||||
**Tracker**: AZ-471
|
||||
**Epic**: AZ-455
|
||||
|
||||
## Problem
|
||||
|
||||
The canvas editor is the SPA's most-interactive surface; canvas-pixel coordinates introduce floating-point + DPR gotchas that have caused subtle off-by-pixel regressions. The 5 scenarios pin geometry, modifier-key semantics, and viewport transformation.
|
||||
|
||||
## Outcome
|
||||
|
||||
- 5 scenarios pass with deterministic numeric fixtures (no `getBoundingClientRect` flakiness across viewport sizes).
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
|
||||
| Scenario | Profile | Source file |
|
||||
|----------|---------|-------------|
|
||||
| FT-P-39 — manual bounding-box draw on `<CanvasEditor>` | fast + e2e | blackbox-tests.md |
|
||||
| FT-P-40 — 8-handle bbox resize | fast | blackbox-tests.md |
|
||||
| FT-P-41 — Ctrl+click multi-select on canvas | fast | blackbox-tests.md |
|
||||
| FT-P-42 — Ctrl+wheel zoom-around-cursor | fast | blackbox-tests.md |
|
||||
| FT-P-43 — Ctrl+drag pan on empty canvas | fast | blackbox-tests.md |
|
||||
|
||||
### Excluded
|
||||
|
||||
- Tile-split interaction (covered in 19_test_tile_split_zoom).
|
||||
- Annotation overlay membership (covered in 07_test_overlay_membership).
|
||||
- Save-on-change (covered in 05_test_annotations_endpoint).
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Manual draw geometry**
|
||||
Draw a bbox at `(x1,y1)→(x2,y2)`; resulting annotation carries the canonical canvas-coordinate quad. Floating-point compare within ±0.5 px tolerance.
|
||||
|
||||
**AC-2: 8-handle resize**
|
||||
Drag each of the 8 handles independently; assert resulting bbox geometry per handle. Each handle's anchor (opposite corner / edge midpoint) is invariant during the drag.
|
||||
|
||||
**AC-3: Ctrl+click multi-select**
|
||||
Ctrl+click on a second bbox adds it to the selection; selection set contains both bboxes (asserted via DOM rendering — selection ring style).
|
||||
|
||||
**AC-4: Zoom-around-cursor**
|
||||
Ctrl+wheel at cursor `(cx, cy)`: the canvas pixel under `(cx, cy)` BEFORE the wheel equals the canvas pixel under `(cx, cy)` AFTER (within ±0.5 px).
|
||||
|
||||
**AC-5: Empty-canvas pan**
|
||||
Ctrl+drag on an empty canvas region: viewport offset shifts by `(dx, dy)`; bbox positions in canvas coords are invariant.
|
||||
|
||||
## System Under Test Boundary
|
||||
|
||||
- System under test: `<CanvasEditor>` + its pointer/mouse handlers + the canvas-coordinate ↔ viewport transform.
|
||||
- Allowed stubs: MSW for annotation load.
|
||||
- Disallowed: stubbing the canvas component or asserting on React state. Pointer events are dispatched via RTL `user-event` (or Playwright `dispatchEvent` for e2e).
|
||||
- Expected observables per `results_report.md` rows 73-78 (Group 16).
|
||||
|
||||
## Constraints
|
||||
|
||||
- Use fixed-size canvas (640×480) so coordinate math is deterministic.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1 — DPR + retina display flakiness**
|
||||
- *Risk*: Test runner on a retina display reports different physical pixels than the e2e Docker container.
|
||||
- *Mitigation*: Playwright config forces `deviceScaleFactor: 1`; Vitest+jsdom defaults to DPR 1.
|
||||
@@ -0,0 +1,54 @@
|
||||
# Test — PhotoMode Switch & yoloId Wire
|
||||
|
||||
**Task**: AZ-473_test_photo_mode
|
||||
**Name**: PhotoMode switch + auto-select + yoloId offsetting on the wire
|
||||
**Description**: Implement the 3 blackbox tests pinning PhotoMode behavior: switching modes sets the offset filter, auto-selecting when the prior class is no longer valid, and the on-wire `classNum == classId + photoModeOffset` assertion.
|
||||
**Complexity**: 2 points
|
||||
**Dependencies**: AZ-456_test_infrastructure
|
||||
**Component**: 06_annotations (PhotoModeContext + AnnotationsPage) (Blackbox Tests)
|
||||
**Tracker**: AZ-473
|
||||
**Epic**: AZ-455
|
||||
|
||||
## Problem
|
||||
|
||||
PhotoMode offsets `classId` to `classNum` on the wire (`classNum == classId + photoModeOffset`). Getting this wrong leaks a different class on every save without any user-visible symptom — until a downstream consumer mis-buckets the data.
|
||||
|
||||
## Outcome
|
||||
|
||||
- 3 scenarios pass — mode switch, auto-select on invalid, wire offset arithmetic.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
|
||||
| Scenario | Profile | Source file |
|
||||
|----------|---------|-------------|
|
||||
| FT-P-48 — PhotoMode switch — mode set + filter | fast | blackbox-tests.md |
|
||||
| FT-P-49 — PhotoMode auto-select when prior class no longer valid | fast | blackbox-tests.md |
|
||||
| FT-P-50 — yoloId on the wire — `classNum == classId + photoModeOffset` | fast + e2e | blackbox-tests.md |
|
||||
|
||||
### Excluded
|
||||
|
||||
- DetectionClasses load + hotkeys (covered in 17_test_detection_classes).
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Switch sets filter**
|
||||
FT-P-48 — toggling PhotoMode updates the rendered class list (filter applied); the selected mode is persisted in `<PhotoModeContext>` (asserted via the rendered filter, not via context read).
|
||||
|
||||
**AC-2: Auto-select**
|
||||
FT-P-49 — switching to a mode where the currently-selected class is out-of-range auto-selects the first valid class in the new window.
|
||||
|
||||
**AC-3: Wire offset**
|
||||
FT-P-50 — issue an annotation save in mode P; outbound body carries `classNum == classId + P` for every detection.
|
||||
|
||||
## System Under Test Boundary
|
||||
|
||||
- System under test: `<PhotoModeContext>` + `<AnnotationsPage>` save call.
|
||||
- Allowed stubs: MSW for `/api/annotations/classes` + annotation save.
|
||||
- Disallowed: reading `<PhotoModeContext>` state directly.
|
||||
- Expected observables per `results_report.md` rows 77-80 region.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Tests exercise all three modes (P ∈ {0, 20, 40}); each saves a probe annotation and asserts the wire offset.
|
||||
@@ -0,0 +1,56 @@
|
||||
# Test — Network Resilience (Offline, SSE Disconnect, Tainted-Canvas)
|
||||
|
||||
**Task**: AZ-478_test_network_resilience
|
||||
**Name**: Network offline at boot + SSE server disconnect indicator + tainted-canvas fallback
|
||||
**Description**: Implement the 3 blackbox tests covering network-loss paths: offline-at-boot error state (NO offline mode per E10), SSE server disconnect surfaces a connection-lost indicator, and annotation download falls back gracefully on a tainted canvas.
|
||||
**Complexity**: 3 points
|
||||
**Dependencies**: AZ-456_test_infrastructure
|
||||
**Component**: 01_api-transport + 06_annotations (Blackbox Tests)
|
||||
**Tracker**: AZ-478
|
||||
**Epic**: AZ-455
|
||||
|
||||
## Problem
|
||||
|
||||
The SPA explicitly rejects offline mode (E10 / NFT-SEC-12) but must still degrade gracefully when the network drops mid-session. The two streams in scope are the SSE streams (must surface a connection-lost banner / indicator) and the annotation-download path (must avoid `SecurityError: tainted canvas` crashes).
|
||||
|
||||
## Outcome
|
||||
|
||||
- 3 scenarios pass.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
|
||||
| Scenario | Profile | Source file | results_report row |
|
||||
|----------|---------|-------------|--------------------|
|
||||
| NFT-RES-03 — Network offline at boot — error state, no offline mode | fast + e2e | resilience-tests.md | per NFT-RES-03 |
|
||||
| NFT-RES-09 — Annotation download tainted-canvas fallback | fast | resilience-tests.md | 96 |
|
||||
| NFT-RES-10 — SSE server disconnect — UI surfaces a connection-lost indicator | fast + e2e | resilience-tests.md | 97 |
|
||||
|
||||
### Excluded
|
||||
|
||||
- 401 → refresh → retry network path (covered in 02_test_auth_token_handling).
|
||||
- Settings save resilience (covered in 22_test_settings_resilience).
|
||||
- Upload 413 path (covered in 21_test_upload_size_cap).
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Offline at boot**
|
||||
NFT-RES-03 — with all `/api/*` requests returning network errors, the SPA renders an error state and does NOT register a service worker / offline cache (defence in depth — NFT-SEC-12 catches the SW separately).
|
||||
|
||||
**AC-2: Tainted-canvas fallback**
|
||||
NFT-RES-09 — annotation download via canvas-to-data-URL on a tainted canvas does not crash the page; a user-visible fallback (alternative download path or in-DOM error) is rendered. Asserted per `results_report.md` row 96.
|
||||
|
||||
**AC-3: Disconnect indicator**
|
||||
NFT-RES-10 — when an SSE EventSource fires `error` with `readyState === 2` (CLOSED), within 2 s a connection-lost indicator is visible in the DOM with the i18n-keyed text. Asserted per `results_report.md` row 97.
|
||||
|
||||
## System Under Test Boundary
|
||||
|
||||
- System under test: `<App>` boot path, SSE consumers (`<FlightsPage>`, `<AnnotationsPage>`), annotation download handler in `<AnnotationsPage>`.
|
||||
- Allowed stubs: MSW for `/api/*` (fast); for e2e, suite `flights/` / `annotations/` services are killed mid-session to drive the disconnect path.
|
||||
- Disallowed: stubbing the SPA components.
|
||||
- Tainted canvas is induced in fast by drawing an `<img crossOrigin="anonymous">` against a stub that intentionally omits CORS headers.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Service-worker registration assertion: `navigator.serviceWorker.getRegistrations()` returns `[]` at all times (defence in depth, also enforced by NFT-SEC-12).
|
||||
@@ -0,0 +1,65 @@
|
||||
# Test — Bundle Size, FCP, & Annotation Session Memory Soak
|
||||
|
||||
**Task**: AZ-479_test_bundle_fcp_soak
|
||||
**Name**: Bundle ≤ 2 MB gzipped + mission-planner excluded + FCP ≤ 3 s + 30-min annotation soak
|
||||
**Description**: Implement the 4 blackbox tests covering the production-bundle budget, the mission-planner exclusion canary, the First-Contentful-Paint budget on `/flights`, and the 30-minute annotation-session memory soak.
|
||||
**Complexity**: 3 points
|
||||
**Dependencies**: AZ-456_test_infrastructure
|
||||
**Component**: 00_foundation (build) + 10_app-shell (FCP) + 06_annotations (memory) (Blackbox Tests)
|
||||
**Tracker**: AZ-479
|
||||
**Epic**: AZ-455
|
||||
|
||||
## Problem
|
||||
|
||||
The 2 MB gzipped budget (NFT-PERF-01 / NFT-RES-LIM-01) bounds first-load on edge hardware. The mission-planner workspace is intentionally excluded from the prod bundle (NFT-RES-LIM-04) — a regression that re-includes it doubles the bundle. FCP ≤ 3 s on `/flights` (NFT-PERF-10) is the user-perceived latency target. The 30-minute annotation soak (NFT-RES-LIM-05) catches slow heap creep that 1-hour live-GPS soak (T08) does not catch on the annotation surface.
|
||||
|
||||
## Outcome
|
||||
|
||||
- 4 scenarios pass.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
|
||||
| Scenario | Profile | Source file | results_report row |
|
||||
|----------|---------|-------------|--------------------|
|
||||
| NFT-PERF-01 — Initial JS bundle ≤ 2 MB gzipped | static | performance-tests.md | per NFT-PERF-01 |
|
||||
| NFT-RES-LIM-04 — `mission-planner/` is excluded from production bundle | static | resource-limit-tests.md | per NFT-RES-LIM-04 |
|
||||
| NFT-PERF-10 — FCP on `/flights` ≤ 3 s | e2e | performance-tests.md | 98 |
|
||||
| NFT-RES-LIM-05 — SPA memory stable across 30-minute annotation session | e2e (long-running) | resource-limit-tests.md | per NFT-RES-LIM-05 |
|
||||
|
||||
### Excluded
|
||||
|
||||
- Per-route lazy-chunk budgets (out of scope for the baseline).
|
||||
- Live-GPS 1-hour soak + 100-flight-selection soak (covered in 08_test_flight_selection_persistence).
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
**AC-1: Bundle budget**
|
||||
NFT-PERF-01 / NFT-RES-LIM-01 — `bun run build` produces `dist/`; sum of gzipped JS in initial chunks ≤ 2 MB. Static check runs after build.
|
||||
|
||||
**AC-2: Mission-planner exclusion**
|
||||
NFT-RES-LIM-04 — no chunk in `dist/` references `mission-planner/` source; ripgrep static check returns no hits in built JS.
|
||||
|
||||
**AC-3: FCP budget**
|
||||
NFT-PERF-10 — Playwright `performance.getEntriesByName('first-contentful-paint')` on `/flights` reports ≤ 3000 ms median over 5 runs on the configured mid-range CPU throttle (4x slowdown per the test env).
|
||||
|
||||
**AC-4: Annotation memory soak**
|
||||
NFT-RES-LIM-05 — 30-minute annotation session: load 50 media items, annotate each, navigate dataset; heap at t=1800 s within 10% of heap at t=60 s.
|
||||
|
||||
## System Under Test Boundary
|
||||
|
||||
- System under test: built `dist/` artefacts (AC-1 / AC-2); the running SPA in Chromium (AC-3 / AC-4).
|
||||
- Allowed stubs: MSW / suite services as needed for the e2e tests.
|
||||
- Disallowed: hand-tuning the bundle to "fit" or excluding files from build for the test.
|
||||
|
||||
## Constraints
|
||||
|
||||
- AC-3 / AC-4 require Chromium-only (Firefox lacks `performance.memory`); soak tagged `@long-running`.
|
||||
- Bundle measurement uses the gzip size, not the raw bytes — matches NFT-PERF-01 contract.
|
||||
|
||||
## Risks & Mitigation
|
||||
|
||||
**Risk 1 — FCP flakiness from cold-cache state**
|
||||
- *Risk*: First navigation to `/flights` includes auth handshake and SSE setup, inflating FCP.
|
||||
- *Mitigation*: the test issues a warmup navigation, then measures FCP on the second navigation; warmup is recorded in the CSV but does not gate.
|
||||
Reference in New Issue
Block a user