Răsfoiți Sursa

test(web): await action acknowledgements before following controls

Tianyi Cui 2 săptămâni în urmă
părinte
comite
be9d401c33

+ 16 - 3
apps/web/tests/github-ready-review.e2e.ts

@@ -139,9 +139,22 @@ describe.skipIf(MODE === 'record')('web e2e: GitHub ready-for-review', () => {
         head: { ref: 'fix-session-replay', sha: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' },
       },
     }
-    expect((await send(webhookOrigin, 'ready', payload)).status).toBe(202)
-    await vi.waitFor(() => { expect(scaffold.ctx.agents.list()).toHaveLength(before + 1) })
-    await vi.waitFor(() => { expect(adapter.requests).toHaveLength(1) })
+    const completed = Promise.withResolvers<undefined>()
+    let reviewSession: string | undefined
+    const off = scaffold.ctx.on('session/event', (session, event) => {
+      if (event.type === 'user/message' && event.data.source.kind === 'webhook'
+        && event.data.source.provider === 'github' && event.data.source.source === 'primary-github'
+        && event.data.source.deliveryId === 'ready' && event.data.source.ruleId === 'review-pr-when-ready') reviewSession = session.id
+      if (event.type === 'turn/end' && session.id === reviewSession) completed.resolve(undefined)
+    })
+    try {
+      expect((await send(webhookOrigin, 'ready', payload)).status).toBe(202)
+      await completed.promise
+    } finally {
+      off()
+    }
+    expect(scaffold.ctx.agents.list()).toHaveLength(before + 1)
+    expect(adapter.requests).toHaveLength(1)
 
     const agent = scaffold.ctx.agents.list().find(candidate => candidate.session.header.cwd === scaffold.workspaceCwd)
     expect(agent).toBeDefined()

+ 4 - 0
apps/web/tests/message-actions.e2e.ts

@@ -233,6 +233,10 @@ describe('web e2e: message IconActions and clocks on settled history', () => {
       () => page.locator('[role="treeitem"][aria-selected="true"]').count(),
       { timeout: 10_000 },
     ).toBe(1)
+    await expect.poll(
+      () => page.locator('[role="treeitem"][aria-selected="true"]').textContent(),
+      { timeout: 10_000 },
+    ).toContain('Use the read tool twice (1)')
     // The row action owns a distinct ui-workspace injection from the message
     // action above, so exercise both through the loaded app before capture.
     const sourceRow = page.locator('[role="treeitem"][aria-selected="true"]')

+ 14 - 2
apps/web/tests/queue-actions.e2e.ts

@@ -65,6 +65,16 @@ describe('web e2e: queue row actions', () => {
     if (failures.length > 1) throw new AggregateError(failures, 'queue-actions teardown failed')
   })
 
+  /** Wait for the exact queue mutation response before observing its unlocked actions. */
+  async function settleQueueAction(action: () => Promise<void>, remainingText: string): Promise<void> {
+    const response = page.waitForResponse('**/api/session/updateQueue')
+    await action()
+    expect((await response).ok()).toBe(true)
+    const row = page.locator('[data-queue-dock] li', { hasText: remainingText })
+    await expect.poll(() => row.getByRole('button', { name: 'Edit queued message' }).isEnabled()).toBe(true)
+    await expect.poll(() => row.getByRole('button', { name: 'Remove queued message' }).isEnabled()).toBe(true)
+  }
+
   it.skipIf(MODE === 'record')('edits and removes exact occurrences and preserves Queue across stop', async () => {
     overrideDir = await mkdtemp(join(tmpdir(), 'dsh-web-queue-actions-'))
     const readyFile = join(overrideDir, '.hang-ready')
@@ -96,6 +106,7 @@ describe('web e2e: queue row actions', () => {
     await input.press('Enter')
     await expect.poll(() => existsSync(readyFile), { timeout: 15_000 }).toBe(true)
 
+    const admitted = page.waitForResponse('**/api/session/prompt')
     const received = Promise.withResolvers<undefined>()
     const release = Promise.withResolvers<undefined>()
     await page.route('**/api/session/prompt', async (route) => {
@@ -125,6 +136,7 @@ describe('web e2e: queue row actions', () => {
     } finally {
       release.resolve(undefined)
     }
+    expect((await admitted).ok()).toBe(true)
     await expect.poll(() => page.getByRole('button', { name: 'Remove queued message' }).isEnabled()).toBe(true)
     expect(await page.locator('[data-queue-dock] [data-submission-echo]').count()).toBe(0)
     expect(await page.locator('[data-queue-dock]').getByRole('status').count()).toBe(0)
@@ -174,11 +186,11 @@ describe('web e2e: queue row actions', () => {
     await editor.fill(EDITED)
     const editingSnapshot = await captureStableAria(page, '[class*="centerCol"]', scaffold.workspaceCwd)
     await compareOrRefreshGolden(EDITING_EXPECTED, editingSnapshot, MODE)
-    await page.getByRole('button', { name: 'Save queued message' }).click()
+    await settleQueueAction(() => page.getByRole('button', { name: 'Save queued message' }).click(), EDITED)
     await page.getByText(EDITED, { exact: true }).waitFor()
 
     const removeRow = page.locator('[data-queue-dock] li', { hasText: REMOVE })
-    await removeRow.getByRole('button', { name: 'Remove queued message' }).click()
+    await settleQueueAction(() => removeRow.getByRole('button', { name: 'Remove queued message' }).click(), EDITED)
     await expect.poll(() => page.getByText(REMOVE, { exact: true }).count()).toBe(0)
 
     const snapshot = await captureStableAria(page, '[class*="centerCol"]', scaffold.workspaceCwd)

+ 4 - 1
apps/web/tests/workspace-management.e2e.ts

@@ -57,7 +57,10 @@ describe('web e2e: workspace management (create / rename / flat view / hover aff
     await dialog.getByRole('button', { name: 'Edit path' }).click()
     const pathInput = dialog.locator('input[aria-label="Edit path"]')
     await pathInput.fill(path)
-    await pathInput.press('Enter')
+    // Enter's keydown can retire the editor before keyup; target the focused keyboard, not that retiring node.
+    await page.keyboard.press('Enter')
+    await pathInput.waitFor({ state: 'detached', timeout: 10_000 })
+    await dialog.getByRole('button', { name: 'Edit path', exact: true }).waitFor()
     return dialog
   }