fix(install-e2e): wait for Electron close before driver exit
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
import { EventEmitter } from 'node:events'
|
||||
import { createRequire } from 'node:module'
|
||||
|
||||
import { expect, it, vi } from 'vitest'
|
||||
|
||||
const { observeProcessClose } = createRequire(import.meta.url)('../tests/install/e2e-assets/process-close.cjs')
|
||||
|
||||
it('waits for native close, not exit, and retains a close observed before hand-off', async () => {
|
||||
const child = new EventEmitter()
|
||||
const waitForClose = observeProcessClose(child)
|
||||
let finished = false
|
||||
const completion = waitForClose().then(() => { finished = true })
|
||||
child.emit('exit', 0)
|
||||
await Promise.resolve()
|
||||
expect(finished).toBe(false)
|
||||
child.emit('close', 0)
|
||||
await completion
|
||||
expect(finished).toBe(true)
|
||||
await expect(waitForClose()).resolves.toBeUndefined()
|
||||
})
|
||||
|
||||
it('fails if the launched process never closes', async () => {
|
||||
vi.useFakeTimers()
|
||||
try {
|
||||
const waitForClose = observeProcessClose(new EventEmitter())
|
||||
const completion = expect(waitForClose(2_000)).rejects.toThrow('Electron process did not close')
|
||||
await vi.advanceTimersByTimeAsync(2_000)
|
||||
await completion
|
||||
expect(vi.getTimerCount()).toBe(0)
|
||||
} finally {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
})
|
||||
@@ -21,6 +21,7 @@ const fs = require('node:fs')
|
||||
|
||||
const { _electron } = require('@playwright/test')
|
||||
const { prepareWindowForInput } = require('./window-input.cjs')
|
||||
const { observeProcessClose } = require('./process-close.cjs')
|
||||
|
||||
const exePath = process.argv[2]
|
||||
const proofDir = process.argv[3]
|
||||
@@ -93,6 +94,9 @@ async function main() {
|
||||
env: { ...process.env },
|
||||
timeout: 120_000
|
||||
})
|
||||
const child = app.process()
|
||||
const waitForProcessClose = observeProcessClose(child)
|
||||
log(`launched Electron pid=${child.pid}`)
|
||||
|
||||
// firstWindow() can grab a helper webContents (wake indicator etc.), not
|
||||
// the main app window. Pick the window that actually renders UI (has a
|
||||
@@ -255,7 +259,8 @@ async function main() {
|
||||
// The "Updating Hermes — this window will close" overlay should appear,
|
||||
// then the app quits (hand-off dwell). Screenshot the overlay while the
|
||||
// window is still alive.
|
||||
await page.waitForTimeout(1200)
|
||||
// The app can close during the dwell. This wait must outlive its page.
|
||||
await new Promise(resolve => setTimeout(resolve, 1200))
|
||||
await shot(page, '05-updating-overlay')
|
||||
|
||||
// ── Wait for the hand-off to take over ────────────────────────────────
|
||||
@@ -315,7 +320,10 @@ async function main() {
|
||||
throw new Error('no hand-off within 150s of Update now (no marker, no result, app still alive)')
|
||||
}
|
||||
|
||||
log('hand-off confirmed — detached updater owns the rest')
|
||||
// A marker appears before Electron exits. Exiting this driver at that point
|
||||
// lets Playwright taskkill the entire tree, including the detached updater.
|
||||
await waitForProcessClose()
|
||||
log('Electron process closed — detached updater owns the rest')
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// Observe at launch: a renderer can close before the native process and its
|
||||
// stdio pipes. Playwright's driver exit cleanup tree-kills until that close.
|
||||
function observeProcessClose(child) {
|
||||
let closed = false
|
||||
const completion = new Promise(resolve => child.once('close', () => {
|
||||
closed = true
|
||||
resolve()
|
||||
}))
|
||||
return async function waitForClose(timeoutMs = 120_000) {
|
||||
if (closed) return
|
||||
let timer
|
||||
try {
|
||||
await Promise.race([
|
||||
completion,
|
||||
new Promise((_, reject) => {
|
||||
timer = setTimeout(() => reject(new Error('Electron process did not close after update hand-off')), timeoutMs)
|
||||
}),
|
||||
])
|
||||
} finally {
|
||||
clearTimeout(timer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { observeProcessClose }
|
||||
@@ -763,6 +763,7 @@ function Invoke-GuiUpdateDesktopRoute([string]$TargetSha) {
|
||||
$driver = Join-Path $driverDir "e2e-drive-update.cjs"
|
||||
Copy-Item (Join-Path $AssetsDir "drive-update.cjs") $driver -Force
|
||||
Copy-Item (Join-Path $AssetsDir "window-input.cjs") (Join-Path $driverDir "window-input.cjs") -Force
|
||||
Copy-Item (Join-Path $AssetsDir "process-close.cjs") (Join-Path $driverDir "process-close.cjs") -Force
|
||||
Push-Location $driverDir
|
||||
$prevEap = $ErrorActionPreference
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
Reference in New Issue
Block a user