diff --git a/tests/e2e/agent-type-change.spec.ts b/tests/e2e/agent-type-change.spec.ts index 444edea..156a9da 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 d015068..d57b697 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 e8399f3..b16a645 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 c4d9ebb..351d4f5 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 04b43e8..dbfe5ef 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 1a6086c..2bbe692 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 708b75c..3738618 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 5bec04a..298ace0 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 f428599..3c31fa4 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 fc8d9d2..0f52fda 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 9b30d45..adfaf2a 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 ff5d3be..a168aea 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 8a1a8a3..a9bc6f9 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 0baa534..0a10c63 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 50268a7..ff0ac1f 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 2779224..683c2cd 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 1d4fbc0..f8c6832 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 9aa827e..84ed2b5 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 ee2a5af..49ad015 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 3d51af9..bc128da 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 d63755f..c292d4a 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 3510470..fbb6bb6 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 ac58bf6..93c2143 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 343eec4..556d6cd 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 c777180..ed4e71a 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 d68d2e1..8ee218e 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 09e5f9f..7c267f0 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 03fb3a5..52490fa 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 fe1b4dc..12a7069 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 5882922..587d60d 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 5b0f214..ee46dfe 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 05732ff..2978ace 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 ecff817..2b6967b 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 c2196ef..53786bf 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 39fb295..4bc68f4 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 0e1a52a..23d2f05 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 7570013..40db32e 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 bc788d2..eac98c6 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 5700c88..e62917b 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 c1a6dc8..fcbf5c6 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 b0a9f1b..9236b0a 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 9c16ad4..460303b 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 b9ae207..1180fea 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 1fc3e42..c03815c 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 32741ed..325d517 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 4d94aa5..0dd96d8 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 acb0464..e9b6f3e 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 a015ce5..6ddfd9a 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 4d47e78..24ff88c 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 b3a4200..08563cb 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 0a665ef..c2e68ff 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 a0497e0..fd93fe2 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 d31ac3b..f5ddde1 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 abf9444..5e99961 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 dbeffd7..1a8bae6 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 45bef77..e185c40 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 41ce460..4426c3e 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 9cbd5f7..ac5f477 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 42cc874..b4a44e8 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 7c55f44..d513398 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 1aa54a3..73e130c 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 d8f77ad..152169c 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 e07ec66..46d6450 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 2a1dd06..335e762 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 d9be9e0..f7f3a91 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 1318c61..d62119d 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 6dcb930..87c8bc6 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 e02e0c7..e99374c 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 89346c9..434453e 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 e9e14c2..ecfebbf 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 aceb15d..40a8477 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 9ee84f3..b2dd911 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 a63025f..6a3ba83 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 aca8aca..ac635b3 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 62293f1..003cba6 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 05373ea..bedbc3e 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 7445d59..f4183fc 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 9a7581b..6d5d7f7 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 7339f9e..11119db 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 9a001a4..610211c 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 39d8cdc..455ae17 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,