Files
SnapOtter/tests/e2e/auth.setup.ts
T
SnapOtterandGitHub 7579634633 test: fix 2.0 integration CI -- 202 async fallback + timeout hardening
Fixes all integration CI failures on the 2.0 branch.

## What was broken

Two independent root causes:

1. **202 assertion failures** -- Under 4-fork CI parallel load, the 30s
   `SYNC_WAIT_MS` sync window can expire before a BullMQ worker finishes a
   heavy encode (avif, heic), returning a legitimate `202 {jobId, async: true}`
   instead of `200`. Tests that hard-asserted `200` were spuriously failing.

2. **Vitest timeout race** -- `SYNC_WAIT_MS` (30s) and the default Vitest
   `testTimeout` (also 30s) fired simultaneously. Vitest won the race,
   reporting "Test timed out in 30000ms" instead of the test receiving the
   202 response.

## Fixes

- Added `isAsyncFallback()` helper to four integration test files; validates
  the `{async: true, jobId}` body shape and returns early so the synchronous
  200 path runs full assertions only when warranted.
- Set `vi.setConfig({ testTimeout: 60_000 })` at module level in
  `image-enhancement.test.ts` and `format-matrix-comprehensive.test.ts`,
  giving a 30s buffer between when `waitForJob()` returns 202 and when
  Vitest gives up.
- Bumped explicit matrix timeouts in `format-matrix.test.ts` and
  `new-formats.test.ts` from 30s to 60s for the same reason.
- Installed missing CI doc-engine binaries (qpdf, pandoc, libreoffice,
  pdfcpu) that were causing unrelated integration failures.
- Fixed E2E smoke specs for 2.0 UI changes (modality selector, tool routes,
  validation behavior).
2026-06-19 18:15:20 +08:00

57 lines
2.5 KiB
TypeScript

import fs from "node:fs";
import path from "node:path";
import { test as setup } from "@playwright/test";
const authFile = path.join(process.cwd(), ".playwright", ".auth", "user.json");
setup("authenticate", async ({ page }) => {
// Ensure directory exists
const dir = path.dirname(authFile);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
await page.goto("/login");
await page.getByLabel("Username").fill("admin");
await page.getByLabel("Password").fill("admin");
await page.getByRole("button", { name: /login/i }).click();
// Wait for login to complete and grab the token in one step
const handle = await page.waitForFunction(() => localStorage.getItem("snapotter-token"), null, {
timeout: 15_000,
});
const token = await handle.jsonValue();
// Dismiss analytics consent via API so it won't block any test
const apiBase = process.env.API_URL || "http://localhost:13490";
await page.request.put(`${apiBase}/api/v1/user/analytics`, {
headers: { Authorization: `Bearer ${token}` },
data: { enabled: false },
});
// Now navigate to "/" - consent guard is satisfied
// Use waitUntil: "domcontentloaded" to avoid racing with client-side redirects
await page.goto("/", { waitUntil: "domcontentloaded" });
// Wait for the URL to settle (app may redirect through consent/auth guards)
await page.waitForURL((url) => url.pathname === "/", { timeout: 30_000 }).catch(() => {});
await page.waitForLoadState("load");
// Fail fast on a misconfigured/stale e2e server. A correctly-configured e2e
// API (SKIP_MUST_CHANGE_PASSWORD=true, fresh per-run DB) lands the admin on
// "/". If we end up on /change-password or /login instead, the server on
// :13490 is almost certainly a stale reused process (e.g. a leftover
// `pnpm dev` without the e2e env, or a server bound to a mutated DB) that
// playwright's `reuseExistingServer` picked up. Without this guard that state
// silently poisons every loggedInPage test with cascading change-password
// redirects, so surface it loudly with the fix.
const landedPath = new URL(page.url()).pathname;
if (landedPath !== "/") {
throw new Error(
`Auth setup landed on "${landedPath}" instead of "/". The e2e API on :13490 is likely a ` +
`stale/misconfigured server reused by playwright. Kill any process on the e2e ports and re-run:\n` +
` lsof -ti :13490 :2349 | xargs kill -9`,
);
}
// Save storage state (includes localStorage with the token)
await page.context().storageState({ path: authFile });
});