diff --git a/packages/app/e2e/agent-bottom-anchor.spec.ts b/packages/app/e2e/agent-bottom-anchor.spec.ts index e122adb78..6237086a9 100644 --- a/packages/app/e2e/agent-bottom-anchor.spec.ts +++ b/packages/app/e2e/agent-bottom-anchor.spec.ts @@ -21,6 +21,10 @@ async function openWorkspaceAgentTab(page: Page, agentId: string) { await tab.click(); } +function buildWorkspaceDraftUrl(workspaceUrl: string) { + return `${workspaceUrl}?open=${encodeURIComponent("draft:new")}`; +} + test("direct load and refresh land at the bottom for history-backed chats", async ({ page, }) => { @@ -69,7 +73,9 @@ test("revisiting a loaded chat restores bottom anchoring", async ({ await waitForAgentReady(page, agent.expectedTailText); await expectNearBottom(page); - await page.getByTestId("sidebar-new-agent").first().click(); + await page.goto(buildWorkspaceDraftUrl(agent.workspaceUrl), { + waitUntil: "domcontentloaded", + }); await expect(page.getByRole("textbox", { name: "Message agent..." }).first()).toBeVisible({ timeout: 30000, }); diff --git a/packages/server/src/server/daemon-e2e/git-operations.e2e.test.ts b/packages/server/src/server/daemon-e2e/git-operations.e2e.test.ts index fc672471f..5101a0be9 100644 --- a/packages/server/src/server/daemon-e2e/git-operations.e2e.test.ts +++ b/packages/server/src/server/daemon-e2e/git-operations.e2e.test.ts @@ -93,31 +93,36 @@ async function waitForTimelineToolCall( async function waitForPathExists( options: { targetPath: string; timeoutMs: number; label: string } ): Promise { - const start = Date.now(); - while (Date.now() - start < options.timeoutMs) { - if (existsSync(options.targetPath)) { - return; - } - await new Promise((resolve) => setTimeout(resolve, 50)); - } - throw new Error( - `Timed out after ${options.timeoutMs}ms waiting for ${options.label}: ${options.targetPath}` - ); + await waitForCondition({ + timeoutMs: options.timeoutMs, + label: `${options.label}: ${options.targetPath}`, + predicate: () => existsSync(options.targetPath), + }); } async function waitForPathRemoved( options: { targetPath: string; timeoutMs: number; label: string } ): Promise { + await waitForCondition({ + timeoutMs: options.timeoutMs, + label: `removal of ${options.label}: ${options.targetPath}`, + predicate: () => !existsSync(options.targetPath), + }); +} + +async function waitForCondition(options: { + timeoutMs: number; + label: string; + predicate: () => boolean | Promise; +}): Promise { const start = Date.now(); while (Date.now() - start < options.timeoutMs) { - if (!existsSync(options.targetPath)) { + if (await options.predicate()) { return; } await new Promise((resolve) => setTimeout(resolve, 50)); } - throw new Error( - `Timed out after ${options.timeoutMs}ms waiting for removal of ${options.label}: ${options.targetPath}` - ); + throw new Error(`Timed out after ${options.timeoutMs}ms waiting for ${options.label}`); } async function withShell(shell: string, run: () => Promise): Promise { @@ -818,10 +823,17 @@ describe("daemon E2E", () => { label: "createAgent should not block on setup", }); - await waitForPathExists({ - targetPath: path.join(agent.cwd, "dev-terminal.txt"), - timeoutMs: 15000, - label: "worktree terminal marker", + await waitForCondition({ + timeoutMs: 30000, + label: `worktree terminal bootstrap for ${agent.cwd}`, + predicate: async () => { + const directories = ctx.daemon.daemon.terminalManager.listDirectories(); + if (!directories.includes(agent.cwd)) { + return false; + } + const terminals = await ctx.client.listTerminals(agent.cwd); + return terminals.terminals.some((terminal) => terminal.name === "Dev Server"); + }, }); const beforeArchiveDirectories = ctx.daemon.daemon.terminalManager.listDirectories();