|
|
@@ -16,6 +16,10 @@ import { join } from 'node:path'
|
|
|
import type { Browser, Page } from 'playwright'
|
|
|
import { chromium } from 'playwright'
|
|
|
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
|
|
|
+import {
|
|
|
+ SESSION_FORMAT_VERSION, SessionId as sessionId, type SessionEvent, type SessionId,
|
|
|
+} from '@deepseek-ai/dsh-session'
|
|
|
+import { snapshotSubagentDescriptor } from '@deepseek-ai/dsh-subagent'
|
|
|
import {
|
|
|
captureStableAria, compareOrRefreshGolden, launchWebScaffold, seedSession, watchConsole,
|
|
|
webSnapshotMode, type WebScaffold,
|
|
|
@@ -78,6 +82,60 @@ function seedLog(): string {
|
|
|
].join('\n')
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * Persist one child so the assembled header snapshot exercises both action
|
|
|
+ * contributors whose relative order is the product contract under test.
|
|
|
+ * @param scaffold - the booted Web scaffold.
|
|
|
+ * @param parentId - the seeded session whose header the browser opens.
|
|
|
+ */
|
|
|
+async function seedSubagent(scaffold: WebScaffold, parentId: SessionId): Promise<void> {
|
|
|
+ const childId = sessionId('agent-preset-selection-child')
|
|
|
+ const createdAt = 1784974100100
|
|
|
+ await scaffold.ctx.sessionPersistence.create({
|
|
|
+ version: SESSION_FORMAT_VERSION,
|
|
|
+ id: childId,
|
|
|
+ createdAt,
|
|
|
+ cwd: scaffold.workspaceCwd,
|
|
|
+ parentSession: parentId,
|
|
|
+ origin: 'subagent',
|
|
|
+ delegationDepth: 1,
|
|
|
+ agentPreset: 'minimal',
|
|
|
+ })
|
|
|
+ await scaffold.ctx.sessionPersistence.append(childId, [
|
|
|
+ {
|
|
|
+ type: 'turn/start',
|
|
|
+ seq: 0,
|
|
|
+ time: createdAt,
|
|
|
+ data: { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'user/message',
|
|
|
+ seq: 1,
|
|
|
+ time: createdAt + 1,
|
|
|
+ data: {
|
|
|
+ content: [{ type: 'text', text: 'Check the session-header action order.' }],
|
|
|
+ source: { kind: 'user' },
|
|
|
+ },
|
|
|
+ surfaceOp: 'append',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'subagent/descriptor',
|
|
|
+ seq: 2,
|
|
|
+ time: createdAt + 2,
|
|
|
+ data: snapshotSubagentDescriptor({
|
|
|
+ mode: 'one-shot', provider: 'spawn', label: 'header order probe',
|
|
|
+ }),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'turn/end',
|
|
|
+ seq: 3,
|
|
|
+ time: createdAt + 3,
|
|
|
+ data: { turn: 1, reason: { kind: 'completed' } },
|
|
|
+ },
|
|
|
+ ] as SessionEvent[])
|
|
|
+ await scaffold.ctx.sessionProjectionCache.coldSnapshot(childId)
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* The preset the host reports for the blank session the workspace connect
|
|
|
* produced. Addressed by id rather than by scanning the serialized list: the
|
|
|
@@ -120,7 +178,8 @@ describe('web e2e: agent-preset selection', () => {
|
|
|
// A resumed session runs what it was created with; seeding one that
|
|
|
// records `minimal` is what makes the header label a claim about the
|
|
|
// session rather than an echo of the current default.
|
|
|
- await seedSession(scaffold, seedLog(), SEED_ID, 'minimal')
|
|
|
+ const seededId = await seedSession(scaffold, seedLog(), SEED_ID, 'minimal')
|
|
|
+ await seedSubagent(scaffold, seededId)
|
|
|
await seedWorkspaceSkill(scaffold.workspaceCwd)
|
|
|
browser = await chromium.launch()
|
|
|
page = await newEnglishPage(browser)
|
|
|
@@ -143,12 +202,12 @@ describe('web e2e: agent-preset selection', () => {
|
|
|
await compareOrRefreshGolden(HERO_EXPECTED, snapshot, MODE)
|
|
|
// The chip opens on the deployment default, by the name that preset
|
|
|
// publishes rather than its directory name.
|
|
|
- expect(snapshot).toContain('标准模式')
|
|
|
+ expect(snapshot).toContain('Standard mode')
|
|
|
})
|
|
|
|
|
|
it('names every preset and what it is for', async () => {
|
|
|
onTestFailed(() => saveFailureShot(page, 'web-e2e-agent-preset-menu'))
|
|
|
- await page.getByRole('button', { name: '标准模式' }).click()
|
|
|
+ await page.getByRole('button', { name: 'Standard mode' }).click()
|
|
|
const menu = page.getByRole('menu')
|
|
|
await menu.waitFor({ timeout: 10_000 })
|
|
|
|
|
|
@@ -157,15 +216,15 @@ describe('web e2e: agent-preset selection', () => {
|
|
|
await compareOrRefreshGolden(MENU_EXPECTED, snapshot, MODE)
|
|
|
// Every shipped preset, each with the sentence saying what it composes —
|
|
|
// the id alone never said what a preset does.
|
|
|
- expect(snapshot).toContain('极简模式')
|
|
|
- expect(snapshot).toContain('创造模式')
|
|
|
+ expect(snapshot).toContain('Minimal mode')
|
|
|
+ expect(snapshot).toContain('Creator mode')
|
|
|
await page.keyboard.press('Escape')
|
|
|
})
|
|
|
|
|
|
it('applies the staged pick to the blank session, and the host honors it', async () => {
|
|
|
onTestFailed(() => saveFailureShot(page, 'web-e2e-agent-preset-stage'))
|
|
|
- await page.getByRole('button', { name: '标准模式' }).click()
|
|
|
- await page.getByRole('menuitem', { name: /极简模式/ }).click()
|
|
|
+ await page.getByRole('button', { name: 'Standard mode' }).click()
|
|
|
+ await page.getByRole('menuitem', { name: /Minimal mode/ }).click()
|
|
|
|
|
|
// The chip stages; the blank session the workspace connect produced is
|
|
|
// what the stage lands on. The host's own answer is what comes back.
|
|
|
@@ -197,8 +256,8 @@ describe('web e2e: agent-preset selection', () => {
|
|
|
// against its list row, so a row that never reprojected the first switch
|
|
|
// answers "already standard" and sends nothing — and restores the catalog
|
|
|
// instead of leaving the session reading the narrower composition.
|
|
|
- await page.getByRole('button', { name: '极简模式' }).click()
|
|
|
- await page.getByRole('menuitem', { name: /^标准模式/ }).first().click()
|
|
|
+ await page.getByRole('button', { name: 'Minimal mode' }).click()
|
|
|
+ await page.getByRole('menuitem', { name: /^Standard mode/ }).first().click()
|
|
|
await expect.poll(() => livePreset(scaffold.baseUrl), { timeout: 15_000 }).toBe('standard')
|
|
|
|
|
|
await composer.fill('/')
|
|
|
@@ -221,10 +280,12 @@ describe('web e2e: agent-preset selection', () => {
|
|
|
const snapshot = await captureStableAria(page, '[class*="titleRow"]', scaffold.workspaceCwd)
|
|
|
|
|
|
await compareOrRefreshGolden(HEADER_EXPECTED, snapshot, MODE)
|
|
|
- expect(snapshot).toContain('极简模式')
|
|
|
+ expect(snapshot).toContain('Minimal mode')
|
|
|
+ expect(snapshot).toContain('button "1 subagent"')
|
|
|
+ expect(snapshot.indexOf('Minimal mode')).toBeLessThan(snapshot.indexOf('button "1 subagent"'))
|
|
|
// Static chrome, not a control: the header can only report a composition
|
|
|
// the host would refuse to change.
|
|
|
- expect(snapshot).not.toContain('button "极简模式"')
|
|
|
+ expect(snapshot).not.toContain('button "Minimal mode"')
|
|
|
})
|
|
|
|
|
|
it('drove every surface without a page error or a stream warning', () => {
|