128 lines
6.6 KiB
TypeScript
128 lines
6.6 KiB
TypeScript
import { expect, test, type Page, type Route } from "@playwright/test";
|
|
|
|
function deferred() {
|
|
let resolve!: () => void;
|
|
const promise = new Promise<void>((done) => { resolve = done; });
|
|
return { promise, resolve };
|
|
}
|
|
|
|
function response(campaignId = "campaign-a", revision = 1, versionId = `${campaignId}-version`, hasMore = false) {
|
|
const version = { id: versionId, campaign_id: campaignId, version_number: 1, edit_revision: revision, raw_json: {} };
|
|
return {
|
|
campaign: { id: campaignId, current_version_id: versionId },
|
|
versions: [version], current_version: version, summary: null,
|
|
deleted: [], watermark: `watermark-${campaignId}-${revision}`, has_more: hasMore, full: true
|
|
};
|
|
}
|
|
|
|
async function fixture(page: Page) {
|
|
const requests: URL[] = [];
|
|
const state = {
|
|
handler: async (route: Route) => {
|
|
const url = new URL(route.request().url());
|
|
const campaignId = url.pathname.split("/")[4];
|
|
await route.fulfill({ json: response(campaignId, 1, url.searchParams.get("version_id") ?? undefined) });
|
|
}
|
|
};
|
|
await page.route((url) => url.pathname.startsWith("/api/"), async (route) => {
|
|
const url = new URL(route.request().url());
|
|
if (url.pathname.endsWith("/workspace/delta")) {
|
|
requests.push(url);
|
|
await state.handler(route);
|
|
} else await route.fulfill({ json: {} });
|
|
});
|
|
await page.goto("/?campaign-workspace");
|
|
await expect(page.getByTestId("workspace-version")).toHaveText("campaign-a-version");
|
|
await expect(page.getByTestId("workspace-loading")).toHaveText("false");
|
|
requests.length = 0;
|
|
return { state, requests };
|
|
}
|
|
|
|
test("failed refresh retains saved workspace and retries without a stale watermark", async ({ page }) => {
|
|
const { state, requests } = await fixture(page);
|
|
state.handler = async (route) => { await route.fulfill({ status: 503, json: { detail: "Refresh unavailable" } }); };
|
|
await page.getByRole("button", { name: "Force reload workspace", exact: true }).click();
|
|
await expect(page.getByTestId("workspace-error")).toContainText("Refresh unavailable");
|
|
await expect(page.getByTestId("workspace-loading")).toHaveText("false");
|
|
await expect(page.getByTestId("workspace-version")).toHaveText("campaign-a-version");
|
|
state.handler = async (route) => { await route.fulfill({ json: response("campaign-a", 2) }); };
|
|
await page.getByRole("button", { name: "Reload workspace", exact: true }).click();
|
|
await expect(page.getByTestId("workspace-revision")).toHaveText("2");
|
|
expect(requests[requests.length - 1].searchParams.has("since")).toBe(false);
|
|
await expect(page.getByTestId("workspace-error")).toBeEmpty();
|
|
});
|
|
|
|
for (const staleFails of [false, true]) {
|
|
test(`newer reload wins over an older ${staleFails ? "failed" : "successful"} response`, async ({ page }) => {
|
|
const { state } = await fixture(page);
|
|
const pending = deferred(); const finished = deferred();
|
|
let requestCount = 0;
|
|
state.handler = async (route) => {
|
|
requestCount += 1;
|
|
if (requestCount === 1) {
|
|
await pending.promise;
|
|
await route.fulfill(staleFails ? { status: 503, json: { detail: "Obsolete error" } } : { json: response("campaign-a", 2) });
|
|
finished.resolve();
|
|
} else await route.fulfill({ json: response("campaign-a", 3) });
|
|
};
|
|
await page.getByRole("button", { name: "Reload workspace", exact: true }).click();
|
|
await expect.poll(() => requestCount).toBe(1);
|
|
await page.getByRole("button", { name: "Reload workspace", exact: true }).click();
|
|
await expect(page.getByTestId("workspace-revision")).toHaveText("3");
|
|
pending.resolve(); await finished.promise;
|
|
await page.evaluate(() => new Promise((done) => requestAnimationFrame(() => requestAnimationFrame(done))));
|
|
await expect(page.getByTestId("workspace-revision")).toHaveText("3");
|
|
await expect(page.getByTestId("workspace-loading")).toHaveText("false");
|
|
await expect(page.getByTestId("workspace-error")).toBeEmpty();
|
|
});
|
|
}
|
|
|
|
for (const change of ["campaign", "identity", "version"] as const) {
|
|
test(`changing ${change} discards old responses and cancels obsolete pagination`, async ({ page }) => {
|
|
const { state, requests } = await fixture(page);
|
|
const pending = deferred(); const finished = deferred();
|
|
let requestCount = 0;
|
|
state.handler = async (route) => {
|
|
requestCount += 1;
|
|
if (requestCount === 1) {
|
|
await pending.promise;
|
|
await route.fulfill({ json: response("campaign-a", 99, "obsolete-version", true) });
|
|
finished.resolve();
|
|
} else await route.fulfill({ json: response(change === "campaign" ? "campaign-b" : "campaign-a", 2, "fresh-version") });
|
|
};
|
|
await page.getByRole("button", { name: "Reload workspace", exact: true }).click();
|
|
await expect.poll(() => requestCount).toBe(1);
|
|
await page.getByRole("button", { name: { campaign: "Switch campaign", identity: "Switch identity", version: "Select version" }[change] }).click();
|
|
await expect(page.getByTestId("workspace-version")).toHaveText("fresh-version");
|
|
pending.resolve(); await finished.promise;
|
|
await page.evaluate(() => new Promise((done) => requestAnimationFrame(() => requestAnimationFrame(done))));
|
|
await expect(page.getByTestId("workspace-version")).toHaveText("fresh-version");
|
|
expect(requests).toHaveLength(2);
|
|
expect(requests[1].searchParams.has("since")).toBe(false);
|
|
});
|
|
}
|
|
|
|
test("failed initial load for a different campaign never reveals the old campaign", async ({ page }) => {
|
|
const { state } = await fixture(page);
|
|
state.handler = async (route) => { await route.fulfill({ status: 403, json: { detail: "Campaign unavailable" } }); };
|
|
await page.getByRole("button", { name: "Switch campaign" }).click();
|
|
await expect(page.getByTestId("workspace-error")).toContainText("Campaign unavailable");
|
|
await expect(page.getByTestId("workspace-campaign")).toHaveText("none");
|
|
await expect(page.getByTestId("workspace-version")).toHaveText("none");
|
|
});
|
|
|
|
test("a failed later page never publishes a partially refreshed workspace", async ({ page }) => {
|
|
const { state } = await fixture(page);
|
|
let requestCount = 0;
|
|
state.handler = async (route) => {
|
|
requestCount += 1;
|
|
await route.fulfill(requestCount === 1
|
|
? { json: response("campaign-a", 4, "partial-version", true) }
|
|
: { status: 503, json: { detail: "Second page unavailable" } });
|
|
};
|
|
await page.getByRole("button", { name: "Reload workspace", exact: true }).click();
|
|
await expect(page.getByTestId("workspace-error")).toContainText("Second page unavailable");
|
|
await expect(page.getByTestId("workspace-version")).toHaveText("campaign-a-version");
|
|
await expect(page.getByTestId("workspace-revision")).toHaveText("1");
|
|
});
|