test(install-e2e): navigate historical updates and permit unattended quit

This commit is contained in:
ethernet
2026-09-21 14:49:33 -04:00
parent b265e87678
commit 980a70dd74
4 changed files with 84 additions and 2 deletions

View File

@@ -289,7 +289,8 @@ test('driver strips caller secrets and records missing executables as failure wi
try {
const env = smokeEnvironment({ PATH: '/usr/bin', DISPLAY: ':1', OPENAI_API_KEY: 'secret', HERMES_DESKTOP_BOOT_FAKE: '1',
HERMES_DESKTOP_HERMES_ROOT: '/wrong', PYTHONPATH: '/wrong', NODE_OPTIONS: '--inspect', HERMES_HOME: '/wrong' }, home, path.join(home, 'user-data'))
expect(env).toMatchObject({ PATH: '/usr/bin', DISPLAY: ':1', HERMES_HOME: home })
expect(env).toMatchObject({ PATH: '/usr/bin', DISPLAY: ':1', HERMES_HOME: home,
HERMES_DESKTOP_SKIP_QUIT_CONFIRM: '1' })
for (const key of ['OPENAI_API_KEY', 'HERMES_DESKTOP_BOOT_FAKE', 'HERMES_DESKTOP_HERMES_ROOT', 'PYTHONPATH', 'NODE_OPTIONS']) {
expect(env[key]).toBeUndefined()
}

View File

@@ -0,0 +1,72 @@
import { JSDOM } from 'jsdom'
import { afterEach, expect, test, vi } from 'vitest'
import updateUi from '../tests/install/e2e-assets/update-ui.cjs'
const windows = []
afterEach(() => {
windows.splice(0).forEach(window => window.close())
vi.useRealTimers()
})
function fixture({ details = true, available = true } = {}) {
vi.useFakeTimers({ toFake: ['Date'] })
vi.setSystemTime(0)
const { window } = new JSDOM('<body></body>', { runScripts: 'outside-only' })
windows.push(window)
const { document } = window
const clicks = []
const addButton = (text, onClick) => {
const button = document.createElement('button')
button.textContent = text
button.onclick = () => { clicks.push(text); onClick?.() }
document.body.append(button)
return button
}
const revealUpdate = () => addButton('Update now')
const check = addButton('Check now', () => {
check.disabled = true
if (!available) return
if (details) {
const more = addButton("See what's new", () => { more.remove(); revealUpdate() })
} else {
revealUpdate()
}
})
const status = { supported: true, behind: available ? 1 : 0 }
window.hermesDesktop = { updates: { check: async () => status } }
const page = {
getByRole(role, { name }) {
expect(role).toBe('button')
const selected = () => [...document.querySelectorAll('button')].find(button => name.test(button.textContent))
const locator = {
first: () => locator,
isVisible: async () => Boolean(selected()),
click: async () => {
const button = selected()
if (!button || button.disabled) throw new Error('button not actionable')
button.click()
},
}
return locator
},
waitForTimeout: async ms => vi.setSystemTime(Date.now() + ms),
evaluate: async fn => window.eval(`(${fn.toString()})()`),
}
return { page, clicks, log: vi.fn(), shot: vi.fn() }
}
test.each([true, false])('reveals the actual update button without applying (details=%s)', async details => {
const f = fixture({ details })
const update = await updateUi.waitForUpdate(f.page, f)
expect(await update.isVisible()).toBe(true)
expect(f.clicks).toEqual(details ? ['Check now', "See what's new"] : ['Check now'])
expect(f.shot).toHaveBeenCalledWith(f.page, '04-update-available')
})
test('does not turn a completed check without an update into success', async () => {
const f = fixture({ available: false })
await expect(updateUi.waitForUpdate(f.page, f)).rejects.toThrow(/"Update now" never appeared/)
expect(f.clicks).toEqual(['Check now'])
expect(f.log).toHaveBeenCalledWith('[update-status] {"supported":true,"behind":0}')
expect(f.shot).toHaveBeenCalledWith(f.page, 'ERROR-no-update-now')
})

View File

@@ -45,6 +45,9 @@ export function smokeEnvironment(inherited, home, userData) {
XDG_DATA_HOME: path.join(home, '.desktop-smoke-home', '.local', 'share'),
XDG_CACHE_HOME: path.join(home, '.desktop-smoke-home', '.cache'),
HERMES_HOME: home, HERMES_DESKTOP_USER_DATA_DIR: userData,
// app.close() cannot answer a native modal. This bypasses confirmation only;
// normal backend teardown and the driver's process-exit checks still run.
HERMES_DESKTOP_SKIP_QUIT_CONFIRM: '1',
APPDATA: path.join(home, '.desktop-smoke-home', 'AppData', 'Roaming'),
LOCALAPPDATA: path.join(home, '.desktop-smoke-home', 'AppData', 'Local'),
};

View File

@@ -80,8 +80,14 @@ async function openAbout(page, { prepare, log, shot, confirmSettings = false, hi
async function waitForUpdate(page, { log, shot }) {
const update = page.getByRole('button', { name: /update now/i }).first()
const details = page.getByRole('button', { name: /^see what['’]s new$/i }).first()
const deadline = Date.now() + 180_000
while (!await update.isVisible().catch(() => false) && Date.now() < deadline) {
if (await details.isVisible().catch(() => false)) {
await details.click({ timeout: 5_000 })
log("opened See what's new")
continue
}
await page.getByRole('button', { name: /check now/i }).first().click({ timeout: 5_000 })
.then(() => log('nudged Check now')).catch(() => {})
await page.waitForTimeout(15_000)
@@ -92,7 +98,7 @@ async function waitForUpdate(page, { log, shot }) {
).catch(error => `updates.check failed: ${error.message}`)
log(`[update-status] ${JSON.stringify(status)}`)
await shot(page, 'ERROR-no-update-now')
throw new Error('"Update now" never appeared — update check did not report an update')
throw new Error('"Update now" never appeared after checking for updates and opening available update details')
}
await shot(page, '04-update-available')
return update