Skip to content

Commit 5cb02a5

Browse files
committed
fix: reject unsupported launch args openings
1 parent 3b8959b commit 5cb02a5

7 files changed

Lines changed: 55 additions & 26 deletions

File tree

src/commands/client-command-contracts.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ export const clientCommandDefinitions = [
8181
surface: enumField(SURFACE_VALUES),
8282
activity: stringField('Android activity name.'),
8383
launchConsole: stringField('Launch console mode.'),
84-
launchArgs: stringArrayField('iOS launch arguments forwarded verbatim to the app process.'),
84+
launchArgs: stringArrayField(
85+
'Launch arguments forwarded verbatim to the platform launch command.',
86+
),
8587
relaunch: booleanField('Force relaunch.'),
8688
saveScript: jsonSchemaField<boolean | string>({ oneOf: [booleanSchema(), stringSchema()] }),
8789
noRecord: booleanField('Do not record this action.'),

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

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ vi.mock('../../platforms/ios/apps.ts', async (importOriginal) => {
2020
});
2121

2222
vi.mock('../../platforms/android/app-lifecycle.ts', async (importOriginal) => {
23-
const actual =
24-
await importOriginal<typeof import('../../platforms/android/app-lifecycle.ts')>();
23+
const actual = await importOriginal<typeof import('../../platforms/android/app-lifecycle.ts')>();
2524
return {
2625
...actual,
2726
openAndroidApp: vi.fn(async () => {}),
@@ -58,6 +57,18 @@ test('dispatch open rejects URL as first argument when second URL is provided',
5857
);
5958
});
6059

60+
test('dispatch open rejects launch arguments without an app target', async () => {
61+
await assert.rejects(
62+
() => dispatchCommand(IOS_SIMULATOR, 'open', [], undefined, { launchArgs: ['-Flag'] }),
63+
(error: unknown) => {
64+
assert.equal(error instanceof AppError, true);
65+
assert.equal((error as AppError).code, 'INVALID_ARGS');
66+
assert.match((error as AppError).message, /requires an app target/i);
67+
return true;
68+
},
69+
);
70+
});
71+
6172
test('dispatch open forwards Android launch arguments to openAndroidApp', async () => {
6273
const device: DeviceInfo = {
6374
platform: 'android',
@@ -79,6 +90,29 @@ test('dispatch open forwards Android launch arguments to openAndroidApp', async
7990
assert.deepEqual(optionsArg.launchArgs, ['--es', 'KEY', 'value']);
8091
});
8192

93+
test('dispatch open rejects launch arguments on Linux', async () => {
94+
const device: DeviceInfo = {
95+
platform: 'linux',
96+
id: 'linux-local',
97+
name: 'Linux',
98+
kind: 'device',
99+
booted: true,
100+
};
101+
102+
await assert.rejects(
103+
() =>
104+
dispatchCommand(device, 'open', ['org.example.App'], undefined, {
105+
launchArgs: ['--fixture', 'demo'],
106+
}),
107+
(error: unknown) => {
108+
assert.equal(error instanceof AppError, true);
109+
assert.equal((error as AppError).code, 'UNSUPPORTED_OPERATION');
110+
assert.match((error as AppError).message, /Linux/i);
111+
return true;
112+
},
113+
);
114+
});
115+
82116
test('dispatch open clears Maestro iOS simulator state and launches once', async () => {
83117
const result = await dispatchCommand(IOS_SIMULATOR, 'open', ['com.example.app'], undefined, {
84118
clearAppState: true,

src/core/dispatch.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,19 +181,26 @@ async function handleOpenCommand(
181181
const app = positionals[0];
182182
const url = positionals[1];
183183
const launchConsole = context?.launchConsole;
184+
const launchArgs = context?.launchArgs;
184185
if (positionals.length > 2) {
185186
throw new AppError('INVALID_ARGS', 'open accepts at most two arguments: <app|url> [url]');
186187
}
187188
if (!app) {
188189
if (launchConsole) {
189190
throw new AppError('INVALID_ARGS', '--launch-console requires an app target');
190191
}
192+
if (launchArgs && launchArgs.length > 0) {
193+
throw new AppError('INVALID_ARGS', '--launch-args requires an app target');
194+
}
191195
await interactor.openDevice();
192196
return { app: null, ...successText('Opened device') };
193197
}
194198
if (launchConsole && (device.platform !== 'ios' || device.kind !== 'simulator')) {
195199
throw new AppError('UNSUPPORTED_OPERATION', LAUNCH_CONSOLE_IOS_SIMULATOR_ONLY_MESSAGE);
196200
}
201+
if (device.platform === 'linux' && launchArgs && launchArgs.length > 0) {
202+
throw new AppError('UNSUPPORTED_OPERATION', '--launch-args is not supported on Linux.');
203+
}
197204
if (url !== undefined) {
198205
if (isDeepLinkTarget(app)) {
199206
throw new AppError(
@@ -210,7 +217,7 @@ async function handleOpenCommand(
210217
await interactor.open(app, {
211218
activity: context?.activity,
212219
appBundleId: context?.appBundleId,
213-
launchArgs: context?.launchArgs,
220+
launchArgs,
214221
url,
215222
});
216223
return { app, url, ...successText(`Opened: ${app}`) };
@@ -237,7 +244,7 @@ async function handleOpenCommand(
237244
activity: context?.activity,
238245
appBundleId: context?.appBundleId,
239246
launchConsole,
240-
launchArgs: context?.launchArgs,
247+
launchArgs,
241248
});
242249
return { app, ...(launchConsole ? { launchConsole } : {}), ...successText(`Opened: ${app}`) };
243250
}

src/platforms/android/__tests__/index.test.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,10 +1205,7 @@ test('openAndroidApp appends launchArgs to am start when activity override is se
12051205
launchArgs: ['--es', 'mode', 'debug'],
12061206
});
12071207
const logged = await fs.readFile(argsLogPath, 'utf8');
1208-
assert.match(
1209-
logged,
1210-
/-n\ncom\.example\.app\/\.MainActivity\n--es\nmode\ndebug/,
1211-
);
1208+
assert.match(logged, /-n\ncom\.example\.app\/\.MainActivity\n--es\nmode\ndebug/);
12121209
},
12131210
);
12141211
});
@@ -1236,10 +1233,7 @@ test('openAndroidApp appends launchArgs to am start for deep link URL opens', as
12361233
launchArgs: ['--es', 'ref', 'campaign'],
12371234
});
12381235
const logged = await fs.readFile(argsLogPath, 'utf8');
1239-
assert.match(
1240-
logged,
1241-
/-d\nmyapp:\/\/item\/42\n--es\nref\ncampaign/,
1242-
);
1236+
assert.match(logged, /-d\nmyapp:\/\/item\/42\n--es\nref\ncampaign/);
12431237
},
12441238
);
12451239
});
@@ -1313,10 +1307,7 @@ test('openAndroidApp shell-quotes launchArgs containing JSON or shell metacharac
13131307
const logged = await fs.readFile(argsLogPath, 'utf8');
13141308
// `--es` and the safe extra key pass through unquoted; the JSON value
13151309
// is single-quoted so `adb shell` re-tokenisation preserves it.
1316-
assert.match(
1317-
logged,
1318-
/--es\nEXTRA_CONFIG\n'\{"a":"x #y;z&w","b":"path\/\*"\}'/,
1319-
);
1310+
assert.match(logged, /--es\nEXTRA_CONFIG\n'\{"a":"x #y;z&w","b":"path\/\*"\}'/);
13201311
},
13211312
);
13221313
});

