Merge branch 'master' into agent/web-bash-error-expand
This commit is contained in:
@@ -138,6 +138,23 @@ describe('web e2e: navigation & panes over a rich seeded session', () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-navigation-trajectory'))
|
||||
await page.getByRole('tab', { name: 'Trajectory' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
const overlayLayout = await page.getByRole('table').evaluate((table) => {
|
||||
const host = table.closest('[data-conversation-scroll]')
|
||||
const seat = host?.querySelector('[data-composer-seat]') ?? null
|
||||
const pane = table.parentElement
|
||||
return {
|
||||
hostPosition: host === null ? null : getComputedStyle(host).position,
|
||||
paneOverflowX: pane === null ? null : getComputedStyle(pane).overflowX,
|
||||
paneScrollableWidth: pane === null ? null : pane.scrollWidth - pane.clientWidth,
|
||||
seatPosition: seat === null ? null : getComputedStyle(seat).position,
|
||||
}
|
||||
})
|
||||
expect(overlayLayout).toEqual({
|
||||
hostPosition: 'relative',
|
||||
paneOverflowX: 'hidden',
|
||||
paneScrollableWidth: 0,
|
||||
seatPosition: 'absolute',
|
||||
})
|
||||
expect({
|
||||
pageErrors: tripwire.pageErrors,
|
||||
slotErrors,
|
||||
@@ -153,6 +170,8 @@ describe('web e2e: navigation & panes over a rich seeded session', () => {
|
||||
await page.locator('tr[data-kind="tool"]').first().click()
|
||||
const details = page.getByRole('complementary', { name: 'Event details' })
|
||||
await expect.poll(() => details.count(), { timeout: 10_000 }).toBe(1)
|
||||
expect(await details.getByRole('tabpanel').evaluate(panel => getComputedStyle(panel).overflowX))
|
||||
.toBe('hidden')
|
||||
await page.evaluate(() => { document.body.setAttribute('data-ds-dark-theme', '') })
|
||||
const darkSummarySurfaces = await details.getByRole('heading', { name: 'Payload' }).evaluate(heading => ({
|
||||
heading: getComputedStyle(heading).backgroundColor,
|
||||
@@ -162,6 +181,17 @@ describe('web e2e: navigation & panes over a rich seeded session', () => {
|
||||
await page.evaluate(() => { document.body.removeAttribute('data-ds-dark-theme') })
|
||||
await page.getByRole('tab', { name: 'Result' }).click()
|
||||
await expect.poll(() => page.getByText('NAVIGATION_OK', { exact: false }).count(), { timeout: 10_000 }).toBeGreaterThanOrEqual(1)
|
||||
const assistantSpan = page.locator('[data-timeline-span="message"][data-assistant-timing="true"]').first()
|
||||
await assistantSpan.hover()
|
||||
const timingTooltip = page.getByRole('tooltip')
|
||||
await timingTooltip.waitFor({ timeout: 5_000 })
|
||||
await expect.poll(() => timingTooltip.textContent(), { timeout: 5_000 }).toMatch(/TTFT .* Decoding/)
|
||||
const assistantTimingStyle = await assistantSpan.evaluate(node => ({
|
||||
background: getComputedStyle(node).backgroundImage,
|
||||
ttft: getComputedStyle(node).getPropertyValue('--trajectory-assistant-ttft'),
|
||||
}))
|
||||
expect(assistantTimingStyle.background).toContain('linear-gradient')
|
||||
expect(assistantTimingStyle.ttft).toMatch(/%$/)
|
||||
const snapshot = (await captureStableAria(page, '[class*="viewArea"]', scaffold.workspaceCwd))
|
||||
.split(SEED_ID).join('{{seededId}}')
|
||||
await compareOrRefreshGolden(TRAJECTORY_EXPECTED, snapshot, MODE)
|
||||
|
||||
@@ -516,6 +516,7 @@ function normalizeAria(snapshot: string, workspaceCwd: string): string {
|
||||
// shape so goldens stay stable across midnight and year boundaries.
|
||||
.replace(/\d{4}年\d{1,2}月\d{1,2}日 \d{2}:\d{2}/g, '{{clock}}')
|
||||
.replace(/\d{1,2}月\d{1,2}日 \d{2}:\d{2}/g, '{{clock}}')
|
||||
.replace(/(?<!\d)\d{1,2}:\d{2}:\d{2}(?:\.\d+)?(?:\s*[AP]M)?(?!\d)/gi, '{{clock}}')
|
||||
.replace(/(?<!\d)\d{2}:\d{2}(?!\d)/g, '{{clock}}')
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
- button "Collapse calls": Calls
|
||||
- img
|
||||
- searchbox "Search trajectory"
|
||||
- region "Trajectory timeline"
|
||||
- region "Trajectory timeline":
|
||||
- tooltip "ASSISTANT {{clock}} → {{clock}} Total 1.5 s · TTFT 368 ms · Decoding 1.2 s"
|
||||
- table:
|
||||
- rowgroup:
|
||||
- row "SYSTEM, Initial System Prompt":
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
- button "Home"
|
||||
- img
|
||||
- button "browse-golden"
|
||||
- button "Edit path"
|
||||
- button "Edit path":
|
||||
- img
|
||||
- list:
|
||||
- listitem:
|
||||
- button "adopted":
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Web e2e scenarios: workspace management — adding a workspace through the
|
||||
// composed directory dialog (its own New folder affordance is the product's
|
||||
// one creation route), same-basename directory adoption, the rename round
|
||||
// one creation route), the dialog's path editor walking the panes with the
|
||||
// typed draft, same-basename directory adoption, the rename round
|
||||
// trip over the real wire (workspace.rename RPC + durable registry), the
|
||||
// duplicate-name pre-check, the
|
||||
// flat "In one list" view with its persisted group-by preference, the session
|
||||
@@ -12,7 +13,7 @@
|
||||
// seeded-history seed reused verbatim — no new recording).
|
||||
import { mkdir, readFile, stat, writeFile } from 'node:fs/promises'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { join } from 'node:path'
|
||||
import { join, sep } from 'node:path'
|
||||
import type { Browser, Locator, Page } from 'playwright'
|
||||
import { chromium } from 'playwright'
|
||||
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
|
||||
@@ -403,6 +404,43 @@ describe('web e2e: workspace management (create / rename / flat view / hover aff
|
||||
expect(tripwire.pageErrors).toEqual([])
|
||||
}, 60_000)
|
||||
|
||||
it('walks the panes with the typed path: deeper past a separator, back up on erase, whole on a miss', async () => {
|
||||
// The panes must track the draft without leaving the editor, so the
|
||||
// typed text and what is listed under it never disagree.
|
||||
// Staged by this scenario itself (mkdir is recursive and idempotent), so
|
||||
// running it alone through -t sees the same tree the assertions describe.
|
||||
const staged = join(scaffold.workspaceCwd, 'browse-golden')
|
||||
await mkdir(join(staged, 'alpha', 'only-under-alpha'), { recursive: true })
|
||||
await mkdir(join(staged, 'beta'), { recursive: true })
|
||||
const dialog = await browseTo(staged)
|
||||
await expect.poll(() => dialog.getByText('alpha', { exact: true }).count(), { timeout: 10_000 }).toBe(1)
|
||||
await dialog.getByRole('button', { name: 'Edit path' }).click()
|
||||
const path = dialog.getByLabel('Edit path')
|
||||
// A directory part no pane lists: the panes walk to it, landing the
|
||||
// ordinary two-pane Miller view (level | its children) with the editor
|
||||
// still up and the draft intact.
|
||||
await path.fill(`${join(staged, 'alpha')}${sep}`)
|
||||
await expect.poll(() => dialog.getByText('only-under-alpha', { exact: true }).count(), { timeout: 10_000 }).toBe(1)
|
||||
await expect.poll(() => dialog.getByRole('list').count(), { timeout: 10_000 }).toBe(2)
|
||||
expect(await path.inputValue()).toBe(`${join(staged, 'alpha')}${sep}`)
|
||||
// Erasing back past the separator walks the panes up, so the level being
|
||||
// typed is the last pane again (its children no longer stand to its
|
||||
// right) and the tail filters it.
|
||||
await path.fill(`${staged}${sep}al`)
|
||||
await expect.poll(() => dialog.getByText('only-under-alpha', { exact: true }).count(), { timeout: 10_000 }).toBe(0)
|
||||
expect(await dialog.getByText('alpha', { exact: true }).count()).toBe(1)
|
||||
expect(await dialog.getByText('beta', { exact: true }).count()).toBe(0)
|
||||
await expect.poll(() => dialog.getByRole('list').count(), { timeout: 10_000 }).toBe(2)
|
||||
// A tail nobody matches is a name still being spelled: the level shows
|
||||
// whole instead of emptying under it.
|
||||
await path.fill(`${staged}${sep}zzz`)
|
||||
await expect.poll(() => dialog.getByText('beta', { exact: true }).count(), { timeout: 10_000 }).toBe(1)
|
||||
expect(await dialog.getByText('alpha', { exact: true }).count()).toBe(1)
|
||||
await dialog.getByRole('button', { name: 'Cancel' }).click()
|
||||
await dialog.waitFor({ state: 'hidden', timeout: 10_000 })
|
||||
expect(tripwire.pageErrors).toEqual([])
|
||||
}, 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
|
||||
|
||||
Reference in New Issue
Block a user