test(web): settle queue tooltip and select exact seeded session

This commit is contained in:
Tianyi Cui
2026-09-07 19:03:25 +08:00
parent 153d54a2f2
commit f3c98e4539
5 changed files with 23 additions and 28 deletions
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-09-06-pr-ci-runner-temporary-storage.md
2026-09-06-pr-ci-runner-temporary-storage.md: 0e6acd23ebf449b6b768d0c5f96ea4ba90dac2f0
2026-09-06-pr-ci-runner-temporary-storage.zh.md: 4b1b5a76d94fdd907c5e194a2353ed5829d429ab
2026-09-06-pr-ci-runner-temporary-storage.md: 53e5ac8498200b82bde1c31393df6a38327db8ef
2026-09-06-pr-ci-runner-temporary-storage.zh.md: 931ab5fd4da363ad350159cd3ebfe9ab1d093158
@@ -32,6 +32,8 @@ The reference-composer fixture maps the known home-abbreviated workspace display
The advanced Python snapshot pauses only its matching workflow childs first pre-step until the parents durable workflow membership event is observed. The fixture supports either event-arrival order and cancels pending waits on abort or disposal. This pins the scenarios cross-session ordering without sorting notifications or changing production scheduling.
The queue snapshot moves the pointer away from the Stop/Send control and waits for its Send tooltip to close before capture. Workspace-management tests select the sole non-blank Session by its actions affordance, not row position, and select that Session before asserting that archiving it removes the empty Ungrouped bucket. Hover behavior, queue contents, durable archive identity, and reload assertions remain unchanged.
## Alternatives considered
**Delete shared temporary files from a PR job.** Another runner may still own those files. Repository jobs must not reclaim a shared directory by pathname or age.
@@ -32,6 +32,8 @@ Reference-composer 夹具将已知的 home 缩写 workspace 显示映射到既
高级 Python 快照仅暂停其匹配的 workflow 子进程首次 pre-step,直到观察到父 Session 的持久化 workflow 成员事件。夹具支持事件先到或等待先建立两种顺序,并在取消或销毁时结束未完成等待。这固定了场景的跨 Session 顺序,而不排序通知或改变生产调度。
Queue 快照在捕获前将指针移离 Stop/Send 控件,并等待其 Send tooltip 关闭。Workspace-management 测试通过操作按钮定位唯一非空 Session,而不依赖行位置;在断言归档会移除空的 Ungrouped 分组前,先选中该 Session。Hover 行为、队列内容、持久化归档身份及重载断言保持不变。
## 考虑过的替代方案
**由 PR 作业删除共享临时文件。** 其他 runner 可能仍在使用这些文件。仓库作业不得按路径或文件年龄回收共享目录。
+3
View File
@@ -222,6 +222,9 @@ describe('web e2e: queue row actions', () => {
await expect.poll(() => page.getByRole('button', { name: 'Remove queued message' }).count())
.toBe(2)
// Stop becomes Send under the pointer; dismiss its hover tooltip before capture.
await page.mouse.move(0, 0)
await expect.poll(() => page.getByRole('tooltip').filter({ hasText: 'Send message' }).count()).toBe(0)
const preservedSnapshot = await captureStableAria(page, '[class*="centerCol"]', scaffold.workspaceCwd)
await compareOrRefreshGolden(PRESERVED_EXPECTED, preservedSnapshot, MODE)
const expanded = await captureExpandedTurnProcessAria(
+14 -26
View File
@@ -463,9 +463,8 @@ describe('web e2e: workspace management (create / rename / flat view / hover aff
}, 60_000)
/**
* Expand Ungrouped and return its seeded session row. The only visible child
* is the non-blank persisted Session; the blank Session created while
* adopting the Workspace stays hidden.
* Expand Ungrouped and return its only non-blank session row. A selected
* blank Session from a deleted Workspace may also be visible, without actions.
* @returns the session row locator, already present.
*/
async function seededSessionRow() {
@@ -480,9 +479,11 @@ describe('web e2e: workspace management (create / rename / flat view / hover aff
}
return await ungroupedRow.getAttribute('aria-expanded')
}, { timeout: 5_000 }).toBe('true')
const row = ungroupedSection.locator('[role="treeitem"]').nth(1)
await row.waitFor({ timeout: 10_000 })
return row
// CSS includes the actions button while it is hidden until row hover.
const rows = ungroupedSection.locator('[role="treeitem"]')
.filter({ has: page.locator('button[aria-label^="Session actions for "]') })
await expect.poll(() => rows.count(), { timeout: 10_000 }).toBe(1)
return rows.first()
}
it('shows the session hover card after a dwell on the row', async () => {
@@ -552,26 +553,13 @@ describe('web e2e: workspace management (create / rename / flat view / hover aff
it('archives the seeded session from its row menu, hiding it durably across reload', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-ws-archive'))
// The seeded session lives under Ungrouped (expanded by the hover-card
// test's gesture; converge again for order independence).
const ungroupedRow = page.getByText('Ungrouped', { exact: true }).locator('..').locator('..')
const ungroupedSection = ungroupedRow.locator('..')
await expect.poll(async () => {
if (await ungroupedRow.getAttribute('aria-expanded') !== 'true') {
await page.getByText('Ungrouped', { exact: true }).click()
await page.waitForTimeout(50)
}
return await ungroupedRow.getAttribute('aria-expanded')
}, { timeout: 5_000 }).toBe('true')
// Anchor on session rows (the rows carrying a session actions button),
// not a positional index, and assert the single-stray assumption loudly
// so a fixture gaining a second stray fails here instead of archiving
// the wrong row. CSS attribute match, not getByRole: the button is
// display:none until its row hovers, and role queries skip hidden nodes.
const sessionRows = ungroupedSection.locator('[role="treeitem"]')
.filter({ has: page.locator('button[aria-label^="Session actions for "]') })
await expect.poll(() => sessionRows.count(), { timeout: 10_000 }).toBe(1)
const sessionRow = sessionRows.first()
const sessionRow = await seededSessionRow()
// Selecting the seed hides any blank stray left by Workspace deletion,
// so archiving this last visible Ungrouped Session must remove the bucket.
await sessionRow.click()
await expect.poll(() => sessionRow.getAttribute('aria-selected'), { timeout: 10_000 }).toBe('true')
const ungroupedSection = page.getByText('Ungrouped', { exact: true }).locator('..').locator('..').locator('..')
await expect.poll(() => ungroupedSection.locator('[role="treeitem"]').count(), { timeout: 10_000 }).toBe(2)
const rowTitle = await sessionRow.locator('[class*="title"]').innerText()
// Row menu: hover reveals the actions button; Archive session commits
// without a confirmation dialog (non-destructive: log + accounting stay).