test(desktop): cover compression and queued stop lifecycle

Add real desktop E2E coverage for session compression continuation and
queue parking after an explicit Stop. Extend the mock server with a
blocking scripted turn and submitted-prompt assertions.
This commit is contained in:
ethernet 2026-07-21 21:48:59 -04:00
parent e89d1f544c
commit ff0c5643b8
2 changed files with 150 additions and 0 deletions

View file

@ -80,11 +80,24 @@ let _sidebarScriptIndex = 0
/** Per-server counter for the cross-session sidebar script. */
let _sidebarCrossIndex = 0
/** Per-server counter for the queue-stop script. */
let _queueStopIndex = 0
/** User messages received by the mock, for E2E assertions on real submits. */
const _receivedUserTexts: string[] = []
/** Reset the script indices (called between tests via restartMockServer). */
function resetScriptIndex(): void {
_scriptIndex = 0
_sidebarScriptIndex = 0
_sidebarCrossIndex = 0
_queueStopIndex = 0
_receivedUserTexts.length = 0
}
/** Return the user prompts the real backend submitted to this mock server. */
export function receivedUserTexts(): readonly string[] {
return _receivedUserTexts
}
// ─── Sidebar-states script ─────────────────────────────────────────────
@ -158,6 +171,14 @@ const SIDEBAR_CROSS_SCRIPT: ScriptedTurn[] = [
},
]
const QUEUE_STOP_SCRIPT: ScriptedTurn[] = [
{
text: 'Starting a task that will keep this turn active.',
toolCalls: [{ name: 'clarify', args: { question: 'Keep working?', choices: ['Yes', 'No'] } }],
},
{ text: 'The paused task completed.' },
]
/**
* Start the mock server on an ephemeral port.
*
@ -226,9 +247,24 @@ export function startMockServer(): Promise<{ port: number; url: string; close: (
const messages: any[] = Array.isArray(parsed.messages) ? parsed.messages : []
const lastUserMsg = [...messages].reverse().find(m => m?.role === 'user')
const userText = typeof lastUserMsg?.content === 'string' ? lastUserMsg.content : ''
if (userText) {
_receivedUserTexts.push(userText)
}
const isInterimTrigger = userText.includes('E2E_INTERIM_TRIGGER')
const isSidebarTrigger = userText.includes('E2E_SIDEBAR_TRIGGER')
const isSidebarCrossTrigger = userText.includes('E2E_SIDEBAR_CROSS')
const isQueueStopTrigger = userText.includes('E2E_QUEUE_STOP_TRIGGER')
if (isQueueStopTrigger) {
const turn = QUEUE_STOP_SCRIPT[_queueStopIndex] ?? QUEUE_STOP_SCRIPT[QUEUE_STOP_SCRIPT.length - 1]
_queueStopIndex++
if (stream) {
streamScriptedTurn(res, model, turn)
} else {
nonStreamingScriptedTurn(res, model, turn)
}
return
}
if (isSidebarCrossTrigger) {
const turn = SIDEBAR_CROSS_SCRIPT[_sidebarCrossIndex] ?? SIDEBAR_CROSS_SCRIPT[SIDEBAR_CROSS_SCRIPT.length - 1]

View file

@ -0,0 +1,114 @@
/**
* E2E coverage for two session lifecycle boundaries that unit tests cannot
* prove: compression rotates a live backend session, and Stop must park rather
* than submit queued prompts.
*/
import { expect, test, type Page } from '@playwright/test'
import {
type MockBackendFixture,
setupMockBackend,
waitForAppReady,
} from './fixtures'
import { receivedUserTexts, restartMockServer } from './mock-server'
async function send(page: Page, text: string): Promise<void> {
const composer = page.locator('[contenteditable="true"]').first()
await composer.click()
await composer.type(text, { delay: 15 })
await page.keyboard.press('Enter')
}
async function waitForTranscript(page: Page, text: string, timeout = 90_000): Promise<void> {
await page.waitForFunction(
expected => document.querySelector('[data-slot="aui_thread-viewport"]')?.textContent?.includes(expected) ?? false,
text,
{ timeout },
)
}
test.describe('session compression', () => {
test.describe.configure({ mode: 'serial' })
let fixture: MockBackendFixture
test.beforeAll(async () => {
restartMockServer()
fixture = await setupMockBackend()
await waitForAppReady(fixture, 120_000)
})
test.afterAll(async () => {
await fixture?.cleanup()
})
test('compresses an existing session and accepts a follow-up turn on its continuation', async () => {
const { page } = fixture
const reply = 'Hello from the mock inference server! The full boot chain is working.'
// Three completed exchanges leave a compressible middle after the
// compressor's protected head/tail boundaries.
await send(page, 'E2E_COMPRESSION_FIRST')
await waitForTranscript(page, reply)
await send(page, 'E2E_COMPRESSION_SECOND')
await expect.poll(() => receivedUserTexts().filter(text => text === 'E2E_COMPRESSION_SECOND').length).toBe(1)
await send(page, 'E2E_COMPRESSION_THIRD')
await expect.poll(() => receivedUserTexts().filter(text => text === 'E2E_COMPRESSION_THIRD').length).toBe(1)
await send(page, '/compress preserve the three test turns')
await expect
.poll(
() => page.locator('[data-slot="aui_thread-viewport"]').textContent(),
{ timeout: 90_000 },
)
.toMatch(/Compressed|No changes from compression/)
// Compression rotates the agent's live session id. A post-compression
// ordinary turn proves the desktop's runtime binding followed that child.
await send(page, 'E2E_COMPRESSION_FOLLOW_UP')
await expect.poll(() => receivedUserTexts().filter(text => text === 'E2E_COMPRESSION_FOLLOW_UP').length).toBe(1)
await waitForTranscript(page, reply)
await page.screenshot({ path: 'test-results/session-compression-continuation.png' })
})
})
test.describe('queued turn stopped explicitly', () => {
test.describe.configure({ mode: 'serial' })
let fixture: MockBackendFixture
test.beforeAll(async () => {
restartMockServer()
fixture = await setupMockBackend()
await waitForAppReady(fixture, 120_000)
})
test.afterAll(async () => {
await fixture?.cleanup()
})
test('keeps a queued message paused when Stop interrupts the active turn', async () => {
const { page } = fixture
const queued = 'E2E_QUEUE_STOP_QUEUED_MESSAGE'
await send(page, 'E2E_QUEUE_STOP_TRIGGER')
await waitForTranscript(page, 'Starting a task that will keep this turn active.', 30_000)
// A second Enter while busy queues instead of submitting the message.
await send(page, queued)
await expect(page.getByText('1 Queued')).toBeVisible()
// With an empty composer, the same primary control is Stop. Explicit stop
// must park the queue rather than letting the busy→idle drain submit it.
await page.locator('form').getByLabel('Stop').click()
await expect(page.getByText('1 Queued — paused')).toBeVisible({ timeout: 30_000 })
// Give the interrupted turn ample time to settle. The real mock receives
// every prompt the backend submits, so this proves the queued turn stayed
// local rather than merely disappearing from the panel.
await page.waitForTimeout(3_000)
expect(receivedUserTexts()).not.toContain(queued)
await page.screenshot({ path: 'test-results/queued-turn-paused-by-stop.png' })
})
})