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>
102 lines
3.6 KiB
TypeScript
102 lines
3.6 KiB
TypeScript
import { test, expect, type Page } from './fixtures';
|
|
|
|
// Network-level mocks for the private-mode UX. The backend integration
|
|
// tests (api_private_mode.rs) cover the actual gate; here we only
|
|
// verify that the SvelteKit universal load redirects anonymous
|
|
// visitors to /login and then back to where they were going.
|
|
|
|
const userFixture = {
|
|
id: 'user-1',
|
|
username: 'alice',
|
|
created_at: '2026-01-01T00:00:00Z',
|
|
is_admin: false
|
|
};
|
|
const emptyPage = { items: [], page: { limit: 50, offset: 0, total: null } };
|
|
|
|
async function stubPrivateInstance(page: Page) {
|
|
let loggedIn = false;
|
|
|
|
// The flag that flips the gate on. Frontend reads it in
|
|
// `+layout.ts` to decide whether to redirect.
|
|
await page.route('**/api/v1/auth/config', async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({
|
|
self_register_enabled: false,
|
|
private_mode: true
|
|
})
|
|
});
|
|
});
|
|
|
|
await page.route('**/api/v1/auth/me', async (route) => {
|
|
if (loggedIn) {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ user: userFixture })
|
|
});
|
|
} else {
|
|
await route.fulfill({
|
|
status: 401,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({
|
|
error: { code: 'unauthenticated', message: 'unauthenticated' }
|
|
})
|
|
});
|
|
}
|
|
});
|
|
|
|
await page.route('**/api/v1/auth/login', async (route) => {
|
|
loggedIn = true;
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ user: userFixture })
|
|
});
|
|
});
|
|
|
|
// The real backend would 401 these too in private mode; we stub
|
|
// success so the post-login navigation can render the home page
|
|
// without an additional redirect cycle.
|
|
await page.route('**/api/v1/mangas*', async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify(emptyPage)
|
|
});
|
|
});
|
|
}
|
|
|
|
test('private mode: anonymous visit to / redirects to /login?next=%2F', async ({ page }) => {
|
|
await stubPrivateInstance(page);
|
|
await page.goto('/');
|
|
await expect(page).toHaveURL(/\/login\?next=%2F$/);
|
|
await expect(page.getByTestId('login-username')).toBeVisible();
|
|
});
|
|
|
|
test('private mode: register link is hidden', async ({ page }) => {
|
|
await stubPrivateInstance(page);
|
|
await page.goto('/login');
|
|
await expect(page.getByTestId('nav-login')).toBeVisible();
|
|
// self_register_enabled is the effective value (false in private
|
|
// mode regardless of ALLOW_SELF_REGISTER), so the navbar must
|
|
// never render the register affordance here.
|
|
await expect(page.getByTestId('nav-register')).toHaveCount(0);
|
|
});
|
|
|
|
test('private mode: after login the user lands back on the requested page', async ({ page }) => {
|
|
await stubPrivateInstance(page);
|
|
|
|
// Visit a deep link → bounced to /login with next= preserving it.
|
|
await page.goto('/');
|
|
await expect(page).toHaveURL(/\/login\?next=%2F$/);
|
|
|
|
await page.getByTestId('login-username').fill('alice');
|
|
await page.getByTestId('login-password').fill('hunter2hunter2');
|
|
await page.getByTestId('login-submit').click();
|
|
|
|
// Authenticated → can now reach the home page without bouncing.
|
|
await expect(page.getByTestId('session-user')).toContainText('alice');
|
|
});
|