repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/mockup/__e2e__/mockup.spec.ts
Rawimport { readFileSync } from "node:fs";
import { expect, test } from "@playwright/test";
import type { MockupResult, MockupServer } from "../mockup.ts";
const mockup: typeof import("../mockup.ts") = await import(
new URL("../../../dist/mockup/mockup.ts", import.meta.url).href
);
const { startMockupServer } = mockup;
let server: MockupServer | undefined;
const interactiveFragment = readFileSync(
new URL("../guidance/mockup-html/SKILL.md", import.meta.url),
"utf8",
).match(/```html\n([\s\S]*?)```/)?.[1];
if (!interactiveFragment)
throw new Error("Missing interactive mockup skill example");
test.afterEach(() => {
if (!server) return;
server.result.catch(() => undefined);
server.close("e2e cleanup");
server = undefined;
});
test("iterates in one page with live Pi progress, stop, and optional selection", async ({
page,
}) => {
let aborts = 0;
server = await startMockupServer(
{
title: "Layout review",
question: "Which layout works?",
options: [
{
id: "dense",
label: "Dense",
description: "Compact layout",
html: `${interactiveFragment}<script type="application/json">{"count":0}</script>`,
},
{
id: "calm",
label: "Calm",
html: interactiveFragment,
},
],
},
{
openBrowser: false,
abortAgent: () => {
aborts++;
return true;
},
},
);
await page.goto(server.url);
const stableUrl = page.url();
await expect(
page.getByRole("heading", { name: "Layout review" }),
).toBeVisible();
await expect(page.getByText("Which layout works?")).toBeVisible();
await expect(page.locator(".option-card")).toHaveCount(2);
const denseCounter = page.locator('[data-fragment="dense"]');
await expect(
denseCounter.locator('script[type="application/json"]'),
).toHaveJSProperty("textContent", '{"count":0}');
const output = denseCounter.locator("output");
await expect(output).toHaveText("0");
await denseCounter.getByRole("button", { name: "Increment" }).click();
await expect(output).toHaveText("1");
await expect(page.locator('[data-fragment="calm"] output')).toHaveText("0");
expect(
await page.evaluate(() =>
[...document.scripts].some((script) =>
Object.hasOwn(script, "mockupRoot"),
),
),
).toBe(false);
await expect
.poll(() =>
denseCounter.locator(".counter").evaluate((element) => ({
shadow: element.getRootNode() instanceof ShadowRoot,
reviewFrameControls:
document.querySelectorAll("#accept, #iterate").length,
})),
)
.toEqual({ shadow: true, reviewFrameControls: 2 });
await page.locator('[data-select-option="dense"]').check();
await page.locator("#global-notes").fill("keep dense");
await page.locator('[data-pin-option="dense"]').click();
const fragment = page.locator('[data-fragment="dense"]');
const fragmentBox = await fragment.boundingBox();
expect(fragmentBox).not.toBeNull();
await fragment.click({
position: { x: (fragmentBox?.width ?? 20) - 10, y: 40 },
});
await page.getByLabel("Pin note").fill("tighten spacing");
const annotationGeometry = await fragment.evaluate((element) => {
const fragmentRect = element.getBoundingClientRect();
const bubbleRect = element
.querySelector(".pin-bubble")
?.getBoundingClientRect();
const pinRect = element.querySelector(".pin")?.getBoundingClientRect();
return {
clientWidth: element.clientWidth,
scrollWidth: element.scrollWidth,
bubbleWithin:
!!bubbleRect &&
bubbleRect.left >= fragmentRect.left &&
bubbleRect.right <= fragmentRect.right,
pinWithin:
!!pinRect &&
pinRect.left >= fragmentRect.left &&
pinRect.right <= fragmentRect.right,
};
});
expect(annotationGeometry).toEqual({
clientWidth: annotationGeometry.clientWidth,
scrollWidth: annotationGeometry.clientWidth,
bubbleWithin: true,
pinWithin: true,
});
const firstSubmission = server.result;
await page.locator("#iterate").click();
await expect(page.locator("#progress-title")).toHaveText(
"Feedback received. Pi is working.",
);
const first: MockupResult = await firstSubmission;
expect(first).toMatchObject({
action: "iterate",
selected: ["dense"],
notes: "keep dense",
annotations: [{ optionId: "dense", note: "tighten spacing" }],
review: { viewport: { devicePixelRatio: 1 } },
});
server.progress("Pi is working on the revision.");
await expect(page.locator("#progress-title")).toHaveText(
"Pi is working on the revision.",
);
await page.getByRole("button", { name: "Stop agent" }).click();
await expect.poll(() => aborts).toBe(1);
const secondSubmission = server.present({
title: "Layout revision",
question: "Does the denser revision work?",
options: [
{
id: "dense-v2",
label: "Dense revision",
html: interactiveFragment,
},
],
});
await expect(
page.getByRole("heading", { name: "Layout revision" }),
).toBeVisible();
expect(page.url()).toBe(stableUrl);
const revised = page.locator('[data-fragment="dense-v2"]');
await revised.getByRole("button", { name: "Increment" }).click();
await expect(revised.locator("output")).toHaveText("1");
await page.locator("#accept").click();
await expect(page.locator("#progress-title")).toHaveText(
"Accepted. You can close this tab.",
);
const second = await secondSubmission;
expect(second).toMatchObject({
action: "accept",
selected: ["dense-v2"],
notes: "",
annotations: [],
});
});