Files
deepseek-harness/apps/web/tests/cua-optin.e2e.ts
T

266 lines
18 KiB
TypeScript

/** CUA follows PTC Agent scopes and changes the default only from its detail page. */
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { chromium, type Browser } from 'playwright'
import { readProfileManifest } from '@deepseek-ai/dsh-app-boot'
import { SessionId } from '@deepseek-ai/dsh-session'
import type {} from '@deepseek-ai/dsh-ptc-runtime'
import type {} from '@deepseek-ai/dsh-agent-preset-registry'
import { load } from 'js-yaml'
import { expect, it, vi } from 'vitest'
import { captureStableAria, compareOrRefreshGolden, launchWebScaffold, webSnapshotMode, type WebScaffold } from './scaffold.ts'
import { connectFreshWorkspaceZh, ZH_BROWSER_LOCALE } from './support.ts'
// The shipped bundle admits only macOS Hosts; unsupported Hosts are exercised in bundle-platform.e2e.ts.
it.skipIf(process.platform !== 'darwin').each([
{ selectedDefault: undefined, codingTools: false, script: false },
{ selectedDefault: undefined, codingTools: true, script: false },
{ selectedDefault: 'minimal', codingTools: true, script: false },
{ selectedDefault: undefined, codingTools: true, script: true },
] as const)('preserves Coding Tools=$codingTools and default=$selectedDefault and Script=$script across CUA toggles', async ({ selectedDefault, codingTools, script }) => {
const directory = await mkdtemp(join(tmpdir(), 'cua-optin-web-'))
let ownedScaffold: WebScaffold | undefined
let browser: Browser | undefined
try {
const patch = join(directory, 'native-fixture.yml')
// Permission inspection fails against this non-helper executable; activation still permits dismissing setup.
await writeFile(patch, JSON.stringify([{ id: 'cua', config: { helperPath: process.execPath } }]))
const defaults = join(directory, 'script-defaults')
if (script) {
await mkdir(defaults)
await writeFile(join(defaults, 'package.json'), JSON.stringify({ name: 'cua-script-defaults', version: '1.0.0', dsh: { bundle: { patch: 'cordis.patch.yml' } } }))
await writeFile(join(defaults, 'cordis.patch.yml'), '- id: ptc-runtime\n config:\n executionMode: vm-script\n scriptPersistence: false\n')
}
const harnessHome = join(directory, 'home')
const scaffold = ownedScaffold = await launchWebScaffold({
profile: { packages: script ? [{ dir: defaults, enabled: true }] : [] },
harnessHome, extraOverlayPath: [
fileURLToPath(new URL('./pin-browse-picker.overlay.yml', import.meta.url)), patch,
] })
const initialDefault = selectedDefault ?? 'standard'
if (selectedDefault !== undefined) await scaffold.ctx.settings.update('agent-preset-registry', { selectedDefault })
await scaffold.ctx.settings.update('ui-settings', { enabled: codingTools })
const settingsPath = join(harnessHome, 'profiles/scaffold/cordis.patch.yml')
const beforeEnable = await readFile(settingsPath, 'utf8')
browser = await chromium.launch()
const page = await browser.newPage({ viewport: { width: 1680, height: 1000 }, locale: ZH_BROWSER_LOCALE })
await page.goto(scaffold.authenticatedUrl, { waitUntil: 'load' })
await connectFreshWorkspaceZh(page, scaffold.workspaceCwd)
await page.getByRole('navigation', { name: '全局面板' }).getByRole('button', { name: '插件', exact: true }).click()
const plugins = page.locator('[data-plugin-panel]')
await plugins.getByRole('button', { name: '更多', exact: true }).waitFor()
expect(await page.locator('[data-plugin-package="@deepseek-ai/dsh-cua-bundle"]').count()).toBe(0)
await plugins.getByRole('button', { name: '更多', exact: true }).click()
const card = page.locator('[data-plugin-package="@deepseek-ai/dsh-cua-bundle"]')
expect(await card.count()).toBe(0)
expect((await scaffold.ctx.pluginManager.listBundles()).find(bundle => bundle.name === '@deepseek-ai/dsh-cua-bundle'))
.toMatchObject({ listedByDefault: false, enabled: false, installed: false })
await plugins.getByRole('button', { name: '返回插件', exact: true }).click()
const offerCua = async () => {
await plugins.getByRole('button', { name: '添加插件', exact: true }).click()
const dialog = page.getByRole('dialog', { name: '添加插件', exact: true })
await dialog.getByRole('textbox', { name: '包名或地址', exact: true }).fill('@deepseek-ai/dsh-cua-bundle')
await dialog.getByRole('button', { name: '安装', exact: true }).click()
const ready = page.getByRole('dialog', { name: '已安装', exact: true })
await ready.waitFor()
expect(await ready.getByText('无需下载,启用即可使用', { exact: true }).count()).toBe(0)
return ready.getByRole('button', { name: '立即启用', exact: true })
}
const enableNow = await offerCua()
let toggle = card.getByRole('switch', { name: '启用 Computer Use', exact: true })
const originalAgent = scaffold.ctx.agents.list()[0]!
const ptcAgent = (await scaffold.ctx.agents.create({
sessionId: SessionId('cua-existing-ptc'),
setup: async (ctx) => { await scaffold.ctx.agentPresets.mount(ctx, 'ptc') },
})).agent
expect(scaffold.ctx.tools.schemas(ptcAgent).some(tool => tool.name === 'cua')).toBe(false)
const runtimeFiber = () => [...scaffold.ctx.loader.entries()].find(entry => entry.options.id === 'ptc-runtime')?.fiber
const originalRuntimeFiber = runtimeFiber()
expect(originalRuntimeFiber).toBeDefined()
const originalRuntime = scaffold.ctx.ptcRuntime
expect(scaffold.ctx.agentPresets.defaultId).toBe(initialDefault)
expect(originalRuntime.programFormat).toBe(script ? 'script' : 'function-body')
expect(scaffold.ctx.get('computerUse')).toBeUndefined()
const profileManifest = () => readProfileManifest('scaffold', join(scaffold.harnessHome, 'profiles/scaffold'))
const selected = () => profileManifest().dsh?.profile?.bundles
const dependencies = profileManifest().dependencies
expect(selected()).not.toContain('@deepseek-ai/dsh-cua-bundle')
const enabling = Promise.withResolvers<undefined>()
const setEnabled = scaffold.ctx.pluginManager.setBundleEnabled.bind(scaffold.ctx.pluginManager)
let applying: ReturnType<typeof setEnabled> | undefined
// Hold the Host request so the pending UI does not depend on machine speed; release into the real operation.
const enableCall = vi.spyOn(scaffold.ctx.pluginManager, 'setBundleEnabled').mockImplementationOnce((name, enabled) => {
applying = enabling.promise.then(() => setEnabled(name, enabled))
return applying
})
try {
await enableNow.click()
await expect.poll(() => enableNow.isEnabled()).toBe(false)
expect(await enableNow.getAttribute('aria-busy')).toBe('true')
expect(await card.count()).toBe(0)
await compareOrRefreshGolden(fileURLToPath(new URL('./expected/cua-permissions/enabling.expected.md', import.meta.url)),
await captureStableAria(page, '[data-install-phase="done"] button[aria-busy]', scaffold.workspaceCwd), webSnapshotMode())
} finally {
enabling.resolve(undefined)
enableCall.mockRestore()
await applying
}
// Bundle transitions await native cleanup under the browser action deadline.
await toggle.and(page.locator('[aria-checked="true"]')).waitFor({ state: 'visible' })
await card.getByRole('status').getByText('正在应用 Computer Use 的更改', { exact: true }).waitFor({ state: 'hidden' })
const setup = page.getByRole('dialog', { name: '使用 Computer Use 前需要设置', exact: true })
await setup.waitFor()
expect(await setup.getByText('Computer Use 插件已开启,但暂时无法检测系统权限。请前往插件详情页查看原因并重试。', { exact: true }).count()).toBe(1)
await compareOrRefreshGolden(fileURLToPath(new URL('./expected/cua-permissions/setup.expected.md', import.meta.url)),
await captureStableAria(page, '[role="dialog"]', scaffold.workspaceCwd), webSnapshotMode())
expect(await setup.getByRole('button', { name: '设为 PTC', exact: true }).count()).toBe(0)
expect(scaffold.ctx.agentPresets.defaultId).toBe(initialDefault)
expect(selected()).toContain('@deepseek-ai/dsh-cua-bundle')
expect(profileManifest().dependencies).toEqual(dependencies)
expect(await card.getByText('实验性', { exact: true }).count()).toBe(1)
expect(await card.textContent()).not.toContain('将 PTC 模式改为可持久化')
expect(await card.textContent()).toContain('请切换到 PTC 模式')
expect(await card.textContent()).toContain('Windows 支持正在开发中')
expect(await card.textContent()).not.toContain('Swift')
const description = await card.getByRole('button', { name: '查看 Computer Use', exact: true }).evaluate((button) => {
const element = document.getElementById(button.getAttribute('aria-describedby')!)!
const style = getComputedStyle(element)
return { lines: style.webkitLineClamp, height: element.getBoundingClientRect().height,
lineHeight: Number.parseFloat(style.lineHeight) }
})
expect(description.lines).toBe('1')
expect(description.height).toBeLessThanOrEqual(description.lineHeight + 1)
expect(scaffold.ctx.get('computerUse')).toBeDefined()
expect(scaffold.ctx.agents.get(originalAgent.id) === originalAgent).toBe(true)
expect(scaffold.ctx.agents.get(ptcAgent.id) === ptcAgent).toBe(true)
expect(scaffold.ctx.agentPresets.composedPreset(originalAgent.ctx)).toBe(initialDefault)
expect(scaffold.ctx.tools.schemas(originalAgent).some(tool => tool.name === 'cua')).toBe(false)
expect(scaffold.ctx.tools.schemas(ptcAgent).some(tool => tool.name === 'cua')).toBe(true)
expect((await scaffold.ctx.systemPrompt.assemble({ scope: ptcAgent })).tools.map(tool => tool.name)).toEqual(['run_code'])
for (const preset of ['ptc', 'standard', 'ptc', initialDefault]) {
await scaffold.ctx.agentPresets.select(originalAgent, preset)
expect(scaffold.ctx.tools.schemas(originalAgent).some(tool => tool.name === 'cua')).toBe(preset === 'ptc')
}
const child = await scaffold.ctx.agents.create({
sessionId: SessionId('cua-child'), parentAgent: ptcAgent,
setup: (ctx) => { scaffold.ctx.agentPresets.composeFrom(ctx, ptcAgent.ctx) },
})
expect(scaffold.ctx.tools.schemas(child.agent).some(tool => tool.name === 'cua')).toBe(true)
await child.dispose()
expect(scaffold.ctx.tools.schemas(child.agent).some(tool => tool.name === 'cua')).toBe(false)
expect(scaffold.ctx.agentPresets.defaultId).toBe(initialDefault)
expect(runtimeFiber() === originalRuntimeFiber).toBe(true)
expect(scaffold.ctx.ptcRuntime.programFormat).toBe(script ? 'script' : 'function-body')
await setup.waitFor()
expect(await setup.getByText('Computer Use 插件已开启,但暂时无法检测系统权限。请前往插件详情页查看原因并重试。', { exact: true }).count()).toBe(1)
await setup.getByRole('button', { name: '稍后', exact: true }).click()
await setup.waitFor({ state: 'hidden' })
let saved = await readFile(settingsPath, 'utf8')
expect(saved).toBe(beforeEnable)
expect(scaffold.ctx.settings.describe().find(row => row.ns === 'ui-settings')?.value).toMatchObject({ enabled: codingTools })
expect(saved).not.toContain('executionMode:')
if (selectedDefault === undefined) {
expect(load(saved)).not.toEqual(expect.arrayContaining([expect.objectContaining({ id: 'agent-preset-registry' })]))
} else {
expect(load(saved)).toEqual(expect.arrayContaining([
expect.objectContaining({ id: 'agent-preset-registry', config: { default: 'standard', selectedDefault } }),
]))
}
const freshDefault = (await scaffold.ctx.agents.create({
sessionId: SessionId('cua-new-default'),
setup: async (ctx) => { await scaffold.ctx.agentPresets.mount(ctx) },
})).agent
expect(scaffold.ctx.agentPresets.composedPreset(freshDefault.ctx)).toBe(initialDefault)
expect(scaffold.ctx.tools.schemas(freshDefault).some(tool => tool.name === 'cua')).toBe(false)
const fresh = (await scaffold.ctx.agents.create({
sessionId: SessionId('cua-new-ptc'),
setup: async (ctx) => { await scaffold.ctx.agentPresets.mount(ctx, 'ptc') },
})).agent
expect(scaffold.ctx.agentPresets.composedPreset(fresh.ctx)).toBe('ptc')
expect(scaffold.ctx.tools.schemas(fresh).some(tool => tool.name === 'cua')).toBe(true)
await card.getByRole('button', { name: '查看 Computer Use', exact: true }).click()
toggle = page.locator('[data-plugin-panel]').getByRole('switch', { name: '启用 Computer Use', exact: true })
const persistent = page.getByRole('group', { name: 'PTC 执行方式', exact: true })
const beforePersistent = scaffold.ctx.ptcRuntime
await persistent.getByRole('button', { name: '设为持久化', exact: true }).click()
await persistent.getByRole('button', { name: '已设为持久化', exact: true }).waitFor()
expect(scaffold.ctx.ptcRuntime.programFormat).toBe(script ? 'script' : 'repl')
expect(scaffold.ctx.ptcRuntime.retainsState).toBe(true)
expect(scaffold.ctx.ptcRuntime === beforePersistent).toBe(false)
saved = await readFile(settingsPath, 'utf8')
expect(saved).toContain(`executionMode: ${script ? 'vm-script' : 'persistent-in-memory'}`)
expect(saved).toContain(`scriptPersistence: ${script}`)
const runtime = scaffold.ctx.ptcRuntime
const persistentRuntimeFiber = runtimeFiber()
const owner = {}
const program = 'globalThis.counter = (globalThis.counter ?? 0) + 1'
expect((await runtime.run(runtime.resolve({ program, bindings: [], stateOwner: owner }))).value).toBe(1)
await toggle.click()
await toggle.and(page.locator('[aria-checked="false"]')).waitFor({ state: 'visible' })
expect(scaffold.ctx.get('computerUse')).toBeUndefined()
expect(runtimeFiber() === persistentRuntimeFiber).toBe(true)
expect(scaffold.ctx.agentPresets.defaultId).toBe(initialDefault)
expect(scaffold.ctx.agents.get(ptcAgent.id) === ptcAgent).toBe(true)
for (const agent of [ptcAgent, fresh]) {
expect(scaffold.ctx.tools.schemas(agent).some(tool => tool.name === 'cua')).toBe(false)
expect((await scaffold.ctx.systemPrompt.assemble({ scope: agent })).tools.map(tool => tool.name)).toEqual(['run_code'])
}
expect((await runtime.run(runtime.resolve({ program, bindings: [], stateOwner: owner }))).value).toBe(2)
expect(await readFile(settingsPath, 'utf8')).toBe(saved)
await toggle.click()
await toggle.and(page.locator('[aria-checked="true"]')).waitFor({ state: 'visible' })
await setup.waitFor()
expect(await setup.getByText('Computer Use 插件已开启,但暂时无法检测系统权限。请前往插件详情页查看原因并重试。', { exact: true }).count()).toBe(1)
expect(scaffold.ctx.agentPresets.defaultId).toBe(initialDefault)
await setup.getByRole('button', { name: '前往设置', exact: true }).click()
await setup.waitFor({ state: 'hidden' })
await page.getByRole('region', { name: '给 Computer Use 授权', exact: true }).getByRole('alert').waitFor()
if (!codingTools) {
expect(await readFile(settingsPath, 'utf8')).toBe(saved)
expect(scaffold.ctx.settings.describe().find(row => row.ns === 'ui-settings')?.value).toMatchObject({ enabled: false })
expect(scaffold.ctx.agentPresets.defaultId).toBe(initialDefault)
expect((await runtime.run(runtime.resolve({ program, bindings: [], stateOwner: owner }))).value).toBe(3)
await runtime.release(owner)
return
}
const defaultSetting = page.getByRole('group', { name: '新会话默认模式', exact: true })
await expect.poll(() => defaultSetting.getByRole('button', { name: '设为 PTC', exact: true }).isEnabled()).toBe(true)
await compareOrRefreshGolden(fileURLToPath(new URL('./expected/cua-permissions/default-mode.expected.md', import.meta.url)),
await captureStableAria(page, '[role="group"][aria-label="新会话默认模式"]', scaffold.workspaceCwd), webSnapshotMode())
await defaultSetting.getByRole('button', { name: '设为 PTC', exact: true }).click()
await expect.poll(() => scaffold.ctx.agentPresets.defaultId).toBe('ptc')
expect(scaffold.ctx.agentPresets.composedPreset(originalAgent.ctx)).toBe(initialDefault)
expect(scaffold.ctx.agentPresets.composedPreset(freshDefault.ctx)).toBe(initialDefault)
const confirmed = (await scaffold.ctx.agents.create({
sessionId: SessionId('cua-confirmed-default'),
setup: async (ctx) => { await scaffold.ctx.agentPresets.mount(ctx) },
})).agent
expect(scaffold.ctx.agentPresets.composedPreset(confirmed.ctx)).toBe('ptc')
expect(scaffold.ctx.tools.schemas(confirmed).some(tool => tool.name === 'cua')).toBe(true)
expect(runtimeFiber() === persistentRuntimeFiber).toBe(true)
expect(scaffold.ctx.tools.schemas(ptcAgent).some(tool => tool.name === 'cua')).toBe(true)
expect((await runtime.run(runtime.resolve({ program, bindings: [], stateOwner: owner }))).value).toBe(3)
const confirmedSettings = await readFile(settingsPath, 'utf8')
expect(load(confirmedSettings)).toEqual(expect.arrayContaining([
expect.objectContaining({ id: 'agent-preset-registry', config: { default: 'standard', selectedDefault: 'ptc' } }),
]))
await toggle.click()
await toggle.and(page.locator('[aria-checked="false"]')).waitFor({ state: 'visible' })
await toggle.click()
await toggle.and(page.locator('[aria-checked="true"]')).waitFor({ state: 'visible' })
await setup.waitFor()
expect(await setup.getByText('Computer Use 插件已开启,但暂时无法检测系统权限。请前往插件详情页查看原因并重试。', { exact: true }).count()).toBe(1)
expect(await setup.getByRole('button', { name: '设为 PTC', exact: true }).count()).toBe(0)
expect(await readFile(settingsPath, 'utf8')).toBe(confirmedSettings)
expect(scaffold.ctx.settings.describe().find(row => row.ns === 'ui-settings')?.value).toMatchObject({ enabled: codingTools })
await runtime.release(owner)
} finally {
try { await browser?.close() } finally {
try { await ownedScaffold?.close() } finally { await rm(directory, { recursive: true, force: true }) }
}
}
})