Skip to content

Commit 6027610

Browse files
fix(webdriverio): fix mobile command argument mismatches for Appium 3 compatibility (#15154)
1 parent 974fb5e commit 6027610

16 files changed

Lines changed: 666 additions & 154 deletions

packages/webdriverio/src/commands/mobile/closeApp.ts

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,60 @@ import { isUnknownMethodError, logAppiumDeprecationWarning } from '../../utils/m
22

33
/**
44
*
5-
* Close the currently open app on the device.
5+
* Close a specific app or the currently active app on the device.
66
*
77
* > **Note:** Falls back to the deprecated Appium 2 protocol endpoint if the driver does not support the `mobile:` execute method.
88
*
9+
* If no `bundleId` (iOS) or `appId` (Android) is provided, the command will automatically detect and close the currently active app.
10+
*
911
* <example>
1012
:closeApp.js
11-
it('should close the current app', async () => {
13+
it('should close the currently active app', async () => {
14+
// Automatically close the currently active app
1215
await browser.closeApp()
1316
})
17+
it('should close a specific iOS app by bundleId', async () => {
18+
// iOS: close a specific app using its bundle ID
19+
await browser.closeApp({ bundleId: 'com.example.myapp' })
20+
})
21+
it('should close a specific Android app by appId', async () => {
22+
// Android: close a specific app using its package name
23+
await browser.closeApp({ appId: 'com.example.myapp' })
24+
})
1425
* </example>
1526
*
27+
* @param {object} [options] Options for closing the app (optional)
28+
* @param {string} [options.bundleId] The bundle ID of the iOS app to close. If not provided, the currently active app is closed. <br /><strong>iOS-ONLY</strong>
29+
* @param {string} [options.appId] The package name of the Android app to close. If not provided, the currently active app is closed. <br /><strong>ANDROID-ONLY</strong>
30+
*
1631
* @support ["ios","android"]
1732
*/
1833
export async function closeApp(
19-
this: WebdriverIO.Browser
34+
this: WebdriverIO.Browser,
35+
options?: {
36+
bundleId?: string
37+
appId?: string
38+
}
2039
) {
2140
const browser = this
2241

2342
if (!browser.isMobile) {
2443
throw new Error('The `closeApp` command is only available for mobile platforms.')
2544
}
2645

46+
let terminateArgs: Record<string, string>
47+
48+
if (browser.isIOS) {
49+
const bundleId = options?.bundleId
50+
?? (await browser.execute('mobile: activeAppInfo') as { bundleId: string }).bundleId
51+
terminateArgs = { bundleId }
52+
} else {
53+
const appId = options?.appId ?? await browser.getCurrentPackage()
54+
terminateArgs = { appId }
55+
}
56+
2757
try {
28-
return await browser.execute('mobile: terminateApp', {})
58+
return await browser.execute('mobile: terminateApp', terminateArgs)
2959
} catch (err: unknown) {
3060
if (!isUnknownMethodError(err)) {
3161
throw err

packages/webdriverio/src/commands/mobile/deepLink.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,23 @@
1414
// Or if you want to have it "cross-platform" you can use it like this
1515
await browser.deepLink('wdio://drag', browser.isIOS ? 'org.reactjs.native.example.wdiodemoapp' : 'com.wdiodemoapp');
1616
})
17+
it('should open a deep link without waiting for the app to launch (Android)', async () => {
18+
// Android-only: do not wait for the app to launch after opening the deep link
19+
await browser.deepLink('wdio://drag', 'com.wdiodemoapp', false)
20+
})
1721
* </example>
1822
*
19-
* @param {string} link The deep link URL that should be opened in the mobile app. It should be a valid deep link URL (e.g. `myapp://path`). If it's a universal deep link, which can be used for iOS, use the `browser.url("your-url")`-method.
20-
* @param {string} appIdentifier The value of the `package` (Android) or `bundleId` (iOS) of the app that the deep link should open.
23+
* @param {string} link The deep link URL that should be opened in the mobile app. It should be a valid deep link URL (e.g. `myapp://path`). If it's a universal deep link, which can be used for iOS, use the `browser.url("your-url")`-method.
24+
* @param {string} appIdentifier The value of the `package` (Android) or `bundleId` (iOS) of the app that the deep link should open.
25+
* @param {boolean} [waitForLaunch] Whether to wait for the app to launch after opening the deep link. Default is `true`. <br /><strong>ANDROID-ONLY</strong>
2126
*
2227
* @support ["ios","android"]
2328
*/
2429
export async function deepLink(
2530
this: WebdriverIO.Browser,
2631
link: string,
27-
appIdentifier: string
32+
appIdentifier: string,
33+
waitForLaunch?: boolean
2834
) {
2935
const browser = this
3036

@@ -43,10 +49,16 @@ export async function deepLink(
4349
throw new Error(`When using a deep link URL for ${mobileOS}, you need to provide the \`${identifierValue}\` of the app that the deep link should open.`)
4450
}
4551

46-
return browser.execute('mobile:deepLink', {
52+
const args: Record<string, unknown> = {
4753
url: link,
4854
[browser.isIOS ? 'bundleId' : 'package']: appIdentifier,
49-
})
55+
}
56+
57+
if (!browser.isIOS && waitForLaunch !== undefined) {
58+
args.waitForLaunch = waitForLaunch
59+
}
60+
61+
return browser.execute('mobile:deepLink', args)
5062
}
5163

5264
function isDeepLinkUrl(link: string): boolean {

packages/webdriverio/src/commands/mobile/gsmSignal.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,27 @@ import { isUnknownMethodError, logAppiumDeprecationWarning } from '../../utils/m
22

33
/**
44
*
5-
* Set the GSM signal strength on the Android emulator. Value must be in range [0, 4] where 0 is
6-
* no signal and 4 is full signal.
5+
* Set the GSM signal strength on the Android emulator.
76
*
87
* > **Note:** Falls back to the deprecated Appium 2 protocol endpoint if the driver does not support the `mobile:` execute method.
98
*
109
* <example>
1110
:gsmSignal.js
1211
it('should set GSM signal to full strength', async () => {
13-
await browser.gsmSignal('4')
12+
await browser.gsmSignal(4)
13+
})
14+
it('should set GSM signal to no signal', async () => {
15+
await browser.gsmSignal(0)
1416
})
1517
* </example>
1618
*
17-
* @param {string} signalStrength The signal strength to set (0–4, where 0 is none and 4 is full)
19+
* @param {number} signalStrength The signal strength to set. Accepted values: `0` (no signal), `1` (very poor), `2` (poor), `3` (moderate), `4` (good/full)
1820
*
1921
* @support ["android"]
2022
*/
2123
export async function gsmSignal(
2224
this: WebdriverIO.Browser,
23-
signalStrength: string
25+
signalStrength: number
2426
) {
2527
const browser = this
2628

@@ -40,6 +42,6 @@ export async function gsmSignal(
4042
}
4143

4244
logAppiumDeprecationWarning('mobile: gsmSignal', '/appium/device/gsm_signal')
43-
return browser.appiumGsmSignal(signalStrength)
45+
return browser.appiumGsmSignal(String(signalStrength))
4446
}
4547
}

packages/webdriverio/src/commands/mobile/launchApp.ts

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,80 @@ import { isUnknownMethodError, logAppiumDeprecationWarning } from '../../utils/m
22

33
/**
44
*
5-
* Launch the app configured in the current session capabilities.
5+
* Launch or activate an app on the device. If no `bundleId` (iOS) or `appId` (Android) is provided,
6+
* the command will automatically detect and activate the currently active app.
67
*
78
* > **Note:** Falls back to the deprecated Appium 2 protocol endpoint if the driver does not support the `mobile:` execute method.
89
*
910
* <example>
1011
:launchApp.js
11-
it('should launch the app', async () => {
12+
it('should launch a specific iOS app', async () => {
13+
// iOS: launch a specific app by bundle ID
14+
await browser.launchApp({ bundleId: 'com.example.myapp' })
15+
})
16+
it('should launch an iOS app with arguments and environment', async () => {
17+
// iOS: launch an app and pass arguments and environment variables
18+
await browser.launchApp({
19+
bundleId: 'com.example.myapp',
20+
arguments: ['-AppleLanguages', '(en)'],
21+
environment: { MY_ENV_VAR: 'value' }
22+
})
23+
})
24+
it('should launch a specific Android app', async () => {
25+
// Android: activate/launch a specific app by package name
26+
await browser.launchApp({ appId: 'com.example.myapp' })
27+
})
28+
it('should activate the currently active app', async () => {
29+
// Automatically detect and activate the current app
1230
await browser.launchApp()
1331
})
1432
* </example>
1533
*
34+
* @param {object} [options] Options for launching the app (optional)
35+
* @param {string} [options.bundleId] The bundle ID of the iOS app to launch. If not provided, the currently active app is used. <br /><strong>iOS-ONLY</strong>
36+
* @param {string|string[]} [options.arguments] Command line arguments to pass to the app on launch. <br /><strong>iOS-ONLY</strong>
37+
* @param {object} [options.environment] Environment variables to set when launching the app (key-value pairs). <br /><strong>iOS-ONLY</strong>
38+
* @param {string} [options.appId] The package name of the Android app to activate. If not provided, the currently active app is used. <br /><strong>ANDROID-ONLY</strong>
39+
*
1640
* @support ["ios","android"]
1741
*/
1842
export async function launchApp(
19-
this: WebdriverIO.Browser
43+
this: WebdriverIO.Browser,
44+
options?: {
45+
bundleId?: string
46+
arguments?: string | string[]
47+
environment?: Record<string, string>
48+
appId?: string
49+
}
2050
) {
2151
const browser = this
2252

2353
if (!browser.isMobile) {
2454
throw new Error('The `launchApp` command is only available for mobile platforms.')
2555
}
2656

27-
const mobileCmd = browser.isIOS ? 'mobile: launchApp' : 'mobile: activateApp'
57+
let mobileCmd: string
58+
let mobileArgs: Record<string, unknown>
59+
60+
if (browser.isIOS) {
61+
mobileCmd = 'mobile: launchApp'
62+
const bundleId = options?.bundleId
63+
?? (await browser.execute('mobile: activeAppInfo') as { bundleId: string }).bundleId
64+
mobileArgs = { bundleId }
65+
if (options?.arguments !== undefined) {
66+
mobileArgs.arguments = options.arguments
67+
}
68+
if (options?.environment !== undefined) {
69+
mobileArgs.environment = options.environment
70+
}
71+
} else {
72+
mobileCmd = 'mobile: activateApp'
73+
const appId = options?.appId ?? await browser.getCurrentPackage()
74+
mobileArgs = { appId }
75+
}
2876

2977
try {
30-
return await browser.execute(mobileCmd, {})
78+
return await browser.execute(mobileCmd, mobileArgs)
3179
} catch (err: unknown) {
3280
if (!isUnknownMethodError(err)) {
3381
throw err

packages/webdriverio/src/commands/mobile/sendKeyEvent.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ import { isUnknownMethodError, logAppiumDeprecationWarning } from '../../utils/m
1414
it('should send a key event', async () => {
1515
// Send the Home key event (keycode '3')
1616
await browser.sendKeyEvent('3')
17+
// Send Shift+A (keycode '29', metastate '1')
18+
await browser.sendKeyEvent('29', '1')
1719
})
1820
* </example>
1921
*
20-
* @param {string} keycode The keycode to send (as a string, e.g. '3' for Home)
21-
* @param {string} [metastate] Meta state to press the keycode with (e.g. '1' for Shift)
22+
* @param {string} keycode The keycode to send (as a string, e.g. `'3'` for Home). See [Android KeyEvent](https://developer.android.com/reference/android/view/KeyEvent) for all available keycodes.
23+
* @param {string} [metastate] The meta state to apply during the key press as a string (e.g. `'1'` for Shift). See [Android KeyEvent](https://developer.android.com/reference/android/view/KeyEvent) for all meta state values.
2224
*
2325
* @support ["android"]
2426
*/
@@ -38,7 +40,11 @@ export async function sendKeyEvent(
3840
}
3941

4042
try {
41-
return await browser.execute('mobile: pressKey', { keycode, metastate })
43+
const args: Record<string, number> = { keycode: parseInt(keycode, 10) }
44+
if (metastate !== undefined) {
45+
args.metastate = parseInt(metastate, 10)
46+
}
47+
return await browser.execute('mobile: pressKey', args)
4248
} catch (err: unknown) {
4349
if (!isUnknownMethodError(err)) {
4450
throw err

0 commit comments

Comments
 (0)