test(cua): cover wrapped loading and persistent diagnostics during row changes
This commit is contained in:
1 parent
8126594af5
commit
2bb84a5974
12 files changed
+64
-11
No files matched your search
@@ -7,7 +7,7 @@ import { ToolCallId } from '@deepseek-ai/dsh-llm'
|
||||
import type {} from '@deepseek-ai/dsh-user-approval'
|
||||
import type {} from '@deepseek-ai/dsh-agent-preset-registry'
|
||||
import { chromium, type Browser } from 'playwright'
|
||||
import { expect, it, onTestFailed, onTestFinished } from 'vitest'
|
||||
import { expect, it, onTestFailed, onTestFinished, vi } from 'vitest'
|
||||
import { captureStableAria, compareOrRefreshGolden, launchWebScaffold, webSnapshotMode, watchConsole, type WebScaffold } from './scaffold.ts'
|
||||
import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
|
||||
import { installNativeFixture, startFixtureHost } from '../../../packages/experimental/cua-macos/tests/native-fixture.ts'
|
||||
@@ -16,7 +16,7 @@ const bundle = fileURLToPath(new URL('../../../packages/experimental/cua-bundle'
|
||||
const expected = fileURLToPath(new URL('./expected/cua-permissions/missing.expected.md', import.meta.url))
|
||||
|
||||
// System-permission onboarding belongs to the macOS-only bundle; rendering and policy units remain portable.
|
||||
it.skipIf(process.platform !== 'darwin').each([false, true])('keeps system requests pending until fresh observation and persists application revocation (read-only runtime: %s)', async (readOnlyRuntime) => {
|
||||
it.skipIf(process.platform !== 'darwin')('keeps system requests pending until fresh observation and persists application revocation', async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), 'dsh-cua-permissions-web-'))
|
||||
const resources: { scaffold?: WebScaffold; browser?: Browser; closeFixture?: () => Promise<void> } = {}
|
||||
onTestFinished(async () => {
|
||||
@@ -38,11 +38,8 @@ it.skipIf(process.platform !== 'darwin').each([false, true])('keeps system reque
|
||||
await writeFile(join(fixtureBundle, 'package.json'), JSON.stringify({ name: 'cua-permission-fixture', version: '1.0.0',
|
||||
dsh: { bundle: { patch: 'cordis.patch.yml' } } }))
|
||||
await writeFile(join(fixtureBundle, 'cordis.patch.yml'), `- id: cua\n config: ${JSON.stringify({ helperPath: helper })}\n`)
|
||||
const runtimePatch = join(root, 'readonly-runtime.yml')
|
||||
await writeFile(runtimePatch, JSON.stringify([{ id: 'ptc-runtime', config: { executionMode: 'per-call' } }]))
|
||||
const scaffold = await launchWebScaffold({
|
||||
profile: { packages: [{ dir: bundle, enabled: true }, { dir: fixtureBundle, enabled: true }] },
|
||||
...(readOnlyRuntime ? { extraOverlayPath: runtimePatch } : {}),
|
||||
})
|
||||
resources.scaffold = scaffold
|
||||
await scaffold.ctx.settings.mutate('cua', [{ op: 'set', path: ['allowedApps'],
|
||||
@@ -100,7 +97,7 @@ it.skipIf(process.platform !== 'darwin').each([false, true])('keeps system reque
|
||||
expect(await panel.getByRole('group', { name: 'Screen Recording', exact: true }).getByText('Visual Studio Code.app', { exact: true }).count()).toBe(1)
|
||||
await expect.poll(() => accessibility.locator('img').evaluate((image: HTMLImageElement) => image.naturalWidth)).toBe(1)
|
||||
await expect.poll(() => panel.getAttribute('aria-busy')).toBe('false')
|
||||
if (!readOnlyRuntime) await compareOrRefreshGolden(expected, await captureStableAria(page, '[data-cua-settings]', scaffold.workspaceCwd), webSnapshotMode())
|
||||
await compareOrRefreshGolden(expected, await captureStableAria(page, '[data-cua-settings]', scaffold.workspaceCwd), webSnapshotMode())
|
||||
try {
|
||||
for (const width of [1680, 390]) {
|
||||
await page.setViewportSize({ width, height: 1000 })
|
||||
@@ -186,6 +183,8 @@ it.skipIf(process.platform !== 'darwin').each([false, true])('keeps system reque
|
||||
const toggle = page.locator('[data-plugin-panel]').getByRole('switch', { name: 'Enable Computer Use', exact: true })
|
||||
for (const width of [1680, 390]) {
|
||||
await page.setViewportSize({ width, height: 1000 })
|
||||
if (width === 390) await page.locator('[data-sidebar-collapsed]').waitFor({ state: 'attached' })
|
||||
await page.locator('[data-animating]').waitFor({ state: 'hidden' })
|
||||
await trace.evaluate((value) => { value.layouts.length = 0 })
|
||||
for (let cycle = 0; cycle < 3; cycle++) {
|
||||
await toggle.click()
|
||||
@@ -205,5 +204,39 @@ it.skipIf(process.platform !== 'darwin').each([false, true])('keeps system reque
|
||||
await trace.evaluate((value) => { value.stop() })
|
||||
await trace.dispose()
|
||||
}
|
||||
await writeFile(state, JSON.stringify({ accessibility: 'unavailable', screenRecording: false }))
|
||||
await page.evaluate(() => { window.dispatchEvent(new Event('focus')) })
|
||||
const diagnostic = panel.getByRole('alert')
|
||||
await diagnostic.waitFor()
|
||||
const diagnosticText = await diagnostic.innerText()
|
||||
const height = await panel.evaluate(element => element.getBoundingClientRect().height)
|
||||
const rowToggle = plugins.getByRole('switch', { name: 'Enable component @deepseek-ai/dsh-experimental-cua-macos', exact: true })
|
||||
const entered = Promise.withResolvers<undefined>(), release = Promise.withResolvers<undefined>()
|
||||
const changeRow = scaffold.ctx.pluginManager.setPluginEnabled.bind(scaffold.ctx.pluginManager)
|
||||
let changing: ReturnType<typeof changeRow> | undefined
|
||||
const hold = vi.spyOn(scaffold.ctx.pluginManager, 'setPluginEnabled').mockImplementationOnce((entryId, enabled) => {
|
||||
entered.resolve(undefined)
|
||||
changing = release.promise.then(() => changeRow(entryId, enabled))
|
||||
return changing
|
||||
})
|
||||
try {
|
||||
await rowToggle.click()
|
||||
await entered.promise
|
||||
await expect.poll(() => panel.getAttribute('aria-busy')).toBe('true')
|
||||
expect(await diagnostic.innerText()).toBe(diagnosticText)
|
||||
expect(await diagnostic.getByRole('button', { name: 'Retry', exact: true }).isEnabled()).toBe(false)
|
||||
expect(await panel.evaluate(element => element.getBoundingClientRect().height)).toBe(height)
|
||||
} finally {
|
||||
release.resolve(undefined)
|
||||
hold.mockRestore()
|
||||
await changing
|
||||
}
|
||||
await expect.poll(() => rowToggle.getAttribute('aria-checked')).toBe('false')
|
||||
await expect.poll(() => rowToggle.isEnabled()).toBe(true)
|
||||
await writeFile(state, JSON.stringify({ accessibility: false, screenRecording: false, launchApplication }))
|
||||
await rowToggle.click()
|
||||
await expect.poll(() => rowToggle.getAttribute('aria-checked')).toBe('true')
|
||||
await expect.poll(() => panel.getAttribute('aria-busy')).toBe('false')
|
||||
await diagnostic.waitFor({ state: 'hidden' })
|
||||
expect(tripwire.pageErrors).toEqual([])
|
||||
}, 60_000)
|
||||
@@ -1,5 +1,5 @@
|
||||
- listitem:
|
||||
- button "查看 Computer Use": Computer Use
|
||||
- text: 实验性 目前仅支持 macOS,Windows 支持正在开发中。为已有及新建 PTC 会话加载 Computer Use。请切换到 PTC 模式来使用,并授予辅助功能和屏幕录制权限。
|
||||
- status "正在应用 Computer Use 的更改"
|
||||
- status: 正在应用 Computer Use 的更改
|
||||
- switch "启用 Computer Use" [disabled]
|
||||
@@ -16,7 +16,7 @@
|
||||
- strong: Screen Recording
|
||||
- paragraph: In System Settings, allow Visual Studio Code.app to capture application windows.
|
||||
- button "Request permission"
|
||||
- paragraph: Enable the application in System Settings. Follow the macOS prompt if it names another application.
|
||||
- paragraph: Manage permissions in System Settings for the application named by macOS.
|
||||
- region "Manage applications Computer Use can access":
|
||||
- heading "Manage applications Computer Use can access" [level=3]
|
||||
- heading "Always-allowed applications" [level=4]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
- button "Back to plugins": Plugins
|
||||
- button "Uninstall @fixture/bundle": Uninstall
|
||||
- status
|
||||
- switch "Enable @fixture/bundle"
|
||||
- heading "@fixture/bundle" [level=3]
|
||||
- text: v0.0.1
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
- button "返回插件": 插件
|
||||
- button "卸载 @fixture/bundle": 卸载
|
||||
- status
|
||||
- switch "启用 @fixture/bundle"
|
||||
- heading "@fixture/bundle" [level=3]
|
||||
- text: v0.0.1
|
||||
|
||||
@@ -25,25 +25,31 @@
|
||||
- listitem:
|
||||
- button "查看 智能体团队": 智能体团队
|
||||
- text: 实验性 启用团队协作、团队工具、成员列表和共享任务看板。
|
||||
- status
|
||||
- switch "启用 智能体团队"
|
||||
- listitem:
|
||||
- button "查看 语音输入": 语音输入
|
||||
- text: 实验性 在本机使用 SenseVoice 转写录音,首次使用需安装依赖。
|
||||
- status
|
||||
- switch "启用 语音输入"
|
||||
- listitem:
|
||||
- button "查看 自动授权审查": 自动授权审查
|
||||
- text: 实验性 提供自动审查权限模式,由模型在每次工具调用前判断是否授权。
|
||||
- status
|
||||
- switch "启用 自动授权审查"
|
||||
- listitem:
|
||||
- button "查看 开发者工具": 开发者工具
|
||||
- text: 实验性 查看调试会话原始数据、聊天消息分组数据,以及调试 NodeJS 后端
|
||||
- status
|
||||
- switch "启用 开发者工具"
|
||||
- heading "已安装插件" [level=3]
|
||||
- list:
|
||||
- listitem:
|
||||
- button "查看 @fixture/bundle": "@fixture/bundle"
|
||||
- text: Registry description for the fixture bundle.
|
||||
- status
|
||||
- switch "启用 @fixture/bundle" [checked]
|
||||
- listitem:
|
||||
- button "查看 dsh-web-scaffold-defaults": dsh-web-scaffold-defaults
|
||||
- status
|
||||
- switch "启用 dsh-web-scaffold-defaults" [checked]
|
||||
@@ -25,25 +25,31 @@
|
||||
- listitem:
|
||||
- button "查看 智能体团队": 智能体团队
|
||||
- text: 实验性 启用团队协作、团队工具、成员列表和共享任务看板。
|
||||
- status
|
||||
- switch "启用 智能体团队"
|
||||
- listitem:
|
||||
- button "查看 语音输入": 语音输入
|
||||
- text: 实验性 在本机使用 SenseVoice 转写录音,首次使用需安装依赖。
|
||||
- status
|
||||
- switch "启用 语音输入"
|
||||
- listitem:
|
||||
- button "查看 自动授权审查": 自动授权审查
|
||||
- text: 实验性 提供自动审查权限模式,由模型在每次工具调用前判断是否授权。
|
||||
- status
|
||||
- switch "启用 自动授权审查"
|
||||
- listitem:
|
||||
- button "查看 开发者工具": 开发者工具
|
||||
- text: 实验性 查看调试会话原始数据、聊天消息分组数据,以及调试 NodeJS 后端
|
||||
- status
|
||||
- switch "启用 开发者工具"
|
||||
- heading "已安装插件" [level=3]
|
||||
- list:
|
||||
- listitem:
|
||||
- button "查看 @fixture/bundle": "@fixture/bundle"
|
||||
- text: Registry description for the fixture bundle.
|
||||
- status
|
||||
- switch "启用 @fixture/bundle"
|
||||
- listitem:
|
||||
- button "查看 dsh-web-scaffold-defaults": dsh-web-scaffold-defaults
|
||||
- status
|
||||
- switch "启用 dsh-web-scaffold-defaults" [checked]
|
||||
@@ -1,5 +1,6 @@
|
||||
- button "返回插件": 插件
|
||||
- button "卸载 @fixture/missing-bundle": 卸载
|
||||
- status
|
||||
- switch "启用 @fixture/missing-bundle" [checked]
|
||||
- heading "@fixture/missing-bundle" [level=3]
|
||||
- text: 异常 未安装
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
- button "返回 实验性插件": 实验性插件
|
||||
- status
|
||||
- switch "启用 DSH 徽章技能"
|
||||
- heading "DSH 徽章技能" [level=3]
|
||||
- text: v{{version}} 实验性
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
background: var(--dsw-alias-bg-skeleton);
|
||||
}
|
||||
.buttonPlaceholder { height: 28px; }
|
||||
.appPlaceholder { width: 160px; height: 20px; }
|
||||
.appsPlaceholder { display: block; width: 240px; height: 1.6em; }
|
||||
.appPlaceholder { width: 160px; height: 1.6em; }
|
||||
.appsPlaceholder { display: block; color: transparent; }
|
||||
|
||||
.iconPlaceholder { display: inline-block; width: 32px; height: 32px; flex-shrink: 0; }
|
||||
@@ -175,7 +175,7 @@ export function PermissionPanel(props: PermissionPanelProps) {
|
||||
<span className={css.error}>{t(key === 'applications' ? 'applicationsFailed' : 'iconsFailed', { message: state[key].error })}</span>
|
||||
<Button size="sm" variant="outline" disabled={transitioning || state[key].busy} onClick={refresh}>{t('retry')}</Button>
|
||||
</div>)}
|
||||
{apps === null ? <div className={css.emptyState}><span className={css.appsPlaceholder} aria-hidden /></div> : <>
|
||||
{apps === null ? <p className={css.emptyState}><span className={css.appsPlaceholder} aria-hidden>{t('noApps')}</span></p> : <>
|
||||
{apps.allowedApps.length === 0 && apps.blockedApps.length === 0 && <p className={css.emptyState}>{t('noApps')}</p>}
|
||||
{apps.allowedApps.length > 0 && <Applications title={t('allowedApps')} apps={apps.allowedApps} icons={state.icons.value}
|
||||
onAction={props.revoke} label={t('revoke')} disabled={appsBusy || !apps.persistentAvailable} />}
|
||||
|
||||
@@ -400,15 +400,19 @@ it('retains uncertainty and the diagnostic until a refresh succeeds', () => {
|
||||
|
||||
it('retains a known read-only configuration and query failure throughout management and refresh', () => {
|
||||
const f = fixture()
|
||||
f.preset.set({ ...f.preset.getSnapshot(), value: { default: 'standard' }, writable: false })
|
||||
f.publish({ ...observe(value), runtime: { value: { persistent: false, writable: false }, busy: false, error: null },
|
||||
applications: { value, busy: false, error: 'offline' } })
|
||||
const view = render(<PermissionPanel {...f.props} />)
|
||||
const readonly = screen.getByText(en.persistentReadOnly)
|
||||
const defaultReadonly = screen.getByText(en.defaultReadOnly)
|
||||
act(() => { f.preset.set({ ...f.preset.getSnapshot(), status: 'loading' }) })
|
||||
const query = screen.getByText(en.applicationsFailed.replace('{message}', 'offline'))
|
||||
f.publish({ changing: true, busy: true,
|
||||
runtime: { ...f.state.getSnapshot().runtime, busy: true },
|
||||
applications: { ...f.state.getSnapshot().applications, busy: true } })
|
||||
expect(screen.getByText(en.persistentReadOnly)).toBe(readonly)
|
||||
expect(screen.getByText(en.defaultReadOnly)).toBe(defaultReadonly)
|
||||
expect(screen.getByText(en.applicationsFailed.replace('{message}', 'offline'))).toBe(query)
|
||||
view.unmount()
|
||||
expect(f.state.getSnapshot().changing).toBe(true)
|
||||
|
||||
Reference in new issue
Block a user