From 99abd77138693344c67d64fcd26de511e535d1f1 Mon Sep 17 00:00:00 2001 From: Antisophy <293439221+Antisophy@users.noreply.github.com> Date: Thu, 16 Jul 2026 11:53:46 -0700 Subject: [PATCH] test(e2e): stop bounding assertions by idle-machine wall clock The suite runs one check derivation per test, and nix builds them massively in parallel (max-jobs), so every test executes on a heavily contended machine. Per-assertion timeouts were sized to observed idle-machine performance (5s Playwright default through 90s), which turns correctness assertions into de facto performance assertions: under contention the condition still becomes true, but the clock fires first. Every historically flaky check passes in seconds when run alone, and its failure durations cluster exactly at the timeout values. Remove per-assertion time bounds as a class: assertions and condition waits share one generous budget (9 minutes, via the expect/action defaults and a page default timeout), and the test timeout (10 minutes) becomes a pure hang detector rather than a performance expectation. Condition waits return the moment they are satisfied, so passing runs are exactly as fast as before; only genuine failures report slower. Explicit backend/mock readiness probes deliberately keep tight budgets, so a broken world still fails fast. --- tests/e2e/agent-type-change.spec.ts | 7 +- tests/e2e/archive.spec.ts | 10 +- tests/e2e/ask-answer.spec.ts | 231 +++++++----------- tests/e2e/ask-user-question.spec.ts | 19 +- tests/e2e/attention-indicators.spec.ts | 3 +- tests/e2e/auto-suggest.spec.ts | 14 +- tests/e2e/background-command.spec.ts | 12 +- tests/e2e/basic-flow.spec.ts | 32 ++- .../blank-mode-switch-message-loss.spec.ts | 6 +- ...x-app-server-startup-serialization.spec.ts | 6 +- tests/e2e/codex-compaction.spec.ts | 4 +- tests/e2e/codex-file-viewer.spec.ts | 10 +- tests/e2e/codex-fork.spec.ts | 31 +-- ...codex-function-call-output-history.spec.ts | 60 ++--- tests/e2e/codex-subtask-duplicate.spec.ts | 3 +- tests/e2e/codex-tool-turn-delimit.spec.ts | 7 +- tests/e2e/codex-web-search.spec.ts | 12 +- tests/e2e/commit-output-type.spec.ts | 30 +-- tests/e2e/connection-status.spec.ts | 5 +- tests/e2e/continuation-orphan.spec.ts | 3 +- tests/e2e/continuation.spec.ts | 46 ++-- tests/e2e/copilot-tool-turn-delimit.spec.ts | 2 +- tests/e2e/discover.spec.ts | 27 +- tests/e2e/draft-cross-tab-sync.spec.ts | 9 +- tests/e2e/draft-persistence.spec.ts | 24 +- tests/e2e/draft-task-lifecycle.spec.ts | 83 +++---- tests/e2e/draft-type-persistence.spec.ts | 22 +- tests/e2e/dropdown-reset-on-type.spec.ts | 17 +- tests/e2e/edit-raw-event.spec.ts | 58 ++--- tests/e2e/export-html.spec.ts | 2 +- tests/e2e/fd-leak.spec.ts | 4 +- tests/e2e/fixtures.ts | 14 +- tests/e2e/follow-up-markdown.spec.ts | 22 +- tests/e2e/frontend-update-notice.spec.ts | 4 +- tests/e2e/history-load-error.spec.ts | 8 +- tests/e2e/history-order.spec.ts | 1 - tests/e2e/image-paste.spec.ts | 10 +- tests/e2e/isolated-entry-worktree.spec.ts | 6 +- tests/e2e/markdown-diff-toggle.spec.ts | 2 +- tests/e2e/notices-empty-auth.spec.ts | 3 +- tests/e2e/optimistic-message.spec.ts | 34 +-- tests/e2e/parallel-task-null.spec.ts | 13 +- tests/e2e/permission-prompt-ask.spec.ts | 4 +- ...uest-history-subscription-boundary.spec.ts | 4 +- tests/e2e/rerender-stability.spec.ts | 2 +- tests/e2e/resume.spec.ts | 103 ++++---- tests/e2e/sandbox-env-djinja.spec.ts | 6 +- tests/e2e/session-ending.spec.ts | 19 +- tests/e2e/session-import.spec.ts | 45 ++-- tests/e2e/session-lifecycle.spec.ts | 25 +- tests/e2e/session-management.spec.ts | 4 +- tests/e2e/session-switch-rerender.spec.ts | 5 +- tests/e2e/sidebar-navigation.spec.ts | 4 +- tests/e2e/source-view-seq-alignment.spec.ts | 12 +- tests/e2e/stderr-handling.spec.ts | 16 +- tests/e2e/subagent-rendering.spec.ts | 3 +- tests/e2e/subtask-missing-outputs-end.spec.ts | 25 +- tests/e2e/subtask-orphan-exit.spec.ts | 9 +- tests/e2e/subtask-result.spec.ts | 3 +- tests/e2e/suggest-context.spec.ts | 2 +- tests/e2e/suggestion-header.spec.ts | 4 +- tests/e2e/system-messages.spec.ts | 17 +- tests/e2e/task-spawn-link.spec.ts | 3 +- tests/e2e/task-type-change.spec.ts | 7 +- tests/e2e/two-agents-one-driver.spec.ts | 12 +- tests/e2e/ui-regression.spec.ts | 21 +- tests/e2e/undo-claude-live-middle.spec.ts | 46 ++-- tests/e2e/undo-codex-alive.spec.ts | 45 ++-- tests/e2e/undo-file-revert.spec.ts | 9 +- tests/e2e/undo-steering.spec.ts | 16 +- tests/e2e/undo-while-running.spec.ts | 22 +- tests/e2e/undo.spec.ts | 52 ++-- tests/e2e/view-source-events.spec.ts | 6 +- tests/e2e/virtual-project-workspace.spec.ts | 10 +- .../worktree-archive-persisted-queue.spec.ts | 14 +- tests/e2e/worktree-archive.spec.ts | 46 ++-- tests/e2e/worktree-fork.spec.ts | 29 +-- tests/e2e/worktree-subproject.spec.ts | 17 +- tests/e2e/worktree-write-conflict.spec.ts | 2 +- tests/failure/process-failure.spec.ts | 6 +- tests/playwright.config.ts | 18 +- 81 files changed, 682 insertions(+), 927 deletions(-) diff --git a/tests/e2e/agent-type-change.spec.ts b/tests/e2e/agent-type-change.spec.ts index 444edea3..156a9da9 100644 --- a/tests/e2e/agent-type-change.spec.ts +++ b/tests/e2e/agent-type-change.spec.ts @@ -26,7 +26,7 @@ async function waitForNewTid(page: Page, before: Set): Promise { ); newTid = tids.find((tid: string) => !before.has(tid)); expect(newTid).toBeTruthy(); - }).toPass({ timeout: 5_000 }); + }).toPass(); return newTid!; } @@ -62,8 +62,7 @@ test("changing agent type after draft creation updates backend", async ({ // Agent picker should be visible in draft mode await expect(page.locator(".agent-picker")).toBeVisible({ - timeout: 5_000, - }); + }); const before = await snapshotTids(page); @@ -76,7 +75,7 @@ test("changing agent type after draft creation updates backend", async ({ const draftTid = await waitForNewTid(page, before); await expect( page.locator(`.sidebar-item[data-tid="${draftTid}"] .draft-label`), - ).toBeVisible({ timeout: 2_000 }); + ).toBeVisible(); // Change the agent picker to a different agent await page.locator(".agent-picker").selectOption(targetAgent); diff --git a/tests/e2e/archive.spec.ts b/tests/e2e/archive.spec.ts index d0150683..d57b6970 100644 --- a/tests/e2e/archive.spec.ts +++ b/tests/e2e/archive.spec.ts @@ -217,8 +217,7 @@ test("archived task cannot be resumed without unarchiving", async ({ await resumeBtn.click(); // The task must NOT become alive (stop button must not appear) await expect(page.locator(".btn-banner-stop")).not.toBeVisible({ - timeout: 3_000, - }); + }); await expect(resumeBtn).toBeVisible(); } }); @@ -244,12 +243,9 @@ test("URL routing for archive nodes", async ({ page, agentType }) => { await page.reload(); // Archive node must still be selected after reload - await expect(page.locator(".sidebar-archive-node")).toHaveClass(/active/, { - timeout: 15_000, - }); + await expect(page.locator(".sidebar-archive-node")).toHaveClass(/active/); // Archive placeholder must be shown (not "Loading task...") await expect(page.locator(".archive-placeholder")).toBeVisible({ - timeout: 15_000, - }); + }); }); diff --git a/tests/e2e/ask-answer.spec.ts b/tests/e2e/ask-answer.spec.ts index e8399f3e..b16a6455 100644 --- a/tests/e2e/ask-answer.spec.ts +++ b/tests/e2e/ask-answer.spec.ts @@ -2,7 +2,6 @@ import { test, expect, enterSession, sendMessage } from "./fixtures"; import type { Locator, Page } from "@playwright/test"; // All ask/answer tests require launching sub-tasks which takes more time. -const TALK_TIMEOUT = 120_000; type TaskResultItem = Record; type TaskCreatedEventLike = { @@ -205,7 +204,7 @@ async function waitForTaskResultEventAfter( sinceIndex: number, options: { timeout?: number } = {}, ): Promise { - const { timeout = 90_000 } = options; + const { timeout = 540_000 } = options; await expect .poll(() => observed.length, { timeout }) .toBeGreaterThan(sinceIndex); @@ -238,7 +237,7 @@ async function waitForTaskResultItem( predicate: (item: TaskResultItem) => boolean, options: { sinceIndex?: number; timeout?: number } = {}, ): Promise { - const { sinceIndex = 0, timeout = 90_000 } = options; + const { sinceIndex = 0, timeout = 540_000 } = options; let matchedItem: TaskResultItem | undefined; await expect .poll( @@ -276,8 +275,7 @@ async function extractLatestQuestionQid(page: Page): Promise { async function activeTid(page: Page): Promise { await expect(page.locator(".sidebar-item.active").first()).toBeVisible({ - timeout: 30_000, - }); + }); const rawTid = await page .locator(".sidebar-item.active") .first() @@ -299,14 +297,12 @@ async function createTopLevelTask(page: Page): Promise { .locator('[style*="display: contents"] .message-list') .getByText(marker, { exact: true }) .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); await expect .poll(async () => { const tids = await sidebarTids(page); return tids[tids.length - 1] ?? 0; - }, { - timeout: 60_000, }) .toBeGreaterThan(beforeMaxTid); @@ -316,15 +312,12 @@ async function createTopLevelTask(page: Page): Promise { async function openTask(page: Page, tid: number): Promise { await page.locator(`.sidebar-item[data-tid="${tid}"]`).click(); await expect(page.locator(`.sidebar-item[data-tid="${tid}"].active`)).toBeVisible({ - timeout: 10_000, - }); + }); } async function sidebarTids(page: Page): Promise { await expect - .poll(async () => page.locator(".sidebar-item[data-tid]").count(), { - timeout: 30_000, - }) + .poll(async () => page.locator(".sidebar-item[data-tid]").count()) .toBeGreaterThan(0); return page.locator(".sidebar-item[data-tid]").evaluateAll((nodes) => nodes @@ -338,7 +331,6 @@ test("Ask/Answer: follow-up to completed sub-task", async ({ page, agentType, }) => { - test.setTimeout(TALK_TIMEOUT); const taskCreatedEvents = observeTaskCreatedEvents(page); const reloadEvents = observeTaskReloadEvents(page); const answerEvents = observeParentAnswerEvents(page, 1); @@ -354,7 +346,7 @@ test("Ask/Answer: follow-up to completed sub-task", async ({ .locator('[style*="display: contents"] .message-list') .getByText("initial-result", { exact: true }) .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); // Wait for the parent's turn to complete (mock responds "Done." after seeing // the Task tool result). This ensures the session is in "alive" state before @@ -365,7 +357,7 @@ test("Ask/Answer: follow-up to completed sub-task", async ({ .locator('[style*="display: contents"] .message-list') .getByText("Done.", { exact: true }) .last(), - ).toBeVisible({ timeout: 30_000 }); + ).toBeVisible(); // L137's Task call drifts focus to the child. Wait for the corrective // focus_hint(child → parent) to land (child completes its turn and @@ -374,7 +366,7 @@ test("Ask/Answer: follow-up to completed sub-task", async ({ // focus returns to parent. await expect( page.locator('.sidebar-item[data-tid="1"].active'), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); let childTid: number | null = null; await expect(async () => { @@ -384,7 +376,7 @@ test("Ask/Answer: follow-up to completed sub-task", async ({ event.relation_type === "subtask" && event.parent_tid === 1, )?.tid ?? null; expect(childTid).not.toBeNull(); - }).toPass({ timeout: 30_000 }); + }).toPass(); const doneCountBeforeFollowUpAsk = await currentMessageList(page) .getByText("Done.", { exact: true }) @@ -402,12 +394,11 @@ test("Ask/Answer: follow-up to completed sub-task", async ({ .locator('[style*="display: contents"] .message-list') .getByText("follow-up-answered", { exact: true }) .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); await expect .poll( () => answerEvents.slice(answerStart).some((event) => event.kind === "output"), - { timeout: 90_000 }, ) .toBe(true); @@ -430,8 +421,6 @@ test("Ask/Answer: follow-up to completed sub-task", async ({ await expect .poll(async () => { return currentMessageList(page).getByText("Done.", { exact: true }).count(); - }, { - timeout: 30_000, }) .toBeGreaterThan(doneCountBeforeFollowUpAsk); } @@ -442,7 +431,7 @@ test("Ask/Answer: follow-up to completed sub-task", async ({ '[style*="display: contents"] .message-list .message.user-message.system-user-message', { hasText: "Follow-up from parent" }, ), - ).toBeVisible({ timeout: 30_000 }); + ).toBeVisible(); await expect .poll( @@ -450,29 +439,26 @@ test("Ask/Answer: follow-up to completed sub-task", async ({ reloadEvents .slice(reloadCountBeforeFollowUpAsk) .some((event) => event.tid === childTid), - { timeout: 90_000 }, ) .toBe(true); await page.reload(); await openTask(page, 1); await expect(page.locator('.sidebar-item[data-tid="1"].active')).toBeVisible({ - timeout: 30_000, - }); + }); await openTask(page, childTid!); await expect( page.locator( '[style*="display: contents"] .message-list .message.user-message.system-user-message', { hasText: "Follow-up from parent" }, ), - ).toBeVisible({ timeout: 30_000 }); + ).toBeVisible(); }); test("Ask/Answer: child asks parent, parent answers", async ({ page, agentType, }) => { - test.setTimeout(TALK_TIMEOUT); const observedTaskResults = observeTaskResultItems(page); await enterSession(page); @@ -490,12 +476,10 @@ test("Ask/Answer: child asks parent, parent answers", async ({ // then navigate back to the parent (tid=1) to see the Task result with the question. await page.locator('.sidebar-item[data-tid="2"]').waitFor({ state: "visible", - timeout: 30_000, - }); + }); await page.locator('.sidebar-item[data-tid="1"]').click(); await expect(page.locator('.sidebar-item[data-tid="1"].active')).toBeVisible({ - timeout: 10_000, - }); + }); // Wait for question to appear in parent's Task tool result. await expect( @@ -503,7 +487,7 @@ test("Ask/Answer: child asks parent, parent answers", async ({ .locator('[style*="display: contents"] .message-list') .getByText("what approach should I use?") .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); const questionResult = await waitForTaskResultItem( observedTaskResults, @@ -522,7 +506,7 @@ test("Ask/Answer: child asks parent, parent answers", async ({ .locator('[style*="display: contents"] .message-list') .getByText("Done.", { exact: true }) .last(), - ).toBeVisible({ timeout: 30_000 }); + ).toBeVisible(); // Parent answers the pending question using the observed qid. await sendMessage(page, `call answer ${capturedQid} use approach A`); @@ -535,13 +519,12 @@ test("Ask/Answer: child asks parent, parent answers", async ({ '[style*="display: contents"] .message-list .tool-result, [style*="display: contents"] .message-list .cydo-task-spec', ) .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); }); test("Ask/Answer: parent can answer child question after another Task call", async ({ page, }) => { - test.setTimeout(TALK_TIMEOUT); const observedTaskResults = observeTaskResultItems(page); const observedTaskEvents = observeTaskResultEvents(page); @@ -554,7 +537,7 @@ test("Ask/Answer: parent can answer child question after another Task call", asy .locator('[style*="display: contents"] .message-list') .getByText("interleaved question") .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); const questionResult = await waitForTaskResultItem( observedTaskResults, @@ -570,7 +553,7 @@ test("Ask/Answer: parent can answer child question after another Task call", asy .locator('[style*="display: contents"] .message-list') .getByText("Done.", { exact: true }) .last(), - ).toBeVisible({ timeout: 30_000 }); + ).toBeVisible(); const beforeInterveningTaskResultIndex = observedTaskResults.length; await sendMessage(page, 'call task research reply with "interleaving-child-done"'); @@ -579,7 +562,7 @@ test("Ask/Answer: parent can answer child question after another Task call", asy .locator('[style*="display: contents"] .message-list') .getByText("interleaving-child-done", { exact: true }) .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); await expect( waitForTaskResultItem( observedTaskResults, @@ -590,8 +573,7 @@ test("Ask/Answer: parent can answer child question after another Task call", asy await page.locator('.sidebar-item[data-tid="1"]').click(); await expect(page.locator('.sidebar-item[data-tid="1"].active')).toBeVisible({ - timeout: 10_000, - }); + }); const beforeAnswerTaskResultIndex = observedTaskResults.length; const beforeAnswerEventCount = observedTaskEvents.length; @@ -622,7 +604,6 @@ test("Ask/Answer: completed task result exposes success status and preserved fie page, agentType, }) => { - test.setTimeout(TALK_TIMEOUT); const observedTaskResults = observeTaskResultItems(page); const observedTaskEvents = observeTaskResultEvents(page); @@ -630,9 +611,7 @@ test("Ask/Answer: completed task result exposes success status and preserved fie await sendMessage(page, 'call task research reply with "structured-success"'); const taskTool = lastTaskTool(page); - await expect(taskTool).toContainText("structured-success", { - timeout: 90_000, - }); + await expect(taskTool).toContainText("structured-success"); expect( await waitForTaskResultItem( @@ -667,16 +646,13 @@ test("Ask/Answer: task validation errors expose error status and error field", a page, agentType, }) => { - test.setTimeout(TALK_TIMEOUT); const observedTaskResults = observeTaskResultItems(page); await enterSession(page); await sendMessage(page, "call task invalid_type reproduce the bug"); const taskTool = lastTaskTool(page); - await expect(taskTool).toContainText(/not in creatable_tasks/i, { - timeout: 90_000, - }); + await expect(taskTool).toContainText(/not in creatable_tasks/i); expect( await waitForTaskResultItem( @@ -696,14 +672,13 @@ test("Ask/Answer: task summaries preserve literal JSON-looking child final text" page, agentType, }) => { - test.setTimeout(TALK_TIMEOUT); const observedTaskResults = observeTaskResultItems(page); await enterSession(page); await sendMessage(page, "call task research reply with json-summary-fixture"); const taskTool = lastTaskTool(page); - await expect(taskTool).toContainText("qid", { timeout: 90_000 }); + await expect(taskTool).toContainText("qid"); expect( await waitForTaskResultItem( observedTaskResults, @@ -723,7 +698,6 @@ test("Ask/Answer: batch with one completing child and one asking child", async ( page, agentType, }) => { - test.setTimeout(TALK_TIMEOUT); const observedTaskResults = observeTaskResultItems(page); @@ -741,7 +715,7 @@ test("Ask/Answer: batch with one completing child and one asking child", async ( .locator('[style*="display: contents"] .message-list') .getByText("what approach should I use?") .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); // Wait for the parent's turn to complete after returning the question. // Sending Answer while this turn is still finalizing can race and skip @@ -751,7 +725,7 @@ test("Ask/Answer: batch with one completing child and one asking child", async ( .locator('[style*="display: contents"] .message-list') .getByText("Done.", { exact: true }) .last(), - ).toBeVisible({ timeout: 30_000 }); + ).toBeVisible(); // Capture the qid from child B's observed question result. const questionResult = await waitForTaskResultItem( @@ -774,7 +748,7 @@ test("Ask/Answer: batch with one completing child and one asking child", async ( ) .last() .locator(".cydo-task-spec"); - await expect(finalSpecs).toHaveCount(2, { timeout: 90_000 }); + await expect(finalSpecs).toHaveCount(2); await expect( finalSpecs.nth(0).locator('[data-testid="cydo-task-spec-open"]'), ).toHaveAttribute("href", /\/task\/2$/); @@ -788,7 +762,6 @@ test("Ask/Answer: same-workspace top-level peer Ask succeeds", async ({ page, agentType, }) => { - test.setTimeout(TALK_TIMEOUT); await enterSession(page); await sendMessage(page, 'reply with "peer-root-ready"'); await expect( @@ -796,7 +769,7 @@ test("Ask/Answer: same-workspace top-level peer Ask succeeds", async ({ .locator('[style*="display: contents"] .message-list') .getByText("peer-root-ready", { exact: true }) .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); const askerTid = await activeTid(page); // Create another top-level task. @@ -821,7 +794,6 @@ test("Ask/Answer: same-workspace top-level peer Ask succeeds", async ({ if (await peerQuestion.isVisible()) return "questioned"; return "pending"; }, - { timeout: 60_000 }, ) .not.toBe("pending"); @@ -835,7 +807,7 @@ test("Ask/Answer: same-workspace top-level peer Ask succeeds", async ({ .locator('[style*="display: contents"] .message-list') .getByText("peer-answer", { exact: true }) .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); await expect( page.locator(`.sidebar-item[data-tid="${askerTid}"] .task-type-icon.waiting`), ).not.toBeVisible(); @@ -845,7 +817,6 @@ test("Ask/Answer: same-workspace non-direct Ask succeeds", async ({ page, agentType, }) => { - test.setTimeout(TALK_TIMEOUT); await enterSession(page); await sendMessage(page, 'reply with "non-direct-root-ready"'); await expect( @@ -853,7 +824,7 @@ test("Ask/Answer: same-workspace non-direct Ask succeeds", async ({ .locator('[style*="display: contents"] .message-list') .getByText("non-direct-root-ready", { exact: true }) .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); const rootTid = await activeTid(page); const observedRootTaskResults = observeTaskResultItems(page, rootTid); @@ -866,7 +837,7 @@ test("Ask/Answer: same-workspace non-direct Ask succeeds", async ({ .locator('[style*="display: contents"] .message-list') .getByText("non-direct-leaf-ready", { exact: true }) .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); const leafTaskResult = await waitForTaskResultItem( observedRootTaskResults, (item) => @@ -878,8 +849,7 @@ test("Ask/Answer: same-workspace non-direct Ask succeeds", async ({ expect(leafTid).toBeGreaterThan(askerTid); await page.locator(`.sidebar-item[data-tid="${leafTid}"]`).waitFor({ state: "visible", - timeout: 30_000, - }); + }); // Ask from top-level peer to a non-direct task in the same workspace. await openTask(page, askerTid); @@ -899,19 +869,18 @@ test("Ask/Answer: same-workspace non-direct Ask succeeds", async ({ if (await nonDirectAnswer.isVisible()) return "answered"; return "pending"; }, - { timeout: 60_000 }, ) .not.toBe("pending"); await openTask(page, leafTid); await expect( page.locator(".system-user-message").filter({ hasText: /Question from task.*qid=/ }).last(), - ).toBeVisible({ timeout: 60_000 }); + ).toBeVisible(); await openTask(page, askerTid); await expect( nonDirectAnswer, - ).toBeVisible({ timeout: 60_000 }); + ).toBeVisible(); await expect( page.locator(`.sidebar-item[data-tid="${askerTid}"] .task-type-icon.waiting`), ).not.toBeVisible(); @@ -921,7 +890,6 @@ test("Ask/Answer: wrong answerer gets Unknown question ID", async ({ page, agentType, }) => { - test.setTimeout(TALK_TIMEOUT); const observedTaskResults = observeTaskResultItems(page); await enterSession(page); @@ -931,7 +899,7 @@ test("Ask/Answer: wrong answerer gets Unknown question ID", async ({ .locator('[style*="display: contents"] .message-list') .getByText("what approach should I use?") .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); const questionResult = await waitForTaskResultItem( observedTaskResults, (item) => @@ -956,7 +924,7 @@ test("Ask/Answer: wrong answerer gets Unknown question ID", async ({ .locator('[style*="display: contents"] .message-list') .getByText(/unknown question id/i) .last(), - ).toBeVisible({ timeout: 60_000 }); + ).toBeVisible(); // Authorized answerer responds and unblocks the waiting child. await openTask(page, askerTid); @@ -968,14 +936,13 @@ test("Ask/Answer: wrong answerer gets Unknown question ID", async ({ .locator('[style*="display: contents"] .message-list') .getByText("recovered-answer", { exact: true }) .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); }); test("Ask/Answer: self Ask is rejected", async ({ page, agentType, }) => { - test.setTimeout(TALK_TIMEOUT); await enterSession(page); await sendMessage(page, 'reply with "self-ask-root-ready"'); await expect( @@ -983,7 +950,7 @@ test("Ask/Answer: self Ask is rejected", async ({ .locator('[style*="display: contents"] .message-list') .getByText("self-ask-root-ready", { exact: true }) .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); const selfTid = await activeTid(page); await sendMessage(page, `call ask ${selfTid} hello`); @@ -993,7 +960,7 @@ test("Ask/Answer: self Ask is rejected", async ({ .locator('[style*="display: contents"] .message-list') .getByText(/ask target must be a different task/i) .last(), - ).toBeVisible({ timeout: 60_000 }); + ).toBeVisible(); await expect( page.locator(`.sidebar-item[data-tid="${selfTid}"] .task-type-icon.waiting`), ).not.toBeVisible(); @@ -1003,7 +970,6 @@ test("Ask/Answer: invalid Ask target returns error", async ({ page, agentType, }) => { - test.setTimeout(TALK_TIMEOUT); await enterSession(page); @@ -1016,14 +982,13 @@ test("Ask/Answer: invalid Ask target returns error", async ({ .locator('[style*="display: contents"] .message-list') .getByText(/not found/i) .last(), - ).toBeVisible({ timeout: 60_000 }); + ).toBeVisible(); }); test("Ask/Answer: tid field present in Task results", async ({ page, agentType, }) => { - test.setTimeout(TALK_TIMEOUT); await enterSession(page); @@ -1032,13 +997,11 @@ test("Ask/Answer: tid field present in Task results", async ({ await page.locator('.sidebar-item[data-tid="2"]').waitFor({ state: "visible", - timeout: 30_000, - }); + }); await page.locator('.sidebar-item[data-tid="1"]').click(); await expect(page.locator('.sidebar-item[data-tid="1"].active')).toBeVisible({ - timeout: 10_000, - }); + }); // Wait for the result to appear in the parent transcript. await expect( @@ -1046,7 +1009,7 @@ test("Ask/Answer: tid field present in Task results", async ({ .locator('[style*="display: contents"] .message-list') .getByText("check-tid", { exact: true }) .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); // The tid field should be visible in the tool result display. // The task result item renders remaining fields (after summary/error) as key: value. @@ -1055,14 +1018,13 @@ test("Ask/Answer: tid field present in Task results", async ({ .locator('[style*="display: contents"] .message-list .cydo-task-spec') .getByText(/tid/) .last(), - ).toBeVisible({ timeout: 10_000 }); + ).toBeVisible(); }); test("Ask/Answer: two children asking simultaneously are queued", async ({ page, agentType, }) => { - test.setTimeout(TALK_TIMEOUT); const observedTaskResults = observeTaskResultItems(page); const taskCreatedEvents = observeTaskCreatedEvents(page); @@ -1086,18 +1048,16 @@ test("Ask/Answer: two children asking simultaneously are queued", async ({ expect(subtaskTids.length).toBeGreaterThanOrEqual(2); secondChildTid = subtaskTids[1] ?? null; expect(secondChildTid).not.toBeNull(); - }).toPass({ timeout: 30_000 }); + }).toPass(); // Wait for both children to appear in the sidebar (confirms auto-focus to tid=2 // has settled), then navigate back to the parent (tid=1) to see the Task result. await page.locator(`.sidebar-item[data-tid="${secondChildTid}"]`).waitFor({ state: "visible", - timeout: 30_000, - }); + }); await page.locator('.sidebar-item[data-tid="1"]').click(); await expect(page.locator('.sidebar-item[data-tid="1"].active')).toBeVisible({ - timeout: 10_000, - }); + }); // Wait for the first question to appear in parent's Task tool result. await expect( @@ -1105,7 +1065,7 @@ test("Ask/Answer: two children asking simultaneously are queued", async ({ .locator('[style*="display: contents"] .message-list') .getByText("what approach?") .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); // Wait for parent's Turn 2 to complete (mock returns "Done." for the Task result). await expect( @@ -1113,7 +1073,7 @@ test("Ask/Answer: two children asking simultaneously are queued", async ({ .locator('[style*="display: contents"] .message-list') .getByText("Done.", { exact: true }) .last(), - ).toBeVisible({ timeout: 30_000 }); + ).toBeVisible(); // Capture qid of the first question from the observed task result. const firstQuestion = await waitForTaskResultItem( @@ -1137,7 +1097,7 @@ test("Ask/Answer: two children asking simultaneously are queued", async ({ // child textarea that gets hidden mid-action by the wrapper flip. await expect( page.locator('.sidebar-item[data-tid="1"].active'), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); // Capture qid of the second question from the next observed item/result after the first answer. const secondQuestion = await waitForTaskResultItem( @@ -1157,7 +1117,7 @@ test("Ask/Answer: two children asking simultaneously are queued", async ({ // :visible.first() against parent's textarea. await expect( page.locator('.sidebar-item[data-tid="1"].active'), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); // Answer the second child using the observed qid. await sendMessage(page, `call answer ${secondQid} answer two`); @@ -1169,14 +1129,13 @@ test("Ask/Answer: two children asking simultaneously are queued", async ({ '[style*="display: contents"] .message-list .tool-result, [style*="display: contents"] .message-list .cydo-task-spec', ) .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); }); test("Ask/Answer: Ask to active sub-task delivers follow-up message", async ({ page, agentType, }) => { - test.setTimeout(TALK_TIMEOUT); await enterSession(page); @@ -1191,12 +1150,10 @@ test("Ask/Answer: Ask to active sub-task delivers follow-up message", async ({ // created and the auto-focus to tid=2 has settled), then navigate to tid=1. await page.locator('.sidebar-item[data-tid="3"]').waitFor({ state: "visible", - timeout: 30_000, - }); + }); await page.locator('.sidebar-item[data-tid="1"]').click(); await expect(page.locator('.sidebar-item[data-tid="1"].active')).toBeVisible({ - timeout: 10_000, - }); + }); // Wait for child 3's question to appear in the parent's Task tool result. await expect( @@ -1204,7 +1161,7 @@ test("Ask/Answer: Ask to active sub-task delivers follow-up message", async ({ .locator('[style*="display: contents"] .message-list') .getByText("am I doing this right?") .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); // Wait for the parent's Turn 2 to complete: after the Task result with the // question is delivered, the mock responds "Done." which the parent outputs @@ -1215,7 +1172,7 @@ test("Ask/Answer: Ask to active sub-task delivers follow-up message", async ({ .locator('[style*="display: contents"] .message-list') .getByText("Done.", { exact: true }) .last(), - ).toBeVisible({ timeout: 30_000 }); + ).toBeVisible(); // Parent asks the stalling (active) child tid=2 with a follow-up. // This should succeed now — asking an active child is allowed. @@ -1227,14 +1184,13 @@ test("Ask/Answer: Ask to active sub-task delivers follow-up message", async ({ // The old behavior would have returned an error immediately without changing state. await expect( page.locator('.sidebar-item[data-tid="1"] .task-type-icon.waiting'), - ).toBeVisible({ timeout: 60_000 }); + ).toBeVisible(); }); test("Ask/Answer: Ask to busy (waiting) sub-task is enqueued", async ({ page, agentType, }) => { - test.setTimeout(TALK_TIMEOUT); await enterSession(page); @@ -1248,14 +1204,12 @@ test("Ask/Answer: Ask to busy (waiting) sub-task is enqueued", async ({ // created its sub-task and entered "waiting" status. await page.locator('.sidebar-item[data-tid="4"]').waitFor({ state: "visible", - timeout: 30_000, - }); + }); // Navigate to parent (tid=1) which should have the Task result. await page.locator('.sidebar-item[data-tid="1"]').click(); await expect(page.locator('.sidebar-item[data-tid="1"].active')).toBeVisible({ - timeout: 10_000, - }); + }); // Wait for the parent's turn to finish (mock responds "Done." after task result). await expect( @@ -1263,7 +1217,7 @@ test("Ask/Answer: Ask to busy (waiting) sub-task is enqueued", async ({ .locator('[style*="display: contents"] .message-list') .getByText("Done.", { exact: true }) .last(), - ).toBeVisible({ timeout: 30_000 }); + ).toBeVisible(); // Parent asks the busy (waiting) child tid=2. await sendMessage(page, "call ask 2 hey are you done?"); @@ -1271,14 +1225,13 @@ test("Ask/Answer: Ask to busy (waiting) sub-task is enqueued", async ({ // Ask is enqueued — parent enters "waiting" state (no error returned). await expect( page.locator('.sidebar-item[data-tid="1"] .task-type-icon.waiting'), - ).toBeVisible({ timeout: 60_000 }); + ).toBeVisible(); }); test("Ask/Answer: Ask to busy sub-task fails if child exits before delivery", async ({ page, agentType, }) => { - test.setTimeout(TALK_TIMEOUT); await enterSession(page); @@ -1289,32 +1242,29 @@ test("Ask/Answer: Ask to busy sub-task fails if child exits before delivery", as await page.locator('.sidebar-item[data-tid="4"]').waitFor({ state: "visible", - timeout: 30_000, - }); + }); await page.locator('.sidebar-item[data-tid="1"]').click(); await expect(page.locator('.sidebar-item[data-tid="1"].active')).toBeVisible({ - timeout: 10_000, - }); + }); await expect( page .locator('[style*="display: contents"] .message-list') .getByText("Done.", { exact: true }) .last(), - ).toBeVisible({ timeout: 30_000 }); + ).toBeVisible(); // Parent asks the busy child. Delivery is queued on the child's idle callbacks. await sendMessage(page, "call ask 2 child-exit-before-delivery?"); await expect( page.locator('.sidebar-item[data-tid="1"] .task-type-icon.waiting'), - ).toBeVisible({ timeout: 60_000 }); + ).toBeVisible(); // Stop the child before it can become idle and drain the queued injected Ask. await page.locator('.sidebar-item[data-tid="2"]').click(); await expect(page.locator('.sidebar-item[data-tid="2"].active')).toBeVisible({ - timeout: 10_000, - }); - await page.getByRole("button", { name: "Kill" }).click({ timeout: 30_000 }); + }); + await page.getByRole("button", { name: "Kill" }).click(); await page.locator('.sidebar-item[data-tid="1"]').click(); await expect( @@ -1322,14 +1272,13 @@ test("Ask/Answer: Ask to busy sub-task fails if child exits before delivery", as .locator('[style*="display: contents"] .message-list') .getByText(/Session ended while waiting for Ask response/i) .last(), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); }); test("Ask/Answer: yield enforcement steers parent with unanswered child question", async ({ page, agentType, }) => { - test.setTimeout(TALK_TIMEOUT * 2); const taskCreatedEvents = observeTaskCreatedEvents(page); await enterSession(page); @@ -1359,7 +1308,7 @@ test("Ask/Answer: yield enforcement steers parent with unanswered child question )?.tid ?? null; expect(parentTid).not.toBeNull(); expect(childTid).not.toBeNull(); - }).toPass({ timeout: 30_000 }); + }).toPass(); // Wait for the child sidebar item to settle before switching focus back to // the parent; otherwise the auto-focus chain can still move from parent→child @@ -1367,8 +1316,7 @@ test("Ask/Answer: yield enforcement steers parent with unanswered child question // wrong task. await page.locator(`.sidebar-item[data-tid="${childTid}"]`).waitFor({ state: "visible", - timeout: 30_000, - }); + }); // Navigate to the parent task to see its message list. await openTask(page, parentTid!); @@ -1379,7 +1327,7 @@ test("Ask/Answer: yield enforcement steers parent with unanswered child question .locator('[style*="display: contents"] .message-list') .getByText(/sub-task waiting for answer/i) .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); await page.reload(); await openTask(page, parentTid!); @@ -1388,14 +1336,13 @@ test("Ask/Answer: yield enforcement steers parent with unanswered child question .locator('[style*="display: contents"] .message-list') .getByText(/sub-task waiting for answer/i) .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); }); test("Ask/Answer: answer delivery is deferred until child becomes idle", async ({ page, agentType, }) => { - test.setTimeout(TALK_TIMEOUT); await enterSession(page); // Create a child that completes normally. @@ -1405,13 +1352,13 @@ test("Ask/Answer: answer delivery is deferred until child becomes idle", async ( .locator('[style*="display: contents"] .message-list') .getByText("initial-result", { exact: true }) .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); await expect( page .locator('[style*="display: contents"] .message-list') .getByText("Done.", { exact: true }) .last(), - ).toBeVisible({ timeout: 30_000 }); + ).toBeVisible(); // L713's Task call drifts focus to the child. Wait for the corrective // focus_hint(child → parent) to land (child completes its turn and @@ -1420,7 +1367,7 @@ test("Ask/Answer: answer delivery is deferred until child becomes idle", async ( // focus returns to parent. await expect( page.locator('.sidebar-item[data-tid="1"].active'), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); // Ask follow-up with "deferred-test" trigger — child will Answer + do extra Bash work. await sendMessage(page, "call ask 2 deferred-test"); @@ -1431,14 +1378,13 @@ test("Ask/Answer: answer delivery is deferred until child becomes idle", async ( .locator('[style*="display: contents"] .message-list') .getByText("deferred-answer-result", { exact: true }) .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); }); test("Ask/Answer: Answer with invalid qid returns error", async ({ page, agentType, }) => { - test.setTimeout(TALK_TIMEOUT); await enterSession(page); await sendMessage(page, "call answer 999 hello"); await expect( @@ -1446,7 +1392,7 @@ test("Ask/Answer: Answer with invalid qid returns error", async ({ .locator('[style*="display: contents"] .message-list') .getByText(/unknown question/i) .last(), - ).toBeVisible({ timeout: 60_000 }); + ).toBeVisible(); }); test("Ask/Answer: SwitchMode preserves unanswered child question", { tag: "@claude-only" }, async ({ @@ -1455,7 +1401,6 @@ test("Ask/Answer: SwitchMode preserves unanswered child question", { tag: "@clau }) => { // Claude-specific: only the Anthropic mock can reliably return SwitchMode // after a Task tool result in a deterministic sequence. - test.setTimeout(TALK_TIMEOUT * 2); await enterSession(page); @@ -1472,7 +1417,7 @@ test("Ask/Answer: SwitchMode preserves unanswered child question", { tag: "@clau hasText: "SwitchMode", }) .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); // Wait for the mode-switch system message divider. await expect( @@ -1481,7 +1426,7 @@ test("Ask/Answer: SwitchMode preserves unanswered child question", { tag: "@clau hasText: /Mode switch: plan/i, }) .last(), - ).toBeVisible({ timeout: 30_000 }); + ).toBeVisible(); // The resumed plan_mode receives the Sub-task waiting reminder and calls Answer. // Wait for the Answer tool call to appear — confirms the fix is working. @@ -1491,7 +1436,7 @@ test("Ask/Answer: SwitchMode preserves unanswered child question", { tag: "@clau hasText: "Answer", }) .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); // Wait for the child task (tid=2) to show as completed in the sidebar. // Research sub-tasks are resumable after completion, so accept either status class. @@ -1499,7 +1444,7 @@ test("Ask/Answer: SwitchMode preserves unanswered child question", { tag: "@clau page.locator( '.sidebar-item[data-tid="2"] .task-type-icon.completed, .sidebar-item[data-tid="2"] .task-type-icon.resumable', ), - ).toBeVisible({ timeout: 60_000 }); + ).toBeVisible(); // Wait for the final Task tool result (batch completed) to appear in the parent. await expect( @@ -1508,7 +1453,7 @@ test("Ask/Answer: SwitchMode preserves unanswered child question", { tag: "@clau '[style*="display: contents"] .message-list .cydo-task-spec', ) .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); }); test("Ask/Answer: Handoff rejected while child question is pending", { tag: "@claude-only" }, async ({ @@ -1517,7 +1462,6 @@ test("Ask/Answer: Handoff rejected while child question is pending", { tag: "@cl }) => { // Claude-specific: only the Anthropic mock can reliably return Handoff then Answer // after a Task tool result in a deterministic multi-step sequence. - test.setTimeout(TALK_TIMEOUT * 2); await enterSession(page); @@ -1534,8 +1478,7 @@ test("Ask/Answer: Handoff rejected while child question is pending", { tag: "@cl // Wait for the grandchild (tid=3) to appear in the sidebar. await page.locator('.sidebar-item[data-tid="3"]').waitFor({ state: "visible", - timeout: 30_000, - }); + }); // No continuation (tid=4) should be created — Handoff must be rejected. // Check immediately: if the backend incorrectly accepted Handoff, tid=4 would @@ -1553,12 +1496,12 @@ test("Ask/Answer: Handoff rejected while child question is pending", { tag: "@cl page.locator( '.sidebar-item[data-tid="3"] .task-type-icon.completed, .sidebar-item[data-tid="3"] .task-type-icon.resumable', ), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); // Wait for the child task (tid=2) to complete — confirms it recovered fully. await expect( page.locator( '.sidebar-item[data-tid="2"] .task-type-icon.completed, .sidebar-item[data-tid="2"] .task-type-icon.resumable', ), - ).toBeVisible({ timeout: 60_000 }); + ).toBeVisible(); }); diff --git a/tests/e2e/ask-user-question.spec.ts b/tests/e2e/ask-user-question.spec.ts index c4d9ebbd..351d4f59 100644 --- a/tests/e2e/ask-user-question.spec.ts +++ b/tests/e2e/ask-user-question.spec.ts @@ -46,7 +46,6 @@ test("ask_user_question clears on all connected clients when one answers", async agentType, }) => { // Copilot spawns a fresh cydo --mcp-server for each MCP tool call which adds latency - if (agentType === "copilot") test.setTimeout(120_000); // Page 1: create session and trigger AskUserQuestion await enterSession(page); @@ -54,7 +53,7 @@ test("ask_user_question clears on all connected clients when one answers", async // Wait for the AskUserForm to appear on page 1 const form1 = page.locator(".ask-user-form"); - await expect(form1).toBeVisible({ timeout: agentType === "copilot" ? 60_000 : responseTimeout(agentType) }); + await expect(form1).toBeVisible({ timeout: responseTimeout(agentType) }); // Extract the task ID from the URL so page 2 can navigate to the same task const url = page.url(); @@ -74,17 +73,17 @@ test("ask_user_question clears on all connected clients when one answers", async // Wait for the AskUserForm to appear on page 2 const form2 = page2.locator(".ask-user-form"); - await expect(form2).toBeVisible({ timeout: 15_000 }); + await expect(form2).toBeVisible(); // Page 1: answer the question by clicking "Yes" await page.locator(".ask-option-btn", { hasText: "Yes" }).click(); await page.locator(".ask-submit-btn").click(); // Page 1: form should be cleared (replaced by input box) - await expect(form1).not.toBeVisible({ timeout: 5_000 }); + await expect(form1).not.toBeVisible(); // Page 2: form should also be cleared - await expect(form2).not.toBeVisible({ timeout: 5_000 }); + await expect(form2).not.toBeVisible(); await context2.close(); }); @@ -102,7 +101,7 @@ test("sidebar shows asking status while AskUserQuestion is pending", async ({ // The active sidebar item should have the .asking class const sidebarItem = page.locator(".sidebar-item.active"); - await expect(sidebarItem).toHaveClass(/asking/, { timeout: 5_000 }); + await expect(sidebarItem).toHaveClass(/asking/); // The question icon should be visible const questionIcon = sidebarItem.locator(".task-type-icon-question"); @@ -113,13 +112,13 @@ test("sidebar shows asking status while AskUserQuestion is pending", async ({ await page.locator(".ask-submit-btn").click(); // Form should disappear - await expect(form).not.toBeVisible({ timeout: 5_000 }); + await expect(form).not.toBeVisible(); // The .asking class should be removed from the sidebar item - await expect(sidebarItem).not.toHaveClass(/asking/, { timeout: 5_000 }); + await expect(sidebarItem).not.toHaveClass(/asking/); // The question icon should no longer be visible - await expect(questionIcon).not.toBeVisible({ timeout: 5_000 }); + await expect(questionIcon).not.toBeVisible(); }); test("codex AskUserQuestion with delimiter text shows selected answer", { tag: "@codex-only" }, async ({ @@ -135,7 +134,7 @@ test("codex AskUserQuestion with delimiter text shows selected answer", { tag: " await page.locator(".ask-option-btn", { hasText: "Yes" }).click(); await page.locator(".ask-submit-btn").click(); - await expect(form).not.toBeVisible({ timeout: 5_000 }); + await expect(form).not.toBeVisible(); await expect( page.locator(".tool-call .ask-answer", { hasText: "Yes" }), diff --git a/tests/e2e/attention-indicators.spec.ts b/tests/e2e/attention-indicators.spec.ts index 04b43e8c..dbfe5efe 100644 --- a/tests/e2e/attention-indicators.spec.ts +++ b/tests/e2e/attention-indicators.spec.ts @@ -123,8 +123,7 @@ test("active sessions sort attention-needing tasks first with attention styling" await page.goto("/local/cydo-test-workspace"); await page.goto("/"); await expect(page.locator(".active-sessions-table")).toBeVisible({ - timeout: 10_000, - }); + }); // The first row in active sessions should be the attention-needing task. // Attention rows are sorted first by the welcome page reducer. diff --git a/tests/e2e/auto-suggest.spec.ts b/tests/e2e/auto-suggest.spec.ts index 1a6086cc..2bbe6923 100644 --- a/tests/e2e/auto-suggest.spec.ts +++ b/tests/e2e/auto-suggest.spec.ts @@ -18,7 +18,7 @@ test("suggestions appear after agent responds", async ({ page, agentType }) => { // Suggestions should appear asynchronously (from suggestion subprocess) const suggestions = page.locator(".btn-suggestion"); - await expect(suggestions.first()).toBeVisible({ timeout: 30_000 }); + await expect(suggestions.first()).toBeVisible(); // Should have 1-3 suggestions const count = await suggestions.count(); @@ -36,7 +36,7 @@ test("suggestions disappear when user types", async ({ page, agentType }) => { // Wait for suggestions const suggestions = page.locator(".btn-suggestion"); - await expect(suggestions.first()).toBeVisible({ timeout: 30_000 }); + await expect(suggestions.first()).toBeVisible(); // Type in input — suggestions should hide const input = page.locator(".input-textarea:visible").first(); @@ -45,7 +45,7 @@ test("suggestions disappear when user types", async ({ page, agentType }) => { // Clear input — suggestions reappear await input.fill(""); - await expect(suggestions.first()).toBeVisible({ timeout: 5_000 }); + await expect(suggestions.first()).toBeVisible(); }); test("clicking suggestion sends it immediately", async ({ @@ -61,7 +61,7 @@ test("clicking suggestion sends it immediately", async ({ // Wait for suggestions and get first button text const suggBtn = page.locator(".btn-suggestion").first(); - await expect(suggBtn).toBeVisible({ timeout: 30_000 }); + await expect(suggBtn).toBeVisible(); const suggText = await suggBtn.innerText(); // Click sends immediately @@ -74,7 +74,7 @@ test("clicking suggestion sends it immediately", async ({ // Should appear as a user message await expect( page.locator(".message.user-message", { hasText: suggText }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); // Input should be empty const input = page.locator(".input-textarea:visible").first(); @@ -93,7 +93,7 @@ test("shift+click suggestion pre-fills input without sending", async ({ }); const suggBtn = page.locator(".btn-suggestion").first(); - await expect(suggBtn).toBeVisible({ timeout: 30_000 }); + await expect(suggBtn).toBeVisible(); const suggText = await suggBtn.innerText(); // Shift+click to pre-fill @@ -101,7 +101,7 @@ test("shift+click suggestion pre-fills input without sending", async ({ // Should fill input with suggestion text, not send const input = page.locator(".input-textarea:visible").first(); - await expect(input).toHaveValue(suggText, { timeout: 5_000 }); + await expect(input).toHaveValue(suggText); // Only the original user message should exist (no new one sent) const userMsgCount = await page.locator(".message.user-message").count(); diff --git a/tests/e2e/background-command.spec.ts b/tests/e2e/background-command.spec.ts index 708b75ce..3738618a 100644 --- a/tests/e2e/background-command.spec.ts +++ b/tests/e2e/background-command.spec.ts @@ -46,8 +46,7 @@ test("background command spinner disappears after command completes", { tag: "@c // before this assertion, the spinner is already absent and this still // confirms the regression stays fixed. await expect(page.locator(".tool-icon-spinner")).not.toBeVisible({ - timeout: 10_000, - }); + }); }); // Regression test: multiple concurrent background commands. @@ -93,7 +92,7 @@ test("multiple background command spinners all disappear after completion", { ta // Wait for both commands to finish. // sleep 8 finishes ~8s after start, sleep 10 ~10s after start (offset by ~0.5s). - await expect(spinners).toHaveCount(0, { timeout: 20_000 }); + await expect(spinners).toHaveCount(0); }); // Regression test: late output_delta content after turn/stop is rendered. @@ -128,7 +127,7 @@ test("late command output appears after turn completes", { tag: "@codex-only" }, // block and rendered by the UI. await expect( page.locator(".tool-result", { hasText: "late-output-marker" }), - ).toBeVisible({ timeout: 10_000 }); + ).toBeVisible(); }); test("kill stops codex background command before delayed side effect", { tag: "@codex-only" }, async ({ @@ -199,10 +198,9 @@ test("killing one codex task also interrupts sibling session on pooled server", const siblingTask = page .locator(".sidebar-item:not(.active):not(.sidebar-new-task)") .first(); - await siblingTask.click({ timeout: 15_000 }); + await siblingTask.click(); await expect(page.locator(".btn-banner-resume")).toBeVisible({ - timeout: 30_000, - }); + }); await page.locator(".btn-banner-resume").click(); await sendMessage(page, 'Please reply with "sibling-resumed"'); diff --git a/tests/e2e/basic-flow.spec.ts b/tests/e2e/basic-flow.spec.ts index 5bec04a7..298ace08 100644 --- a/tests/e2e/basic-flow.spec.ts +++ b/tests/e2e/basic-flow.spec.ts @@ -12,8 +12,7 @@ import { test("page loads and shows CyDo branding", { tag: "@no-codex" }, async ({ page, agentType }) => { await page.goto("/"); await expect(page.locator(".welcome-page-header .logo-banner")).toBeVisible({ - timeout: 10_000, - }); + }); }); test( @@ -23,21 +22,19 @@ test( await page.goto("/"); const filterInput = page.locator(".welcome-filter-input"); - await expect(filterInput).toBeVisible({ timeout: 15_000 }); + await expect(filterInput).toBeVisible(); await filterInput.fill("cydo-test-workspace"); await filterInput.press("Enter"); - await expect(page).toHaveURL(/\/local\/cydo-test-workspace$/, { - timeout: 15_000, - }); + await expect(page).toHaveURL(/\/local\/cydo-test-workspace$/); await expect( page.locator(".sidebar-title", { hasText: "cydo-test-workspace" }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); await page.goBack(); - await expect(filterInput).toBeVisible({ timeout: 15_000 }); + await expect(filterInput).toBeVisible(); await expect(filterInput).toHaveValue("cydo-test-workspace"); }, ); @@ -46,13 +43,12 @@ test("basic message and response", async ({ page, agentType }) => { await enterSession(page); const input = page.locator(".input-textarea"); - await expect(input).toBeEnabled({ timeout: 15_000 }); + await expect(input).toBeEnabled(); await input.fill('Please reply with "OK"'); await page.getByRole("button", { name: "Send" }).click(); await expect(page.locator(".message.user-message")).toBeVisible({ - timeout: 15_000, - }); + }); await expect(lastAssistantText(page, "OK")).toBeVisible({ timeout: responseTimeout(agentType), @@ -65,7 +61,7 @@ test("tool call flow", async ({ page, agentType }) => { // Use base64 so the output ("aGVsbG8tZnJvbS10ZXN0Cg==") is distinct from // the input command — this lets us assert input and output independently. const input = page.locator(".input-textarea"); - await expect(input).toBeEnabled({ timeout: 15_000 }); + await expect(input).toBeEnabled(); await input.fill("Please run command echo hello-from-test | base64"); await page.getByRole("button", { name: "Send" }).click(); @@ -120,7 +116,7 @@ workspaces: await page.selectOption(".agent-picker", "codex"); const input = page.locator(".input-textarea"); - await expect(input).toBeEnabled({ timeout: 15_000 }); + await expect(input).toBeEnabled(); await input.fill('reply with "hello"'); await page.getByRole("button", { name: "Send" }).click(); @@ -128,7 +124,7 @@ workspaces: timeout: responseTimeout(agentType), }); // codex is not the default agent (claude is), so the banner should show "codex". - await expect(page.locator(".banner-agent")).toBeVisible({ timeout: 10_000 }); + await expect(page.locator(".banner-agent")).toBeVisible(); await expect(page.locator(".banner-agent")).toContainText("codex", { ignoreCase: true, }); @@ -166,9 +162,9 @@ test("codex file fixture shows view-file action", { tag: "@codex-only" }, async await tool.locator(".tool-header").hover(); const viewBtn = tool.locator(".tool-view-file"); - await expect(viewBtn).toBeVisible({ timeout: 5_000 }); + await expect(viewBtn).toBeVisible(); await viewBtn.click(); - await expect(page.locator(".file-viewer")).toBeVisible({ timeout: 5_000 }); + await expect(page.locator(".file-viewer")).toBeVisible(); await expect(page.locator(".file-viewer")).toContainText( "codex-fileviewer-create.txt", ); @@ -210,7 +206,7 @@ test("codex update fixture shows patch preview", { tag: "@codex-only" }, async ( // dispatchEvent bypasses CSS display:none on the hover-reveal button await tool.locator(".tool-view-file").dispatchEvent("click"); - await expect(page.locator(".file-viewer")).toBeVisible({ timeout: 5_000 }); + await expect(page.locator(".file-viewer")).toBeVisible(); await expect(page.locator(".file-viewer")).toContainText( "codex-fileviewer-create.txt", ); @@ -269,7 +265,7 @@ test("codex delete fixture shows deleted state", { tag: "@codex-only" }, async ( // dispatchEvent bypasses CSS display:none on the hover-reveal button await tool.locator(".tool-view-file").dispatchEvent("click"); - await expect(page.locator(".file-viewer")).toBeVisible({ timeout: 5_000 }); + await expect(page.locator(".file-viewer")).toBeVisible(); await expect(page.locator(".file-viewer")).toContainText( "codex-fileviewer-create.txt", ); diff --git a/tests/e2e/blank-mode-switch-message-loss.spec.ts b/tests/e2e/blank-mode-switch-message-loss.spec.ts index f4285998..3c31fa42 100644 --- a/tests/e2e/blank-mode-switch-message-loss.spec.ts +++ b/tests/e2e/blank-mode-switch-message-loss.spec.ts @@ -14,7 +14,7 @@ test("blank-mode entry point preserves first user message after SwitchMode", { t await sendMessage(page, FIRST); // Wait for an assistant text response so the message is fully processed and persisted in JSONL. - await expect(assistantText(page, /./).first()).toBeVisible({ timeout: 60_000 }); + await expect(assistantText(page, /./).first()).toBeVisible(); // Now trigger SwitchMode keep_context. This kills the codex agent, resets td.history, and the // continuation emits a task_reload that forces the frontend to re-request the JSONL. @@ -23,7 +23,7 @@ test("blank-mode entry point preserves first user message after SwitchMode", { t // Wait for the switch to surface — the mode-switch system message divider appears post-replay. await expect( page.locator(".result-divider.system-user-message", { hasText: "Mode switch: plan" }), - ).toBeVisible({ timeout: 60_000 }); + ).toBeVisible(); // ── Assertions that fail with the bug ─────────────────────────────────────── // 1) The first message must not have leaked into the input box. @@ -37,5 +37,5 @@ test("blank-mode entry point preserves first user message after SwitchMode", { t // `.result-divider.system-user-message` with the label, no body. await expect( page.locator(".message.user-message.system-user-message", { hasText: FIRST }), - ).toBeVisible({ timeout: 10_000 }); + ).toBeVisible(); }); diff --git a/tests/e2e/codex-app-server-startup-serialization.spec.ts b/tests/e2e/codex-app-server-startup-serialization.spec.ts index fc8d9d2e..0f52fdad 100644 --- a/tests/e2e/codex-app-server-startup-serialization.spec.ts +++ b/tests/e2e/codex-app-server-startup-serialization.spec.ts @@ -242,7 +242,7 @@ exec "$CYDO_REAL_CODEX_BIN" "$@" data.type === "task_created" && data.correlation_id === correlationId && typeof data.tid === "number", - 30_000, + 540_000, ); ws.send( JSON.stringify({ @@ -264,7 +264,7 @@ exec "$CYDO_REAL_CODEX_BIN" "$@" data.tid === tid && data.event?.type === "turn/result" && data.event?.subtype === "success", - 90_000, + 540_000, ); ws.send( JSON.stringify({ @@ -280,7 +280,7 @@ exec "$CYDO_REAL_CODEX_BIN" "$@" const historyEnd = waitForMessage( ws, (data) => data.type === "task_history_end" && data.tid === tid, - 30_000, + 540_000, ); ws.send(JSON.stringify({ type: "request_history", tid })); await historyEnd; diff --git a/tests/e2e/codex-compaction.spec.ts b/tests/e2e/codex-compaction.spec.ts index 9b30d458..adfaf2a2 100644 --- a/tests/e2e/codex-compaction.spec.ts +++ b/tests/e2e/codex-compaction.spec.ts @@ -88,7 +88,7 @@ test("codex reconnect during active turn does not replay stale compacting status await page.reload(); await page .locator('.sidebar-item[data-tid="1"]') - .click({ timeout: 15_000 }); + .click(); await expect(assistantText(page, "Done.")).toBeVisible({ timeout }); @@ -119,7 +119,7 @@ test("codex reconnect during active turn does not replay stale compacting status await page.reload(); await page .locator('.sidebar-item[data-tid="1"]') - .click({ timeout: 15_000 }); + .click(); await expect(assistantText(page, "Done.")).toBeVisible({ timeout }); const completedReplayFrames = frames.slice(beforeCompletedReloadIdx); diff --git a/tests/e2e/codex-file-viewer.spec.ts b/tests/e2e/codex-file-viewer.spec.ts index ff5d3be2..a168aead 100644 --- a/tests/e2e/codex-file-viewer.spec.ts +++ b/tests/e2e/codex-file-viewer.spec.ts @@ -54,7 +54,7 @@ test("file viewer shows diff content for codex update without prior create", { t ); await tool.locator(".tool-view-file").dispatchEvent("click"); - await expect(page.locator(".file-viewer")).toBeVisible({ timeout: 5_000 }); + await expect(page.locator(".file-viewer")).toBeVisible(); await expect(page.locator(".file-viewer")).toContainText( "codex-fileviewer-create.txt", ); @@ -109,7 +109,7 @@ test("cumulative diff shows net change after codex create and update", { tag: "@ await expect(tools).toHaveCount(2, { timeout }); await tools.last().locator(".tool-view-file").dispatchEvent("click"); - await expect(page.locator(".file-viewer")).toBeVisible({ timeout: 5_000 }); + await expect(page.locator(".file-viewer")).toBeVisible(); const contentViewer = page.locator(".file-viewer .content-viewer"); @@ -154,7 +154,7 @@ test("rendered markdown view includes all collected partial hunks", { tag: "@cod await tools.last().locator(".tool-view-file").dispatchEvent("click"); const contentViewer = page.locator(".file-viewer .content-viewer"); - await expect(page.locator(".file-viewer")).toBeVisible({ timeout: 5_000 }); + await expect(page.locator(".file-viewer")).toBeVisible(); await expect( contentViewer.getByRole("button", { name: "Rendered" }), ).toHaveClass(/active/); @@ -191,7 +191,7 @@ test("live codex fileChange markdown add renders inline preview and rendered vie ); await tool.locator(".tool-view-file").dispatchEvent("click"); - await expect(page.locator(".file-viewer")).toBeVisible({ timeout: 5_000 }); + await expect(page.locator(".file-viewer")).toBeVisible(); const contentViewer = page.locator(".file-viewer .content-viewer"); await expect( @@ -241,7 +241,7 @@ test("replayed apply_patch markdown sections render per-file previews and fallba } await expect( updateTool.locator(".markdown-diff-wrap .markdown-toggle-btn"), - ).toBeVisible({ timeout: 5_000 }); + ).toBeVisible(); await expect(updateTool).toContainText( "*** Update File: tmp/codex-inline-preview.md", ); diff --git a/tests/e2e/codex-fork.spec.ts b/tests/e2e/codex-fork.spec.ts index 8a1a8a36..a9bc6f99 100644 --- a/tests/e2e/codex-fork.spec.ts +++ b/tests/e2e/codex-fork.spec.ts @@ -31,14 +31,14 @@ async function waitForNewTid( ); newTid = tids.find((tid: string) => !before.has(tid)); expect(newTid).toBeTruthy(); - }).toPass({ timeout: 15_000 }); + }).toPass(); return newTid!; } async function resumeIfNeeded(page: Parameters[0]) { const resumeBtn = page.locator(".btn-banner-resume:visible").first(); const visible = await resumeBtn - .isVisible({ timeout: 5_000 }) + .isVisible() .catch(() => false); if (visible) { await resumeBtn.click(); @@ -118,9 +118,8 @@ test("codex fork from older turn truncates later history and isolates branches", .last(); await turnOneAssistant.hover(); await expect(turnOneAssistant.locator(".fork-btn")).toBeVisible({ - timeout: 5_000, - }); - }).toPass({ timeout: 20_000 }); + }); + }).toPass(); await page .locator("[style*='display: contents'] .message-wrapper", { @@ -136,7 +135,7 @@ test("codex fork from older turn truncates later history and isolates branches", event.relation_type === "fork" && event.parent_tid === parentTid, ); expect(fork).toBeTruthy(); - }).toPass({ timeout: 20_000 }); + }).toPass(); const forkTid = taskCreatedEvents.find( (event) => event.relation_type === "fork" && event.parent_tid === parentTid, @@ -146,14 +145,11 @@ test("codex fork from older turn truncates later history and isolates branches", if (!page.url().endsWith(`/task/${forkTid}`)) { await page.locator(`.sidebar-item[data-tid="${forkTid}"]`).click(); } - await expect(page).toHaveURL(new RegExp(`/task/${forkTid}$`), { - timeout: 5_000, - }); - }).toPass({ timeout: 20_000 }); + await expect(page).toHaveURL(new RegExp(`/task/${forkTid}$`)); + }).toPass(); await expect(activeAssistantText(page, turnOne)).toBeVisible({ - timeout: 15_000, - }); + }); await expect( page.locator( "[style*='display: contents'] [data-testid='assistant-text']", @@ -170,9 +166,7 @@ test("codex fork from older turn truncates later history and isolates branches", }); await page.locator(`.sidebar-item[data-tid="${parentTid}"]`).click(); - await expect(page).toHaveURL(new RegExp(`/task/${parentTid}$`), { - timeout: 15_000, - }); + await expect(page).toHaveURL(new RegExp(`/task/${parentTid}$`)); await resumeIfNeeded(page); await sendMessage(page, `Reply exactly with ${parentOnly}`); @@ -189,12 +183,9 @@ test("codex fork from older turn truncates later history and isolates branches", ).toHaveCount(0); await page.locator(`.sidebar-item[data-tid="${forkTid}"]`).click(); - await expect(page).toHaveURL(new RegExp(`/task/${forkTid}$`), { - timeout: 15_000, - }); + await expect(page).toHaveURL(new RegExp(`/task/${forkTid}$`)); await expect(activeAssistantText(page, forkOnly)).toBeVisible({ - timeout: 15_000, - }); + }); await expect( page.locator( "[style*='display: contents'] [data-testid='assistant-text']", diff --git a/tests/e2e/codex-function-call-output-history.spec.ts b/tests/e2e/codex-function-call-output-history.spec.ts index 0baa534c..0a10c633 100644 --- a/tests/e2e/codex-function-call-output-history.spec.ts +++ b/tests/e2e/codex-function-call-output-history.spec.ts @@ -195,13 +195,12 @@ async function seedTaskAndLocateRollout( await page.locator('button[title="New task"]').first().click(); const input = page.locator(".input-textarea:visible").first(); - await expect(input).toBeEnabled({ timeout: 15_000 }); + await expect(input).toBeEnabled(); await input.fill('reply with "seed-history"'); await page.locator(".btn-send:visible").first().click(); await expect(assistantText(page, "seed-history")).toBeVisible({ - timeout: 90_000, - }); + }); const taskUrl = page.url(); await page.waitForTimeout(1_000); @@ -223,13 +222,12 @@ async function replayAndFindTaskTool( await page.goto(taskUrl); await expect(assistantText(page, "seed-history")).toBeVisible({ - timeout: 15_000, - }); + }); const taskTool = page.locator(".tool-call").filter({ has: page.locator(".tool-name", { hasText: "Task" }), }); - await expect(taskTool).toBeVisible({ timeout: 15_000 }); + await expect(taskTool).toBeVisible(); return taskTool; } @@ -241,7 +239,7 @@ test("live invalid child task_type returns structured task error payload", { tag await page.locator('button[title="New task"]').first().click(); const input = page.locator(".input-textarea:visible").first(); - await expect(input).toBeEnabled({ timeout: 15_000 }); + await expect(input).toBeEnabled(); await input.fill("call task invalid_type reproduce the bug"); await page.locator(".btn-send:visible").first().click(); @@ -250,7 +248,7 @@ test("live invalid child task_type returns structured task error payload", { tag const taskTool = page.locator(".tool-call").filter({ has: page.locator(".tool-name", { hasText: "Task" }), }); - await expect(taskTool).toContainText(toolError, { timeout: 90_000 }); + await expect(taskTool).toContainText(toolError); const taskText = await taskTool.innerText(); expect(taskText).not.toContain("0: T"); @@ -312,7 +310,7 @@ test("codex history replay renders primitive task error as one message", { tag: restartableBackend, taskUrl, ); - await expect(taskTool).toContainText(toolError, { timeout: 15_000 }); + await expect(taskTool).toContainText(toolError); const taskText = await taskTool.innerText(); expect(taskText).not.toContain("0: T"); }); @@ -364,7 +362,7 @@ test("codex history replay renders structured task error object cleanly", { tag: restartableBackend, taskUrl, ); - await expect(taskTool).toContainText(toolError, { timeout: 15_000 }); + await expect(taskTool).toContainText(toolError); const taskText = await taskTool.innerText(); expect(taskText).not.toContain("0: T"); }); @@ -419,11 +417,9 @@ test("codex history replay keeps successful structured task rendering", { tag: " restartableBackend, taskUrl, ); - await expect(taskTool).toContainText("Task finished successfully", { - timeout: 15_000, - }); - await expect(taskTool).toContainText("output_file:", { timeout: 15_000 }); - await expect(taskTool).toContainText("/tmp/out.md", { timeout: 15_000 }); + await expect(taskTool).toContainText("Task finished successfully"); + await expect(taskTool).toContainText("output_file:"); + await expect(taskTool).toContainText("/tmp/out.md"); }); test("codex history replay renders custom exec output as one expanded tool", { tag: "@codex-only" }, async ({ @@ -437,8 +433,7 @@ test("codex history replay renders custom exec output as one expanded tool", { t await restartableBackend.restart(); await page.goto(taskUrl); await expect(assistantText(page, "seed-history")).toBeVisible({ - timeout: 15_000, - }); + }); const diagnostics = page .locator(".message.system-message") .filter({ hasText: "Unrecognized agent data" }); @@ -479,8 +474,7 @@ test("codex history replay renders custom exec output as one expanded tool", { t await restartableBackend.restart(); await page.goto(taskUrl); await expect(assistantText(page, "seed-history")).toBeVisible({ - timeout: 15_000, - }); + }); const execTool = page.locator(".tool-call").filter({ has: page.locator(".tool-name", { hasText: "exec" }), @@ -489,8 +483,7 @@ test("codex history replay renders custom exec output as one expanded tool", { t await expect(execTool.locator(".write-content")).toHaveText(script); await expect(execTool.locator(".field-label", { hasText: "input:" })).toHaveCount(0); await expect(execTool.locator(".write-content span").first()).toBeVisible({ - timeout: 15_000, - }); + }); const scriptCopy = execTool .locator(".code-pre-wrap") .first() @@ -518,8 +511,7 @@ test("codex history replay renders wait output", { tag: "@codex-only" }, async ( await restartableBackend.restart(); await page.goto(taskUrl); await expect(assistantText(page, "seed-history")).toBeVisible({ - timeout: 15_000, - }); + }); const diagnostics = page .locator(".message.system-message") .filter({ hasText: "Unrecognized agent data" }); @@ -569,8 +561,7 @@ test("codex history replay renders wait output", { tag: "@codex-only" }, async ( await restartableBackend.restart(); await page.goto(taskUrl); await expect(assistantText(page, "seed-history")).toBeVisible({ - timeout: 15_000, - }); + }); const waitTool = page.locator(".tool-call").filter({ has: page.locator(".tool-name", { hasText: "wait" }), @@ -623,7 +614,7 @@ test("codex history replay exposes unknown response-item payloads in dev mode", const diagnostic = page .locator(".message.system-message") .filter({ hasText: "future_codex_payload" }); - await expect(diagnostic).toBeVisible({ timeout: 15_000 }); + await expect(diagnostic).toBeVisible(); await expect(diagnostic).toContainText("Unrecognized agent data"); }); @@ -631,13 +622,12 @@ test("codex history replay renders live task output from organic rollout", { tag page, restartableBackend, }) => { - test.setTimeout(180_000); await page.goto("/"); await page.locator('button[title="New task"]').first().click(); const input = page.locator(".input-textarea:visible").first(); - await expect(input).toBeEnabled({ timeout: 15_000 }); + await expect(input).toBeEnabled(); const markdownItem = "Organic rollout markdown item"; await input.fill(`call task research reply with "1. ${markdownItem}"`); @@ -646,13 +636,13 @@ test("codex history replay renders live task output from organic rollout", { tag const taskTool = page.locator(".tool-call").filter({ has: page.locator(".tool-name", { hasText: "Task" }), }); - await expect(taskTool).toContainText("Open task", { timeout: 90_000 }); - await expect(taskTool).toContainText(markdownItem, { timeout: 90_000 }); + await expect(taskTool).toContainText("Open task"); + await expect(taskTool).toContainText(markdownItem); await expect( taskTool.locator(".text-content ol li", { hasText: markdownItem, }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); const taskUrl = page.url(); await page.waitForTimeout(1_000); @@ -672,14 +662,12 @@ test("codex history replay renders live task output from organic rollout", { tag const replayedTaskTool = page.locator(".tool-call").filter({ has: page.locator(".tool-name", { hasText: "Task" }), }); - await expect(replayedTaskTool).toContainText("Open task", { timeout: 15_000 }); - await expect(replayedTaskTool).toContainText(markdownItem, { - timeout: 15_000, - }); + await expect(replayedTaskTool).toContainText("Open task"); + await expect(replayedTaskTool).toContainText(markdownItem); await expect( replayedTaskTool.locator(".text-content ol li", { hasText: markdownItem, }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); await expect(replayedTaskTool).not.toContainText("Wall time:"); }); diff --git a/tests/e2e/codex-subtask-duplicate.spec.ts b/tests/e2e/codex-subtask-duplicate.spec.ts index 50268a7b..ff0ac1f1 100644 --- a/tests/e2e/codex-subtask-duplicate.spec.ts +++ b/tests/e2e/codex-subtask-duplicate.spec.ts @@ -18,7 +18,6 @@ import { test, expect, enterSession, sendMessage } from "./fixtures"; test("codex sub-task result should not be delivered twice", { tag: "@codex-only" }, async ({ page, }) => { - test.setTimeout(120_000); await enterSession(page); @@ -30,7 +29,7 @@ test("codex sub-task result should not be delivered twice", { tag: "@codex-only" const messageList = page.locator('[style*="display: contents"] .message-list'); await expect( messageList.getByText("unique-subtask-marker").last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); // Now check for the spurious duplicate: the batch delivery path sends a // message containing "session was interrupted". This should NOT appear. diff --git a/tests/e2e/codex-tool-turn-delimit.spec.ts b/tests/e2e/codex-tool-turn-delimit.spec.ts index 27792240..683c2cdd 100644 --- a/tests/e2e/codex-tool-turn-delimit.spec.ts +++ b/tests/e2e/codex-tool-turn-delimit.spec.ts @@ -42,17 +42,16 @@ test("codex tool turn produces separate assistant messages for tool call and res // one for "Done."). The intermediate turn/stop from thread/tokenUsage/updated // seals the first message before the second inference pass starts. const assistantMessages = page.locator(".message.assistant-message"); - await expect(assistantMessages).toHaveCount(2, { timeout: 5_000 }); + await expect(assistantMessages).toHaveCount(2); // First message should contain the tool call block (commandExecution) await expect(assistantMessages.first().locator(".tool-call")).toBeVisible({ - timeout: 5_000, - }); + }); // Second message should contain the "Done." text await expect( assistantMessages.nth(1).locator('[data-testid="assistant-text"]', { hasText: "Done.", }), - ).toBeVisible({ timeout: 5_000 }); + ).toBeVisible(); }); diff --git a/tests/e2e/codex-web-search.spec.ts b/tests/e2e/codex-web-search.spec.ts index 1d4fbc0d..f8c68320 100644 --- a/tests/e2e/codex-web-search.spec.ts +++ b/tests/e2e/codex-web-search.spec.ts @@ -172,13 +172,12 @@ async function seedTaskAndLocateRollout( await page.locator('button[title="New task"]').first().click(); const input = page.locator(".input-textarea:visible").first(); - await expect(input).toBeEnabled({ timeout: 15_000 }); + await expect(input).toBeEnabled(); await input.fill('reply with "seed-history"'); await page.locator(".btn-send:visible").first().click(); await expect(assistantText(page, "seed-history")).toBeVisible({ - timeout: 90_000, - }); + }); const taskUrl = page.url(); await page.waitForTimeout(1_000); @@ -228,7 +227,7 @@ test("codex web search renders query subtitle and formatted results", { tag: "@c const replayedToolCall = page.locator(".tool-call").filter({ has: page.locator(".tool-name", { hasText: "webSearch" }), }); - await expect(replayedToolCall).toBeVisible({ timeout: 15_000 }); + await expect(replayedToolCall).toBeVisible(); await expect(replayedToolCall.locator(".tool-subtitle")).toContainText( "dagster incremental pipelines", ); @@ -273,14 +272,13 @@ test("codex history replay renders web_search_call from rollout JSONL", { tag: " // Verify seed message replays await expect(assistantText(page, "seed-history")).toBeVisible({ - timeout: 15_000, - }); + }); // Verify web search tool call renders from history const wsToolCall = page.locator(".tool-call").filter({ has: page.locator(".tool-name", { hasText: "webSearch" }), }); - await expect(wsToolCall).toBeVisible({ timeout: 15_000 }); + await expect(wsToolCall).toBeVisible(); // Verify subtitle has main query await expect(wsToolCall.locator(".tool-subtitle")).toContainText( diff --git a/tests/e2e/commit-output-type.spec.ts b/tests/e2e/commit-output-type.spec.ts index 9aa827e6..84ed2b5d 100644 --- a/tests/e2e/commit-output-type.spec.ts +++ b/tests/e2e/commit-output-type.spec.ts @@ -18,7 +18,6 @@ test( "commit output enforcement fires when subtask exits without committing", { tag: "@claude-only" }, async ({ page }) => { - test.setTimeout(120_000); let childTid: number | null = null; @@ -48,8 +47,7 @@ test( await expect .poll(() => childTid, { message: "expected subtask to be created", - timeout: 30_000, - }) + }) .not.toBeNull(); const parentItem = page.locator('.sidebar-item[data-tid="1"]'); @@ -67,7 +65,7 @@ test( .locator('[style*="display: contents"] .message-list') .getByText("Done.", { exact: true }) .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); await subtaskItem.click(); await expect( @@ -79,13 +77,13 @@ test( .locator('[style*="display: contents"] .message-list') .getByText("Missing required outputs") .last(), - ).toBeVisible({ timeout: 30_000 }); + ).toBeVisible(); await expect( subtaskItem.locator( ".task-type-icon.completed, .task-type-icon.resumable", ), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); await expect( subtaskItem.locator(".task-type-icon.processing"), ).not.toBeVisible(); @@ -99,7 +97,6 @@ test( "commit output happy path: subtask commits and parent receives commits in result", { tag: "@claude-only" }, async ({ page }) => { - test.setTimeout(120_000); let childTid: number | null = null; @@ -130,8 +127,7 @@ test( await expect .poll(() => childTid, { message: "expected subtask to be created", - timeout: 30_000, - }) + }) .not.toBeNull(); const parentItem = page.locator('.sidebar-item[data-tid="1"]'); @@ -148,7 +144,7 @@ test( .locator('[style*="display: contents"] .message-list') .getByText("Done.", { exact: true }) .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); }, ); @@ -156,7 +152,6 @@ test( "second required-worktree child returns only its own commit", { tag: "@claude-only" }, async ({ page }) => { - test.setTimeout(120_000); const childTids: number[] = []; const taskResults: Record[] = []; page.on("websocket", (ws) => @@ -178,7 +173,7 @@ test( await enterSession(page); await page.locator(".task-type-row", { hasText: "isolated" }).click(); await sendMessage(page, "sequential required commit children"); - await expect.poll(() => childTids.length, { timeout: 90_000 }).toBe(2); + await expect.poll(() => childTids.length).toBe(2); await page.locator('.sidebar-item[data-tid="1"]').click(); await expect( page @@ -186,8 +181,7 @@ test( .getByText("Done.", { exact: true }) .last(), ).toBeVisible({ - timeout: 90_000, - }); + }); const rows = execFileSync( "sqlite3", @@ -204,7 +198,7 @@ test( expect(rows[0][1]).toBe(rows[1][1]); expect(Number(rows[0][1])).toBeGreaterThan(0); await expect - .poll(() => taskResults.length, { timeout: 30_000 }) + .poll(() => taskResults.length) .toBeGreaterThan(1); const secondResult = taskResults.at(-1)!; const firstResult = taskResults.at(-2)!; @@ -228,7 +222,6 @@ test( // worktree (or the project path) and compare against that. This test // commits in the shared worktree and asserts the missing-outputs prompt // does not fire. - test.setTimeout(120_000); let childTid: number | null = null; @@ -259,8 +252,7 @@ test( await expect .poll(() => childTid, { message: "expected subtask to be created", - timeout: 30_000, - }) + }) .not.toBeNull(); const parentItem = page.locator('.sidebar-item[data-tid="1"]'); @@ -275,7 +267,7 @@ test( .locator('[style*="display: contents"] .message-list') .getByText("Done.", { exact: true }) .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); // Sub-task must complete cleanly without the enforcement prompt firing. await subtaskItem.click(); diff --git a/tests/e2e/connection-status.spec.ts b/tests/e2e/connection-status.spec.ts index ee2a5af8..49ad0153 100644 --- a/tests/e2e/connection-status.spec.ts +++ b/tests/e2e/connection-status.spec.ts @@ -42,8 +42,7 @@ test("should show connection overlay on welcome page while connecting", { tag: " // The overlay is rendered when !connected. With the stub in place the // connection never completes so connected stays false indefinitely. await expect(page.locator(".connection-overlay")).toBeVisible({ - timeout: 5_000, - }); + }); }); test("should use the selected light theme while connecting", { tag: "@claude-only" }, async ({ @@ -54,7 +53,7 @@ test("should use the selected light theme while connecting", { tag: "@claude-onl await page.goto("/"); const overlay = page.locator(".connection-overlay"); - await expect(overlay).toBeVisible({ timeout: 5_000 }); + await expect(overlay).toBeVisible(); await expect .poll(async () => diff --git a/tests/e2e/continuation-orphan.spec.ts b/tests/e2e/continuation-orphan.spec.ts index 3d51af9c..bc128da1 100644 --- a/tests/e2e/continuation-orphan.spec.ts +++ b/tests/e2e/continuation-orphan.spec.ts @@ -1,7 +1,6 @@ import { test, expect, enterSession, sendMessage } from "./fixtures"; test("SwitchMode with orphaned process completes continuation", { tag: "@claude-only" }, async ({ page }) => { - test.setTimeout(30_000); // Listen for task_reload broadcast frames to detect continuation completion. const reloadEvents: Array<{ tid: number; reason?: string }> = []; @@ -33,5 +32,5 @@ test("SwitchMode with orphaned process completes continuation", { tag: "@claude- await expect(async () => { const continuationReload = reloadEvents.find((e) => e.reason === "continuation"); expect(continuationReload).toBeTruthy(); - }).toPass({ timeout: 25_000 }); + }).toPass(); }); diff --git a/tests/e2e/continuation.spec.ts b/tests/e2e/continuation.spec.ts index d63755f8..c292d4af 100644 --- a/tests/e2e/continuation.spec.ts +++ b/tests/e2e/continuation.spec.ts @@ -4,6 +4,7 @@ import { enterSession, sendMessage, assistantText, + responseTimeout, } from "./fixtures"; test("keep_context continuation injects prompt template", async ({ @@ -17,17 +18,17 @@ test("keep_context continuation injects prompt template", async ({ // Verify MCP tool call shows correct tool name (not "cydo__SwitchMode" or "unknown"). await expect( page.locator(".tool-name", { hasText: "SwitchMode" }), - ).toBeVisible({ timeout: 30_000 }); + ).toBeVisible(); await expect( page.locator(".result-divider.system-user-message", { hasText: "Mode switch: plan", }), - ).toBeVisible({ timeout: 30_000 }); + ).toBeVisible(); await expect( assistantText(page, "SwitchMode to plan successful."), - ).toBeVisible({ timeout: 30_000 }); + ).toBeVisible(); }); test("keep_context SwitchMode preface uses continuation key", async ({ @@ -42,11 +43,11 @@ test("keep_context SwitchMode preface uses continuation key", async ({ page.locator(".result-divider.system-user-message", { hasText: "Mode switch: implement", }), - ).toBeVisible({ timeout: 30_000 }); + ).toBeVisible(); await expect( assistantText(page, "SwitchMode to implement successful."), - ).toBeVisible({ timeout: 30_000 }); + ).toBeVisible(); }); test("mode switch replay rebuilds known system message metadata", async ({ @@ -54,7 +55,7 @@ test("mode switch replay rebuilds known system message metadata", async ({ agentType, }) => { await enterSession(page); - const timeout = agentType === "copilot" ? 60_000 : 30_000; + const timeout = responseTimeout(agentType); await sendMessage(page, "call switchmode plan"); await expect( @@ -79,20 +80,17 @@ test("unsent steer is either recovered into input box or shown in history after await sendMessage(page, "run command sleep 60"); await expect(page.locator(".tool-call", { hasText: "sleep 60" })).toBeVisible( - { timeout: 30_000 }, ); await sendMessage(page, "this should be recovered"); await page.locator(".btn-banner-stop").click(); await expect(page.locator(".btn-banner-resume")).toBeVisible({ - timeout: 10_000, - }); + }); await page.locator(".btn-banner-resume").click(); await expect(page.locator(".btn-banner-stop")).toBeVisible({ - timeout: 15_000, - }); + }); const input = page.locator(".input-textarea:visible").first(); const historyMessage = page.locator(".user-message", { @@ -105,7 +103,7 @@ test("unsent steer is either recovered into input box or shown in history after expect(inputValue === "this should be recovered" || messageVisible).toBe( true, ); - }).toPass({ timeout: 10_000 }); + }).toPass(); }); test("handoff continuation exit navigates to grandparent, not completed parent", async ({ @@ -125,9 +123,7 @@ test("handoff continuation exit navigates to grandparent, not completed parent", ); // Wait for the task URL to settle so we can capture G's tid. - await expect(page).toHaveURL(/\/[^/]+\/[^/]+\/task\/\d+/, { - timeout: 15_000, - }); + await expect(page).toHaveURL(/\/[^/]+\/[^/]+\/task\/\d+/); const tidG = parseInt( page.url().match(/\/[^/]+\/[^/]+\/task\/(\d+)/)?.[1] ?? "0", ); @@ -138,9 +134,7 @@ test("handoff continuation exit navigates to grandparent, not completed parent", // // With the bug, it would navigate to A (completed direct parent). // With the fix, it walks up through completed A to find alive G. - await expect(page).toHaveURL(new RegExp(`/task/${tidG}$`), { - timeout: 30_000, - }); + await expect(page).toHaveURL(new RegExp(`/task/${tidG}$`)); }); test("handoff replay rebuilds known system message metadata", async ({ @@ -165,7 +159,7 @@ test("handoff replay rebuilds known system message metadata", async ({ }); await enterSession(page); - const timeout = agentType === "copilot" ? 60_000 : 30_000; + const timeout = responseTimeout(agentType); await sendMessage( page, @@ -239,7 +233,7 @@ test("SwitchMode from sub-task sends is_continuation flag", async ({ // "call switchmode plan", triggering a keep_context continuation. await sendMessage(page, "call task blank call switchmode plan"); - const timeout = agentType === "copilot" ? 60_000 : 30_000; + const timeout = responseTimeout(agentType); // Wait for the child task to be created first; otherwise continuation reload // checks can race ahead before the sub-task exists on slower MCP paths. @@ -300,7 +294,7 @@ test("on_yield continuation auto-fires on clean exit", async ({ (e) => e.relation_type === "continuation", ); expect(continuationCreated).toBeTruthy(); - }).toPass({ timeout: 30_000 }); + }).toPass(); }); test("on_yield does not fire on non-zero exit", { tag: "@no-codex" }, async ({ page, agentType }) => { @@ -345,14 +339,14 @@ test("on_yield does not fire on non-zero exit", { tag: "@no-codex" }, async ({ p (e) => e.relation_type === "subtask", ); expect(subTask).toBeTruthy(); - }).toPass({ timeout: 30_000 }); + }).toPass(); const subTaskTid = taskCreatedEvents.find( (e) => e.relation_type === "subtask", )!.tid; // Kill the sub-task. getByRole targets only accessible elements, so it // finds the sub-task's Kill button and ignores hidden buttons from other tasks. - await page.getByRole("button", { name: "Kill" }).click({ timeout: 30_000 }); + await page.getByRole("button", { name: "Kill" }).click(); // Wait for the sub-task to show as dead via broadcast task_updated. await expect(async () => { @@ -360,7 +354,7 @@ test("on_yield does not fire on non-zero exit", { tag: "@no-codex" }, async ({ p (e) => e.tid === subTaskTid && !e.alive, ); expect(deadUpdate).toBeTruthy(); - }).toPass({ timeout: 10_000 }); + }).toPass(); // No task_created with relation_type "continuation" should have appeared. const continuationCreated = taskCreatedEvents.find( @@ -378,9 +372,9 @@ test("input box stays empty after mode switch", async ({ page, agentType }) => { page.locator(".result-divider.system-user-message", { hasText: "Mode switch: plan", }), - ).toBeVisible({ timeout: 30_000 }); + ).toBeVisible(); const input = page.locator(".input-textarea:visible").first(); - await expect(input).toBeEnabled({ timeout: 10_000 }); + await expect(input).toBeEnabled(); await expect(input).toHaveValue(""); }); diff --git a/tests/e2e/copilot-tool-turn-delimit.spec.ts b/tests/e2e/copilot-tool-turn-delimit.spec.ts index 35104703..fbb6bb65 100644 --- a/tests/e2e/copilot-tool-turn-delimit.spec.ts +++ b/tests/e2e/copilot-tool-turn-delimit.spec.ts @@ -25,7 +25,7 @@ test("copilot text before tool produces separate assistant messages without dupl .first(), ).toBeVisible({ timeout }); - await expect(assistantMessages).toHaveCount(2, { timeout: 5_000 }); + await expect(assistantMessages).toHaveCount(2); await expect( assistantMessages.first().locator('[data-testid="assistant-text"]', { hasText: "pre-tool-visible-text", diff --git a/tests/e2e/discover.spec.ts b/tests/e2e/discover.spec.ts index ac58bf6b..93c2143c 100644 --- a/tests/e2e/discover.spec.ts +++ b/tests/e2e/discover.spec.ts @@ -179,7 +179,7 @@ test( // project-alpha should appear (not masked) await expect( page.locator(".project-card-title", { hasText: "project-alpha" }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); // project-beta must NOT appear (masked by empty_dir overlay) await expect( @@ -228,7 +228,7 @@ test( page.locator(".project-card-title", { hasText: "cydo-test-workspace", }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); // Update config to add a second workspace. // The backend watches config.yaml via inotify (closeWrite event) and @@ -247,7 +247,7 @@ test( // The new workspace's project appears after the reload-triggered broadcast. await expect( page.locator(".project-card-title", { hasText: "new-project" }), - ).toBeVisible({ timeout: 20_000 }); + ).toBeVisible(); // Original workspace still present. await expect( @@ -294,7 +294,6 @@ test( // Page loads: the backend is alive after the failed discovery. await expect(page.locator(".welcome-page-header .logo-banner")).toBeVisible( - { timeout: 10_000 }, ); // The good workspace still shows its project. @@ -302,7 +301,7 @@ test( page.locator(".project-card-title", { hasText: "cydo-test-workspace", }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); // The failing workspace is NOT rendered: zero projects → no workspace // group element in the DOM (WelcomePage returns null when projects=[]). @@ -361,13 +360,13 @@ test( await expect( page.locator(".project-card-title", { hasText: "project-git" }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); await expect( page.locator(".project-card-title", { hasText: "project-claude" }), - ).toBeVisible({ timeout: 5_000 }); + ).toBeVisible(); await expect( page.locator(".project-card-title", { hasText: "project-both" }), - ).toBeVisible({ timeout: 5_000 }); + ).toBeVisible(); await expect( page.locator(".project-card-title", { hasText: "plain-dir" }), ).not.toBeVisible(); @@ -416,10 +415,10 @@ test( // Both the outer and inner repos must appear await expect( page.locator(".project-card-title[title='mono']"), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); await expect( page.locator(".project-card-title[title='mono/sub']"), - ).toBeVisible({ timeout: 5_000 }); + ).toBeVisible(); } finally { await killBackend(proc); rmSync(workDir, { recursive: true, force: true }); @@ -464,10 +463,10 @@ test( await expect( page.locator(".project-card-title", { hasText: "alpha" }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); await expect( page.locator(".project-card-title", { hasText: "gamma" }), - ).toBeVisible({ timeout: 5_000 }); + ).toBeVisible(); await expect( page.locator(".project-card-title", { hasText: "beta" }), ).not.toBeVisible(); @@ -518,7 +517,7 @@ test( // d is too deep — must not appear await expect( page.locator(".project-card-title", { hasText: "d" }), - ).not.toBeVisible({ timeout: 10_000 }); + ).not.toBeVisible(); // Increase depth limit to 4: d IS reachable now writeFileSync( @@ -535,7 +534,7 @@ test( await expect( page.locator(".project-card-title", { hasText: "d" }), - ).toBeVisible({ timeout: 20_000 }); + ).toBeVisible(); } finally { await killBackend(proc); rmSync(workDir, { recursive: true, force: true }); diff --git a/tests/e2e/draft-cross-tab-sync.spec.ts b/tests/e2e/draft-cross-tab-sync.spec.ts index 343eec48..556d6cd3 100644 --- a/tests/e2e/draft-cross-tab-sync.spec.ts +++ b/tests/e2e/draft-cross-tab-sync.spec.ts @@ -20,7 +20,7 @@ async function waitForNewTid(page: Page, before: Set): Promise { ); newTid = tids.find((tid: string) => !before.has(tid)); expect(newTid).toBeTruthy(); - }).toPass({ timeout: 5_000 }); + }).toPass(); return newTid!; } @@ -34,8 +34,7 @@ test("draft creation syncs title and body to another tab", async ({ const page2 = await context2.newPage(); await page2.goto(page.url()); await expect(page2.locator(".input-textarea:visible").first()).toBeEnabled({ - timeout: 15_000, - }); + }); const before = await snapshotTids(page); const draftText = "cross tab draft sync body"; @@ -47,7 +46,7 @@ test("draft creation syncs title and body to another tab", async ({ const draftTid = await waitForNewTid(page, before); const tab2Draft = page2.locator(`.sidebar-item[data-tid="${draftTid}"]`); - await expect(tab2Draft).toBeAttached({ timeout: 10_000 }); + await expect(tab2Draft).toBeAttached(); const tab2Label = tab2Draft.locator(".sidebar-label"); await page2.waitForTimeout(1_000); @@ -55,7 +54,7 @@ test("draft creation syncs title and body to another tab", async ({ await tab2Draft.click(); const input2 = page2.locator(".input-textarea:visible").first(); - await expect(input2).toBeVisible({ timeout: 5_000 }); + await expect(input2).toBeVisible(); expect({ sidebarTitleBeforeClick, diff --git a/tests/e2e/draft-persistence.spec.ts b/tests/e2e/draft-persistence.spec.ts index c7771804..ed4e71ae 100644 --- a/tests/e2e/draft-persistence.spec.ts +++ b/tests/e2e/draft-persistence.spec.ts @@ -29,11 +29,11 @@ test("draft persists across page reload", async ({ page, agentType }) => { await page.reload(); await page .locator(".sidebar-item .sidebar-label", { hasText: "draft-reload-test" }) - .click({ timeout: 15_000 }); + .click(); // Wait for the input to appear and check the draft was restored const restoredInput = page.locator(".input-textarea:visible").first(); - await expect(restoredInput).toBeVisible({ timeout: 15_000 }); + await expect(restoredInput).toBeVisible(); await expect(restoredInput).toHaveValue("my unsent draft"); }); @@ -53,11 +53,11 @@ test("draft clears after sending message", async ({ page, agentType }) => { await page.reload(); await page .locator(".sidebar-item .sidebar-label", { hasText: "draft-clear-test" }) - .click({ timeout: 15_000 }); + .click(); // Input should be empty (draft was cleared on send) const input = page.locator(".input-textarea:visible").first(); - await expect(input).toBeVisible({ timeout: 15_000 }); + await expect(input).toBeVisible(); await expect(input).toHaveValue(""); }); @@ -90,12 +90,12 @@ test("draft syncs to second client via tasks_list", async ({ // Page 2 selects the task via sidebar await page2 .locator(".sidebar-item .sidebar-label", { hasText: "draft-sync-test" }) - .click({ timeout: 15_000 }); + .click(); // Page 2 should see the draft hydrated from tasks_list const input2 = page2.locator(".input-textarea:visible").first(); - await expect(input2).toBeVisible({ timeout: 15_000 }); - await expect(input2).toHaveValue("synced draft text", { timeout: 5_000 }); + await expect(input2).toBeVisible(); + await expect(input2).toHaveValue("synced draft text"); await context2.close(); }); @@ -122,9 +122,9 @@ test("draft broadcasts live to subscribed clients", async ({ // Page 2 selects the task and waits for InputBox await page2 .locator(".sidebar-item .sidebar-label", { hasText: "draft-live-test" }) - .click({ timeout: 15_000 }); + .click(); const input2 = page2.locator(".input-textarea:visible").first(); - await expect(input2).toBeVisible({ timeout: 15_000 }); + await expect(input2).toBeVisible(); // Page 2 input should be empty initially await expect(input2).toHaveValue(""); @@ -138,7 +138,7 @@ test("draft broadcasts live to subscribed clients", async ({ await page.waitForTimeout(1500); // Page 2 should receive the draft via draft_updated broadcast - await expect(input2).toHaveValue("live broadcast draft", { timeout: 5_000 }); + await expect(input2).toHaveValue("live broadcast draft"); await context2.close(); }); @@ -165,9 +165,9 @@ test("draft broadcast does not overwrite local typing", async ({ // Page 2 selects the task await page2 .locator(".sidebar-item .sidebar-label", { hasText: "draft-nooverwrite" }) - .click({ timeout: 15_000 }); + .click(); const input2 = page2.locator(".input-textarea:visible").first(); - await expect(input2).toBeVisible({ timeout: 15_000 }); + await expect(input2).toBeVisible(); // Page 2 types something first await input2.click(); diff --git a/tests/e2e/draft-task-lifecycle.spec.ts b/tests/e2e/draft-task-lifecycle.spec.ts index d68d2e12..8ee218ec 100644 --- a/tests/e2e/draft-task-lifecycle.spec.ts +++ b/tests/e2e/draft-task-lifecycle.spec.ts @@ -26,7 +26,7 @@ async function waitForNewTid(page: Page, before: Set): Promise { ); newTid = tids.find((tid: string) => !before.has(tid)); expect(newTid).toBeTruthy(); - }).toPass({ timeout: 5_000 }); + }).toPass(); return newTid!; } @@ -48,7 +48,7 @@ test("task created on first keystroke, deleted on blanking", async ({ // Assert the new sidebar item has draft styling await expect( page.locator(`.sidebar-item[data-tid="${draftTid}"] .draft-label`), - ).toBeVisible({ timeout: 2_000 }); + ).toBeVisible(); // Clear the text to trigger draft deletion await input.fill(""); @@ -57,8 +57,7 @@ test("task created on first keystroke, deleted on blanking", async ({ await expect( page.locator(`.sidebar-item[data-tid="${draftTid}"]`), ).not.toBeAttached({ - timeout: 5_000, - }); + }); }); test("draft task becomes active on send", async ({ page, agentType }) => { @@ -78,7 +77,7 @@ test("draft task becomes active on send", async ({ page, agentType }) => { await page.locator(".btn-send:visible").first().click(); // Assert URL changed to /task/:tid pattern - await expect(page).toHaveURL(/\/task\/\d+/, { timeout: 5_000 }); + await expect(page).toHaveURL(/\/task\/\d+/); // Assert agent response arrives await expect(assistantText(page, "draft-active-test")).toBeVisible({ @@ -88,7 +87,7 @@ test("draft task becomes active on send", async ({ page, agentType }) => { // Assert sidebar item no longer has draft styling await expect( page.locator(`.sidebar-item[data-tid="${draftTid}"] .draft-label`), - ).not.toBeAttached({ timeout: 5_000 }); + ).not.toBeAttached(); }); test("no remount during draft creation", async ({ page, agentType }) => { @@ -106,7 +105,7 @@ test("no remount during draft creation", async ({ page, agentType }) => { const draftTid = await waitForNewTid(page, before); await expect( page.locator(`.sidebar-item[data-tid="${draftTid}"] .draft-label`), - ).toBeVisible({ timeout: 5_000 }); + ).toBeVisible(); await expect(input).toBeFocused(); await expect(input).toHaveValue("h"); @@ -140,8 +139,7 @@ test("multiple draft create-delete cycles", async ({ page }) => { await expect( page.locator(`.sidebar-item[data-tid="${tid1}"]`), ).not.toBeAttached({ - timeout: 5_000, - }); + }); // Cycle 2: type new text → new draft created (different tid) const before2 = await snapshotTids(page); @@ -157,8 +155,7 @@ test("multiple draft create-delete cycles", async ({ page }) => { await expect( page.locator(`.sidebar-item[data-tid="${tid2}"]`), ).not.toBeAttached({ - timeout: 5_000, - }); + }); }); test("fast type and send before task_created", async ({ page, agentType }) => { @@ -185,7 +182,7 @@ test("fast type and send before task_created", async ({ page, agentType }) => { for (const tid of newTids) { await expect( page.locator(`.sidebar-item[data-tid="${tid}"] .draft-label`), - ).not.toBeAttached({ timeout: 2_000 }); + ).not.toBeAttached(); } }); @@ -211,8 +208,7 @@ test("draft task visible to second client", async ({ page, browser }) => { await expect( page2.locator(`.sidebar-item[data-tid="${draftTid}"]`), ).toBeAttached({ - timeout: 10_000, - }); + }); // Client 1: clear text (deletes draft) await input1.fill(""); @@ -221,8 +217,7 @@ test("draft task visible to second client", async ({ page, browser }) => { await expect( page2.locator(`.sidebar-item[data-tid="${draftTid}"]`), ).not.toBeAttached({ - timeout: 5_000, - }); + }); await context2.close(); }); @@ -241,41 +236,37 @@ test("new task after draft clears form correctly", async ({ page }) => { const draftTid = await waitForNewTid(page, before); await expect( page.locator(`.sidebar-item[data-tid="${draftTid}"] .draft-label`), - ).toBeVisible({ timeout: 2_000 }); + ).toBeVisible(); // Step 3: Click 'New Task' in the sidebar await page.locator(".sidebar-new-task").click(); // Bug 1: Should show welcome prompt, not 'Loading task…' await expect(page.locator(".session-loading")).not.toBeAttached({ - timeout: 5_000, - }); + }); await expect(page.locator(".welcome-prompt:visible")).toBeVisible({ - timeout: 5_000, - }); + }); // Bug 1: Input should be empty in the new task form const newInput = page.locator(".input-textarea:visible").first(); - await expect(newInput).toHaveValue("", { timeout: 5_000 }); + await expect(newInput).toHaveValue(""); // Step 4: Click the draft task in sidebar to go back to it await page.locator(`.sidebar-item[data-tid="${draftTid}"]`).click(); // Bug 2: Should show the task type picker (SessionConfig) on return await expect(page.locator(".welcome-prompt .task-type-picker")).toBeVisible({ - timeout: 5_000, - }); + }); // Bug 3: Clear input text — draft should be deleted from sidebar const draftInput = page.locator(".input-textarea:visible").first(); - await expect(draftInput).toBeVisible({ timeout: 5_000 }); + await expect(draftInput).toBeVisible(); await draftInput.fill(""); await expect( page.locator(`.sidebar-item[data-tid="${draftTid}"]`), ).not.toBeAttached({ - timeout: 5_000, - }); + }); }); test("draft persists across page reload", async ({ page }) => { @@ -299,16 +290,13 @@ test("draft persists across page reload", async ({ page }) => { await expect( page.locator(`.sidebar-item[data-tid="${draftTid}"]`), ).toBeAttached({ - timeout: 15_000, - }); + }); // Click it to navigate to the draft task await page.locator(`.sidebar-item[data-tid="${draftTid}"]`).click(); const restoredInput = page.locator(".input-textarea:visible").first(); - await expect(restoredInput).toBeVisible({ timeout: 5_000 }); - await expect(restoredInput).toHaveValue("reload test draft", { - timeout: 5_000, - }); + await expect(restoredInput).toBeVisible(); + await expect(restoredInput).toHaveValue("reload test draft"); }); test("draft sidebar title survives page reload", async ({ page }) => { @@ -328,9 +316,7 @@ test("draft sidebar title survives page reload", async ({ page }) => { const sidebarLabel = page.locator( `.sidebar-item[data-tid="${draftTid}"] .sidebar-label`, ); - await expect(sidebarLabel).toHaveText("my important draft title", { - timeout: 5_000, - }); + await expect(sidebarLabel).toHaveText("my important draft title"); // Wait for debounce to persist draft to backend await page.waitForTimeout(1000); @@ -338,8 +324,7 @@ test("draft sidebar title survives page reload", async ({ page }) => { // Navigate away so InputBox for this task is NOT mounted after reload await page.locator(".sidebar-new-task").click(); await expect(page.locator(".welcome-prompt:visible")).toBeVisible({ - timeout: 5_000, - }); + }); // Reload the page await page.reload(); @@ -348,16 +333,13 @@ test("draft sidebar title survives page reload", async ({ page }) => { await expect( page.locator(`.sidebar-item[data-tid="${draftTid}"]`), ).toBeAttached({ - timeout: 15_000, - }); + }); // Sidebar title should still show the draft text, not "Task NNN" const reloadedLabel = page.locator( `.sidebar-item[data-tid="${draftTid}"] .sidebar-label`, ); - await expect(reloadedLabel).toHaveText("my important draft title", { - timeout: 5_000, - }); + await expect(reloadedLabel).toHaveText("my important draft title"); }); test("draft deletable after page reload", async ({ page }) => { @@ -381,21 +363,17 @@ test("draft deletable after page reload", async ({ page }) => { await expect( page.locator(`.sidebar-item[data-tid="${draftTid}"]`), ).toBeAttached({ - timeout: 15_000, - }); + }); // Click the draft task in the sidebar to navigate to it await page.locator(`.sidebar-item[data-tid="${draftTid}"]`).click(); const restoredInput = page.locator(".input-textarea:visible").first(); - await expect(restoredInput).toBeVisible({ timeout: 5_000 }); - await expect(restoredInput).toHaveValue("delete after reload", { - timeout: 5_000, - }); + await expect(restoredInput).toBeVisible(); + await expect(restoredInput).toHaveValue("delete after reload"); // Wait for re-adopt to complete (task type picker visible = onContentEnd wired) await expect(page.locator(".welcome-prompt .task-type-picker")).toBeVisible({ - timeout: 5_000, - }); + }); // Clear the text — this should trigger draft deletion await restoredInput.fill(""); @@ -404,6 +382,5 @@ test("draft deletable after page reload", async ({ page }) => { await expect( page.locator(`.sidebar-item[data-tid="${draftTid}"]`), ).not.toBeAttached({ - timeout: 5_000, - }); + }); }); diff --git a/tests/e2e/draft-type-persistence.spec.ts b/tests/e2e/draft-type-persistence.spec.ts index 09e5f9fd..7c267f0f 100644 --- a/tests/e2e/draft-type-persistence.spec.ts +++ b/tests/e2e/draft-type-persistence.spec.ts @@ -26,7 +26,7 @@ async function waitForNewTid(page: Page, before: Set): Promise { ); newTid = tids.find((tid: string) => !before.has(tid)); expect(newTid).toBeTruthy(); - }).toPass({ timeout: 5_000 }); + }).toPass(); return newTid!; } @@ -46,14 +46,14 @@ test("sidebar icon updates when entry point changed on draft", async ({ const draftTid = await waitForNewTid(page, before); await expect( page.locator(`.sidebar-item[data-tid="${draftTid}"] .draft-label`), - ).toBeVisible({ timeout: 2_000 }); + ).toBeVisible(); // Default icon should be "conversation" (agentic is the first entry point in test env) await expect( page.locator( `.sidebar-item[data-tid="${draftTid}"] .task-type-icon-conversation`, ), - ).toBeVisible({ timeout: 2_000 }); + ).toBeVisible(); // Change entry point to "blank" via the picker await page.locator(".task-type-row", { hasText: "blank" }).click(); @@ -65,7 +65,7 @@ test("sidebar icon updates when entry point changed on draft", async ({ // because the selected entry point was not being persisted on the draft task await expect( page.locator(`.sidebar-item[data-tid="${draftTid}"] .task-type-icon-blank`), - ).toBeVisible({ timeout: 3_000 }); + ).toBeVisible(); }); test("entry point persists across page reload on draft", async ({ page }) => { @@ -82,7 +82,7 @@ test("entry point persists across page reload on draft", async ({ page }) => { const draftTid = await waitForNewTid(page, before); await expect( page.locator(`.sidebar-item[data-tid="${draftTid}"] .draft-label`), - ).toBeVisible({ timeout: 2_000 }); + ).toBeVisible(); // Change entry point to "blank" await page.locator(".task-type-row", { hasText: "blank" }).click(); @@ -99,13 +99,12 @@ test("entry point persists across page reload on draft", async ({ page }) => { // Navigate to the draft task await expect( page.locator(`.sidebar-item[data-tid="${draftTid}"]`), - ).toBeAttached({ timeout: 15_000 }); + ).toBeAttached(); await page.locator(`.sidebar-item[data-tid="${draftTid}"]`).click(); // Wait for the entry-point picker to be visible await expect(page.locator(".task-type-picker")).toBeVisible({ - timeout: 5_000, - }); + }); // BUG: entry point should still be "blank" but reverts to default "agentic" await expect( @@ -156,7 +155,7 @@ test("agent type persists across page reload on draft", async ({ const draftTid = await waitForNewTid(page, before); await expect( page.locator(`.sidebar-item[data-tid="${draftTid}"] .draft-label`), - ).toBeVisible({ timeout: 2_000 }); + ).toBeVisible(); // Change the agent type await page.locator(".agent-picker").selectOption(targetAgent); @@ -171,13 +170,12 @@ test("agent type persists across page reload on draft", async ({ // Navigate to the draft task await expect( page.locator(`.sidebar-item[data-tid="${draftTid}"]`), - ).toBeAttached({ timeout: 15_000 }); + ).toBeAttached(); await page.locator(`.sidebar-item[data-tid="${draftTid}"]`).click(); // Wait for the agent picker to be visible await expect(page.locator(".agent-picker")).toBeVisible({ - timeout: 5_000, - }); + }); // BUG: agent type should still be the changed value but reverts to default await expect(page.locator(".agent-picker")).toHaveValue(targetAgent); diff --git a/tests/e2e/dropdown-reset-on-type.spec.ts b/tests/e2e/dropdown-reset-on-type.spec.ts index 03fb3a5b..52490fa9 100644 --- a/tests/e2e/dropdown-reset-on-type.spec.ts +++ b/tests/e2e/dropdown-reset-on-type.spec.ts @@ -20,7 +20,7 @@ async function waitForNewTid(page: Page, before: Set): Promise { ); newTid = tids.find((tid: string) => !before.has(tid)); expect(newTid).toBeTruthy(); - }).toPass({ timeout: 5_000 }); + }).toPass(); return newTid!; } @@ -34,7 +34,7 @@ test("agent type dropdown not reset when typing first character", async ({ await enterSession(page); // Agent picker should be visible in draft mode - await expect(page.locator(".agent-picker")).toBeVisible({ timeout: 5_000 }); + await expect(page.locator(".agent-picker")).toBeVisible(); const before = await snapshotTids(page); @@ -65,7 +65,7 @@ test("agent type dropdown not reset when typing then deleting", async ({ await enterSession(page); - await expect(page.locator(".agent-picker")).toBeVisible({ timeout: 5_000 }); + await expect(page.locator(".agent-picker")).toBeVisible(); const before = await snapshotTids(page); @@ -82,7 +82,7 @@ test("agent type dropdown not reset when typing then deleting", async ({ const draftTid = await waitForNewTid(page, before); await expect( page.locator(`.sidebar-item[data-tid="${draftTid}"] .draft-label`), - ).toBeVisible({ timeout: 2_000 }); + ).toBeVisible(); // Delete the character — triggers deleteDraftTask await input.fill(""); @@ -90,7 +90,7 @@ test("agent type dropdown not reset when typing then deleting", async ({ // Draft should be removed from sidebar await expect( page.locator(`.sidebar-item[data-tid="${draftTid}"]`), - ).not.toBeAttached({ timeout: 5_000 }); + ).not.toBeAttached(); // Agent picker should still show the selected value, not reset to default await expect(page.locator(".agent-picker")).toHaveValue(targetAgent); @@ -103,8 +103,7 @@ test("task type dropdown not reset when typing then deleting", async ({ // Task type picker should be visible in draft mode await expect(page.locator(".task-type-picker")).toBeVisible({ - timeout: 5_000, - }); + }); const before = await snapshotTids(page); @@ -123,7 +122,7 @@ test("task type dropdown not reset when typing then deleting", async ({ const draftTid = await waitForNewTid(page, before); await expect( page.locator(`.sidebar-item[data-tid="${draftTid}"] .draft-label`), - ).toBeVisible({ timeout: 2_000 }); + ).toBeVisible(); // Delete the character — triggers deleteDraftTask await input.fill(""); @@ -131,7 +130,7 @@ test("task type dropdown not reset when typing then deleting", async ({ // Draft should be removed from sidebar await expect( page.locator(`.sidebar-item[data-tid="${draftTid}"]`), - ).not.toBeAttached({ timeout: 5_000 }); + ).not.toBeAttached(); // Task type picker should still show "blank", not the default await expect( diff --git a/tests/e2e/edit-raw-event.spec.ts b/tests/e2e/edit-raw-event.spec.ts index fe1b4dc7..12a70693 100644 --- a/tests/e2e/edit-raw-event.spec.ts +++ b/tests/e2e/edit-raw-event.spec.ts @@ -115,30 +115,30 @@ async function openRawEditorForMessageWrapper(page: Page, messageWrapper: Locato await messageWrapper.hover(); const viewSourceBtn = messageWrapper.locator(".view-source-btn"); - await expect(viewSourceBtn).toBeVisible({ timeout: 5_000 }); + await expect(viewSourceBtn).toBeVisible(); await viewSourceBtn.click(); const sourceView = page.locator(".source-view"); - await expect(sourceView).toBeVisible({ timeout: 5_000 }); + await expect(sourceView).toBeVisible(); const firstEventHeader = sourceView.locator(".source-event-header").first(); - await expect(firstEventHeader).toBeVisible({ timeout: 5_000 }); + await expect(firstEventHeader).toBeVisible(); await firstEventHeader.click(); const rawTab = sourceView.locator(".source-tab", { hasText: "Raw" }); - await expect(rawTab).toBeVisible({ timeout: 5_000 }); + await expect(rawTab).toBeVisible(); await rawTab.click(); const rawBlock = sourceView.locator(".code-pre-wrap").first(); - await expect(rawBlock).toBeVisible({ timeout: 10_000 }); + await expect(rawBlock).toBeVisible(); await rawBlock.hover(); const editBtn = rawBlock.locator(".edit-btn"); - await expect(editBtn).toBeVisible({ timeout: 5_000 }); + await expect(editBtn).toBeVisible(); await editBtn.click(); const textarea = page.locator(".raw-edit-textarea"); - await expect(textarea).toBeVisible({ timeout: 5_000 }); + await expect(textarea).toBeVisible(); return textarea.inputValue(); } @@ -189,7 +189,7 @@ async function createEditableStoppedSession(page: Page, agentType: AgentType) { ).toBeVisible({ timeout }); await expect(lastAssistantText(page, "Done.")).toBeVisible({ timeout }); await expect - .poll(() => page.url(), { timeout: 15_000 }) + .poll(() => page.url()) .toMatch(/\/task\/\d+(?:$|[/?#])/); const historyPath = historyPathForTask(currentTaskTid(page)); @@ -276,8 +276,7 @@ test("edit raw JSON event persists to disk across reload", async ({ await page.locator(".edit-actions .btn-primary").click(); await expect(lastAssistantText(page, "Done.")).toBeVisible({ - timeout: 15_000, - }); + }); const currentFile = readHistoryFile(historyPath); expect(currentFile).toContain(marker); @@ -298,8 +297,7 @@ test("clearing raw JSON deletes the source line instead of writing null", async await page.locator(".edit-actions .btn-primary").click(); await expect(lastAssistantText(page, "Done.")).not.toBeVisible({ - timeout: 15_000, - }); + }); const rewrittenFile = readHistoryFile(historyPath); expect(rewrittenFile).not.toContain("\nnull\n"); @@ -325,11 +323,9 @@ test("editing raw JSON to two top-level objects expands into two history lines", await page.locator(".edit-actions .btn-primary").click(); await expect(lastAssistantText(page, firstText)).toBeVisible({ - timeout: 15_000, - }); + }); await expect(lastAssistantText(page, secondText)).toBeVisible({ - timeout: 15_000, - }); + }); const rawLines = readHistoryFile(historyPath) .split("\n") @@ -406,12 +402,10 @@ isolatedTest("editing the second identical raw line rewrites that physical JSONL ".project-card-sessions .sidebar-item .sidebar-label", { hasText: duplicatePrompt }, ); - await expect(importableLabel).toBeVisible({ timeout: 15_000 }); + await expect(importableLabel).toBeVisible(); await importableLabel.click(); - await expect(duplicatePromptText(page)).toHaveCount(2, { - timeout: 15_000, - }); + await expect(duplicatePromptText(page)).toHaveCount(2); const duplicateValue = await openRawEditorForUserMessage(page, duplicatePrompt); const rawLinesBefore = readHistoryFile(historyPath) @@ -425,16 +419,13 @@ isolatedTest("editing the second identical raw line rewrites that physical JSONL await page.locator(".raw-edit-textarea").fill(rewrittenValue); await page.locator(".edit-actions .btn-primary").click(); - await expect(duplicatePromptText(page)).toHaveCount(1, { - timeout: 15_000, - }); + await expect(duplicatePromptText(page)).toHaveCount(1); await expect( page.locator(".message.user-message .user-text").getByText(replacement, { exact: true, }), ).toBeVisible({ - timeout: 15_000, - }); + }); const rewrittenLines = readHistoryFile(historyPath) .split("\n") @@ -517,15 +508,13 @@ isolatedTest("editing replayed steering raw JSON rewrites the earlier enqueue li await page.goto(BACKEND_URL + "/"); const importableLabel = page.locator(".project-card-sessions .sidebar-item .sidebar-label").first(); - await expect(importableLabel).toBeVisible({ timeout: 15_000 }); + await expect(importableLabel).toBeVisible(); await importableLabel.click(); await expect(page.locator(".message.user-message", { hasText: steeringPrompt })).toBeVisible({ - timeout: 15_000, - }); + }); await expect(page.locator(".message.assistant-message", { hasText: assistantReply })).toBeVisible({ - timeout: 15_000, - }); + }); const currentValue = await openRawEditorForUserMessage(page, steeringPrompt); expect(currentValue).toContain('"operation": "enqueue"'); @@ -537,14 +526,11 @@ isolatedTest("editing replayed steering raw JSON rewrites the earlier enqueue li await page.locator(".edit-actions .btn-primary").click(); await expect(page.locator(".message.user-message", { hasText: steeringPrompt })).not.toBeVisible({ - timeout: 15_000, - }); + }); await expect(page.locator(".message.user-message", { hasText: replacement })).toBeVisible({ - timeout: 15_000, - }); + }); await expect(page.locator(".message.assistant-message", { hasText: assistantReply })).toBeVisible({ - timeout: 15_000, - }); + }); const rewrittenLines = readHistoryFile(historyPath) .split("\n") diff --git a/tests/e2e/export-html.spec.ts b/tests/e2e/export-html.spec.ts index 58829221..587d60d4 100644 --- a/tests/e2e/export-html.spec.ts +++ b/tests/e2e/export-html.spec.ts @@ -46,7 +46,7 @@ test("export-html creates viewable HTML file", { tag: "@claude-only" }, async ({ await expect( page.locator('[data-testid="assistant-text"]', { hasText: "export-marker-ok" }), - ).toBeVisible({ timeout: 10_000 }); + ).toBeVisible(); await expect(page.locator(".sidebar-item")).toHaveCount( await page.locator(".sidebar-item").count(), diff --git a/tests/e2e/fd-leak.spec.ts b/tests/e2e/fd-leak.spec.ts index 5b0f214f..ee46dfe6 100644 --- a/tests/e2e/fd-leak.spec.ts +++ b/tests/e2e/fd-leak.spec.ts @@ -15,7 +15,7 @@ function getFdCount(pid: number): number { async function waitForFdStabilization( pid: number, - timeoutMs: number = 10_000, + timeoutMs: number = 540_000, pollMs: number = 200, stableSamples: number = 4, ): Promise { @@ -59,7 +59,6 @@ test("agent session teardown does not leak file descriptors", async ({ backend, agentType, }) => { - test.setTimeout(120_000); // Warm up: load the page so HTTP/WebSocket connections are established // before we take the baseline FD measurement. @@ -105,7 +104,6 @@ test("FD count stays stable across multiple session cycles", async ({ backend, agentType, }) => { - test.setTimeout(300_000); // Warm up await page.goto("/"); diff --git a/tests/e2e/fixtures.ts b/tests/e2e/fixtures.ts index 05732ffc..2978ace5 100644 --- a/tests/e2e/fixtures.ts +++ b/tests/e2e/fixtures.ts @@ -58,13 +58,15 @@ export function lastAssistantText( /** Kill the active session and wait for it to become inactive. */ export async function killSession(page: Page, agentType: AgentType) { await page.locator(".btn-banner-stop").click(); - const timeout = 15_000; - await expect(page.locator(".btn-banner-archive")).toBeVisible({ timeout }); + await expect(page.locator(".btn-banner-archive")).toBeVisible(); } -/** Return an appropriate response timeout for the given agent. */ -export function responseTimeout(agentType: AgentType): number { - return agentType === "codex" ? 90_000 : 60_000; +/** Assertion budget for agent responses. Deliberately the shared generous + * budget (see playwright.config.ts): condition waits return the moment they + * are satisfied, so this costs nothing on passing runs and stops contention + * from failing correctness tests. */ +export function responseTimeout(_agentType: AgentType): number { + return 540_000; } type TestFixtures = { @@ -165,6 +167,8 @@ export const test = base.extend({ }, page: async ({ page }, use, testInfo: TestInfo) => { + // page-level waits (waitForEvent and friends) share the assertion budget + page.setDefaultTimeout(540_000); const pageErrors: string[] = []; page.on("console", (msg) => console.error(`[browser] console.${msg.type()}: ${msg.text()}`), diff --git a/tests/e2e/follow-up-markdown.spec.ts b/tests/e2e/follow-up-markdown.spec.ts index ecff817b..2b6967bb 100644 --- a/tests/e2e/follow-up-markdown.spec.ts +++ b/tests/e2e/follow-up-markdown.spec.ts @@ -5,12 +5,10 @@ import { test, expect, enterSession, sendMessage } from "./fixtures"; // but sends a message that contains Markdown bold syntax so we can assert that // the rendered body contains a element rather than raw **…** text. -const TALK_TIMEOUT = 120_000; test("Follow-up from parent renders body as Markdown (live and after reload)", async ({ page, }) => { - test.setTimeout(TALK_TIMEOUT); await enterSession(page); @@ -23,7 +21,7 @@ test("Follow-up from parent renders body as Markdown (live and after reload)", a .locator('[style*="display: contents"] .message-list') .getByText("initial-result", { exact: true }) .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); // Wait for parent's turn to complete before sending the follow-up. await expect( @@ -31,12 +29,12 @@ test("Follow-up from parent renders body as Markdown (live and after reload)", a .locator('[style*="display: contents"] .message-list') .getByText("Done.", { exact: true }) .last(), - ).toBeVisible({ timeout: 30_000 }); + ).toBeVisible(); // Wait for focus to return to the parent task (tid=1). await expect( page.locator('.sidebar-item[data-tid="1"].active'), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); // Parent asks the completed child (tid=2) with a Markdown-formatted message. await sendMessage(page, "call ask 2 **important question**"); @@ -47,13 +45,13 @@ test("Follow-up from parent renders body as Markdown (live and after reload)", a .locator('[style*="display: contents"] .message-list') .getByText("follow-up-answered", { exact: true }) .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); // Navigate to the child task and verify Markdown rendering. await page.locator('.sidebar-item[data-tid="2"]').click(); await expect( page.locator('.sidebar-item[data-tid="2"].active'), - ).toBeVisible({ timeout: 10_000 }); + ).toBeVisible(); const followUpMessage = page .locator( @@ -61,19 +59,19 @@ test("Follow-up from parent renders body as Markdown (live and after reload)", a { hasText: "Follow-up from parent" }, ) .last(); - await expect(followUpMessage).toBeVisible({ timeout: 30_000 }); + await expect(followUpMessage).toBeVisible(); // The body must contain a element — proof that **…** was rendered as Markdown. await expect( followUpMessage.locator(".system-user-body strong").first(), - ).toBeVisible({ timeout: 10_000 }); + ).toBeVisible(); // After page reload the offline replay path must also render Markdown. await page.reload(); await page.locator('.sidebar-item[data-tid="2"]').click(); await expect( page.locator('.sidebar-item[data-tid="2"].active'), - ).toBeVisible({ timeout: 10_000 }); + ).toBeVisible(); const followUpMessageAfterReload = page .locator( @@ -81,8 +79,8 @@ test("Follow-up from parent renders body as Markdown (live and after reload)", a { hasText: "Follow-up from parent" }, ) .last(); - await expect(followUpMessageAfterReload).toBeVisible({ timeout: 30_000 }); + await expect(followUpMessageAfterReload).toBeVisible(); await expect( followUpMessageAfterReload.locator(".system-user-body strong").first(), - ).toBeVisible({ timeout: 10_000 }); + ).toBeVisible(); }); diff --git a/tests/e2e/frontend-update-notice.spec.ts b/tests/e2e/frontend-update-notice.spec.ts index c2196ef0..53786bf2 100644 --- a/tests/e2e/frontend-update-notice.spec.ts +++ b/tests/e2e/frontend-update-notice.spec.ts @@ -50,7 +50,7 @@ test("frontend update notice appears on build hash mismatch", { tag: "@claude-on const notice = page.locator(".notice-item", { hasText: /outdated CyDo UI/i, }); - await expect(notice).toBeVisible({ timeout: 15_000 }); + await expect(notice).toBeVisible(); const reloadButton = notice.locator("button.notice-action-button"); await expect(reloadButton).toHaveText(/reload/i); @@ -61,5 +61,5 @@ test("frontend update notice appears on build hash mismatch", { tag: "@claude-on const navigated = page.waitForEvent("framenavigated"); await reloadButton.click(); await navigated; - await expect(notice).toBeVisible({ timeout: 15_000 }); + await expect(notice).toBeVisible(); }); diff --git a/tests/e2e/history-load-error.spec.ts b/tests/e2e/history-load-error.spec.ts index 39fb2955..4bc68f4a 100644 --- a/tests/e2e/history-load-error.spec.ts +++ b/tests/e2e/history-load-error.spec.ts @@ -163,14 +163,14 @@ test( page.locator(".sidebar-item .sidebar-label", { hasText: "orphan-agent-task", }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); await page .locator(".sidebar-item .sidebar-label", { hasText: "orphan-agent-task" }) .click(); // The stream must contain exactly one severity-error system message. const errorMsg = page.locator(".system-user-message.severity-error"); - await expect(errorMsg).toHaveCount(1, { timeout: 10_000 }); + await expect(errorMsg).toHaveCount(1); // The body must mention the orphan agent name and say it is not configured. await expect(errorMsg).toContainText(/agent.*nonexistent.*is not configured/i); @@ -181,12 +181,12 @@ test( page.locator(".sidebar-item .sidebar-label", { hasText: "orphan-agent-task", }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); await page .locator(".sidebar-item .sidebar-label", { hasText: "orphan-agent-task" }) .click(); const errorMsgAfterReload = page.locator(".system-user-message.severity-error"); - await expect(errorMsgAfterReload).toHaveCount(1, { timeout: 10_000 }); + await expect(errorMsgAfterReload).toHaveCount(1); await expect(errorMsgAfterReload).toContainText( /agent.*nonexistent.*is not configured/i, ); diff --git a/tests/e2e/history-order.spec.ts b/tests/e2e/history-order.spec.ts index 0e1a52a2..23d2f05c 100644 --- a/tests/e2e/history-order.spec.ts +++ b/tests/e2e/history-order.spec.ts @@ -42,7 +42,6 @@ test("user message appears above assistant response during live session", async // Wait for the confirmed (non-pending) user message await expect(page.locator(".message.user-message:not(.pending)")).toBeVisible( - { timeout: 15_000 }, ); // Check DOM order: user message must come BEFORE assistant in the same diff --git a/tests/e2e/image-paste.spec.ts b/tests/e2e/image-paste.spec.ts index 7570013a..40db32e5 100644 --- a/tests/e2e/image-paste.spec.ts +++ b/tests/e2e/image-paste.spec.ts @@ -37,7 +37,7 @@ test.describe("image paste", () => { await enterSession(page); const input = page.locator(".input-textarea:visible").first(); - await expect(input).toBeEnabled({ timeout: 15_000 }); + await expect(input).toBeEnabled(); // Step 1: Simulate pasting an image await input.focus(); @@ -45,8 +45,7 @@ test.describe("image paste", () => { // Step 2: Verify image preview appears await expect(page.locator(".image-preview img")).toBeVisible({ - timeout: 5_000, - }); + }); // Step 3: Type text and send await input.fill("describe this image"); @@ -79,7 +78,7 @@ test.describe("image paste", () => { await enterSession(page); const input = page.locator(".input-textarea:visible").first(); - await expect(input).toBeEnabled({ timeout: 15_000 }); + await expect(input).toBeEnabled(); // Paste an image await input.focus(); @@ -87,8 +86,7 @@ test.describe("image paste", () => { // Verify preview appears await expect(page.locator(".image-preview img")).toBeVisible({ - timeout: 5_000, - }); + }); // Click remove button await page.locator(".image-preview-remove").click(); diff --git a/tests/e2e/isolated-entry-worktree.spec.ts b/tests/e2e/isolated-entry-worktree.spec.ts index bc788d2d..eac98c6c 100644 --- a/tests/e2e/isolated-entry-worktree.spec.ts +++ b/tests/e2e/isolated-entry-worktree.spec.ts @@ -63,7 +63,7 @@ test("isolated Copilot Bash runs inside its task worktree", { tag: "@copilot-onl (e) => e.entry_point === "isolated" || e.task_type === "isolated", ); expect(isolatedTask).toBeTruthy(); - }).toPass({ timeout: 15_000 }); + }).toPass(); const tid = taskUpdatedEvents.find( (e) => e.entry_point === "isolated" || e.task_type === "isolated", @@ -76,9 +76,7 @@ test("isolated Copilot Bash runs inside its task worktree", { tag: "@copilot-onl }); await expect - .poll(() => existsSync(worktreeDir), { - timeout: 30_000, - }) + .poll(() => existsSync(worktreeDir)) .toBe(true); const bashResultHeader = bashToolCall.locator(".tool-result-header"); diff --git a/tests/e2e/markdown-diff-toggle.spec.ts b/tests/e2e/markdown-diff-toggle.spec.ts index 5700c88c..e62917b2 100644 --- a/tests/e2e/markdown-diff-toggle.spec.ts +++ b/tests/e2e/markdown-diff-toggle.spec.ts @@ -39,5 +39,5 @@ test("markdown diff toggle button visible for .md file edit with structuredPatch const toggleBtn = toolCall.locator( ".markdown-diff-wrap .markdown-toggle-btn", ); - await expect(toggleBtn).toBeVisible({ timeout: 5_000 }); + await expect(toggleBtn).toBeVisible(); }); diff --git a/tests/e2e/notices-empty-auth.spec.ts b/tests/e2e/notices-empty-auth.spec.ts index c1a6dc80..fcbf5c6e 100644 --- a/tests/e2e/notices-empty-auth.spec.ts +++ b/tests/e2e/notices-empty-auth.spec.ts @@ -17,6 +17,5 @@ test("auth-enabled startup handles empty notices list", { tag: "@claude-only" }, await page.goto("/"); await expect(page.locator('button[title="New task"]').first()).toBeVisible({ - timeout: 15_000, - }); + }); }); diff --git a/tests/e2e/optimistic-message.spec.ts b/tests/e2e/optimistic-message.spec.ts index b0a9f1b1..9236b0a8 100644 --- a/tests/e2e/optimistic-message.spec.ts +++ b/tests/e2e/optimistic-message.spec.ts @@ -21,7 +21,7 @@ test("user bubble appears immediately after Send (ack-4 optimistic render)", { t await input.click(); await input.fill("stall session"); const sendBtn = page.locator(".btn-send:visible").first(); - await expect(sendBtn).toBeEnabled({ timeout: 5_000 }); + await expect(sendBtn).toBeEnabled(); // Click send — do NOT await the assistant reply, check immediately. await sendBtn.click(); @@ -29,7 +29,7 @@ test("user bubble appears immediately after Send (ack-4 optimistic render)", { t // The optimistic bubble must be visible within one render cycle. await expect( page.locator(".user-message", { hasText: "stall session" }), - ).toBeVisible({ timeout: 3_000 }); + ).toBeVisible(); }); test("outbox replays unsent message after offline reload", { tag: "@claude-only" }, async ({ @@ -54,13 +54,13 @@ test("outbox replays unsent message after offline reload", { tag: "@claude-only" await input.click(); await input.fill('Please reply with "outbox-recovery"'); const sendBtn = page.locator(".btn-send:visible").first(); - await expect(sendBtn).toBeEnabled({ timeout: 5_000 }); + await expect(sendBtn).toBeEnabled(); await sendBtn.click(); // Placeholder should be visible with ack-4 (offline, no backend ack). await expect( page.locator(".user-message", { hasText: "outbox-recovery" }), - ).toBeVisible({ timeout: 3_000 }); + ).toBeVisible(); // Verify outbox has an entry. const outboxBefore = await page.evaluate(() => @@ -77,7 +77,7 @@ test("outbox replays unsent message after offline reload", { tag: "@claude-only" .locator(".sidebar-item .sidebar-label", { hasText: "outbox-test-established", }) - .click({ timeout: 15_000 }); + .click(); // The replayed message should eventually get a response. await expect(assistantText(page, "outbox-recovery")).toBeVisible({ @@ -124,7 +124,7 @@ test("backend deduplicates message sent twice with same nonce", { tag: "@claude- await input.click(); await input.fill('Please reply with "dedupe-test"'); const sendBtn = page.locator(".btn-send:visible").first(); - await expect(sendBtn).toBeEnabled({ timeout: 5_000 }); + await expect(sendBtn).toBeEnabled(); await sendBtn.click(); const outboxSnapshot = await page.evaluate(() => { @@ -172,7 +172,7 @@ test("backend deduplicates message sent twice with same nonce", { tag: "@claude- await page.reload(); await page .locator(".sidebar-item .sidebar-label", { hasText: "dedupe-test" }) - .click({ timeout: 15_000 }); + .click(); // Wait for the task to be visible, then check no second reply arrives. await expect(assistantText(page, "dedupe-test").first()).toBeVisible({ @@ -324,7 +324,7 @@ test("claude skips ack-2 (no agent-ack signal)", { tag: "@claude-only" }, async await input.click(); await input.fill("stall session"); const sendBtn = page.locator(".btn-send:visible").first(); - await expect(sendBtn).toBeEnabled({ timeout: 5_000 }); + await expect(sendBtn).toBeEnabled(); await sendBtn.click(); const bubble = page @@ -335,8 +335,8 @@ test("claude skips ack-2 (no agent-ack signal)", { tag: "@claude-only" }, async // By the time the DOM paints, the optimistic placeholder may still be // ack-4 or may already have been upgraded to ack-3 by the backend echo. - await expect(bubble).toHaveClass(/ack-(3|4)/, { timeout: 3_000 }); - await expect(bubble).toHaveClass(/ack-3/, { timeout: 15_000 }); + await expect(bubble).toHaveClass(/ack-(3|4)/); + await expect(bubble).toHaveClass(/ack-3/); // Claude has no agent-ack signal — ack-2 must never appear. // Wait slightly longer than the ack-3 round-trip to confirm stability. @@ -358,13 +358,13 @@ test("late-joining tab sees pending bubble from history", { tag: "@claude-only" await input.click(); await input.fill("stall session"); const sendBtn = page.locator(".btn-send:visible").first(); - await expect(sendBtn).toBeEnabled({ timeout: 5_000 }); + await expect(sendBtn).toBeEnabled(); await sendBtn.click(); // Wait for the unconfirmed-user-event echo so the pending bubble is in history. await expect( page.locator(".user-message", { hasText: "stall session" }), - ).toBeVisible({ timeout: 5_000 }); + ).toBeVisible(); // Open the same task in a second context. const url = page.url(); @@ -375,7 +375,7 @@ test("late-joining tab sees pending bubble from history", { tag: "@claude-only" // Tab B should see the pending bubble replayed from history. await expect( page2.locator(".user-message", { hasText: "stall session" }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); await ctx2.close(); }); @@ -400,14 +400,14 @@ test("outbox ack-4 placeholder visible offline; persists across reload", { tag: await inputEl.click(); await inputEl.fill('Please reply with "outbox-reload-pending"'); const sendBtn = page.locator(".btn-send:visible").first(); - await expect(sendBtn).toBeEnabled({ timeout: 5_000 }); + await expect(sendBtn).toBeEnabled(); await sendBtn.click(); // Render-layer outbox composition: ack-4 placeholder is visible immediately // from localStorage — no WS round-trip needed (we're offline). await expect( page.locator(".user-message.ack-4", { hasText: "outbox-reload-pending" }), - ).toBeVisible({ timeout: 3_000 }); + ).toBeVisible(); const outboxBefore = await page.evaluate(() => JSON.parse(localStorage.getItem("cydo.outbox.v1") ?? "[]"), ); @@ -424,7 +424,7 @@ test("outbox ack-4 placeholder visible offline; persists across reload", { tag: .locator(".sidebar-item .sidebar-label", { hasText: "reload-outbox-established", }) - .click({ timeout: 15_000 }); + .click(); // Full delivery: outbox replays the message and the backend replies. await expect(assistantText(page, "outbox-reload-pending")).toBeVisible({ @@ -497,7 +497,7 @@ test("identical-text messages use separate nonces (no placeholder collision)", { .locator(".sidebar-item .sidebar-label", { hasText: "nonce-dedup-established", }) - .click({ timeout: 15_000 }); + .click(); // After both outbox entries are replayed and ack-3 arrives for each, the // reducer places them as two separate user message bubbles (one per nonce). diff --git a/tests/e2e/parallel-task-null.spec.ts b/tests/e2e/parallel-task-null.spec.ts index 9c16ad4d..460303b5 100644 --- a/tests/e2e/parallel-task-null.spec.ts +++ b/tests/e2e/parallel-task-null.spec.ts @@ -4,7 +4,6 @@ test("parallel Task results keep request order and never render null", async ({ page, }) => { // Sub-task creation + completion for 2 children requires extra time. - test.setTimeout(120_000); await enterSession(page); @@ -17,12 +16,11 @@ test("parallel Task results keep request order and never render null", async ({ // to the parent to see the Task result with batch results. await page.locator('.sidebar-item[data-tid="3"]').waitFor({ state: "visible", - timeout: 30_000, - }); + }); await page.locator('.sidebar-item[data-tid="1"]').click(); await expect( page.locator('.sidebar-item[data-tid="1"].active'), - ).toBeVisible({ timeout: 10_000 }); + ).toBeVisible(); const msgList = page.locator('[style*="display: contents"] .message-list'); @@ -33,7 +31,7 @@ test("parallel Task results keep request order and never render null", async ({ const firstSpec = msgList.locator('.tool-result-container .cydo-task-spec').first(); await expect( msgList.getByText("Done.", { exact: true }).or(firstSpec).first(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); // Scope to the tool result container inside the Task tool call. // The .tool-result-container holds result items, while .cydo-task-spec also @@ -41,7 +39,7 @@ test("parallel Task results keep request order and never render null", async ({ const resultSpecs = msgList.locator( '.tool-result-container .cydo-task-spec', ); - await expect(resultSpecs).toHaveCount(2, { timeout: 10_000 }); + await expect(resultSpecs).toHaveCount(2); // The UI should preserve request order (tid 2, then tid 3) and each item // should contain the child output instead of fallback null text. @@ -54,8 +52,7 @@ test("parallel Task results keep request order and never render null", async ({ for (let i = 0; i < 2; i++) { const spec = resultSpecs.nth(i); await expect(spec.getByText("parallel-ok")).toBeVisible({ - timeout: 5_000, - }); + }); } await expect( msgList.locator('.tool-result-container').last().getByText("result: null"), diff --git a/tests/e2e/permission-prompt-ask.spec.ts b/tests/e2e/permission-prompt-ask.spec.ts index b9ae2075..1180fea9 100644 --- a/tests/e2e/permission-prompt-ask.spec.ts +++ b/tests/e2e/permission-prompt-ask.spec.ts @@ -39,7 +39,7 @@ workspaces: await page.locator(".permission-allow-btn").click(); // Form disappears after response. - await expect(form).not.toBeVisible({ timeout: 10_000 }); + await expect(form).not.toBeVisible(); // Session completes with "Done." from the mock LLM. await expect(assistantText(page, "Done.")).toBeVisible({ @@ -80,7 +80,7 @@ workspaces: await page.locator(".permission-deny-btn").click(); // Form disappears after response. - await expect(form).not.toBeVisible({ timeout: 10_000 }); + await expect(form).not.toBeVisible(); // Session completes with "Done." from the mock LLM after the denied tool_result. await expect(assistantText(page, "Done.")).toBeVisible({ diff --git a/tests/e2e/request-history-subscription-boundary.spec.ts b/tests/e2e/request-history-subscription-boundary.spec.ts index 1fc3e42b..c03815cd 100644 --- a/tests/e2e/request-history-subscription-boundary.spec.ts +++ b/tests/e2e/request-history-subscription-boundary.spec.ts @@ -137,7 +137,7 @@ test("request_history replays before later live task updates on the same socket" await expect(assistantText(page, seedReply)).toBeVisible({ timeout: timeoutMs, }); - await expect.poll(() => tid ?? -1, { timeout: 15_000 }).toBeGreaterThan(0); + await expect.poll(() => tid ?? -1).toBeGreaterThan(0); const taskId = tid!; const observer = new WebSocket(`${backend.baseURL.replace(/^http/, "ws")}/ws`); @@ -173,7 +173,6 @@ test("request_history replays before later live task updates on the same socket" msg?.type === "task_history_end" && msg?.tid === taskId, ), - { timeout: 15_000 }, ) .not.toBe(-1); @@ -196,7 +195,6 @@ test("request_history replays before later live task updates on the same socket" msg?.event !== undefined) && containsLiveTurnText(msg, livePrompt, liveReply), ), - { timeout: 15_000 }, ) .not.toBe(-1); diff --git a/tests/e2e/rerender-stability.spec.ts b/tests/e2e/rerender-stability.spec.ts index 32741ed3..325d5170 100644 --- a/tests/e2e/rerender-stability.spec.ts +++ b/tests/e2e/rerender-stability.spec.ts @@ -85,7 +85,7 @@ test("completed messages are not recreated when new messages arrive", { tag: "@c // The fork button is rendered (though hidden) for forkable messages. await expect( page.locator("[style*='display: contents'] .fork-btn"), - ).not.toHaveCount(0, { timeout: 5_000 }); + ).not.toHaveCount(0); // Verify hook is working const totalCalls = await page.evaluate( diff --git a/tests/e2e/resume.spec.ts b/tests/e2e/resume.spec.ts index 4d94aa5c..0dd96d87 100644 --- a/tests/e2e/resume.spec.ts +++ b/tests/e2e/resume.spec.ts @@ -175,7 +175,7 @@ supports_websockets = false async function waitForSidebarTask( page: Page, labelText: string, - timeoutMs = 15_000, + timeoutMs = 540_000, ) { await expect( page.locator(".sidebar-item .sidebar-label", { hasText: labelText }), @@ -191,7 +191,7 @@ function sidebarTaskByLabel(page: Page, labelText: string): Locator { async function openSidebarTaskByLabel( page: Page, labelText: string, - timeoutMs = 15_000, + timeoutMs = 540_000, ): Promise { const row = sidebarTaskByLabel(page, labelText); await expect(row).toBeVisible({ timeout: timeoutMs }); @@ -203,7 +203,7 @@ async function openSidebarTaskByLabel( async function expectVisibleToolCall( page: Page, commandText: string, - timeoutMs = 30_000, + timeoutMs = 540_000, ): Promise { await expect( page.locator(".tool-call:visible", { hasText: commandText }), @@ -214,7 +214,7 @@ async function openRunningChildTask( page: Page, labelText: string, commandText: string, - timeoutMs = 30_000, + timeoutMs = 540_000, ): Promise { const row = await openSidebarTaskByLabel(page, labelText, timeoutMs); await expect(row.locator(".task-type-icon.processing")).toBeVisible({ @@ -236,16 +236,15 @@ test("idle task is not nudged after resume + restart", { tag: "@no-codex" }, asy await page.goto("/"); await page.locator('button[title="New task"]').first().click(); const input = page.locator(".input-textarea:visible").first(); - await expect(input).toBeEnabled({ timeout: 15_000 }); + await expect(input).toBeEnabled(); await input.fill('Please reply with "restart-alive"'); const sendBtn = page.locator(".btn-send:visible").first(); - await expect(sendBtn).toBeEnabled({ timeout: 5_000 }); + await expect(sendBtn).toBeEnabled(); await sendBtn.click(); // Wait for the response — task becomes "alive" (idle) await expect(assistantText(page, "restart-alive")).toBeVisible({ - timeout: 30_000, - }); + }); // Exactly one assistant message before restart await expect(page.locator(".message.assistant-message")).toHaveCount(1); @@ -268,19 +267,17 @@ test("idle task is not nudged after resume + restart", { tag: "@no-codex" }, asy // set status to "active" even though the session is idle). const resumeBtn = page.locator(".btn-banner-resume"); const isResumeVisible = await resumeBtn - .isVisible({ timeout: 5_000 }) + .isVisible() .catch(() => false); if (isResumeVisible) { await resumeBtn.click(); await expect(page.locator(".btn-banner-stop")).toBeVisible({ - timeout: 15_000, - }); + }); } // History preserved, still one assistant message await expect(assistantText(page, "restart-alive")).toBeVisible({ - timeout: 10_000, - }); + }); await expect(page.locator(".message.assistant-message")).toHaveCount(1); // --- Second restart --- @@ -295,8 +292,7 @@ test("idle task is not nudged after resume + restart", { tag: "@no-codex" }, asy // Wait for history to load await expect(assistantText(page, "restart-alive")).toBeVisible({ - timeout: 10_000, - }); + }); // Wait to give any [SYSTEM:] nudge time to trigger a response. // If nudged, the mock API responds with "Done." — a second assistant message. @@ -314,15 +310,14 @@ test("MCP tools work after backend restart", async ({ await page.goto("/"); await page.locator('button[title="New task"]').first().click(); const input = page.locator(".input-textarea:visible").first(); - await expect(input).toBeEnabled({ timeout: 15_000 }); + await expect(input).toBeEnabled(); await input.fill('reply with "mcp-ready"'); const sendBtn = page.locator(".btn-send:visible").first(); - await expect(sendBtn).toBeEnabled({ timeout: 5_000 }); + await expect(sendBtn).toBeEnabled(); await sendBtn.click(); await expect(assistantText(page, "mcp-ready")).toBeVisible({ - timeout: 30_000, - }); + }); // Restart — task is auto-resumed with the new MCP socket await restartableBackend.restart(); @@ -335,10 +330,10 @@ test("MCP tools work after backend restart", async ({ // Send a message that triggers an MCP tool call (Task tool). // If the MCP socket is broken, this will fail with "Backend connection failed". const input2 = page.locator(".input-textarea:visible").first(); - await expect(input2).toBeEnabled({ timeout: 15_000 }); + await expect(input2).toBeEnabled(); await input2.fill('call task research reply with "sub-task-done"'); const sendBtn2 = page.locator(".btn-send:visible").first(); - await expect(sendBtn2).toBeEnabled({ timeout: 5_000 }); + await expect(sendBtn2).toBeEnabled(); await sendBtn2.click(); // The sub-task should be created and complete. Its result ("sub-task-done") @@ -348,7 +343,7 @@ test("MCP tools work after backend restart", async ({ page .locator(".tool-result-section") .getByText("sub-task-done", { exact: true }), - ).toBeVisible({ timeout: 60_000 }); + ).toBeVisible(); }); test("active task receives nudge and continues after restart", { tag: "@claude-only" }, async ({ @@ -359,15 +354,14 @@ test("active task receives nudge and continues after restart", { tag: "@claude-o await page.goto("/"); await page.locator('button[title="New task"]').first().click(); const input = page.locator(".input-textarea:visible").first(); - await expect(input).toBeEnabled({ timeout: 15_000 }); + await expect(input).toBeEnabled(); await input.fill("run command sleep 60"); const sendBtn = page.locator(".btn-send:visible").first(); - await expect(sendBtn).toBeEnabled({ timeout: 5_000 }); + await expect(sendBtn).toBeEnabled(); await sendBtn.click(); // Wait until the tool call is visible (task is mid-turn / "active") await expect(page.locator(".tool-call", { hasText: "sleep 60" })).toBeVisible( - { timeout: 30_000 }, ); // Kill and restart the backend while task is "active" @@ -377,16 +371,14 @@ test("active task receives nudge and continues after restart", { tag: "@claude-o await page.goto("/"); // Task should still be in the sidebar - await expect(page.locator(".sidebar-item")).toHaveCount(1, { - timeout: 10_000, - }); + await expect(page.locator(".sidebar-item")).toHaveCount(1); // Click on the task await page.locator(".sidebar-item").first().click(); // The nudge message and the agent's reply should appear // After nudge, the agent retries and eventually responds with "Done." - await expect(assistantText(page, "Done.")).toBeVisible({ timeout: 120_000 }); + await expect(assistantText(page, "Done.")).toBeVisible(); }); test("sub-task result delivered to parent after backend restart", { tag: "@claude-only" }, async ({ @@ -398,10 +390,10 @@ test("sub-task result delivered to parent after backend restart", { tag: "@claud await page.goto("/"); await page.locator('button[title="New task"]').first().click(); const input = page.locator(".input-textarea:visible").first(); - await expect(input).toBeEnabled({ timeout: 15_000 }); + await expect(input).toBeEnabled(); await input.fill("call task research run command sleep 10"); const sendBtn = page.locator(".btn-send:visible").first(); - await expect(sendBtn).toBeEnabled({ timeout: 5_000 }); + await expect(sendBtn).toBeEnabled(); await sendBtn.click(); // Wait for the sub-task's shell tool call to appear — this confirms the sub-task @@ -409,7 +401,7 @@ test("sub-task result delivered to parent after backend restart", { tag: "@claud // Use :visible to avoid strict-mode errors from hidden tool-calls in other tasks. await expect( page.locator(".tool-call:visible", { hasText: "sleep 10" }), - ).toBeVisible({ timeout: 30_000 }); + ).toBeVisible(); // Kill and restart the backend while the sub-task is still running await restartableBackend.restart(); @@ -420,11 +412,11 @@ test("sub-task result delivered to parent after backend restart", { tag: "@claud // Navigate to the parent task. The parent has the lower task ID so it appears // last when tasks are sorted by descending tid (WelcomePage) or in the Sidebar. const taskItems = page.locator(".sidebar-item:not(.sidebar-new-task)"); - await expect(taskItems).toHaveCount(1, { timeout: 15_000 }); + await expect(taskItems).toHaveCount(1); await taskItems.last().click(); // The parent should eventually process the sub-task result and respond with "Done." - await expect(assistantText(page, "Done.")).toBeVisible({ timeout: 60_000 }); + await expect(assistantText(page, "Done.")).toBeVisible(); }); test("waiting parent receives batch results after restart", { tag: "@claude-only" }, async ({ @@ -436,15 +428,15 @@ test("waiting parent receives batch results after restart", { tag: "@claude-only await page.goto("/"); await page.locator('button[title="New task"]').first().click(); const input = page.locator(".input-textarea:visible").first(); - await expect(input).toBeEnabled({ timeout: 15_000 }); + await expect(input).toBeEnabled(); await input.fill("call 2 tasks research run command sleep 30"); const sendBtn = page.locator(".btn-send:visible").first(); - await expect(sendBtn).toBeEnabled({ timeout: 5_000 }); + await expect(sendBtn).toBeEnabled(); await sendBtn.click(); // Wait for parent + 2 children to appear in the sidebar. const allTaskItems = page.locator(".sidebar-item:not(.sidebar-new-task)"); - await expect(allTaskItems).toHaveCount(3, { timeout: 30_000 }); + await expect(allTaskItems).toHaveCount(3); // Navigate to each child and verify both are running. await openRunningChildTask(page, "Test task 1", "sleep 30"); @@ -456,11 +448,11 @@ test("waiting parent receives batch results after restart", { tag: "@claude-only // Navigate to the parent task (lowest tid → last in desc-sorted sidebar). const taskItems = page.locator(".sidebar-item:not(.sidebar-new-task)"); - await expect(taskItems).toHaveCount(1, { timeout: 15_000 }); + await expect(taskItems).toHaveCount(1); await taskItems.last().click(); // Wait for the parent to respond to the batch delivery. - await expect(assistantText(page, "Done.")).toBeVisible({ timeout: 60_000 }); + await expect(assistantText(page, "Done.")).toBeVisible(); // Allow time for any spurious additional messages. await page.waitForTimeout(3_000); @@ -474,19 +466,18 @@ test("waiting parent retains pre-restart completed child in batch results", { ta page, restartableBackend, }, testInfo) => { - test.setTimeout(150_000); await page.goto("/"); await page.locator('button[title="New task"]').first().click(); const input = page.locator(".input-textarea:visible").first(); - await expect(input).toBeEnabled({ timeout: 15_000 }); + await expect(input).toBeEnabled(); await input.fill("call partial-restart-batch"); const sendBtn = page.locator(".btn-send:visible").first(); - await expect(sendBtn).toBeEnabled({ timeout: 5_000 }); + await expect(sendBtn).toBeEnabled(); await sendBtn.click(); const allTaskItems = page.locator(".sidebar-item:not(.sidebar-new-task)"); - await expect(allTaskItems).toHaveCount(3, { timeout: 30_000 }); + await expect(allTaskItems).toHaveCount(3); await openRunningChildTask(page, "Slow child", "sleep 20"); const fastChildRow = await openSidebarTaskByLabel(page, "Fast child"); @@ -494,20 +485,20 @@ test("waiting parent retains pre-restart completed child in batch results", { ta fastChildRow.locator( ".task-type-icon.completed, .task-type-icon.resumable", ), - ).toBeVisible({ timeout: 30_000 }); + ).toBeVisible(); // Restart after one child has completed but before the slow child has. await restartableBackend.restart(); await page.goto("/"); const taskItems = page.locator(".sidebar-item:not(.sidebar-new-task)"); - await expect(taskItems).toHaveCount(1, { timeout: 15_000 }); + await expect(taskItems).toHaveCount(1); await taskItems.last().click(); const batchDivider = page.locator(".result-divider.system-user-message", { hasText: "Sub-task results", }); - await expect(batchDivider).toBeVisible({ timeout: 90_000 }); + await expect(batchDivider).toBeVisible(); await batchDivider.click(); const batchMessage = page.locator(".message.user-message.system-user-expanded", { @@ -539,16 +530,15 @@ test("waiting parent with completed children gets results after restart", async await page.goto("/"); await page.locator('button[title="New task"]').first().click(); const input = page.locator(".input-textarea:visible").first(); - await expect(input).toBeEnabled({ timeout: 15_000 }); + await expect(input).toBeEnabled(); await input.fill('reply with "parent-ready"'); const sendBtn = page.locator(".btn-send:visible").first(); - await expect(sendBtn).toBeEnabled({ timeout: 5_000 }); + await expect(sendBtn).toBeEnabled(); await sendBtn.click(); // Wait for the response — task is now "alive" with a valid session. await expect(assistantText(page, "parent-ready")).toBeVisible({ - timeout: 30_000, - }); + }); // Stop the backend before manipulating the DB to avoid "database is locked". await restartableBackend.stop(); @@ -579,7 +569,7 @@ test("waiting parent with completed children gets results after restart", async // The parent should receive the [SYSTEM: Session resumed] message with // task_results and respond with "Done." (mock API handles [SYSTEM: messages). - await expect(assistantText(page, "Done.")).toBeVisible({ timeout: 60_000 }); + await expect(assistantText(page, "Done.")).toBeVisible(); }); test( @@ -591,15 +581,14 @@ test( await page.goto("/"); await page.locator('button[title="New task"]').first().click(); const input = page.locator(".input-textarea:visible").first(); - await expect(input).toBeEnabled({ timeout: 15_000 }); + await expect(input).toBeEnabled(); await input.fill('reply with "deferred-history-check"'); const sendBtn = page.locator(".btn-send:visible").first(); - await expect(sendBtn).toBeEnabled({ timeout: 5_000 }); + await expect(sendBtn).toBeEnabled(); await sendBtn.click(); await expect(assistantText(page, "deferred-history-check")).toBeVisible({ - timeout: 90_000, - }); + }); const modelBeforeRestart = await page.locator(".banner-model").textContent(); expect(modelBeforeRestart).toBeTruthy(); @@ -660,12 +649,12 @@ test( // Wait for history to load (prior turn's assistant response visible). await expect( assistantText(page, "deferred-history-check"), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); // Wait for the history replay to complete. await expect(async () => { expect(replayDone).toBe(true); - }).toPass({ timeout: 10_000 }); + }).toPass(); // Exactly one session/init from the JSONL session_meta line. // A second synthetic one would appear if onThreadStarted emitted diff --git a/tests/e2e/sandbox-env-djinja.spec.ts b/tests/e2e/sandbox-env-djinja.spec.ts index acb0464d..e9b6f3e7 100644 --- a/tests/e2e/sandbox-env-djinja.spec.ts +++ b/tests/e2e/sandbox-env-djinja.spec.ts @@ -143,7 +143,7 @@ test("agents[*].sandbox.env values render Djinja env templates", async ({ const createdPromise = waitForMessage( ws, (data) => data.type === "task_created" && typeof data.tid === "number", - 10_000, + 540_000, ); ws.send( JSON.stringify({ @@ -161,7 +161,7 @@ test("agents[*].sandbox.env values render Djinja env templates", async ({ const historyEndPromise = waitForMessage( ws, (data) => data.type === "task_history_end" && data.tid === tid, - 10_000, + 540_000, ); ws.send(JSON.stringify({ type: "request_history", tid })); await historyEndPromise; @@ -172,7 +172,7 @@ test("agents[*].sandbox.env values render Djinja env templates", async ({ data.type === "title_update" && data.tid === tid && data.title === "secret-42", - 30_000, + 540_000, ); ws.send( diff --git a/tests/e2e/session-ending.spec.ts b/tests/e2e/session-ending.spec.ts index a015ce52..6ddfd9a1 100644 --- a/tests/e2e/session-ending.spec.ts +++ b/tests/e2e/session-ending.spec.ts @@ -60,15 +60,14 @@ test("End button shows ending state then session exits", { tag: "@no-codex" }, a // The End button should disappear (or be hidden) await expect(page.locator(".btn-banner-end")).not.toBeVisible({ - timeout: 5_000, - }); + }); const ending = page.locator(".banner-processing", { hasText: "Ending..." }); const stop = page.locator(".btn-banner-stop"); const archive = page.locator(".btn-banner-archive"); // End transitions to either ending state or direct completion. - const deadline = Date.now() + 5_000; + const deadline = Date.now() + 540_000; let sawEnding = false; let sawArchive = false; while (Date.now() < deadline) { @@ -83,7 +82,7 @@ test("End button shows ending state then session exits", { tag: "@no-codex" }, a await expect(async () => { expect(await ending.isVisible()).toBe(true); expect(await stop.isVisible()).toBe(true); - }).toPass({ timeout: 5_000 }); + }).toPass(); } // The textarea should still be enabled (user can keep typing drafts) @@ -95,8 +94,7 @@ test("End button shows ending state then session exits", { tag: "@no-codex" }, a // Eventually the session should exit and show the Archive button await expect(page.locator(".btn-banner-archive")).toBeVisible({ - timeout: 30_000, - }); + }); // "Ending..." should no longer be visible await expect(page.locator(".banner-processing")).not.toBeVisible(); @@ -151,7 +149,7 @@ test("background command output re-enters processing state", { tag: "@no-copilot expect(falseIdx).toBeGreaterThanOrEqual(0); // turn completed const reentry = processingTransitions.slice(falseIdx + 1).includes(true); expect(reentry).toBe(true); // re-entered processing - }).toPass({ timeout: 10_000 }); + }).toPass(); }); test("repro: ending capability matches Stop visibility", { tag: "@copilot-only" }, async ({ @@ -169,14 +167,13 @@ test("repro: ending capability matches Stop visibility", { tag: "@copilot-only" await page.locator(".btn-banner-end").click(); await expect(page.locator(".btn-banner-end")).not.toBeVisible({ - timeout: 5_000, - }); + }); const ending = page.locator(".banner-processing", { hasText: "Ending..." }); const stop = page.locator(".btn-banner-stop"); const archive = page.locator(".btn-banner-archive"); - const deadline = Date.now() + 5_000; + const deadline = Date.now() + 540_000; while (Date.now() < deadline) { const endingVisible = await ending.isVisible(); if (!endingVisible) { @@ -194,5 +191,5 @@ test("repro: ending capability matches Stop visibility", { tag: "@copilot-only" return; } - await expect(archive).toBeVisible({ timeout: 30_000 }); + await expect(archive).toBeVisible(); }); diff --git a/tests/e2e/session-import.spec.ts b/tests/e2e/session-import.spec.ts index 4d47e786..24ff88c3 100644 --- a/tests/e2e/session-import.spec.ts +++ b/tests/e2e/session-import.spec.ts @@ -143,13 +143,13 @@ test("Import group node in sidebar expands on click and navigates correctly", { await page.goto(BACKEND_URL + "/testws/cydo-test-workspace"); // Sidebar should appear. - await expect(page.locator(".sidebar")).toBeVisible({ timeout: 15_000 }); + await expect(page.locator(".sidebar")).toBeVisible(); // Wait for the Import group node to appear (enumerateSessions is async). const importGroupNode = page.locator(".sidebar-item.sidebar-archive-node", { hasText: /Import \(\d+\)/, }); - await expect(importGroupNode).toBeVisible({ timeout: 15_000 }); + await expect(importGroupNode).toBeVisible(); // Before clicking the group, its children should NOT be visible. const importableEntry = page.locator(".sidebar-item .sidebar-label", { @@ -161,34 +161,34 @@ test("Import group node in sidebar expands on click and navigates correctly", { await importGroupNode.click(); // URL must contain /import (not navigate to /). - await expect(page).toHaveURL(/\/import/, { timeout: 5_000 }); + await expect(page).toHaveURL(/\/import/); // Group is now expanded: child importable session is visible. - await expect(importableEntry).toBeVisible({ timeout: 5_000 }); + await expect(importableEntry).toBeVisible(); // Click the importable session to load its history. await importableEntry.click(); // URL must preserve workspace/project context (not just /task/). - await expect(page).toHaveURL(/\/testws\/cydo-test-workspace\/task\//, { timeout: 5_000 }); + await expect(page).toHaveURL(/\/testws\/cydo-test-workspace\/task\//); // History loads. await expect( page.locator(".message.user-message", { hasText: "sidebar import group test", }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); // Group remains expanded because a descendant is active. await expect(importableEntry).toBeVisible(); // Click the Import group node again — must stay on /import (not navigate to /). await importGroupNode.click(); - await expect(page).toHaveURL(/\/import/, { timeout: 5_000 }); + await expect(page).toHaveURL(/\/import/); // Group is still visible and expanded. await expect(importGroupNode).toBeVisible(); - await expect(importableEntry).toBeVisible({ timeout: 5_000 }); + await expect(importableEntry).toBeVisible(); } finally { await killBackend(proc); rmSync(workDir, { recursive: true, force: true }); @@ -243,7 +243,7 @@ test("importable session appears on startup, history loads, Import Session promo page.locator(".project-card-title", { hasText: "cydo-test-workspace", }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); // The importable session should appear in the project card task list. // enumerateSessions() runs asynchronously in a background thread, so @@ -252,14 +252,14 @@ test("importable session appears on startup, history loads, Import Session promo ".project-card-sessions .sidebar-item .sidebar-label", { hasText: "hello imported session" }, ); - await expect(importableLabel).toBeVisible({ timeout: 15_000 }); + await expect(importableLabel).toBeVisible(); // Click the importable session to navigate to it. This triggers // setActiveTaskId(String(tid)) which routes to /:ws/:proj/task/:tid. await importableLabel.click(); // Session view with sidebar should now be visible. - await expect(page.locator(".sidebar")).toBeVisible({ timeout: 10_000 }); + await expect(page.locator(".sidebar")).toBeVisible(); // The Import group must be visible and expanded (the active task is a // descendant, so flattenTree renders the group's children). @@ -267,45 +267,45 @@ test("importable session appears on startup, history loads, Import Session promo page.locator(".sidebar-item.sidebar-archive-node", { hasText: /Import \(1\)/, }), - ).toBeVisible({ timeout: 10_000 }); + ).toBeVisible(); // The importable session entry is visible inside the expanded group. await expect( page.locator(".sidebar-item .sidebar-label", { hasText: "hello imported session", }), - ).toBeVisible({ timeout: 5_000 }); + ).toBeVisible(); // History loads: the user message from the JSONL file is rendered. await expect( page.locator(".message.user-message", { hasText: "hello imported session", }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); // The "Import Session" button is shown for importable tasks. const importBtn = page.locator(".btn-resume", { hasText: "Import Session", }); - await expect(importBtn).toBeVisible({ timeout: 5_000 }); + await expect(importBtn).toBeVisible(); // Click "Import Session" to promote the task to a regular session. await importBtn.click(); // After promotion the "Import Session" button disappears. - await expect(importBtn).not.toBeVisible({ timeout: 10_000 }); + await expect(importBtn).not.toBeVisible(); // The Import group disappears because there are no more importable sessions. await expect( page.locator(".sidebar-item.sidebar-archive-node", { hasText: /Import/, }), - ).not.toBeVisible({ timeout: 10_000 }); + ).not.toBeVisible(); // The promoted session is now a regular resumable task in the sidebar. await expect( page.locator(".btn-banner-resume"), - ).toBeVisible({ timeout: 5_000 }); + ).toBeVisible(); } finally { await killBackend(proc); rmSync(workDir, { recursive: true, force: true }); @@ -382,23 +382,22 @@ test("import replay drops durable session/status rows but keeps compact boundary ".project-card-sessions .sidebar-item .sidebar-label", { hasText: "import replay status fixture" }, ); - await expect(importableLabel).toBeVisible({ timeout: 15_000 }); + await expect(importableLabel).toBeVisible(); await importableLabel.click(); - await expect(page).toHaveURL(/\/task\//, { timeout: 5_000 }); + await expect(page).toHaveURL(/\/task\//); await expect( page.locator(".message.user-message", { hasText: "import replay status fixture", }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); // session/status is transient and must not appear in transcript replay. await expect(page.locator(".system-status-message")).toHaveCount(0); // compact_boundary is durable and must remain visible in replay. await expect(page.locator(".compact-boundary-message")).toBeVisible({ - timeout: 10_000, - }); + }); // Completed imported sessions should not resurrect transient banners. await expect(page.locator(".banner-processing")).not.toBeVisible(); diff --git a/tests/e2e/session-lifecycle.spec.ts b/tests/e2e/session-lifecycle.spec.ts index b3a42007..08563cb6 100644 --- a/tests/e2e/session-lifecycle.spec.ts +++ b/tests/e2e/session-lifecycle.spec.ts @@ -20,17 +20,17 @@ test("history survives page reload", async ({ page, agentType }) => { await expect( page.locator(".message.user-message", { hasText: "persistent" }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); await page.reload(); await page .locator(".sidebar-item .sidebar-label", { hasText: "persistent" }) - .click({ timeout: 15_000 }); + .click(); await expect( page.locator(".message.user-message", { hasText: "persistent" }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); }); test("no duplicate messages after reload", async ({ page, agentType }) => { @@ -45,17 +45,17 @@ test("no duplicate messages after reload", async ({ page, agentType }) => { await expect( page.locator(".message.user-message", { hasText: "nodups" }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); const countBefore = await page.locator(".message.user-message").count(); await page.reload(); await page .locator(".sidebar-item .sidebar-label", { hasText: "nodups" }) - .click({ timeout: 15_000 }); + .click(); await expect( page.locator(".message.user-message", { hasText: "nodups" }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); const countAfter = await page.locator(".message.user-message").count(); expect(countAfter).toBeLessThanOrEqual(countBefore); @@ -122,12 +122,11 @@ test("session resume continues conversation", async ({ page, agentType }) => { }); const input = page.locator(".input-textarea").first(); - await expect(input).toBeVisible({ timeout: 5_000 }); + await expect(input).toBeVisible(); await input.click(); await input.fill('Please reply with "post-resume"'); await expect(page.locator(".btn-send").first()).toBeEnabled({ - timeout: 5_000, - }); + }); await page.locator(".btn-send").first().click(); await expect(assistantText(page, "post-resume")).toBeVisible({ @@ -168,7 +167,7 @@ test("codex reload replays apply_patch tool call", { tag: "@codex-only" }, async }); const activeTaskRow = page.locator(".sidebar-item.active").first(); - await expect(activeTaskRow).toBeVisible({ timeout: 5_000 }); + await expect(activeTaskRow).toBeVisible(); const taskTid = await activeTaskRow.getAttribute("data-tid"); expect(taskTid).toBeTruthy(); @@ -177,7 +176,7 @@ test("codex reload replays apply_patch tool call", { tag: "@codex-only" }, async const reloadedTaskRow = page .locator(`.sidebar-item[data-tid="${taskTid!}"]`) .first(); - await expect(reloadedTaskRow).toBeVisible({ timeout: 15_000 }); + await expect(reloadedTaskRow).toBeVisible(); await reloadedTaskRow.click(); await expect(reloadedTaskRow).toHaveClass(/active/); @@ -189,9 +188,9 @@ test("codex reload replays apply_patch tool call", { tag: "@codex-only" }, async await tool.locator(".tool-header").hover(); const viewBtn = tool.locator(".tool-view-file"); - await expect(viewBtn).toBeVisible({ timeout: 5_000 }); + await expect(viewBtn).toBeVisible(); await viewBtn.click(); - await expect(page.locator(".file-viewer")).toBeVisible({ timeout: 5_000 }); + await expect(page.locator(".file-viewer")).toBeVisible(); await expect(page.locator(".file-viewer")).toContainText( "codex-fileviewer-create.txt", ); diff --git a/tests/e2e/session-management.spec.ts b/tests/e2e/session-management.spec.ts index 0a665efe..c2e68ff6 100644 --- a/tests/e2e/session-management.spec.ts +++ b/tests/e2e/session-management.spec.ts @@ -19,7 +19,7 @@ test("session creation shows sidebar entry", async ({ page, agentType }) => { page.locator(".message.user-message", { hasText: 'Please reply with "hello-claude"', }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); }); test("session switching preserves messages", async ({ page, agentType }) => { @@ -42,7 +42,7 @@ test("session switching preserves messages", async ({ page, agentType }) => { .locator(".sidebar-item .sidebar-label", { hasText: "first" }) .click(); - await expect(assistantText(page, "first")).toBeVisible({ timeout: 10_000 }); + await expect(assistantText(page, "first")).toBeVisible(); await expect(assistantText(page, "second")).not.toBeVisible(); }); diff --git a/tests/e2e/session-switch-rerender.spec.ts b/tests/e2e/session-switch-rerender.spec.ts index a0497e0d..fd93fe2e 100644 --- a/tests/e2e/session-switch-rerender.spec.ts +++ b/tests/e2e/session-switch-rerender.spec.ts @@ -95,8 +95,7 @@ test("switching sessions does not re-render every mounted SessionView", { tag: " .locator(".sidebar-item .sidebar-label", { hasText: labels[0]! }) .click(); await expect(assistantText(page, labels[0]!)).toBeVisible({ - timeout: 10_000, - }); + }); const probeCount = await page.evaluate(() => { const t = (window as any).__renderTracker; @@ -128,7 +127,7 @@ test("switching sessions does not re-render every mounted SessionView", { tag: " await page .locator(".sidebar-item .sidebar-label", { hasText: label }) .click(); - await expect(assistantText(page, label)).toBeVisible({ timeout: 10_000 }); + await expect(assistantText(page, label)).toBeVisible(); // Brief flush so render counts settle before the next click. await page.waitForTimeout(50); } diff --git a/tests/e2e/sidebar-navigation.spec.ts b/tests/e2e/sidebar-navigation.spec.ts index d31ac3b6..f5ddde14 100644 --- a/tests/e2e/sidebar-navigation.spec.ts +++ b/tests/e2e/sidebar-navigation.spec.ts @@ -34,7 +34,7 @@ test("sidebar click pushes exactly one history entry", async ({ .click(); // Verify we navigated to the first task - await expect(assistantText(page, "alpha")).toBeVisible({ timeout: 10_000 }); + await expect(assistantText(page, "alpha")).toBeVisible(); // Check that exactly one history entry was pushed (not two) const historyAfter = await page.evaluate(() => history.length); @@ -42,5 +42,5 @@ test("sidebar click pushes exactly one history entry", async ({ // The definitive test: one Back click should return to "beta" await page.goBack(); - await expect(assistantText(page, "beta")).toBeVisible({ timeout: 10_000 }); + await expect(assistantText(page, "beta")).toBeVisible(); }); diff --git a/tests/e2e/source-view-seq-alignment.spec.ts b/tests/e2e/source-view-seq-alignment.spec.ts index abf94444..5e999617 100644 --- a/tests/e2e/source-view-seq-alignment.spec.ts +++ b/tests/e2e/source-view-seq-alignment.spec.ts @@ -46,15 +46,15 @@ test("source view abstract/raw events stay aligned across multi-turn streaming", // Open source view const viewSourceBtn = lastAssistantMsg.locator(".view-source-btn"); - await expect(viewSourceBtn).toBeVisible({ timeout: 5_000 }); + await expect(viewSourceBtn).toBeVisible(); await viewSourceBtn.click(); const sourceView = page.locator(".source-view"); - await expect(sourceView).toBeVisible({ timeout: 5_000 }); + await expect(sourceView).toBeVisible(); // Collect all event headers const eventHeaders = sourceView.locator(".source-event-header"); - await expect(eventHeaders.first()).toBeVisible({ timeout: 5_000 }); + await expect(eventHeaders.first()).toBeVisible(); const eventCount = await eventHeaders.count(); expect(eventCount).toBeGreaterThan(0); @@ -70,7 +70,7 @@ test("source view abstract/raw events stay aligned across multi-turn streaming", // The event body should now be visible const eventItem = sourceView.locator(".source-event").nth(i); const eventBody = eventItem.locator(".source-event-body"); - await expect(eventBody).toBeVisible({ timeout: 5_000 }); + await expect(eventBody).toBeVisible(); // Check if a Raw tab exists (it may not if no raw data) const rawTab = eventBody.locator(".source-tab", { hasText: "Raw" }); @@ -81,7 +81,7 @@ test("source view abstract/raw events stay aligned across multi-turn streaming", // Wait for raw JSON to load const rawBlock = eventBody.locator(".code-pre-wrap").first(); - await expect(rawBlock).toBeVisible({ timeout: 10_000 }); + await expect(rawBlock).toBeVisible(); const rawText = await rawBlock.locator("pre").innerText(); @@ -102,7 +102,7 @@ test("source view abstract/raw events stay aligned across multi-turn streaming", // Collapse the event before moving to the next one await header.click(); - await expect(eventBody).not.toBeVisible({ timeout: 2_000 }); + await expect(eventBody).not.toBeVisible(); } }); diff --git a/tests/e2e/stderr-handling.spec.ts b/tests/e2e/stderr-handling.spec.ts index dbeffd70..1a8bae69 100644 --- a/tests/e2e/stderr-handling.spec.ts +++ b/tests/e2e/stderr-handling.spec.ts @@ -44,29 +44,27 @@ test("codex stderr view source keeps tabs and shows abstract stderr payload", { .last(); await stderrWrapper.hover(); const viewSourceBtn = stderrWrapper.locator(".view-source-btn"); - await expect(viewSourceBtn).toBeVisible({ timeout: 5_000 }); + await expect(viewSourceBtn).toBeVisible(); await viewSourceBtn.click(); const sourceView = page.locator(".source-view").last(); - await expect(sourceView).toBeVisible({ timeout: 5_000 }); + await expect(sourceView).toBeVisible(); // Expand the first (and likely only) event in the list const firstEvent = sourceView.locator(".source-event-header").first(); - await expect(firstEvent).toBeVisible({ timeout: 5_000 }); + await expect(firstEvent).toBeVisible(); await firstEvent.click(); // Both tabs should be visible inside the expanded event await expect( sourceView.locator(".source-tab", { hasText: "Raw" }), - ).toBeVisible({ timeout: 5_000 }); + ).toBeVisible(); const abstractTab = sourceView.locator(".source-tab", { hasText: "Abstract", }); - await expect(abstractTab).toBeVisible({ timeout: 5_000 }); + await expect(abstractTab).toBeVisible(); await abstractTab.click(); - await expect(sourceView).toContainText('"type": "process/stderr"', { - timeout: 5_000, - }); - await expect(sourceView).toContainText('"text":', { timeout: 5_000 }); + await expect(sourceView).toContainText('"type": "process/stderr"'); + await expect(sourceView).toContainText('"text":'); }); diff --git a/tests/e2e/subagent-rendering.spec.ts b/tests/e2e/subagent-rendering.spec.ts index 45bef779..e185c400 100644 --- a/tests/e2e/subagent-rendering.spec.ts +++ b/tests/e2e/subagent-rendering.spec.ts @@ -20,7 +20,6 @@ test("sub-agent messages with parent_tool_use_id render nested", { tag: "@claude page, agentType, }) => { - test.setTimeout(120_000); await enterSession(page); @@ -52,5 +51,5 @@ test("sub-agent messages with parent_tool_use_id render nested", { tag: "@claude page.locator( '[style*="display: contents"] .sub-agent-messages .assistant-message', ), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); }); diff --git a/tests/e2e/subtask-missing-outputs-end.spec.ts b/tests/e2e/subtask-missing-outputs-end.spec.ts index 41ce460a..4426c3ec 100644 --- a/tests/e2e/subtask-missing-outputs-end.spec.ts +++ b/tests/e2e/subtask-missing-outputs-end.spec.ts @@ -1,7 +1,6 @@ import { test, expect, enterSession, sendMessage } from "./fixtures"; test("subtask auto-ends after satisfying missing-outputs retry", { tag: "@claude-only" }, async ({ page }) => { - test.setTimeout(120_000); let childTid: number | null = null; @@ -25,8 +24,7 @@ test("subtask auto-ends after satisfying missing-outputs retry", { tag: "@claude await expect .poll(() => childTid, { message: "expected subtask to be created", - timeout: 30_000, - }) + }) .not.toBeNull(); const parentItem = page.locator('.sidebar-item[data-tid="1"]'); @@ -38,7 +36,7 @@ test("subtask auto-ends after satisfying missing-outputs retry", { tag: "@claude page.locator('[style*="display: contents"] .message-list') .getByText("Done.", { exact: true }) .last(), - ).toBeVisible({ timeout: 60_000 }); + ).toBeVisible(); await subtaskItem.click(); await expect(page.locator(`.sidebar-item[data-tid="${childTid}"].active`)).toBeVisible(); @@ -47,11 +45,11 @@ test("subtask auto-ends after satisfying missing-outputs retry", { tag: "@claude page.locator('[style*="display: contents"] .message-list') .getByText("Missing required outputs") .last(), - ).toBeVisible({ timeout: 60_000 }); + ).toBeVisible(); await expect( subtaskItem.locator(".task-type-icon.completed, .task-type-icon.resumable"), - ).toBeVisible({ timeout: 30_000 }); + ).toBeVisible(); await expect(subtaskItem.locator(".task-type-icon.processing")).not.toBeVisible(); await expect(subtaskItem.locator(".task-type-icon.alive")).not.toBeVisible(); await expect(subtaskItem.locator(".task-type-icon.failed")).not.toBeVisible(); @@ -61,7 +59,6 @@ test("subtask auto-ends after satisfying missing-outputs retry", { tag: "@claude test("repro: manually ending a resumed completed subtask does not rerun output enforcement", { tag: "@claude-only" }, async ({ page, }) => { - test.setTimeout(120_000); let childTid: number | null = null; @@ -90,8 +87,7 @@ test("repro: manually ending a resumed completed subtask does not rerun output e await expect .poll(() => childTid, { message: "expected subtask to be created", - timeout: 30_000, - }) + }) .not.toBeNull(); const parentItem = page.locator('.sidebar-item[data-tid="1"]'); @@ -104,23 +100,21 @@ test("repro: manually ending a resumed completed subtask does not rerun output e await expect(page.locator('.sidebar-item[data-tid="1"].active')).toBeVisible(); await expect( currentMessages.getByText("Done.", { exact: true }).last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); await subtaskItem.click(); await expect( page.locator(`.sidebar-item[data-tid="${childTid}"].active`), ).toBeVisible(); await expect(page.locator(".btn-banner-resume:visible").first()).toBeVisible({ - timeout: 30_000, - }); + }); await expect(currentMessages.getByText("Missing required outputs")).toHaveCount( 0, ); await page.locator(".btn-banner-resume:visible").first().click(); await expect(page.locator(".btn-banner-end:visible").first()).toBeVisible({ - timeout: 30_000, - }); + }); const doneCountBeforeQuestion = await currentMessages .getByText("Done.", { exact: true }) @@ -132,7 +126,6 @@ test("repro: manually ending a resumed completed subtask does not rerun output e await expect .poll( async () => currentMessages.getByText("Done.", { exact: true }).count(), - { timeout: 60_000 }, ) .toBeGreaterThan(doneCountBeforeQuestion); @@ -140,7 +133,7 @@ test("repro: manually ending a resumed completed subtask does not rerun output e await expect( subtaskItem.locator(".task-type-icon.completed, .task-type-icon.resumable"), - ).toBeVisible({ timeout: 30_000 }); + ).toBeVisible(); await expect(subtaskItem.locator(".task-type-icon.processing")).not.toBeVisible(); await expect(subtaskItem.locator(".task-type-icon.alive")).not.toBeVisible(); await expect(subtaskItem.locator(".task-type-icon.failed")).not.toBeVisible(); diff --git a/tests/e2e/subtask-orphan-exit.spec.ts b/tests/e2e/subtask-orphan-exit.spec.ts index 9cbd5f74..ac5f477c 100644 --- a/tests/e2e/subtask-orphan-exit.spec.ts +++ b/tests/e2e/subtask-orphan-exit.spec.ts @@ -1,7 +1,6 @@ import { test, expect, enterSession, sendMessage } from "./fixtures"; test("subtask with orphaned background command exits cleanly", { tag: "@claude-only" }, async ({ page }) => { - test.setTimeout(90_000); await enterSession(page); @@ -18,13 +17,12 @@ test("subtask with orphaned background command exits cleanly", { tag: "@claude-o // delivered and the browser switched back to the parent. await expect( page.locator('[style*="display: contents"] .message-list .cydo-task-spec').last(), - ).toBeVisible({ timeout: 60_000 }); + ).toBeVisible(); }); test("subtask result is completed (not failed) when process is killed after result event", { tag: "@claude-only" }, async ({ page, }) => { - test.setTimeout(90_000); await enterSession(page); @@ -38,7 +36,7 @@ test("subtask result is completed (not failed) when process is killed after resu // .cydo-task-spec renders only for Task tool result items in the parent view. await expect( page.locator('[style*="display: contents"] .message-list .cydo-task-spec').last(), - ).toBeVisible({ timeout: 60_000 }); + ).toBeVisible(); // After result delivery, the subtask process is killed (SIGTERM after 5s). // Wait for the child sidebar item to leave the "processing" state. @@ -46,8 +44,7 @@ test("subtask result is completed (not failed) when process is killed after resu .locator(".sidebar-item:not(.active):not(.sidebar-new-task)") .first(); await expect(childItem.locator(".task-type-icon.processing")).not.toBeVisible({ - timeout: 15_000, - }); + }); // The subtask must end up as "completed", not "failed". await expect(childItem.locator(".task-type-icon.failed")).not.toBeVisible(); diff --git a/tests/e2e/subtask-result.spec.ts b/tests/e2e/subtask-result.spec.ts index 42cc8741..b4a44e80 100644 --- a/tests/e2e/subtask-result.spec.ts +++ b/tests/e2e/subtask-result.spec.ts @@ -2,7 +2,6 @@ import { test, expect, enterSession, sendMessage } from "./fixtures"; test("sub-task result text delivered to parent", async ({ page }) => { // Sub-task creation + completion requires more time than the default 60s budget. - test.setTimeout(120_000); await enterSession(page); @@ -19,5 +18,5 @@ test("sub-task result text delivered to parent", async ({ page }) => { page.locator('[style*="display: contents"] .message-list') .getByText("subtask-result-marker", { exact: true }) .last(), - ).toBeVisible({ timeout: 90_000 }); + ).toBeVisible(); }); diff --git a/tests/e2e/suggest-context.spec.ts b/tests/e2e/suggest-context.spec.ts index 7c55f441..d5133986 100644 --- a/tests/e2e/suggest-context.spec.ts +++ b/tests/e2e/suggest-context.spec.ts @@ -25,7 +25,7 @@ async function getSuggestionTexts( page: Parameters[0], ): Promise { const suggestions = page.locator(".btn-suggestion"); - await expect(suggestions.first()).toBeVisible({ timeout: 30_000 }); + await expect(suggestions.first()).toBeVisible(); const count = await suggestions.count(); const texts: string[] = []; for (let i = 0; i < count; i++) { diff --git a/tests/e2e/suggestion-header.spec.ts b/tests/e2e/suggestion-header.spec.ts index 1aa54a30..73e130c7 100644 --- a/tests/e2e/suggestion-header.spec.ts +++ b/tests/e2e/suggestion-header.spec.ts @@ -24,7 +24,7 @@ test("suggestion header reports correct user message count", async ({ // Wait for suggestions to appear (the mock echoes the session header as second suggestion) const suggestions = page.locator(".btn-suggestion"); - await expect(suggestions.first()).toBeVisible({ timeout: 30_000 }); + await expect(suggestions.first()).toBeVisible(); // Collect all suggestion texts const count = await suggestions.count(); @@ -73,7 +73,7 @@ test("suggestion history includes assistant text entries", async ({ // Wait for suggestions to appear (the mock echoes the conversation body as third suggestion) const suggestions = page.locator(".btn-suggestion"); - await expect(suggestions.first()).toBeVisible({ timeout: 30_000 }); + await expect(suggestions.first()).toBeVisible(); // Collect all suggestion texts const count = await suggestions.count(); diff --git a/tests/e2e/system-messages.spec.ts b/tests/e2e/system-messages.spec.ts index d8f77ada..152169cf 100644 --- a/tests/e2e/system-messages.spec.ts +++ b/tests/e2e/system-messages.spec.ts @@ -23,7 +23,7 @@ test("first message renders as collapsed system-user-message with entry point la const userMsg = page .locator(".message.user-message.system-user-message") .first(); - await expect(userMsg).toBeVisible({ timeout: 15_000 }); + await expect(userMsg).toBeVisible(); const headerText = await userMsg.locator(".system-user-header").innerText(); expect(headerText.trim().length).toBeGreaterThan(0); @@ -87,7 +87,7 @@ test("session-start system message stays collapsed after reload", { tag: "@no-co await page.reload(); await expect( page.locator(".system-user-message", { hasText: "Session start:" }).first(), - ).toBeVisible({ timeout: 30_000 }); + ).toBeVisible(); }); test("task prompt system message keeps task type label after reload", { tag: "@no-codex" }, async ({ @@ -124,30 +124,29 @@ test("task prompt system message keeps task type label after reload", { tag: "@n taskCreatedEvents.find((event) => event.relation_type === "subtask")?.tid ?? null; expect(childTid).not.toBeNull(); - }).toPass({ timeout: 30_000 }); + }).toPass(); await page.locator(`.sidebar-item[data-tid="${childTid}"]`).waitFor({ state: "visible", - timeout: 30_000, - }); + }); await page.locator(`.sidebar-item[data-tid="${childTid}"]`).click(); await expect( page.locator(`.sidebar-item[data-tid="${childTid}"].active`), - ).toBeVisible({ timeout: 10_000 }); + ).toBeVisible(); await expect( page.locator('[style*="display: contents"] .message-list .system-user-message', { hasText: "Task prompt: research", }), - ).toBeVisible({ timeout: 30_000 }); + ).toBeVisible(); await page.reload(); await page.locator(`.sidebar-item[data-tid="${childTid}"]`).click(); await expect( page.locator(`.sidebar-item[data-tid="${childTid}"].active`), - ).toBeVisible({ timeout: 10_000 }); + ).toBeVisible(); await expect( page.locator('[style*="display: contents"] .message-list .system-user-message', { hasText: "Task prompt: research", }), - ).toBeVisible({ timeout: 30_000 }); + ).toBeVisible(); }); diff --git a/tests/e2e/task-spawn-link.spec.ts b/tests/e2e/task-spawn-link.spec.ts index e07ec66a..46d6450b 100644 --- a/tests/e2e/task-spawn-link.spec.ts +++ b/tests/e2e/task-spawn-link.spec.ts @@ -3,7 +3,6 @@ import { test, expect, enterSession, sendMessage } from "./fixtures"; test("parent tool-call card shows Open task link to spawned subtask", async ({ page, }) => { - test.setTimeout(120_000); await enterSession(page); await sendMessage(page, 'call task research reply with "ping"'); @@ -13,7 +12,7 @@ test("parent tool-call card shows Open task link to spawned subtask", async ({ .locator('[style*="display: contents"] .message-list') .locator('[data-testid="cydo-task-spec-open"]') .first(); - await expect(link).toBeVisible({ timeout: 90_000 }); + await expect(link).toBeVisible(); // Verify href targets a numeric task id. const href = await link.getAttribute("href"); diff --git a/tests/e2e/task-type-change.spec.ts b/tests/e2e/task-type-change.spec.ts index 2a1dd06c..335e762d 100644 --- a/tests/e2e/task-type-change.spec.ts +++ b/tests/e2e/task-type-change.spec.ts @@ -26,7 +26,7 @@ async function waitForNewTid(page: Page, before: Set): Promise { ); newTid = tids.find((tid: string) => !before.has(tid)); expect(newTid).toBeTruthy(); - }).toPass({ timeout: 5_000 }); + }).toPass(); return newTid!; } @@ -57,8 +57,7 @@ test("changing entry point after draft creation updates backend", async ({ // Entry-point picker should be visible in draft mode await expect(page.locator(".task-type-picker")).toBeVisible({ - timeout: 5_000, - }); + }); const before = await snapshotTids(page); @@ -71,7 +70,7 @@ test("changing entry point after draft creation updates backend", async ({ const draftTid = await waitForNewTid(page, before); await expect( page.locator(`.sidebar-item[data-tid="${draftTid}"] .draft-label`), - ).toBeVisible({ timeout: 2_000 }); + ).toBeVisible(); // Click a different entry point ("blank") await page.locator(".task-type-row", { hasText: "blank" }).click(); diff --git a/tests/e2e/two-agents-one-driver.spec.ts b/tests/e2e/two-agents-one-driver.spec.ts index d9be9e00..f7f3a91b 100644 --- a/tests/e2e/two-agents-one-driver.spec.ts +++ b/tests/e2e/two-agents-one-driver.spec.ts @@ -148,7 +148,7 @@ test("two agents sharing a driver get separate sandbox envs", async ({ const created = waitForMessage( ws, (d) => d.type === "task_created" && typeof d.tid === "number", - 10_000, + 540_000, ); ws.send( JSON.stringify({ @@ -170,7 +170,7 @@ test("two agents sharing a driver get separate sandbox envs", async ({ const end = waitForMessage( ws, (d) => d.type === "task_history_end" && d.tid === tid, - 10_000, + 540_000, ); ws.send(JSON.stringify({ type: "request_history", tid })); await end; @@ -180,7 +180,7 @@ test("two agents sharing a driver get separate sandbox envs", async ({ ws, (d) => d.type === "title_update" && d.tid === tidWork && d.title === "Work Title", - 30_000, + 540_000, ); const titlePersonal = waitForMessage( ws, @@ -188,7 +188,7 @@ test("two agents sharing a driver get separate sandbox envs", async ({ d.type === "title_update" && d.tid === tidPersonal && d.title === "Personal Title", - 30_000, + 540_000, ); for (const tid of [tidWork, tidPersonal]) { @@ -213,7 +213,7 @@ test("two agents sharing a driver get separate sandbox envs", async ({ d.tid === tidWork && d.event?.type === "session/init" && d.event?.agent_name === "work-claude", - 30_000, + 540_000, ); const initPersonal = waitForMessage( ws, @@ -222,7 +222,7 @@ test("two agents sharing a driver get separate sandbox envs", async ({ d.tid === tidPersonal && d.event?.type === "session/init" && d.event?.agent_name === "personal-claude", - 30_000, + 540_000, ); ws.send(JSON.stringify({ type: "request_history", tid: tidWork })); diff --git a/tests/e2e/ui-regression.spec.ts b/tests/e2e/ui-regression.spec.ts index 1318c613..d62119dc 100644 --- a/tests/e2e/ui-regression.spec.ts +++ b/tests/e2e/ui-regression.spec.ts @@ -26,14 +26,14 @@ test("sidebar status dot reflects session state", async ({ timeout: responseTimeout(agentType), }); - const dotAliveTimeout = agentType === "codex" ? 10_000 : 5_000; + const dotAliveTimeout = responseTimeout(agentType); await expect(sidebarItem.locator(".task-type-icon.alive")).toBeVisible({ timeout: dotAliveTimeout, }); await killSession(page, agentType); - const dotFailedTimeout = agentType === "codex" ? 10_000 : 5_000; + const dotFailedTimeout = responseTimeout(agentType); await expect(sidebarItem.locator(".task-type-icon.failed")).toBeVisible({ timeout: dotFailedTimeout, }); @@ -54,7 +54,7 @@ test("multi-client navigation isolation", { tag: "@no-codex" }, async ({ await expect( pageA.locator(".message.user-message", { hasText: "isolation-a" }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); await expect( pageB.locator(".message.user-message", { hasText: "isolation-a" }), @@ -62,7 +62,7 @@ test("multi-client navigation isolation", { tag: "@no-codex" }, async ({ await expect( pageB.locator(".sidebar-item .sidebar-label", { hasText: "isolation-a" }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); await pageB.close(); }); @@ -98,13 +98,13 @@ test("tool result with Bash output renders correctly", async ({ await expect( page.locator(".tool-result", { hasText: "tool-result-test" }), - ).toBeVisible({ timeout: responseTimeout(agentType) + 15_000 }); + ).toBeVisible({ timeout: responseTimeout(agentType) }); // Tool subtitle only present for Claude (description field) if (agentType === "claude") { await expect( page.locator(".tool-subtitle", { hasText: "Running command" }), - ).toBeVisible({ timeout: 5_000 }); + ).toBeVisible(); } }); @@ -130,24 +130,23 @@ test("fork stays focused on forked session", async ({ page, agentType }) => { }); await userMsg.hover(); const forkBtn = userMsg.locator(".fork-btn"); - await expect(forkBtn).toBeVisible({ timeout: 15_000 }); + await expect(forkBtn).toBeVisible(); await forkBtn.click(); const forkEntry = page.locator(".sidebar-item .sidebar-label", { hasText: "(fork)", }); - await expect(forkEntry).toBeVisible({ timeout: 10_000 }); + await expect(forkEntry).toBeVisible(); const forkSidebarItem = page.locator(".sidebar-item.active", { hasText: "(fork)", }); - await expect(forkSidebarItem).toBeVisible({ timeout: 5_000 }); + await expect(forkSidebarItem).toBeVisible(); // Use :visible to avoid strict mode violation from multiple resume buttons (codex sessions) await expect(page.locator(".btn-banner-resume:visible").first()).toBeVisible({ - timeout: 5_000, - }); + }); }); test("assistant messages do not render literal undefined", async ({ diff --git a/tests/e2e/undo-claude-live-middle.spec.ts b/tests/e2e/undo-claude-live-middle.spec.ts index 6dcb9307..87c8bc6c 100644 --- a/tests/e2e/undo-claude-live-middle.spec.ts +++ b/tests/e2e/undo-claude-live-middle.spec.ts @@ -21,9 +21,9 @@ async function openUndoDialogForTurn(page: Page, turnText: string) { }) .last(); await userMsg.hover(); - await expect(userMsg.locator(".undo-btn")).toBeVisible({ timeout: 5_000 }); + await expect(userMsg.locator(".undo-btn")).toBeVisible(); await userMsg.locator(".undo-btn").click(); - await expect(page.locator(".undo-dialog")).toBeVisible({ timeout: 5_000 }); + await expect(page.locator(".undo-dialog")).toBeVisible(); } async function readUndoRemovalCount(page: Page): Promise { @@ -125,19 +125,17 @@ test("claude live idle undo latest turn avoids UUID truncation alert", { tag: "@ await page.locator(".btn-undo").click(); const input = page.locator(".input-textarea:visible").first(); - await expect(input).toBeEnabled({ timeout: 15_000 }); + await expect(input).toBeEnabled(); await expect(async () => { await assertTurnPresence(page, ["alert-one"], true); await assertTurnPresence(page, ["alert-two"], false); - }).toPass({ timeout: 15_000 }); + }).toPass(); expect( dialogs.filter((message) => message.includes("UUID not found for truncation")), ).toEqual([]); - await expect(input).toHaveValue('Please reply with "alert-two"', { - timeout: 15_000, - }); + await expect(input).toHaveValue('Please reply with "alert-two"'); }); test("claude live idle undo restores user message text into the textarea", { tag: "@claude-only" }, async ({ @@ -153,7 +151,7 @@ test("claude live idle undo restores user message text into the textarea", { tag await expect(assistantText(page, "reply-one")).toBeVisible({ timeout }); const input = page.locator(".input-textarea:visible").first(); - await expect(input).toBeEnabled({ timeout: 15_000 }); + await expect(input).toBeEnabled(); page.on("dialog", (d) => { d.dismiss().catch(() => {}); @@ -162,12 +160,12 @@ test("claude live idle undo restores user message text into the textarea", { tag await openUndoDialogForTurn(page, "reply-one"); await page.locator(".btn-undo").click(); - await expect(input).toBeEnabled({ timeout: 15_000 }); + await expect(input).toBeEnabled(); await expect(async () => { await assertTurnPresence(page, ["reply-one"], false); - }).toPass({ timeout: 15_000 }); + }).toPass(); - await expect(input).toHaveValue(prompt, { timeout: 15_000 }); + await expect(input).toHaveValue(prompt); }); test("claude live idle undo on turn three removes only turns three through five", { tag: "@claude-only" }, async ({ @@ -191,11 +189,11 @@ test("claude live idle undo on turn three removes only turns three through five" } const input = page.locator(".input-textarea:visible").first(); - await expect(input).toBeEnabled({ timeout: 15_000 }); + await expect(input).toBeEnabled(); await openUndoDialogForTurn(page, "live-three"); await page.locator(".btn-undo").click(); - await expect(input).toBeEnabled({ timeout: 15_000 }); + await expect(input).toBeEnabled(); await expect(async () => { const { userTexts, assistantTexts } = await readVisibleTurnTexts(page); const survivingUserTurns = turns.filter((turn) => @@ -214,11 +212,10 @@ test("claude live idle undo on turn three removes only turns three through five" ["live-three", "live-four", "live-five"], false, ); - }).toPass({ timeout: 15_000 }); + }).toPass(); await expect(input).toHaveValue( 'Please reply with "live-three"\n\nPlease reply with "live-four"\n\nPlease reply with "live-five"', - { timeout: 15_000 }, ); await sendMessage(page, 'Please reply with "live-six"'); @@ -246,30 +243,26 @@ test("claude undo preview targets the same turn before and after reload", { tag: } await expect(page.locator(".input-textarea:visible").first()).toBeEnabled({ - timeout: 15_000, - }); + }); await openUndoDialogForTurn(page, "reload-three"); await page.locator(".undo-dialog .btn", { hasText: "Cancel" }).click(); await expect(page.locator(".undo-dialog")).not.toBeVisible({ - timeout: 5_000, - }); + }); await killSession(page, agentType); await expect( page.locator(".message.user-message:not(.pending):not(.meta-message)", { hasText: "reload-three", }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); await openUndoDialogForTurn(page, "reload-three"); await page.locator(".btn-undo").click(); await expect(page.locator(".input-textarea:visible").first()).toBeEnabled({ - timeout: 15_000, - }); + }); await expect(page.locator(".input-textarea:visible").first()).toHaveValue( /Please reply with "reload-three"/, - { timeout: 15_000 }, ); await expect(async () => { const { userTexts, assistantTexts } = await readVisibleTurnTexts(page); @@ -289,7 +282,7 @@ test("claude undo preview targets the same turn before and after reload", { tag: ["reload-three", "reload-four", "reload-five"], false, ); - }).toPass({ timeout: 15_000 }); + }).toPass(); }); test("claude undo protocol keeps reload barrier and stable seq assignments", { tag: "@claude-only" }, async ({ @@ -328,8 +321,7 @@ test("claude undo protocol keeps reload barrier and stable seq assignments", { t const expectedRemoved = await readUndoRemovalCount(page); await page.locator(".btn-undo").click(); await expect(page.locator(".input-textarea:visible").first()).toBeEnabled({ - timeout: 15_000, - }); + }); await expect(async () => { const undoIdx = frames.findIndex( @@ -357,7 +349,7 @@ test("claude undo protocol keeps reload barrier and stable seq assignments", { t (msg, idx) => idx > reloadIdx && msg?.type === "task_history_end", ); expect(historyEndIdx).toBeGreaterThan(reloadIdx); - }).toPass({ timeout: 15_000 }); + }).toPass(); const seqToUuid = new Map(); const conflicts: string[] = []; diff --git a/tests/e2e/undo-codex-alive.spec.ts b/tests/e2e/undo-codex-alive.spec.ts index e02e0c70..e99374c7 100644 --- a/tests/e2e/undo-codex-alive.spec.ts +++ b/tests/e2e/undo-codex-alive.spec.ts @@ -19,11 +19,10 @@ async function openUndoDialogForUserMessage( }) .last(); await userMsg.hover(); - await expect(userMsg.locator(".undo-btn")).toBeVisible({ timeout: 5_000 }); + await expect(userMsg.locator(".undo-btn")).toBeVisible(); await userMsg.locator(".undo-btn").click(); await expect(page.locator(".undo-dialog:visible")).toBeVisible({ - timeout: 5_000, - }); + }); } async function undoUserMessage( @@ -42,33 +41,27 @@ test("codex alive-path undo: session stays alive after undo", { tag: "@codex-onl await sendMessage(page, 'Please reply with "alive-one"'); await expect(assistantText(page, "alive-one")).toBeVisible({ - timeout: 90_000, - }); + }); await sendMessage(page, 'Please reply with "alive-two"'); await expect(assistantText(page, "alive-two")).toBeVisible({ - timeout: 90_000, - }); + }); await sendMessage(page, 'Please reply with "alive-three"'); await expect(assistantText(page, "alive-three")).toBeVisible({ - timeout: 90_000, - }); + }); await sendMessage(page, 'Please reply with "alive-four"'); await expect(assistantText(page, "alive-four")).toBeVisible({ - timeout: 90_000, - }); + }); await sendMessage(page, 'Please reply with "alive-five"'); await expect(assistantText(page, "alive-five")).toBeVisible({ - timeout: 90_000, - }); + }); // Session is idle but alive — do NOT kill it before undoing. await expect(page.locator(".input-textarea:visible").first()).toBeEnabled({ - timeout: 15_000, - }); + }); await undoUserMessage(page, 'Please reply with "alive-three"'); @@ -77,14 +70,13 @@ test("codex alive-path undo: session stays alive after undo", { tag: "@codex-onl page.locator( ".message.user-message:not(.pending):not(.meta-message):visible", ), - ).toHaveCount(2, { timeout: 15_000 }); + ).toHaveCount(2); // After undo: exactly 2 assistant messages remain. await expect(page.locator(".message.assistant-message:visible")).toHaveCount( 2, { - timeout: 15_000, - }, + }, ); // alive-one/alive-two remain. @@ -111,14 +103,12 @@ test("codex alive-path undo: session stays alive after undo", { tag: "@codex-onl // Session is still alive: input box is visible and enabled. await expect(page.locator(".input-textarea:visible").first()).toBeEnabled({ - timeout: 15_000, - }); + }); // Send a follow-up message to confirm the session is fully functional. await sendMessage(page, 'Please reply with "alive-six"'); await expect(assistantText(page, "alive-six")).toBeVisible({ - timeout: 90_000, - }); + }); }); test("codex alive-path undo counts only active turns after prior rollback", { tag: "@codex-only" }, async ({ @@ -134,8 +124,7 @@ test("codex alive-path undo counts only active turns after prior rollback", { ta ]) { await sendMessage(page, `Please reply with "${marker}"`); await expect(assistantText(page, marker)).toBeVisible({ - timeout: 90_000, - }); + }); } await undoUserMessage(page, 'Please reply with "rolled-count-three"'); @@ -143,12 +132,11 @@ test("codex alive-path undo counts only active turns after prior rollback", { ta page.locator( ".message.user-message:visible:not(.pending):not(.meta-message)", ), - ).toHaveCount(2, { timeout: 15_000 }); + ).toHaveCount(2); await sendMessage(page, 'Please reply with "rolled-count-four"'); await expect(assistantText(page, "rolled-count-four")).toBeVisible({ - timeout: 90_000, - }); + }); await openUndoDialogForUserMessage(page, 'Please reply with "rolled-count-two"'); await expect(page.locator(".undo-dialog-count:visible")).toContainText( @@ -160,10 +148,9 @@ test("codex alive-path undo counts only active turns after prior rollback", { ta page.locator( ".message.user-message:visible:not(.pending):not(.meta-message)", ), - ).toHaveCount(1, { timeout: 15_000 }); + ).toHaveCount(1); await expect(page.locator(".message.assistant-message:visible")).toHaveCount( 1, - { timeout: 15_000 }, ); await expect( diff --git a/tests/e2e/undo-file-revert.spec.ts b/tests/e2e/undo-file-revert.spec.ts index 89346c91..434453e0 100644 --- a/tests/e2e/undo-file-revert.spec.ts +++ b/tests/e2e/undo-file-revert.spec.ts @@ -27,7 +27,7 @@ test("undo with file revert removes file created by agent", { tag: "@claude-only // Wait for the Write tool call and its result to complete (the mock follows // up with a "Done." text response after the tool result) - await expect(assistantText(page, "Done.")).toBeVisible({ timeout: 30_000 }); + await expect(assistantText(page, "Done.")).toBeVisible(); // 2. Verify the file was created on disk expect(existsSync(testFile), `File should exist at ${testFile}`).toBe(true); @@ -42,17 +42,16 @@ test("undo with file revert removes file created by agent", { tag: "@claude-only }) .last(); await userMsg.hover(); - await expect(userMsg.locator(".undo-btn")).toBeVisible({ timeout: 5_000 }); + await expect(userMsg.locator(".undo-btn")).toBeVisible(); await userMsg.locator(".undo-btn").click(); // 5. Confirm undo in the dialog (with file revert enabled — the default) - await expect(page.locator(".undo-dialog")).toBeVisible({ timeout: 5_000 }); + await expect(page.locator(".undo-dialog")).toBeVisible(); await page.locator(".btn-undo").click(); // 6. Wait for the undo to complete — the result banner confirms rewindFiles finished await expect(page.locator(".undo-result-banner")).toBeVisible({ - timeout: 15_000, - }); + }); // 7. Verify the file was reverted (should no longer exist since it didn't // exist before the undone message) diff --git a/tests/e2e/undo-steering.spec.ts b/tests/e2e/undo-steering.spec.ts index e9e14c22..ecfebbf4 100644 --- a/tests/e2e/undo-steering.spec.ts +++ b/tests/e2e/undo-steering.spec.ts @@ -9,7 +9,7 @@ test("undo removes preceding queue-operation lines from steering message", { tag await sendMessage(page, "run command sleep 5"); await expect( page.locator(".tool-call", { hasText: "sleep 5" }), - ).toBeVisible({ timeout: 30_000 }); + ).toBeVisible(); // 2. While the agent is busy, send a steering message. // This creates queue-operation enqueue/dequeue in the JSONL. @@ -20,7 +20,7 @@ test("undo removes preceding queue-operation lines from steering message", { tag // dequeued the steering message into the JSONL as a confirmed type:"user" line. await expect( page.locator(".message.user-message:not(.pending)", { hasText: "steered-reply" }), - ).toBeVisible({ timeout: 30_000 }); + ).toBeVisible(); // 4. Kill the session so we can undo. await killSession(page, agentType); @@ -30,7 +30,7 @@ test("undo removes preceding queue-operation lines from steering message", { tag // we need request_history to complete before hovering for the undo button. await expect( page.locator(".message.user-message:not(.pending)", { hasText: "steered-reply" }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); // 5. Find the confirmed user message for the steering message and undo it. const steerUserMsg = page @@ -39,25 +39,25 @@ test("undo removes preceding queue-operation lines from steering message", { tag }) .last(); await steerUserMsg.hover(); - await expect(steerUserMsg.locator(".undo-btn")).toBeVisible({ timeout: 5_000 }); + await expect(steerUserMsg.locator(".undo-btn")).toBeVisible(); await steerUserMsg.locator(".undo-btn").click(); // 6. Confirm undo. - await expect(page.locator(".undo-dialog")).toBeVisible({ timeout: 5_000 }); + await expect(page.locator(".undo-dialog")).toBeVisible(); await page.locator(".btn-undo").click(); // 7. Wait for reload to complete — the input box should appear. const input = page.locator(".input-textarea:visible").first(); - await expect(input).toBeVisible({ timeout: 15_000 }); + await expect(input).toBeVisible(); // 8. BUG ASSERTION: After undo, there should be NO pending user message // from the steering. The queue-operation enqueue should have been removed. await expect( page.locator(".message.user-message.pending"), - ).not.toBeVisible({ timeout: 10_000 }); + ).not.toBeVisible(); // The steered message's confirmed echo should also be gone. await expect( page.locator(".message.user-message:not(.pending)", { hasText: "steered-reply" }), - ).not.toBeVisible({ timeout: 5_000 }); + ).not.toBeVisible(); }); diff --git a/tests/e2e/undo-while-running.spec.ts b/tests/e2e/undo-while-running.spec.ts index aceb15d4..40a8477f 100644 --- a/tests/e2e/undo-while-running.spec.ts +++ b/tests/e2e/undo-while-running.spec.ts @@ -14,13 +14,11 @@ test("undo while session is running stops session and removes message", async ({ await sendMessage(page, 'Please reply with "first-reply"'); await expect(assistantText(page, "first-reply")).toBeVisible({ - timeout: 30_000, - }); + }); await sendMessage(page, 'Please reply with "second-reply"'); await expect(assistantText(page, "second-reply")).toBeVisible({ - timeout: 30_000, - }); + }); // Send "stall session" — mock API starts a response but never completes it, // keeping the session alive indefinitely. @@ -28,8 +26,7 @@ test("undo while session is running stops session and removes message", async ({ // Confirm the session is still running (stop button visible means it's processing). await expect(page.locator(".btn-banner-stop")).toBeVisible({ - timeout: 30_000, - }); + }); // Hover over the second user message to reveal the undo button. const secondUserMsg = page @@ -40,11 +37,10 @@ test("undo while session is running stops session and removes message", async ({ await secondUserMsg.hover(); await expect(secondUserMsg.locator(".undo-btn")).toBeVisible({ - timeout: 30_000, - }); + }); await secondUserMsg.locator(".undo-btn").click(); - await expect(page.locator(".undo-dialog")).toBeVisible({ timeout: 5_000 }); + await expect(page.locator(".undo-dialog")).toBeVisible(); await page.locator(".btn-undo").click(); // The undo is async: backend stops the session first (setGoal Dead), then @@ -55,15 +51,13 @@ test("undo while session is running stops session and removes message", async ({ page.locator(".message.user-message:not(.pending)", { hasText: "second-reply", }), - ).not.toBeVisible({ timeout: 30_000 }); + ).not.toBeVisible(); // The first reply should still be visible. await expect(assistantText(page, "first-reply").first()).toBeVisible({ - timeout: 15_000, - }); + }); // Verify the session auto-resumed (input box visible). await expect(page.locator(".input-textarea:visible").first()).toBeVisible({ - timeout: 15_000, - }); + }); }); diff --git a/tests/e2e/undo.spec.ts b/tests/e2e/undo.spec.ts index 9ee84f34..b2dd911b 100644 --- a/tests/e2e/undo.spec.ts +++ b/tests/e2e/undo.spec.ts @@ -15,28 +15,23 @@ test("undo moves user message text to input box", async ({ await sendMessage(page, 'Please reply with "reply-one"'); await expect(assistantText(page, "reply-one")).toBeVisible({ - timeout: 30_000, - }); + }); await sendMessage(page, 'Please reply with "reply-two"'); await expect(assistantText(page, "reply-two")).toBeVisible({ - timeout: 30_000, - }); + }); await sendMessage(page, 'Please reply with "reply-three"'); await expect(assistantText(page, "reply-three")).toBeVisible({ - timeout: 30_000, - }); + }); await sendMessage(page, 'Please reply with "reply-four"'); await expect(assistantText(page, "reply-four")).toBeVisible({ - timeout: 30_000, - }); + }); await sendMessage(page, 'Please reply with "reply-five"'); await expect(assistantText(page, "reply-five")).toBeVisible({ - timeout: 30_000, - }); + }); await killSession(page, agentType); @@ -54,8 +49,7 @@ test("undo moves user message text to input box", async ({ "reply-five", ]) { await expect(replyUser.filter({ hasText: marker })).toBeVisible({ - timeout: 15_000, - }); + }); } // Undo at message 3 @@ -67,20 +61,17 @@ test("undo moves user message text to input box", async ({ await thirdUserMsg.hover(); await expect(thirdUserMsg.locator(".undo-btn")).toBeVisible({ - timeout: 5_000, - }); + }); await thirdUserMsg.locator(".undo-btn").click(); - await expect(page.locator(".undo-dialog")).toBeVisible({ timeout: 5_000 }); + await expect(page.locator(".undo-dialog")).toBeVisible(); await page.locator(".btn-undo").click(); // After undo: exactly 2 confirmed user messages remain - await expect(replyUser).toHaveCount(2, { timeout: 15_000 }); + await expect(replyUser).toHaveCount(2); // After undo: exactly 2 assistant messages remain (reply-one and reply-two) - await expect(page.locator(".message.assistant-message")).toHaveCount(2, { - timeout: 15_000, - }); + await expect(page.locator(".message.assistant-message")).toHaveCount(2); // Messages 1 and 2 are still visible (user + assistant) await expect(replyUser.filter({ hasText: "reply-one" })).toBeVisible(); @@ -96,10 +87,9 @@ test("undo moves user message text to input box", async ({ // Input box contains the undone message text const input = page.locator(".input-textarea:visible").first(); - await expect(input).toBeVisible({ timeout: 15_000 }); + await expect(input).toBeVisible(); await expect(input).toHaveValue( 'Please reply with "reply-three"\n\nPlease reply with "reply-four"\n\nPlease reply with "reply-five"', - { timeout: 15_000 }, ); }); @@ -113,8 +103,7 @@ test("undo on first Claude message restores draft input", { tag: "@claude-only" const prompt = 'Please reply with "first-undo-draft"'; await sendMessage(page, prompt); await expect(assistantText(page, "first-undo-draft")).toBeVisible({ - timeout: 30_000, - }); + }); await killSession(page, agentType); @@ -123,27 +112,24 @@ test("undo on first Claude message restores draft input", { tag: "@claude-only" has: page.locator(".user-message", { hasText: "first-undo-draft" }), }) .last(); - await expect(firstUserMsg).toBeVisible({ timeout: 15_000 }); + await expect(firstUserMsg).toBeVisible(); await firstUserMsg.hover(); await expect(firstUserMsg.locator(".undo-btn")).toBeVisible({ - timeout: 5_000, - }); + }); await firstUserMsg.locator(".undo-btn").click(); - await expect(page.locator(".undo-dialog")).toBeVisible({ timeout: 5_000 }); + await expect(page.locator(".undo-dialog")).toBeVisible(); await page.locator(".btn-undo").click(); await expect( page.locator(".message.user-message:not(.pending)", { hasText: "first-undo-draft", }), - ).toHaveCount(0, { timeout: 15_000 }); - await expect(assistantText(page, "first-undo-draft")).toHaveCount(0, { - timeout: 15_000, - }); + ).toHaveCount(0); + await expect(assistantText(page, "first-undo-draft")).toHaveCount(0); const input = page.locator(".input-textarea:visible").first(); - await expect(input).toBeVisible({ timeout: 15_000 }); - await expect(input).toHaveValue(prompt, { timeout: 15_000 }); + await expect(input).toBeVisible(); + await expect(input).toHaveValue(prompt); }); diff --git a/tests/e2e/view-source-events.spec.ts b/tests/e2e/view-source-events.spec.ts index a63025f3..6a3ba836 100644 --- a/tests/e2e/view-source-events.spec.ts +++ b/tests/e2e/view-source-events.spec.ts @@ -33,16 +33,16 @@ test("View Source shows item-level events in collapsible list", { tag: "@claude- // Click the View Source button const viewSourceBtn = lastAssistantMsg.locator(".view-source-btn"); - await expect(viewSourceBtn).toBeVisible({ timeout: 5_000 }); + await expect(viewSourceBtn).toBeVisible(); await viewSourceBtn.click(); // The source view should show collapsible event items with type labels const sourceView = page.locator(".source-view"); - await expect(sourceView).toBeVisible({ timeout: 5_000 }); + await expect(sourceView).toBeVisible(); // Event types should be visible in collapsed headers const eventTypes = sourceView.locator(".source-event-type"); - await expect(eventTypes.first()).toBeVisible({ timeout: 5_000 }); + await expect(eventTypes.first()).toBeVisible(); const allTypes = await eventTypes.allInnerTexts(); expect(allTypes).toContain("item/started"); diff --git a/tests/e2e/virtual-project-workspace.spec.ts b/tests/e2e/virtual-project-workspace.spec.ts index aca8aca7..ac635b32 100644 --- a/tests/e2e/virtual-project-workspace.spec.ts +++ b/tests/e2e/virtual-project-workspace.spec.ts @@ -251,10 +251,10 @@ test( // Both workspace sections must appear (both discover "shared-project"). await expect( page.locator(".workspace-group-title", { hasText: "alpha" }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); await expect( page.locator(".workspace-group-title", { hasText: "beta" }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); const alphaSection = page.locator("section.workspace-group").filter({ has: page.locator(".workspace-group-title", { hasText: "alpha" }), @@ -267,15 +267,15 @@ test( // enumerateSessions() is async, so wait for it to land. await expect( alphaSection.locator(".sidebar-label", { hasText: "importable shared task" }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); await expect( betaSection.locator(".sidebar-label", { hasText: "importable shared task" }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); // Assertion 2: pinned task (workspace="alpha") appears ONLY in alpha. await expect( alphaSection.locator(".sidebar-label", { hasText: "pinned alpha task" }), - ).toBeVisible({ timeout: 5_000 }); + ).toBeVisible(); await expect( betaSection.locator(".sidebar-label", { hasText: "pinned alpha task" }), ).not.toBeVisible(); diff --git a/tests/e2e/worktree-archive-persisted-queue.spec.ts b/tests/e2e/worktree-archive-persisted-queue.spec.ts index 62293f15..003cba6d 100644 --- a/tests/e2e/worktree-archive-persisted-queue.spec.ts +++ b/tests/e2e/worktree-archive-persisted-queue.spec.ts @@ -68,8 +68,7 @@ async function openNewTask(page: Page): Promise { await page.goto(`${BACKEND_URL}/`); await page.locator('button[title="New task"]').first().click(); await expect(page.locator(".input-textarea:visible").first()).toBeEnabled({ - timeout: 15_000, - }); + }); } async function createSpikeAndWaitForComplete( @@ -87,7 +86,7 @@ async function createSpikeAndWaitForComplete( await expect(async () => { const subtasks = taskCreatedEvents.filter((e) => e.relation_type === "subtask"); expect(subtasks.length).toBeGreaterThan(priorSubtaskCount); - }).toPass({ timeout: 60_000 }); + }).toPass(); const subtasks = taskCreatedEvents.filter((e) => e.relation_type === "subtask"); const spikeTid = subtasks[subtasks.length - 1]!.tid; @@ -104,11 +103,9 @@ async function createSpikeAndWaitForComplete( } } expect(completed).toBeTruthy(); - }).toPass({ timeout: 60_000 }); + }).toPass(); - await page.waitForURL((url) => !url.pathname.endsWith(`/task/${spikeTid}`), { - timeout: 10_000, - }); + await page.waitForURL((url) => !url.pathname.endsWith(`/task/${spikeTid}`)); return spikeTid; } @@ -177,9 +174,8 @@ test( await page.goto(`${BACKEND_URL}/task/${spikeTidA}`); await expect(page.locator(".btn-banner-archive:visible")).toHaveText( "Archive", - { timeout: 3_000 }, ); - }).toPass({ timeout: 30_000 }); + }).toPass(); await page.locator(".btn-banner-archive:visible").click(); await expect(page.locator(".btn-banner-archive:visible")).toHaveText( diff --git a/tests/e2e/worktree-archive.spec.ts b/tests/e2e/worktree-archive.spec.ts index 05373eae..bedbc3ec 100644 --- a/tests/e2e/worktree-archive.spec.ts +++ b/tests/e2e/worktree-archive.spec.ts @@ -48,7 +48,7 @@ test("archive and unarchive a spike task's worktree", { tag: "@no-codex" }, asyn await expect(async () => { const spike = taskCreatedEvents.find((e) => e.relation_type === "subtask"); expect(spike).toBeTruthy(); - }).toPass({ timeout: 60_000 }); + }).toPass(); const spikeTid = taskCreatedEvents.find( (e) => e.relation_type === "subtask", )!.tid; @@ -66,26 +66,21 @@ test("archive and unarchive a spike task's worktree", { tag: "@no-codex" }, asyn } } expect(completed).toBeTruthy(); - }).toPass({ timeout: 60_000 }); + }).toPass(); // 4. Wait for auto-navigation away from the spike (process/exit handler fires). - await page.waitForURL((url) => !url.pathname.endsWith(`/task/${spikeTid}`), { - timeout: 10_000, - }); + await page.waitForURL((url) => !url.pathname.endsWith(`/task/${spikeTid}`)); // 5. Navigate to the spike task and wait for the Archive button (task is inactive). await expect(async () => { await page.locator(`.sidebar-item[data-tid="${spikeTid}"]`).click(); await expect( page.locator(`.sidebar-item[data-tid="${spikeTid}"].active`), - ).toBeVisible({ timeout: 3_000 }); + ).toBeVisible(); await expect(page.locator(".btn-banner-archive")).toBeVisible({ - timeout: 3_000, - }); - await expect(page.locator(".btn-banner-archive")).toHaveText("Archive", { - timeout: 1_000, - }); - }).toPass({ timeout: 30_000 }); + }); + await expect(page.locator(".btn-banner-archive")).toHaveText("Archive"); + }).toPass(); // Verify worktree directory exists before archiving. const wtDir = `${backend.wsDir}/.cydo/tasks/${spikeTid}/worktree`; @@ -162,14 +157,14 @@ test("cannot archive parent with alive descendant", { tag: "@no-codex" }, async // 2. Wait for the conversation task_created event (first event). await expect(async () => { expect(taskCreatedEvents.length).toBeGreaterThan(0); - }).toPass({ timeout: 30_000 }); + }).toPass(); const convTid = taskCreatedEvents[0].tid; // 3. Wait for spike to be created and alive. await expect(async () => { const spike = taskCreatedEvents.find((e) => e.relation_type === "subtask"); expect(spike).toBeTruthy(); - }).toPass({ timeout: 60_000 }); + }).toPass(); const spikeTid = taskCreatedEvents.find( (e) => e.relation_type === "subtask", )!.tid; @@ -179,7 +174,7 @@ test("cannot archive parent with alive descendant", { tag: "@no-codex" }, async (e) => e.tid === spikeTid && e.alive, ); expect(aliveEvent).toBeTruthy(); - }).toPass({ timeout: 30_000 }); + }).toPass(); // 4. Navigate to the parent conversation task (which is alive, waiting for // the stalling spike's MCP result). @@ -187,8 +182,8 @@ test("cannot archive parent with alive descendant", { tag: "@no-codex" }, async await page.locator(`.sidebar-item[data-tid="${convTid}"]`).click(); await expect( page.locator(`.sidebar-item[data-tid="${convTid}"].active`), - ).toBeVisible({ timeout: 3_000 }); - }).toPass({ timeout: 15_000 }); + ).toBeVisible(); + }).toPass(); // 5. Attempt to archive via keyboard shortcut while the subtree is alive. // Ctrl+Shift+A calls setArchived regardless of the task's own alive state, @@ -245,14 +240,14 @@ test("archiving parent task archives spike's worktree", { tag: "@no-codex" }, as // 2. Wait for the conversation task_created event (first event). await expect(async () => { expect(taskCreatedEvents.length).toBeGreaterThan(0); - }).toPass({ timeout: 30_000 }); + }).toPass(); const convTid = taskCreatedEvents[0].tid; // 3. Wait for spike to be created. await expect(async () => { const spike = taskCreatedEvents.find((e) => e.relation_type === "subtask"); expect(spike).toBeTruthy(); - }).toPass({ timeout: 60_000 }); + }).toPass(); const spikeTid = taskCreatedEvents.find( (e) => e.relation_type === "subtask", )!.tid; @@ -270,26 +265,23 @@ test("archiving parent task archives spike's worktree", { tag: "@no-codex" }, as } } expect(completed).toBeTruthy(); - }).toPass({ timeout: 60_000 }); + }).toPass(); // 5. Wait for auto-navigation away from the spike. - await page.waitForURL((url) => !url.pathname.endsWith(`/task/${spikeTid}`), { - timeout: 10_000, - }); + await page.waitForURL((url) => !url.pathname.endsWith(`/task/${spikeTid}`)); // 6. Navigate to the parent conversation task. await expect(async () => { await page.locator(`.sidebar-item[data-tid="${convTid}"]`).click(); await expect( page.locator(`.sidebar-item[data-tid="${convTid}"].active`), - ).toBeVisible({ timeout: 3_000 }); - }).toPass({ timeout: 15_000 }); + ).toBeVisible(); + }).toPass(); // 7. Stop if still alive, then wait for Archive button. try { await expect(page.locator(".btn-banner-stop")).toBeVisible({ - timeout: 5_000, - }); + }); await page.locator(".btn-banner-stop").click(); } catch { // Task already inactive — no stop button diff --git a/tests/e2e/worktree-fork.spec.ts b/tests/e2e/worktree-fork.spec.ts index 7445d596..f4183fcf 100644 --- a/tests/e2e/worktree-fork.spec.ts +++ b/tests/e2e/worktree-fork.spec.ts @@ -43,7 +43,7 @@ test("forked worktree spike task appears in sidebar", { tag: "@no-codex" }, asyn await expect(async () => { const spike = taskCreatedEvents.find((e) => e.relation_type === "subtask"); expect(spike).toBeTruthy(); - }).toPass({ timeout: 60_000 }); + }).toPass(); const spikeTid = taskCreatedEvents.find( (e) => e.relation_type === "subtask", @@ -68,15 +68,13 @@ test("forked worktree spike task appears in sidebar", { tag: "@no-codex" }, asyn } } expect(completed).toBeTruthy(); - }).toPass({ timeout: 60_000 }); + }).toPass(); // 3b. Wait for auto-navigation away from spike (process/exit handler). // This fires ~100ms after the spike exits. If we don't wait, our // subsequent hover+fork-click may land on the conversation task // instead of the spike (race with the auto-navigation). - await page.waitForURL((url) => !url.pathname.endsWith(`/task/${spikeTid}`), { - timeout: 10_000, - }); + await page.waitForURL((url) => !url.pathname.endsWith(`/task/${spikeTid}`)); // 4+5. Navigate to the spike task and wait for its history to load. // The process/exit event is sent before task_updated, so React may process @@ -88,7 +86,7 @@ test("forked worktree spike task appears in sidebar", { tag: "@no-codex" }, asyn // Confirm spike is the active task (not navigated away by process/exit). await expect( page.locator(`.sidebar-item[data-tid="${spikeTid}"].active`), - ).toBeVisible({ timeout: 3_000 }); + ).toBeVisible(); // Confirm spike's history loaded (scoped to active session to avoid // matching the conversation task's tool-call result, which also // contains "worktree-fork-content" in its text-content block). @@ -97,8 +95,8 @@ test("forked worktree spike task appears in sidebar", { tag: "@no-codex" }, asyn "[style*='display: contents'] [data-testid='assistant-text']", { hasText: "worktree-fork-content" }, ), - ).toBeVisible({ timeout: 10_000 }); - }).toPass({ timeout: 30_000 }); + ).toBeVisible(); + }).toPass(); // 6. Hover over the assistant message to reveal the fork button. // Scope to the active session to avoid matching the conversation task's @@ -115,9 +113,8 @@ test("forked worktree spike task appears in sidebar", { tag: "@no-codex" }, asyn .last(); await assistantWrapper.hover(); await expect(assistantWrapper.locator(".fork-btn")).toBeVisible({ - timeout: 5_000, - }); - }).toPass({ timeout: 15_000 }); + }); + }).toPass(); // 7. Fork the spike task (re-resolve the locator fresh after the hover). await page @@ -134,7 +131,7 @@ test("forked worktree spike task appears in sidebar", { tag: "@no-codex" }, asyn await expect(async () => { const fork = taskCreatedEvents.find((e) => e.relation_type === "fork"); expect(fork).toBeTruthy(); - }).toPass({ timeout: 15_000 }); + }).toPass(); const forkTid = taskCreatedEvents.find( (e) => e.relation_type === "fork", @@ -153,9 +150,7 @@ test("forked worktree spike task appears in sidebar", { tag: "@no-codex" }, asyn } } // Wait for the fork's URL. - await expect(page).toHaveURL(new RegExp(`/task/${forkTid}$`), { - timeout: 5_000, - }); + await expect(page).toHaveURL(new RegExp(`/task/${forkTid}$`)); // Wait for the fork's content to be visible in the active session. await expect( page.locator( @@ -164,6 +159,6 @@ test("forked worktree spike task appears in sidebar", { tag: "@no-codex" }, asyn hasText: "worktree-fork-content", }, ), - ).toBeVisible({ timeout: 5_000 }); - }).toPass({ timeout: 30_000 }); + ).toBeVisible(); + }).toPass(); }); diff --git a/tests/e2e/worktree-subproject.spec.ts b/tests/e2e/worktree-subproject.spec.ts index 9a7581b8..6d5d7f70 100644 --- a/tests/e2e/worktree-subproject.spec.ts +++ b/tests/e2e/worktree-subproject.spec.ts @@ -176,12 +176,11 @@ test( await expect( page.locator(".project-card-title[title='monorepo/project']"), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); await page.locator(".project-card-title[title='monorepo/project']").click(); await expect(page.locator(".input-textarea:visible").first()).toBeEnabled({ - timeout: 15_000, - }); + }); await expect(page).toHaveTitle("project — CyDo"); await page.locator(".input-textarea:visible").first().fill( @@ -199,13 +198,13 @@ test( const derived = listNumericTaskIds(tasksDir).filter((tid) => tid > 1); spikeTid = derived.length > 0 ? Math.min(...derived) : null; expect(spikeTid).not.toBeNull(); - }).toPass({ timeout: 60_000 }); + }).toPass(); const workspaceScopedWorktree = `${wsRoot}/.cydo/tasks/${spikeTid!}/worktree`; const repoScopedWorktree = `${repoDir}/.cydo/tasks/${spikeTid!}/worktree`; await expect - .poll(() => existsSync(workspaceScopedWorktree), { timeout: 30_000 }) + .poll(() => existsSync(workspaceScopedWorktree)) .toBe(true); expect(existsSync(repoScopedWorktree)).toBe(false); @@ -218,7 +217,6 @@ test( listFilesRecursive(expectedHistoryDir).filter((path) => path.endsWith(".jsonl"), ).length, - { timeout: 30_000 }, ) .toBeGreaterThan(0); } finally { @@ -246,12 +244,11 @@ test( await expect( page.locator(".project-card-title[title='monorepo/project']"), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); await page.locator(".project-card-title[title='monorepo/project']").click(); await expect(page.locator(".input-textarea:visible").first()).toBeEnabled({ - timeout: 15_000, - }); + }); await page.locator(".input-textarea:visible").first().fill( "Please run command sh -lc 'printf sandbox-root-ok > ../repo-root-write.txt && cat ../repo-root-write.txt'", @@ -259,7 +256,7 @@ test( await page.locator(".btn-send:visible").first().click(); await expect - .poll(() => existsSync(repoRootFile), { timeout: 60_000 }) + .poll(() => existsSync(repoRootFile)) .toBe(true); expect(readFileSync(repoRootFile, "utf8")).toBe("sandbox-root-ok"); } finally { diff --git a/tests/e2e/worktree-write-conflict.spec.ts b/tests/e2e/worktree-write-conflict.spec.ts index 7339f9e5..11119db5 100644 --- a/tests/e2e/worktree-write-conflict.spec.ts +++ b/tests/e2e/worktree-write-conflict.spec.ts @@ -45,7 +45,7 @@ test("Task tool rejects batch with multiple non-read-only siblings on shared wor // Wait for the parent task to respond (mock returns "Done." after tool_result), // confirming the Task call error was processed. - await expect(assistantText(page, "Done.")).toBeVisible({ timeout: 60_000 }); + await expect(assistantText(page, "Done.")).toBeVisible(); // The entire batch must have been rejected: no children created. const children = taskCreatedEvents.filter( diff --git a/tests/failure/process-failure.spec.ts b/tests/failure/process-failure.spec.ts index 9a001a4d..610211ca 100644 --- a/tests/failure/process-failure.spec.ts +++ b/tests/failure/process-failure.spec.ts @@ -6,11 +6,11 @@ test("process failure shows session-failed label", async ({ page }) => { await expect( page.locator(".session-failed-label"), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); await expect( page.locator(".input-textarea"), - ).not.toBeVisible({ timeout: 5_000 }); + ).not.toBeVisible(); }); test("process failure shows error text", async ({ page }) => { @@ -21,5 +21,5 @@ test("process failure shows error text", async ({ page }) => { page.locator(".session-failed-label", { hasText: /simulated process failure/, }), - ).toBeVisible({ timeout: 15_000 }); + ).toBeVisible(); }); diff --git a/tests/playwright.config.ts b/tests/playwright.config.ts index 39d8cdcf..455ae171 100644 --- a/tests/playwright.config.ts +++ b/tests/playwright.config.ts @@ -2,15 +2,31 @@ import { defineConfig } from "@playwright/test"; export default defineConfig({ testDir: "./e2e", - timeout: 180_000, + // The test timeout is a hang detector, not a performance expectation: many + // check derivations run concurrently (nix max-jobs), so wall-clock budgets + // sized for an idle machine turn correctness tests into de facto + // performance tests that flake under contention. Assertions are + // condition-based and return the moment they are satisfied, so generous + // budgets cost nothing on passing runs; only genuine failures report + // slower. Hitting these limits means "wedged", never "the machine was + // busy". + timeout: 600_000, // Nix provides effective reproducibility. As such, flaky tests are bugs. retries: 0, // Agents: you MAY NOT increase this value. fullyParallel: true, workers: 1, // One test per derivation — no Playwright-level parallelism reporter: [["list"]], + expect: { + // Assertion waits share one generous budget (see timeout above); keep it + // under the test timeout so a failing assertion reports its own + // expected/received detail instead of a generic test timeout. + timeout: 540_000, + }, use: { headless: true, screenshot: "on", + actionTimeout: 540_000, + navigationTimeout: 540_000, launchOptions: { executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH || undefined,