import { test, expect } from '@playwright/test' test('file memories and an independent project are editable on desktop and mobile', async ({ page }, testInfo) => { await page.goto('/conversations') await page.getByRole('textbox', { name: '账号', exact: true }).fill('fixture') await page.getByRole('textbox', { name: '密码', exact: true }).fill('fixture-pass') await page.getByRole('button', { name: '登录', exact: true }).click() for (const name of ['记忆甲', '记忆乙']) { await page.locator('.desktop-sidebar .sidebar-create-trigger').click() await page.getByRole('menuitem', { name: '新建 Bot', exact: true }).click() const editor = page.getByRole('dialog', { name: '创建机器人' }) await editor.getByRole('textbox', { name: '名称', exact: true }).fill(name) await editor.getByRole('button', { name: '保存', exact: true }).click() await expect(page.getByRole('heading', { name, exact: true })).toBeVisible() } await page.getByRole('button', { name: '记忆与协作', exact: true }).click() const panel = page.getByRole('dialog', { name: 'Bot 记忆与协作' }) await panel.getByRole('button', { name: '项目', exact: true }).click() await panel.getByRole('button', { name: '新建项目', exact: true }).click() await panel.getByRole('textbox', { name: '名称', exact: true }).fill('文件记忆项目') await panel.getByRole('textbox', { name: '说明', exact: true }).fill('多位 Bot 共同维护项目事实') await panel.getByRole('group', { name: '项目成员', exact: true }).getByRole('checkbox', { name: '记忆甲', exact: true }).check() await panel.getByRole('group', { name: '项目成员', exact: true }).getByRole('checkbox', { name: '记忆乙', exact: true }).check() await panel.getByRole('button', { name: '保存项目', exact: true }).click() await expect(panel.getByRole('heading', { name: '文件记忆项目', exact: true })).toBeVisible() await panel.getByRole('button', { name: '项目记忆', exact: true }).click() await panel.getByRole('button', { name: '添加记忆', exact: true }).click() await panel.getByRole('textbox', { name: '事实', exact: true }).fill('接口变更必须兼容旧客户端') await panel.getByRole('button', { name: '保存', exact: true }).click() await expect(panel.locator('article').filter({ hasText: '接口变更必须兼容旧客户端' })).toBeVisible() const article = panel.locator('article').filter({ hasText: '接口变更必须兼容旧客户端' }).first() await article.getByRole('button', { name: '编辑', exact: true }).click() await panel.getByRole('textbox', { name: '事实', exact: true }).fill('接口变更需兼容旧客户端并注明版本') await panel.getByRole('button', { name: '保存', exact: true }).click() const edited = panel.locator('article').filter({ hasText: '接口变更需兼容旧客户端并注明版本' }).first() await expect(edited).toContainText('版本 2') await edited.getByRole('button', { name: '修订记录', exact: true }).click() await expect(panel.getByRole('heading', { name: '修订记录', exact: true })).toBeVisible() await page.screenshot({ path: testInfo.outputPath('bot-project-memory-desktop.png') }) await page.setViewportSize({ width: 390, height: 844 }) await page.emulateMedia({ colorScheme: 'dark', reducedMotion: 'reduce' }) await expect(panel).toBeInViewport() expect(await panel.evaluate(el => el.scrollWidth <= el.clientWidth + 1)).toBe(true) await page.screenshot({ path: testInfo.outputPath('bot-project-memory-mobile.png') }) await panel.getByRole('button', { name: '收起', exact: true }).click() await edited.getByRole('button', { name: '遗忘', exact: true }).click() await expect(panel.getByText('暂无记忆。可手动添加,也可在对话中说明需要长期记住的事实。')).toBeVisible() await panel.getByRole('button', { name: '用户记忆', exact: true }).click() await panel.getByRole('button', { name: '添加记忆', exact: true }).click() await panel.getByRole('textbox', { name: '事实', exact: true }).fill('用户偏好简洁的中文说明') await panel.getByRole('button', { name: '保存', exact: true }).click() await expect(panel.locator('article').filter({ hasText: '用户偏好简洁的中文说明' })).toBeVisible() const download = page.waitForEvent('download') await panel.getByRole('link', { name: '导出 Markdown', exact: true }).click() expect((await download).suggestedFilename()).toBe('bot-memory.md') await panel.getByRole('button', { name: '关闭记忆与协作', exact: true }).click() }) test('simple group creation keeps chat lightweight and explicit delivery has editable collapsed details',async({page},testInfo)=>{ await page.goto('/conversations') await page.getByRole('textbox',{name:'账号',exact:true}).fill('fixture') await page.getByRole('textbox',{name:'密码',exact:true}).fill('fixture-pass') await page.getByRole('button',{name:'登录',exact:true}).click() await expect(page.locator('.desktop-sidebar .sidebar-create-trigger')).toBeVisible() for (const name of ['交付负责人','协作成员']) { await page.locator('.desktop-sidebar .sidebar-create-trigger').click() await page.getByRole('menuitem',{name:'新建 Bot',exact:true}).click() const editor=page.getByRole('dialog',{name:'创建机器人'}) await editor.getByRole('textbox',{name:'名称',exact:true}).fill(name) await expect(editor.getByRole('checkbox',{name:'允许组建团队',exact:true})).toHaveCount(0) await editor.getByRole('button',{name:'保存',exact:true}).click() await expect(page.getByRole('heading',{name,exact:true})).toBeVisible() } await page.reload() await page.locator('.desktop-sidebar .sidebar-create-trigger').click() await page.getByRole('menuitem',{name:'新建群聊',exact:true}).click() const dialog=page.getByRole('dialog',{name:'创建群聊'}) await expect(dialog.getByRole('combobox',{name:'协作方式',exact:true})).toBeHidden() await expect(dialog.getByText('软件开发团队',{exact:true})).toBeHidden() await dialog.getByRole('textbox',{name:'名称',exact:true}).fill('轻量协作群') await dialog.getByRole('checkbox',{name:'交付负责人',exact:true}).check() await dialog.getByRole('checkbox',{name:'协作成员',exact:true}).check() await expect(dialog.getByRole('combobox',{name:'负责人',exact:true})).toBeHidden() await dialog.getByRole('textbox',{name:'群规则(可选)',exact:true}).fill('回答简洁,结论注明来源。') await page.screenshot({path:testInfo.outputPath('simple-group-desktop.png')}) await page.setViewportSize({width:390,height:844}) await page.emulateMedia({colorScheme:'dark',reducedMotion:'reduce'}) await expect(dialog.getByRole('button',{name:'保存',exact:true})).toBeInViewport() await page.screenshot({path:testInfo.outputPath('simple-group-mobile.png')}) await dialog.getByRole('button',{name:'保存',exact:true}).click() await expect(page.getByRole('heading',{name:'轻量协作群',exact:true})).toBeVisible() const id=new URL(page.url()).pathname.split('/').at(-1)! const snapshot=await (await page.request.get(`/api/app/conversations/${id}`)).json() expect(snapshot.tasks).toHaveLength(1) expect(snapshot.task.goal).toBeUndefined() expect(snapshot.assignments).toEqual([]) await expect(page.locator('.task-plan')).toHaveCount(0) const composer=page.getByRole('textbox',{name:'输入消息,Enter 发送,Shift + Enter 换行'}) await composer.fill('简单讨论一下这个方案') await page.getByRole('button',{name:'发送消息',exact:true}).click() await expect(page.locator('.message--assistant')).not.toHaveCount(0) await expect(page.getByRole('button',{name:'交付目标',exact:true})).toBeEnabled() const before=await (await page.request.get(`/api/app/conversations/${id}`)).json() expect(before.task.goal).toBeUndefined() await page.getByRole('button',{name:'交付目标',exact:true}).click() await composer.fill('把方案做完并交付一份报告') const sent=page.waitForResponse(r=>r.request().method()==='POST'&&r.url().endsWith(`/api/app/conversations/${id}/messages`)) await page.getByRole('button',{name:'发送消息',exact:true}).click() const response=await sent expect(response.request().postDataJSON().mode).toBe('goal') expect(response.ok(),await response.text()).toBe(true) await expect(page.getByRole('region',{name:'目标进度'})).toBeVisible() const details=page.locator('.task-plan details') await expect(details).not.toHaveAttribute('open') expect((await (await page.request.get(`/api/app/conversations/${id}`)).json()).tasks).toHaveLength(1) await details.locator('summary').click() await page.getByRole('button',{name:'调整验收要求',exact:true}).click() await page.getByRole('textbox',{name:'验收要求',exact:true}).fill('比较三种方案\n说明结论和依据') await page.getByRole('button',{name:'保存验收要求',exact:true}).click() await expect(details).toContainText('比较三种方案') await expect(page.getByRole('button',{name:'调整验收要求',exact:true})).toBeVisible() await page.screenshot({path:testInfo.outputPath('delivery-criteria-mobile.png')}) await page.setViewportSize({width:1280,height:900}) await page.emulateMedia({colorScheme:'light'}) await details.locator('summary').click() await page.screenshot({path:testInfo.outputPath('delivery-progress-desktop.png')}) await page.getByRole('button',{name:'停止任务',exact:true}).click() await expect(page.locator('.task-plan header')).toContainText('已停止') }) test('new bots can team by default and the permission checkboxes are gone', async ({page}) => { await page.goto('/conversations') await page.getByRole('textbox',{name:'账号',exact:true}).fill('fixture') await page.getByRole('textbox',{name:'密码',exact:true}).fill('fixture-pass') await page.getByRole('button',{name:'登录',exact:true}).click() await expect(page.getByRole('heading',{name:'还没有聊天'})).toBeVisible() await page.locator('.desktop-sidebar .sidebar-create-trigger').click() await page.getByRole('menuitem',{name:'新建 Bot',exact:true}).click() const dialog=page.getByRole('dialog') await expect(dialog.getByRole('checkbox',{name:'允许组建团队',exact:true})).toHaveCount(0) await expect(dialog.getByRole('checkbox',{name:'允许 Bot 协作',exact:true})).toHaveCount(0) await dialog.getByRole('textbox',{name:'名称',exact:true}).fill('组队老板') const saved=page.waitForResponse(r=>r.request().method()==='POST' && r.url().endsWith('/api/app/agents')) await dialog.getByRole('button',{name:'保存',exact:true}).click() const response=await saved expect(response.ok(),await response.text()).toBe(true) expect((await response.json()).agent.canManageTeam).toBe(true) }) test('task plans show owners and keep independent text and attachment drafts when switching tasks',async({page},testInfo)=>{ await page.goto('/conversations') await page.getByRole('textbox',{name:'账号',exact:true}).fill('fixture') await page.getByRole('textbox',{name:'密码',exact:true}).fill('fixture-pass') await page.getByRole('button',{name:'登录',exact:true}).click() await expect(page.locator('.desktop-sidebar .sidebar-create-trigger')).toBeVisible() const seeded=await page.request.post('/__test/task-plan?helper=1',{data:{}}) expect(seeded.ok()).toBe(true) const ids=await seeded.json() await page.goto(`/conversations/${ids.sourceConversationId}`) await page.getByRole('link',{name:'打开任务',exact:true}).click() await expect(page).toHaveURL(new RegExp(`/conversations/${ids.conversationId}\\?taskId=${ids.firstTaskId}`)) await page.locator('.task-plan details summary').click() await expect(page.locator('.task-plan')).toContainText('研究资料') await expect(page.locator('.task-plan')).toContainText('验收研究员') await expect(page.locator('.task-plan')).toContainText('临时助手') await page.screenshot({path:testInfo.outputPath('task-helper-active.png')}) const composer=page.getByRole('textbox',{name:'输入消息,Enter 发送,Shift + Enter 换行'}) await composer.fill('调研任务的草稿') await page.locator('.composer-area input[type=file]').first().setInputFiles({name:'draft.txt',mimeType:'text/plain',buffer:Buffer.from('待发送附件')}) await page.getByRole('combobox',{name:'当前话题'}).selectOption(ids.secondTaskId) await expect(composer).toHaveValue('') await composer.fill('独立任务的草稿') await page.getByRole('combobox',{name:'当前话题'}).selectOption(ids.firstTaskId) await expect(composer).toHaveValue('调研任务的草稿') await expect(page.locator('.composer-attachments')).toContainText('draft.txt') await page.screenshot({path:testInfo.outputPath('task-plan-drafts.png')}) await page.getByRole('button',{name:'停止任务',exact:true}).click() await expect(page.locator('.task-plan')).toContainText('已停止') await expect(page.locator('.task-plan')).toContainText('已退役') if(!await page.getByText('验收研究员(临时) · 临时助手', {exact:false}).first().isVisible())await page.locator('.task-plan details summary').click() await page.screenshot({path:testInfo.outputPath('task-helper-retired.png')}) }) test('registers an execution node, downloads its private config and disables it',async({page},testInfo)=>{ await page.goto('/conversations') await page.getByRole('textbox',{name:'账号',exact:true}).fill('fixture') await page.getByRole('textbox',{name:'密码',exact:true}).fill('fixture-pass') await page.getByRole('button',{name:'登录',exact:true}).click() await page.locator('.desktop-sidebar .sidebar-account-switcher__main').click() await page.getByRole('menuitem',{name:'我的设置',exact:true}).click() const dialog=page.getByRole('dialog',{name:'我的设置'}) await dialog.getByRole('button',{name:'Hermes 连接',exact:true}).click() await dialog.locator('details.runner-settings summary').click() const panel=dialog.getByRole('region',{name:'执行节点设置'}) await panel.getByRole('textbox',{name:'节点名称',exact:true}).fill('隔离测试 Mac') await panel.getByRole('textbox',{name:'允许的 Profile'}).fill('default') await panel.getByRole('checkbox',{name:'启用隔离电脑 Worker'}).check() await panel.getByRole('textbox',{name:'兼容镜像 ID'}).fill('sha256:'+'a'.repeat(64)) await panel.getByRole('textbox',{name:'Hermes Python 路径'}).fill('/fixture/venv/bin/python') await panel.getByRole('textbox',{name:'Hermes 源码目录'}).fill('/fixture/hermes-agent') await panel.getByRole('textbox',{name:'Hermes 配置目录'}).fill('/fixture/hermes') await panel.scrollIntoViewIfNeeded() await page.screenshot({path:testInfo.outputPath('runner-registration.png')}) await panel.getByRole('button',{name:'注册节点',exact:true}).click() await expect(panel).toContainText('待连接') const downloaded=page.waitForEvent('download') await panel.getByRole('button',{name:'下载 Runner 配置',exact:true}).click() const download=await downloaded expect(download.suggestedFilename()).toBe('runner.json') const stream=await download.createReadStream();const chunks:Buffer[]=[] for await(const chunk of stream!)chunks.push(Buffer.from(chunk)) const configuration=JSON.parse(Buffer.concat(chunks).toString()) expect(configuration).toMatchObject({protocol:1,allowedProfiles:['default'],artifactRoots:[],computers:{network:'public-proxy',runtime:'docker',python:'/fixture/venv/bin/python',hermesHome:'/fixture/hermes'}}) expect(configuration.token.length).toBeGreaterThan(32) const result=await (await page.request.get('/api/app/admin/runners')).json() expect(JSON.stringify(result)).not.toContain(configuration.token) expect(JSON.stringify(result)).not.toContain('tokenHash') await page.setViewportSize({width:390,height:844}) await panel.getByRole('button',{name:'停用 隔离测试 Mac'}).scrollIntoViewIfNeeded() await page.screenshot({path:testInfo.outputPath('runner-registered-mobile.png')}) await panel.getByRole('button',{name:'停用 隔离测试 Mac'}).click() await expect(panel).toContainText('已停用') expect((await (await page.request.get('/api/app/admin/runners')).json()).runners.find((r:any)=>r.id===configuration.runnerId).enabled).toBe(false) }) test('configures the Agent computer in its chat panel and keeps preparation in app settings',async({page},testInfo)=>{ await page.goto('/conversations') await page.getByRole('textbox',{name:'账号',exact:true}).fill('fixture');await page.getByRole('textbox',{name:'密码',exact:true}).fill('fixture-pass');await page.getByRole('button',{name:'登录',exact:true}).click() await page.locator('.desktop-sidebar .sidebar-create-trigger').click();await page.getByRole('menuitem',{name:'新建 Bot',exact:true}).click() const editor=page.getByRole('dialog');await editor.getByRole('textbox',{name:'名称',exact:true}).fill('尚未配置的本地电脑') await expect(editor.getByRole('combobox',{name:'执行环境'})).toHaveCount(0) await editor.getByRole('button',{name:'保存',exact:true}).click() await page.getByRole('button',{name:'电脑与定时任务',exact:true}).click() const dock=page.getByRole('complementary',{name:'机器人电脑面板'}) const vmBox=dock.locator('.env-check',{hasText:'本地虚拟机'}).getByRole('checkbox') await vmBox.check() await expect(vmBox).toBeChecked() await expect(dock.getByRole('button',{name:'设置本地虚拟机',exact:true})).toBeVisible() await page.screenshot({path:testInfo.outputPath('local-vm-unconfigured-agent.png')}) }) async function envCheck(page:any,label:string){ const dock=page.getByRole('complementary',{name:'机器人电脑面板'}) const box=dock.locator('.env-check',{hasText:label}).getByRole('checkbox') return {box,dock} } async function openVmSettings(page:any){ await page.locator('.desktop-sidebar').getByRole('button',{name:'工具',exact:true}).click() await page.getByRole('menuitem',{name:'Bot 设置',exact:true}).click() const settings=page.getByRole('dialog',{name:'Bot 设置',exact:true}) await settings.getByRole('button',{name:'本地虚拟机',exact:true}).click() return settings } async function login(page:any,ready=false){ await page.goto('/conversations');await page.getByRole('textbox',{name:'账号',exact:true}).fill('fixture');await page.getByRole('textbox',{name:'密码',exact:true}).fill('fixture-pass');await page.getByRole('button',{name:'登录',exact:true}).click() await expect(page.locator('.desktop-sidebar .sidebar-create-trigger')).toBeVisible() await page.request.post('/__test/local-vm'+(ready?'?ready=1':''),{data:{}}) } test('persists idle-stop settings and parallel environment toggles on desktop and mobile',async({page},testInfo)=>{ await login(page,true) const settings=await openVmSettings(page) const timeout=settings.getByRole('combobox',{name:'虚拟机空闲停止时间',exact:true}) await timeout.selectOption('30');await expect(timeout).toHaveValue('30') await timeout.selectOption('0');await expect(settings).toContainText('空闲时保持运行') expect((await(await page.request.get('/api/app/admin/local-vm')).json()).idleStopMinutes).toBe(0) await timeout.scrollIntoViewIfNeeded();await page.screenshot({path:testInfo.outputPath('idle-stop-settings.png')}) await settings.getByRole('button',{name:'关闭Bot 设置',exact:true}).click() await page.request.post('/__test/hybrid-runner',{data:{}}) const seed=await(await page.request.post('/__test/computer?off=1',{data:{}})).json() await page.goto(`/conversations/${seed.conversationId}`);await page.getByRole('button',{name:'电脑与定时任务',exact:true}).click() const dock=page.getByRole('complementary',{name:'机器人电脑面板'}) const vmBox=dock.locator('.env-check',{hasText:'本地虚拟机'}).getByRole('checkbox'),hostBox=dock.locator('.env-check',{hasText:'本机控制'}).getByRole('checkbox') await vmBox.check();await expect(vmBox).toBeChecked() await hostBox.check();await expect(hostBox).toBeChecked() expect((await(await page.request.get('/api/app/agents')).json()).agents.find((a:any)=>a.id===seed.agentId)).toMatchObject({envs:{vm:true,desktop:true},computer:'vm',execution:'computer'}) await expect(dock).toContainText('永不自动停止') await hostBox.scrollIntoViewIfNeeded();await page.screenshot({path:testInfo.outputPath('hybrid-desktop.png')}) await page.reload();await page.getByRole('button',{name:'电脑与定时任务',exact:true}).click();await expect(vmBox).toBeChecked();await expect(hostBox).toBeChecked() await page.setViewportSize({width:390,height:844});await hostBox.scrollIntoViewIfNeeded();await page.screenshot({path:testInfo.outputPath('hybrid-mobile.png')}) expect(await page.evaluate(()=>document.documentElement.scrollWidth)).toBeLessThanOrEqual(390) await hostBox.uncheck();await expect(dock.getByRole('checkbox',{name:'允许本机环境',exact:true})).toHaveCount(0) expect((await(await page.request.get('/api/app/agents')).json()).agents.find((a:any)=>a.id===seed.agentId)).toMatchObject({envs:{vm:true,desktop:false},computer:'vm',allowHostEnvironment:false}) }) test('keeps the parallel VM toggle through reload on desktop and phone',async({page},testInfo)=>{ await login(page,true) await page.request.post('/__test/hybrid-runner',{data:{}}) const seed=await(await page.request.post('/__test/computer?off=1',{data:{}})).json() await page.goto(`/conversations/${seed.conversationId}`);await page.getByRole('button',{name:'电脑与定时任务',exact:true}).click() const dock=page.getByRole('complementary',{name:'机器人电脑面板'}) const vmBox=dock.locator('.env-check',{hasText:'本地虚拟机'}).getByRole('checkbox') await expect(dock.getByRole('combobox',{name:'本地虚拟机执行方式',exact:true})).toHaveCount(0) await expect(vmBox).toBeChecked() await vmBox.uncheck() expect((await(await page.request.get('/api/app/agents')).json()).agents.find((a:any)=>a.id===seed.agentId)).toMatchObject({envs:{vm:false},computer:'off',execution:'profile'}) await vmBox.check();await expect(vmBox).toBeChecked() expect((await(await page.request.get('/api/app/agents')).json()).agents.find((a:any)=>a.id===seed.agentId)).toMatchObject({envs:{vm:true},computer:'vm',execution:'computer'}) await vmBox.scrollIntoViewIfNeeded();await page.screenshot({path:testInfo.outputPath('profile-vm-desktop.png')}) await page.reload();await page.getByRole('button',{name:'电脑与定时任务',exact:true}).click();await expect(vmBox).toBeChecked() await page.setViewportSize({width:390,height:844});await page.emulateMedia({colorScheme:'dark',reducedMotion:'reduce'}) await vmBox.scrollIntoViewIfNeeded();await page.screenshot({path:testInfo.outputPath('profile-vm-phone.png')}) expect(await page.evaluate(()=>document.documentElement.scrollWidth)).toBeLessThanOrEqual(390) await vmBox.uncheck() expect((await(await page.request.get('/api/app/agents')).json()).agents.find((a:any)=>a.id===seed.agentId)).toMatchObject({envs:{vm:false},computer:'off',execution:'profile'}) }) test('cloud stays selected while desktop runs in parallel from the checkbox panel',async({page},testInfo)=>{ await login(page,true) const seed=await(await page.request.post('/__test/cloud-host-option',{data:{}})).json() await page.goto(`/conversations/${seed.conversationId}`);await page.getByRole('button',{name:'电脑与定时任务',exact:true}).click() const dock=page.getByRole('complementary',{name:'机器人电脑面板'}) const cloudBox=dock.locator('.env-check',{hasText:'云端 · Grok Bot'}).getByRole('checkbox'),hostBox=dock.locator('.env-check',{hasText:'本机控制'}).getByRole('checkbox') await expect(cloudBox).toBeChecked() await hostBox.check();await expect(cloudBox).toBeChecked();await expect(hostBox).toBeChecked() expect((await(await page.request.get('/api/app/agents')).json()).agents.find((a:any)=>a.id===seed.agentId)).toMatchObject({envs:{cloud:true,desktop:true},computer:'cloud',execution:'profile'}) await hostBox.scrollIntoViewIfNeeded();await page.screenshot({path:testInfo.outputPath('cloud-host-option.png')}) await page.reload();await page.getByRole('button',{name:'电脑与定时任务',exact:true}).click();await expect(cloudBox).toBeChecked();await expect(hostBox).toBeChecked() await hostBox.uncheck() expect((await(await page.request.get('/api/app/agents')).json()).agents.find((a:any)=>a.id===seed.agentId)).toMatchObject({envs:{cloud:true,desktop:false},allowHostEnvironment:false}) }) test('Local VM settings replace the image catalog and prepare the managed desktop',async({page},testInfo)=>{ await login(page) await expect(page.getByRole('button',{name:'电脑与环境',exact:true})).toHaveCount(0) const settings=await openVmSettings(page) const panel=settings.getByRole('region',{name:'本地虚拟机设置'}) await expect(panel).toContainText('隔离方式') await expect(panel.getByRole('button',{name:'上传并导入'})).toHaveCount(0) await panel.getByRole('button',{name:'准备所选镜像',exact:true}).click() await expect(panel).toContainText('虚拟机环境已就绪') await panel.getByRole('combobox',{name:'虚拟机数量上限'}).selectOption('3') await expect(panel.getByRole('combobox',{name:'虚拟机数量上限'})).toHaveValue('3') await panel.scrollIntoViewIfNeeded() await page.screenshot({path:testInfo.outputPath('local-vm-settings.png')}) expect((await page.request.get('/api/app/admin/runners/11111111-1111-4111-8111-111111111111/images')).status()).toBe(404) }) test('Agent computer appears as a right panel and opens an interactive desktop',async({page},testInfo)=>{ await login(page) const seed=await(await page.request.post('/__test/computer?off=1',{data:{}})).json() await page.goto(`/conversations/${seed.conversationId}`) await page.getByRole('button',{name:'电脑与定时任务',exact:true}).click() const dock=page.getByRole('complementary',{name:'机器人电脑面板'}) await expect(dock).toBeVisible();await expect(dock).toContainText('本地虚拟机') await dock.getByRole('button',{name:'设置本地虚拟机',exact:true}).click() const settings=page.getByRole('dialog',{name:'Bot 设置'}) await settings.getByRole('button',{name:'准备所选镜像',exact:true}).click() await settings.getByRole('button',{name:'关闭Bot 设置',exact:true}).click() await expect(dock.getByRole('button',{name:/创建.*的虚拟机/})).toBeVisible({timeout:12000}) await dock.getByRole('button',{name:/创建.*的虚拟机/}).click() await expect(dock.getByRole('img')).toBeVisible() const bounds=await dock.boundingBox();expect(bounds!.x).toBeGreaterThan(500) await page.screenshot({path:testInfo.outputPath('agent-local-vm-panel.png')}) expect((await(await page.request.get('/__test/computer')).json()).actions).toEqual([]) await dock.getByRole('button',{name:'打开桌面',exact:true}).click() const viewer=page.getByRole('dialog',{name:'隔离电脑',exact:true}) await expect(viewer).toContainText('人工控制中') await viewer.getByRole('button',{name:'键盘与操作',exact:true}).click() await viewer.getByRole('textbox',{name:'输入到电脑',exact:true}).fill('本地虚拟机交互验收') await viewer.getByRole('button',{name:'输入文字',exact:true}).click() await expect.poll(async()=>(await(await page.request.get('/__test/computer')).json()).actions).toEqual([{kind:'text',text:'本地虚拟机交互验收'}]) await viewer.getByRole('img').dblclick() await expect.poll(async()=>(await(await page.request.get('/__test/computer')).json()).actions.filter((a:any)=>a.kind==='click')).toMatchObject([{kind:'click',button:'left',count:2}]) await viewer.getByRole('button',{name:'交还并关闭',exact:true}).click() await dock.getByRole('button',{name:'停止虚拟机',exact:true}).click() await expect(dock.getByRole('button',{name:'启动虚拟机',exact:true})).toBeVisible() }) test('two desktops are watch-only on opening and release before changing the controlled pane',async({page},testInfo)=>{ await login(page,true) const first=await(await page.request.post('/__test/computer',{data:{}})).json() const second=await(await page.request.post('/__test/computer',{data:{}})).json() await page.goto(`/conversations/${first.conversationId}`) await page.getByRole('button',{name:'电脑与定时任务',exact:true}).click() const dock=page.getByRole('complementary',{name:'机器人电脑面板'}) await expect(dock.getByRole('button',{name:'打开双桌面'})).toBeVisible() await dock.getByRole('button',{name:'打开双桌面'}).click() const workspace=page.getByRole('main',{name:'双桌面工作区'}) await expect(workspace.locator('.pane')).toHaveCount(2) await workspace.getByRole('combobox',{name:'选择桌面 2'}).selectOption(second.agentId) await expect(workspace.locator('.pane').last().getByRole('img')).toBeVisible() await expect(workspace.getByRole('button',{name:'接管电脑',exact:true})).toHaveCount(2) await workspace.locator('.pane').first().getByRole('button',{name:'接管电脑',exact:true}).click() await expect(workspace.locator('.pane').first()).toContainText('人工控制中') await workspace.locator('.pane').last().getByRole('button',{name:'接管电脑',exact:true}).click() await expect(workspace.locator('.pane').first().getByRole('button',{name:'接管电脑',exact:true})).toBeVisible() await expect(workspace.locator('.pane').last()).toContainText('人工控制中') await page.screenshot({path:testInfo.outputPath('local-vm-two-desktops.png')}) await workspace.getByRole('button',{name:'交还并关闭',exact:true}).click() await expect(workspace).toHaveCount(0) })