inline-update.test.mjs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. import { test } from 'node:test'
  2. import assert from 'node:assert/strict'
  3. import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises'
  4. import { createServer } from 'node:net'
  5. import { spawn } from 'node:child_process'
  6. import { tmpdir } from 'node:os'
  7. import { join, resolve } from 'node:path'
  8. import { _electron as electron, expect } from '@playwright/test'
  9. async function freePort() {
  10. const server = createServer()
  11. await new Promise(done => server.listen(0, '127.0.0.1', done))
  12. const port = server.address().port
  13. await new Promise(done => server.close(done))
  14. return port
  15. }
  16. test('the real desktop workspace downloads in place and waits for an explicit restart', { timeout: 60000 }, async () => {
  17. const root = resolve(import.meta.dirname, '..'), home = await mkdtemp(join(tmpdir(), 'yaoyao-inline-update-'))
  18. const port = await freePort(), upstream = await freePort(), origin = `http://127.0.0.1:${port}`
  19. await mkdir(join(home, 'server'))
  20. const server = spawn(process.execPath, ['--import', 'tsx', 'tests/fixtures/workspace-server.ts'], {
  21. cwd: root, env: { ...process.env, WORKSPACE_FIXTURE_HOME: join(home, 'server'), WORKSPACE_FIXTURE_PORT: String(port), WORKSPACE_FIXTURE_UPSTREAM_PORT: String(upstream) }, stdio: 'ignore',
  22. })
  23. const desktopHome = join(home, 'desktop'); await mkdir(desktopHome)
  24. await writeFile(join(desktopHome, 'desktop-preferences.json'), JSON.stringify({ startupChoice: 'remote', remoteServer: origin }))
  25. let app
  26. try {
  27. await expect.poll(async () => { try { return (await fetch(origin + '/healthz')).status } catch { return 0 } }, { timeout: 30000 }).toBe(200)
  28. app = await electron.launch({ args: [root], cwd: root, env: { ...process.env, HERMES_YAOYAO_DESKTOP_CLIENT_ONLY: '1', HERMES_YAOYAO_DESKTOP_TEST_HOME: desktopHome, HERMES_YAOYAO_DESKTOP_TEST_AUTO_UPDATE: '1' } })
  29. const page = await app.firstWindow()
  30. await app.evaluate(({ app }, root) => {
  31. const { createRequire } = process.getBuiltinModule('node:module')
  32. const { autoUpdater } = createRequire(root + '/package.json')(root + '/.desktop-build-client/electron-updater.cjs')
  33. globalThis.inlineFixture = { installs: 0 }
  34. autoUpdater.checkForUpdates = async () => ({ isUpdateAvailable: true, updateInfo: { version: '99.0.0' } })
  35. autoUpdater.downloadUpdate = () => new Promise(resolve => {
  36. inlineFixture.ready = () => resolve([])
  37. inlineFixture.stage = () => autoUpdater.emit('update-downloaded')
  38. autoUpdater.emit('download-progress', { transferred: 37, total: 100 })
  39. })
  40. autoUpdater.quitAndInstall = () => { inlineFixture.installs++ }
  41. }, root)
  42. await expect.poll(() => page.evaluate(() => window.yaoyaoDesktop.status().then(state => state.phase))).toBe('ready')
  43. await page.reload()
  44. await page.locator('#username').fill('fixture')
  45. await page.locator('#password').fill('fixture-pass')
  46. await page.locator('#submit').click()
  47. await page.waitForURL(origin + '/**')
  48. await page.goto(origin + '/conversations')
  49. await expect(page.locator('.desktop-sidebar .sidebar-account-switcher__main')).toBeVisible()
  50. await page.evaluate(() => window.yaoyaoDesktop.updateAction('check'))
  51. const footer = page.locator('.desktop-sidebar .desktop-update-entry')
  52. await expect(footer.getByRole('button', { name: '可更新', exact: true })).toBeVisible()
  53. await expect(page.locator('#desktop-update-entry')).toHaveCount(0)
  54. assert.equal(app.windows().length, 1)
  55. const output = join(root, 'test-results/desktop/inline-workspace'); await mkdir(output, { recursive: true })
  56. await page.screenshot({ path: join(output, 'available.png') })
  57. await footer.getByRole('button', { name: '可更新', exact: true }).click()
  58. await expect(footer.getByRole('progressbar')).toHaveAttribute('aria-valuenow', '37')
  59. await page.locator('.desktop-sidebar .sidebar-account-switcher__main').click()
  60. const menu = page.locator('.workspace-settings-menu .desktop-update-entry')
  61. await expect(menu).toContainText('正在下载 · 37%')
  62. await page.screenshot({ path: join(output, 'downloading-menu.png') })
  63. await page.keyboard.press('Escape')
  64. await expect(menu).toHaveCount(0)
  65. await app.evaluate(() => inlineFixture.stage())
  66. await expect(footer).toContainText('正在准备…')
  67. await page.reload()
  68. await expect(footer).toContainText('正在准备…')
  69. await expect(footer.getByRole('button')).toBeDisabled()
  70. await app.evaluate(() => inlineFixture.ready())
  71. await expect(footer.getByRole('button', { name: '重新启动', exact: true })).toBeEnabled()
  72. assert.equal(await app.evaluate(() => inlineFixture.installs), 0)
  73. assert.equal(app.windows().length, 1)
  74. await page.screenshot({ path: join(output, 'ready.png') })
  75. await page.evaluate(() => document.documentElement.classList.add('dark'))
  76. await page.screenshot({ path: join(output, 'ready-dark.png') })
  77. await footer.getByRole('button', { name: '重新启动', exact: true }).click()
  78. await expect.poll(() => app.evaluate(() => inlineFixture.installs)).toBe(1)
  79. await expect(footer.getByRole('button', { name: '正在重启…', exact: true })).toBeDisabled()
  80. assert.equal((await fetch(origin + '/healthz')).status, 200)
  81. } finally {
  82. await app?.close().catch(() => {})
  83. server.kill('SIGTERM')
  84. await new Promise(done => {
  85. if (server.exitCode !== null) return done()
  86. const timer = setTimeout(() => { server.kill('SIGKILL'); done() }, 3000)
  87. server.once('exit', () => { clearTimeout(timer); done() })
  88. })
  89. await rm(home, { recursive: true, force: true })
  90. }
  91. })