tests/lab.spec.js
import { test, expect } from "@playwright/test";
const STATIONS = ["overview", "network", "render", "eventloop", "engine", "threads", "storage", "vitals", "debug"];
// Fail any test on an error the page did not throw on purpose.
test.beforeEach(async ({ page }) => {
page.on("pageerror", (err) => {
if (!/Boom from a timer|Nobody handled me/.test(err.message)) throw err;
});
});
const open = async (page, id) => {
await page.goto(`/#/${id}`);
await expect(page.locator(".station h2")).toBeVisible();
};
for (const id of STATIONS) {
test(`station ${id} renders`, async ({ page }) => {
await open(page, id);
await expect(page.getByRole("link", { name: new RegExp(id === "eventloop" ? "event loop" : "", "i") }).first()).toBeVisible();
await expect(page.getByRole("list", { name: "Missions" })).toBeVisible();
});
}
test("the server is cross-origin isolated", async ({ page }) => {
await open(page, "overview");
expect(await page.evaluate(() => crossOriginIsolated)).toBe(true);
});
test("the event-loop model matches the real engine for every snippet", async ({ page }) => {
await open(page, "eventloop");
for (const tab of await page.getByRole("tab").all()) {
await tab.click();
await page.getByRole("button", { name: "Run it for real" }).click();
await expect(page.locator("[data-match]")).toHaveAttribute("data-match", "true");
}
await expect(page.locator('[data-mission="verify"]')).toHaveClass(/done/);
});
test("stepping through a snippet fills the queues", async ({ page }) => {
await open(page, "eventloop");
const next = page.getByRole("button", { name: "Step ▶" });
for (let i = 0; i < 4; i++) await next.click();
await expect(page.locator(".box.webapis .token")).toHaveCount(1);
await expect(page.locator(".box.micro .token")).toHaveCount(1);
});
test("request burst: protocol decides how many connections", async ({ page }, testInfo) => {
await open(page, "network");
await page.getByLabel("Number of requests").fill("12");
await page.getByLabel("Server delay (ms)").fill("300");
await page.getByRole("button", { name: "Fire the burst" }).click();
const readout = (label) => page.locator(".readout", { hasText: label }).locator("b");
if (testInfo.project.name === "h2") {
await expect(readout("Protocol")).toHaveText("h2");
await expect(readout("Connections used")).toHaveText("1");
} else {
await expect(readout("Protocol")).toHaveText("http/1.1");
// 6 connections per origin, give or take one the page already had open
await expect(readout("Connections used")).toHaveText(/^[2-7]$/);
}
await expect(page.locator(".wf-row")).toHaveCount(12);
});
test("whoami reports TLS 1.3 over HTTP/2", async ({ page }, testInfo) => {
test.skip(testInfo.project.name !== "h2", "TLS only on the https project");
await open(page, "network");
await page.getByRole("button", { name: "Ask the server" }).click();
await expect(page.locator(".kv dd").first()).toHaveText("2.0");
await expect(page.getByText("TLSv1.3")).toBeVisible();
});
test("layout thrashing is slower than batching", async ({ page }) => {
await open(page, "render");
await page.getByRole("button", { name: "Interleave read/write" }).click();
await page.getByRole("button", { name: "Batch reads, then writes" }).click();
const value = async (label) => parseFloat(await page.locator(".readout", { hasText: label }).locator("b").innerText());
expect(await value("interleaved")).toBeGreaterThan(await value("batched"));
});
test("the render tree drops display:none but keeps ::before", async ({ page }) => {
await open(page, "render");
await expect(page.locator(".tree .gone")).toContainText("p [hidden] (display:none → no box)");
await expect(page.locator(".tree .pseudo")).toContainText("::before");
});
test("Atomics fixes the data race", async ({ page }) => {
await open(page, "threads");
await page.getByRole("button", { name: "Fix with Atomics.add" }).click();
await expect(page.getByText(/Atomics\.add: expected 4,000,000, got 4,000,000 → 0 updates lost/)).toBeVisible({ timeout: 15000 });
});
test("a worker keeps the page responsive", async ({ page }) => {
await open(page, "threads");
await page.getByLabel("How much work").selectOption("3000000");
await page.getByRole("button", { name: "Run in a Web Worker" }).click();
await expect(page.getByText(/The ball never stopped/)).toBeVisible({ timeout: 15000 });
});
test("IndexedDB: write, read back, and the inactive-transaction pitfall", async ({ page }) => {
await open(page, "storage");
await page.getByLabel("Note text").fill("written by Playwright");
await page.getByRole("button", { name: "Add note" }).click();
await expect(page.getByRole("list", { name: "Stored notes" })).toContainText("written by Playwright");
await page.getByRole("button", { name: "Trigger it" }).click();
await expect(page.getByRole("list", { name: "IndexedDB event log" })).toContainText("TransactionInactiveError");
});
test("a late banner pushes CLS past 0.1", async ({ page }) => {
await open(page, "vitals");
await page.getByRole("button", { name: "Load a late banner" }).click();
await expect(page.locator(".shift-banner")).toHaveCount(1);
await expect(page.locator('[data-metric="CLS"]')).toHaveClass(/needs-improvement|poor/);
await expect(page.locator('[data-mission="cls"]')).toHaveClass(/done/);
});
test("a 350ms click handler shows up as a slow interaction", async ({ page }) => {
await open(page, "vitals");
await page.getByRole("button", { name: "Slow handler (350ms)" }).click();
await expect(page.getByText(/Last interaction: .* took (3[5-9]\d|[4-9]\d\d) ms/)).toBeVisible();
});
test("errors, rejections and CSP violations are captured", async ({ page }) => {
await open(page, "debug");
await page.getByRole("button", { name: "Throw an uncaught error" }).click();
await page.getByRole("button", { name: "Reject a promise, never catch it" }).click();
await page.getByRole("button", { name: "Violate the CSP (eval)" }).click();
const list = page.getByRole("list", { name: "Captured errors" });
await expect(list).toContainText("[error] Uncaught Error: Boom from a timer callback");
await expect(list).toContainText("[unhandledrejection] Nobody handled me");
await expect(list).toContainText("[csp] script-src blocked eval");
await expect(page.locator('[data-mission="csp"]')).toHaveClass(/done/);
});
test("missions persist across reloads", async ({ page }) => {
await open(page, "engine");
await page.getByRole("button", { name: "{ x, y }", exact: true }).click();
await page.getByRole("button", { name: "{ y, x }", exact: true }).click();
await expect(page.locator('[data-mission="shapes"]')).toHaveClass(/done/);
await page.reload();
await expect(page.locator('[data-mission="shapes"]')).toHaveClass(/done/);
await expect(page.locator("#progress-text")).toContainText("1 of");
});