(params: {
avdName: string;
serial?: string;
timeoutMs?: number;
headless?: boolean;
})
| 422 | } |
| 423 | |
| 424 | export async function ensureAndroidEmulatorBooted(params: { |
| 425 | avdName: string; |
| 426 | serial?: string; |
| 427 | timeoutMs?: number; |
| 428 | headless?: boolean; |
| 429 | }): Promise<DeviceInfo> { |
| 430 | await ensureAndroidSdkPathConfigured(); |
| 431 | const requestedAvdName = params.avdName.trim(); |
| 432 | if (!requestedAvdName) { |
| 433 | throw new AppError('INVALID_ARGS', 'Android emulator boot requires a non-empty AVD name.'); |
| 434 | } |
| 435 | const timeoutMs = params.timeoutMs ?? ANDROID_EMULATOR_BOOT_TIMEOUT_MS; |
| 436 | |
| 437 | if (!(await whichCmd('adb'))) { |
| 438 | throw new AppError('TOOL_MISSING', 'adb not found in PATH'); |
| 439 | } |
| 440 | if (!(await whichCmd('emulator'))) { |
| 441 | throw new AppError('TOOL_MISSING', 'emulator not found in PATH'); |
| 442 | } |
| 443 | |
| 444 | const avdNames = await listAndroidAvdNames(); |
| 445 | const resolvedAvdName = resolveAndroidAvdName(avdNames, requestedAvdName); |
| 446 | if (!resolvedAvdName) { |
| 447 | throw new AppError('DEVICE_NOT_FOUND', `No Android emulator AVD named ${params.avdName}`, { |
| 448 | requestedAvdName, |
| 449 | availableAvds: avdNames, |
| 450 | hint: 'Run `emulator -list-avds` and pass an existing AVD name to --device.', |
| 451 | }); |
| 452 | } |
| 453 | |
| 454 | const startedAt = Date.now(); |
| 455 | const existing = findAndroidEmulatorByAvdName( |
| 456 | await listAndroidDevices(), |
| 457 | resolvedAvdName, |
| 458 | params.serial, |
| 459 | ); |
| 460 | const runningExisting = existing && isEmulatorSerial(existing.id) ? existing : undefined; |
| 461 | if (!runningExisting) { |
| 462 | const launchArgs = ['-avd', resolvedAvdName]; |
| 463 | if (params.headless) { |
| 464 | launchArgs.push('-no-window', '-no-audio'); |
| 465 | } |
| 466 | runCmdDetached('emulator', launchArgs); |
| 467 | } |
| 468 | |
| 469 | const discovered = |
| 470 | runningExisting ?? |
| 471 | (await waitForAndroidEmulatorByAvdName({ |
| 472 | avdName: resolvedAvdName, |
| 473 | serial: params.serial, |
| 474 | timeoutMs, |
| 475 | })); |
| 476 | |
| 477 | const elapsedMs = Date.now() - startedAt; |
| 478 | const remainingMs = Math.max(1_000, timeoutMs - elapsedMs); |
| 479 | await waitForAndroidBoot(discovered.id, remainingMs); |
| 480 | const refreshed = (await listAndroidDevices()).find((device) => device.id === discovered.id); |
| 481 | if (refreshed) { |
no test coverage detected