diff --git a/.nx/version-plans/version-plan-1777795200000.md b/.nx/version-plans/version-plan-1777795200000.md new file mode 100644 index 00000000..f9b9aece --- /dev/null +++ b/.nx/version-plans/version-plan-1777795200000.md @@ -0,0 +1,5 @@ +--- +__default__: patch +--- + +Harness will stop trying to treat connected physical Android devices as emulators. If you run against an Android emulator and also have a physical device plugged in, Harness will now pick the emulator cleanly instead of failing during device resolution. diff --git a/packages/platform-android/src/__tests__/adb-id.test.ts b/packages/platform-android/src/__tests__/adb-id.test.ts new file mode 100644 index 00000000..3d6078c7 --- /dev/null +++ b/packages/platform-android/src/__tests__/adb-id.test.ts @@ -0,0 +1,51 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { getAdbId, isAdbIdEmulator } from '../adb-id.js'; +import * as adb from '../adb.js'; + +describe('adb id resolution', () => { + beforeEach(() => { + vi.restoreAllMocks(); + }); + + it('identifies emulator adb ids', () => { + expect(isAdbIdEmulator('emulator-5554')).toBe(true); + expect(isAdbIdEmulator('device-serial-001')).toBe(false); + }); + + it('skips non-emulator ids when resolving an emulator device', async () => { + vi.spyOn(adb, 'getDeviceIds').mockResolvedValue([ + 'device-serial-001', + 'emulator-5554', + ]); + vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Test_AVD_API_35'); + + await expect( + getAdbId({ + type: 'emulator', + name: 'Test_AVD_API_35', + }), + ).resolves.toBe('emulator-5554'); + }); + + it('resolves matching physical device by manufacturer and model', async () => { + vi.spyOn(adb, 'getDeviceIds').mockResolvedValue([ + 'device-serial-001', + 'emulator-5554', + ]); + vi.spyOn(adb, 'getDeviceInfo').mockImplementation(async (adbId) => { + const results: Record = { + 'device-serial-001': { manufacturer: 'Acme', model: 'Model A1' }, + 'emulator-5554': { manufacturer: 'Emulator', model: 'Emulator Model' }, + }; + return results[adbId] || null; + }); + + await expect( + getAdbId({ + type: 'physical', + manufacturer: 'acme', + model: 'model a1', + }), + ).resolves.toBe('device-serial-001'); + }); +}); diff --git a/packages/platform-android/src/adb-id.ts b/packages/platform-android/src/adb-id.ts index a24b938b..2d714d0f 100644 --- a/packages/platform-android/src/adb-id.ts +++ b/packages/platform-android/src/adb-id.ts @@ -16,6 +16,10 @@ export const getAdbId = async ( for (const adbId of adbIds) { if (isAndroidDeviceEmulator(device)) { + if (!isAdbIdEmulator(adbId)) { + continue; + } + const emulatorName = await adb.getEmulatorName(adbId); if (emulatorName === device.name) { diff --git a/packages/platform-android/vite.config.ts b/packages/platform-android/vite.config.ts new file mode 100644 index 00000000..c1ee0570 --- /dev/null +++ b/packages/platform-android/vite.config.ts @@ -0,0 +1,18 @@ +/// +import { defineConfig } from 'vite'; + +export default defineConfig(() => ({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/platform-android', + test: { + watch: false, + globals: true, + environment: 'node', + include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + coverage: { + reportsDirectory: './test-output/vitest/coverage', + provider: 'v8' as const, + }, + }, +}));