mirror of
https://github.com/azaion/ui.git
synced 2026-06-21 12:21:10 +00:00
bd2b718ddf
- AZ-463 flight selection persistence (FT-P-16) + rehydration
on boot (FT-P-17) PASS at the wire; 100-cycle leak guard
(NFT-RES-LIM-07) and 1h SSE soak (NFT-RES-LIM-06)
scaffolded as RUN_LONG_RUNNING-gated e2e companions.
- AZ-469 browser-support smoke (FT-P-34) runs in both
Chromium and Firefox via the existing playwright config;
responsive variants (FT-P-35 480px / FT-P-36 1024px) PASS
in fast (Tailwind class shape) and e2e (visibility).
- AZ-476 upload 501 MB -> 413: AC-1 user-visible error is
drift today (uploadFiles silently falls through to local
mode); it.fails() + control + e2e test.fail. AC-2 no-alert
PASS via dialog spy.
- AZ-477 settings save 500 / network drop: AC-1+AC-2+AC-3
all drift today (no try/finally, no error region, deadline
unmeasurable); 4 it.fails() + control pinning the stuck-
disabled drift; e2e companions test.fail mirror it.
- LESSONS.md seeded: vi.stubGlobal('URL', {...URL,...})
destroys the URL constructor and breaks new URL(...) in
MSW; patch the methods directly instead.
Code review: PASS (0 findings). Fast: 22/22 files, 120
passed / 13 skipped. Static: 24/24 PASS.
Co-authored-by: Cursor <cursoragent@cursor.com>
87 lines
3.8 KiB
TypeScript
87 lines
3.8 KiB
TypeScript
import { test, expect } from '@playwright/test'
|
|
|
|
// AZ-477 — e2e companion for Settings save resilience + 2 s deadline.
|
|
//
|
|
// AC-1 (FT-N-13 / NFT-RES-05): real backend returns 500 on settings PUT;
|
|
// SPA renders an error region AND clears the
|
|
// `saving` flag (button enabled again) within
|
|
// 2 s. Today both contracts are drift —
|
|
// `test.fail()` until try/finally + alert lands.
|
|
// AC-2 (FT-N-14 / NFT-RES-06): same on a network drop. The fast-profile
|
|
// test pins both contracts against MSW; this
|
|
// companion exercises the real wire boundary
|
|
// against the suite stack.
|
|
// AC-3 (NFT-PERF-09): ≤ 2 s deadline for error visibility — pinned
|
|
// in the fast suite via `performance.now()`;
|
|
// the e2e companion just asserts visibility
|
|
// within Playwright's 2 s timeout.
|
|
//
|
|
// Requires the suite docker-compose stack (`e2e/docker-compose.suite-e2e.yml`).
|
|
// Uses `page.route` to inject the failure mode without depending on a real
|
|
// crashed backend in CI.
|
|
|
|
test.describe('AZ-477 — Settings save resilience (e2e companion)', () => {
|
|
test.fail(
|
|
'AC-1 (500) — Save button re-enables AND error region visible within 2 s',
|
|
async ({ page }) => {
|
|
// Force the system-settings PUT to fail with a 500. Other endpoints
|
|
// pass through so the page mounts normally.
|
|
await page.route('**/api/annotations/settings/system', async (route) => {
|
|
if (route.request().method() === 'PUT') {
|
|
await route.fulfill({ status: 500, body: 'upstream failure' })
|
|
return
|
|
}
|
|
await route.continue()
|
|
})
|
|
|
|
await page.goto('/settings')
|
|
|
|
// Tenant Configuration heading + scoped Save button — same anchor as
|
|
// the fast suite. If the suite seed has no tenant config, the test
|
|
// reports the gap rather than masking the UI.
|
|
const tenantHeading = page.getByRole('heading', { name: /Tenant Configuration/i })
|
|
if (!(await tenantHeading.isVisible({ timeout: 5000 }).catch(() => false))) {
|
|
test.skip(true, 'Suite UI did not render Settings → Tenant Configuration')
|
|
}
|
|
const tenantPanel = tenantHeading.locator('xpath=..')
|
|
const saveBtn = tenantPanel.getByRole('button', { name: /^Save$/i })
|
|
|
|
await saveBtn.click()
|
|
|
|
// Both assertions race the 2 s deadline.
|
|
await expect(saveBtn).toBeEnabled({ timeout: 2000 })
|
|
const alertEl = page.getByRole('alert').first()
|
|
await expect(alertEl).toBeVisible({ timeout: 2000 })
|
|
await expect(alertEl).toContainText(/error|failed|try again|500/i)
|
|
},
|
|
)
|
|
|
|
test.fail(
|
|
'AC-2 (network drop) — Save button re-enables AND error region visible within 2 s',
|
|
async ({ page }) => {
|
|
await page.route('**/api/annotations/settings/system', async (route) => {
|
|
if (route.request().method() === 'PUT') {
|
|
await route.abort('connectionfailed')
|
|
return
|
|
}
|
|
await route.continue()
|
|
})
|
|
|
|
await page.goto('/settings')
|
|
|
|
const tenantHeading = page.getByRole('heading', { name: /Tenant Configuration/i })
|
|
if (!(await tenantHeading.isVisible({ timeout: 5000 }).catch(() => false))) {
|
|
test.skip(true, 'Suite UI did not render Settings → Tenant Configuration')
|
|
}
|
|
const tenantPanel = tenantHeading.locator('xpath=..')
|
|
const saveBtn = tenantPanel.getByRole('button', { name: /^Save$/i })
|
|
|
|
await saveBtn.click()
|
|
|
|
await expect(saveBtn).toBeEnabled({ timeout: 2000 })
|
|
const alertEl = page.getByRole('alert').first()
|
|
await expect(alertEl).toBeVisible({ timeout: 2000 })
|
|
},
|
|
)
|
|
})
|