appium-xcuitest-driver 10.14.1 → 10.14.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/build/lib/commands/alert.d.ts +26 -31
- package/build/lib/commands/alert.d.ts.map +1 -1
- package/build/lib/commands/alert.js +20 -29
- package/build/lib/commands/alert.js.map +1 -1
- package/build/lib/commands/appearance.d.ts +7 -9
- package/build/lib/commands/appearance.d.ts.map +1 -1
- package/build/lib/commands/appearance.js +13 -19
- package/build/lib/commands/appearance.js.map +1 -1
- package/build/lib/commands/execute.d.ts +10 -22
- package/build/lib/commands/execute.d.ts.map +1 -1
- package/build/lib/commands/execute.js +12 -28
- package/build/lib/commands/execute.js.map +1 -1
- package/build/lib/commands/location.d.ts +8 -11
- package/build/lib/commands/location.d.ts.map +1 -1
- package/build/lib/commands/location.js +7 -15
- package/build/lib/commands/location.js.map +1 -1
- package/build/lib/commands/navigation.d.ts +14 -26
- package/build/lib/commands/navigation.d.ts.map +1 -1
- package/build/lib/commands/navigation.js +10 -18
- package/build/lib/commands/navigation.js.map +1 -1
- package/build/lib/commands/pcap.d.ts +18 -38
- package/build/lib/commands/pcap.d.ts.map +1 -1
- package/build/lib/commands/pcap.js +9 -14
- package/build/lib/commands/pcap.js.map +1 -1
- package/build/lib/commands/permissions.d.ts +15 -17
- package/build/lib/commands/permissions.d.ts.map +1 -1
- package/build/lib/commands/permissions.js +12 -18
- package/build/lib/commands/permissions.js.map +1 -1
- package/build/lib/commands/proxy-helper.d.ts +11 -11
- package/build/lib/commands/proxy-helper.d.ts.map +1 -1
- package/build/lib/commands/proxy-helper.js +15 -24
- package/build/lib/commands/proxy-helper.js.map +1 -1
- package/build/lib/commands/screenshots.d.ts +15 -9
- package/build/lib/commands/screenshots.d.ts.map +1 -1
- package/build/lib/commands/screenshots.js +13 -11
- package/build/lib/commands/screenshots.js.map +1 -1
- package/build/lib/commands/simctl.d.ts +16 -22
- package/build/lib/commands/simctl.d.ts.map +1 -1
- package/build/lib/commands/simctl.js +8 -17
- package/build/lib/commands/simctl.js.map +1 -1
- package/build/lib/commands/source.d.ts +10 -8
- package/build/lib/commands/source.d.ts.map +1 -1
- package/build/lib/commands/source.js +11 -14
- package/build/lib/commands/source.js.map +1 -1
- package/build/lib/commands/timeouts.d.ts +25 -32
- package/build/lib/commands/timeouts.d.ts.map +1 -1
- package/build/lib/commands/timeouts.js +18 -14
- package/build/lib/commands/timeouts.js.map +1 -1
- package/build/lib/driver.js +1 -1
- package/build/lib/driver.js.map +1 -1
- package/build/lib/execute-method-map.d.ts.map +1 -1
- package/build/lib/execute-method-map.js +0 -2
- package/build/lib/execute-method-map.js.map +1 -1
- package/lib/commands/alert.ts +98 -0
- package/lib/commands/appearance.ts +70 -0
- package/lib/commands/{execute.js → execute.ts} +41 -37
- package/lib/commands/{location.js → location.ts} +19 -22
- package/lib/commands/{navigation.js → navigation.ts} +23 -26
- package/lib/commands/{pcap.js → pcap.ts} +28 -28
- package/lib/commands/permissions.ts +90 -0
- package/lib/commands/{proxy-helper.js → proxy-helper.ts} +26 -26
- package/lib/commands/{screenshots.js → screenshots.ts} +24 -16
- package/lib/commands/{simctl.js → simctl.ts} +27 -21
- package/lib/commands/{source.js → source.ts} +23 -20
- package/lib/commands/timeouts.ts +95 -0
- package/lib/driver.ts +1 -1
- package/lib/execute-method-map.ts +0 -2
- package/npm-shrinkwrap.json +5 -5
- package/package.json +1 -1
- package/lib/commands/alert.js +0 -88
- package/lib/commands/appearance.js +0 -71
- package/lib/commands/permissions.js +0 -85
- package/lib/commands/timeouts.js +0 -68
|
@@ -2,14 +2,18 @@ import _ from 'lodash';
|
|
|
2
2
|
import {XCUITestDriver} from '../driver';
|
|
3
3
|
import {errors, errorFromCode, errorFromW3CJsonCode} from 'appium/driver';
|
|
4
4
|
import {util} from 'appium/support';
|
|
5
|
+
import type {Element, StringRecord} from '@appium/types';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Collect the response of an async script execution
|
|
8
|
-
* @this {XCUITestDriver}
|
|
9
9
|
* @deprecated
|
|
10
10
|
* @privateRemarks It's unclear what this is for. Don't use it.
|
|
11
11
|
*/
|
|
12
|
-
export async function receiveAsyncResponse(
|
|
12
|
+
export async function receiveAsyncResponse(
|
|
13
|
+
this: XCUITestDriver,
|
|
14
|
+
status: number | null | undefined,
|
|
15
|
+
value: any,
|
|
16
|
+
): Promise<void> {
|
|
13
17
|
this.log.debug(`Received async response: ${JSON.stringify(value)}`);
|
|
14
18
|
if (!util.hasValue(this.asyncPromise)) {
|
|
15
19
|
this.log.warn(
|
|
@@ -33,21 +37,22 @@ export async function receiveAsyncResponse(status, value) {
|
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
/**
|
|
36
|
-
* @template
|
|
37
|
-
* @
|
|
38
|
-
* @param
|
|
39
|
-
* @param {TArgs} [args]
|
|
40
|
-
* @this {XCUITestDriver}
|
|
41
|
-
* @returns {Promise<TReturn>}
|
|
40
|
+
* @template TReturn
|
|
41
|
+
* @param script - Either a script to run, or in the case of an Execute Method, the name of the script to execute.
|
|
42
|
+
* @param args
|
|
42
43
|
*/
|
|
43
|
-
export async function execute
|
|
44
|
+
export async function execute<TReturn = unknown>(
|
|
45
|
+
this: XCUITestDriver,
|
|
46
|
+
script: string,
|
|
47
|
+
args?: ExecuteMethodArgs,
|
|
48
|
+
): Promise<TReturn> {
|
|
44
49
|
// TODO: create a type that converts args to the parameters of the associated method using the `command` prop of `executeMethodMap`
|
|
45
50
|
script = script.trim().replace(/^mobile:\s*/, 'mobile: ');
|
|
46
51
|
if (isExecuteMethod(script)) {
|
|
47
|
-
const executeMethodArgs = preprocessExecuteMethodArgs(script, args);
|
|
52
|
+
const executeMethodArgs = preprocessExecuteMethodArgs(script, args as ExecuteMethodArgs | undefined);
|
|
48
53
|
return await this.executeMethod(script, [executeMethodArgs]);
|
|
49
54
|
} else if (this.isWebContext()) {
|
|
50
|
-
const atomsArgs = this.convertElementsForAtoms(
|
|
55
|
+
const atomsArgs = this.convertElementsForAtoms(args as readonly any[] | undefined);
|
|
51
56
|
const result = await this.executeAtom('execute_script', [script, atomsArgs]);
|
|
52
57
|
return this.cacheWebElements(result);
|
|
53
58
|
} else {
|
|
@@ -56,10 +61,13 @@ export async function execute(script, args) {
|
|
|
56
61
|
}
|
|
57
62
|
|
|
58
63
|
/**
|
|
59
|
-
* @this {XCUITestDriver}
|
|
60
64
|
* @group Mobile Web Only
|
|
61
65
|
*/
|
|
62
|
-
export async function executeAsync(
|
|
66
|
+
export async function executeAsync(
|
|
67
|
+
this: XCUITestDriver,
|
|
68
|
+
script: string,
|
|
69
|
+
args?: readonly any[],
|
|
70
|
+
): Promise<any> {
|
|
63
71
|
if (!this.isWebContext()) {
|
|
64
72
|
throw new errors.NotImplementedError();
|
|
65
73
|
}
|
|
@@ -77,16 +85,17 @@ export async function executeAsync(script, args) {
|
|
|
77
85
|
|
|
78
86
|
/**
|
|
79
87
|
* Checks if script expects a particular parameter (either optional or required).
|
|
80
|
-
* @template
|
|
81
|
-
* @param
|
|
82
|
-
* @param
|
|
88
|
+
* @template Script
|
|
89
|
+
* @param script - Script name
|
|
90
|
+
* @param param - Parameter name
|
|
83
91
|
* @returns {boolean}
|
|
84
92
|
*/
|
|
85
|
-
function executeMethodExpectsParam
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
let
|
|
93
|
+
function executeMethodExpectsParam<Script extends keyof typeof XCUITestDriver.executeMethodMap>(
|
|
94
|
+
script: Script,
|
|
95
|
+
param: string,
|
|
96
|
+
): boolean {
|
|
97
|
+
let required: ReadonlyArray<string> | undefined;
|
|
98
|
+
let optional: ReadonlyArray<string> | undefined;
|
|
90
99
|
const execMethodDef = XCUITestDriver.executeMethodMap[script];
|
|
91
100
|
if ('params' in execMethodDef) {
|
|
92
101
|
if ('required' in execMethodDef.params) {
|
|
@@ -101,24 +110,26 @@ function executeMethodExpectsParam(script, param) {
|
|
|
101
110
|
}
|
|
102
111
|
|
|
103
112
|
/**
|
|
104
|
-
* @param
|
|
113
|
+
* @param script
|
|
105
114
|
* @returns {script is keyof XCUITestDriver.executeMethodMap}
|
|
106
115
|
*/
|
|
107
|
-
function isExecuteMethod(script) {
|
|
116
|
+
function isExecuteMethod(script: string): script is keyof typeof XCUITestDriver.executeMethodMap {
|
|
108
117
|
return script in XCUITestDriver.executeMethodMap;
|
|
109
118
|
}
|
|
110
119
|
|
|
111
120
|
/**
|
|
112
121
|
* Massages the arguments going into an execute method.
|
|
113
|
-
* @param
|
|
114
|
-
* @param
|
|
115
|
-
* @returns {StringRecord<unknown>}
|
|
122
|
+
* @param script
|
|
123
|
+
* @param args
|
|
116
124
|
*/
|
|
117
|
-
function preprocessExecuteMethodArgs(
|
|
125
|
+
function preprocessExecuteMethodArgs(
|
|
126
|
+
script: keyof typeof XCUITestDriver.executeMethodMap,
|
|
127
|
+
args?: ExecuteMethodArgs,
|
|
128
|
+
): StringRecord<unknown> {
|
|
118
129
|
if (_.isArray(args)) {
|
|
119
130
|
args = _.first(args);
|
|
120
131
|
}
|
|
121
|
-
const executeMethodArgs =
|
|
132
|
+
const executeMethodArgs = (args ?? {}) as StringRecord<unknown>;
|
|
122
133
|
/**
|
|
123
134
|
* Renames the deprecated `element` key to `elementId`. Historically,
|
|
124
135
|
* all of the pre-Execute-Method-Map execute methods accepted an `element` _or_ and `elementId` param.
|
|
@@ -136,18 +147,11 @@ function preprocessExecuteMethodArgs(script, args) {
|
|
|
136
147
|
*/
|
|
137
148
|
if ('elementId' in executeMethodArgs && executeMethodExpectsParam(script, 'elementId')) {
|
|
138
149
|
executeMethodArgs.elementId = util.unwrapElement(
|
|
139
|
-
|
|
150
|
+
executeMethodArgs.elementId as Element<string> | string,
|
|
140
151
|
);
|
|
141
152
|
}
|
|
142
153
|
|
|
143
154
|
return executeMethodArgs;
|
|
144
155
|
}
|
|
145
156
|
|
|
146
|
-
|
|
147
|
-
* @template [T=any]
|
|
148
|
-
* @typedef {import('@appium/types').StringRecord<T>} StringRecord
|
|
149
|
-
*/
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* @typedef {readonly any[] | readonly [StringRecord] | Readonly<StringRecord>} ExecuteMethodArgs
|
|
153
|
-
*/
|
|
157
|
+
type ExecuteMethodArgs = readonly any[] | readonly [StringRecord<unknown>] | Readonly<StringRecord<unknown>>;
|
|
@@ -3,6 +3,10 @@ import {errors} from 'appium/driver';
|
|
|
3
3
|
import {util} from 'appium/support';
|
|
4
4
|
import {AuthorizationStatus} from './enum';
|
|
5
5
|
import { isIos17OrNewer } from '../utils';
|
|
6
|
+
import type {XCUITestDriver} from '../driver';
|
|
7
|
+
import type {Location} from '@appium/types';
|
|
8
|
+
import type {LocationWithAltitude, WDALocationInfo} from './types';
|
|
9
|
+
import type {Simulator} from 'appium-ios-simulator';
|
|
6
10
|
|
|
7
11
|
/**
|
|
8
12
|
* Returns location of the device under test.
|
|
@@ -17,12 +21,11 @@ import { isIos17OrNewer } from '../utils';
|
|
|
17
21
|
* "mobile:getSimulatedLocation" if the simulated location has been previously set
|
|
18
22
|
* "mobile:setSimulatedLocation" already.
|
|
19
23
|
*
|
|
20
|
-
* @returns
|
|
24
|
+
* @returns Location with altitude
|
|
21
25
|
* @throws {Error} If the device under test returns an error message.
|
|
22
26
|
* i.e.: tvOS returns unsupported error
|
|
23
|
-
* @this {XCUITestDriver}
|
|
24
27
|
*/
|
|
25
|
-
export async function getGeoLocation() {
|
|
28
|
+
export async function getGeoLocation(this: XCUITestDriver): Promise<LocationWithAltitude> {
|
|
26
29
|
// Currently we proxy the setGeoLocation to mobile:setSimulatedLocation for iOS 17+.
|
|
27
30
|
// It would be helpful to address to use "mobile:getSimulatedLocation" for iOS 17+.
|
|
28
31
|
if (isIos17OrNewer(this.opts)) {
|
|
@@ -41,9 +44,7 @@ export async function getGeoLocation() {
|
|
|
41
44
|
// '/wda/device/location' returns current device location information,
|
|
42
45
|
// but '/wda/simulatedLocation' returns `null` values until the WDA process
|
|
43
46
|
// sets a simulated location. After setting the value, both returns the same values.
|
|
44
|
-
const {authorizationStatus, latitude, longitude, altitude} =
|
|
45
|
-
await this.proxyCommand('/wda/device/location', 'GET')
|
|
46
|
-
);
|
|
47
|
+
const {authorizationStatus, latitude, longitude, altitude} = await this.proxyCommand('/wda/device/location', 'GET') as WDALocationInfo;
|
|
47
48
|
|
|
48
49
|
// '3' is 'Always' in the privacy
|
|
49
50
|
// https://developer.apple.com/documentation/corelocation/clauthorizationstatus
|
|
@@ -66,19 +67,21 @@ export async function getGeoLocation() {
|
|
|
66
67
|
* iOS 17+ real device environment will be via "mobile:setSimulatedLocation" as
|
|
67
68
|
* setting simulated location for XCTest session.
|
|
68
69
|
*
|
|
69
|
-
* @param
|
|
70
|
-
* @this {XCUITestDriver}
|
|
70
|
+
* @param location - Location with latitude and longitude
|
|
71
71
|
*/
|
|
72
|
-
export async function setGeoLocation(
|
|
73
|
-
|
|
72
|
+
export async function setGeoLocation(
|
|
73
|
+
this: XCUITestDriver,
|
|
74
|
+
location: Partial<Location>,
|
|
75
|
+
): Promise<Location> {
|
|
76
|
+
const {latitude, longitude} = location;
|
|
74
77
|
|
|
75
78
|
if (!util.hasValue(latitude) || !util.hasValue(longitude)) {
|
|
76
79
|
throw new errors.InvalidArgumentError(`Both latitude and longitude should be set`);
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
if (this.isSimulator()) {
|
|
80
|
-
await
|
|
81
|
-
return
|
|
83
|
+
await (this.device as Simulator).setGeolocation(`${latitude}`, `${longitude}`);
|
|
84
|
+
return {latitude, longitude, altitude: 0};
|
|
82
85
|
}
|
|
83
86
|
|
|
84
87
|
if (isIos17OrNewer(this.opts)) {
|
|
@@ -88,7 +91,7 @@ export async function setGeoLocation(location) {
|
|
|
88
91
|
const service = await services.startSimulateLocationService(this.opts.udid);
|
|
89
92
|
try {
|
|
90
93
|
service.setLocation(latitude, longitude);
|
|
91
|
-
} catch (e) {
|
|
94
|
+
} catch (e: any) {
|
|
92
95
|
throw this.log.errorWithException(
|
|
93
96
|
`Can't set the location on device '${this.opts.udid}'. Original error: ${e.message}`,
|
|
94
97
|
);
|
|
@@ -97,7 +100,7 @@ export async function setGeoLocation(location) {
|
|
|
97
100
|
}
|
|
98
101
|
}
|
|
99
102
|
|
|
100
|
-
return
|
|
103
|
+
return {latitude, longitude, altitude: 0};
|
|
101
104
|
}
|
|
102
105
|
|
|
103
106
|
/**
|
|
@@ -105,9 +108,8 @@ export async function setGeoLocation(location) {
|
|
|
105
108
|
*
|
|
106
109
|
* @throws {Error} If the device is simulator and iOS version is below 17,
|
|
107
110
|
* or 'resetLocation' raises an error.
|
|
108
|
-
* @this {XCUITestDriver}
|
|
109
111
|
*/
|
|
110
|
-
export async function mobileResetLocationService() {
|
|
112
|
+
export async function mobileResetLocationService(this: XCUITestDriver): Promise<void> {
|
|
111
113
|
if (isIos17OrNewer(this.opts)) {
|
|
112
114
|
this.log.info(`Proxying to mobile:resetSimulatedLocation method for iOS 17+`);
|
|
113
115
|
await this.mobileResetSimulatedLocation();
|
|
@@ -121,7 +123,7 @@ export async function mobileResetLocationService() {
|
|
|
121
123
|
const service = await services.startSimulateLocationService(this.opts.udid);
|
|
122
124
|
try {
|
|
123
125
|
service.resetLocation();
|
|
124
|
-
} catch (err) {
|
|
126
|
+
} catch (err: any) {
|
|
125
127
|
throw this.log.errorWithException(
|
|
126
128
|
`Failed to reset the location on the device on device '${this.opts.udid}'. ` +
|
|
127
129
|
`Original error: ${err.message}`,
|
|
@@ -131,8 +133,3 @@ export async function mobileResetLocationService() {
|
|
|
131
133
|
}
|
|
132
134
|
}
|
|
133
135
|
|
|
134
|
-
/**
|
|
135
|
-
* @typedef {import('../driver').XCUITestDriver} XCUITestDriver
|
|
136
|
-
* @typedef {import('./types').WDALocationInfo} WDALocationInfo
|
|
137
|
-
* @typedef {import('@appium/types').Location} Location
|
|
138
|
-
*/
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import {errors} from 'appium/driver';
|
|
2
2
|
import {waitForCondition} from 'asyncbox';
|
|
3
3
|
import { isTvOs } from '../utils';
|
|
4
|
+
import type {XCUITestDriver} from '../driver';
|
|
5
|
+
import type {Element} from '@appium/types';
|
|
4
6
|
|
|
5
7
|
// these two constitute the wait after closing a window
|
|
6
8
|
const CLOSE_WINDOW_TIMEOUT = 5000;
|
|
7
9
|
const CLOSE_WINDOW_INTERVAL = 100;
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
|
-
*
|
|
12
|
+
* Navigate back in the browser history or native app navigation.
|
|
11
13
|
*/
|
|
12
|
-
export async function back() {
|
|
14
|
+
export async function back(this: XCUITestDriver): Promise<void> {
|
|
13
15
|
if (!this.isWebContext()) {
|
|
14
16
|
await this.nativeBack();
|
|
15
17
|
} else {
|
|
@@ -18,18 +20,22 @@ export async function back() {
|
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
/**
|
|
21
|
-
*
|
|
23
|
+
* Navigate forward in the browser history.
|
|
22
24
|
*/
|
|
23
|
-
export async function forward() {
|
|
25
|
+
export async function forward(this: XCUITestDriver): Promise<void> {
|
|
24
26
|
if (!this.isWebContext()) {
|
|
27
|
+
// No-op for native context
|
|
28
|
+
return;
|
|
25
29
|
}
|
|
26
30
|
await this.mobileWebNav('forward');
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
/**
|
|
30
|
-
*
|
|
34
|
+
* Closes the current window in a web context.
|
|
35
|
+
*
|
|
36
|
+
* @returns Promise that resolves when the window is closed
|
|
31
37
|
*/
|
|
32
|
-
export async function closeWindow() {
|
|
38
|
+
export async function closeWindow(this: XCUITestDriver): Promise<any> {
|
|
33
39
|
if (!this.isWebContext()) {
|
|
34
40
|
throw new errors.NotImplementedError();
|
|
35
41
|
}
|
|
@@ -59,14 +65,16 @@ export async function closeWindow() {
|
|
|
59
65
|
*
|
|
60
66
|
* (Note: the version of Xcode must be 14.3+ and iOS must be 16.4+)
|
|
61
67
|
*
|
|
62
|
-
* @param
|
|
63
|
-
* @param
|
|
68
|
+
* @param url - the URL to be opened, e.g. `myscheme://yolo`
|
|
69
|
+
* @param bundleId - the application to open the given URL with. If not provided, then
|
|
64
70
|
* the application assigned by the operating system to handle URLs of the appropriate type
|
|
65
|
-
* @returns {Promise<void>}
|
|
66
71
|
* @since 4.17
|
|
67
|
-
* @this {XCUITestDriver}
|
|
68
72
|
*/
|
|
69
|
-
export async function mobileDeepLink(
|
|
73
|
+
export async function mobileDeepLink(
|
|
74
|
+
this: XCUITestDriver,
|
|
75
|
+
url: string,
|
|
76
|
+
bundleId?: string,
|
|
77
|
+
): Promise<void> {
|
|
70
78
|
return await this.proxyCommand('/url', 'POST', {
|
|
71
79
|
url,
|
|
72
80
|
bundleId,
|
|
@@ -74,9 +82,9 @@ export async function mobileDeepLink(url, bundleId) {
|
|
|
74
82
|
}
|
|
75
83
|
|
|
76
84
|
/**
|
|
77
|
-
*
|
|
85
|
+
* Navigate back in native app navigation by finding and clicking the back button.
|
|
78
86
|
*/
|
|
79
|
-
export async function nativeBack() {
|
|
87
|
+
export async function nativeBack(this: XCUITestDriver): Promise<void> {
|
|
80
88
|
if (isTvOs(this.opts.platformName)) {
|
|
81
89
|
this.log.debug(`Sending Menu button as back behavior in tvOS`);
|
|
82
90
|
return await this.mobilePressButton('Menu');
|
|
@@ -88,7 +96,7 @@ export async function nativeBack() {
|
|
|
88
96
|
'XCUIElementTypeNavigationBar',
|
|
89
97
|
false,
|
|
90
98
|
);
|
|
91
|
-
let dstButton
|
|
99
|
+
let dstButton: Element<string>;
|
|
92
100
|
const backButtons = await this.findNativeElementOrElements(
|
|
93
101
|
'-ios predicate string',
|
|
94
102
|
'type == "XCUIElementTypeButton" AND label == "Back"',
|
|
@@ -113,19 +121,8 @@ export async function nativeBack() {
|
|
|
113
121
|
}
|
|
114
122
|
|
|
115
123
|
await this.nativeClick(dstButton);
|
|
116
|
-
} catch (err) {
|
|
124
|
+
} catch (err: any) {
|
|
117
125
|
this.log.error(`Unable to find navigation bar and back button: ${err.message}`);
|
|
118
126
|
}
|
|
119
127
|
}
|
|
120
128
|
|
|
121
|
-
/**
|
|
122
|
-
* @typedef {import('../driver').XCUITestDriver} XCUITestDriver
|
|
123
|
-
*/
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* @typedef {Object} DeepLinkOptions
|
|
127
|
-
* @property {string} url The URL to be opened. This parameter is manadatory
|
|
128
|
-
* @property {string?} bundleId The bundle identifier of an application to open the
|
|
129
|
-
* given url with. If not provided then the default application for the given url scheme
|
|
130
|
-
* is going to be used.
|
|
131
|
-
*/
|
|
@@ -2,27 +2,29 @@ import { Pyidevice } from '../device/clients/py-ios-device-client';
|
|
|
2
2
|
import {fs, tempDir, util} from 'appium/support';
|
|
3
3
|
import {encodeBase64OrUpload} from '../utils';
|
|
4
4
|
import {errors} from 'appium/driver';
|
|
5
|
+
import type {XCUITestDriver} from '../driver';
|
|
6
|
+
import type {SubProcess} from 'teen_process';
|
|
5
7
|
|
|
6
8
|
const MAX_CAPTURE_TIME_SEC = 60 * 60 * 12;
|
|
7
9
|
const DEFAULT_EXT = '.pcap';
|
|
8
10
|
|
|
9
11
|
export class TrafficCapture {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
private mainProcess: SubProcess | null = null;
|
|
13
|
+
private readonly udid: string;
|
|
14
|
+
private readonly log: any;
|
|
15
|
+
private readonly resultPath: string;
|
|
16
|
+
|
|
17
|
+
constructor(udid: string, log: any, resultPath: string) {
|
|
13
18
|
this.udid = udid;
|
|
14
19
|
this.log = log;
|
|
15
20
|
this.resultPath = resultPath;
|
|
16
|
-
this.mainProcess = null;
|
|
17
21
|
}
|
|
18
22
|
|
|
19
|
-
async start(timeoutSeconds) {
|
|
20
|
-
this.mainProcess =
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}).collectPcap(this.resultPath)
|
|
25
|
-
);
|
|
23
|
+
async start(timeoutSeconds: number): Promise<void> {
|
|
24
|
+
this.mainProcess = await new Pyidevice({
|
|
25
|
+
udid: this.udid,
|
|
26
|
+
log: this.log,
|
|
27
|
+
}).collectPcap(this.resultPath);
|
|
26
28
|
this.mainProcess.on('line-stderr', (line) => this.log.info(`[Pcap] ${line}`));
|
|
27
29
|
this.log.info(
|
|
28
30
|
`Starting network traffic capture session on the device '${this.udid}'. ` +
|
|
@@ -37,17 +39,17 @@ export class TrafficCapture {
|
|
|
37
39
|
});
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
isCapturing() {
|
|
42
|
+
isCapturing(): boolean {
|
|
41
43
|
return !!this.mainProcess?.isRunning;
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
async interrupt(force = false) {
|
|
46
|
+
async interrupt(force = false): Promise<boolean> {
|
|
45
47
|
if (this.isCapturing()) {
|
|
46
48
|
const interruptPromise = this.mainProcess?.stop(force ? 'SIGTERM' : 'SIGINT');
|
|
47
49
|
this.mainProcess = null;
|
|
48
50
|
try {
|
|
49
51
|
await interruptPromise;
|
|
50
|
-
} catch (e) {
|
|
52
|
+
} catch (e: any) {
|
|
51
53
|
this.log.warn(
|
|
52
54
|
`Cannot ${force ? 'terminate' : 'interrupt'} the traffic capture session. ` +
|
|
53
55
|
`Original error: ${e.message}`,
|
|
@@ -59,12 +61,12 @@ export class TrafficCapture {
|
|
|
59
61
|
return true;
|
|
60
62
|
}
|
|
61
63
|
|
|
62
|
-
async finish() {
|
|
64
|
+
async finish(): Promise<string> {
|
|
63
65
|
await this.interrupt();
|
|
64
66
|
return this.resultPath;
|
|
65
67
|
}
|
|
66
68
|
|
|
67
|
-
async cleanup() {
|
|
69
|
+
async cleanup(): Promise<void> {
|
|
68
70
|
if (await fs.exists(this.resultPath)) {
|
|
69
71
|
await fs.rimraf(this.resultPath);
|
|
70
72
|
}
|
|
@@ -74,13 +76,15 @@ export class TrafficCapture {
|
|
|
74
76
|
/**
|
|
75
77
|
* Records the given network traffic capture into a .pcap file.
|
|
76
78
|
*
|
|
77
|
-
* @param
|
|
78
|
-
* @param
|
|
79
|
+
* @param timeLimitSec - The maximum recording time, in seconds. The maximum value is `43200` (12 hours).
|
|
80
|
+
* @param forceRestart - Whether to restart traffic capture process forcefully when startPcap is called (`true`) or ignore the call until the current traffic capture is completed (`false`, the default value).
|
|
79
81
|
* @throws {Error} If network traffic capture has failed to start.
|
|
80
|
-
* @returns {Promise<void>}
|
|
81
|
-
* @this {XCUITestDriver}
|
|
82
82
|
*/
|
|
83
|
-
export async function mobileStartPcap(
|
|
83
|
+
export async function mobileStartPcap(
|
|
84
|
+
this: XCUITestDriver,
|
|
85
|
+
timeLimitSec = 180,
|
|
86
|
+
forceRestart = false,
|
|
87
|
+
): Promise<void> {
|
|
84
88
|
if (this.isSimulator()) {
|
|
85
89
|
throw this.log.errorWithException('Network traffic capture only works on real devices');
|
|
86
90
|
}
|
|
@@ -135,17 +139,16 @@ export async function mobileStartPcap(timeLimitSec = 180, forceRestart = false)
|
|
|
135
139
|
* If no previously recorded file is found and no active traffic capture processes are running, then the method returns an empty string.
|
|
136
140
|
*
|
|
137
141
|
* @remarks Network capture files can be viewed in [Wireshark](https://www.wireshark.org/) and other similar applications.
|
|
138
|
-
* @returns
|
|
142
|
+
* @returns Base64-encoded content of the recorded pcap file or an empty string if no traffic capture has been started before.
|
|
139
143
|
* @throws {Error} If there was an error while getting the capture file.
|
|
140
|
-
* @this {XCUITestDriver}
|
|
141
144
|
*/
|
|
142
|
-
export async function mobileStopPcap() {
|
|
145
|
+
export async function mobileStopPcap(this: XCUITestDriver): Promise<string> {
|
|
143
146
|
if (!this._trafficCapture) {
|
|
144
147
|
this.log.info('Network traffic collector has not been started. There is nothing to stop');
|
|
145
148
|
return '';
|
|
146
149
|
}
|
|
147
150
|
|
|
148
|
-
let resultPath;
|
|
151
|
+
let resultPath: string;
|
|
149
152
|
try {
|
|
150
153
|
resultPath = await this._trafficCapture.finish();
|
|
151
154
|
if (!(await fs.exists(resultPath))) {
|
|
@@ -163,6 +166,3 @@ export async function mobileStopPcap() {
|
|
|
163
166
|
return await encodeBase64OrUpload(resultPath);
|
|
164
167
|
}
|
|
165
168
|
|
|
166
|
-
/**
|
|
167
|
-
* @typedef {import('../driver').XCUITestDriver} XCUITestDriver
|
|
168
|
-
*/
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import {PermissionService} from './enum';
|
|
3
|
+
import {assertSimulator as _assertSimulator} from '../utils';
|
|
4
|
+
import type {XCUITestDriver} from '../driver';
|
|
5
|
+
import type {PermissionState} from './types';
|
|
6
|
+
import type {Simulator} from 'appium-ios-simulator';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Resets the given permission for the active application under test.
|
|
10
|
+
* Works for both Simulator and real devices using Xcode SDK 11.4+
|
|
11
|
+
*
|
|
12
|
+
* @param service - One of the available service names. This could also be an integer protected resource identifier; see [this list](https://developer.apple.com/documentation/xctest/xcuiprotectedresource?language=objc)
|
|
13
|
+
* @throws If permission reset fails on the device.
|
|
14
|
+
*/
|
|
15
|
+
export async function mobileResetPermission(
|
|
16
|
+
this: XCUITestDriver,
|
|
17
|
+
service: PermissionService | number,
|
|
18
|
+
): Promise<void> {
|
|
19
|
+
if (!service) {
|
|
20
|
+
throw new Error(`The 'service' option is expected to be present`);
|
|
21
|
+
}
|
|
22
|
+
let resource: number;
|
|
23
|
+
if (_.isString(service)) {
|
|
24
|
+
resource = PermissionService[_.toLower(service) as keyof typeof PermissionService];
|
|
25
|
+
if (!resource) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
`The 'service' value must be one of ` + `${JSON.stringify(_.keys(PermissionService))}`,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
} else if (_.isInteger(service)) {
|
|
31
|
+
resource = service;
|
|
32
|
+
} else {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`The 'service' value must be either a string or an integer. ` +
|
|
35
|
+
`'${service}' is passed instead`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
await this.proxyCommand('/wda/resetAppAuth', 'POST', {resource});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Gets application permission state on a simulated device.
|
|
44
|
+
*
|
|
45
|
+
* **This method requires [WIX applesimutils](https://github.com/wix/AppleSimulatorUtils) to be installed on the Appium server host.**
|
|
46
|
+
*
|
|
47
|
+
* @param bundleId - Bundle identifier of the target application
|
|
48
|
+
* @param service - Service name
|
|
49
|
+
* @returns Either 'yes', 'no', 'unset' or 'limited'
|
|
50
|
+
* @throws If permission getting fails or the device is not a Simulator.
|
|
51
|
+
* @group Simulator Only
|
|
52
|
+
*/
|
|
53
|
+
export async function mobileGetPermission(
|
|
54
|
+
this: XCUITestDriver,
|
|
55
|
+
bundleId: string,
|
|
56
|
+
service: PermissionService,
|
|
57
|
+
): Promise<PermissionState> {
|
|
58
|
+
if (!service) {
|
|
59
|
+
throw new Error(`The 'service' option is expected to be present`);
|
|
60
|
+
}
|
|
61
|
+
assertSimulator(this);
|
|
62
|
+
|
|
63
|
+
return await (this.device as Simulator).getPermission(
|
|
64
|
+
bundleId, String(service)
|
|
65
|
+
) as PermissionState;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Set application permission state on Simulator.
|
|
70
|
+
*
|
|
71
|
+
* @param access - One or more access rules to set.
|
|
72
|
+
* @param bundleId - Bundle identifier of the target application
|
|
73
|
+
* @since Xcode SDK 11.4
|
|
74
|
+
* @throws If permission setting fails or the device is not a Simulator.
|
|
75
|
+
* @group Simulator Only
|
|
76
|
+
*/
|
|
77
|
+
export async function mobileSetPermissions(
|
|
78
|
+
this: XCUITestDriver,
|
|
79
|
+
access: Record<string, PermissionState>,
|
|
80
|
+
bundleId: string,
|
|
81
|
+
): Promise<void> {
|
|
82
|
+
if (!_.isPlainObject(access)) {
|
|
83
|
+
throw new Error(`The 'access' option is expected to be a map`);
|
|
84
|
+
}
|
|
85
|
+
assertSimulator(this);
|
|
86
|
+
|
|
87
|
+
await (this.device as Simulator).setPermissions(bundleId, access);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const assertSimulator = (driver: XCUITestDriver) => _assertSimulator.call(driver, 'Permission-related operations');
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import {errors, routeToCommandName} from 'appium/driver';
|
|
2
2
|
import B from 'bluebird';
|
|
3
|
+
import type {XCUITestDriver} from '../driver';
|
|
3
4
|
|
|
4
5
|
const GET = 'GET';
|
|
5
6
|
const POST = 'POST';
|
|
6
7
|
const DELETE = 'DELETE';
|
|
7
|
-
const SUPPORTED_METHODS = Object.freeze(new Set(
|
|
8
|
+
const SUPPORTED_METHODS = Object.freeze(new Set([GET, POST, DELETE] as const));
|
|
8
9
|
|
|
9
|
-
const WDA_ROUTES =
|
|
10
|
+
const WDA_ROUTES = {
|
|
10
11
|
'/wda/screen': {
|
|
11
12
|
GET: 'getScreenInfo',
|
|
12
13
|
},
|
|
@@ -43,22 +44,30 @@ const WDA_ROUTES = /** @type {const} */ ({
|
|
|
43
44
|
'/wda/locked': {
|
|
44
45
|
GET: 'isLocked',
|
|
45
46
|
},
|
|
46
|
-
}
|
|
47
|
+
} as const;
|
|
48
|
+
|
|
49
|
+
export type AllowedHttpMethod = 'GET' | 'POST' | 'DELETE';
|
|
47
50
|
|
|
48
51
|
/**
|
|
49
52
|
* Proxies a command to WebDriverAgent
|
|
50
|
-
*
|
|
51
|
-
* @template
|
|
52
|
-
* @
|
|
53
|
-
* @param
|
|
54
|
-
* @param
|
|
55
|
-
* @param
|
|
56
|
-
* @this
|
|
57
|
-
* @returns
|
|
53
|
+
*
|
|
54
|
+
* @template TReq - Request body type
|
|
55
|
+
* @template TRes - Response type
|
|
56
|
+
* @param url - The endpoint URL
|
|
57
|
+
* @param method - HTTP method to use
|
|
58
|
+
* @param body - Optional request body
|
|
59
|
+
* @param isSessionCommand - Whether this is a session command (default: true)
|
|
60
|
+
* @returns Promise resolving to the response
|
|
58
61
|
*/
|
|
59
|
-
export async function proxyCommand
|
|
62
|
+
export async function proxyCommand<TReq = any, TRes = unknown>(
|
|
63
|
+
this: XCUITestDriver,
|
|
64
|
+
url: string,
|
|
65
|
+
method: AllowedHttpMethod,
|
|
66
|
+
body?: TReq,
|
|
67
|
+
isSessionCommand: boolean = true,
|
|
68
|
+
): Promise<TRes> {
|
|
60
69
|
if (this.shutdownUnexpectedly) {
|
|
61
|
-
return
|
|
70
|
+
return undefined as TRes;
|
|
62
71
|
}
|
|
63
72
|
|
|
64
73
|
if (!url) {
|
|
@@ -83,12 +92,12 @@ export async function proxyCommand(url, method, body, isSessionCommand = true) {
|
|
|
83
92
|
}
|
|
84
93
|
|
|
85
94
|
if (!timeout) {
|
|
86
|
-
return
|
|
95
|
+
return await proxy.command(url, method, body) as TRes;
|
|
87
96
|
}
|
|
88
97
|
|
|
89
98
|
this.log.debug(`Setting custom timeout to ${timeout} ms for '${cmdName}' command`);
|
|
90
99
|
try {
|
|
91
|
-
return
|
|
100
|
+
return await B.resolve(proxy.command(url, method, body)).timeout(timeout) as TRes;
|
|
92
101
|
} catch (e) {
|
|
93
102
|
if (!(e instanceof B.Promise.TimeoutError)) {
|
|
94
103
|
throw e;
|
|
@@ -102,18 +111,9 @@ export async function proxyCommand(url, method, body, isSessionCommand = true) {
|
|
|
102
111
|
}
|
|
103
112
|
}
|
|
104
113
|
|
|
105
|
-
|
|
106
|
-
* @param {string} endpoint
|
|
107
|
-
* @param {AllowedHttpMethod} method
|
|
108
|
-
* @returns {string|undefined}
|
|
109
|
-
*/
|
|
110
|
-
function wdaRouteToCommandName(endpoint, method) {
|
|
114
|
+
function wdaRouteToCommandName(endpoint: string, method: AllowedHttpMethod): string | undefined {
|
|
111
115
|
if (endpoint in WDA_ROUTES) {
|
|
112
|
-
return WDA_ROUTES[endpoint][method];
|
|
116
|
+
return WDA_ROUTES[endpoint as keyof typeof WDA_ROUTES]?.[method];
|
|
113
117
|
}
|
|
114
118
|
}
|
|
115
119
|
|
|
116
|
-
/**
|
|
117
|
-
* @typedef {import('../driver').XCUITestDriver} XCUITestDriver
|
|
118
|
-
* @typedef {'GET'|'POST'|'DELETE'} AllowedHttpMethod
|
|
119
|
-
*/
|