From 32da1f233a7b24af5e26a697ac476f56d5b681dc Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Wed, 27 May 2026 18:34:33 -0700 Subject: [PATCH] test: stabilize droid notification hook e2e (#2961) Use the main hook status snapshot as the synthetic hook setup precondition and give the inactive-worktree notification assertions a CI-sized wait. --- tests/e2e/droid-notification.spec.ts | 51 ++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/tests/e2e/droid-notification.spec.ts b/tests/e2e/droid-notification.spec.ts index 7cfa6f5bdf5..70ceb31a3a4 100644 --- a/tests/e2e/droid-notification.spec.ts +++ b/tests/e2e/droid-notification.spec.ts @@ -21,6 +21,14 @@ type NotificationDispatch = { agentLastAssistantMessage?: string } +type AgentStatusSummary = { + paneKey: string + state: string + agentType?: string + prompt?: string + lastAssistantMessage?: string +} + async function emitOscTitle(page: Page, ptyId: string, title: string) { await sendToTerminal(page, ptyId, `printf '\\033]0;${title}\\007'\r`) } @@ -93,15 +101,7 @@ async function switchToOtherExistingWorktree(page: Page): Promise { }) } -async function getAgentStatuses(page: Page): Promise< - { - paneKey: string - state: string - agentType?: string - prompt?: string - lastAssistantMessage?: string - }[] -> { +async function getAgentStatuses(page: Page): Promise { return page.evaluate(() => { const store = window.__store if (!store) { @@ -117,6 +117,27 @@ async function getAgentStatuses(page: Page): Promise< }) } +async function getCachedAgentStatuses(page: Page): Promise { + return page.evaluate(async () => { + const snapshot = await window.api.agentStatus.getSnapshot() + return snapshot.map((entry) => ({ + paneKey: entry.paneKey, + state: entry.state, + agentType: entry.agentType, + prompt: entry.prompt, + lastAssistantMessage: entry.lastAssistantMessage + })) + }) +} + +async function getRendererOrCachedAgentStatuses(page: Page): Promise { + const [rendererStatuses, cachedStatuses] = await Promise.all([ + getAgentStatuses(page), + getCachedAgentStatuses(page) + ]) + return [...rendererStatuses, ...cachedStatuses] +} + test.describe('Droid notifications', () => { test('Codex hook completion dispatches while its worktree is inactive', async ({ orcaPage, @@ -147,13 +168,15 @@ test.describe('Droid notifications', () => { await expect .poll( async () => - (await getAgentStatuses(orcaPage)).some( + (await getRendererOrCachedAgentStatuses(orcaPage)).some( (status) => status.agentType === 'codex' && status.state === 'working' && status.prompt === prompt ), { - timeout: 10_000, - message: 'Codex UserPromptSubmit hook did not reach renderer agent status' + timeout: 30_000, + // Why: this synthetic hook posts directly to main; main's cache is + // the durable source used to recover renderer startup/listener races. + message: 'Codex UserPromptSubmit hook did not reach agent status cache' } ) .toBe(true) @@ -179,7 +202,7 @@ test.describe('Droid notifications', () => { status.lastAssistantMessage === finalMessage ), { - timeout: 10_000, + timeout: 30_000, message: 'Codex Stop hook did not reach renderer agent status' } ) @@ -192,7 +215,7 @@ test.describe('Droid notifications', () => { return dispatches.filter((dispatch) => dispatch.source === 'agent-task-complete') }, { - timeout: 10_000, + timeout: 30_000, message: 'Codex hook Stop did not dispatch task-complete while worktree was inactive' } )