core-lifecycle.test.mjs 4.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243
  1. import { enterLocal } from './test-support/onboarding.mjs'
  2. import {test} from 'node:test'
  3. import assert from 'node:assert/strict'
  4. import {mkdtemp,readFile,rm,mkdir} from 'node:fs/promises'
  5. import {tmpdir} from 'node:os'
  6. import {join,resolve} from 'node:path'
  7. import {createServer} from 'node:net'
  8. import {spawn} from 'node:child_process'
  9. import {_electron as electron} from '@playwright/test'
  10. import {localJSON,DesktopServiceManager} from './service-manager.mjs'
  11. const root=resolve(import.meta.dirname,'..')
  12. const release=JSON.parse(await readFile(join(root,'release.json'),'utf8'))
  13. async function freePort(){const server=createServer();await new Promise(done=>server.listen(0,'127.0.0.1',done));const port=server.address().port;await new Promise(done=>server.close(done));return port}
  14. async function until(read,accept,timeout=30000){const end=Date.now()+timeout;while(Date.now()<end){try{const value=await read();if(accept(value))return value}catch{}await new Promise(done=>setTimeout(done,100))}throw new Error('等待桌面状态超时')}
  15. test('packaged core service starts, preserves window-close work, recovers, and stops only its own service',{timeout:60000},async()=>{
  16. const home=await mkdtemp(join(tmpdir(),'yaoyao-app-core-')),port=await freePort(),upstreamPort=await freePort()
  17. const upstream=spawn(process.execPath,[join(root,'tests/fixtures/fake-hermes.mjs')],{cwd:root,env:{...process.env,FAKE_HERMES_PORT:String(upstreamPort),FAKE_HERMES_LOCAL_AUTH:'1'},stdio:'ignore'})
  18. let app,pid
  19. try{
  20. await until(()=>localJSON(`http://127.0.0.1:${upstreamPort}/api/status`),Boolean)
  21. app=await electron.launch({...(process.env.DESKTOP_TEST_EXECUTABLE?{executablePath:process.env.DESKTOP_TEST_EXECUTABLE}:{}),args:process.env.DESKTOP_TEST_EXECUTABLE?[]:[root],cwd:root,env:{...process.env,HERMES_YAOYAO_DESKTOP_TEST_HOME:home,HERMES_YAOYAO_DESKTOP_PORT:String(port),HERMES_YAOYAO_UPSTREAM:`http://127.0.0.1:${upstreamPort}`},timeout:30000})
  22. const page=await app.firstWindow();await enterLocal(page);await page.waitForURL(`http://127.0.0.1:${port}/**`)
  23. const initial=JSON.parse(await readFile(join(home,'service-instance.json'),'utf8'));pid=initial.pid
  24. const identity=await localJSON(`http://127.0.0.1:${port}/desktop/service`,{'x-yaoyao-desktop-token':initial.token})
  25. assert.equal(identity.desktopOwned,true);assert.equal(identity.pid,pid);assert.equal(identity.version,release.webVersion)
  26. assert.equal(await page.evaluate(async()=>{try{await window.yaoyaoDesktop.retry();return false}catch{return true}}),true)
  27. await mkdir(join(root,'test-results/desktop'),{recursive:true});await page.screenshot({path:join(root,'test-results/desktop/packaged-core.png')})
  28. await app.evaluate(({BrowserWindow})=>BrowserWindow.getAllWindows()[0].close())
  29. assert.equal(await app.evaluate(({BrowserWindow})=>BrowserWindow.getAllWindows()[0].isVisible()),false)
  30. assert.equal((await localJSON(`http://127.0.0.1:${port}/healthz`)).ok,true)
  31. await app.evaluate(({app})=>app.emit('activate'))
  32. assert.equal(await app.evaluate(({BrowserWindow})=>BrowserWindow.getAllWindows()[0].isVisible()),true)
  33. process.kill(pid,'SIGKILL')
  34. const replacement=await until(async()=>JSON.parse(await readFile(join(home,'service-instance.json'),'utf8')),value=>value.pid!==initial.pid&&value.url)
  35. pid=replacement.pid;assert.notEqual(replacement.instanceId,initial.instanceId)
  36. await until(()=>localJSON(`http://127.0.0.1:${port}/healthz`),value=>value.ok)
  37. const attached=new DesktopServiceManager({home,port,fork:()=>{throw new Error('必须复用已有服务')}})
  38. assert.equal((await attached.start()).external,true);await attached.stop()
  39. assert.equal((await localJSON(`http://127.0.0.1:${port}/healthz`)).ok,true)
  40. await app.close();app=undefined
  41. await until(()=>{try{process.kill(pid,0);return false}catch{return true}},Boolean,10000)
  42. }finally{await app?.close().catch(()=>{});if(pid)try{process.kill(pid,'SIGTERM')}catch{}upstream.kill('SIGTERM');await rm(home,{recursive:true,force:true})}
  43. })