From eae658621ba8c139cfcb26199e2a68e699642655 Mon Sep 17 00:00:00 2001 From: 5Hyeons Date: Wed, 22 Jul 2026 21:25:49 +0900 Subject: [PATCH] fix(emulator): support device-only iOS AX --- src/main/emulator/emulator-bridge.test.ts | 30 +++++++++++++++++++++++ src/main/emulator/emulator-bridge.ts | 11 ++++++--- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/main/emulator/emulator-bridge.test.ts b/src/main/emulator/emulator-bridge.test.ts index 3cb355bf9ee..1c57873feaf 100644 --- a/src/main/emulator/emulator-bridge.test.ts +++ b/src/main/emulator/emulator-bridge.test.ts @@ -383,6 +383,36 @@ describe('RuntimeEmulatorCommands attach lifecycle', () => { ) }) + it('reads iOS accessibility from an attached device without a worktree', async () => { + const tree = [{ type: 'Application', children: [] }] + netFetchMock.mockResolvedValue(new Response(JSON.stringify(tree), { status: 200 })) + listSimulatorDevicesMock.mockResolvedValue([ + { + name: 'iPhone attached', + udid: 'device-1', + state: 'Booted', + runtime: 'iOS 26.0' + } + ]) + const bridge = new EmulatorBridge() + bridge.registerActiveEmulator('wt-1', session('device-1'), { managed: true }) + const commands = new RuntimeEmulatorCommands({ + getEmulatorBridge: () => bridge, + resolveWorktreeSelector: vi.fn(async () => ({ id: 'wt-1' })), + getAuthoritativeWindow: () => ({ webContents: { send: vi.fn() } }) as never, + getSettings: () => ({ + mobileEmulatorEnabled: true, + mobileEmulatorDefaultDeviceUdid: null + }) + }) + + await expect(commands.emulatorAx({ device: 'device-1' })).resolves.toEqual(tree) + expect(netFetchMock).toHaveBeenCalledWith( + 'http://127.0.0.1:3100/device-1/ax', + expect.any(Object) + ) + }) + it('reports when the requested iOS device differs from the active session', async () => { listSimulatorDevicesMock.mockResolvedValue([ { diff --git a/src/main/emulator/emulator-bridge.ts b/src/main/emulator/emulator-bridge.ts index e1c1f36a299..f915634e10a 100644 --- a/src/main/emulator/emulator-bridge.ts +++ b/src/main/emulator/emulator-bridge.ts @@ -205,14 +205,17 @@ export class EmulatorBridge { return backend.accessibilityTree!(device) } const udid = await backend.resolveDeviceId(device) - const active = this.getActiveForWorktree(opts?.worktreeId) - if (active && active.deviceUdid !== udid) { + const worktreeId = opts?.worktreeId + const session = worktreeId + ? this.getActiveForWorktree(worktreeId) + : this.sessionRegistry.getSession(udid) + if (worktreeId && session && session.deviceUdid !== udid) { throw new EmulatorError( 'emulator_no_active', - `iOS simulator ${udid} is not active for this worktree (active: ${active.deviceUdid}); attach the requested simulator first.` + `iOS simulator ${udid} is not active for this worktree (active: ${session.deviceUdid}); attach the requested simulator first.` ) } - return backend.accessibilityTree!(udid, active?.axUrl) + return backend.accessibilityTree!(udid, session?.axUrl) }) }