computer-viewer.test.mjs 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142
  1. import { test } from 'node:test'
  2. import assert from 'node:assert/strict'
  3. import { mkdtemp, writeFile, mkdir, rm } from 'node:fs/promises'
  4. import { tmpdir } from 'node:os'
  5. import { join, resolve } from 'node:path'
  6. import { pathToFileURL } from 'node:url'
  7. import { spawn } from 'node:child_process'
  8. import { _electron as electron, expect } from '@playwright/test'
  9. const root=resolve(import.meta.dirname,'..')
  10. test('desktop takeover opens a separate large viewer and returns its lease before native close',{timeout:90000},async()=>{
  11. const home=await mkdtemp(join(tmpdir(),'yaoyao-viewer-')),port=18864,origin=`http://127.0.0.1:${port}`
  12. const server=spawn(process.execPath,['--import','tsx','tests/fixtures/workspace-server.ts'],{cwd:root,env:{...process.env,WORKSPACE_FIXTURE_HOME:home,WORKSPACE_FIXTURE_TEAM_TOOLS:'1',WORKSPACE_FIXTURE_PORT:String(port),WORKSPACE_FIXTURE_UPSTREAM_PORT:'19164'},stdio:'ignore'})
  13. let app
  14. try{
  15. await expect.poll(async()=>{try{return(await fetch(origin+'/healthz')).status}catch{return 0}},{timeout:30000}).toBe(200)
  16. const harness=join(home,'main.mjs')
  17. await writeFile(harness,`import {app,BrowserWindow} from '${pathToFileURL(join(root,'node_modules/electron/index.js')).href}';\nimport {installComputerViewer} from '${pathToFileURL(join(root,'desktop/computer-viewer.mjs')).href}';\napp.setPath('userData',${JSON.stringify(join(home,'chromium'))});app.whenReady().then(async()=>{let quitting=false;app.on('before-quit',()=>quitting=true);const owner=new BrowserWindow({width:1440,height:900,webPreferences:{preload:${JSON.stringify(join(root,'desktop/preload.cjs'))},sandbox:true,contextIsolation:true}});installComputerViewer({owner:()=>owner,origin:()=>${JSON.stringify(origin)},preload:${JSON.stringify(join(root,'desktop/preload.cjs'))},quitting:()=>quitting});await owner.loadURL(${JSON.stringify(origin+'/conversations')});});`)
  18. // Electron's built-in module must be resolved in its own process.
  19. const content=await (await import('node:fs/promises')).readFile(harness,'utf8');await writeFile(harness,content.replace(pathToFileURL(join(root,'node_modules/electron/index.js')).href,'electron'))
  20. app=await electron.launch({args:[harness],cwd:root})
  21. const page=await app.firstWindow();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()
  22. await expect(page.locator('.desktop-sidebar .sidebar-create-trigger')).toBeVisible()
  23. const seed=await(await page.request.post(origin+'/__test/computer',{data:{}})).json()
  24. await page.goto(origin+'/conversations/'+seed.conversationId)
  25. const agents=(await(await page.request.get(origin+'/api/app/agents')).json()).agents
  26. const agent=agents.find(item=>item.id===seed.agentId)||agents.find(item=>item.execution==='computer')||agents[0]
  27. const opened=app.waitForEvent('window');await page.evaluate(id=>window.yaoyaoDesktop.openComputer(id),agent.id)
  28. const viewer=await opened;await expect(viewer.locator('.computer-panel.standalone')).toContainText('人工控制中')
  29. await expect(viewer.locator('.computer-screen img')).toBeVisible()
  30. await expect(viewer.locator('footer')).toBeHidden()
  31. const screen=await viewer.locator('.computer-screen').boundingBox();assert.ok(screen.height>650)
  32. await viewer.locator('.computer-screen img').click();await viewer.keyboard.type('takeover-'+Date.now())
  33. await expect.poll(async()=>(await(await page.request.get(origin+'/__test/computer')).json()).actions.filter(item=>item.kind==='text').length).toBeGreaterThan(0)
  34. await mkdir(join(root,'docs/verification/2026-09-10-v044'),{recursive:true});await viewer.screenshot({path:join(root,'docs/verification/2026-09-10-v044/computer-takeover.png')})
  35. await viewer.getByRole('button',{name:'键盘与操作',exact:true}).click();await expect(viewer.getByRole('textbox',{name:'交还说明'})).toBeVisible()
  36. await viewer.getByRole('textbox',{name:'交还说明'}).fill('已完成人工操作')
  37. await app.evaluate(({BrowserWindow})=>BrowserWindow.getAllWindows().find(win=>win.webContents.getURL().includes('/conversations/computer/')).close())
  38. await expect.poll(()=>viewer.isClosed()).toBe(true)
  39. const status=await(await page.request.get(origin+`/api/app/agents/${agent.id}/computer`)).json();assert.notEqual(status.mode,'human')
  40. assert.equal(page.isClosed(),false)
  41. }finally{await app?.close().catch(()=>{});server.kill('SIGTERM');if(server.exitCode===null)await new Promise(done=>server.once('exit',done));await rm(home,{recursive:true,force:true})}
  42. })