mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
test(web): adapt workspace browser e2e interactions to the compact sidebar
The sidebar redesign removed group session counts, collapsed search into a header action, and moved grouping into the View options menu. Update the browser scenarios that waited on the old surfaces: - Wait on the Ungrouped bucket row instead of the removed session-count text. - Expand the collapsed search control before filling the renamed input. - Open the View options menu instead of the removed Group by button. - Assert the persisted grouping under the new dsh.workspace.view.v4 key. - built-boot: assert the fixture workspace group row instead of its count.
This commit is contained in:
@@ -21,7 +21,12 @@ it('boots the built plugin graph and renders a fixture session end to end', asyn
|
||||
|
||||
// The sidebar renders from the boot graph: every inject layer activated.
|
||||
const tree = await screen.findByRole('tree', { name: 'Sessions' }, { timeout: 10_000 })
|
||||
await within(tree).findByText('4 sessions')
|
||||
// The compact layout dropped group session counts; the fixture workspace
|
||||
// group row renders immediately with its sessions beneath it.
|
||||
const fixtureGroup = (await within(tree).findAllByText('fixture'))
|
||||
.map(el => el.closest<HTMLElement>('[role="treeitem"]'))
|
||||
.find(el => el?.getAttribute('aria-expanded') !== null)
|
||||
if (fixtureGroup === undefined) throw new Error('fixture Workspace group missing')
|
||||
|
||||
// The resident fixture has both a question and an approval; composer routing
|
||||
// exposes the question first, and the assembled workspace plugin mirrors that
|
||||
|
||||
@@ -78,8 +78,13 @@ async function nextPaint(page: Page): Promise<void> {
|
||||
}
|
||||
|
||||
async function openSeed(page: Page): Promise<void> {
|
||||
await page.getByText(/^\d+ sessions?$/, { exact: true }).waitFor({ timeout: 30_000 })
|
||||
const search = page.getByRole('textbox', { name: 'Search name, keywords...', exact: true })
|
||||
// The compact layout dropped group session counts; the seeded baseline is
|
||||
// the Ungrouped bucket once cold summaries load.
|
||||
await page.getByText('Ungrouped', { exact: true }).waitFor({ timeout: 30_000 })
|
||||
// Search collapsed into a header action; expand it before filling.
|
||||
const searchButton = page.getByRole('button', { name: 'Search sessions' })
|
||||
if (await searchButton.getAttribute('aria-expanded') !== 'true') await searchButton.click()
|
||||
const search = page.getByRole('textbox', { name: 'Search sessions...', exact: true })
|
||||
await search.fill(FIXTURE.markers.user(1))
|
||||
const results = page.getByRole('tree', { name: 'Search results' }).getByRole('treeitem')
|
||||
await results.first().waitFor({ timeout: 60_000 })
|
||||
|
||||
@@ -168,8 +168,10 @@ async function launchScrollWorld(options: ScrollWorldOptions): Promise<ScrollWor
|
||||
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
|
||||
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
|
||||
// Session-list bootstrap can replace the controlled search state. Wait
|
||||
// for the seeded baseline before openSeed starts the lazy content query.
|
||||
await page.getByText(/^\d+ sessions?$/, { exact: true }).waitFor({ timeout: 30_000 })
|
||||
// for the seeded baseline before openSeed starts the lazy content query
|
||||
// (the compact layout dropped group session counts; the Ungrouped bucket
|
||||
// row is the barrier).
|
||||
await page.getByText('Ungrouped', { exact: true }).waitFor({ timeout: 30_000 })
|
||||
return {
|
||||
events,
|
||||
page,
|
||||
@@ -258,7 +260,10 @@ async function conversationTurns(page: Page): Promise<number> {
|
||||
}
|
||||
|
||||
async function openSeed(page: Page, fixture: ChatScrollFixture, tailMarker?: string): Promise<void> {
|
||||
const search = page.getByRole('textbox', { name: 'Search name, keywords...', exact: true })
|
||||
// Search collapsed into a header action; expand it before filling.
|
||||
const searchButton = page.getByRole('button', { name: 'Search sessions' })
|
||||
if (await searchButton.getAttribute('aria-expanded') !== 'true') await searchButton.click()
|
||||
const search = page.getByRole('textbox', { name: 'Search sessions...', exact: true })
|
||||
// Cold summaries initially show the temporary workspace basename, so the
|
||||
// persisted first-message marker is the stable user-facing identity. The
|
||||
// query itself triggers lazy content-index reconciliation; no transient
|
||||
|
||||
@@ -249,7 +249,10 @@ async function compareTabsWithoutReservation(page: Page): Promise<TabComparison>
|
||||
* @param page - the page under test.
|
||||
*/
|
||||
async function openSeededSession(page: Page): Promise<void> {
|
||||
const search = page.getByRole('textbox', { name: 'Search name, keywords...', exact: true })
|
||||
// Search collapsed into a header action; expand it before filling.
|
||||
const searchButton = page.getByRole('button', { name: 'Search sessions' })
|
||||
if (await searchButton.getAttribute('aria-expanded') !== 'true') await searchButton.click()
|
||||
const search = page.getByRole('textbox', { name: 'Search sessions...', exact: true })
|
||||
await search.fill(FIXTURE.markers.user(1))
|
||||
const results = page.getByRole('tree', { name: 'Search results' }).getByRole('treeitem')
|
||||
const deadline = Date.now() + 60_000
|
||||
|
||||
@@ -197,8 +197,13 @@ describe('web e2e: lifecycle & chrome (workspace flow / reload / dark mode)', ()
|
||||
it.skipIf(MODE === 'record')('materialized a real Workspace and Session over the wire', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-lifecycle-materialize'))
|
||||
// Browser: the sidebar tree now carries the auto-created workspace group
|
||||
// with its one session, and the opened session is the selected row.
|
||||
await expect.poll(() => page.getByText('1 session', { exact: true }).count(), { timeout: 15_000 }).toBeGreaterThanOrEqual(1)
|
||||
// with its one session, and the opened session is the selected row. The
|
||||
// compact layout dropped group session counts, so the group row itself is
|
||||
// the barrier.
|
||||
await expect.poll(
|
||||
() => page.locator('[role="treeitem"][aria-expanded]').filter({ hasText: 'workspace' }).count(),
|
||||
{ timeout: 15_000 },
|
||||
).toBeGreaterThanOrEqual(1)
|
||||
await expect.poll(() => page.locator('[role="treeitem"][aria-selected="true"]').count(), { timeout: 10_000 }).toBe(1)
|
||||
await expect.poll(() => page.getByText('LIGHTHOUSE', { exact: true }).count(), { timeout: 15_000 }).toBeGreaterThanOrEqual(1)
|
||||
// Host: the session's durable header cwd is the folder the workspace
|
||||
|
||||
@@ -53,7 +53,10 @@ async function assertBaselineSucceeded(response: Response, method: string): Prom
|
||||
|
||||
async function ensureSeedOpen(page: Page): Promise<void> {
|
||||
const chat = page.getByRole('tab', { name: 'Chat', exact: true })
|
||||
const search = page.getByPlaceholder('Search name, keywords', { exact: false })
|
||||
// Search is a collapsed header action; expand it so the input is actionable.
|
||||
const searchButton = page.getByRole('button', { name: 'Search sessions' })
|
||||
if (await searchButton.getAttribute('aria-expanded') !== 'true') await searchButton.click()
|
||||
const search = page.getByPlaceholder('Search sessions', { exact: false })
|
||||
if (await chat.count() === 0) {
|
||||
await search.fill('WATERFALL')
|
||||
const result = page.getByRole('tree', { name: 'Search results' }).getByRole('treeitem')
|
||||
@@ -119,8 +122,9 @@ describe('web e2e: navigation & panes over a rich seeded session', () => {
|
||||
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
|
||||
// The frame mounts before the asynchronous session-list baseline lands.
|
||||
// Search must target the settled seeded row, not the startup input that
|
||||
// the ready projection replaces.
|
||||
await page.getByText('1 session', { exact: true }).waitFor({ timeout: 30_000 })
|
||||
// the ready projection replaces (the compact layout dropped group session
|
||||
// counts; the Ungrouped bucket row is the barrier).
|
||||
await page.getByText('Ungrouped', { exact: true }).waitFor({ timeout: 30_000 })
|
||||
}, 120_000)
|
||||
|
||||
afterEach(async () => {
|
||||
@@ -176,9 +180,13 @@ describe('web e2e: navigation & panes over a rich seeded session', () => {
|
||||
it.skipIf(MODE === 'record')('finds an unopened seeded session by message content and opens it', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-navigation-search'))
|
||||
// The API baselines can settle before React commits their projection. The
|
||||
// seeded count is the final user-visible barrier before editing search.
|
||||
await page.getByText('1 session', { exact: true }).waitFor({ timeout: 30_000 })
|
||||
const search = page.getByPlaceholder('Search name, keywords', { exact: false })
|
||||
// seeded Ungrouped bucket row is the final user-visible barrier before
|
||||
// editing search (the compact layout dropped group session counts).
|
||||
await page.getByText('Ungrouped', { exact: true }).waitFor({ timeout: 30_000 })
|
||||
// Search is a collapsed header action; expand it so the input is actionable.
|
||||
const searchButton = page.getByRole('button', { name: 'Search sessions' })
|
||||
if (await searchButton.getAttribute('aria-expanded') !== 'true') await searchButton.click()
|
||||
const search = page.getByPlaceholder('Search sessions', { exact: false })
|
||||
// The cold row has not been opened, so only the persisted log can satisfy
|
||||
// this query. First search lazily reconciles the SQLite content index.
|
||||
await search.fill('zzzqx-no-such-session')
|
||||
|
||||
@@ -68,8 +68,11 @@ describe.skipIf(MODE === 'record' || !HAS_PWSH)('web e2e: pwsh calls use the bas
|
||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-pwsh-terminal'))
|
||||
// Open the seeded session through content search: the sidebar groups
|
||||
// sessions by workspace and its row order is world-dependent, while the
|
||||
// search index covers the seeded log deterministically.
|
||||
const search = page.getByPlaceholder('Search name, keywords', { exact: false })
|
||||
// search index covers the seeded log deterministically. Search is a
|
||||
// collapsed header action; expand it so the input is actionable.
|
||||
const searchButton = page.getByRole('button', { name: 'Search sessions' })
|
||||
if (await searchButton.getAttribute('aria-expanded') !== 'true') await searchButton.click()
|
||||
const search = page.getByPlaceholder('Search sessions', { exact: false })
|
||||
await search.fill('Run a PowerShell command')
|
||||
const result = page.getByRole('tree', { name: 'Search results' }).getByRole('treeitem')
|
||||
await expect.poll(() => result.count(), { timeout: 15_000 }).toBe(1)
|
||||
|
||||
@@ -59,7 +59,10 @@ interface RowAnchor {
|
||||
}
|
||||
|
||||
async function openSeed(page: Page): Promise<void> {
|
||||
const search = page.getByRole('textbox', { name: 'Search name, keywords...', exact: true })
|
||||
// Search collapsed into a header action; expand it before filling.
|
||||
const searchButton = page.getByRole('button', { name: 'Search sessions' })
|
||||
if (await searchButton.getAttribute('aria-expanded') !== 'true') await searchButton.click()
|
||||
const search = page.getByRole('textbox', { name: 'Search sessions...', exact: true })
|
||||
await search.fill(FIXTURE.markers.user(1))
|
||||
const result = page.getByRole('tree', { name: 'Search results' }).getByRole('treeitem')
|
||||
await expect.poll(() => result.count(), { timeout: 60_000 }).toBe(1)
|
||||
@@ -182,7 +185,9 @@ describe('web e2e: Trajectory virtualization over tail-paged history', () => {
|
||||
tripwire = watchConsole(page)
|
||||
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
|
||||
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
|
||||
await page.getByText('1 session', { exact: true }).waitFor({ timeout: 30_000 })
|
||||
// The compact layout dropped group session counts; the seeded baseline is
|
||||
// the Ungrouped bucket once cold summaries load.
|
||||
await page.getByText('Ungrouped', { exact: true }).waitFor({ timeout: 30_000 })
|
||||
}, 120_000)
|
||||
|
||||
afterAll(async () => {
|
||||
|
||||
@@ -372,21 +372,22 @@ describe('web e2e: workspace management (create / rename / flat view / hover aff
|
||||
// Grouped default: workspace group rows render (the seeded session sits
|
||||
// under Ungrouped; the created workspaces are empty groups).
|
||||
await expect.poll(() => page.getByText('Workspaces', { exact: true }).count(), { timeout: 10_000 }).toBe(1)
|
||||
await page.getByRole('button', { name: 'Group by' }).click()
|
||||
// Grouping and ordering moved into the View options menu.
|
||||
await page.getByRole('button', { name: 'View options' }).click()
|
||||
await page.getByRole('menuitem', { name: 'In one list' }).click()
|
||||
// Flat mode: the section label flips and the seeded session is a
|
||||
// top-level row with no group headers above it.
|
||||
await expect.poll(() => page.getByText('Sessions', { exact: true }).count(), { timeout: 10_000 }).toBeGreaterThanOrEqual(1)
|
||||
await expect.poll(() => page.getByText('Ungrouped', { exact: true }).count(), { timeout: 5_000 }).toBe(0)
|
||||
await expect.poll(() => page.locator('[role="treeitem"]').count(), { timeout: 10_000 }).toBeGreaterThanOrEqual(1)
|
||||
expect(await page.evaluate(() => localStorage.getItem('dsh.workspace.view'))).toContain('flat')
|
||||
expect(await page.evaluate(() => localStorage.getItem('dsh.workspace.view.v4'))).toContain('flat')
|
||||
// Persisted across reload; then restore grouped for inter-spec hygiene.
|
||||
const warningStart = tripwire.warnings.length
|
||||
await page.reload({ waitUntil: 'load' })
|
||||
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
|
||||
acknowledgeReloadConnectionLoss(tripwire, warningStart)
|
||||
await expect.poll(() => page.getByText('Ungrouped', { exact: true }).count(), { timeout: 15_000 }).toBe(0)
|
||||
await page.getByRole('button', { name: 'Group by' }).click()
|
||||
await page.getByRole('button', { name: 'View options' }).click()
|
||||
await page.getByRole('menuitem', { name: 'WorkSpace' }).click()
|
||||
await expect.poll(() => page.getByText('Ungrouped', { exact: true }).count(), { timeout: 10_000 }).toBeGreaterThanOrEqual(1)
|
||||
expect(tripwire.pageErrors).toEqual([])
|
||||
|
||||
Reference in New Issue
Block a user