A full parallel run intermittently failed the first few tests: unmocked /api/v1 calls fell through to the dev proxy, whose backend isn't running under E2E, so each incurred a slow ECONNREFUSED round-trip + Vite error logging that piled up across 8 workers at cold start. - Add e2e/fixtures.ts: a shared `test` whose auto-use fixture installs an `**/api/v1/**` fallback (fast 503, logged) for any endpoint a spec didn't mock. Registered before the test body, so per-test routes still win; only genuine gaps land here. Turns silent mock-gap hangs into instant, attributable failures — and cuts the full run ~96s → ~25s. - Point all 22 specs at ./fixtures instead of @playwright/test. - retries: 1 in the config as a safety net for the residual Vite cold-compile timing (re-runs on the now-warm server). Two consecutive full runs: 110 passed, 0 flaky, no retries consumed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
68 lines
2.4 KiB
TypeScript
68 lines
2.4 KiB
TypeScript
import { test, expect, type Page } from './fixtures';
|
|
|
|
// Guards the title-on-nav behavior: without this, a stale title from
|
|
// the last manga / author page lingers when the user navigates to a
|
|
// generic page like /upload.
|
|
|
|
async function mockAnonymous(page: Page) {
|
|
await page.route('**/api/v1/auth/config', async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ self_register_enabled: true, private_mode: false })
|
|
});
|
|
});
|
|
await page.route('**/api/v1/auth/me', async (route) => {
|
|
await route.fulfill({
|
|
status: 401,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ error: { code: 'unauthenticated', message: 'unauthenticated' } })
|
|
});
|
|
});
|
|
await page.route('**/api/v1/mangas*', async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ items: [], page: { limit: 50, offset: 0, total: 0 } })
|
|
});
|
|
});
|
|
}
|
|
|
|
test('static route titles use the brand-first layout map', async ({ page }) => {
|
|
await mockAnonymous(page);
|
|
|
|
await page.goto('/');
|
|
await expect(page).toHaveTitle('Mangalord');
|
|
|
|
await page.goto('/upload');
|
|
await expect(page).toHaveTitle('Mangalord | Upload');
|
|
|
|
await page.goto('/login');
|
|
await expect(page).toHaveTitle('Mangalord | Login');
|
|
|
|
await page.goto('/bookmarks');
|
|
await expect(page).toHaveTitle('Mangalord | Bookmarks');
|
|
|
|
await page.goto('/collections');
|
|
await expect(page).toHaveTitle('Mangalord | Collections');
|
|
});
|
|
|
|
test('title updates when navigating away from a content page', async ({ page }) => {
|
|
await mockAnonymous(page);
|
|
|
|
// Pretend we just left a manga detail page — the document title
|
|
// would have been overridden to "Mangalord | Berserk". Use evaluate
|
|
// to set it synthetically so we can assert the regression cleanly
|
|
// even though the dynamic page itself isn't mocked here.
|
|
await page.goto('/');
|
|
await page.evaluate(() => {
|
|
document.title = 'Mangalord | Berserk';
|
|
});
|
|
expect(await page.title()).toBe('Mangalord | Berserk');
|
|
|
|
// Client-side nav to /upload — the root layout must reassert its
|
|
// mapped title or the stale "Berserk" lingers.
|
|
await page.goto('/upload');
|
|
await expect(page).toHaveTitle('Mangalord | Upload');
|
|
});
|