132 lines
4.7 KiB
TypeScript
132 lines
4.7 KiB
TypeScript
import { expect, test, type Page } from "@playwright/test";
|
|
|
|
const APP_ORIGIN = "http://127.0.0.1:4173";
|
|
|
|
async function keepNetworkLocal(page: Page): Promise<string[]> {
|
|
const externalRequests: string[] = [];
|
|
await page.route("**/*", async (route) => {
|
|
const requestUrl = new URL(route.request().url());
|
|
if (requestUrl.origin !== APP_ORIGIN) {
|
|
externalRequests.push(requestUrl.href);
|
|
await route.abort();
|
|
return;
|
|
}
|
|
await route.continue();
|
|
});
|
|
return externalRequests;
|
|
}
|
|
|
|
function recordRuntimeErrors(page: Page): string[] {
|
|
const errors: string[] = [];
|
|
page.on("pageerror", (error) => errors.push(error.message));
|
|
page.on("console", (message) => {
|
|
if (message.type() === "error") errors.push(message.text());
|
|
});
|
|
return errors;
|
|
}
|
|
|
|
test("encodes text and navigates the local workspaces", async ({ page }) => {
|
|
const runtimeErrors = recordRuntimeErrors(page);
|
|
const externalRequests = await keepNetworkLocal(page);
|
|
await page.goto("/deep/nested/helpers/");
|
|
|
|
await expect(
|
|
page.getByRole("heading", { name: "Useful values, without a round trip" }),
|
|
).toBeVisible();
|
|
await page.getByLabel("Text", { exact: true }).fill("local ✓");
|
|
const encodeCard = page
|
|
.getByRole("heading", { name: "Encode text" })
|
|
.locator("..");
|
|
await expect(encodeCard).toContainText("bG9jYWwg4pyT");
|
|
await page.getByLabel("URL component", { exact: true }).fill("a b/✓");
|
|
await expect(page.getByText("a%20b%2F%E2%9C%93")).toBeVisible();
|
|
await page.getByRole("tab", { name: /^Text & Unicode/u }).click();
|
|
await expect(page).toHaveURL(/#text$/u);
|
|
await expect(
|
|
page.getByRole("heading", { name: "Text & Unicode" }),
|
|
).toBeVisible();
|
|
|
|
expect(externalRequests).toEqual([]);
|
|
expect(runtimeErrors).toEqual([]);
|
|
});
|
|
|
|
test("calculates network, structured data and seeded values", async ({
|
|
page,
|
|
}) => {
|
|
const runtimeErrors = recordRuntimeErrors(page);
|
|
const externalRequests = await keepNetworkLocal(page);
|
|
await page.goto("/deep/nested/helpers/#network");
|
|
|
|
await expect(page.getByText("2001:db8::/64")).toBeVisible();
|
|
await page.getByLabel("IPv4 or IPv6 CIDR").fill("192.0.2.99/24");
|
|
await page.getByLabel("Address to test").fill("192.0.3.1");
|
|
await expect(page.getByText("192.0.2.0/24")).toBeVisible();
|
|
await expect(page.getByText("No", { exact: true })).toBeVisible();
|
|
|
|
await page.getByRole("tab", { name: /^Structured/u }).click();
|
|
await page.getByLabel("JSON input").fill('{"z":1,"a":2}');
|
|
await expect(page.getByText(/"a": 2/u)).toBeVisible();
|
|
await page.getByLabel("CSV input").fill('name,note\nalpha,"a,b"');
|
|
await expect(
|
|
page.getByText("Rows", { exact: true }).locator(".."),
|
|
).toContainText("2");
|
|
|
|
await page.getByRole("tab", { name: /^Random/u }).click();
|
|
await page.getByLabel("Byte count (maximum 4096 here)").fill("8");
|
|
const generate = page.getByRole("button", { name: "Generate seeded bytes" });
|
|
await generate.click();
|
|
const output = page.locator("output").filter({ hasText: /^[0-9a-f]{16}$/u });
|
|
const first = await output.textContent();
|
|
await generate.click();
|
|
await expect(output).toHaveText(first ?? "");
|
|
|
|
expect(externalRequests).toEqual([]);
|
|
expect(runtimeErrors).toEqual([]);
|
|
});
|
|
|
|
test("produces browser digests without clearing the previous result", async ({
|
|
page,
|
|
}) => {
|
|
const runtimeErrors = recordRuntimeErrors(page);
|
|
const externalRequests = await keepNetworkLocal(page);
|
|
await page.goto("/deep/nested/helpers/#checksums");
|
|
|
|
await expect(page.getByText("cbf43926")).toBeVisible();
|
|
await expect(
|
|
page.getByText(
|
|
"15e2b0d3c33891ebb0f1ef609ec419420c20e320ce94c65fbc8c3312448eb225",
|
|
),
|
|
).toBeVisible();
|
|
await page.getByLabel("UTF-8 input").fill("abc");
|
|
await expect(
|
|
page.getByText(
|
|
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
|
|
),
|
|
).toBeVisible();
|
|
|
|
expect(externalRequests).toEqual([]);
|
|
expect(runtimeErrors).toEqual([]);
|
|
});
|
|
|
|
test("serves a relocatable production artifact with hardened headers", async ({
|
|
request,
|
|
}) => {
|
|
const index = await request.get("/deep/nested/helpers/");
|
|
expect(index.ok()).toBe(true);
|
|
expect(index.headers()["content-security-policy"]).toContain(
|
|
"default-src 'self'",
|
|
);
|
|
expect(index.headers()["permissions-policy"]).toContain("geolocation=()");
|
|
expect(index.headers()["x-content-type-options"]).toBe("nosniff");
|
|
expect(await index.text()).not.toMatch(/\b(?:src|href)=["']\//u);
|
|
|
|
const manifest = await request.get("/deep/nested/helpers/toolbox-app.json");
|
|
expect(manifest.headers()["content-type"]).toContain("application/json");
|
|
await expect(manifest.json()).resolves.toMatchObject({
|
|
id: "de.add-ideas.helper-tools",
|
|
version: "0.2.0",
|
|
entry: "./",
|
|
icon: "./favicon.svg",
|
|
});
|
|
});
|