// Web e2e: /goal opts its command input into the human transcript while the // command remains log-only. The shipped composition runs with no model adapter, // so an accidental turn fails loud in addition to the event-level assertions. 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 type { SessionEvent } from '@deepseek-ai/dsh-session/types' import type {} from '@deepseek-ai/dsh-commands/types' import { acknowledgeReloadConnectionLoss, assertFixtureInventory, captureStableAria, compareOrRefreshGolden, launchWebScaffold, watchConsole, webSnapshotMode, type WebScaffold, } from './scaffold.ts' import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts' const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/goal-command-presentation', import.meta.url)) const UI_EXPECTED = fileURLToPath(new URL( './snapshots/goal-command-presentation/ui.expected.md', import.meta.url, )) const MODE = webSnapshotMode() describe('web e2e: /goal human transcript presentation', () => { let scaffold: WebScaffold let browser: Browser let page: Page let tripwire: ReturnType const events: SessionEvent[] = [] beforeAll(async () => { scaffold = await launchWebScaffold() scaffold.ctx.on('session/event', (_session, event: SessionEvent) => { events.push(event) }) browser = await chromium.launch() page = await newEnglishPage(browser) tripwire = watchConsole(page) await page.goto(scaffold.baseUrl, { waitUntil: 'load' }) await page.waitForSelector('[class*="frame"]', { timeout: 30_000 }) await connectFreshWorkspace(page, scaffold.workspaceCwd) }, 120_000) afterAll(async () => { await browser?.close() await scaffold?.close() }) it('shows the bare input and result from a fresh session without a model turn', async () => { onTestFailed(() => saveFailureShot(page, 'web-e2e-goal-command-presentation')) await expect.poll(() => page.getByText('Into the Unknown', { exact: false }).count(), { timeout: 15_000, }).toBe(1) const input = page.locator('textarea').first() await input.fill('/goal') await input.press('Enter') await expect.poll(() => input.inputValue()).toBe('/goal ') await input.press('Enter') const commandInput = page.locator('[data-command-input]') await commandInput.waitFor({ timeout: 10_000 }) await expect.poll(() => commandInput.textContent()).toBe('/goal') expect(await commandInput.getAttribute('role')).toBe('group') expect(await commandInput.getAttribute('aria-label')).toBe('Command input') expect(await commandInput.getByRole('button').count()).toBe(0) const typography = await commandInput.evaluate((element) => { const bubble = element.firstElementChild?.firstElementChild if (!(bubble instanceof HTMLElement)) throw new Error('command input bubble is missing') const rootStyle = getComputedStyle(element) const bubbleStyle = getComputedStyle(bubble) return { fontFamily: bubbleStyle.fontFamily, parentFontFamily: rootStyle.fontFamily, fontSize: bubbleStyle.fontSize, lineHeight: bubbleStyle.lineHeight, } }) expect(typography).toMatchObject({ fontSize: '14px', lineHeight: '22px' }) expect(typography.fontFamily).not.toBe(typography.parentFontFamily) const resultRow = page.locator('[data-variant="others"]').filter({ hasText: 'No goal is currently set.' }) await expect.poll(() => resultRow.count(), { timeout: 10_000 }).toBe(1) expect(await resultRow.getByText('goal', { exact: true }).count()).toBe(1) await expect.poll(() => page.locator('[data-phase="active"]').count()).toBe(1) expect(await page.getByText('Into the Unknown', { exact: false }).count()).toBe(0) const run = events.find(event => event.type === 'command/run') expect(run).toMatchObject({ type: 'command/run', data: { name: 'goal', args: ' ', source: { kind: 'user' } }, }) expect(events.some(event => event.type === 'command/done')).toBe(true) expect(events.some(event => event.type === 'user/message')).toBe(false) expect(events.some(event => event.type === 'turn/start')).toBe(false) expect(events.some(event => event.type === 'step/start')).toBe(false) expect(events.some(event => event.type === 'request/header')).toBe(false) const snapshot = await captureStableAria(page, '[class*="centerCol"]', scaffold.workspaceCwd) await compareOrRefreshGolden(UI_EXPECTED, snapshot, MODE) }, 60_000) it('reloads the same bubble and result from the persisted command lifecycle', async () => { onTestFailed(() => saveFailureShot(page, 'web-e2e-goal-command-presentation-reload')) 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.locator('[data-command-input]').textContent(), { timeout: 15_000 }).toBe('/goal') const resultRow = page.locator('[data-variant="others"]').filter({ hasText: 'No goal is currently set.' }) await expect.poll(() => resultRow.count(), { timeout: 10_000 }).toBe(1) await expect.poll(() => page.locator('[data-phase="active"]').count()).toBe(1) const sessions = scaffold.ctx.sessions.list() expect(sessions).toHaveLength(1) const persisted = sessions[0]?.events ?? [] expect(persisted.filter(event => event.type === 'command/run' || event.type === 'command/done') .map(event => event.type)).toEqual(['command/run', 'command/done']) expect(persisted.some(event => event.type === 'user/message')).toBe(false) expect(persisted.some(event => event.type === 'turn/start')).toBe(false) expect(persisted.some(event => event.type === 'step/start')).toBe(false) expect(persisted.some(event => event.type === 'request/header')).toBe(false) expect(tripwire.pageErrors).toEqual([]) expect(tripwire.warnings).toEqual([]) await assertFixtureInventory(SNAPSHOT_DIR, ['ui.expected.md']) }, 90_000) })