src/platforms/android/app-lifecycle.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -433,10 +433,7 @@ async function openAndroidPackageActivity(
433433
? activity
434434
: `${packageName}/${activity.startsWith('.') ? activity : `.${activity}`}`;
435435
try {
436-
await runAndroidAdb(
437-
device,
438-
buildAndroidActivityLaunchArgs(component, launchCategory, options),
439-
);
436+
await runAndroidAdb(device, buildAndroidActivityLaunchArgs(component, launchCategory, options));
440437
} catch (error) {
441438
await maybeRethrowAndroidMissingPackageError(device, packageName, error);
442439
throw error;
@@ -481,10 +478,7 @@ async function openAndroidPackage(
481478
stderr: primaryResult.stderr,
482479
});
483480
}
484-
await runAndroidAdb(
485-
device,
486-
buildAndroidActivityLaunchArgs(component, launchCategory, options),
487-
);
481+
await runAndroidAdb(device, buildAndroidActivityLaunchArgs(component, launchCategory, options));
488482
}
489483

490484
function buildAndroidActivityLaunchArgs(

src/utils/__tests__/args.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,7 @@ test('usageForCommand documents open --launch-args', () => {
331331
if (help === null) throw new Error('Expected open help text');
332332
assert.match(help, /--launch-args <arg>/);
333333
assert.match(help, /forwarded verbatim/);
334+
assert.match(help, /Linux and macOS reject the flag/);
334335
});
335336

336337
test('parseArgs accepts install-from-source GitHub Actions artifact flag', () => {

src/utils/cli-flags.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ const FLAG_DEFINITIONS: readonly FlagDefinition[] = [
506506
multiple: true,
507507
usageLabel: '--launch-args <arg>',
508508
usageDescription:
509-
'open: repeatable launch argument forwarded verbatim to the iOS launch command (simctl launch positional args for simulators; devicectl process launch positional args for devices, after `--`). Currently supported only on iOS; Android and macOS reject the flag.',
509+
'open: repeatable launch argument forwarded verbatim to the platform launch command (iOS app process args; Android adb shell am start args). Linux and macOS reject the flag.',
510510
},
511511
{
512512
key: 'header',

0 commit comments

Comments
 (0)