core-lifecycle.test.mjs 3.9 KB

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