From fc170ccf1008c4594623d89df8beedd3918ff755 Mon Sep 17 00:00:00 2001 From: Anders Olsson Date: Tue, 9 Jun 2026 11:37:20 +0200 Subject: [PATCH] ci: install Playwright chromium for the storybook vitest project; deterministic in-flight test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CI runs the @vitest/browser-playwright (storybook) project, which needs the chromium browser downloaded — add 'playwright install --with-deps chromium'. - object-new-page in-flight test held the create mutation open with a 50ms delay and raced the pending-state assertion (failed under CI timing); gate it on a promise released only after asserting 'saving…', so it's deterministic. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitea/workflows/ci.yaml | 1 + web/src/objects/object-new-page.test.tsx | 14 +++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 62a0779..208de4a 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -26,6 +26,7 @@ jobs: - run: pnpm install --frozen-lockfile - run: pnpm typecheck - run: pnpm lint + - run: pnpm exec playwright install --with-deps chromium - run: pnpm test - run: pnpm build - run: pnpm check:size diff --git a/web/src/objects/object-new-page.test.tsx b/web/src/objects/object-new-page.test.tsx index b682655..a07ee9e 100644 --- a/web/src/objects/object-new-page.test.tsx +++ b/web/src/objects/object-new-page.test.tsx @@ -1,7 +1,7 @@ import { expect, test } from "vitest"; import { screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; -import { delay, http, HttpResponse } from "msw"; +import { http, HttpResponse } from "msw"; import { Routes, Route } from "react-router-dom"; import { server } from "../test/server"; import { renderApp } from "../test/render"; @@ -72,11 +72,15 @@ test("partial create: fields PUT fails -> edit page shows the 'created' banner a test("in-flight submit: button disabled + shows Saving…, create fires exactly once on double-click", async () => { let postCount = 0; + let release!: () => void; + const gate = new Promise((resolve) => { + release = resolve; + }); server.use( http.post("/api/admin/objects", async () => { postCount += 1; - await delay(50); + await gate; return HttpResponse.json({ id: "new-id-3" }, { status: 201 }); }), ); @@ -91,9 +95,13 @@ test("in-flight submit: button disabled + shows Saving…, create fires exactly await userEvent.click(button); await userEvent.click(button); - await waitFor(() => expect(screen.getByText(/saving…/i)).toBeInTheDocument()); + // The mutation is held open by `gate`, so the pending state is observed + // deterministically (no reliance on a timing window). + expect(await screen.findByText(/saving…/i)).toBeInTheDocument(); expect(screen.getByRole("button", { name: /saving…/i })).toBeDisabled(); + release(); + await waitFor(() => expect(screen.getByText("detail view")).toBeInTheDocument()); expect(postCount).toBe(1); });