Files
EpicNext-Cms/e2e/ui/furniture-jobs.spec.ts
Simo c977fe95ba
CI / check (push) Successful in 3m12s
CI / deploy (push) Successful in 19s
CI / publish-container (push) Successful in 1m12s
fix(studio): recover uncertain imports with persistent request identities
2026-09-13 18:33:06 +02:00

189 lines
6.0 KiB
TypeScript

import { expect, test } from "@playwright/test";
import {
furnitureCursorFixture,
furnitureJobFixture,
} from "../../src/test/furniture-jobs-fixtures";
test("history hook deduplicates refreshes, pages and reports completion once", async ({
page,
}) => {
let requests = 0;
let completed = false;
await page.route("**/api/admin/studio/import-jobs?*", async (route) => {
requests++;
const secondPage = new URL(route.request().url()).searchParams.has(
"before",
);
await new Promise((resolve) => setTimeout(resolve, 100));
await route.fulfill({
json: {
ok: true,
jobs: [
{
...furnitureJobFixture,
id: secondPage
? "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb"
: furnitureJobFixture.id,
state: completed ? "completed" : "queued",
},
],
nextCursor: secondPage ? null : furnitureCursorFixture,
},
});
});
await page.goto("/admin/jobs-harness");
await expect(page.getByLabel("Jobs", { exact: true })).toContainText(
furnitureJobFixture.id,
);
await expect(page.getByLabel("Loading", { exact: true })).toHaveText("false");
const initial = requests;
await page.getByRole("button", { name: "Refresh twice" }).click();
await expect(page.getByLabel("Loading", { exact: true })).toHaveText("true");
await expect(page.getByLabel("Loading", { exact: true })).toHaveText("false");
expect(requests).toBe(initial + 1);
await page.getByRole("button", { name: "Next page" }).click();
await expect(page.getByLabel("Page", { exact: true })).toHaveText("2");
await expect(page.getByLabel("Jobs", { exact: true })).toContainText(
"bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",
);
await page.getByRole("button", { name: "Previous page" }).click();
await expect(page.getByLabel("Page", { exact: true })).toHaveText("1");
await expect(page.getByLabel("Jobs", { exact: true })).toContainText(
furnitureJobFixture.id,
);
await expect(page.getByLabel("Loading", { exact: true })).toHaveText("false");
completed = true;
await page.getByRole("button", { name: "Refresh twice" }).click();
await expect(page.getByLabel("Loading", { exact: true })).toHaveText("true");
await expect(page.getByLabel("Completions", { exact: true })).toHaveText("1");
await expect(page.getByLabel("Loading", { exact: true })).toHaveText("false");
await page.getByRole("button", { name: "Refresh twice" }).click();
await expect(page.getByLabel("Loading", { exact: true })).toHaveText("true");
await expect(page.getByLabel("Loading", { exact: true })).toHaveText("false");
await expect(page.getByLabel("Completions", { exact: true })).toHaveText("1");
});
test("history polling pauses while hidden and refreshes on visibility return", async ({
page,
}) => {
let requests = 0;
await page.clock.install();
await page.route("**/api/admin/studio/import-jobs?*", (route) => {
requests++;
return route.fulfill({
json: { ok: true, jobs: [furnitureJobFixture], nextCursor: null },
});
});
await page.goto("/admin/jobs-harness");
await expect(page.getByLabel("Jobs", { exact: true })).toContainText(
furnitureJobFixture.id,
);
await expect(page.getByLabel("Loading", { exact: true })).toHaveText("false");
await page.evaluate(() => {
Object.defineProperty(document, "visibilityState", {
configurable: true,
get: () => "hidden",
});
document.dispatchEvent(new Event("visibilitychange"));
});
const hiddenCount = requests;
await page.clock.fastForward(60000);
expect(requests).toBe(hiddenCount);
await page.evaluate(() => {
Object.defineProperty(document, "visibilityState", {
configurable: true,
get: () => "visible",
});
document.dispatchEvent(new Event("visibilitychange"));
});
await expect.poll(() => requests).toBe(hiddenCount + 1);
});
test("history shows the current phase and validated recovery source", async ({
page,
}) => {
await page.route("**/api/admin/studio/import-jobs?*", (route) =>
route.fulfill({
json: {
ok: true,
jobs: [
{
...furnitureJobFixture,
state: "running",
items: [
{
...furnitureJobFixture.items[0],
state: "running",
phase: "writing_db",
phaseStartedAt: new Date().toISOString(),
recoveredSource: {
sourceId: "configured",
sourceName: "Configured source",
revision: 42,
},
},
],
},
],
nextCursor: null,
},
}),
);
await page.goto("/admin/jobs-harness?progress");
await page.locator("details details summary").click();
await expect(
page.getByText("Current phase: Saving SQL and catalog"),
).toBeVisible();
await expect(
page.getByText(/Bundle recovered from Configured source/),
).toBeVisible();
expect(
await page.evaluate(
() => document.documentElement.scrollWidth > innerWidth,
),
).toBe(false);
});
test("uncertain import submissions keep their identity after a page reload", async ({
page,
}) => {
const ids: string[] = [];
await page.route("**/api/admin/csrf", (route) =>
route.fulfill({ json: { token: "a".repeat(64) } }),
);
await page.route("**/api/admin/studio/import-jobs**", async (route) => {
if (route.request().method() !== "POST") {
await route.fulfill({ json: { ok: true, jobs: [], nextCursor: null } });
return;
}
const body = route.request().postDataJSON();
ids.push(body.id);
if (ids.length === 1) await route.abort("failed");
else
await route.fulfill({
json: { ok: true, job: { ...furnitureJobFixture, id: body.id } },
});
});
await page.goto("/admin/jobs-harness");
await page
.getByRole("button", { name: "Submit import", exact: true })
.click();
await expect(page.getByLabel("Submitted", { exact: true })).toHaveText(
"false",
);
await page.reload();
await page
.getByRole("button", { name: "Submit import", exact: true })
.click();
await expect(page.getByLabel("Submitted", { exact: true })).toHaveText(
"true",
);
expect(ids).toHaveLength(2);
expect(ids[1]).toBe(ids[0]);
await page
.getByRole("button", { name: "Submit import", exact: true })
.click();
await expect.poll(() => ids.length).toBe(3);
expect(ids[2]).not.toBe(ids[0]);
});