import { _electron as electron, expect, test } from "@playwright/test"; import { mkdirSync } from "node:fs"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; import { randomUUID } from "node:crypto"; import { seedBuiltinSkillsFixture } from "./builtin-skills-fixture"; import { closeApp } from "./onboard"; /** * The founder's direct line to the CEO: onboarding leaves a pinned CEO * conversation at the top of Messages, the page opens on it, a founder * message lands there and queues a CEO wake run, and no amount of channel * recency displaces the pin. */ test("the pinned CEO conversation is the Messages front door", async () => { const profileDir = join(tmpdir(), `agentcompany-new-e2e-ceo-${randomUUID()}`); mkdirSync(profileDir, { recursive: true }); const builtinDir = seedBuiltinSkillsFixture(join(tmpdir(), `agentcompany-new-e2e-ceo-builtin-${randomUUID()}`)); const app = await electron.launch({ args: [resolve("out/main/index.js")], env: { ...process.env, AGENTCOMPANY_NEW_PROFILE_DIR: profileDir, AGENTCOMPANY_NEW_AUTORUN: "0", AGENTCOMPANY_NEW_BUILTIN_SKILLS_DIR: builtinDir, }, }); try { const page = await app.firstWindow(); await expect(page.getByRole("heading", { name: "Welcome" })).toBeVisible(); await page.getByRole("button", { name: "Get started" }).click(); await page.getByRole("button", { name: /^OpenAI API key/ }).click(); await page.getByLabel("API key").fill("e2e-secret"); await page.getByLabel("Model").fill("gpt-5.4"); await page.getByRole("button", { name: "Continue" }).click(); await page.getByLabel("Company name").fill("FlowPilot"); await page.getByLabel("Your name").fill("Yang"); await page.getByLabel("Description").fill("A CSM analytics product for SMBs."); await page.getByRole("button", { name: "Continue" }).click(); await page.getByRole("button", { name: "Continue" }).click(); await page.getByLabel("Goal").fill("Ship the FlowPilot MVP."); await page.getByLabel("Acceptance criteria").fill("A working product, not documents about one."); await page.getByRole("button", { name: "Launch" }).click(); await expect(page.getByRole("heading", { name: "Connectors" })).toBeVisible(); await page.getByRole("button", { name: "Finish" }).click(); // The CEO conversation is the first row, badged, and already selected — // opening Messages IS opening the CEO chat. await page.getByRole("button", { name: "Messages" }).click(); const firstRow = page.locator(".convo-item").first(); await expect(firstRow).toContainText("CEO"); await expect(firstRow.locator(".convo-ceo-badge")).toBeVisible(); await expect(firstRow).toHaveClass(/active/); await expect(firstRow).toContainText("Ask about progress, goals, or direction"); // A founder message lands in the conversation and queues the CEO's wake // run — the reply pipeline is armed even with autonomous execution off. await page.getByRole("textbox", { name: "Message" }).fill("How are we tracking against the goal?"); await page.getByRole("button", { name: "Send" }).click(); const chatLog = page.getByRole("log", { name: "Conversation messages" }); await expect(chatLog.getByText("How are we tracking against the goal?")).toBeVisible(); const wake = await page.evaluate(async () => { const api = (window as unknown as { agentCompany: { loadSnapshot: () => Promise<{ ok: true; data: { agents: Array<{ id: string; roleLevel: string }>; taskRuns: Array<{ agentId: string | null; trigger: string; state: string }>; } }> } }).agentCompany; const snapshot = await api.loadSnapshot(); const ceo = snapshot.data.agents.find((agent) => agent.roleLevel === "c_suite")!; return snapshot.data.taskRuns.filter((run) => run.agentId === ceo.id && run.trigger === "chat_mention").map((run) => run.state); }); expect(wake).toEqual(["queued"]); // The message itself says where the reply stands. await expect(chatLog.locator(".chat-message", { hasText: "How are we tracking against the goal?" }).first()).toContainText("CEO will reply; queued."); // Each settings section says what a change there affects. await page.getByRole("button", { name: "Settings" }).click(); await expect(page.getByRole("note").filter({ hasText: "Applies to FlowPilot only." })).toBeVisible(); await expect(page.getByRole("note").filter({ hasText: "Applies to every company in this app." })).toBeVisible(); // Picking a language does not restart the app; the restart waits for the user, after saying what it interrupts. await page.getByLabel("Language", { exact: true }).selectOption("zh"); await expect(page.getByRole("status").filter({ hasText: "中文 applies after a restart." })).toBeVisible(); await page.getByRole("button", { name: "Restart now" }).click(); await expect(page.locator(".modal")).toContainText("Text you typed but have not sent or saved is lost."); await expect(page.locator(".modal")).toContainText("Running agent sessions: 0. They stop now"); await page.locator(".modal").getByRole("button", { name: "Cancel" }).click(); await page.getByLabel("Language", { exact: true }).selectOption("en"); await expect(page.getByRole("status").filter({ hasText: "applies after a restart." })).toHaveCount(0); // How the company runs can be changed after onboarding. const editCompany = async (mode: string, autoHires: boolean) => { await page.getByRole("button", { name: "Edit company" }).click(); await page.getByLabel("Who runs the work").selectOption(mode); await expect(page.locator(".modal select[name=workLanguage]")).toHaveValue("en"); await page.getByLabel("Auto-approve hires").setChecked(autoHires); await page.getByRole("button", { name: "Save" }).click(); }; await editCompany("ai_cofounder", false); await expect(page.getByText("AI Co-Founder", { exact: true })).toBeVisible(); await expect(page.getByText("Hires require approval")).toBeVisible(); await editCompany("ai_ceo", true); await expect(page.getByText("AI CEO", { exact: true })).toBeVisible(); // A workspace can be kept up after it is added: renamed, made primary, removed. const folder = join(tmpdir(), `agentcompany-new-e2e-ws-${randomUUID()}`); mkdirSync(folder, { recursive: true }); await page.evaluate(async (localPath) => { const api = (window as unknown as { agentCompany: { loadSnapshot: () => Promise<{ ok: true; data: { activeOrgId: string } }>; createWorkspace: (input: unknown) => Promise<{ ok: boolean }>; } }).agentCompany; const orgId = (await api.loadSnapshot()).data.activeOrgId; await api.createWorkspace({ orgId, name: "Scratch", localPath, projectId: null }); }, folder); await page.getByRole("button", { name: "Edit Scratch" }).click(); await page.getByLabel("Name").fill("Scratch repo"); await page.getByRole("button", { name: "Save" }).click(); await expect(page.getByText("Scratch repo", { exact: true })).toBeVisible(); const scratch = page.getByRole("group", { name: "Scratch repo" }); await scratch.getByRole("button", { name: "Remove" }).click(); await page.getByRole("alertdialog", { name: "Remove this workspace?" }).getByRole("button", { name: "Remove" }).click(); await expect(page.getByText("Scratch repo", { exact: true })).toHaveCount(0); // A goal's pace is measured against its target date; without one, no pace is claimed. await page.getByRole("button", { name: "Home" }).click(); await expect(page.getByText("no target date")).toBeVisible(); await page.getByLabel("Target date").fill("2099-12-31"); await expect(page.getByText("on track", { exact: true })).toBeVisible(); await expect(page.getByLabel("Target date")).toHaveValue("2099-12-31"); // From a task, its goal, run and back are one click each. await page.getByRole("button", { name: "Work" }).click(); // The goal view says what it shows: progress, not a timeline without dates. await expect(page.getByText("Progress", { exact: true })).toBeVisible(); await expect(page.getByText("Timeline", { exact: true })).toHaveCount(0); // The CEO's reply to the founder is a chat vehicle, not work: it stays off the board. await expect(page.getByRole("button", { name: /Reply to Yang/ })).toHaveCount(0); await page.getByRole("button", { name: /Operationalize goal: Ship the FlowPilot MVP\./ }).first().click(); const taskDialog = page.getByRole("dialog", { name: "Operationalize goal: Ship the FlowPilot MVP." }); await taskDialog.getByRole("button", { name: "Open goal Ship the FlowPilot MVP." }).click(); await expect(page.getByText("Goal: Ship the FlowPilot MVP.", { exact: true })).toBeVisible(); await page.getByRole("button", { name: /Operationalize goal: Ship the FlowPilot MVP\./ }).first().click(); await taskDialog.getByRole("button", { name: /^Open run/ }).first().click(); // Stored values read as words, and shortcuts use this platform's notation. await expect(page.getByText("Company launch").first()).toBeVisible(); await expect(page.locator(".search-kbd")).toHaveText(process.platform === "darwin" ? "⌘K" : "Ctrl+K"); // A queued run can be cancelled before it starts; then it offers to resume. await page.getByRole("button", { name: "Cancel run" }).click(); await expect(page.getByRole("button", { name: "Resume" })).toBeVisible(); await expect(page.getByRole("button", { name: "Open task Operationalize goal: Ship the FlowPilot MVP." })).toBeVisible(); await page.getByRole("button", { name: "Open task Operationalize goal: Ship the FlowPilot MVP." }).click(); await expect(taskDialog).toBeVisible(); // A wrong link is corrected in the same task's editor, as at creation. await taskDialog.getByRole("button", { name: "Edit" }).click(); await taskDialog.getByLabel("Writing").selectOption("technical"); await taskDialog.getByLabel("Requires workspace access").check(); // The working language is the task's own; blank names the company's. await expect(taskDialog.locator("select[name=workLanguage] option").first()).toHaveText("Company default (English)"); await taskDialog.locator("select[name=workLanguage]").selectOption("zh"); await taskDialog.getByRole("button", { name: "Save" }).click(); await expect(taskDialog.getByRole("heading", { name: "Edit task" })).toHaveCount(0); const edited = await page.evaluate(async () => { const api = (window as unknown as { agentCompany: { loadSnapshot: () => Promise<{ ok: true; data: { tasks: Array<{ title: string; writingMode: string; requiresFilesystem: boolean; workLanguage: string | null }> } }> } }).agentCompany; const task = (await api.loadSnapshot()).data.tasks.find((item) => item.title === "Operationalize goal: Ship the FlowPilot MVP.")!; return [task.writingMode, task.requiresFilesystem, task.workLanguage]; }); expect(edited).toEqual(["technical", true, "zh"]); // A pending request opens for review: read it in full and decide with notes; nothing there pretends to edit it. await taskDialog.getByRole("button", { name: "Close" }).click(); await page.evaluate(async () => { const api = (window as unknown as { agentCompany: { loadSnapshot: () => Promise<{ ok: true; data: { activeOrgId: string } }>; createApproval: (input: unknown) => Promise<{ ok: boolean }>; } }).agentCompany; const orgId = (await api.loadSnapshot()).data.activeOrgId; await api.createApproval({ orgId, kind: "budget_increase", title: "Raise the ads budget", details: "From $200 to $500.", risk: "medium", requestedByType: "system", requestedById: null }); }); await page.getByRole("button", { name: "Approvals" }).click(); await expect(page.getByRole("button", { name: "Edit", exact: true })).toHaveCount(0); await page.getByRole("button", { name: "Review" }).first().click(); const review = page.getByRole("dialog", { name: "Raise the ads budget" }); await expect(review.getByRole("textbox", { name: "Decision notes" })).toBeVisible(); // Notes belong to one request: closing or switching never carries them to the next. await review.getByRole("textbox", { name: "Decision notes" }).fill("Only for the ads budget."); await review.getByRole("button", { name: "Close" }).click(); await page.getByRole("button", { name: "Review" }).first().click(); await expect(review.getByRole("textbox", { name: "Decision notes" })).toHaveValue(""); await review.getByRole("button", { name: "Close" }).click(); // A batch approval says how far it got, item by item. await page.evaluate(async () => { const api = (window as unknown as { agentCompany: { loadSnapshot: () => Promise<{ ok: true; data: { activeOrgId: string } }>; createApproval: (input: unknown) => Promise<{ ok: boolean }>; } }).agentCompany; const orgId = (await api.loadSnapshot()).data.activeOrgId; for (const title of ["Order stickers", "Renew the domain"]) { await api.createApproval({ orgId, kind: "purchase", title, details: "Small spend.", risk: "low", requestedByType: "system", requestedById: null }); } }); await expect(page.getByText("Renew the domain")).toBeVisible(); await page.getByRole("button", { name: "Approve all low risk" }).click(); await expect(page.getByRole("status").filter({ hasText: "Approved" })).toContainText("Approved 2 of 2."); // A decided request can be reopened in full: what was asked, the notes, who decided and when. await page.getByRole("button", { name: "View record of Order stickers" }).click(); const record = page.getByRole("dialog", { name: "Record of Order stickers" }); await expect(record).toContainText("Small spend."); await expect(record).toContainText("No notes"); await expect(record).toContainText("Yang"); await record.getByRole("button", { name: "Close" }).click(); await page.getByRole("button", { name: "Work" }).click(); await page.getByRole("button", { name: /Operationalize goal: Ship the FlowPilot MVP\./ }).first().click(); // Blocking says why on the task and stops it; work can also move backwards. await taskDialog.getByRole("button", { name: "Block", exact: true }).click(); await taskDialog.getByRole("textbox", { name: "Why is it blocked?" }).fill("Waiting for the legal review."); await taskDialog.getByRole("button", { name: "Block task" }).click(); await expect(taskDialog.getByRole("status")).toContainText("Blocked: Waiting for the legal review."); await taskDialog.getByRole("button", { name: "Unblock" }).click(); await expect(taskDialog.getByText("Blocked: Waiting for the legal review.")).toHaveCount(0); await taskDialog.getByRole("button", { name: "In progress" }).click(); await taskDialog.getByRole("button", { name: "Back to backlog" }).click(); await expect(taskDialog.getByRole("button", { name: "In progress" })).toBeVisible(); await taskDialog.getByRole("button", { name: "Close" }).click(); await page.getByRole("button", { name: "Messages" }).click(); // Recency pressure: newer traffic in a channel must not displace the pin. await page.evaluate(async () => { const api = (window as unknown as { agentCompany: { loadSnapshot: () => Promise<{ ok: true; data: { activeOrgId: string; employees: Array<{ id: string; orgId: string }>; chatSpaces: Array<{ id: string; orgId: string; name: string }>; } }>; sendMessage: (input: unknown) => Promise; } }).agentCompany; const snapshot = await api.loadSnapshot(); const orgId = snapshot.data.activeOrgId; const employee = snapshot.data.employees.find((item) => item.orgId === orgId)!; const general = snapshot.data.chatSpaces.find((item) => item.orgId === orgId && item.name === "general")!; await api.sendMessage({ orgId, spaceId: general.id, threadId: null, senderType: "employee", senderId: employee.id, bodyMd: "Channel chatter arriving after the founder's CEO message.", attachmentFileIds: [], }); }); await expect(page.locator(".convo-item").first().locator(".convo-ceo-badge")).toBeVisible(); await expect(page.locator(".convo-item").first()).toContainText("How are we tracking against the goal?"); // A send the backend refuses keeps the draft and says why, instead of clearing it. const composer = page.getByRole("textbox", { name: "Message" }); await composer.fill("/nosuch keep this draft"); await page.getByRole("button", { name: "Send" }).click(); await expect(page.getByRole("alert")).toContainText("Not sent: Slash command /nosuch is unavailable."); await expect(composer).toHaveValue("/nosuch keep this draft"); await composer.fill("Second thought: what is blocked?"); await page.getByRole("button", { name: "Send" }).click(); await expect(chatLog.getByText("Second thought: what is blocked?")).toBeVisible(); await expect(composer).toHaveValue(""); await expect(page.getByRole("alert")).toHaveCount(0); // An edit the backend refuses keeps the editor open with the text and says why. const own = chatLog.locator(".chat-message", { hasText: "Second thought: what is blocked?" }); await own.getByRole("button", { name: "Edit message" }).click(); const editor = own.getByRole("textbox", { name: "Edit message" }); await editor.fill("/status everything"); await own.getByRole("button", { name: "Save" }).click(); await expect(own.getByRole("alert")).toContainText("Not saved: Slash command messages are immutable."); await expect(editor).toHaveValue("/status everything"); await editor.fill("Second thought: what is blocked right now?"); await own.getByRole("button", { name: "Save" }).click(); await expect(chatLog.getByText("Second thought: what is blocked right now?")).toBeVisible(); await expect(chatLog.getByRole("textbox", { name: "Edit message" })).toHaveCount(0); // Each conversation keeps its own unsent draft. await composer.fill("Draft for the CEO only"); await page.locator(".convo-item", { hasText: "general" }).click(); await expect(composer).toHaveValue(""); await composer.fill("Draft for general"); await page.locator(".convo-item").first().click(); await expect(composer).toHaveValue("Draft for the CEO only"); await page.locator(".convo-item", { hasText: "general" }).click(); await expect(composer).toHaveValue("Draft for general"); // A reply goes into the thread of the message it answers, and the timeline says what it answers. await page.locator(".convo-item").first().click(); const root = chatLog.locator(".chat-message", { hasText: "How are we tracking against the goal?" }).first(); await root.getByRole("button", { name: "Reply in thread" }).click(); const thread = page.getByRole("complementary", { name: "Thread" }); await expect(thread).toContainText("How are we tracking against the goal?"); await thread.getByRole("textbox", { name: "Reply" }).fill("Also: which task is late?"); await thread.getByRole("button", { name: "Send reply" }).click(); await expect(thread.locator(".chat-message", { hasText: "Also: which task is late?" })).toBeVisible(); const reply = chatLog.locator(".chat-message", { hasText: "Also: which task is late?" }); await expect(reply.getByRole("button", { name: /^Replying to Yang/ })).toContainText("How are we tracking against the goal?"); await expect(root).toContainText("1 reply"); await thread.getByRole("button", { name: "Close thread" }).click(); await expect(thread).toHaveCount(0); // A message that arrives while the reader is up in history stays unread until they scroll down to it. await page.evaluate(async () => { const api = (window as unknown as { agentCompany: { loadSnapshot: () => Promise<{ ok: true; data: { activeOrgId: string; employees: Array<{ id: string; orgId: string }>; chatSpaces: Array<{ id: string; orgId: string; spaceType: string }> } }>; sendMessage: (input: unknown) => Promise; } }).agentCompany; const snapshot = (await api.loadSnapshot()).data; const employee = snapshot.employees.find((item) => item.orgId === snapshot.activeOrgId)!; const ceoChat = snapshot.chatSpaces.find((item) => item.orgId === snapshot.activeOrgId && item.spaceType === "direct")!; for (let index = 0; index < 40; index += 1) { await api.sendMessage({ orgId: snapshot.activeOrgId, spaceId: ceoChat.id, threadId: null, senderType: "employee", senderId: employee.id, bodyMd: `History line ${index}`, attachmentFileIds: [] }); } }); await expect(chatLog.getByText("History line 39")).toBeVisible(); await chatLog.evaluate((element) => { element.scrollTop = 0; element.dispatchEvent(new Event("scroll")); }); await page.evaluate(async () => { const api = (window as unknown as { agentCompany: { loadSnapshot: () => Promise<{ ok: true; data: { activeOrgId: string; agents: Array<{ id: string; orgId: string; roleLevel: string }>; chatSpaces: Array<{ id: string; orgId: string; spaceType: string }> } }>; sendMessage: (input: unknown) => Promise; } }).agentCompany; const snapshot = (await api.loadSnapshot()).data; const ceo = snapshot.agents.find((agent) => agent.orgId === snapshot.activeOrgId && agent.roleLevel === "c_suite")!; const ceoChat = snapshot.chatSpaces.find((item) => item.orgId === snapshot.activeOrgId && item.spaceType === "direct")!; await api.sendMessage({ orgId: snapshot.activeOrgId, spaceId: ceoChat.id, threadId: null, senderType: "agent", senderId: ceo.id, bodyMd: "An update you have not seen yet.", attachmentFileIds: [] }); }); const ceoRow = page.locator(".convo-item").first(); await expect(ceoRow.locator(".nav-badge")).toHaveText("1"); await page.getByRole("button", { name: "Jump to latest" }).click(); await expect(ceoRow.locator(".nav-badge")).toHaveCount(0); // History older than the loaded window comes from the database, back to the very first message. await page.evaluate(async () => { const api = (window as unknown as { agentCompany: { loadSnapshot: () => Promise<{ ok: true; data: { activeOrgId: string; employees: Array<{ id: string; orgId: string }>; chatSpaces: Array<{ id: string; orgId: string; name: string }> } }>; sendMessage: (input: unknown) => Promise; } }).agentCompany; const snapshot = (await api.loadSnapshot()).data; const employee = snapshot.employees.find((item) => item.orgId === snapshot.activeOrgId)!; const general = snapshot.chatSpaces.find((item) => item.orgId === snapshot.activeOrgId && item.name === "general")!; for (let index = 0; index < 520; index += 1) { await api.sendMessage({ orgId: snapshot.activeOrgId, spaceId: general.id, threadId: null, senderType: "employee", senderId: employee.id, bodyMd: `General line ${index}`, attachmentFileIds: [] }); } }); await page.locator(".convo-item", { hasText: "general" }).click(); await expect(chatLog.getByText("General line 519")).toBeVisible(); const start = chatLog.getByText("This is the start of the conversation."); for (let click = 0; click < 10 && !(await start.isVisible()); click += 1) { await chatLog.getByRole("button", { name: "Show earlier messages" }).click(); await page.waitForTimeout(300); } await expect(start).toBeVisible(); await expect(chatLog.getByText("General line 0", { exact: true })).toBeVisible(); // Runs older than the loaded window page in from the database, down to the first one. await page.getByRole("button", { name: "Runs" }).click(); const runRows = page.locator(".pane-scroll .nav-row"); await expect(runRows).toHaveCount(200); await page.getByRole("button", { name: "Show older runs" }).click(); await expect.poll(async () => runRows.count()).toBeGreaterThan(200); for (let click = 0; click < 10 && await page.getByRole("button", { name: "Show older runs" }).isVisible(); click += 1) { await page.getByRole("button", { name: "Show older runs" }).click(); await page.waitForTimeout(300); } await expect(page.getByText("These are all the runs.")).toBeVisible(); await page.getByRole("button", { name: "Messages" }).click(); await page.locator(".convo-item", { hasText: "general" }).click(); // The header lists the real members, how each agent replies, and who can read. await page.getByRole("button", { name: /^Members \(/ }).click(); const roster = page.getByRole("menu"); await expect(roster).toContainText("Everyone in the company can read this channel."); await expect(roster.locator(".members-roster")).toHaveCSS("overflow-y", "auto"); await expect(roster).toContainText("Replies when @mentioned, in threads it joined, and to messages that name no one"); await page.keyboard.press("Escape"); await page.locator(".convo-item").first().click(); await page.getByRole("button", { name: /^Members \(/ }).click(); await expect(page.getByRole("menu")).toContainText("Replies to every message"); await page.keyboard.press("Escape"); // Deleting a task first says what it takes with it, then waits for the result. await page.evaluate(async () => { const api = (window as unknown as { agentCompany: { loadSnapshot: () => Promise<{ ok: true; data: { activeOrgId: string; tasks: Array<{ id: string; title: string }> } }>; createTask: (input: unknown) => Promise<{ ok: boolean }>; updateTask: (input: unknown) => Promise<{ ok: boolean }>; } }).agentCompany; const before = (await api.loadSnapshot()).data; const parent = before.tasks.find((task) => task.title === "Operationalize goal: Ship the FlowPilot MVP.")!; await api.createTask({ orgId: before.activeOrgId, title: "Child step", description: "d", status: "backlog", assigneeAgentId: null, risk: "low", projectId: null, goalId: null, workspaceId: null, priority: "normal", requiresFilesystem: false, }); const child = (await api.loadSnapshot()).data.tasks.find((task) => task.title === "Child step")!; await api.updateTask({ orgId: before.activeOrgId, taskId: child.id, title: "Child step", description: "d", assigneeAgentId: null, risk: "low", priority: "normal", parentTaskId: parent.id, projectId: null, goalId: null, workspaceId: null, requiresFilesystem: false, writingMode: "conversational", workLanguage: null, }); }); await page.getByRole("button", { name: "Work" }).click(); await page.getByRole("button", { name: /Operationalize goal: Ship the FlowPilot MVP\./ }).first().click(); const parentDialog = page.getByRole("dialog", { name: "Operationalize goal: Ship the FlowPilot MVP." }); await parentDialog.getByRole("button", { name: "Delete", exact: true }).click(); await expect(parentDialog.getByRole("alertdialog", { name: "Delete this task?" })).toContainText("1 subtasks stay and move to the top level."); // Switching to another task drops a pending confirmation, so it can never delete the wrong task. await parentDialog.getByRole("button", { name: "Subtasks" }).click(); await parentDialog.getByRole("button", { name: "Open task Child step" }).click(); const childDialog = page.getByRole("dialog", { name: "Child step" }); await expect(childDialog).toBeVisible(); await expect(childDialog.getByRole("alertdialog")).toHaveCount(0); await childDialog.getByRole("button", { name: "Close" }).click(); await page.getByRole("button", { name: /Operationalize goal: Ship the FlowPilot MVP\./ }).first().click(); const doomed = page.getByRole("dialog", { name: "Operationalize goal: Ship the FlowPilot MVP." }); await doomed.getByRole("button", { name: "Delete", exact: true }).click(); const confirm = doomed.getByRole("alertdialog", { name: "Delete this task?" }); await expect(confirm).toContainText("sessions and their action records are deleted."); await confirm.getByRole("button", { name: "Delete task" }).click(); await expect(doomed).toHaveCount(0); await expect(page.getByRole("button", { name: /Operationalize goal: Ship the FlowPilot MVP\./ })).toHaveCount(0); } finally { await closeApp(app); } });