test(web): await action acknowledgements before following controls

This commit is contained in:
Tianyi Cui committed 2026-09-08 15:00:53 +08:00
1 parent 6c0e258b27
commit be9d401c33
4 files changed
+38 -6

No files matched your search

+16 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}