fix(web): align composer context stack
This commit is contained in:
@@ -22,6 +22,7 @@ const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/queue-actions', import.m
|
||||
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 LAYOUT_EXPECTED = join(SNAPSHOT_DIR, 'layout.expected.md')
|
||||
const PRESERVED_EXPECTED = join(SNAPSHOT_DIR, 'preserved.expected.md')
|
||||
const UI_EXPECTED = join(SNAPSHOT_DIR, 'ui.expected.md')
|
||||
const MODE = webSnapshotMode()
|
||||
@@ -172,10 +173,89 @@ describe('web e2e: queue row actions', () => {
|
||||
await expect.poll(() => page.locator('[data-queue-dock]').count()).toBe(0)
|
||||
}, 120_000)
|
||||
|
||||
it.skipIf(MODE === 'record')('orders Todo before Goal and Queue on one desktop card column', async () => {
|
||||
overrideDir = await mkdtemp(join(tmpdir(), 'dsh-web-context-layout-'))
|
||||
const readyFile = join(overrideDir, '.hang-ready')
|
||||
const overridePath = join(overrideDir, 'replay.override.json')
|
||||
await writeFile(overridePath, JSON.stringify([{ kind: 'hang', readyFile } satisfies ReplayEntry]))
|
||||
|
||||
const sessionEvents: SessionEvent[] = []
|
||||
scaffold = await launchWebScaffold({ replayFixture: FIXTURE, replayOverride: overridePath })
|
||||
scaffold.ctx.on('session/event', (_session, event: SessionEvent) => { sessionEvents.push(event) })
|
||||
browser = await chromium.launch()
|
||||
page = await newEnglishPage(browser)
|
||||
const tripwire = watchConsole(page)
|
||||
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
|
||||
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
|
||||
await connectFreshWorkspace(page, scaffold.workspaceCwd)
|
||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-context-layout'))
|
||||
|
||||
const input = page.locator('textarea').first()
|
||||
const settled = scaffold.whenTurnSettled()
|
||||
await input.fill('/goal Keep the composer context panels aligned')
|
||||
await input.press('Enter')
|
||||
await expect.poll(() => existsSync(readyFile), { timeout: 15_000 }).toBe(true)
|
||||
await page.locator('[data-goal-bar]').waitFor({ timeout: 10_000 })
|
||||
|
||||
const sessions = scaffold.ctx.sessions.list()
|
||||
expect(sessions).toHaveLength(1)
|
||||
sessions[0]!.append('todo/write', {
|
||||
todos: [
|
||||
{ content: 'Confirm the panel order', status: 'completed' },
|
||||
{ content: 'Align the panel widths', status: 'in_progress' },
|
||||
],
|
||||
})
|
||||
await page.locator('[data-testid="todo-panel"]').waitFor({ timeout: 10_000 })
|
||||
|
||||
for (const text of ['Layout queue first', 'Layout queue second']) {
|
||||
await input.fill(text)
|
||||
await input.press('Enter')
|
||||
}
|
||||
const queueHeader = page.getByRole('button', { name: '2 queued messages' })
|
||||
await expect.poll(() => queueHeader.getAttribute('aria-expanded'), { timeout: 10_000 })
|
||||
.toBe('false')
|
||||
|
||||
const layoutSnapshot = await captureStableAria(
|
||||
page,
|
||||
'[class*="centerCol"]',
|
||||
scaffold.workspaceCwd,
|
||||
)
|
||||
await compareOrRefreshGolden(LAYOUT_EXPECTED, layoutSnapshot, MODE)
|
||||
|
||||
const queuePanelBox = await page.locator('[data-queue-dock] > div').boundingBox()
|
||||
const todoBox = await page.locator('[data-testid="todo-panel"]').boundingBox()
|
||||
const goalBox = await page.locator('[data-goal-bar] > div').boundingBox()
|
||||
expect(queuePanelBox).not.toBeNull()
|
||||
expect(todoBox).not.toBeNull()
|
||||
expect(goalBox).not.toBeNull()
|
||||
expect(todoBox!.y).toBeLessThan(goalBox!.y)
|
||||
expect(goalBox!.y).toBeLessThan(queuePanelBox!.y)
|
||||
expect(todoBox!.x).toBeCloseTo(goalBox!.x, 1)
|
||||
expect(todoBox!.x).toBeCloseTo(queuePanelBox!.x, 1)
|
||||
expect(todoBox!.width).toBeCloseTo(goalBox!.width, 1)
|
||||
expect(todoBox!.width).toBeCloseTo(queuePanelBox!.width, 1)
|
||||
|
||||
await queueHeader.click()
|
||||
const removeButtons = page.getByRole('button', { name: 'Remove queued message' })
|
||||
await expect.poll(() => removeButtons.count(), { timeout: 10_000 }).toBe(2)
|
||||
await removeButtons.first().click()
|
||||
await expect.poll(() => removeButtons.count(), { timeout: 10_000 }).toBe(1)
|
||||
await removeButtons.first().click()
|
||||
await expect.poll(() => page.locator('[data-queue-dock]').count(), { timeout: 10_000 }).toBe(0)
|
||||
await page.getByRole('button', { name: 'Clear goal' }).click()
|
||||
await expect.poll(() => page.locator('[data-goal-bar]').count(), { timeout: 10_000 }).toBe(0)
|
||||
await page.getByRole('button', { name: 'Stop generating' }).click()
|
||||
await settled
|
||||
|
||||
expect(turnEndReasons(sessionEvents)).toEqual(['aborted'])
|
||||
expect(tripwire.pageErrors).toEqual([])
|
||||
expect(tripwire.warnings).toEqual([])
|
||||
}, 120_000)
|
||||
|
||||
it.skipIf(MODE === 'record')('keeps its snapshot inventory closed', async () => {
|
||||
await assertFixtureInventory(
|
||||
SNAPSHOT_DIR,
|
||||
['collapsed.expected.md', 'editing.expected.md', 'preserved.expected.md', 'ui.expected.md'],
|
||||
['collapsed.expected.md', 'editing.expected.md', 'layout.expected.md', 'preserved.expected.md', 'ui.expected.md'],
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
42
apps/web/tests/snapshots/queue-actions/layout.expected.md
Normal file
42
apps/web/tests/snapshots/queue-actions/layout.expected.md
Normal file
@@ -0,0 +1,42 @@
|
||||
- banner:
|
||||
- heading "workspace" [level=1]
|
||||
- tablist:
|
||||
- tab "Chat" [selected]
|
||||
- tab "Trajectory"
|
||||
- 'button "goal Goal created Status: active Objective: Keep the composer context panels aligned Rounds: 0/256 Activation: armed Commands: /goal edit <objective>, /goal pause, /goal clear"':
|
||||
- img
|
||||
- img
|
||||
- text: "goal Goal created Status: active Objective: Keep the composer context panels aligned Rounds: 0/256 Activation: armed Commands: /goal edit <objective>, /goal pause, /goal clear"
|
||||
- button "Context injection":
|
||||
- img
|
||||
- img
|
||||
- text: Context injection
|
||||
- button "Context injection":
|
||||
- img
|
||||
- img
|
||||
- text: Context injection
|
||||
- button "Context injection":
|
||||
- img
|
||||
- img
|
||||
- text: Context injection
|
||||
- paragraph: partial
|
||||
- status: Deep diving...
|
||||
- region "To-dos":
|
||||
- button "To-dos 1/2 tasks · 1 in progress"
|
||||
- img
|
||||
- text: Ongoing Goal Keep the composer context panels aligned
|
||||
- button "Pause goal":
|
||||
- img
|
||||
- button "Edit goal":
|
||||
- img
|
||||
- button "Clear goal":
|
||||
- img
|
||||
- button "2 queued messages"
|
||||
- textbox "Message the agent"
|
||||
- button "Commands":
|
||||
- img
|
||||
- 'button "Access mode, current: Workspace Write"': Workspace Write
|
||||
- button "Select model, current DeepSeek-V4-Flash":
|
||||
- text: DeepSeek-V4-Flash
|
||||
- img
|
||||
- button "Stop generating"
|
||||
Reference in New Issue
Block a user