diff --git a/canvas/e2e/filestab-smoke.spec.ts b/canvas/e2e/filestab-smoke.spec.ts new file mode 100644 index 00000000..05e86610 --- /dev/null +++ b/canvas/e2e/filestab-smoke.spec.ts @@ -0,0 +1,84 @@ +import { test, expect } from "@playwright/test"; + +/** + * Smoke test for the PR #10 FilesTab split. Exercises the UI end-to-end: + * - creates a workspace on the platform + * - opens the detail panel + * - switches to the Files tab + * - confirms tree, toolbar, and editor panels render (the three extracted + * sibling components: FileTree, FilesToolbar, FileEditor) + * - saves a screenshot for visual review + * + * Requires platform on :8080 and canvas on :3000. + */ +test("FilesTab renders after split", async ({ page, request }) => { + // Clean slate + const { workspaces } = await request + .get("http://localhost:8080/workspaces") + .then(async (r) => ({ workspaces: (await r.json()) as Array<{ id: string }> })); + for (const w of workspaces) { + await request.delete(`http://localhost:8080/workspaces/${w.id}?confirm=true`); + } + + // Create a workspace + const created = await request + .post("http://localhost:8080/workspaces", { + data: { name: "FilesTab Smoke", tier: 1, runtime: "langgraph" }, + headers: { "Content-Type": "application/json" }, + }) + .then((r) => r.json()); + const wsId = created.id as string; + + // Register so status flips online (so detail panel content loads cleanly) + await request.post("http://localhost:8080/registry/register", { + data: { id: wsId, url: "http://localhost:9999", agent_card: { name: "Smoke", skills: [] } }, + headers: { "Content-Type": "application/json" }, + }); + + await page.goto("/"); + await expect(page).toHaveTitle(/Molecule AI/); + + // Screenshot: landing + await page.screenshot({ path: "/tmp/filestab-1-landing.png", fullPage: false }); + + // Dismiss any onboarding overlay if present (best-effort) + const skip = page.getByText(/skip guide/i).first(); + if (await skip.isVisible().catch(() => false)) await skip.click(); + + // Click the workspace node — title text is unique + const node = page.getByText("FilesTab Smoke").first(); + await node.waitFor({ timeout: 10_000 }); + await node.click(); + + // Side panel should open + await page.waitForTimeout(300); + await page.screenshot({ path: "/tmp/filestab-2-panel.png", fullPage: false }); + + // Switch to Files tab. The tab bar overflows-x and buttons off-screen + // resist the usual click path. Use Playwright's force-click on the + // hidden button; this fires a real React onClick. + // Tab button text is "⊞ Files" (icon + label). Use hasText substring. + const filesBtn = page.locator("button").filter({ hasText: "Files" }); + await filesBtn.first().scrollIntoViewIfNeeded(); + await filesBtn.first().click({ force: true }); + + await page.waitForTimeout(1200); // let files API load + render the 3 split components + await page.screenshot({ path: "/tmp/filestab-3-files.png", fullPage: false }); + + // Hard assertion: all three split components are visible. + // FilesToolbar: "+ New", "Upload", "Export", "Clear" buttons. + // FileTree: the config.yaml file from the Go provisioner's default template. + // FileEditor: the empty-state placeholder "Select a file to edit". + const toolbarNew = page.getByRole("button", { name: /new/i }); + const toolbarUpload = page.getByRole("button", { name: /upload/i }); + const treeFile = page.getByText("config.yaml"); + const editorEmpty = page.getByText(/select a file/i); + + await expect(toolbarNew.first()).toBeVisible({ timeout: 5_000 }); + await expect(toolbarUpload.first()).toBeVisible({ timeout: 5_000 }); + await expect(treeFile.first()).toBeVisible({ timeout: 5_000 }); + await expect(editorEmpty.first()).toBeVisible({ timeout: 5_000 }); + + // Cleanup + await request.delete(`http://localhost:8080/workspaces/${wsId}?confirm=true`); +}); diff --git a/canvas/package-lock.json b/canvas/package-lock.json index 125a356a..37720b8a 100644 --- a/canvas/package-lock.json +++ b/canvas/package-lock.json @@ -24,6 +24,7 @@ "zustand": "^5.0.0" }, "devDependencies": { + "@playwright/test": "^1.59.1", "@testing-library/jest-dom": "^6.6.0", "@testing-library/react": "^16.1.0", "@types/node": "^22.0.0", @@ -488,6 +489,23 @@ "url": "https://github.com/sponsors/Boshen" } }, + "node_modules/@playwright/test": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz", + "integrity": "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==", + "devOptional": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "playwright": "1.59.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@radix-ui/primitive": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", @@ -3885,6 +3903,53 @@ "node": ">= 6" } }, + "node_modules/playwright": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz", + "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.59.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz", + "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/postcss": { "version": "8.5.8", "funding": [ diff --git a/canvas/package.json b/canvas/package.json index 77e9cf76..4fb7aeb6 100644 --- a/canvas/package.json +++ b/canvas/package.json @@ -26,6 +26,7 @@ "zustand": "^5.0.0" }, "devDependencies": { + "@playwright/test": "^1.59.1", "@testing-library/jest-dom": "^6.6.0", "@testing-library/react": "^16.1.0", "@types/node": "^22.0.0",