| import { test, expect, type Page, type ConsoleMessage } from "@playwright/test"; |
|
|
| |
| |
| |
| |
| |
| |
| const DYNAMIC_MASKS: Record<string, string[]> = { |
| integration: ["#server-time"], |
| "basic-preact": ["#server-time"], |
| "basic-react": ["#server-time"], |
| "basic-solid": ["#server-time"], |
| "basic-svelte": ["#server-time"], |
| "basic-vanillajs": ["#server-time"], |
| "basic-vue": ["#server-time"], |
| "cohort-heatmap": ['[class*="heatmapWrapper"]'], |
| "customer-segmentation": [".chart-container"], |
| shadertoy: ["#canvas"], |
| "system-monitor": [ |
| ".chart-container", |
| "#status-text", |
| "#memory-percent", |
| "#memory-detail", |
| "#memory-bar-fill", |
| "#info-uptime", |
| ], |
| threejs: ["#threejs-canvas", ".threejs-container"], |
| "wiki-explorer": ["#graph"], |
| }; |
|
|
| |
| const SLOW_SERVERS: Record<string, number> = { |
| "map-server": 5000, |
| threejs: 2000, |
| }; |
|
|
| |
| const SERVERS = [ |
| { key: "integration", name: "Integration Test Server" }, |
| { key: "basic-preact", name: "Basic MCP App Server (Preact)" }, |
| { key: "basic-react", name: "Basic MCP App Server (React)" }, |
| { key: "basic-solid", name: "Basic MCP App Server (Solid)" }, |
| { key: "basic-svelte", name: "Basic MCP App Server (Svelte)" }, |
| { key: "basic-vanillajs", name: "Basic MCP App Server (Vanilla JS)" }, |
| { key: "basic-vue", name: "Basic MCP App Server (Vue)" }, |
| { key: "budget-allocator", name: "Budget Allocator Server" }, |
| { key: "cohort-heatmap", name: "Cohort Heatmap Server" }, |
| { key: "customer-segmentation", name: "Customer Segmentation Server" }, |
| { key: "map-server", name: "CesiumJS Map Server" }, |
| { key: "pdf-server", name: "PDF Server" }, |
| { key: "scenario-modeler", name: "SaaS Scenario Modeler" }, |
| { key: "shadertoy", name: "ShaderToy Server" }, |
| { key: "sheet-music", name: "Sheet Music Server" }, |
| { key: "system-monitor", name: "System Monitor Server" }, |
| { key: "threejs", name: "Three.js Server" }, |
| { key: "transcript", name: "Transcript Server" }, |
| { key: "wiki-explorer", name: "Wiki Explorer" }, |
| ]; |
|
|
| |
| |
| |
| function getAppFrame(page: Page) { |
| return page.frameLocator("iframe").first().frameLocator("iframe").first(); |
| } |
|
|
| |
| |
| |
| function captureHostLogs(page: Page): string[] { |
| const logs: string[] = []; |
| page.on("console", (msg: ConsoleMessage) => { |
| const text = msg.text(); |
| if (text.includes("[HOST]")) { |
| logs.push(text); |
| } |
| }); |
| return logs; |
| } |
|
|
| |
| |
| |
| |
| async function waitForAppLoad(page: Page) { |
| const outerFrame = page.frameLocator("iframe").first(); |
| await expect(outerFrame.locator("iframe")).toBeVisible(); |
| } |
|
|
| |
| |
| |
| async function loadServer(page: Page, serverName: string) { |
| await page.goto("/"); |
| |
| await expect(page.locator("select").first()).toBeEnabled({ timeout: 30000 }); |
| await page.locator("select").first().selectOption({ label: serverName }); |
| await page.click('button:has-text("Call Tool")'); |
| await waitForAppLoad(page); |
| } |
|
|
| |
| |
| |
| function getMaskLocators(page: Page, serverKey: string) { |
| const selectors = DYNAMIC_MASKS[serverKey]; |
| if (!selectors) return []; |
|
|
| const appFrame = getAppFrame(page); |
| return selectors.map((selector) => appFrame.locator(selector)); |
| } |
|
|
| test.describe("Host UI", () => { |
| test("initial state shows controls", async ({ page }) => { |
| await page.goto("/"); |
| await expect(page.locator("label:has-text('Server')")).toBeVisible(); |
| await expect(page.locator("label:has-text('Tool')")).toBeVisible(); |
| await expect(page.locator('button:has-text("Call Tool")')).toBeVisible(); |
| }); |
|
|
| test("screenshot of initial state", async ({ page }) => { |
| await page.goto("/"); |
| await expect(page.locator('button:has-text("Call Tool")')).toBeVisible(); |
| await expect(page).toHaveScreenshot("host-initial.png"); |
| }); |
| }); |
|
|
| |
| SERVERS.forEach((server) => { |
| test.describe(server.name, () => { |
| test("loads app UI", async ({ page }) => { |
| await loadServer(page, server.name); |
| }); |
|
|
| test("screenshot matches golden", async ({ page }) => { |
| await loadServer(page, server.name); |
|
|
| |
| const stabilizationMs = SLOW_SERVERS[server.key] ?? 500; |
| await page.waitForTimeout(stabilizationMs); |
|
|
| |
| const mask = getMaskLocators(page, server.key); |
|
|
| await expect(page).toHaveScreenshot(`${server.key}.png`, { |
| mask, |
| maxDiffPixelRatio: 0.06, |
| }); |
| }); |
| }); |
| }); |
|
|
| |
| const integrationServer = SERVERS.find((s) => s.key === "integration")!; |
|
|
| test.describe(`${integrationServer.name} - Interactions`, () => { |
| test("Send Message button triggers host callback", async ({ page }) => { |
| const logs = captureHostLogs(page); |
| await loadServer(page, integrationServer.name); |
|
|
| const appFrame = getAppFrame(page); |
| await appFrame.locator('button:has-text("Send Message")').click(); |
|
|
| |
| await page.waitForTimeout(500); |
|
|
| expect(logs.some((log) => log.includes("Message from MCP App"))).toBe(true); |
| }); |
|
|
| test("Send Log button triggers host callback", async ({ page }) => { |
| const logs = captureHostLogs(page); |
| await loadServer(page, integrationServer.name); |
|
|
| const appFrame = getAppFrame(page); |
| await appFrame.locator('button:has-text("Send Log")').click(); |
|
|
| await page.waitForTimeout(500); |
|
|
| expect(logs.some((log) => log.includes("Log message from MCP App"))).toBe( |
| true, |
| ); |
| }); |
|
|
| test("Open Link button triggers host callback", async ({ page }) => { |
| const logs = captureHostLogs(page); |
| await loadServer(page, integrationServer.name); |
|
|
| const appFrame = getAppFrame(page); |
| await appFrame.locator('button:has-text("Open Link")').click(); |
|
|
| await page.waitForTimeout(500); |
|
|
| expect(logs.some((log) => log.includes("Open link request"))).toBe(true); |
| }); |
| }); |
|
|