Skip to content

Commit abdf8fb

Browse files
fix(e2e): make Playwright tests pass — route prefix + test routing (#22)
Two related fixes that turn the failing CI green for the first time since /app/* routing was introduced in PR #14: 1. src/layout/AppShell.tsx — routeIdToKey() now strips the /app prefix before looking up PAGE_META. Before this change, every authenticated page rendered an empty <h1> (the meta.title lookup missed because the keys are short, e.g. '/resources', but the actual pathname after PR #14 is '/app/resources'). The empty h1 was visible in the trace from github.com/InstaNode-dev/instanode-web/actions/runs/25675342041 — heading[level=1] had no text content even though the rest of the page rendered correctly. 2. e2e/auth.spec.ts + e2e/navigation.spec.ts — updated the tests to navigate to /app instead of / for authenticated routes. The old tests assumed the dashboard lived at /, but that's the public marketing page now. Updated 4 test cases: - "unauthenticated user redirected to /login": go to /app - "login accepts valid token, lands on overview": expect /app on success - "signed-in user lands on overview directly": go to /app, expect Overview h1 - "every nav link reaches its page": all targets get the /app prefix, plus go to /app instead of / first - "OAuth buttons": dropped Google assertion (UI exposes GitHub + magic-link only today; Google OAuth route exists on the backend but no UI button has been added yet) Result locally: 10/10 Playwright tests pass. The CI failure that started on PR #11 (slice B, the first PR after /app/* routing landed) is gone. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent dd4d67f commit abdf8fb

3 files changed

Lines changed: 37 additions & 19 deletions

File tree

e2e/auth.spec.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ import { expect, test } from '@playwright/test'
22
import { installAPIFake, signIn } from './fixtures'
33

44
test.describe('Auth gate', () => {
5+
// The authenticated app is mounted under /app/* (see App.tsx); `/` is
6+
// the public marketing page and is reachable without a session. So we
7+
// poke a protected route to exercise the AuthGate redirect.
58
test('unauthenticated user is redirected to /login', async ({ page }) => {
6-
await page.goto('/')
9+
await page.goto('/app')
710
await expect(page).toHaveURL(/\/login$/)
811
await expect(page.getByRole('heading', { name: /Sign in/i })).toBeVisible()
912
})
@@ -31,21 +34,26 @@ test.describe('Auth gate', () => {
3134
await page.getByTestId('toggle-token-form').click()
3235
await page.getByTestId('token-input').fill('ink_VALID')
3336
await page.getByTestId('login-submit').click()
34-
await expect(page).toHaveURL(/\/$/)
37+
// LoginPage navigates to /app on success (see LoginPage.tsx).
38+
await expect(page).toHaveURL(/\/app\/?$/)
3539
})
3640

3741
test('OAuth buttons redirect to backend handlers', async ({ page }) => {
42+
// LoginPage currently exposes GitHub OAuth + email magic-link only.
43+
// Google OAuth is wired on the backend (POST /auth/google) but not
44+
// surfaced in the UI yet — add the button + a test assertion below
45+
// when it ships.
3846
await page.goto('/login')
3947
await expect(page.getByTestId('oauth-github')).toBeVisible()
40-
await expect(page.getByTestId('oauth-google')).toBeVisible()
4148
})
4249

4350
test('signed-in user lands on overview directly', async ({ page }) => {
4451
await signIn(page)
4552
await installAPIFake(page)
46-
await page.goto('/')
47-
await expect(page).toHaveURL(/\/$/)
48-
// The new design's h1 is "Overview." with a period; flexible match.
53+
// `/` is the public marketing page; the authenticated overview lives
54+
// at /app. Navigate there directly to exercise the post-sign-in landing.
55+
await page.goto('/app')
56+
await expect(page).toHaveURL(/\/app\/?$/)
4957
await expect(page.getByRole('heading', { level: 1, name: /Overview/ })).toBeVisible()
5058
})
5159
})

e2e/navigation.spec.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@ test.describe('Navigation', () => {
88
})
99

1010
test('every nav link reaches its page', async ({ page }) => {
11-
await page.goto('/')
11+
// The authenticated app is mounted under /app/*; the sidebar nav links
12+
// point to /app/<section>. The Overview is just /app (or /app/).
13+
await page.goto('/app')
1214
const targets: { name: RegExp; pathFragment?: string }[] = [
13-
{ name: /Resources/i, pathFragment: '/resources' },
14-
{ name: /Deployments/i, pathFragment: '/deployments' },
15-
{ name: /Stacks/i, pathFragment: '/stacks' },
16-
{ name: /Vault/i, pathFragment: '/vault' },
17-
{ name: /Team/i, pathFragment: '/team' },
18-
{ name: /Billing/i, pathFragment: '/billing' },
19-
{ name: /Settings/i, pathFragment: '/settings' },
20-
{ name: /Overview/i, pathFragment: '/' },
15+
{ name: /Resources/i, pathFragment: '/app/resources' },
16+
{ name: /Deployments/i, pathFragment: '/app/deployments' },
17+
{ name: /Stacks/i, pathFragment: '/app/stacks' },
18+
{ name: /Vault/i, pathFragment: '/app/vault' },
19+
{ name: /Team/i, pathFragment: '/app/team' },
20+
{ name: /Billing/i, pathFragment: '/app/billing' },
21+
{ name: /Settings/i, pathFragment: '/app/settings' },
22+
{ name: /Overview/i, pathFragment: '/app' },
2123
]
2224
for (const t of targets) {
2325
// Use the sidebar link by accessible name. The new design renders nav

src/layout/AppShell.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -284,12 +284,20 @@ export function AppShell() {
284284
)
285285
}
286286

287-
// react-router gives us route-id strings — coerce to our PAGE_META key
287+
// react-router gives us route-id strings — coerce to our PAGE_META key.
288+
//
289+
// The authenticated app is mounted under `/app/*` (see App.tsx), but PAGE_META
290+
// keys are short (`/resources`, `/deployments/:id`, …) because they predate
291+
// the /app prefix. Strip the prefix here so the lookup hits — otherwise
292+
// every page renders an empty <h1> because getMeta() falls through to its
293+
// default. The /app -> root mapping ('/app' becomes '/') matches the
294+
// Overview page entry. (PR-fixed long-standing CI failure 2026-05-11.)
288295
function routeIdToKey(_id: string, pathname: string): string {
296+
const stripped = pathname.replace(/^\/app/, '') || '/'
289297
// detail routes
290-
if (/^\/resources\/[^/]+$/.test(pathname)) return '/resources/:id'
291-
if (/^\/deployments\/[^/]+$/.test(pathname)) return '/deployments/:id'
292-
return pathname
298+
if (/^\/resources\/[^/]+$/.test(stripped)) return '/resources/:id'
299+
if (/^\/deployments\/[^/]+$/.test(stripped)) return '/deployments/:id'
300+
return stripped
293301
}
294302

295303
// ──────────────────────────────────────────────────────────────────────────

0 commit comments

Comments
 (0)