mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-10-12 04:29:51 +08:00
npm run test:e2e starts dist/index.js over the MCP SDK with DRAWIO_PREVIEW_UI=shell, opens the URL start_session returns in headless Chromium, and checks that the bundled draw.io loads without console errors and that the theme toggle switches draw.io in place.
114 lines
3.9 KiB
TypeScript
114 lines
3.9 KiB
TypeScript
/**
|
|
* The canvas shell as start_session opens it with DRAWIO_PREVIEW_UI=shell:
|
|
* the built server (dist/index.js) runs over stdio as an MCP client would
|
|
* start it, and headless Chromium opens the URL it returns.
|
|
*/
|
|
import { mkdtempSync, rmSync } from "node:fs"
|
|
import { tmpdir } from "node:os"
|
|
import { dirname, join } from "node:path"
|
|
import { fileURLToPath } from "node:url"
|
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js"
|
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
|
|
import { expect, test } from "@playwright/test"
|
|
|
|
const pkg = join(dirname(fileURLToPath(import.meta.url)), "../..")
|
|
|
|
let client: Client
|
|
let dataDir: string
|
|
let shellUrl: string
|
|
|
|
test.beforeAll(async () => {
|
|
dataDir = mkdtempSync(join(tmpdir(), "mcp-shell-e2e-"))
|
|
const transport = new StdioClientTransport({
|
|
command: process.execPath,
|
|
args: [join(pkg, "dist/index.js")],
|
|
env: {
|
|
...process.env,
|
|
DRAWIO_DATA_DIR: dataDir,
|
|
DRAWIO_PREVIEW_UI: "shell",
|
|
// Away from the web app's dev server and other test runs
|
|
PORT: String(6100 + Math.floor(Math.random() * 400)),
|
|
BROWSER: "none",
|
|
},
|
|
stderr: "pipe",
|
|
})
|
|
client = new Client({ name: "shell-e2e", version: "0.0.1" })
|
|
await client.connect(transport)
|
|
const result = await client.callTool({
|
|
name: "start_session",
|
|
arguments: {},
|
|
})
|
|
const text = (result.content as { type: string; text?: string }[])
|
|
.map((c) => c.text ?? "")
|
|
.join("\n")
|
|
const match = text.match(/Browser URL: (\S+)/)
|
|
if (!match) throw new Error(`start_session gave no URL:\n${text}`)
|
|
shellUrl = match[1]
|
|
})
|
|
|
|
test.afterAll(async () => {
|
|
await client?.close()
|
|
rmSync(dataDir, { recursive: true, force: true })
|
|
})
|
|
|
|
test("start_session opens the shell, which loads the bundled draw.io", async ({
|
|
page,
|
|
}) => {
|
|
expect(shellUrl).toMatch(/^http:\/\/localhost:\d+\/shell\/\?mcp=mcp-/)
|
|
const errors: string[] = []
|
|
page.on("console", (message) => {
|
|
if (message.type() === "error") errors.push(message.text())
|
|
})
|
|
page.on("pageerror", (error) => errors.push(error.message))
|
|
|
|
await page.goto(shellUrl)
|
|
const iframe = page.locator("iframe[title='draw.io']")
|
|
await expect(iframe).toHaveAttribute(
|
|
"src",
|
|
/^http:\/\/localhost:\d+\/drawio\/index\.html\?embed=1&proto=json&configure=1&/,
|
|
)
|
|
// The cover goes once draw.io sent "init" and loaded the first document
|
|
await expect(page.getByTestId("canvas-loading")).toHaveCount(0, {
|
|
timeout: 60_000,
|
|
})
|
|
const frame = page.frameLocator("iframe[title='draw.io']")
|
|
await expect(frame.locator(".geDiagramContainer")).toBeVisible()
|
|
|
|
// The shell's own texts and state
|
|
await expect(page.getByTestId("shell-status")).toHaveText(
|
|
"Waiting for the MCP server…",
|
|
)
|
|
await expect(page.getByTestId("shell-session")).toHaveText(
|
|
shellUrl.slice(-8),
|
|
)
|
|
expect(errors).toEqual([])
|
|
})
|
|
|
|
test("the theme toggle switches the page and draw.io, and is remembered", async ({
|
|
page,
|
|
}) => {
|
|
await page.goto(shellUrl)
|
|
await expect(page.getByTestId("canvas-loading")).toHaveCount(0, {
|
|
timeout: 60_000,
|
|
})
|
|
const html = page.locator("html")
|
|
const frameBody = page
|
|
.frameLocator("iframe[title='draw.io']")
|
|
.locator("body")
|
|
await expect(html).not.toHaveClass(/dark/)
|
|
await expect(frameBody).not.toHaveClass(/geDarkMode/)
|
|
|
|
await page.getByTestId("theme-toggle").click()
|
|
await expect(html).toHaveClass(/dark/)
|
|
// Same-origin draw.io switches in place, without a reload
|
|
await expect(frameBody).toHaveClass(/geDarkMode/)
|
|
expect(
|
|
await page.evaluate(() => localStorage.getItem("mcp:dark-mode")),
|
|
).toBe("true")
|
|
|
|
await page.reload()
|
|
await expect(html).toHaveClass(/dark/)
|
|
await page.getByTestId("theme-toggle").click()
|
|
await expect(html).not.toHaveClass(/dark/)
|
|
})
|