test(web): run chart-links E2E serially to reuse warmed Plot module
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
This commit is contained in:
parent
7f75dfc2bc
commit
51b456409d
@ -3,89 +3,90 @@ import { captureScreenshot } from "./helpers";
|
||||
|
||||
const fixture = "/e2e/fixtures/chart-links.html";
|
||||
|
||||
test("chart links cannot execute script when clicked or expanded", async ({ page }, testInfo) => {
|
||||
test.setTimeout(120_000);
|
||||
// ChartCanvas lazy-loads Observable Plot; pay the import cost once before the matrix below.
|
||||
await page.goto(`${fixture}?${new URLSearchParams({ href: "#details", inspect: "0" })}`);
|
||||
await expect(page.locator("svg circle").first()).toBeVisible({ timeout: 90_000 });
|
||||
for (const inspect of ["0", "1"]) {
|
||||
for (const href of [
|
||||
'javascript:void(document.body.dataset.plotExecuted="yes")',
|
||||
' \u0000\u001fJaVa\tScRi\nPt:void(document.body.dataset.plotExecuted="yes")',
|
||||
'javascript:%76oid(document.body.dataset.plotExecuted="yes")',
|
||||
"data:text/html,<script>document.body.dataset.plotExecuted='yes'</script>",
|
||||
"file:///example.txt",
|
||||
]) {
|
||||
await page.goto(`${fixture}?${new URLSearchParams({ href, inspect })}`);
|
||||
const circle = page.locator("svg circle").first();
|
||||
// Every navigation renders Plot again, which takes seconds on a loaded CI runner.
|
||||
await expect(circle).toBeVisible({ timeout: 30_000 });
|
||||
const assertInert = async () => {
|
||||
const links = page.locator("svg a");
|
||||
expect(await links.count()).toBeGreaterThan(0);
|
||||
expect(
|
||||
await links.evaluateAll((anchors) =>
|
||||
anchors.every(
|
||||
(anchor) =>
|
||||
!anchor.hasAttribute("href") &&
|
||||
!anchor.hasAttributeNS("http://www.w3.org/1999/xlink", "href"),
|
||||
),
|
||||
),
|
||||
).toBe(true);
|
||||
await expect(page.locator("body")).not.toHaveAttribute("data-plot-executed");
|
||||
};
|
||||
await assertInert();
|
||||
await circle.click();
|
||||
// Hover inspection still works, without reintroducing unsafe links.
|
||||
if (inspect === "1") await expect(page.locator('[aria-label="tip"]')).toBeVisible();
|
||||
await assertInert();
|
||||
await page.getByRole("button", { name: "Expand", exact: true }).click();
|
||||
const dialog = page.getByRole("dialog");
|
||||
await expect(dialog.locator("svg circle")).toBeVisible({ timeout: 30_000 });
|
||||
await dialog.locator("svg circle").click();
|
||||
await assertInert();
|
||||
await expect(page).toHaveURL(`${fixture}?${new URLSearchParams({ href, inspect })}`);
|
||||
await page.getByRole("button", { name: "Close chart" }).click();
|
||||
}
|
||||
}
|
||||
await captureScreenshot(page, testInfo, "chart-with-inert-link");
|
||||
});
|
||||
|
||||
test("chart links retain supported URLs and browser encoding semantics", async ({ page }) => {
|
||||
test.setTimeout(240_000);
|
||||
await page.route("https://example.test/**", (route) =>
|
||||
route.fulfill({ body: "Chart destination" }),
|
||||
);
|
||||
await page.route("**/e2e/fixtures/java*", (route) =>
|
||||
route.fulfill({ body: "Chart destination" }),
|
||||
);
|
||||
// Each test gets a fresh browser context, so this one pays the cold Plot import too.
|
||||
await page.goto(`${fixture}?${new URLSearchParams({ href: "#details", inspect: "0" })}`);
|
||||
await expect(page.locator("svg circle").first()).toBeVisible({ timeout: 120_000 });
|
||||
for (const href of [
|
||||
"https://example.test/chart?a=1&b=2",
|
||||
"mailto:chart@example.test",
|
||||
"tel:+15555550123",
|
||||
"#details",
|
||||
"javascript:void(0)",
|
||||
"javascript%3Avoid(0)",
|
||||
"java%73cript:void(0)",
|
||||
]) {
|
||||
await page.goto(`${fixture}?${new URLSearchParams({ href })}`);
|
||||
test.describe.serial("chart links", () => {
|
||||
test("cannot execute script when clicked or expanded", async ({ page }, testInfo) => {
|
||||
test.setTimeout(120_000);
|
||||
// ChartCanvas lazy-loads Observable Plot; pay the import cost once before the matrix below.
|
||||
await page.goto(`${fixture}?${new URLSearchParams({ href: "#details", inspect: "0" })}`);
|
||||
await expect(page.locator("svg circle").first()).toBeVisible({ timeout: 90_000 });
|
||||
const link = page.locator("svg a").first();
|
||||
await expect(link).toBeAttached();
|
||||
expect(
|
||||
await link.evaluate((anchor) =>
|
||||
anchor.getAttributeNS("http://www.w3.org/1999/xlink", "href"),
|
||||
),
|
||||
).toBe(href);
|
||||
const destination = new URL(href, page.url());
|
||||
expect(destination.protocol).not.toBe("javascript:");
|
||||
if (destination.protocol === "http:" || destination.protocol === "https:") {
|
||||
await link.locator("circle").click();
|
||||
await expect(page).toHaveURL(destination.href);
|
||||
await expect(page.locator("body")).not.toHaveAttribute("data-plot-executed");
|
||||
for (const inspect of ["0", "1"]) {
|
||||
for (const href of [
|
||||
'javascript:void(document.body.dataset.plotExecuted="yes")',
|
||||
' \u0000\u001fJaVa\tScRi\nPt:void(document.body.dataset.plotExecuted="yes")',
|
||||
'javascript:%76oid(document.body.dataset.plotExecuted="yes")',
|
||||
"data:text/html,<script>document.body.dataset.plotExecuted='yes'</script>",
|
||||
"file:///example.txt",
|
||||
]) {
|
||||
await page.goto(`${fixture}?${new URLSearchParams({ href, inspect })}`);
|
||||
const circle = page.locator("svg circle").first();
|
||||
// Every navigation renders Plot again, which takes seconds on a loaded CI runner.
|
||||
await expect(circle).toBeVisible({ timeout: 30_000 });
|
||||
const assertInert = async () => {
|
||||
const links = page.locator("svg a");
|
||||
expect(await links.count()).toBeGreaterThan(0);
|
||||
expect(
|
||||
await links.evaluateAll((anchors) =>
|
||||
anchors.every(
|
||||
(anchor) =>
|
||||
!anchor.hasAttribute("href") &&
|
||||
!anchor.hasAttributeNS("http://www.w3.org/1999/xlink", "href"),
|
||||
),
|
||||
),
|
||||
).toBe(true);
|
||||
await expect(page.locator("body")).not.toHaveAttribute("data-plot-executed");
|
||||
};
|
||||
await assertInert();
|
||||
await circle.click();
|
||||
// Hover inspection still works, without reintroducing unsafe links.
|
||||
if (inspect === "1") await expect(page.locator('[aria-label="tip"]')).toBeVisible();
|
||||
await assertInert();
|
||||
await page.getByRole("button", { name: "Expand", exact: true }).click();
|
||||
const dialog = page.getByRole("dialog");
|
||||
await expect(dialog.locator("svg circle")).toBeVisible({ timeout: 30_000 });
|
||||
await dialog.locator("svg circle").click();
|
||||
await assertInert();
|
||||
await expect(page).toHaveURL(`${fixture}?${new URLSearchParams({ href, inspect })}`);
|
||||
await page.getByRole("button", { name: "Close chart" }).click();
|
||||
}
|
||||
}
|
||||
}
|
||||
await captureScreenshot(page, testInfo, "chart-with-inert-link");
|
||||
});
|
||||
|
||||
test("retain supported URLs and browser encoding semantics", async ({ page }) => {
|
||||
test.setTimeout(120_000);
|
||||
await page.route("https://example.test/**", (route) =>
|
||||
route.fulfill({ body: "Chart destination" }),
|
||||
);
|
||||
await page.route("**/e2e/fixtures/java*", (route) =>
|
||||
route.fulfill({ body: "Chart destination" }),
|
||||
);
|
||||
await page.goto(`${fixture}?${new URLSearchParams({ href: "#details" })}`);
|
||||
await expect(page.locator("svg circle").first()).toBeVisible({ timeout: 30_000 });
|
||||
for (const href of [
|
||||
"https://example.test/chart?a=1&b=2",
|
||||
"mailto:chart@example.test",
|
||||
"tel:+15555550123",
|
||||
"#details",
|
||||
"javascript:void(0)",
|
||||
"javascript%3Avoid(0)",
|
||||
"java%73cript:void(0)",
|
||||
]) {
|
||||
await page.goto(`${fixture}?${new URLSearchParams({ href })}`);
|
||||
await expect(page.locator("svg circle").first()).toBeVisible({ timeout: 30_000 });
|
||||
const link = page.locator("svg a").first();
|
||||
await expect(link).toBeAttached();
|
||||
expect(
|
||||
await link.evaluate((anchor) =>
|
||||
anchor.getAttributeNS("http://www.w3.org/1999/xlink", "href"),
|
||||
),
|
||||
).toBe(href);
|
||||
const destination = new URL(href, page.url());
|
||||
expect(destination.protocol).not.toBe("javascript:");
|
||||
if (destination.protocol === "http:" || destination.protocol === "https:") {
|
||||
await link.locator("circle").click();
|
||||
await expect(page).toHaveURL(destination.href);
|
||||
await expect(page.locator("body")).not.toHaveAttribute("data-plot-executed");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user