diff --git a/src/platforms/apple/core/__tests__/physical-device-control.test.ts b/src/platforms/apple/core/__tests__/physical-device-control.test.ts index 1e401fce7..946127184 100644 --- a/src/platforms/apple/core/__tests__/physical-device-control.test.ts +++ b/src/platforms/apple/core/__tests__/physical-device-control.test.ts @@ -5,6 +5,7 @@ import path from 'node:path'; import { test } from 'vitest'; import { IOS_DEVICE as SHARED_IOS_DEVICE } from '../../../../__tests__/test-utils/index.ts'; import type { DeviceInfo } from '@agent-device/kernel/device'; +import type { AppError } from '@agent-device/kernel/errors'; import { createAppleInteractor } from '../../interactor.ts'; import { installIosApp } from '../app-install.ts'; import { closeIosApp, openIosApp } from '../app-launch.ts'; @@ -47,11 +48,15 @@ test('XCTest readiness uses xcdevice instead of devicectl', async () => { args: ['xcdevice', 'wait', '--both', '--timeout=15', XCTEST_IOS_DEVICE.id], }, ]); - assert.deepEqual( - await resolveIosPhysicalDeviceControl(XCTEST_IOS_DEVICE).resolveRunnerTransport( - XCTEST_IOS_DEVICE, - ), - { kind: 'usbmux' }, + // An XCTest-backed device has no CoreDevice tunnel, and the route resolver + // never asks it for one: it returns a usbmux route before reaching this. + await assert.rejects( + async () => + await resolveIosPhysicalDeviceControl(XCTEST_IOS_DEVICE).resolveTunnel(XCTEST_IOS_DEVICE), + (error: unknown) => { + assert.equal((error as AppError).code, 'UNSUPPORTED_OPERATION'); + return true; + }, ); }); diff --git a/src/platforms/apple/core/physical-device-control.ts b/src/platforms/apple/core/physical-device-control.ts index 732f595e9..673e12349 100644 --- a/src/platforms/apple/core/physical-device-control.ts +++ b/src/platforms/apple/core/physical-device-control.ts @@ -32,9 +32,12 @@ import { import { runXcrun } from './tool-provider.ts'; export type IosPhysicalDeviceBackend = 'coredevice' | 'xctest'; -export type IosPhysicalDeviceRunnerTransport = - | { kind: 'network'; tunnelIp: string | null } - | { kind: 'usbmux' }; +/** + * Only the CoreDevice tunnel address is resolved here. Which transport a runner + * command uses is decided by the route resolver, which reaches this at all only + * after usbmux has reported the device unattached. + */ +export type IosPhysicalDeviceTunnel = { tunnelIp: string | null }; type IosPhysicalDeviceLaunchOptions = { payloadUrl?: string; @@ -75,10 +78,7 @@ export type IosPhysicalDeviceControl = { outPath: string, timeoutMs?: number, ): Promise; - resolveRunnerTransport( - device: DeviceInfo, - timeoutBudgetMs?: number, - ): Promise; + resolveTunnel(device: DeviceInfo, timeoutBudgetMs?: number): Promise; }; const CONTROLS: Record = { @@ -94,8 +94,7 @@ const CONTROLS: Record = { resolveAppProcesses: resolveCoreDeviceAppProcesses, captureScreenshot: captureCoreDeviceScreenshot, copyRunnerFile: copyCoreDeviceRunnerFile, - resolveRunnerTransport: async (device, timeoutBudgetMs) => ({ - kind: 'network', + resolveTunnel: async (device, timeoutBudgetMs) => ({ tunnelIp: await resolveCoreDeviceTunnelIp(device, timeoutBudgetMs), }), }, @@ -111,7 +110,7 @@ const CONTROLS: Record = { resolveAppProcesses: rejectXctestProcessLookup, captureScreenshot: captureXctestDeviceScreenshot, copyRunnerFile: rejectXctestRunnerFileCopy, - resolveRunnerTransport: async () => ({ kind: 'usbmux' }), + resolveTunnel: rejectXctestTunnelLookup, }, }; @@ -159,6 +158,18 @@ async function rejectXctestProcessLookup(device: DeviceInfo): Promise { ); } +async function rejectXctestTunnelLookup(device: DeviceInfo): Promise { + throw new AppError( + 'UNSUPPORTED_OPERATION', + 'XCTest-backed physical iOS devices have no CoreDevice tunnel.', + { + deviceId: device.id, + backend: 'xctest', + hint: 'Connect the device by cable so it is reachable through usbmux.', + }, + ); +} + async function rejectXctestRunnerFileCopy(device: DeviceInfo): Promise { throw new AppError( 'UNSUPPORTED_OPERATION', diff --git a/src/platforms/apple/core/runner/runner-command-route.ts b/src/platforms/apple/core/runner/runner-command-route.ts index 84fc624be..c2c5e608f 100644 --- a/src/platforms/apple/core/runner/runner-command-route.ts +++ b/src/platforms/apple/core/runner/runner-command-route.ts @@ -61,11 +61,7 @@ export function createRunnerCommandRouteResolver(device: DeviceInfo, port: numbe return buildNetworkRoute(device, port, requestTunnelIp, false); } } - const transport = await control.resolveRunnerTransport(device, timeoutBudgetMs); - if (transport.kind === 'usbmux') { - return buildUsbmuxRoute(device, port); - } - const tunnelIp = transport.tunnelIp; + const { tunnelIp } = await control.resolveTunnel(device, timeoutBudgetMs); requestTunnelIp = tunnelIp; if (tunnelIp) writeDeviceTunnelIpCache(device.id, tunnelIp); return buildNetworkRoute(device, port, tunnelIp, false);