appium-xcuitest-driver 10.14.0 → 10.14.2
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/condition.d.ts +9 -72
- package/build/lib/commands/condition.d.ts.map +1 -1
- package/build/lib/commands/condition.js +5 -66
- package/build/lib/commands/condition.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/record-audio.d.ts +25 -53
- package/build/lib/commands/record-audio.d.ts.map +1 -1
- package/build/lib/commands/record-audio.js +17 -19
- package/build/lib/commands/record-audio.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/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/types.d.ts +58 -0
- package/build/lib/commands/types.d.ts.map +1 -1
- package/build/lib/commands/xctest-record-screen.d.ts +17 -47
- package/build/lib/commands/xctest-record-screen.d.ts.map +1 -1
- package/build/lib/commands/xctest-record-screen.js +28 -59
- package/build/lib/commands/xctest-record-screen.js.map +1 -1
- package/build/lib/driver.d.ts +1 -1
- package/build/lib/driver.d.ts.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 -6
- package/build/lib/execute-method-map.js.map +1 -1
- package/lib/commands/{condition.js → condition.ts} +21 -77
- 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/{record-audio.js → record-audio.ts} +35 -33
- package/lib/commands/{screenshots.js → screenshots.ts} +24 -16
- package/lib/commands/{source.js → source.ts} +23 -20
- package/lib/commands/types.ts +63 -0
- package/lib/commands/{xctest-record-screen.js → xctest-record-screen.ts} +54 -71
- package/lib/driver.ts +2 -2
- package/lib/execute-method-map.ts +0 -6
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
import {INSTRUMENT_CHANNEL, services} from 'appium-ios-device';
|
|
2
2
|
import _ from 'lodash';
|
|
3
3
|
import { isIos18OrNewer } from '../utils';
|
|
4
|
+
import type {XCUITestDriver} from '../driver';
|
|
5
|
+
import type {DVTServiceWithConnection} from 'appium-ios-remotexpc';
|
|
6
|
+
import type {Condition} from './types';
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
9
|
* Get all available ConditionInducer configuration information, which can be used with
|
|
7
10
|
* {@linkcode XCUITestDriver.enableConditionInducer}
|
|
8
|
-
* @returns {Promise<Condition[]>}
|
|
9
11
|
* @since 4.9.0
|
|
10
12
|
* @see {@link https://help.apple.com/xcode/mac/current/#/dev308429d42}
|
|
11
|
-
* @this {XCUITestDriver}
|
|
12
13
|
*/
|
|
13
|
-
export async function listConditionInducers() {
|
|
14
|
+
export async function listConditionInducers(this: XCUITestDriver): Promise<Condition[]> {
|
|
14
15
|
requireConditionInducerCompatibleDevice.call(this);
|
|
15
16
|
|
|
16
17
|
if (isIos18OrNewer(this.opts)) {
|
|
17
18
|
const dvtConnection = await startRemoteXPC(this.device.udid);
|
|
18
19
|
try {
|
|
19
20
|
const result = await dvtConnection.conditionInducer.list();
|
|
20
|
-
return
|
|
21
|
-
} catch (err) {
|
|
21
|
+
return result as Condition[];
|
|
22
|
+
} catch (err: any) {
|
|
22
23
|
this.log.error(`Failed to list condition inducers via RemoteXPC: ${err.message}`);
|
|
23
24
|
throw err;
|
|
24
25
|
} finally {
|
|
@@ -50,15 +51,18 @@ export async function listConditionInducers() {
|
|
|
50
51
|
* (Note: the socket needs to remain connected during operation)
|
|
51
52
|
* (Note: Device conditions are available only for real devices running iOS 13.0 and later.)
|
|
52
53
|
*
|
|
53
|
-
* @param
|
|
54
|
-
* @param
|
|
55
|
-
* @returns
|
|
54
|
+
* @param conditionID - Determine which condition IDs are available with the {@linkcode XCUITestDriver.listConditionInducers} command
|
|
55
|
+
* @param profileID - Determine which profile IDs are available with the {@linkcode XCUITestDriver.listConditionInducers} command
|
|
56
|
+
* @returns `true` if enabling the condition succeeded
|
|
56
57
|
* @throws {Error} If you try to start another Condition and the previous Condition has not stopped
|
|
57
58
|
* @since 4.9.0
|
|
58
59
|
* @see {@link https://help.apple.com/xcode/mac/current/#/dev308429d42}
|
|
59
|
-
* @this {XCUITestDriver}
|
|
60
60
|
*/
|
|
61
|
-
export async function enableConditionInducer(
|
|
61
|
+
export async function enableConditionInducer(
|
|
62
|
+
this: XCUITestDriver,
|
|
63
|
+
conditionID: string,
|
|
64
|
+
profileID: string,
|
|
65
|
+
): Promise<boolean> {
|
|
62
66
|
requireConditionInducerCompatibleDevice.call(this);
|
|
63
67
|
|
|
64
68
|
if (isIos18OrNewer(this.opts)) {
|
|
@@ -76,7 +80,7 @@ export async function enableConditionInducer(conditionID, profileID) {
|
|
|
76
80
|
|
|
77
81
|
this.log.info(`Successfully enabled condition profile: ${profileID}`);
|
|
78
82
|
return true;
|
|
79
|
-
} catch (err) {
|
|
83
|
+
} catch (err: any) {
|
|
80
84
|
await closeRemoteXPC.call(this);
|
|
81
85
|
throw this.log.errorWithException(`Condition inducer '${profileID}' cannot be enabled: '${err.message}'`);
|
|
82
86
|
}
|
|
@@ -109,12 +113,11 @@ export async function enableConditionInducer(conditionID, profileID) {
|
|
|
109
113
|
* condition inducer will be automatically disabled
|
|
110
114
|
*
|
|
111
115
|
* (Note: this is also automatically called upon session cleanup)
|
|
112
|
-
* @returns
|
|
116
|
+
* @returns `true` if disable the condition succeeded
|
|
113
117
|
* @since 4.9.0
|
|
114
118
|
* @see {@link https://help.apple.com/xcode/mac/current/#/dev308429d42}
|
|
115
|
-
* @this {XCUITestDriver}
|
|
116
119
|
*/
|
|
117
|
-
export async function disableConditionInducer() {
|
|
120
|
+
export async function disableConditionInducer(this: XCUITestDriver): Promise<boolean> {
|
|
118
121
|
requireConditionInducerCompatibleDevice.call(this);
|
|
119
122
|
|
|
120
123
|
if (isIos18OrNewer(this.opts)) {
|
|
@@ -127,7 +130,7 @@ export async function disableConditionInducer() {
|
|
|
127
130
|
await this._remoteXPCConditionInducerConnection.conditionInducer.disable();
|
|
128
131
|
this.log.info('Successfully disabled condition inducer');
|
|
129
132
|
return true;
|
|
130
|
-
} catch (err) {
|
|
133
|
+
} catch (err: any) {
|
|
131
134
|
this.log.warn(`Failed to disable condition inducer via RemoteXPC: ${err.message}`);
|
|
132
135
|
return false;
|
|
133
136
|
} finally {
|
|
@@ -158,81 +161,22 @@ export async function disableConditionInducer() {
|
|
|
158
161
|
}
|
|
159
162
|
}
|
|
160
163
|
|
|
161
|
-
|
|
162
|
-
* @this {XCUITestDriver}
|
|
163
|
-
* @returns {void}
|
|
164
|
-
*/
|
|
165
|
-
function requireConditionInducerCompatibleDevice() {
|
|
164
|
+
function requireConditionInducerCompatibleDevice(this: XCUITestDriver): void {
|
|
166
165
|
if (this.isSimulator()) {
|
|
167
166
|
throw this.log.errorWithException('Condition inducer only works on real devices');
|
|
168
167
|
}
|
|
169
168
|
}
|
|
170
169
|
|
|
171
|
-
|
|
172
|
-
* @param {string} udid
|
|
173
|
-
* @returns {Promise<DVTServiceWithConnection>}
|
|
174
|
-
*/
|
|
175
|
-
async function startRemoteXPC(udid) {
|
|
170
|
+
async function startRemoteXPC(udid: string): Promise<DVTServiceWithConnection> {
|
|
176
171
|
const {Services} = await import('appium-ios-remotexpc');
|
|
177
172
|
return Services.startDVTService(udid);
|
|
178
173
|
}
|
|
179
174
|
|
|
180
|
-
|
|
181
|
-
* @this {XCUITestDriver}
|
|
182
|
-
* @returns {Promise<void>}
|
|
183
|
-
*/
|
|
184
|
-
async function closeRemoteXPC() {
|
|
175
|
+
async function closeRemoteXPC(this: XCUITestDriver): Promise<void> {
|
|
185
176
|
if (this._remoteXPCConditionInducerConnection) {
|
|
186
177
|
await this._remoteXPCConditionInducerConnection.remoteXPC.close();
|
|
187
178
|
this._remoteXPCConditionInducerConnection = null;
|
|
188
179
|
}
|
|
189
180
|
}
|
|
190
181
|
|
|
191
|
-
/**
|
|
192
|
-
* @typedef {import('appium-ios-remotexpc', {with: {'resolution-mode': 'import'}}).DVTServiceWithConnection} DVTServiceWithConnection
|
|
193
|
-
*/
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* @typedef {Object} Profile
|
|
197
|
-
* @property {string} name
|
|
198
|
-
* @property {string} identifier the property is profileID used in {@linkcode XCUITestDriver.enableConditionInducer}
|
|
199
|
-
* @property {string} description Configuration details
|
|
200
|
-
*/
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* We can use the returned data to determine whether the Condition is enabled and the currently enabled configuration information
|
|
204
|
-
* @typedef {Object} Condition
|
|
205
|
-
* @property {Profile[]} profiles
|
|
206
|
-
* @property {string} identifier the property is conditionID used in {@linkcode XCUITestDriver.enableConditionInducer}
|
|
207
|
-
* @property {boolean} profilesSorted
|
|
208
|
-
* @property {boolean} isDestructive
|
|
209
|
-
* @property {boolean} isInternal
|
|
210
|
-
* @property {boolean} isActive `true` if this condition identifier is enabled
|
|
211
|
-
* @property {string} activeProfile enabled profiles identifier
|
|
212
|
-
* @example {
|
|
213
|
-
* "profiles": [
|
|
214
|
-
* {
|
|
215
|
-
* "name": "100% packet loss",
|
|
216
|
-
* "identifier": "SlowNetwork100PctLoss", // MobileEnableConditionInducer profileID
|
|
217
|
-
* "description": "Name: 100% Loss Scenario\n
|
|
218
|
-
* Downlink Bandwidth: 0 Mbps\n
|
|
219
|
-
* Downlink Latency:0 ms\n
|
|
220
|
-
* Downlink Packet Loss Ratio: 100%\n
|
|
221
|
-
* Uplink Bandwidth: 0 Mbps\n
|
|
222
|
-
* Uplink Latency: 0 ms\n
|
|
223
|
-
* Uplink Packet Loss Ratio: 100%"
|
|
224
|
-
* }
|
|
225
|
-
* ],
|
|
226
|
-
* "profilesSorted": true,
|
|
227
|
-
* "identifier": "SlowNetworkCondition", // MobileEnableConditionInducer conditionID
|
|
228
|
-
* "isDestructive": false,
|
|
229
|
-
* "isInternal": false,
|
|
230
|
-
* "activeProfile": "",
|
|
231
|
-
* "name": "Network Link",
|
|
232
|
-
* "isActive": false
|
|
233
|
-
* }
|
|
234
|
-
*/
|
|
235
182
|
|
|
236
|
-
/**
|
|
237
|
-
* @typedef {import('../driver').XCUITestDriver} XCUITestDriver
|
|
238
|
-
*/
|
|
@@ -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
|
-
*/
|