From da45b2b84fdea680416d470fb3de30a3a86fc1ef Mon Sep 17 00:00:00 2001 From: "Molecule AI Dev Engineer A (Kimi)" Date: Sun, 14 Jun 2026 01:04:51 +0000 Subject: [PATCH] test(e2e): make context-menu delete-confirm spec fail-closed (#2798) The suite-level beforeEach skipped the whole file when /workspaces was empty, so the delete-confirm regression path false-greened on clean environments even though that test creates its own workspace. - Remove the suite-level empty-workspace skip. - Extract a seedWorkspace helper that creates and registers a leaf node. - Assert the canvas starts empty in the delete-confirm test (regression). - Make the cancel test seed its own workspace instead of relying on leftover state. Fixes #2798 Co-Authored-By: Claude --- canvas/e2e/context-menu-delete.spec.ts | 75 +++++++++++--------------- 1 file changed, 32 insertions(+), 43 deletions(-) diff --git a/canvas/e2e/context-menu-delete.spec.ts b/canvas/e2e/context-menu-delete.spec.ts index 4b4373230..a0ca8177a 100644 --- a/canvas/e2e/context-menu-delete.spec.ts +++ b/canvas/e2e/context-menu-delete.spec.ts @@ -1,4 +1,4 @@ -import { test, expect } from "@playwright/test"; +import { test, expect, type APIRequestContext } from "@playwright/test"; /** * Playwright E2E for context-menu → delete confirm flow. @@ -15,37 +15,39 @@ import { test, expect } from "@playwright/test"; */ const API = process.env.E2E_API_URL ?? "http://localhost:8080"; -test.describe("Context Menu → Delete Confirm", () => { - test.beforeEach(async ({ request }) => { - // Ensure at least one workspace exists so the menu can be triggered - const res = await request.get(`${API}/workspaces`); - const workspaces = (await res.json()) as Array<{ id: string; name: string }>; - if (workspaces.length === 0) { - test.skip("No workspaces on canvas — cannot test context menu"); - } +/** Create and register a leaf workspace that will render on the canvas. */ +async function seedWorkspace(request: APIRequestContext, name: string) { + const create = await request.post(`${API}/workspaces`, { + data: { name, tier: 1, runtime: "claude-code" }, + headers: { "Content-Type": "application/json" }, + }); + const workspace = (await create.json()) as { id: string; name: string }; + + await request.post(`${API}/registry/register`, { + data: { + id: workspace.id, + url: `http://localhost:9999`, + agent_card: { name, skills: [] }, + }, + headers: { "Content-Type": "application/json" }, }); + return workspace; +} + +test.describe("Context Menu → Delete Confirm", () => { test("Delete button opens ConfirmDialog and clicking Confirm deletes the workspace", async ({ page, request, }) => { - // 1. Create a workspace to delete (leaf node — no children, no cascade) - const create = await request.post(`${API}/workspaces`, { - data: { name: "E2E Delete Test", tier: 1, runtime: "claude-code" }, - headers: { "Content-Type": "application/json" }, - }); - const workspace = (await create.json()) as { id: string; name: string }; - const wsId = workspace.id; + // Regression: this test must run from an empty canvas so the old + // suite-level `test.skip()` cannot mask a failure to create/register. + const before = await request.get(`${API}/workspaces`); + const beforeWorkspaces = (await before.json()) as Array<{ id: string; name: string }>; + expect(beforeWorkspaces).toHaveLength(0); - // Register so the node appears online on the canvas - await request.post(`${API}/registry/register`, { - data: { - id: wsId, - url: `http://localhost:9999`, - agent_card: { name: "E2E Delete Test", skills: [] }, - }, - headers: { "Content-Type": "application/json" }, - }); + // 1. Create a workspace to delete (leaf node — no children, no cascade) + const { id: wsId } = await seedWorkspace(request, "E2E Delete Test"); // 2. Open the canvas and wait for the workspace node await page.goto("/", { waitUntil: "networkidle" }); @@ -89,32 +91,19 @@ test.describe("Context Menu → Delete Confirm", () => { }); test("Cancel closes the dialog and the workspace remains", async ({ page, request }) => { - const res = await request.get(`${API}/workspaces`); - const workspaces = (await res.json()) as Array<{ id: string; name: string }>; - if (workspaces.length === 0) { - test.skip("No workspaces"); - } - - const ws = workspaces[0]; - - // Register if not already - await request.post(`${API}/registry/register`, { - data: { id: ws.id, url: `http://localhost:9999`, agent_card: { name: ws.name, skills: [] } }, - headers: { "Content-Type": "application/json" }, - }); + // Seed our own workspace so this test is fail-closed and does not depend + // on leftovers from earlier suites. + const { name: wsName } = await seedWorkspace(request, "E2E Cancel Test"); await page.goto("/", { waitUntil: "networkidle" }); await page.waitForTimeout(2000); - const node = page.locator(`.react-flow__node`).filter({ hasText: ws.name }).first(); + const node = page.locator(`.react-flow__node`).filter({ hasText: wsName }).first(); await node.click({ button: "right" }); const menu = page.locator('[role="menu"]').first(); await expect(menu).toBeVisible(); - // Get workspace name before we click Delete (can't easily look it up after) - const wsName = ws.name; - await menu.getByRole("menuitem").filter({ hasText: /Delete/i }).click(); const dialog = page.locator('[role="dialog"]'); await expect(dialog).toBeVisible({ timeout: 3000 }); @@ -128,4 +117,4 @@ test.describe("Context Menu → Delete Confirm", () => { page.locator(`.react-flow__node`).filter({ hasText: wsName }).first() ).toBeVisible({ timeout: 5000 }); }); -}); \ No newline at end of file +}); -- 2.52.0