test(nightly): stabilize the exhaustive nightly suite (#345)

Triaged the nightly failures (all pre-existing, unrelated to the analytics
work) and fixed the ones with clear root causes:

- video-speed: a 1s tiny.mp4 sped up 2x rounds to ~0.75s, flaking the +/-25%
  duration assertion under heavy CI load. Use the 8s hero.mp4 (still 44.1kHz)
  so rounding is negligible. Verified locally.
- Extended Matrix + Coverage timeouts: full-matrix / coverage-instrumented runs
  starve the heavy media tests under 4 forks at the 30s default. Make maxForks
  env-overridable (VITEST_MAX_FORKS) and run those jobs with 2 forks + a 300s
  timeout so format-matrix conversions and qr-generate stop timing out.
- Device Matrix visual baselines: the update-visual-baselines workflow could
  not start the app ('failed to create database') because it never provisioned
  Postgres/Redis. Add the same services block the e2e jobs use.
- Docker E2E: a container pnpm install network blip exits 254. Add fetch
  retries + a longer network timeout (frozen-lockfile already passes locally).
- Cross-browser: the home page is the tool catalog now (no dropzone), and the
  tool routes moved to /<section>/<toolId>. Point the upload test at a real
  tool page and fix the stale single-segment routes (/resize -> /image/resize,
  etc.).

The flaky/timeout and cross-browser fixes can only be confirmed by the nightly
(they are load- and browser-specific); a fresh nightly run will verify.
This commit is contained in:
SnapOtter
2026-06-24 18:23:23 +08:00
committed by GitHub
parent 8f4235d2c6
commit 33dfcecd6a
6 changed files with 74 additions and 17 deletions
+18 -13
View File
@@ -37,13 +37,18 @@ test.describe("Cross-browser smoke tests", () => {
expect(errors).toHaveLength(0);
});
test("home page file upload: upload image, verify preview", async ({ loggedInPage: page }) => {
test("tool page file upload: upload image, verify it is accepted", async ({
loggedInPage: page,
}) => {
const errors = collectConsoleErrors(page);
// The home page is the tool catalog now; uploads happen on a tool page.
await page.goto("/image/resize");
await page.waitForLoadState("networkidle");
await uploadImage(page);
await expect(page.locator("[class*='text-green']").first()).toBeVisible();
// After a successful upload the tool's settings panel renders.
await expect(page.getByText("Settings").first()).toBeVisible();
expect(errors).toHaveLength(0);
});
@@ -51,7 +56,7 @@ test.describe("Cross-browser smoke tests", () => {
test("resize E2E: upload, set dimensions, process, download", async ({ loggedInPage: page }) => {
const errors = collectConsoleErrors(page);
await page.goto("/resize");
await page.goto("/image/resize");
await page.waitForLoadState("networkidle");
await uploadImage(page);
@@ -81,7 +86,7 @@ test.describe("Cross-browser smoke tests", () => {
}) => {
const errors = collectConsoleErrors(page);
await page.goto("/compress");
await page.goto("/image/compress");
await page.waitForLoadState("networkidle");
// Upload and wait for processing to produce the before-after view
@@ -278,7 +283,7 @@ test.describe("Cross-browser smoke tests", () => {
expect(bgColor).not.toBe("");
// Navigate to a tool page and verify CSS layout
await page.goto("/resize");
await page.goto("/image/resize");
await page.waitForLoadState("networkidle");
await page.waitForTimeout(500);
@@ -297,7 +302,7 @@ test.describe("Cross-browser smoke tests", () => {
}) => {
const errors = collectConsoleErrors(page);
await page.goto("/resize");
await page.goto("/image/resize");
await page.waitForLoadState("networkidle");
await uploadImage(page);
@@ -338,7 +343,7 @@ test.describe("Cross-browser smoke tests", () => {
test("drag-and-drop: drop image file onto dropzone", async ({ loggedInPage: page }) => {
const errors = collectConsoleErrors(page);
await page.goto("/resize");
await page.goto("/image/resize");
await page.waitForLoadState("networkidle");
await page.waitForTimeout(500);
@@ -363,7 +368,7 @@ test.describe("Cross-browser smoke tests", () => {
test("canvas interactions: crop tool draw and adjust region", async ({ loggedInPage: page }) => {
const errors = collectConsoleErrors(page);
await page.goto("/crop");
await page.goto("/image/crop");
await page.waitForLoadState("networkidle");
await uploadImage(page);
@@ -458,7 +463,7 @@ test.describe("Cross-browser smoke tests", () => {
test("convert E2E: upload, select format, process", async ({ loggedInPage: page }) => {
const errors = collectConsoleErrors(page);
await page.goto("/convert");
await page.goto("/image/convert");
await page.waitForLoadState("networkidle");
await uploadImage(page);
@@ -557,7 +562,7 @@ test.describe("Cross-browser smoke tests", () => {
test("qr-generate: enter text and verify preview renders", async ({ loggedInPage: page }) => {
const errors = collectConsoleErrors(page);
await page.goto("/qr-generate");
await page.goto("/image/qr-generate");
await page.waitForLoadState("networkidle");
await page.waitForTimeout(500);
@@ -593,7 +598,7 @@ test.describe("Cross-browser smoke tests", () => {
test("collage tool renders templates across browsers", async ({ loggedInPage: page }) => {
const errors = collectConsoleErrors(page);
await page.goto("/collage");
await page.goto("/image/collage");
await page.waitForLoadState("networkidle");
await page.waitForTimeout(500);
@@ -645,12 +650,12 @@ test.describe("Cross-browser smoke tests", () => {
const errors = collectConsoleErrors(page);
// Navigate to resize
await page.goto("/resize");
await page.goto("/image/resize");
await page.waitForLoadState("networkidle");
await page.waitForTimeout(300);
// Navigate to compress
await page.goto("/compress");
await page.goto("/image/compress");
await page.waitForLoadState("networkidle");
await page.waitForTimeout(300);
@@ -11,7 +11,10 @@ import {
type TestApp,
} from "../../test-server.js";
const MP4 = readFixture(fixtures.video.tiny("mp4"));
// An 8s clip (44.1kHz audio): long enough that the 2x-speed duration check is
// robust to ffmpeg's frame/packet rounding. The 1s tiny.mp4 rounded to ~0.75s
// for a 2x speed-up, which flaked the +/-25% assertion under heavy CI load.
const MP4 = readFixture(fixtures.video.hero.mp4);
let testApp: TestApp;
let adminToken: string;