import { expect, test, type Page } from "@playwright/test"; import { run, terminalInput, terminalLog } from "./fixtures/terminal.ts"; /** * The demo project's `npm run dev` (node:http on port 3000, served from `index.ts`) from the * Processes panel and from the terminal. Set NIB_SHOTS to a directory to also save screenshots there. */ const escape = (text: string) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const tab = (page: Page, title: string) => page.locator("nib-tablist > nib-tab").filter({ hasText: new RegExp(`^${escape(title)}×?$`) }); const processes = (page: Page) => page.locator("nib-processes"); const list = (page: Page) => processes(page).getByRole("list", { name: "Processes", exact: true }); const server = (page: Page) => page.frameLocator("nib-browser iframe"); const shoot = async (page: Page, name: string) => { if (process.env.NIB_SHOTS) await page.screenshot({ path: `${process.env.NIB_SHOTS}/processes-${name}.png`, animations: "disabled" }); }; test.use({ viewport: { width: 1280, height: 800 } }); test.beforeEach(async ({ page }) => { await page.goto("/new?reset"); await tab(page, "Processes").click(); }); test("Run dev starts the server, the Preview panel binds to it, and Stop ends it", async ({ page }) => { await expect(processes(page).getByRole("status")).toHaveText("No processes. Run a script, or npm run dev in the terminal.", { timeout: 20_000 }); const empty = page.locator("nib-browser").getByRole("region", { name: "No server is running" }); await expect(empty).toHaveText("No server is runningRun npm run dev in the terminal, or Run dev in Processes."); await expect(tab(page, "Preview")).toHaveJSProperty("selected", true); await expect(page.locator("nib-browser iframe")).toHaveCount(0); await processes(page).getByRole("button", { name: "Run dev" }).click(); const npm = list(page).getByRole("listitem").filter({ hasText: "npm run dev" }); const child = npm.getByRole("list", { name: "Started by npm run dev" }).getByRole("listitem"); await expect(child).toHaveCount(1, { timeout: 20_000 }); await expect(child.getByText("node index.ts", { exact: true })).toBeVisible(); await expect(child.getByRole("button", { name: "Open localhost:3000" })).toHaveText("localhost:3000"); await expect(tab(page, "localhost:3000")).toHaveJSProperty("selected", true); await expect(server(page).getByRole("heading", { level: 1 })).toHaveText("Hello from nibling"); await expect(page.locator("nib-browser")).toHaveCount(1); await expect(tab(page, "Preview")).toHaveCount(0); await shoot(page, "running"); // The script ran in the terminal, where its output and Ctrl+C are. await tab(page, "Terminal").click(); await expect(terminalLog(page)).toContainText("~ $ npm run dev\n"); await expect(terminalLog(page)).toContainText("Listening on http://localhost:3000/"); await tab(page, "Processes").click(); await npm.getByRole("button", { name: /^Stop npm run dev/ }).first().click(); await expect(list(page).getByRole("listitem")).toHaveCount(0); await expect(page.locator("nib-browser").getByRole("status")).toHaveText("Waiting for localhost:3000…"); await shoot(page, "stopped"); }); test("npm run dev in the terminal does the same, and a closed auto-opened panel stays closed", async ({ page }) => { await tab(page, "Terminal").click(); await run(page, "npm run dev"); await expect(server(page).getByRole("heading", { level: 1 })).toHaveText("Hello from nibling", { timeout: 20_000 }); await expect(terminalLog(page)).toContainText("Listening on http://localhost:3000/"); await tab(page, "Processes").click(); await expect(list(page).getByRole("button", { name: "Open localhost:3000" })).toBeVisible(); await tab(page, "localhost:3000").locator("[part=close]").click(); await expect(page.locator("nib-browser")).toHaveCount(0); // Ctrl+C reaches npm, which passes it to the server; running it again leaves the closed panel closed. await tab(page, "Terminal").click(); await terminalInput(page).focus(); await page.keyboard.press("Control+c"); await tab(page, "Processes").click(); await expect(list(page).getByRole("listitem")).toHaveCount(0); await tab(page, "Terminal").click(); await run(page, "npm run dev"); await tab(page, "Processes").click(); await expect(list(page).getByRole("button", { name: "Open localhost:3000" })).toBeVisible({ timeout: 20_000 }); await expect(page.locator("nib-browser")).toHaveCount(0); // The port button opens it by hand. await list(page).getByRole("button", { name: "Open localhost:3000" }).click(); await expect(server(page).getByRole("heading", { level: 1 })).toHaveText("Hello from nibling"); }); test("the bound Preview stays bound across a reload, waiting until its server runs again", async ({ page }) => { await processes(page).getByRole("button", { name: "Run dev" }).click(); await expect(server(page).getByRole("heading", { level: 1 })).toHaveText("Hello from nibling", { timeout: 20_000 }); await page.goto("/new"); await expect(tab(page, "localhost:3000")).toHaveCount(1); await expect(tab(page, "Preview")).toHaveCount(0); await expect(page.locator("nib-browser").getByRole("status")).toHaveText("Waiting for localhost:3000…"); await tab(page, "Processes").click(); await processes(page).getByRole("button", { name: "Run dev" }).click(); await expect(server(page).getByRole("heading", { level: 1 })).toHaveText("Hello from nibling", { timeout: 20_000 }); await expect(page.locator("nib-browser")).toHaveCount(1); }); test("with the Preview closed, Run dev opens a browser panel of its own", async ({ page }) => { await tab(page, "Preview").locator("[part=close]").click(); await expect(page.locator("nib-browser")).toHaveCount(0); await processes(page).getByRole("button", { name: "Run dev" }).click(); await expect(server(page).getByRole("heading", { level: 1 })).toHaveText("Hello from nibling", { timeout: 20_000 }); await expect(page.locator("nib-browser")).toHaveCount(1); await expect(tab(page, "localhost:3000")).toHaveJSProperty("selected", true); await expect(tab(page, "Preview")).toHaveCount(0); }); test("a browser panel restored before its server runs waits, then loads, and reloads when the server restarts", async ({ page }) => { await page.evaluate(() => (globalThis as unknown as { workbench: import("../src/shell/index.ts").Workbench }).workbench.open( { kind: "browser", input: { url: "http://localhost:3000/" } }, )); await expect(page.locator("nib-browser").getByRole("status")).toHaveText("Waiting for localhost:3000…"); await processes(page).getByRole("button", { name: "Run dev" }).click(); await expect(server(page).getByRole("heading", { level: 1 })).toHaveText("Hello from nibling", { timeout: 20_000 }); await list(page).getByRole("button", { name: /^Stop npm run dev/ }).click(); await expect(page.locator("nib-browser").getByRole("status")).toHaveText("Waiting for localhost:3000…"); await processes(page).getByRole("button", { name: "Run dev" }).click(); await expect(server(page).getByRole("heading", { level: 1 })).toHaveText("Hello from nibling", { timeout: 20_000 }); await expect(page.locator("nib-browser")).toHaveCount(2); await expect(page.locator("nib-browser").getByRole("region", { name: "No server is running" })).toHaveCount(1); });