mirror of
https://github.com/azaion/ui.git
synced 2026-06-21 18:01:12 +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,104 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
// AZ-478 — e2e companion for NFT-RES-03 (offline at boot) and
|
||||
// NFT-RES-10 (SSE disconnect indicator). Both are marked `fast + e2e` in
|
||||
// the task spec; NFT-RES-09 (tainted-canvas fallback) is fast-only and is
|
||||
// not duplicated here.
|
||||
//
|
||||
// Both tests are `test.fail()` today because the production drifts pinned
|
||||
// in `tests/network_resilience.test.tsx` are unfixed:
|
||||
//
|
||||
// - NFT-RES-03: SPA falls through to /login on offline boot rather than
|
||||
// rendering an explicit network-error indicator.
|
||||
// - NFT-RES-10: SSE consumers do NOT render a connection-lost indicator
|
||||
// when the EventSource fires error+CLOSED.
|
||||
//
|
||||
// Once the drifts land, remove the `test.fail` and these turn green.
|
||||
|
||||
const ALICE_EMAIL = 'op_alice@test.local'
|
||||
const ALICE_PASSWORD = 'TestPassword!23'
|
||||
|
||||
async function login(page: import('@playwright/test').Page): Promise<void> {
|
||||
await page.goto('/login')
|
||||
await page.getByLabel(/email/i).fill(ALICE_EMAIL)
|
||||
await page.getByLabel(/password/i).fill(ALICE_PASSWORD)
|
||||
await Promise.all([
|
||||
page.waitForResponse(
|
||||
(r) => r.url().includes('/api/admin/auth/login') && r.request().method() === 'POST',
|
||||
),
|
||||
page.getByRole('button', { name: /sign in/i }).click(),
|
||||
])
|
||||
}
|
||||
|
||||
test.describe('AZ-478 — network resilience (e2e companion)', () => {
|
||||
test.fail(
|
||||
'NFT-RES-03 — offline at boot: SPA renders an explicit network-error indicator',
|
||||
async ({ page }) => {
|
||||
test.setTimeout(20_000)
|
||||
|
||||
// Block ALL /api/* requests at the network layer to simulate a true
|
||||
// offline boot. The SPA boot path hits /api/admin/auth/refresh first;
|
||||
// every other downstream request also fails.
|
||||
await page.route('**/api/**', async (route) => {
|
||||
await route.abort('failed')
|
||||
})
|
||||
|
||||
await page.goto('/')
|
||||
|
||||
// Drift: the SPA redirects to /login silently. Spec NFT-RES-03 calls
|
||||
// for an in-DOM network-error indicator with the i18n-keyed text.
|
||||
// We look for either an explicit data-testid or a localized banner;
|
||||
// the assertion keeps both shapes acceptable so the fix can choose.
|
||||
const banner = page.locator('[data-testid="network-error-banner"]')
|
||||
const localizedText = page.getByText(/offline|network unavailable|connection lost/i)
|
||||
|
||||
await expect(banner.or(localizedText)).toBeVisible({ timeout: 5_000 })
|
||||
|
||||
// Defence in depth — service worker remains unregistered.
|
||||
const swCount = await page.evaluate(async () => {
|
||||
if (!('serviceWorker' in navigator)) return 0
|
||||
const regs = await navigator.serviceWorker.getRegistrations()
|
||||
return regs.length
|
||||
})
|
||||
expect(swCount).toBe(0)
|
||||
},
|
||||
)
|
||||
|
||||
test.fail(
|
||||
'NFT-RES-10 — SSE disconnect surfaces a connection-lost indicator within 2 s',
|
||||
async ({ page }) => {
|
||||
test.setTimeout(20_000)
|
||||
|
||||
await login(page)
|
||||
await page.goto('/flights')
|
||||
await page.getByRole('button', { name: /gps/i }).click()
|
||||
await page.getByRole('button', { name: /select flight/i }).click()
|
||||
await page.getByRole('button', { name: /flight-1|recon alpha/i }).first().click()
|
||||
|
||||
// Wait until the live-GPS SSE is observed, then abort all subsequent
|
||||
// event-stream requests to drive the server-disconnect path. This
|
||||
// mirrors the spec scenario: the SSE was healthy, then drops.
|
||||
await page.waitForRequest(
|
||||
(req) => /\/api\/flights\/[^/]+\/live-gps/.test(req.url()),
|
||||
{ timeout: 5_000 },
|
||||
)
|
||||
|
||||
await page.route('**/api/flights/**/live-gps**', async (route) => {
|
||||
await route.abort('failed')
|
||||
})
|
||||
|
||||
// Force a re-subscribe so the abort takes effect on a live channel.
|
||||
// Switching back to params then to GPS retriggers the effect.
|
||||
await page.getByRole('button', { name: /params/i }).click()
|
||||
await page.getByRole('button', { name: /gps/i }).click()
|
||||
|
||||
// Drift: the SPA today never renders a connection-lost indicator.
|
||||
// Spec NFT-RES-10 requires the indicator within 2 s, with i18n-keyed
|
||||
// text. Accept either a data-testid hook or the localized text.
|
||||
const banner = page.locator('[data-testid="sse-disconnect-banner"]')
|
||||
const localizedText = page.getByText(/connection lost|disconnected|reconnect/i)
|
||||
|
||||
await expect(banner.or(localizedText)).toBeVisible({ timeout: 2_000 })
|
||||
},
|
||||
)
|
||||
})
|
||||
Reference in New Issue
Block a user