Skip to content

Commit 79c8bc7

Browse files
committed
feat: support iOS simulator camera videos
1 parent af5eeb6 commit 79c8bc7

29 files changed

Lines changed: 693 additions & 19 deletions

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@
159159
"!android-snapshot-helper/dist/*.idsig",
160160
"android-multitouch-helper/dist",
161161
"!android-multitouch-helper/dist/*.idsig",
162+
"third_party/serve-sim-camera",
162163
"src/platforms/linux/atspi-dump.py",
163164
"skills",
164165
"server.json",

src/__tests__/client.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,14 @@ test('apps.open resolves session device identifiers from open response', async (
6666
const result = await client.apps.open({
6767
app: 'Settings',
6868
platform: 'ios',
69+
cameraVideo: './fixtures/back.mp4',
6970
relaunch: true,
7071
});
7172

7273
assert.equal(setup.calls.length, 1);
7374
assert.equal(setup.calls[0]?.command, 'open');
7475
assert.deepEqual(setup.calls[0]?.positionals, ['Settings']);
76+
assert.equal(setup.calls[0]?.flags?.cameraVideo, './fixtures/back.mp4');
7577
assert.equal(result.identifiers.session, 'qa');
7678
assert.equal(result.identifiers.deviceId, 'SIM-001');
7779
assert.equal(result.identifiers.udid, 'SIM-001');

src/backend.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ export type BackendOpenTarget = {
194194
};
195195

196196
export type BackendOpenOptions = {
197+
cameraVideo?: string;
197198
launchArgs?: string[];
198199
relaunch?: boolean;
199200
};

src/client-normalizers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ export function buildFlags(options: InternalRequestOptions): CommandFlags {
277277
androidDeviceAllowlist: options.androidDeviceAllowlist,
278278
surface: options.surface,
279279
activity: options.activity,
280+
cameraVideo: options.cameraVideo,
280281
launchConsole: options.launchConsole,
281282
launchArgs: options.launchArgs,
282283
relaunch: options.relaunch,

src/client-types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ export type AppOpenOptions = AgentDeviceRequestOverrides &
189189
url?: string;
190190
surface?: SessionSurface;
191191
activity?: string;
192+
cameraVideo?: string;
192193
launchConsole?: string;
193194
launchArgs?: string[];
194195
relaunch?: boolean;
@@ -555,6 +556,7 @@ type RepeatedPressOptions = {
555556

556557
export type DeviceBootOptions = DeviceCommandBaseOptions & {
557558
headless?: boolean;
559+
cameraVideo?: string;
558560
cameraFront?: string;
559561
cameraBack?: string;
560562
};
@@ -882,6 +884,7 @@ export type InternalRequestOptions = AgentDeviceClientConfig &
882884
overlayRefs?: boolean;
883885
surface?: SessionSurface;
884886
activity?: string;
887+
cameraVideo?: string;
885888
launchConsole?: string;
886889
launchArgs?: string[];
887890
relaunch?: boolean;

src/commands/apps.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const MAX_APP_PUSH_PAYLOAD_BYTES = 8 * 1024;
2525

2626
export type OpenAppCommandOptions = CommandContext &
2727
BackendOpenTarget & {
28+
cameraVideo?: string;
2829
launchArgs?: string[];
2930
relaunch?: boolean;
3031
};
@@ -105,6 +106,7 @@ export const openAppCommand: RuntimeCommand<OpenAppCommandOptions, OpenAppComman
105106
toAppBackendContext(runtime, options),
106107
target,
107108
{
109+
...(options.cameraVideo !== undefined ? { cameraVideo: options.cameraVideo } : {}),
108110
launchArgs: options.launchArgs,
109111
relaunch: options.relaunch,
110112
},

src/commands/cli-grammar/apps.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export const appCliReaders = {
4343
url: positionals[1],
4444
surface: flags.surface,
4545
activity: flags.activity,
46+
cameraVideo: flags.cameraVideo,
4647
launchConsole: flags.launchConsole,
4748
launchArgs: flags.launchArgs,
4849
relaunch: flags.relaunch,

src/commands/client-command-metadata.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export const clientCommandMetadata = [
5656
url: stringField('Optional URL passed with an app shell.'),
5757
surface: enumField(SESSION_SURFACES),
5858
activity: stringField('Android activity name.'),
59+
cameraVideo: stringField('iOS simulator video file path injected as the app camera stream.'),
5960
launchConsole: stringField('Launch console mode.'),
6061
launchArgs: stringArrayField(
6162
'Launch arguments forwarded verbatim to the platform launch command.',

src/core/__tests__/dispatch-open.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,52 @@ test('dispatch open rejects launch arguments without an app target', async () =>
7777
);
7878
});
7979

80+
test('dispatch open rejects camera video without an app target', async () => {
81+
await assert.rejects(
82+
() => dispatchCommand(IOS_SIMULATOR, 'open', [], undefined, { cameraVideo: './back.mp4' }),
83+
(error: unknown) => {
84+
assert.equal(error instanceof AppError, true);
85+
assert.equal((error as AppError).code, 'INVALID_ARGS');
86+
assert.match((error as AppError).message, /requires an app target/i);
87+
return true;
88+
},
89+
);
90+
});
91+
92+
test('dispatch open forwards iOS simulator camera video to openIosApp', async () => {
93+
await dispatchCommand(IOS_SIMULATOR, 'open', ['com.example.app'], undefined, {
94+
cameraVideo: '/tmp/back.mp4',
95+
});
96+
97+
assert.equal(mockOpenIosApp.mock.calls.length, 1);
98+
assert.equal(mockOpenIosApp.mock.calls[0]?.[0], IOS_SIMULATOR);
99+
assert.equal(mockOpenIosApp.mock.calls[0]?.[1], 'com.example.app');
100+
assert.equal(mockOpenIosApp.mock.calls[0]?.[2]?.cameraVideo, '/tmp/back.mp4');
101+
});
102+
103+
test('dispatch open rejects camera video outside iOS simulator', async () => {
104+
const device: DeviceInfo = {
105+
platform: 'android',
106+
id: 'emulator-5554',
107+
name: 'Pixel',
108+
kind: 'emulator',
109+
booted: true,
110+
};
111+
112+
await assert.rejects(
113+
() =>
114+
dispatchCommand(device, 'open', ['com.example.app'], undefined, {
115+
cameraVideo: './back.mp4',
116+
}),
117+
(error: unknown) => {
118+
assert.equal(error instanceof AppError, true);
119+
assert.equal((error as AppError).code, 'UNSUPPORTED_OPERATION');
120+
assert.match((error as AppError).message, /iOS simulators/i);
121+
return true;
122+
},
123+
);
124+
});
125+
80126
test('dispatch open forwards Android launch arguments to openAndroidApp', async () => {
81127
const device: DeviceInfo = {
82128
platform: 'android',

src/core/dispatch-context.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export type DispatchContext = ScreenshotDispatchFlags & {
3333
requestId?: string;
3434
appBundleId?: string;
3535
activity?: string;
36+
cameraVideo?: string;
3637
launchConsole?: string;
3738
launchArgs?: string[];
3839
clearAppState?: boolean;

0 commit comments

Comments
 (0)