T46 E2E: Playwright walks the built site, desktop and phone, live stories included

This commit is contained in:
ZacharyZhang-NY committed 2026-09-29 23:40:04 -04:00
1 parent 6ff43607b7
commit f50a708951
6 files changed
+203 -1

No files matched your search

+70
View File
@@ -0,0 +1,70 @@
import { expect, test, type Page } from "@playwright/test";
import manifest from "../../examples/gallery/stories.json" with { type: "json" };
const STARTS = 45_000;
async function live(page: Page, path: string) {
await page.goto(path);
const frame = page.frameLocator(".stage iframe");
// The host hides its state once the gallery says it is ready.
await expect(page.locator(".stage .state")).toBeHidden({ timeout: STARTS });
return frame;
}
test("a story runs live and follows the site's mode", async ({ page }) => {
await page.emulateMedia({ colorScheme: "light" });
const frame = await live(page, "/components/buttons/button/");
await expect(frame.locator("canvas")).toBeVisible();
await expect(frame.locator("html")).not.toHaveClass(/dark/);
await page.locator("[data-mode]").click();
await expect(frame.locator("html")).toHaveClass(/dark/);
});
test("the live story leaves focus with the host until it is pressed", async ({ page, isMobile }) => {
test.skip(isMobile, "no hardware keyboard");
await live(page, "/components/forms/input-textfield-clearableinput/");
await page.waitForTimeout(1000);
expect(await page.evaluate(() => document.activeElement?.tagName)).not.toBe("IFRAME");
await page.keyboard.press("/");
await expect(page.getByRole("dialog", { name: "Search components" })).toBeVisible();
});
test("a skip link passes the live example", async ({ page, isMobile }) => {
test.skip(isMobile, "no hardware keyboard");
await page.goto("/components/buttons/button/");
const skip = page.getByRole("link", { name: "Skip the live example" });
await skip.focus();
await expect(skip).toBeVisible();
await page.keyboard.press("Enter");
await expect(page.locator("#after-stage")).toBeFocused();
});
test("typing reaches a live field after a press", async ({ page, isMobile }) => {
test.skip(isMobile, "no hardware keyboard");
const frame = await live(page, "/components/forms/input-textfield-clearableinput/");
const box = (await page.locator(".stage iframe").boundingBox())!;
// The first field sits under the section's note, as the gallery lays it.
await page.mouse.click(box.x + 120, box.y + 72);
const before = await frame.locator("canvas").screenshot();
await page.keyboard.type("Ada");
await expect(frame.locator("textarea")).toBeFocused();
await expect.poll(async () => (await frame.locator("canvas").screenshot()).equals(before)).toBe(false);
});
test("full screen covers the page and Escape leaves it", async ({ page }) => {
await live(page, "/components/buttons/button/");
await page.locator(".stage").hover();
await page.getByRole("button", { name: "Full screen" }).click();
await expect(page.locator(".stage")).toHaveClass(/full/);
await page.keyboard.press("Escape");
await expect(page.locator(".stage")).not.toHaveClass(/full/);
});
// One story from every fifth chapter, each drawn in its frame.
for (const chapter of manifest.pages.filter((_, i) => i % 5 === 0)) {
test(`the first ${chapter.title} story draws`, async ({ page }) => {
const frame = await live(page, `/components/${chapter.slug}/`);
const shot = await frame.locator("canvas").screenshot();
expect(shot.length).toBeGreaterThan(4000);
});
}
+85
View File
@@ -0,0 +1,85 @@
import { expect, test, type Page } from "@playwright/test";
import manifest from "../../examples/gallery/stories.json" with { type: "json" };
/** Fails the test on any console error or uncaught exception. */
function watch(page: Page): string[] {
const errors: string[] = [];
page.on("console", (m) => m.type() === "error" && errors.push(m.text()));
page.on("pageerror", (e) => errors.push(e.message));
return errors;
}
test("the home page shows the pitch, the scene and the credits", async ({ page }) => {
const errors = watch(page);
await page.goto("/");
await expect(page.getByRole("heading", { level: 1 })).toContainText("component library for GPUI");
await expect(page.locator(".hero canvas")).toBeVisible();
await expect(page.locator("#foot")).toContainText("Solar Icons by 480 Design");
expect(errors).toEqual([]);
});
test("the mode toggles, and a reload keeps it", async ({ page }) => {
await page.emulateMedia({ colorScheme: "light" });
await page.goto("/components/");
const html = page.locator("html");
await expect(html).not.toHaveClass(/dark/);
await page.getByRole("button", { name: "Dark mode" }).click();
await expect(html).toHaveClass(/dark/);
await page.reload();
await expect(html).toHaveClass(/dark/);
});
test("the index lists every chapter with its story count", async ({ page }) => {
await page.goto("/components/");
const rows = page.locator(".grid a");
await expect(rows).toHaveCount(manifest.pages.length);
const first = manifest.pages[0];
await expect(rows.first()).toContainText(`${first.stories.length} stories`);
});
test("a chapter opens its first story, and paging walks on", async ({ page }) => {
const [chapter] = manifest.pages;
await page.goto(`/components/${chapter.slug}/`);
await expect(page.getByRole("heading", { level: 1 })).toHaveText(chapter.stories[0].title);
await page.locator(".pager a.next").click();
await expect(page).toHaveURL(`/components/${chapter.slug}/${chapter.stories[1].slug}/`);
await expect(page.getByRole("heading", { level: 1 })).toHaveText(chapter.stories[1].title);
});
test("the sidebar filters by story and component name", async ({ page }) => {
await page.goto("/components/");
await page.getByPlaceholder("Filter components").fill("Toast");
await expect(page.locator(".side li a").first()).toContainText("Toast");
await page.getByPlaceholder("Filter components").fill("zzzz");
await expect(page.locator(".side .none")).toContainText("Nothing matches");
});
test("search opens with the keyboard and goes where Enter points", async ({ page, isMobile }) => {
test.skip(isMobile, "no hardware keyboard");
await page.goto("/components/");
await page.keyboard.press("/");
const dialog = page.getByRole("dialog", { name: "Search components" });
await expect(dialog).toBeVisible();
await page.keyboard.type("breadcrumb");
await page.keyboard.press("Enter");
await expect(dialog).toBeHidden();
await expect(page).toHaveURL(/\/components\/navigation\/.*breadcrumb/);
});
test("the Browse menu opens a chapter's stories beside it", async ({ page, isMobile }) => {
test.skip(isMobile, "hover menu");
await page.goto("/components/");
await page.getByRole("button", { name: "Browse" }).click();
await page.getByRole("menuitem", { name: "Charts" }).hover();
const story = page.getByRole("menuitem", { name: /^RadarChart/ });
await expect(story).toBeVisible();
await story.click();
await expect(page).toHaveURL(/\/components\/charts\/radarchart/);
});
test("an unknown chapter says so", async ({ page }) => {
const errors = watch(page);
await page.goto("/components/nowhere/");
await expect(page.getByRole("heading", { level: 1 })).toHaveText("Not found");
expect(errors.join()).toContain("No chapter");
});