mode-switch.test.mjs 5.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. import {test} from 'node:test'
  2. import assert from 'node:assert/strict'
  3. import {mkdtemp,readFile,writeFile,realpath,rm} from 'node:fs/promises'
  4. import {tmpdir} from 'node:os'
  5. import {join,resolve} from 'node:path'
  6. import {createServer} from 'node:http'
  7. import {_electron as electron,expect} from '@playwright/test'
  8. import {dataKey} from './service-manager.mjs'
  9. test('signed-in desktop pages can switch both ways; other windows cannot control the runtime',{timeout:60000},async()=>{
  10. const root=resolve(import.meta.dirname,'..'),home=await realpath(await mkdtemp(join(tmpdir(),'yaoyao-mode-switch-')))
  11. const version=JSON.parse(await readFile(join(root,'.desktop-build/release.json'),'utf8')).webVersion
  12. const identity={protocol:1,instanceId:'mode-switch',pid:process.pid,dataKey:dataKey(home),version}
  13. let releaseIdentity
  14. const identityReady=new Promise(resolve=>{releaseIdentity=resolve})
  15. const local=createServer(async(req,res)=>{
  16. if(req.url==='/desktop/service'){await identityReady;res.setHeader('Content-Type','application/json');res.end(JSON.stringify(identity))}
  17. else {res.setHeader('Content-Type','text/html; charset=utf-8');res.end('<title>已登录的本机页面</title>')}
  18. })
  19. const remote=createServer((_req,res)=>{res.setHeader('Content-Type','text/html; charset=utf-8');res.end('<title>已登录的服务器页面</title>')})
  20. await new Promise(done=>local.listen(0,'127.0.0.1',done))
  21. await new Promise(done=>remote.listen(0,'127.0.0.1',done))
  22. const origin=`http://127.0.0.1:${local.address().port}`,remoteURL=`http://127.0.0.1:${remote.address().port}`
  23. await writeFile(join(home,'service-instance.json'),JSON.stringify({...identity,url:origin,token:'fixture'}))
  24. await writeFile(join(home,'desktop-preferences.json'),JSON.stringify({startupChoice:'local',remoteServer:remoteURL,backgroundAtLogin:false}))
  25. let app
  26. try{
  27. app=await electron.launch({args:[root],cwd:root,env:{...process.env,HERMES_YAOYAO_DESKTOP_TEST_HOME:home,HERMES_YAOYAO_DESKTOP_PORT:String(local.address().port),HERMES_YAOYAO_DESKTOP_TEST_SYNC:'0'}})
  28. const page=await app.firstWindow();releaseIdentity();await page.waitForURL(origin+'/**')
  29. assert.deepEqual(await page.evaluate(()=>window.yaoyaoDesktop.modeState()),{mode:'server',serverURL:origin,switching:false})
  30. assert.deepEqual(await page.evaluate(()=>window.yaoyaoDesktop.authorizeComputer('csrf-test')),{registered:false})
  31. assert.equal(await page.evaluate(()=>window.yaoyaoDesktop.switchMode('invalid').then(()=>false,()=>true)),true)
  32. await page.evaluate(()=>{void window.yaoyaoDesktop.switchMode('client')})
  33. await page.waitForURL(remoteURL+'/**')
  34. assert.deepEqual(await page.evaluate(()=>window.yaoyaoDesktop.modeState()),{mode:'client',serverURL:remoteURL,switching:false})
  35. await expect.poll(async()=>JSON.parse(await readFile(join(home,'desktop-preferences.json'),'utf8')).startupChoice).toBe('remote')
  36. // Unchecking "ask at startup" must retain the active client role.
  37. await app.evaluate(({Menu})=>{const item=Menu.getApplicationMenu().getMenuItemById('desktop-host-ask');item.click(item)})
  38. await expect.poll(async()=>JSON.parse(await readFile(join(home,'desktop-preferences.json'),'utf8')).startupChoice).toBe('ask')
  39. await app.evaluate(({Menu})=>{const item=Menu.getApplicationMenu().getMenuItemById('desktop-host-ask');item.click(item)})
  40. await expect.poll(async()=>JSON.parse(await readFile(join(home,'desktop-preferences.json'),'utf8')).startupChoice).toBe('remote')
  41. const denied=await app.evaluate(async({BrowserWindow},{origin,preload})=>{
  42. const outsider=new BrowserWindow({show:false,webPreferences:{sandbox:true,contextIsolation:true,preload}})
  43. try{
  44. await outsider.loadURL(origin)
  45. return await outsider.webContents.executeJavaScript('Promise.all([window.yaoyaoDesktop.modeState(),window.yaoyaoDesktop.switchMode("server"),window.yaoyaoDesktop.openRemoteLogin(),window.yaoyaoDesktop.authorizeComputer("csrf-test")].map(p=>p.then(()=>false,()=>true)))')
  46. }finally{outsider.destroy()}
  47. },{origin:remoteURL,preload:join(root,'desktop/preload.cjs')})
  48. assert.deepEqual(denied,[true,true,true,true])
  49. await page.evaluate(()=>{void window.yaoyaoDesktop.switchMode('server')})
  50. await page.waitForURL(origin+'/**')
  51. await expect.poll(()=>page.evaluate(()=>window.yaoyaoDesktop.modeState())).toEqual({mode:'server',serverURL:origin,switching:false})
  52. assert.equal(JSON.parse(await readFile(join(home,'desktop-preferences.json'),'utf8')).startupChoice,'local')
  53. assert.equal(local.listening,true,'switching must leave an independent service running')
  54. assert.equal(remote.listening,true)
  55. // The server picker remains available after either login/mode selection.
  56. const opened=app.waitForEvent('window');await page.evaluate(()=>window.yaoyaoDesktop.openRemoteLogin())
  57. const login=await opened;await login.waitForLoadState()
  58. assert.equal(app.windows().length,2)
  59. await page.evaluate(()=>window.yaoyaoDesktop.openRemoteLogin())
  60. assert.equal(app.windows().length,2)
  61. await login.evaluate(()=>{void window.yaoyaoRemoteLogin.useLocal()})
  62. await expect.poll(()=>app.windows().length).toBe(1)
  63. await page.evaluate(()=>{void window.yaoyaoDesktop.switchMode('client')})
  64. await page.waitForURL(remoteURL+'/**')
  65. const pickerOpened=app.waitForEvent('window');await page.evaluate(()=>window.yaoyaoDesktop.openRemoteLogin())
  66. const picker=await pickerOpened;await picker.waitForLoadState()
  67. await picker.evaluate(()=>{void window.yaoyaoRemoteLogin.useLocal()})
  68. await page.waitForURL(origin+'/**')
  69. await expect.poll(()=>page.evaluate(()=>window.yaoyaoDesktop.modeState())).toEqual({mode:'server',serverURL:origin,switching:false})
  70. }finally{
  71. releaseIdentity();await app?.close().catch(()=>{})
  72. await Promise.all([local,remote].map(server=>new Promise(done=>{server.close(done);server.closeAllConnections()})))
  73. await rm(home,{recursive:true,force:true})
  74. }
  75. })