|
| 1 | +/* persona-bugs.spec.ts — coverage for the four P3 (Pro founder) persona |
| 2 | + * regressions found on 2026-05-13. |
| 3 | + * |
| 4 | + * Bug 1 — instanode.dev/app returned 404. The SPA shell now ships at |
| 5 | + * dist/app/index.html so GH Pages serves the entry path with |
| 6 | + * HTTP 200 and the React Router takes over. We verify the route |
| 7 | + * loads cleanly in dev (no 404, response is 200, AuthGate is |
| 8 | + * reachable). |
| 9 | + * |
| 10 | + * Bug 3 — /incidents link from /status was dead. The new IncidentsPage |
| 11 | + * renders an empty-state ("No active incidents") when the |
| 12 | + * /api/v1/incidents endpoint is missing. |
| 13 | + */ |
| 14 | + |
| 15 | +import { expect, test, type Route } from '@playwright/test' |
| 16 | + |
| 17 | +test.describe('P3 persona bug fixes', () => { |
| 18 | + // Bug 1: /app must not 404. |
| 19 | + test('GET /app responds 200 and mounts the SPA (no 404)', async ({ page }) => { |
| 20 | + // The Vite dev server returns the SPA shell for any route via the |
| 21 | + // middleware mode. A pre-fix regression would either 404 here (in |
| 22 | + // production this surfaced via GH Pages) or render an empty page |
| 23 | + // with no <div id="root"> mount. |
| 24 | + const response = await page.goto('/app') |
| 25 | + expect(response).not.toBeNull() |
| 26 | + // dev server may return 200 directly; in any case the SPA must mount. |
| 27 | + expect(response!.status()).toBeLessThan(400) |
| 28 | + |
| 29 | + // SPA root mount node must be present (the build pipeline now writes |
| 30 | + // this template to dist/app/index.html, so GH Pages serves it too). |
| 31 | + await expect(page.locator('#root')).toBeAttached() |
| 32 | + |
| 33 | + // With no auth token, AuthGate redirects to /login — that's fine, |
| 34 | + // it means the SPA booted and reacted to the route. The opposite |
| 35 | + // failure mode (regression) is a static 404 page with no router. |
| 36 | + await page.waitForURL(/\/(login|app).*$/) |
| 37 | + }) |
| 38 | + |
| 39 | + // Bug 3: /incidents must render (empty-state by default). |
| 40 | + test('GET /incidents renders "No active incidents" empty state', async ({ page }) => { |
| 41 | + // The page calls fetchIncidents() → GET /api/v1/incidents on mount. |
| 42 | + // That endpoint doesn't exist on the agent API yet; in MOCKED mode |
| 43 | + // (default in this repo) page.route lets us stub it. We return 404 |
| 44 | + // because that's what the live API does today, and the page should |
| 45 | + // tolerate it and render the empty state. |
| 46 | + await page.route('**/api/v1/incidents', (route: Route) => |
| 47 | + route.fulfill({ |
| 48 | + status: 404, |
| 49 | + contentType: 'application/json', |
| 50 | + body: JSON.stringify({ ok: false, error: 'not_found' }), |
| 51 | + }), |
| 52 | + ) |
| 53 | + |
| 54 | + const response = await page.goto('/incidents') |
| 55 | + expect(response).not.toBeNull() |
| 56 | + expect(response!.status()).toBeLessThan(400) |
| 57 | + |
| 58 | + // The page renders the empty-state — the literal "No active incidents" |
| 59 | + // headline plus a mailto for reporting. |
| 60 | + await expect(page.getByTestId('incidents-empty')).toBeVisible() |
| 61 | + await expect( |
| 62 | + page.getByTestId('incidents-empty').getByText(/no active incidents/i), |
| 63 | + ).toBeVisible() |
| 64 | + // Report-an-incident link in the empty state. |
| 65 | + await expect( |
| 66 | + page.getByRole('link', { name: /report an incident/i }).first(), |
| 67 | + ).toBeVisible() |
| 68 | + }) |
| 69 | + |
| 70 | + // Sanity check: /status footer link still points at /incidents. |
| 71 | + test('GET /status footer "Incident log" link targets /incidents', async ({ page }) => { |
| 72 | + await page.goto('/status') |
| 73 | + const link = page.getByRole('link', { name: /incident log/i }) |
| 74 | + await expect(link).toBeVisible() |
| 75 | + expect(await link.getAttribute('href')).toBe('/incidents') |
| 76 | + }) |
| 77 | +}) |
0 commit comments