fix(emulator): wait for the requested AVD during Android boot (#35536)

* fix(emulator): match the requested AVD while waiting for boot

* test(emulator): trim AVD boot identity test to the regression case

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
OrcaWin
2026-10-29 15:00:43 -07:00
committed by GitHub
co-authored by OrcaWin
parent 71918d4a78
commit b0e2bf6f81
3 changed files with 62 additions and 3 deletions
@@ -0,0 +1,55 @@
import { describe, expect, it, vi } from 'vitest'
import { bootAndroidDevice } from './android-avd-boot'
import type { AndroidSdkPaths } from './android-sdk-discovery'
vi.mock('node:child_process', () => ({
spawn: () => ({ on: vi.fn(), unref: vi.fn() })
}))
const sdk: AndroidSdkPaths = {
sdkRoot: 'sdk',
adb: 'adb',
emulator: 'emulator',
avdmanager: 'avdmanager'
}
type Device = { serial: string; name: string; booted: boolean }
const other: Device = { serial: 'emulator-5556', name: 'Other', booted: true }
const target: Device = {
serial: 'emulator-5558',
name: 'Wanted',
booted: true
}
function bootWithInventory(snapshots: Device[][]) {
let reads = 0
let current: Device[] = []
const runner = vi.fn(async (binary: string, args: readonly string[]) => {
let stdout = ''
if (binary === sdk.emulator && args[0] === '-list-avds') {
stdout = 'Wanted\nOther\n'
} else if (args[0] === 'devices') {
current = snapshots[Math.min(reads++, snapshots.length - 1)]
stdout = `List of devices attached\n${current.map((d) => `${d.serial}\tdevice`).join('\n')}`
} else {
const device = current.find((d) => d.serial === args[1])
stdout = args[2] === 'emu' ? `${device?.name ?? ''}\nOK\n` : device?.booted ? '1' : '0'
}
return { code: 0, stdout, stderr: '' }
})
return bootAndroidDevice(runner, sdk, 'Wanted', {
bootTimeoutMs: 30,
pollIntervalMs: 10,
sleep: async () => {}
})
}
describe('Android AVD boot identity', () => {
it('waits for the requested AVD when another emulator boots first', async () => {
await expect(
bootWithInventory([[], [other], [other, { ...target, booted: false }], [other, target]])
).resolves.toBe(target.serial)
await expect(bootWithInventory([[], [other]])).rejects.toThrow(
'AVD "Wanted" did not finish booting in time.'
)
})
})
@@ -64,10 +64,11 @@ async function waitForNewBootedSerial(
const fresh = (await listRunningAdbDevices(runner, sdk)).filter(
(device) => device.isEmulator && !known.has(device.serial)
)
for (const device of fresh) {
const booted = await runner(sdk.adb, bootCompletedArgs(device.serial))
const serial = await findRunningAvdSerial(runner, sdk, avdName, fresh)
if (serial) {
const booted = await runner(sdk.adb, bootCompletedArgs(serial))
if (isBootCompleted(booted.stdout)) {
return device.serial
return serial
}
}
await options.sleep(options.pollIntervalMs)
@@ -235,6 +235,9 @@ describe('AndroidEmulatorBackend', () => {
if (binary === SDK.adb && a === '-s emulator-5556 shell getprop sys.boot_completed') {
return ok('1')
}
if (binary === SDK.adb && a === '-s emulator-5556 emu avd name') {
return ok('Pixel_Tablet\nOK\n')
}
if (binary === SDK.emulator && a === '-list-avds') {
return ok('Pixel_Tablet')
}