diff --git a/packages/platform-apple/src/core/__tests__/apps.test.ts b/packages/platform-apple/src/core/__tests__/apps.test.ts index 4104358338..16102604bb 100644 --- a/packages/platform-apple/src/core/__tests__/apps.test.ts +++ b/packages/platform-apple/src/core/__tests__/apps.test.ts @@ -260,9 +260,9 @@ test('openIosApp web URL on iOS device without app falls back to Safari', async 'launch', '--device', 'ios-device-1', - 'com.apple.mobilesafari', '--payload-url', 'https://example.com/path', + 'com.apple.mobilesafari', ], ]); }, @@ -290,9 +290,9 @@ test('openIosApp custom scheme on iOS device uses active app context', async () 'launch', '--device', 'ios-device-1', - 'com.example.app', '--payload-url', 'myapp://item/42', + 'com.example.app', ], ]); }, @@ -382,9 +382,9 @@ test('openIosApp appends launchArgs alongside --payload-url for iOS device deep 'launch', '--device', 'ios-device-1', - 'com.example.app', '--payload-url', 'myapp://item/42', + 'com.example.app', '--', '-Tracking', 'NO', @@ -562,9 +562,9 @@ test('openIosApp with app and URL on iOS device launches app bundle with payload 'launch', '--device', 'ios-device-1', - 'com.example.app', '--payload-url', 'myapp://screen/to', + 'com.example.app', ], ]); }, diff --git a/packages/platform-apple/src/core/__tests__/physical-device-coredevice.test.ts b/packages/platform-apple/src/core/__tests__/physical-device-coredevice.test.ts index 368c91cc22..6b9f1ab3d2 100644 --- a/packages/platform-apple/src/core/__tests__/physical-device-coredevice.test.ts +++ b/packages/platform-apple/src/core/__tests__/physical-device-coredevice.test.ts @@ -4,6 +4,7 @@ import path from 'node:path'; import { test } from 'vitest'; import type { DeviceInfo } from '@agent-device/kernel/device'; import { + launchCoreDeviceApp, parseIosDeviceDetailsPayload, readIosDeviceReadiness, resolveIosReadyHint, @@ -15,6 +16,7 @@ import { } from '../devicectl.ts'; import { resolveIosPhysicalDeviceControl } from '../physical-device-control.ts'; import { createLocalAppleToolProvider, withAppleToolProvider } from '../tool-provider.ts'; +import { withFakeAppleTool } from '../../__tests__/fake-apple-tool.ts'; /** * `xcrun devicectl device info details` is the one tool that answers what a device thinks of itself, @@ -29,6 +31,59 @@ const DEVICE_INFO_DETAILS_CAPTURE = JSON.parse( ), ) as unknown; +test('launchCoreDeviceApp puts --payload-url before the bundle ID', async () => { + await withFakeAppleTool( + () => '', + async ({ calls }) => { + await launchCoreDeviceApp(IOS_DEVICE, 'com.example.app', { + payloadUrl: 'myapp://item/42', + }); + assert.deepEqual(calls, [ + [ + 'devicectl', + 'device', + 'process', + 'launch', + '--device', + IOS_DEVICE.id, + '--payload-url', + 'myapp://item/42', + 'com.example.app', + ], + ]); + }, + { device: IOS_DEVICE }, + ); +}); + +test('launchCoreDeviceApp keeps launch args after the bundle ID, separated by --', async () => { + await withFakeAppleTool( + () => '', + async ({ calls }) => { + await launchCoreDeviceApp(IOS_DEVICE, 'com.example.app', { + payloadUrl: 'myapp://item/42', + launchArgs: ['--debug'], + }); + assert.deepEqual(calls, [ + [ + 'devicectl', + 'device', + 'process', + 'launch', + '--device', + IOS_DEVICE.id, + '--payload-url', + 'myapp://item/42', + 'com.example.app', + '--', + '--debug', + ], + ]); + }, + { device: IOS_DEVICE }, + ); +}); + test('parseIosDeviceDetailsPayload reads direct and nested tunnel state', () => { assert.equal( parseIosDeviceDetailsPayload({ diff --git a/packages/platform-apple/src/core/physical-device-coredevice.ts b/packages/platform-apple/src/core/physical-device-coredevice.ts index ec983782cb..7a78065003 100644 --- a/packages/platform-apple/src/core/physical-device-coredevice.ts +++ b/packages/platform-apple/src/core/physical-device-coredevice.ts @@ -21,10 +21,13 @@ export async function launchCoreDeviceApp( bundleId: string, options: { payloadUrl?: string; launchArgs?: string[] } = {}, ): Promise { - const args = ['device', 'process', 'launch', '--device', device.id, bundleId]; + const args = ['device', 'process', 'launch', '--device', device.id]; if (options.payloadUrl) { + // `devicectl` treats everything after the bundle ID as app argv, so + // `--payload-url` must precede it to be honored as a launch option. args.push('--payload-url', options.payloadUrl); } + args.push(bundleId); if (options.launchArgs && options.launchArgs.length > 0) { // `devicectl` uses Swift ArgumentParser; preserve app-owned leading dashes. args.push('--', ...options.launchArgs); diff --git a/src/daemon/app-event-delivery.test.ts b/src/daemon/app-event-delivery.test.ts index 9b687d11cf..a3ccd68d85 100644 --- a/src/daemon/app-event-delivery.test.ts +++ b/src/daemon/app-event-delivery.test.ts @@ -201,9 +201,9 @@ test('trigger-app-event supports iOS device path and prefers iOS template', asyn 'launch', '--device', 'ios-device-1', - 'com.example.app', '--payload-url', expectedUrl, + 'com.example.app', ]); } finally { process.env.PATH = previousPath;