mirror of
https://github.com/azaion/ui.git
synced 2026-06-21 23:41:10 +00:00
cdebfccada
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>
104 lines
4.1 KiB
TypeScript
104 lines
4.1 KiB
TypeScript
import { test, expect } from '@playwright/test'
|
|
|
|
// AZ-471 — e2e companion for FT-P-39 (manual bounding-box draw).
|
|
//
|
|
// The fast suite covers all 5 ACs in JSDOM with deterministic canvas
|
|
// instrumentation. This e2e companion is the FT-P-39 (manual draw) row
|
|
// only — task spec marks it `fast + e2e`. The other rows (FT-P-40/41/42/43)
|
|
// stay fast-only because Playwright's pointer event timing makes pixel-
|
|
// perfect anchor invariance harder to assert than the JSDOM spy already
|
|
// does.
|
|
//
|
|
// Discipline: black-box. We observe the DOM (canvas pixels via
|
|
// canvas.toDataURL) and the network (annotation save POST), never React
|
|
// internals. The drift documented in the fast suite (Ctrl+drag pan,
|
|
// Ctrl+wheel zoom-around-cursor, Ctrl+click multi-select) is NOT re-asserted
|
|
// here — those are state-machine drifts and the fast tests pin them.
|
|
|
|
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-471 — CanvasEditor manual draw (e2e companion)', () => {
|
|
test('FT-P-39 — manual bbox draw produces a save with one detection', async ({
|
|
page,
|
|
browserName,
|
|
}) => {
|
|
test.skip(
|
|
browserName !== 'chromium',
|
|
'Pointer event timing on Firefox makes draw assertions noisy; fast suite covers it',
|
|
)
|
|
test.setTimeout(30_000)
|
|
|
|
await login(page)
|
|
await page.goto('/annotations')
|
|
|
|
// Need a media item selected for the canvas to mount with a backing
|
|
// image. If the suite seed has no media, the test reports the gap
|
|
// explicitly rather than masking the contract.
|
|
const canvas = page.locator('canvas').first()
|
|
if (!(await canvas.isVisible({ timeout: 5000 }).catch(() => false))) {
|
|
test.skip(true, 'Suite seed has no media available for annotation')
|
|
}
|
|
|
|
// Capture annotation save POSTs so we can assert one detection lands
|
|
// on the wire after the user-driven draw.
|
|
const saves: Array<{ url: string; body: string | null }> = []
|
|
await page.route('**/api/annotations/annotations**', async (route) => {
|
|
const req = route.request()
|
|
if (req.method() === 'POST') {
|
|
saves.push({ url: req.url(), body: req.postData() })
|
|
}
|
|
await route.continue()
|
|
})
|
|
|
|
const box = await canvas.boundingBox()
|
|
expect(box, 'canvas must have a bounding box').not.toBeNull()
|
|
if (!box) return
|
|
|
|
// Draw a bbox spanning ~30 % → ~60 % of the canvas width / height. Use
|
|
// mouse.down + steps + mouse.up to drive a real drag — a single move
|
|
// wouldn't trigger the pointer-move handlers reliably.
|
|
const x1 = box.x + box.width * 0.30
|
|
const y1 = box.y + box.height * 0.30
|
|
const x2 = box.x + box.width * 0.60
|
|
const y2 = box.y + box.height * 0.60
|
|
|
|
await page.mouse.move(x1, y1)
|
|
await page.mouse.down()
|
|
await page.mouse.move(x2, y2, { steps: 12 })
|
|
await page.mouse.up()
|
|
|
|
// After the draw, look for a Save affordance and click it. CanvasEditor
|
|
// saves are gated by the page Save button (per AZ-460 e2e). If no Save
|
|
// button is visible, the SPA may auto-save — flush via a navigation tick
|
|
// and inspect the recorded POSTs.
|
|
const saveBtn = page.getByRole('button', { name: /^Save$/i }).first()
|
|
if (await saveBtn.isVisible({ timeout: 1000 }).catch(() => false)) {
|
|
await saveBtn.click().catch(() => null)
|
|
}
|
|
|
|
await page.waitForTimeout(750)
|
|
|
|
expect(saves.length, 'manual draw must produce at least one save POST').toBeGreaterThan(0)
|
|
const lastSave = saves[saves.length - 1]
|
|
expect(lastSave.url).toContain('/api/annotations/annotations')
|
|
if (lastSave.body) {
|
|
const parsed = JSON.parse(lastSave.body) as { detections?: unknown[] }
|
|
expect(Array.isArray(parsed.detections)).toBe(true)
|
|
expect((parsed.detections ?? []).length).toBeGreaterThan(0)
|
|
}
|
|
})
|
|
})
|