Merge branch 'master' into xtr/trajectory-timeline-click-focus

This commit is contained in:
imccyu
2026-07-30 23:44:32 +08:00
committed by GitHub
108 changed files with 2087 additions and 514 deletions

View File

@@ -8,6 +8,7 @@ import { fileURLToPath } from 'node:url'
import type { Browser, Page } from 'playwright'
import { chromium } from 'playwright'
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
import { SessionId } from '@deepseek-ai/dsh-session'
import {
assertFixtureInventory, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
launchWebScaffold, seedSession, watchConsole, webSnapshotMode, type WebScaffold,
@@ -19,6 +20,7 @@ const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/message-actions', import
// a new recording (workspace-management / sidebar-scrollbar pattern).
const SEED = fileURLToPath(new URL('./snapshots/seeded-history/seed.jsonl', import.meta.url))
const UI_EXPECTED = join(SNAPSHOT_DIR, 'ui.expected.md')
const FORK_EXPECTED = join(SNAPSHOT_DIR, 'fork.expected.md')
const MODE = webSnapshotMode()
const SEED_ID = 'message-actions-web-e2e'
@@ -85,9 +87,58 @@ describe('web e2e: message IconActions and clocks on settled history', () => {
await compareOrRefreshGolden(UI_EXPECTED, snapshot, MODE)
})
it.skipIf(MODE === 'record')('forks through the settled-message and session-row actions', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-message-fork'))
// Exercise the assistant action specifically; package coverage pins the
// user action separately at its own event seq.
await page.getByRole('button', { name: '在新对话中分支' }).last().click()
await expect.poll(
() => scaffold.ctx.agents.list().find(agent => agent.session.header.parentSession === SessionId(SEED_ID)),
{ timeout: 15_000 },
).toBeDefined()
await expect.poll(
() => page.locator('[role="treeitem"]').count(),
{ timeout: 10_000 },
).toBe(3)
await expect.poll(
() => page.locator('[role="treeitem"][aria-selected="true"]').count(),
{ timeout: 10_000 },
).toBe(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"]')
const rowBox = await sourceRow.boundingBox()
if (rowBox === null) throw new Error('fork source row has no layout box')
const actionButton = sourceRow.locator('button[aria-label^="Session actions for "]')
await sourceRow.hover({ position: { x: rowBox.width - 16, y: rowBox.height / 2 } })
await expect.poll(() => actionButton.isVisible(), { timeout: 2_000 }).toBe(true)
const buttonBox = await actionButton.boundingBox()
if (buttonBox === null) throw new Error('fork source row action has no layout box')
await page.mouse.click(buttonBox.x + buttonBox.width / 2, buttonBox.y + buttonBox.height / 2)
await page.getByRole('menuitem', { name: 'Fork session' }).click()
await expect.poll(
() => scaffold.ctx.agents.list().filter(agent => agent.session.header.parentSession !== undefined).length,
{ timeout: 15_000 },
).toBe(2)
await expect.poll(
() => page.locator('[role="treeitem"]').count(),
{ timeout: 10_000 },
).toBe(4)
await expect.poll(
() => page.locator('[role="treeitem"][aria-selected="true"]').count(),
{ timeout: 10_000 },
).toBe(1)
const tree = await captureStableAria(
page,
'[role="tree"][aria-label="Sessions"]',
scaffold.workspaceCwd,
)
await compareOrRefreshGolden(FORK_EXPECTED, tree, MODE)
})
it.skipIf(MODE === 'record')('issued zero model calls and kept a closed inventory', async () => {
expect(tripwire.pageErrors).toEqual([])
expect(tripwire.warnings).toEqual([])
await assertFixtureInventory(SNAPSHOT_DIR, ['ui.expected.md'])
await assertFixtureInventory(SNAPSHOT_DIR, ['fork.expected.md', 'ui.expected.md'])
})
})

View File

@@ -20,6 +20,7 @@ import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './suppor
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/queue-actions', import.meta.url))
const FIXTURE = fileURLToPath(new URL('./snapshots/live-interactions/session.jsonl', import.meta.url))
const COLLAPSED_EXPECTED = join(SNAPSHOT_DIR, 'collapsed.expected.md')
const EDITING_EXPECTED = join(SNAPSHOT_DIR, 'editing.expected.md')
const UI_EXPECTED = join(SNAPSHOT_DIR, 'ui.expected.md')
const MODE = webSnapshotMode()
@@ -80,6 +81,16 @@ describe('web e2e: queue row actions', () => {
await input.fill(text)
await input.press('Enter')
}
const queueHeader = page.getByRole('button', { name: '2 条排队消息' })
await expect.poll(() => queueHeader.getAttribute('aria-expanded'), { timeout: 10_000 })
.toBe('false')
const collapsedSnapshot = await captureStableAria(
page,
'[class*="centerCol"]',
scaffold.workspaceCwd,
)
await compareOrRefreshGolden(COLLAPSED_EXPECTED, collapsedSnapshot, MODE)
await queueHeader.click()
await expect.poll(
() => page.getByRole('button', { name: '删除排队消息' }).count(),
{ timeout: 10_000 },
@@ -112,6 +123,9 @@ describe('web e2e: queue row actions', () => {
}, 120_000)
it.skipIf(MODE === 'record')('keeps its snapshot inventory closed', async () => {
await assertFixtureInventory(SNAPSHOT_DIR, ['editing.expected.md', 'ui.expected.md'])
await assertFixtureInventory(
SNAPSHOT_DIR,
['collapsed.expected.md', 'editing.expected.md', 'ui.expected.md'],
)
})
})

View File

@@ -0,0 +1,7 @@
- tree "Sessions":
- treeitem "Ungrouped 3 sessions" [expanded]:
- img
- text: Ungrouped 3 sessions
- treeitem "Use the read tool twice (2) now" [selected]
- treeitem "Use the read tool twice (1) now"
- treeitem "Use the read tool twice 1min"

View File

@@ -0,0 +1,24 @@
- banner:
- navigation "Session hierarchy":
- button "Reply with a one-sentence description" [disabled]
- tablist:
- tab "Chat" [selected]
- tab "Trajectory"
- text: Reply with a one-sentence description of event sourcing, then stop. {{clock}}
- button "复制":
- img
- button "在新对话中分支":
- img
- button "编辑":
- img
- paragraph: partial
- button "2 条排队消息"
- textbox "Message the agent"
- button "Add attachment":
- img
- 'button "Access mode, current: Danger Full Access"': Danger Full Access
- button "Plan mode off, press to turn on": Plan off
- button "Select model, current DeepSeek-V4-Flash":
- text: DeepSeek-V4-Flash
- img
- button "Stop generating"

View File

@@ -12,6 +12,7 @@
- button "编辑":
- img
- paragraph: partial
- button "2 条排队消息" [disabled] [expanded]
- list:
- listitem:
- text: Queue item to remove