From 4354ad3e27116d7c83306c0fee012239dff68ab6 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sat, 20 Jun 2026 16:59:21 +0800 Subject: [PATCH] Fix Playwright workspace isolation (#1627) * Fix Playwright workspace isolation * Clean up empty project E2E cleanup * Clean up worktree restore E2E projects --- docs/testing.md | 1 + packages/app/e2e/agent-stream-ui.spec.ts | 10 +++---- packages/app/e2e/archive-tab.spec.ts | 17 ++++++++++++ packages/app/e2e/composer-attachments.spec.ts | 10 +++---- .../app/e2e/empty-project-persists.spec.ts | 16 ++++++++---- packages/app/e2e/helpers/archive-tab.ts | 16 +++--------- packages/app/e2e/helpers/composer.ts | 26 +++++++++++++++---- packages/app/e2e/helpers/new-workspace.ts | 1 + packages/app/e2e/helpers/rewind-flow.ts | 3 +++ packages/app/e2e/helpers/seed-client.ts | 13 ++++++---- packages/app/e2e/helpers/terminal-dsl.ts | 5 ++++ packages/app/e2e/helpers/with-workspace.ts | 8 ++++++ packages/app/e2e/helpers/workspace-setup.ts | 1 + .../settings-toggle-tab-regression.spec.ts | 4 +++ .../e2e/workspace-agent-tab-rename.spec.ts | 1 + .../workspace-navigation-regression.spec.ts | 4 +++ .../app/e2e/workspace-pane-remount.spec.ts | 1 + .../worktree-restore-after-restart.spec.ts | 10 ++++++- packages/app/e2e/worktree-restore.spec.ts | 15 +++++++++-- 19 files changed, 119 insertions(+), 43 deletions(-) diff --git a/docs/testing.md b/docs/testing.md index 2c6a30705..bbd418003 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -130,6 +130,7 @@ Test suites in this repo are heavy. Running them in bulk freezes the machine, es - Never re-run a suite another agent already reported green. - For full-suite confidence, push to CI and check GitHub Actions. - Never run the full Playwright E2E suite locally — defer whole-suite verification to CI. Targeted Playwright specs are allowed when you changed or need to prove that specific flow. +- App Playwright specs share one isolated daemon per run. Helpers that create projects or workspaces must remove the daemon project record during cleanup, not only delete the temp directory. Agent helpers must pass the intended `workspaceId` through to agent creation; never infer ownership from `cwd`. ## Agent authentication in tests diff --git a/packages/app/e2e/agent-stream-ui.spec.ts b/packages/app/e2e/agent-stream-ui.spec.ts index 8c51323cb..65083a2fa 100644 --- a/packages/app/e2e/agent-stream-ui.spec.ts +++ b/packages/app/e2e/agent-stream-ui.spec.ts @@ -12,7 +12,7 @@ import { startRunningMockAgent } from "./helpers/composer"; test.describe("Agent stream UI", () => { test("auto-scroll sticks to bottom across token bursts", async ({ page }) => { test.setTimeout(120_000); - const { client, repo } = await startRunningMockAgent(page, { + const agent = await startRunningMockAgent(page, { prefix: "stream-scroll-", model: "one-minute-stream", prompt: "Stream for auto-scroll test.", @@ -21,14 +21,13 @@ test.describe("Agent stream UI", () => { await awaitAssistantMessage(page); await expectScrollFollowsNewContent(page); } finally { - await client.close(); - await repo.cleanup(); + await agent.cleanup(); } }); test("working-indicator transitions to copy-button when stream ends", async ({ page }) => { test.setTimeout(60_000); - const { client, repo } = await startRunningMockAgent(page, { + const agent = await startRunningMockAgent(page, { prefix: "stream-indicator-", model: "ten-second-stream", prompt: "Stream briefly for indicator transition test.", @@ -39,8 +38,7 @@ test.describe("Agent stream UI", () => { await expectAgentIdle(page, 30_000); await expectTurnCopyButton(page); } finally { - await client.close(); - await repo.cleanup(); + await agent.cleanup(); } }); diff --git a/packages/app/e2e/archive-tab.spec.ts b/packages/app/e2e/archive-tab.spec.ts index 6e785eb9b..42583e46f 100644 --- a/packages/app/e2e/archive-tab.spec.ts +++ b/packages/app/e2e/archive-tab.spec.ts @@ -26,15 +26,26 @@ import { test.describe("Archive tab reconciliation", () => { let client: Awaited>; let tempRepo: { path: string; cleanup: () => Promise }; + let projectId: string; + let workspaceId: string; test.describe.configure({ timeout: 300_000 }); test.beforeAll(async () => { tempRepo = await createTempGitRepo("archive-tab-"); client = await connectSeedClient(); + const created = await client.createWorkspace({ + source: { kind: "directory", path: tempRepo.path }, + }); + if (!created.workspace) { + throw new Error(created.error ?? `Failed to create workspace ${tempRepo.path}`); + } + projectId = created.workspace.projectId; + workspaceId = created.workspace.id; }); test.afterAll(async () => { + await client?.removeProject(projectId).catch(() => undefined); await client?.close().catch(() => undefined); await tempRepo?.cleanup(); }); @@ -42,10 +53,12 @@ test.describe("Archive tab reconciliation", () => { test("non-UI archive prunes the archived tab across open pages and reload", async ({ page }) => { const archived = await createIdleAgent(client, { cwd: tempRepo.path, + workspaceId, title: `cli-archive-${randomUUID().slice(0, 8)}`, }); const surviving = await createIdleAgent(client, { cwd: tempRepo.path, + workspaceId, title: `cli-control-${randomUUID().slice(0, 8)}`, }); const passivePage = await page.context().newPage(); @@ -81,10 +94,12 @@ test.describe("Archive tab reconciliation", () => { test("Sessions archive prunes the archived tab across open pages", async ({ page }) => { const archived = await createIdleAgent(client, { cwd: tempRepo.path, + workspaceId, title: `ui-archive-${randomUUID().slice(0, 8)}`, }); const surviving = await createIdleAgent(client, { cwd: tempRepo.path, + workspaceId, title: `ui-control-${randomUUID().slice(0, 8)}`, }); const passivePage = await page.context().newPage(); @@ -111,10 +126,12 @@ test.describe("Archive tab reconciliation", () => { test("clicking an archived session unarchives it and opens the agent", async ({ page }) => { const archived = await createIdleAgent(client, { cwd: tempRepo.path, + workspaceId, title: `unarchive-archived-${randomUUID().slice(0, 8)}`, }); const surviving = await createIdleAgent(client, { cwd: tempRepo.path, + workspaceId, title: `unarchive-control-${randomUUID().slice(0, 8)}`, }); diff --git a/packages/app/e2e/composer-attachments.spec.ts b/packages/app/e2e/composer-attachments.spec.ts index b13e14ef5..e77c2bd25 100644 --- a/packages/app/e2e/composer-attachments.spec.ts +++ b/packages/app/e2e/composer-attachments.spec.ts @@ -193,7 +193,7 @@ test.describe("Composer attachments", () => { page, }) => { test.setTimeout(120_000); - const { client, repo } = await startRunningMockAgent(page, { + const agent = await startRunningMockAgent(page, { prefix: "attach-queue-", model: "one-minute-stream", prompt: "Stay running for queue test.", @@ -205,8 +205,7 @@ test.describe("Composer attachments", () => { await expectQueuedMessageButton(page); await expectComposerDraft(page, ""); } finally { - await client.close(); - await repo.cleanup(); + await agent.cleanup(); } }); @@ -214,7 +213,7 @@ test.describe("Composer attachments", () => { page, }) => { test.setTimeout(120_000); - const { client, repo } = await startRunningMockAgent(page, { + const agent = await startRunningMockAgent(page, { prefix: "attach-interrupt-", model: "ten-second-stream", prompt: "Stay running for interrupt test.", @@ -226,8 +225,7 @@ test.describe("Composer attachments", () => { await expectAgentIdle(page, 15_000); await expectComposerDraft(page, "preserve me"); } finally { - await client.close(); - await repo.cleanup(); + await agent.cleanup(); } }); diff --git a/packages/app/e2e/empty-project-persists.spec.ts b/packages/app/e2e/empty-project-persists.spec.ts index 262fcdbb1..e7c37b914 100644 --- a/packages/app/e2e/empty-project-persists.spec.ts +++ b/packages/app/e2e/empty-project-persists.spec.ts @@ -4,7 +4,7 @@ import { gotoAppShell } from "./helpers/app"; import { connectSeedClient, seedWorkspace } from "./helpers/seed-client"; import { getServerId } from "./helpers/server-id"; import { createTempGitRepo } from "./helpers/workspace"; -import { waitForNoProjectsInSidebar, waitForSidebarHydration } from "./helpers/workspace-ui"; +import { waitForSidebarHydration } from "./helpers/workspace-ui"; function workspaceRowTestId(workspaceId: string): string { return `sidebar-workspace-row-${getServerId()}:${workspaceId}`; @@ -66,6 +66,13 @@ async function addProjectFromPicker(page: Page, projectPath: string): Promise { + await page + .locator('[data-testid="sidebar-project-empty-state"], [data-testid^="sidebar-project-row-"]') + .first() + .waitFor({ state: "visible", timeout: 60_000 }); +} + // Projects are parents in the sidebar. Archiving the last workspace leaves the // project row in place with a ghost "+ New workspace" child row. test.describe("Project with no workspaces persists", () => { @@ -76,7 +83,7 @@ test.describe("Project with no workspaces persists", () => { try { await gotoAppShell(page); - await waitForNoProjectsInSidebar(page); + await waitForSidebarProjectListReady(page); projectId = await addProjectFromPicker(page, repo.path); const projectRow = page.getByTestId(`sidebar-project-row-${projectId}`); @@ -92,7 +99,7 @@ test.describe("Project with no workspaces persists", () => { expect(workspaces.entries).toEqual([]); } finally { if (projectId) { - await removeProjectFromSidebar(page, projectId).catch(() => undefined); + await client.removeProject(projectId).catch(() => undefined); } await client.close().catch(() => undefined); await repo.cleanup().catch(() => undefined); @@ -163,7 +170,7 @@ test.describe("Project remove", () => { await expect(projectRow).toHaveCount(0, { timeout: 30_000 }); await page.reload(); - await waitForSidebarHydration(page); + await waitForSidebarProjectListReady(page); await expect(projectRow).toHaveCount(0, { timeout: 30_000 }); const readded = await workspace.client.addProject(workspace.repoPath); @@ -179,7 +186,6 @@ test.describe("Project remove", () => { page.getByTestId(`sidebar-project-new-workspace-row-${workspace.projectId}`), ).toBeVisible({ timeout: 30_000 }); } finally { - await removeProjectFromSidebar(page, workspace.projectId).catch(() => undefined); await workspace.cleanup(); } }); diff --git a/packages/app/e2e/helpers/archive-tab.ts b/packages/app/e2e/helpers/archive-tab.ts index c62d3c90f..8bdf9f75e 100644 --- a/packages/app/e2e/helpers/archive-tab.ts +++ b/packages/app/e2e/helpers/archive-tab.ts @@ -35,10 +35,6 @@ function buildSeededStoragePayload() { * idle agent from the same client it uses for everything else. */ export interface IdleAgentSeedClient { - createWorkspace(input: { source: { kind: "directory"; path: string } }): Promise<{ - workspace: { id: string } | null; - error: string | null; - }>; createAgent(options: { provider: string; model: string; @@ -56,20 +52,14 @@ export interface IdleAgentSeedClient { export async function createIdleAgent( client: IdleAgentSeedClient, - input: { cwd: string; title: string }, + input: { cwd: string; workspaceId: string; title: string }, ): Promise { - const createdWorkspace = await client.createWorkspace({ - source: { kind: "directory", path: input.cwd }, - }); - if (!createdWorkspace.workspace) { - throw new Error(createdWorkspace.error ?? `Failed to create workspace ${input.cwd}`); - } const created = await client.createAgent({ provider: "opencode", model: "opencode/gpt-5-nano", modeId: "bypassPermissions", cwd: input.cwd, - workspaceId: createdWorkspace.workspace.id, + workspaceId: input.workspaceId, title: input.title, }); const snapshot = await client.waitForAgentUpsert( @@ -84,7 +74,7 @@ export async function createIdleAgent( id: created.id, title: input.title, cwd: input.cwd, - workspaceId: createdWorkspace.workspace.id, + workspaceId: input.workspaceId, }; } diff --git a/packages/app/e2e/helpers/composer.ts b/packages/app/e2e/helpers/composer.ts index c88f5610b..b40570bc2 100644 --- a/packages/app/e2e/helpers/composer.ts +++ b/packages/app/e2e/helpers/composer.ts @@ -149,6 +149,7 @@ export async function selectGithubOption( export interface MockAgentSetup { client: SeedDaemonClient; repo: Awaited>; + cleanup: () => Promise; } /** Create a temp repo, start a mock agent, navigate to it, and wait for it to be running. */ @@ -166,20 +167,29 @@ export async function startRunningMockAgent( if (!createdWorkspace.workspace) { throw new Error(createdWorkspace.error ?? "Failed to create workspace"); } + const workspace = createdWorkspace.workspace; const agent = await client.createAgent({ provider: "mock", cwd: repo.path, - workspaceId: createdWorkspace.workspace.id, + workspaceId: workspace.id, model: opts.model, }); - const agentUrl = `${buildHostWorkspaceRoute(serverId, createdWorkspace.workspace.id)}?open=${encodeURIComponent(`agent:${agent.id}`)}`; + const agentUrl = `${buildHostWorkspaceRoute(serverId, workspace.id)}?open=${encodeURIComponent(`agent:${agent.id}`)}`; await page.goto(agentUrl); await expectComposerVisible(page); await client.sendAgentMessage(agent.id, opts.prompt); await expect(page.getByRole("button", { name: /stop|cancel/i }).first()).toBeVisible({ timeout: 30_000, }); - return { client, repo }; + return { + client, + repo, + cleanup: async () => { + await client.removeProject(workspace.projectId).catch(() => undefined); + await client.close().catch(() => undefined); + await repo.cleanup().catch(() => undefined); + }, + }; } export interface GithubWorkspaceHandle { @@ -198,8 +208,14 @@ export async function openGithubWorkspace( if (!createdWorkspace.workspace) { throw new Error(createdWorkspace.error ?? `Failed to create workspace ${repoPath}`); } + const workspace = createdWorkspace.workspace; await gotoAppShell(page); - await selectWorkspaceInSidebar(page, createdWorkspace.workspace.id); + await selectWorkspaceInSidebar(page, workspace.id); await waitForTabBar(page); - return { cleanup: () => client.close().catch(() => undefined) }; + return { + cleanup: async () => { + await client.removeProject(workspace.projectId).catch(() => undefined); + await client.close().catch(() => undefined); + }, + }; } diff --git a/packages/app/e2e/helpers/new-workspace.ts b/packages/app/e2e/helpers/new-workspace.ts index 27e5eaa39..4448e2382 100644 --- a/packages/app/e2e/helpers/new-workspace.ts +++ b/packages/app/e2e/helpers/new-workspace.ts @@ -18,6 +18,7 @@ type NewWorkspaceDaemonClient = Pick< | "getPaseoWorktreeList" | "getDaemonConfig" | "patchDaemonConfig" + | "removeProject" >; type CreateWorkspacePayload = Awaited>; diff --git a/packages/app/e2e/helpers/rewind-flow.ts b/packages/app/e2e/helpers/rewind-flow.ts index 70bb4b2a2..faf8d49b9 100644 --- a/packages/app/e2e/helpers/rewind-flow.ts +++ b/packages/app/e2e/helpers/rewind-flow.ts @@ -14,6 +14,7 @@ export interface AgentHandle { page: Page; client: SeedDaemonClient; agentId: string; + projectId: string; workspaceId: string; cwd: string; provider: RewindFlowProvider; @@ -187,6 +188,7 @@ export async function launchAgent(input: { page: input.page, client, agentId: agent.id, + projectId: createdWorkspace.workspace.projectId, workspaceId: createdWorkspace.workspace.id, cwd: input.cwd, provider: input.provider, @@ -196,6 +198,7 @@ export async function launchAgent(input: { } export async function closeAgent(handle: AgentHandle): Promise { + await handle.client.removeProject(handle.projectId).catch(() => undefined); await handle.client.close().catch(() => undefined); } diff --git a/packages/app/e2e/helpers/seed-client.ts b/packages/app/e2e/helpers/seed-client.ts index 5c91ff228..5c3e30bcb 100644 --- a/packages/app/e2e/helpers/seed-client.ts +++ b/packages/app/e2e/helpers/seed-client.ts @@ -30,6 +30,7 @@ export interface SeedDaemonClient { } | null; error: string | null; }>; + removeProject(projectId: string): Promise<{ removedWorkspaceIds: string[] }>; fetchWorkspaces(options?: { filter?: { projectId?: string } }): Promise<{ entries: SeedWorkspaceDescriptor[]; }>; @@ -199,15 +200,17 @@ export async function seedWorkspace(options: { if (!created.workspace) { throw new Error(created.error ?? `Failed to create workspace ${project.path}`); } + const workspace = created.workspace; return { client, repoPath: project.path, - workspaceId: created.workspace.id, - workspaceName: created.workspace.name, - workspaceDirectory: created.workspace.workspaceDirectory, - projectId: created.workspace.projectId, - projectDisplayName: created.workspace.projectDisplayName, + workspaceId: workspace.id, + workspaceName: workspace.name, + workspaceDirectory: workspace.workspaceDirectory, + projectId: workspace.projectId, + projectDisplayName: workspace.projectDisplayName, cleanup: async () => { + await client.removeProject(workspace.projectId).catch(() => undefined); await client.close().catch(() => undefined); await project.cleanup().catch(() => undefined); }, diff --git a/packages/app/e2e/helpers/terminal-dsl.ts b/packages/app/e2e/helpers/terminal-dsl.ts index d74946ade..bf1627646 100644 --- a/packages/app/e2e/helpers/terminal-dsl.ts +++ b/packages/app/e2e/helpers/terminal-dsl.ts @@ -28,15 +28,18 @@ function sleep(ms: number): Promise { export class TerminalE2EHarness { readonly client: SeedDaemonClient; readonly tempRepo: TempRepo; + readonly projectId: string; readonly workspaceId: string; private constructor(input: { client: SeedDaemonClient; tempRepo: TempRepo; + projectId: string; workspaceId: string; }) { this.client = input.client; this.tempRepo = input.tempRepo; + this.projectId = input.projectId; this.workspaceId = input.workspaceId; } @@ -54,11 +57,13 @@ export class TerminalE2EHarness { return new TerminalE2EHarness({ client, tempRepo, + projectId: seedResult.workspace.projectId, workspaceId: seedResult.workspace.id, }); } async cleanup(): Promise { + await this.client.removeProject(this.projectId).catch(() => {}); await this.client.close().catch(() => {}); await this.tempRepo.cleanup().catch(() => {}); } diff --git a/packages/app/e2e/helpers/with-workspace.ts b/packages/app/e2e/helpers/with-workspace.ts index b13cf7a66..e3e113c5a 100644 --- a/packages/app/e2e/helpers/with-workspace.ts +++ b/packages/app/e2e/helpers/with-workspace.ts @@ -37,6 +37,7 @@ export function createWithWorkspace(page: Page): WithWorkspaceHandle { let client: WorkspaceSetupDaemonClient | null = null; const repos: Array<{ cleanup: () => Promise }> = []; const worktrees: WorktreeRecord[] = []; + const projectIds = new Set(); const withWorkspace: WithWorkspace = async (options) => { if (!client) { @@ -64,6 +65,7 @@ export function createWithWorkspace(page: Page): WithWorkspaceHandle { if (!added.project) { throw new Error(added.error ?? `Failed to add project ${repo.path}`); } + projectIds.add(added.project.projectId); } const created = await client.createWorkspace({ @@ -73,6 +75,7 @@ export function createWithWorkspace(page: Page): WithWorkspaceHandle { throw new Error(created.error ?? `Failed to create workspace ${workspacePath}`); } const workspaceId = created.workspace.id; + projectIds.add(created.workspace.projectId); return { workspaceId, @@ -88,6 +91,11 @@ export function createWithWorkspace(page: Page): WithWorkspaceHandle { return { withWorkspace, cleanup: async () => { + if (client) { + for (const projectId of projectIds) { + await client.removeProject(projectId).catch(() => undefined); + } + } for (const { repoPath, worktreePath } of worktrees) { try { execSync(`git worktree remove ${JSON.stringify(worktreePath)} --force`, { diff --git a/packages/app/e2e/helpers/workspace-setup.ts b/packages/app/e2e/helpers/workspace-setup.ts index 84758d411..cb5e38600 100644 --- a/packages/app/e2e/helpers/workspace-setup.ts +++ b/packages/app/e2e/helpers/workspace-setup.ts @@ -19,6 +19,7 @@ type WorkspaceSetupDaemonClient = Pick< | "fetchAgents" | "fetchWorkspaces" | "listTerminals" + | "removeProject" | "subscribeRawMessages" >; diff --git a/packages/app/e2e/settings-toggle-tab-regression.spec.ts b/packages/app/e2e/settings-toggle-tab-regression.spec.ts index 259ec758e..319289860 100644 --- a/packages/app/e2e/settings-toggle-tab-regression.spec.ts +++ b/packages/app/e2e/settings-toggle-tab-regression.spec.ts @@ -54,10 +54,12 @@ test.describe("Settings toggle tab regression", () => { try { const firstAgent = await createIdleAgent(workspace.client, { cwd: workspace.repoPath, + workspaceId: workspace.workspaceId, title: `settings-toggle-a-${Date.now()}`, }); const secondAgent = await createIdleAgent(workspace.client, { cwd: workspace.repoPath, + workspaceId: workspace.workspaceId, title: `settings-toggle-b-${Date.now()}`, }); @@ -95,10 +97,12 @@ test.describe("Settings toggle tab regression", () => { try { const firstAgent = await createIdleAgent(workspace.client, { cwd: workspace.repoPath, + workspaceId: workspace.workspaceId, title: `agent-route-refresh-a-${Date.now()}`, }); const secondAgent = await createIdleAgent(workspace.client, { cwd: workspace.repoPath, + workspaceId: workspace.workspaceId, title: `agent-route-refresh-b-${Date.now()}`, }); diff --git a/packages/app/e2e/workspace-agent-tab-rename.spec.ts b/packages/app/e2e/workspace-agent-tab-rename.spec.ts index f2516b22a..68a69de63 100644 --- a/packages/app/e2e/workspace-agent-tab-rename.spec.ts +++ b/packages/app/e2e/workspace-agent-tab-rename.spec.ts @@ -34,6 +34,7 @@ test.describe("Workspace agent tab rename", () => { const initialTitle = `agent-rename-${randomUUID().slice(0, 8)}`; const agent = await createIdleAgent(workspace.client, { cwd: workspace.repoPath, + workspaceId: workspace.workspaceId, title: initialTitle, }); diff --git a/packages/app/e2e/workspace-navigation-regression.spec.ts b/packages/app/e2e/workspace-navigation-regression.spec.ts index 20581687c..861d06908 100644 --- a/packages/app/e2e/workspace-navigation-regression.spec.ts +++ b/packages/app/e2e/workspace-navigation-regression.spec.ts @@ -204,6 +204,7 @@ test.describe("Workspace navigation regression", () => { try { const agent = await createIdleAgent(workspace.client, { cwd: workspace.repoPath, + workspaceId: workspace.workspaceId, title: `workspace-reconnect-${Date.now()}`, }); @@ -306,6 +307,7 @@ test.describe("Workspace navigation regression", () => { try { const agent = await createIdleAgent(workspace.client, { cwd: workspace.repoPath, + workspaceId: workspace.workspaceId, title: `workspace-refresh-route-${Date.now()}`, }); await injectDesktopBridge(page, { @@ -344,10 +346,12 @@ test.describe("Workspace navigation regression", () => { try { const firstAgent = await createIdleAgent(firstWorkspace.client, { cwd: firstWorkspace.repoPath, + workspaceId: firstWorkspace.workspaceId, title: `workspace-nav-a-${Date.now()}`, }); const secondAgent = await createIdleAgent(secondWorkspace.client, { cwd: secondWorkspace.repoPath, + workspaceId: secondWorkspace.workspaceId, title: `workspace-nav-b-${Date.now()}`, }); diff --git a/packages/app/e2e/workspace-pane-remount.spec.ts b/packages/app/e2e/workspace-pane-remount.spec.ts index 552d2498b..de56c8c4e 100644 --- a/packages/app/e2e/workspace-pane-remount.spec.ts +++ b/packages/app/e2e/workspace-pane-remount.spec.ts @@ -18,6 +18,7 @@ test.describe("Workspace pane mounting", () => { try { const agent = await createIdleAgent(workspace.client, { cwd: workspace.repoPath, + workspaceId: workspace.workspaceId, title: `pane-remount-${Date.now()}`, }); diff --git a/packages/app/e2e/worktree-restore-after-restart.spec.ts b/packages/app/e2e/worktree-restore-after-restart.spec.ts index 58be83b47..4ef4c3dce 100644 --- a/packages/app/e2e/worktree-restore-after-restart.spec.ts +++ b/packages/app/e2e/worktree-restore-after-restart.spec.ts @@ -20,6 +20,7 @@ test.describe("Worktree restore after daemon restart", () => { let worktreeClient: Awaited>; let tempRepo: { path: string; cleanup: () => Promise }; const createdWorktreeDirectories = new Set(); + const createdProjectIds = new Set(); test.describe.configure({ retries: 0, timeout: 180_000 }); @@ -34,6 +35,10 @@ test.describe("Worktree restore after daemon restart", () => { await archiveWorkspaceFromDaemon(worktreeClient, directory).catch(() => undefined); } createdWorktreeDirectories.clear(); + for (const projectId of createdProjectIds) { + await worktreeClient.removeProject(projectId).catch(() => undefined); + } + createdProjectIds.clear(); await client?.close().catch(() => undefined); await worktreeClient?.close().catch(() => undefined); await tempRepo?.cleanup().catch(() => undefined); @@ -49,15 +54,18 @@ test.describe("Worktree restore after daemon restart", () => { // the History table cells must show after restore — never "main". const worktreeSlug = `restart-restore-${randomUUID().slice(0, 8)}`; - await openProjectViaDaemon(worktreeClient, tempRepo.path); + const project = await openProjectViaDaemon(worktreeClient, tempRepo.path); + createdProjectIds.add(project.projectKey); const worktree = await createWorktreeViaDaemon(worktreeClient, { cwd: tempRepo.path, slug: worktreeSlug, }); + createdProjectIds.add(worktree.projectKey); createdWorktreeDirectories.add(worktree.workspaceDirectory); const agent = await createIdleAgent(client, { cwd: worktree.workspaceDirectory, + workspaceId: worktree.workspaceId, title: `restart-restore-${randomUUID().slice(0, 8)}`, }); expect(existsSync(worktree.workspaceDirectory)).toBe(true); diff --git a/packages/app/e2e/worktree-restore.spec.ts b/packages/app/e2e/worktree-restore.spec.ts index 1dbaf709d..b0f184d30 100644 --- a/packages/app/e2e/worktree-restore.spec.ts +++ b/packages/app/e2e/worktree-restore.spec.ts @@ -25,6 +25,7 @@ test.describe("Worktree restore", () => { let worktreeClient: Awaited>; let tempRepo: { path: string; cleanup: () => Promise }; const createdWorktreeDirectories = new Set(); + const createdProjectIds = new Set(); test.describe.configure({ timeout: 120_000 }); @@ -39,6 +40,10 @@ test.describe("Worktree restore", () => { await archiveWorkspaceFromDaemon(worktreeClient, directory).catch(() => undefined); } createdWorktreeDirectories.clear(); + for (const projectId of createdProjectIds) { + await worktreeClient.removeProject(projectId).catch(() => undefined); + } + createdProjectIds.clear(); await client?.close().catch(() => undefined); await worktreeClient?.close().catch(() => undefined); await tempRepo?.cleanup().catch(() => undefined); @@ -48,15 +53,18 @@ test.describe("Worktree restore", () => { page, }) => { const serverId = getServerId(); - await openProjectViaDaemon(worktreeClient, tempRepo.path); + const project = await openProjectViaDaemon(worktreeClient, tempRepo.path); + createdProjectIds.add(project.projectKey); const worktree = await createWorktreeViaDaemon(worktreeClient, { cwd: tempRepo.path, slug: `restore-inplace-${randomUUID().slice(0, 8)}`, }); + createdProjectIds.add(worktree.projectKey); createdWorktreeDirectories.add(worktree.workspaceDirectory); const agent = await createIdleAgent(client, { cwd: worktree.workspaceDirectory, + workspaceId: worktree.workspaceId, title: `restore-inplace-${randomUUID().slice(0, 8)}`, }); expect(existsSync(worktree.workspaceDirectory)).toBe(true); @@ -90,15 +98,18 @@ test.describe("Worktree restore", () => { page, }) => { const serverId = getServerId(); - await openProjectViaDaemon(worktreeClient, tempRepo.path); + const project = await openProjectViaDaemon(worktreeClient, tempRepo.path); + createdProjectIds.add(project.projectKey); const worktree = await createWorktreeViaDaemon(worktreeClient, { cwd: tempRepo.path, slug: `restore-recreate-${randomUUID().slice(0, 8)}`, }); + createdProjectIds.add(worktree.projectKey); createdWorktreeDirectories.add(worktree.workspaceDirectory); const agent = await createIdleAgent(client, { cwd: worktree.workspaceDirectory, + workspaceId: worktree.workspaceId, title: `restore-recreate-${randomUUID().slice(0, 8)}`, }); expect(existsSync(worktree.workspaceDirectory)).toBe(true);