offlineUpdate.spec.ts 4.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. import { expect, test } from '@playwright/test'
  2. import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
  3. import { tmpdir } from 'node:os'
  4. import { join } from 'node:path'
  5. import { pathToFileURL } from 'node:url'
  6. import { once } from 'node:events'
  7. import type { AddressInfo } from 'node:net'
  8. import serve from 'koa-static'
  9. import send from 'koa-send'
  10. test('opens settings and schedules an independent Web update while all 9119 requests fail', async ({ page }, testInfo) => {
  11. const home = mkdtempSync(join(tmpdir(), 'yaoyao-offline-ui-'))
  12. const { createApplication, createNodeServer } = await import(pathToFileURL(join(process.cwd(), 'dist-server/server/app.js')).href)
  13. const { SystemUpdateManager } = await import(pathToFileURL(join(process.cwd(), 'dist-server/server/updateManager.js')).href)
  14. const current = JSON.parse(readFileSync('release.json', 'utf8'))
  15. const [major, minor, patch] = current.webVersion.split('.').map(Number)
  16. const version = `${major}.${minor}.${patch + 1}`
  17. const latest = { ...current, releaseVersion: version, webVersion: version, gitTag: `v${version}` }
  18. const config = {
  19. host: '127.0.0.1', port: 0, upstream: new URL('http://127.0.0.1:1'),
  20. allowedHosts: new Set(['127.0.0.1']), home, mediaRoot: home, attachmentsRoot: home, imagesRoot: home,
  21. mediaOwner: 'test', allowInsecureLan: false, insecureLan: false, production: true,
  22. }
  23. const launched: string[] = []
  24. const updates = new SystemUpdateManager(config, {
  25. projectRoot: process.cwd(), platform: 'darwin',
  26. inspectRemote: async () => ({ manifest: latest, commit: 'b'.repeat(40) }),
  27. // Exercise scheduling and polling only; never install or stop a real service.
  28. launchUpdater: (path: string) => {
  29. const job = JSON.parse(readFileSync(path, 'utf8'))
  30. launched.push(job.target.releaseVersion)
  31. writeFileSync(path, JSON.stringify({ ...job, state: 'succeeded', message: '离线 Web 升级任务验收完成' }))
  32. rmSync(join(home, 'updates', 'active.lock'))
  33. },
  34. })
  35. const runtime = createApplication({ config, updates })
  36. runtime.app.use(serve(join(process.cwd(), 'dist')))
  37. runtime.app.use(async (ctx: Parameters<ReturnType<typeof serve>>[0]) => {
  38. if (ctx.method === 'GET' && ctx.accepts('html')) await send(ctx, 'index.html', { root: join(process.cwd(), 'dist') })
  39. })
  40. const node = createNodeServer(runtime)
  41. node.server.listen(0, '127.0.0.1'); await once(node.server, 'listening')
  42. const origin = `http://127.0.0.1:${(node.server.address() as AddressInfo).port}`
  43. try {
  44. const bootstrap = await (await page.request.get(origin + '/api/app/bootstrap')).json()
  45. const login = await page.request.post(origin + '/api/app/setup', {
  46. headers: { Origin: origin, 'X-CSRF-Token': bootstrap.csrfToken }, data: { username: 'admin', password: 'offline-e2e-password' },
  47. })
  48. const signedIn = await login.json()
  49. expect(login.ok(), `${login.status()} ${signedIn.error || ''}`).toBe(true)
  50. expect((await page.request.get(origin + '/readyz')).status()).toBe(503)
  51. await page.goto(origin + '/chat')
  52. await page.getByRole('button', { name: /^(打开设置中心|设置与模式)$/ }).click()
  53. if (await page.getByRole('menuitem', { name: '进入设置', exact: true }).isVisible()) await page.getByRole('menuitem', { name: '进入设置', exact: true }).click()
  54. const dialog = page.getByRole('dialog', { name: '设置中心' })
  55. await dialog.getByRole('button', { name: '系统概览', exact: true }).click()
  56. await expect(dialog.getByText(`Web ${current.webVersion} · 可独立升级与回滚`)).toBeVisible()
  57. await dialog.getByRole('button', { name: '更新与回滚', exact: true }).click()
  58. await expect(dialog.getByText('未检测(不影响 Web 升级)')).toBeVisible()
  59. await expect(dialog.getByText(version, { exact: true })).toBeVisible()
  60. await expect(dialog.getByRole('alert')).toHaveCount(0)
  61. await expect(page.locator('.settings-center-layer')).toHaveCSS('opacity', '1')
  62. await page.screenshot({ path: testInfo.outputPath('offline-web-update.png') })
  63. page.once('dialog', confirmation => confirmation.accept())
  64. await dialog.getByRole('button', { name: '升级 Web', exact: true }).click()
  65. await expect(dialog.getByText('离线 Web 升级任务验收完成')).toBeVisible()
  66. expect(launched).toEqual([version])
  67. } finally {
  68. await page.goto('about:blank')
  69. node.server.closeAllConnections()
  70. await node.close()
  71. rmSync(home, { recursive: true, force: true })
  72. }
  73. })