appium-uiautomator2-driver 2.29.11 → 2.31.0
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 +14 -0
- package/build/index.d.ts +4 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +8 -15
- package/build/index.js.map +1 -0
- package/build/lib/commands/actions.d.ts +2 -0
- package/build/lib/commands/actions.d.ts.map +1 -0
- package/build/lib/commands/actions.js +67 -62
- package/build/lib/commands/actions.js.map +1 -1
- package/build/lib/commands/alert.d.ts +2 -0
- package/build/lib/commands/alert.d.ts.map +1 -0
- package/build/lib/commands/alert.js +28 -26
- package/build/lib/commands/alert.js.map +1 -1
- package/build/lib/commands/app-strings.d.ts +3 -0
- package/build/lib/commands/app-strings.d.ts.map +1 -0
- package/build/lib/commands/app-strings.js +86 -57
- package/build/lib/commands/app-strings.js.map +1 -1
- package/build/lib/commands/battery.d.ts +2 -0
- package/build/lib/commands/battery.d.ts.map +1 -0
- package/build/lib/commands/battery.js +26 -16
- package/build/lib/commands/battery.js.map +1 -1
- package/build/lib/commands/element.d.ts +2 -0
- package/build/lib/commands/element.d.ts.map +1 -0
- package/build/lib/commands/element.js +140 -159
- package/build/lib/commands/element.js.map +1 -1
- package/build/lib/commands/find.d.ts +2 -0
- package/build/lib/commands/find.d.ts.map +1 -0
- package/build/lib/commands/find.js +39 -25
- package/build/lib/commands/find.js.map +1 -1
- package/build/lib/commands/general.d.ts +4 -0
- package/build/lib/commands/general.d.ts.map +1 -0
- package/build/lib/commands/general.js +209 -215
- package/build/lib/commands/general.js.map +1 -1
- package/build/lib/commands/gestures.d.ts +2 -0
- package/build/lib/commands/gestures.d.ts.map +1 -0
- package/build/lib/commands/gestures.js +206 -193
- package/build/lib/commands/gestures.js.map +1 -1
- package/build/lib/commands/index.d.ts +2 -0
- package/build/lib/commands/index.d.ts.map +1 -0
- package/build/lib/commands/index.js +13 -22
- package/build/lib/commands/index.js.map +1 -1
- package/build/lib/commands/mixins.d.ts +87 -0
- package/build/lib/commands/mixins.d.ts.map +1 -0
- package/build/lib/commands/mixins.js +26 -0
- package/build/lib/commands/mixins.js.map +1 -0
- package/build/lib/commands/screenshot.d.ts +2 -0
- package/build/lib/commands/screenshot.d.ts.map +1 -0
- package/build/lib/commands/screenshot.js +77 -62
- package/build/lib/commands/screenshot.js.map +1 -1
- package/build/lib/commands/touch.d.ts +2 -0
- package/build/lib/commands/touch.d.ts.map +1 -0
- package/build/lib/commands/touch.js +48 -38
- package/build/lib/commands/touch.js.map +1 -1
- package/build/lib/commands/types.d.ts +452 -0
- package/build/lib/commands/types.d.ts.map +1 -0
- package/build/lib/commands/types.js +3 -0
- package/build/lib/commands/types.js.map +1 -0
- package/build/lib/commands/viewport.d.ts +2 -0
- package/build/lib/commands/viewport.d.ts.map +1 -0
- package/build/lib/commands/viewport.js +37 -35
- package/build/lib/commands/viewport.js.map +1 -1
- package/build/lib/constraints.d.ts +325 -0
- package/build/lib/constraints.d.ts.map +1 -0
- package/build/lib/constraints.js +51 -0
- package/build/lib/constraints.js.map +1 -0
- package/build/lib/css-converter.d.ts +45 -0
- package/build/lib/css-converter.d.ts.map +1 -0
- package/build/lib/css-converter.js +272 -175
- package/build/lib/css-converter.js.map +1 -1
- package/build/lib/driver.d.ts +904 -0
- package/build/lib/driver.d.ts.map +1 -0
- package/build/lib/driver.js +726 -485
- package/build/lib/driver.js.map +1 -1
- package/build/lib/execute-method-map.d.ts +477 -0
- package/build/lib/execute-method-map.d.ts.map +1 -0
- package/build/lib/execute-method-map.js +542 -0
- package/build/lib/execute-method-map.js.map +1 -0
- package/build/lib/extensions.d.ts +3 -0
- package/build/lib/extensions.d.ts.map +1 -0
- package/build/lib/extensions.js +7 -9
- package/build/lib/extensions.js.map +1 -1
- package/build/lib/helpers.d.ts +7 -0
- package/build/lib/helpers.d.ts.map +1 -0
- package/build/lib/helpers.js +36 -29
- package/build/lib/helpers.js.map +1 -1
- package/build/lib/logger.d.ts +3 -0
- package/build/lib/logger.d.ts.map +1 -0
- package/build/lib/logger.js +5 -10
- package/build/lib/logger.js.map +1 -1
- package/build/lib/method-map.d.ts +389 -0
- package/build/lib/method-map.d.ts.map +1 -0
- package/build/lib/method-map.js +11 -17
- package/build/lib/method-map.js.map +1 -1
- package/build/lib/types.d.ts +44 -0
- package/build/lib/types.d.ts.map +1 -0
- package/build/lib/types.js +3 -0
- package/build/lib/types.js.map +1 -0
- package/build/lib/uiautomator2.d.ts +45 -0
- package/build/lib/uiautomator2.d.ts.map +1 -0
- package/build/lib/uiautomator2.js +340 -299
- package/build/lib/uiautomator2.js.map +1 -1
- package/build/lib/utils.d.ts +10 -0
- package/build/lib/utils.d.ts.map +1 -0
- package/build/lib/utils.js +23 -16
- package/build/lib/utils.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +1 -0
- package/index.js +5 -3
- package/lib/commands/actions.js +115 -101
- package/lib/commands/alert.js +36 -44
- package/lib/commands/app-strings.js +79 -58
- package/lib/commands/battery.js +27 -28
- package/lib/commands/element.js +231 -134
- package/lib/commands/find.js +40 -21
- package/lib/commands/general.js +262 -336
- package/lib/commands/gestures.js +252 -366
- package/lib/commands/index.js +11 -31
- package/lib/commands/mixins.ts +169 -0
- package/lib/commands/screenshot.js +80 -76
- package/lib/commands/touch.js +64 -31
- package/lib/commands/types.ts +473 -0
- package/lib/commands/viewport.js +43 -31
- package/lib/constraints.ts +53 -0
- package/lib/css-converter.js +9 -1
- package/lib/{driver.js → driver.ts} +374 -239
- package/lib/execute-method-map.ts +573 -0
- package/lib/method-map.ts +11 -0
- package/lib/types.ts +57 -0
- package/lib/uiautomator2.js +21 -2
- package/lib/utils.js +2 -2
- package/npm-shrinkwrap.json +395 -528
- package/package.json +96 -70
- package/build/lib/desired-caps.js +0 -71
- package/build/lib/desired-caps.js.map +0 -1
- package/lib/desired-caps.js +0 -70
- package/lib/method-map.js +0 -11
|
@@ -1,24 +1,44 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
2
|
+
import type {
|
|
3
|
+
DefaultCreateSessionResult,
|
|
4
|
+
DriverData,
|
|
5
|
+
ExternalDriver,
|
|
6
|
+
InitialOpts,
|
|
7
|
+
Orientation,
|
|
8
|
+
RouteMatcher,
|
|
9
|
+
SingularSessionData,
|
|
10
|
+
StringRecord,
|
|
11
|
+
} from '@appium/types';
|
|
12
|
+
import {DEFAULT_ADB_PORT} from 'appium-adb';
|
|
13
|
+
import AndroidDriver, {SETTINGS_HELPER_PKG_ID, androidHelpers} from 'appium-android-driver';
|
|
14
|
+
import {BaseDriver, DeviceSettings} from 'appium/driver';
|
|
15
|
+
import {fs, mjpeg, util} from 'appium/support';
|
|
16
|
+
import {retryInterval} from 'asyncbox';
|
|
9
17
|
import B from 'bluebird';
|
|
10
|
-
import
|
|
11
|
-
import
|
|
18
|
+
import _ from 'lodash';
|
|
19
|
+
import os from 'node:os';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
import {checkPortStatus, findAPortNotInUse} from 'portscanner';
|
|
22
|
+
import type {ExecError} from 'teen_process';
|
|
23
|
+
import UIAUTOMATOR2_CONSTRAINTS, {type Uiautomator2Constraints} from './constraints';
|
|
24
|
+
import {executeMethodMap} from './execute-method-map';
|
|
25
|
+
import {APKS_EXTENSION, APK_EXTENSION} from './extensions';
|
|
12
26
|
import uiautomator2Helpers from './helpers';
|
|
13
|
-
import {
|
|
14
|
-
import
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
27
|
+
import {newMethodMap} from './method-map';
|
|
28
|
+
import type {
|
|
29
|
+
Uiautomator2Settings,
|
|
30
|
+
Uiautomator2DeviceDetails,
|
|
31
|
+
Uiautomator2DeviceInfo,
|
|
32
|
+
Uiautomator2DriverCaps,
|
|
33
|
+
Uiautomator2DriverOpts,
|
|
34
|
+
Uiautomator2SessionCaps,
|
|
35
|
+
Uiautomator2SessionInfo,
|
|
36
|
+
Uiautomator2StartSessionOpts,
|
|
37
|
+
W3CUiautomator2DriverCaps,
|
|
38
|
+
} from './types';
|
|
39
|
+
import {SERVER_PACKAGE_ID, SERVER_TEST_PACKAGE_ID, UiAutomator2Server} from './uiautomator2';
|
|
40
|
+
|
|
41
|
+
const helpers = {...uiautomator2Helpers, ...androidHelpers};
|
|
22
42
|
|
|
23
43
|
// The range of ports we can use on the system for communicating to the
|
|
24
44
|
// UiAutomator2 HTTP server on the device
|
|
@@ -45,7 +65,7 @@ const LOCALHOST_IP4 = '127.0.0.1';
|
|
|
45
65
|
// TODO: Add the list of paths that we never want to proxy to UiAutomator2 server.
|
|
46
66
|
// TODO: Need to segregate the paths better way using regular expressions wherever applicable.
|
|
47
67
|
// (Not segregating right away because more paths to be added in the NO_PROXY list)
|
|
48
|
-
const NO_PROXY = [
|
|
68
|
+
const NO_PROXY: RouteMatcher[] = [
|
|
49
69
|
['DELETE', new RegExp('^/session/[^/]+/actions')],
|
|
50
70
|
['GET', new RegExp('^/session/(?!.*/)')],
|
|
51
71
|
['GET', new RegExp('^/session/[^/]+/alert_[^/]+')],
|
|
@@ -114,7 +134,7 @@ const NO_PROXY = [
|
|
|
114
134
|
];
|
|
115
135
|
|
|
116
136
|
// This is a set of methods and paths that we never want to proxy to Chromedriver.
|
|
117
|
-
const CHROME_NO_PROXY = [
|
|
137
|
+
const CHROME_NO_PROXY: RouteMatcher[] = [
|
|
118
138
|
['GET', new RegExp('^/session/[^/]+/appium')],
|
|
119
139
|
['GET', new RegExp('^/session/[^/]+/context')],
|
|
120
140
|
['GET', new RegExp('^/session/[^/]+/element/[^/]+/rect')],
|
|
@@ -139,56 +159,98 @@ const CHROME_NO_PROXY = [
|
|
|
139
159
|
['POST', new RegExp('^/session/[^/]+/se/log$')],
|
|
140
160
|
];
|
|
141
161
|
|
|
142
|
-
const MEMOIZED_FUNCTIONS = [
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
162
|
+
const MEMOIZED_FUNCTIONS = ['getStatusBarHeight', 'getDevicePixelRatio'] as const;
|
|
163
|
+
|
|
164
|
+
class AndroidUiautomator2Driver
|
|
165
|
+
extends AndroidDriver
|
|
166
|
+
implements
|
|
167
|
+
ExternalDriver<
|
|
168
|
+
Uiautomator2Constraints,
|
|
169
|
+
string,
|
|
170
|
+
StringRecord
|
|
171
|
+
>
|
|
172
|
+
{
|
|
173
|
+
static newMethodMap = newMethodMap;
|
|
146
174
|
|
|
147
|
-
|
|
175
|
+
static executeMethodMap = executeMethodMap;
|
|
148
176
|
|
|
149
|
-
|
|
177
|
+
uiautomator2?: UiAutomator2Server;
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* @privateRemarks moved from `this.opts`
|
|
181
|
+
*/
|
|
182
|
+
systemPort: number | undefined;
|
|
183
|
+
|
|
184
|
+
_hasSystemPortInCaps: boolean | undefined;
|
|
150
185
|
|
|
151
|
-
|
|
186
|
+
mjpegStream?: mjpeg.MJpegStream;
|
|
187
|
+
|
|
188
|
+
override caps: Uiautomator2DriverCaps;
|
|
189
|
+
|
|
190
|
+
override opts: Uiautomator2DriverOpts;
|
|
191
|
+
|
|
192
|
+
override desiredCapConstraints: Uiautomator2Constraints;
|
|
193
|
+
|
|
194
|
+
constructor(opts: InitialOpts = {} as InitialOpts, shouldValidateCaps = true) {
|
|
152
195
|
// `shell` overwrites adb.shell, so remove
|
|
196
|
+
// @ts-expect-error FIXME: what is this?
|
|
153
197
|
delete opts.shell;
|
|
154
198
|
|
|
155
199
|
super(opts, shouldValidateCaps);
|
|
200
|
+
|
|
156
201
|
this.locatorStrategies = [
|
|
157
202
|
'xpath',
|
|
158
203
|
'id',
|
|
159
204
|
'class name',
|
|
160
205
|
'accessibility id',
|
|
161
206
|
'css selector',
|
|
162
|
-
'-android uiautomator'
|
|
207
|
+
'-android uiautomator',
|
|
163
208
|
];
|
|
164
|
-
this.desiredCapConstraints =
|
|
165
|
-
this.uiautomator2 = null;
|
|
209
|
+
this.desiredCapConstraints = _.cloneDeep(UIAUTOMATOR2_CONSTRAINTS);
|
|
166
210
|
this.jwpProxyActive = false;
|
|
167
211
|
this.jwpProxyAvoid = NO_PROXY;
|
|
168
212
|
this.apkStrings = {}; // map of language -> strings obj
|
|
169
213
|
|
|
170
|
-
this.settings = new DeviceSettings(
|
|
171
|
-
|
|
214
|
+
this.settings = new DeviceSettings(
|
|
215
|
+
{ignoreUnimportantViews: false, allowInvisibleElements: false},
|
|
216
|
+
this.onSettingsUpdate.bind(this)
|
|
217
|
+
);
|
|
172
218
|
// handle webview mechanics from AndroidDriver
|
|
173
|
-
this.chromedriver = null;
|
|
174
219
|
this.sessionChromedrivers = {};
|
|
175
220
|
|
|
221
|
+
this.caps = {} as Uiautomator2DriverCaps;
|
|
222
|
+
this.opts = opts as Uiautomator2DriverOpts;
|
|
176
223
|
// memoize functions here, so that they are done on a per-instance basis
|
|
177
224
|
for (const fn of MEMOIZED_FUNCTIONS) {
|
|
178
|
-
this[fn] = _.memoize(this[fn]);
|
|
225
|
+
this[fn] = _.memoize(this[fn]) as any;
|
|
179
226
|
}
|
|
180
227
|
}
|
|
181
228
|
|
|
182
|
-
validateDesiredCaps
|
|
183
|
-
return
|
|
229
|
+
override validateDesiredCaps(caps: any): caps is Uiautomator2DriverCaps {
|
|
230
|
+
return (
|
|
231
|
+
BaseDriver.prototype.validateDesiredCaps.call(this, caps) &&
|
|
232
|
+
androidHelpers.validateDesiredCaps(caps)
|
|
233
|
+
);
|
|
184
234
|
}
|
|
185
235
|
|
|
186
|
-
async createSession
|
|
236
|
+
async createSession(
|
|
237
|
+
w3cCaps1: W3CUiautomator2DriverCaps,
|
|
238
|
+
w3cCaps2?: W3CUiautomator2DriverCaps,
|
|
239
|
+
w3cCaps3?: W3CUiautomator2DriverCaps,
|
|
240
|
+
driverData?: DriverData[]
|
|
241
|
+
): Promise<any> {
|
|
187
242
|
try {
|
|
188
243
|
// TODO handle otherSessionData for multiple sessions
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
244
|
+
const [sessionId, caps] = (await BaseDriver.prototype.createSession.call(
|
|
245
|
+
this,
|
|
246
|
+
w3cCaps1,
|
|
247
|
+
w3cCaps2,
|
|
248
|
+
w3cCaps3,
|
|
249
|
+
driverData
|
|
250
|
+
)) as DefaultCreateSessionResult<Uiautomator2Constraints>;
|
|
251
|
+
|
|
252
|
+
const startSessionOpts: Uiautomator2StartSessionOpts = {
|
|
253
|
+
...caps,
|
|
192
254
|
platform: 'LINUX',
|
|
193
255
|
webStorageEnabled: false,
|
|
194
256
|
takesScreenshot: true,
|
|
@@ -197,160 +259,180 @@ class AndroidUiautomator2Driver extends BaseDriver {
|
|
|
197
259
|
networkConnectionEnabled: true,
|
|
198
260
|
locationContextEnabled: false,
|
|
199
261
|
warnings: {},
|
|
200
|
-
desired:
|
|
262
|
+
desired: caps,
|
|
201
263
|
};
|
|
202
264
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
this.curContext = this.defaultContextName();
|
|
206
|
-
|
|
207
|
-
let defaultOpts = {
|
|
265
|
+
const defaultOpts = {
|
|
208
266
|
fullReset: false,
|
|
209
267
|
autoLaunch: true,
|
|
210
268
|
adbPort: DEFAULT_ADB_PORT,
|
|
211
|
-
androidInstallTimeout: 90000
|
|
269
|
+
androidInstallTimeout: 90000,
|
|
212
270
|
};
|
|
213
271
|
_.defaults(this.opts, defaultOpts);
|
|
214
272
|
|
|
215
273
|
if (this.isChromeSession) {
|
|
216
274
|
this.log.info("We're going to run a Chrome-based session");
|
|
217
|
-
|
|
275
|
+
const {pkg, activity} = helpers.getChromePkg(this.opts.browserName!);
|
|
218
276
|
this.opts.appPackage = this.caps.appPackage = pkg;
|
|
219
277
|
this.opts.appActivity = this.caps.appActivity = activity;
|
|
220
278
|
this.log.info(`Chrome-type package and activity are ${pkg} and ${activity}`);
|
|
221
279
|
}
|
|
222
280
|
|
|
281
|
+
// @ts-expect-error FIXME: missing CLI option?
|
|
223
282
|
if (this.opts.reboot) {
|
|
224
|
-
this.setAvdFromCapabilities(
|
|
283
|
+
this.setAvdFromCapabilities(startSessionOpts);
|
|
225
284
|
}
|
|
226
285
|
|
|
227
286
|
if (this.opts.app) {
|
|
228
287
|
// find and copy, or download and unzip an app url or path
|
|
229
|
-
this.opts.app = await this.helpers.configureApp(this.opts.app, [
|
|
288
|
+
this.opts.app = await this.helpers.configureApp(this.opts.app, [
|
|
289
|
+
APK_EXTENSION,
|
|
290
|
+
APKS_EXTENSION,
|
|
291
|
+
]);
|
|
230
292
|
await this.checkAppPresent();
|
|
231
293
|
} else if (this.opts.appPackage) {
|
|
232
294
|
// the app isn't an actual app file but rather something we want to
|
|
233
295
|
// assume is on the device and just launch via the appPackage
|
|
234
296
|
this.log.info(`Starting '${this.opts.appPackage}' directly on the device`);
|
|
235
297
|
} else {
|
|
236
|
-
this.log.info(
|
|
237
|
-
'
|
|
298
|
+
this.log.info(
|
|
299
|
+
`Neither 'app' nor 'appPackage' was set. Starting UiAutomator2 ` +
|
|
300
|
+
'without the target application'
|
|
301
|
+
);
|
|
238
302
|
}
|
|
239
303
|
this.opts.adbPort = this.opts.adbPort || DEFAULT_ADB_PORT;
|
|
240
304
|
|
|
241
|
-
await this.startUiAutomator2Session();
|
|
242
|
-
|
|
305
|
+
const result = await this.startUiAutomator2Session(startSessionOpts);
|
|
306
|
+
|
|
243
307
|
if (this.opts.mjpegScreenshotUrl) {
|
|
244
308
|
this.log.info(`Starting MJPEG stream reading URL: '${this.opts.mjpegScreenshotUrl}'`);
|
|
245
309
|
this.mjpegStream = new mjpeg.MJpegStream(this.opts.mjpegScreenshotUrl);
|
|
246
310
|
await this.mjpegStream.start();
|
|
247
311
|
}
|
|
248
|
-
return [sessionId,
|
|
312
|
+
return [sessionId, result];
|
|
249
313
|
} catch (e) {
|
|
250
314
|
await this.deleteSession();
|
|
251
315
|
throw e;
|
|
252
316
|
}
|
|
253
317
|
}
|
|
254
318
|
|
|
255
|
-
async
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
319
|
+
async getDeviceDetails(): Promise<Uiautomator2DeviceDetails> {
|
|
320
|
+
const [pixelRatio, statBarHeight, viewportRect] = await B.all([
|
|
321
|
+
this.getDevicePixelRatio(),
|
|
322
|
+
this.getStatusBarHeight(),
|
|
323
|
+
this.getViewPortRect(),
|
|
324
|
+
]);
|
|
325
|
+
return {pixelRatio, statBarHeight, viewportRect};
|
|
259
326
|
}
|
|
260
327
|
|
|
261
|
-
get driverData
|
|
328
|
+
override get driverData() {
|
|
262
329
|
// TODO fill out resource info here
|
|
263
330
|
return {};
|
|
264
331
|
}
|
|
265
332
|
|
|
266
|
-
async getSession
|
|
267
|
-
|
|
333
|
+
override async getSession(): Promise<SingularSessionData<Uiautomator2Constraints>> {
|
|
334
|
+
const sessionData = await BaseDriver.prototype.getSession.call(this);
|
|
268
335
|
this.log.debug('Getting session details from server to mix in');
|
|
269
|
-
|
|
270
|
-
return
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
isEmulator () {
|
|
274
|
-
return helpers.isEmulator(this.adb, this.opts);
|
|
336
|
+
const uia2Data = (await this.uiautomator2!.jwproxy.command('/', 'GET', {})) as any;
|
|
337
|
+
return {...sessionData, ...uia2Data};
|
|
275
338
|
}
|
|
276
339
|
|
|
277
|
-
setAvdFromCapabilities
|
|
340
|
+
setAvdFromCapabilities(caps: Uiautomator2StartSessionOpts) {
|
|
278
341
|
if (this.opts.avd) {
|
|
279
342
|
this.log.info('avd name defined, ignoring device name and platform version');
|
|
280
343
|
} else {
|
|
281
344
|
if (!caps.deviceName) {
|
|
282
|
-
this.log.errorAndThrow(
|
|
345
|
+
this.log.errorAndThrow(
|
|
346
|
+
'avd or deviceName should be specified when reboot option is enables'
|
|
347
|
+
);
|
|
348
|
+
throw new Error(); // unreachable
|
|
283
349
|
}
|
|
284
350
|
if (!caps.platformVersion) {
|
|
285
|
-
this.log.errorAndThrow(
|
|
351
|
+
this.log.errorAndThrow(
|
|
352
|
+
'avd or platformVersion should be specified when reboot option is enabled'
|
|
353
|
+
);
|
|
354
|
+
throw new Error(); // unreachable
|
|
286
355
|
}
|
|
287
|
-
|
|
356
|
+
const avdDevice = caps.deviceName.replace(/[^a-zA-Z0-9_.]/g, '-');
|
|
288
357
|
this.opts.avd = `${avdDevice}__${caps.platformVersion}`;
|
|
289
358
|
}
|
|
290
359
|
}
|
|
291
360
|
|
|
292
|
-
async allocateSystemPort
|
|
293
|
-
const forwardPort = async (localPort) => {
|
|
294
|
-
this.log.debug(
|
|
361
|
+
async allocateSystemPort() {
|
|
362
|
+
const forwardPort = async (localPort: number) => {
|
|
363
|
+
this.log.debug(
|
|
364
|
+
`Forwarding UiAutomator2 Server port ${DEVICE_PORT} to local port ${localPort}`
|
|
365
|
+
);
|
|
295
366
|
if ((await checkPortStatus(localPort, LOCALHOST_IP4)) === 'open') {
|
|
296
|
-
this.log.errorAndThrow(
|
|
297
|
-
`
|
|
298
|
-
|
|
299
|
-
|
|
367
|
+
this.log.errorAndThrow(
|
|
368
|
+
`UiAutomator2 Server cannot start because the local port #${localPort} is busy. ` +
|
|
369
|
+
`Make sure the port you provide via 'systemPort' capability is not occupied. ` +
|
|
370
|
+
`This situation might often be a result of an inaccurate sessions management, e.g. ` +
|
|
371
|
+
`old automation sessions on the same device must always be closed before starting new ones.`
|
|
372
|
+
);
|
|
300
373
|
}
|
|
301
|
-
await this.adb
|
|
374
|
+
await this.adb!.forwardPort(localPort, DEVICE_PORT);
|
|
302
375
|
};
|
|
303
376
|
|
|
304
|
-
if (this.
|
|
377
|
+
if (this.systemPort) {
|
|
305
378
|
this._hasSystemPortInCaps = true;
|
|
306
|
-
return await forwardPort(this.
|
|
379
|
+
return await forwardPort(this.systemPort);
|
|
307
380
|
}
|
|
308
381
|
|
|
309
382
|
await DEVICE_PORT_ALLOCATION_GUARD(async () => {
|
|
310
383
|
const [startPort, endPort] = DEVICE_PORT_RANGE;
|
|
311
384
|
try {
|
|
312
|
-
this.
|
|
385
|
+
this.systemPort = await findAPortNotInUse(startPort, endPort);
|
|
313
386
|
} catch (e) {
|
|
314
387
|
this.log.errorAndThrow(
|
|
315
388
|
`Cannot find any free port in range ${startPort}..${endPort}}. ` +
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
389
|
+
`Please set the available port number by providing the systemPort capability or ` +
|
|
390
|
+
`double check the processes that are locking ports within this range and terminate ` +
|
|
391
|
+
`these which are not needed anymore`
|
|
392
|
+
);
|
|
393
|
+
throw new Error(); // unreachable
|
|
319
394
|
}
|
|
320
|
-
await forwardPort(this.
|
|
395
|
+
await forwardPort(this.systemPort);
|
|
321
396
|
});
|
|
322
397
|
}
|
|
323
398
|
|
|
324
|
-
async releaseSystemPort
|
|
325
|
-
if (!this.
|
|
399
|
+
async releaseSystemPort() {
|
|
400
|
+
if (!this.systemPort || !this.adb) {
|
|
326
401
|
return;
|
|
327
402
|
}
|
|
328
403
|
|
|
329
404
|
if (this._hasSystemPortInCaps) {
|
|
330
|
-
await this.adb.removePortForward(this.
|
|
405
|
+
await this.adb.removePortForward(this.systemPort);
|
|
331
406
|
} else {
|
|
332
|
-
await DEVICE_PORT_ALLOCATION_GUARD(
|
|
407
|
+
await DEVICE_PORT_ALLOCATION_GUARD(
|
|
408
|
+
async () => await this.adb!.removePortForward(this.systemPort!)
|
|
409
|
+
);
|
|
333
410
|
}
|
|
334
411
|
}
|
|
335
412
|
|
|
336
|
-
async allocateMjpegServerPort
|
|
413
|
+
async allocateMjpegServerPort() {
|
|
337
414
|
if (this.opts.mjpegServerPort) {
|
|
338
|
-
this.log.debug(
|
|
339
|
-
`
|
|
340
|
-
|
|
415
|
+
this.log.debug(
|
|
416
|
+
`MJPEG broadcasting requested, forwarding MJPEG server port ${MJPEG_SERVER_DEVICE_PORT} ` +
|
|
417
|
+
`to local port ${this.opts.mjpegServerPort}`
|
|
418
|
+
);
|
|
419
|
+
await this.adb!.forwardPort(this.opts.mjpegServerPort, MJPEG_SERVER_DEVICE_PORT);
|
|
341
420
|
}
|
|
342
421
|
}
|
|
343
422
|
|
|
344
|
-
async releaseMjpegServerPort
|
|
423
|
+
async releaseMjpegServerPort() {
|
|
345
424
|
if (this.opts.mjpegServerPort) {
|
|
346
|
-
await this.adb
|
|
425
|
+
await this.adb!.removePortForward(this.opts.mjpegServerPort);
|
|
347
426
|
}
|
|
348
427
|
}
|
|
349
428
|
|
|
350
|
-
async startUiAutomator2Session
|
|
429
|
+
async startUiAutomator2Session(
|
|
430
|
+
caps: Uiautomator2StartSessionOpts
|
|
431
|
+
): Promise<Uiautomator2SessionCaps> {
|
|
351
432
|
// get device udid for this session
|
|
352
|
-
|
|
433
|
+
const {udid, emPort} = await helpers.getDeviceInfoFromCaps(this.opts);
|
|
353
434
|
this.opts.udid = udid;
|
|
435
|
+
// @ts-expect-error do not put random stuff on opts
|
|
354
436
|
this.opts.emPort = emPort;
|
|
355
437
|
|
|
356
438
|
// now that we know our java version and device info, we can create our
|
|
@@ -360,11 +442,14 @@ class AndroidUiautomator2Driver extends BaseDriver {
|
|
|
360
442
|
const apiLevel = await this.adb.getApiLevel();
|
|
361
443
|
|
|
362
444
|
if (apiLevel < 21) {
|
|
363
|
-
this.log.errorAndThrow(
|
|
364
|
-
'
|
|
445
|
+
this.log.errorAndThrow(
|
|
446
|
+
'UIAutomator2 is only supported since Android 5.0 (Lollipop). ' +
|
|
447
|
+
'You could still use other supported backends in order to automate older Android versions.'
|
|
448
|
+
);
|
|
365
449
|
}
|
|
366
450
|
|
|
367
|
-
if (apiLevel >= 28) {
|
|
451
|
+
if (apiLevel >= 28) {
|
|
452
|
+
// Android P
|
|
368
453
|
this.log.info('Relaxing hidden api policy');
|
|
369
454
|
await this.adb.setHiddenApiPolicy('1', !!this.opts.ignoreHiddenApiPolicyError);
|
|
370
455
|
}
|
|
@@ -372,7 +457,9 @@ class AndroidUiautomator2Driver extends BaseDriver {
|
|
|
372
457
|
// check if we have to enable/disable gps before running the application
|
|
373
458
|
if (util.hasValue(this.opts.gpsEnabled)) {
|
|
374
459
|
if (this.isEmulator()) {
|
|
375
|
-
this.log.info(
|
|
460
|
+
this.log.info(
|
|
461
|
+
`Trying to ${this.opts.gpsEnabled ? 'enable' : 'disable'} gps location provider`
|
|
462
|
+
);
|
|
376
463
|
await this.adb.toggleGPSLocationProvider(this.opts.gpsEnabled);
|
|
377
464
|
} else {
|
|
378
465
|
this.log.warn(`Sorry! 'gpsEnabled' capability is only available for emulators`);
|
|
@@ -382,18 +469,25 @@ class AndroidUiautomator2Driver extends BaseDriver {
|
|
|
382
469
|
// get appPackage et al from manifest if necessary
|
|
383
470
|
const appInfo = await helpers.getLaunchInfo(this.adb, this.opts);
|
|
384
471
|
// and get it onto our 'opts' object so we use it from now on
|
|
385
|
-
|
|
472
|
+
this.opts = {...this.opts, ...(appInfo ?? {})};
|
|
386
473
|
|
|
387
474
|
// set actual device name, udid, platform version, screen size, screen density, model and manufacturer details
|
|
388
|
-
|
|
389
|
-
|
|
475
|
+
const sessionInfo: Uiautomator2SessionInfo = {
|
|
476
|
+
deviceName: this.adb.curDeviceId!,
|
|
477
|
+
deviceUDID: this.opts.udid!,
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
const capsWithSessionInfo = {
|
|
481
|
+
...caps,
|
|
482
|
+
...sessionInfo,
|
|
483
|
+
};
|
|
390
484
|
|
|
391
485
|
// start an avd, set the language/locale, pick an emulator, etc...
|
|
392
486
|
// TODO with multiple devices we'll need to parameterize this
|
|
393
487
|
await helpers.initDevice(this.adb, this.opts);
|
|
394
488
|
|
|
395
489
|
// Prepare the device by forwarding the UiAutomator2 port
|
|
396
|
-
// This call mutates this.
|
|
490
|
+
// This call mutates this.systemPort if it is not set explicitly
|
|
397
491
|
await this.allocateSystemPort();
|
|
398
492
|
|
|
399
493
|
// Prepare the device by forwarding the UiAutomator2 MJPEG server port (if
|
|
@@ -401,10 +495,11 @@ class AndroidUiautomator2Driver extends BaseDriver {
|
|
|
401
495
|
await this.allocateMjpegServerPort();
|
|
402
496
|
|
|
403
497
|
// set up the modified UiAutomator2 server etc
|
|
404
|
-
await this.initUiAutomator2Server();
|
|
498
|
+
const uiautomator2 = await this.initUiAutomator2Server();
|
|
405
499
|
|
|
406
500
|
// Should be after installing io.appium.settings in helpers.initDevice
|
|
407
|
-
if (this.opts.disableWindowAnimation && (await this.adb.getApiLevel() < 26)
|
|
501
|
+
if (this.opts.disableWindowAnimation && (await this.adb.getApiLevel()) < 26) {
|
|
502
|
+
// API level 26 is Android 8.0.
|
|
408
503
|
// Granting android.permission.SET_ANIMATION_SCALE is necessary to handle animations under API level 26
|
|
409
504
|
// Read https://github.com/appium/appium/pull/11640#issuecomment-438260477
|
|
410
505
|
// `--no-window-animation` works over Android 8 to disable all of animations
|
|
@@ -422,25 +517,29 @@ class AndroidUiautomator2Driver extends BaseDriver {
|
|
|
422
517
|
await this.initAUT();
|
|
423
518
|
|
|
424
519
|
// Adding AUT package name in the capabilities if package name not exist in caps
|
|
425
|
-
if (!
|
|
426
|
-
|
|
520
|
+
if (!capsWithSessionInfo.appPackage && appInfo) {
|
|
521
|
+
capsWithSessionInfo.appPackage = appInfo.appPackage;
|
|
427
522
|
}
|
|
428
523
|
|
|
429
524
|
// launch UiAutomator2 and wait till its online and we have a session
|
|
430
|
-
await
|
|
525
|
+
await uiautomator2.startSession(capsWithSessionInfo);
|
|
431
526
|
|
|
432
|
-
|
|
527
|
+
const capsWithSessionAndDeviceInfo = {
|
|
528
|
+
...capsWithSessionInfo,
|
|
529
|
+
...(await this.getDeviceInfoFromUia2()),
|
|
530
|
+
};
|
|
433
531
|
|
|
434
532
|
// Unlock the device after the session is started.
|
|
435
533
|
if (!this.opts.skipUnlock) {
|
|
436
534
|
// unlock the device to prepare it for testing
|
|
437
|
-
await helpers.unlock(this, this.adb, this.caps);
|
|
535
|
+
await helpers.unlock(this as any, this.adb, this.caps);
|
|
438
536
|
} else {
|
|
439
537
|
this.log.debug(`'skipUnlock' capability set, so skipping device unlock`);
|
|
440
538
|
}
|
|
441
539
|
|
|
442
|
-
if (this.isChromeSession) {
|
|
443
|
-
|
|
540
|
+
if (this.isChromeSession) {
|
|
541
|
+
// start a chromedriver session
|
|
542
|
+
await this.startChromeSession();
|
|
444
543
|
} else if (this.opts.autoLaunch && this.opts.appPackage) {
|
|
445
544
|
await this.ensureAppStarts();
|
|
446
545
|
}
|
|
@@ -448,7 +547,7 @@ class AndroidUiautomator2Driver extends BaseDriver {
|
|
|
448
547
|
// if the initial orientation is requested, set it
|
|
449
548
|
if (util.hasValue(this.opts.orientation)) {
|
|
450
549
|
this.log.debug(`Setting initial orientation to '${this.opts.orientation}'`);
|
|
451
|
-
await this.setOrientation(this.opts.orientation);
|
|
550
|
+
await this.setOrientation(this.opts.orientation as Orientation);
|
|
452
551
|
}
|
|
453
552
|
|
|
454
553
|
// if we want to immediately get into a webview, set our context
|
|
@@ -463,30 +562,29 @@ class AndroidUiautomator2Driver extends BaseDriver {
|
|
|
463
562
|
// now that everything has started successfully, turn on proxying so all
|
|
464
563
|
// subsequent session requests go straight to/from uiautomator2
|
|
465
564
|
this.jwpProxyActive = true;
|
|
565
|
+
|
|
566
|
+
return {...capsWithSessionAndDeviceInfo, ...(await this.getDeviceDetails())};
|
|
466
567
|
}
|
|
467
568
|
|
|
468
|
-
async
|
|
469
|
-
const {
|
|
470
|
-
|
|
569
|
+
async getDeviceInfoFromUia2(): Promise<Uiautomator2DeviceInfo> {
|
|
570
|
+
const {apiVersion, platformVersion, manufacturer, model, realDisplaySize, displayDensity} =
|
|
571
|
+
await this.mobileGetDeviceInfo();
|
|
572
|
+
return {
|
|
573
|
+
deviceApiLevel: _.parseInt(apiVersion),
|
|
471
574
|
platformVersion,
|
|
472
|
-
manufacturer,
|
|
473
|
-
model,
|
|
474
|
-
realDisplaySize,
|
|
475
|
-
displayDensity,
|
|
476
|
-
}
|
|
477
|
-
this.caps.deviceApiLevel = parseInt(apiVersion, 10);
|
|
478
|
-
this.caps.platformVersion = platformVersion;
|
|
479
|
-
this.caps.deviceScreenSize = realDisplaySize;
|
|
480
|
-
this.caps.deviceScreenDensity = displayDensity;
|
|
481
|
-
this.caps.deviceModel = model;
|
|
482
|
-
this.caps.deviceManufacturer = manufacturer;
|
|
575
|
+
deviceManufacturer: manufacturer,
|
|
576
|
+
deviceModel: model,
|
|
577
|
+
deviceScreenSize: realDisplaySize,
|
|
578
|
+
deviceScreenDensity: displayDensity,
|
|
579
|
+
};
|
|
483
580
|
}
|
|
484
581
|
|
|
485
|
-
async initUiAutomator2Server
|
|
582
|
+
async initUiAutomator2Server() {
|
|
486
583
|
// broken out for readability
|
|
487
584
|
const uiautomator2Opts = {
|
|
585
|
+
// @ts-expect-error FIXME: maybe `address` instead of `host`?
|
|
488
586
|
host: this.opts.remoteAdbHost || this.opts.host || LOCALHOST_IP4,
|
|
489
|
-
systemPort: this.
|
|
587
|
+
systemPort: this.systemPort,
|
|
490
588
|
devicePort: DEVICE_PORT,
|
|
491
589
|
adb: this.adb,
|
|
492
590
|
apk: this.opts.app,
|
|
@@ -501,28 +599,37 @@ class AndroidUiautomator2Driver extends BaseDriver {
|
|
|
501
599
|
// uiautomator2 with the appropriate options
|
|
502
600
|
this.uiautomator2 = new UiAutomator2Server(this.log, uiautomator2Opts);
|
|
503
601
|
this.proxyReqRes = this.uiautomator2.proxyReqRes.bind(this.uiautomator2);
|
|
504
|
-
this.proxyCommand = this.uiautomator2.proxyCommand.bind(
|
|
602
|
+
this.proxyCommand = this.uiautomator2.proxyCommand.bind(
|
|
603
|
+
this.uiautomator2
|
|
604
|
+
) as typeof this.proxyCommand;
|
|
505
605
|
|
|
506
606
|
if (this.opts.skipServerInstallation) {
|
|
507
607
|
this.log.info(`'skipServerInstallation' is set. Skipping UIAutomator2 server installation.`);
|
|
508
608
|
} else {
|
|
509
609
|
await this.uiautomator2.installServerApk(this.opts.uiautomator2ServerInstallTimeout);
|
|
510
610
|
try {
|
|
511
|
-
await this.adb
|
|
512
|
-
SETTINGS_HELPER_PKG_ID,
|
|
611
|
+
await this.adb!.addToDeviceIdleWhitelist(
|
|
612
|
+
SETTINGS_HELPER_PKG_ID,
|
|
613
|
+
SERVER_PACKAGE_ID,
|
|
614
|
+
SERVER_TEST_PACKAGE_ID
|
|
513
615
|
);
|
|
514
616
|
} catch (e) {
|
|
515
|
-
|
|
516
|
-
|
|
617
|
+
const err = e as ExecError;
|
|
618
|
+
this.log.warn(
|
|
619
|
+
`Cannot add server packages to the Doze whitelist. Original error: ` +
|
|
620
|
+
(err.stderr || err.message)
|
|
621
|
+
);
|
|
517
622
|
}
|
|
518
623
|
}
|
|
624
|
+
|
|
625
|
+
return this.uiautomator2;
|
|
519
626
|
}
|
|
520
627
|
|
|
521
|
-
async initAUT
|
|
628
|
+
async initAUT() {
|
|
522
629
|
// Uninstall any uninstallOtherPackages which were specified in caps
|
|
523
630
|
if (this.opts.uninstallOtherPackages) {
|
|
524
631
|
await helpers.uninstallOtherPackages(
|
|
525
|
-
this.adb
|
|
632
|
+
this.adb!,
|
|
526
633
|
helpers.parseArray(this.opts.uninstallOtherPackages),
|
|
527
634
|
[SETTINGS_HELPER_PKG_ID, SERVER_PACKAGE_ID, SERVER_TEST_PACKAGE_ID]
|
|
528
635
|
);
|
|
@@ -534,61 +641,80 @@ class AndroidUiautomator2Driver extends BaseDriver {
|
|
|
534
641
|
try {
|
|
535
642
|
otherApps = helpers.parseArray(this.opts.otherApps);
|
|
536
643
|
} catch (e) {
|
|
537
|
-
this.log.errorAndThrow(`Could not parse "otherApps" capability: ${e.message}`);
|
|
644
|
+
this.log.errorAndThrow(`Could not parse "otherApps" capability: ${(e as Error).message}`);
|
|
645
|
+
throw new Error(); // unrechable
|
|
538
646
|
}
|
|
539
|
-
otherApps = await B.all(
|
|
540
|
-
.map((app) => this.helpers.configureApp(app, [APK_EXTENSION, APKS_EXTENSION]))
|
|
541
|
-
|
|
647
|
+
otherApps = await B.all(
|
|
648
|
+
otherApps.map((app) => this.helpers.configureApp(app, [APK_EXTENSION, APKS_EXTENSION]))
|
|
649
|
+
);
|
|
650
|
+
await helpers.installOtherApks(otherApps, this.adb!, this.opts);
|
|
542
651
|
}
|
|
543
652
|
|
|
544
653
|
if (this.opts.app) {
|
|
545
|
-
if (
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
654
|
+
if (
|
|
655
|
+
(this.opts.noReset && !(await this.adb!.isAppInstalled(this.opts.appPackage!))) ||
|
|
656
|
+
!this.opts.noReset
|
|
657
|
+
) {
|
|
658
|
+
if (
|
|
659
|
+
!this.opts.noSign &&
|
|
660
|
+
!(await this.adb!.checkApkCert(this.opts.app, this.opts.appPackage, {
|
|
661
|
+
requireDefaultCert: false,
|
|
662
|
+
}))
|
|
663
|
+
) {
|
|
664
|
+
await helpers.signApp(this.adb!, this.opts.app);
|
|
551
665
|
}
|
|
552
666
|
if (!this.opts.skipUninstall) {
|
|
553
|
-
await this.adb
|
|
667
|
+
await this.adb!.uninstallApk(this.opts.appPackage!);
|
|
554
668
|
}
|
|
555
|
-
await helpers.installApk(this.adb
|
|
669
|
+
await helpers.installApk(this.adb!, this.opts);
|
|
556
670
|
} else {
|
|
557
|
-
this.log.debug(
|
|
671
|
+
this.log.debug(
|
|
672
|
+
'noReset has been requested and the app is already installed. Doing nothing'
|
|
673
|
+
);
|
|
558
674
|
}
|
|
559
675
|
} else {
|
|
560
676
|
if (this.opts.fullReset) {
|
|
561
|
-
this.log.errorAndThrow(
|
|
677
|
+
this.log.errorAndThrow(
|
|
678
|
+
'Full reset requires an app capability, use fastReset if app is not provided'
|
|
679
|
+
);
|
|
562
680
|
}
|
|
563
681
|
this.log.debug('No app capability. Assuming it is already on the device');
|
|
564
682
|
if (this.opts.fastReset && this.opts.appPackage) {
|
|
565
|
-
await helpers.resetApp(this.adb
|
|
683
|
+
await helpers.resetApp(this.adb!, this.opts);
|
|
566
684
|
}
|
|
567
685
|
}
|
|
568
686
|
}
|
|
569
687
|
|
|
570
|
-
async ensureAppStarts
|
|
688
|
+
async ensureAppStarts() {
|
|
571
689
|
// make sure we have an activity and package to wait for
|
|
572
690
|
const appWaitPackage = this.opts.appWaitPackage || this.opts.appPackage;
|
|
573
691
|
const appWaitActivity = this.opts.appWaitActivity || this.opts.appActivity;
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
692
|
+
this.log.info(
|
|
693
|
+
`Starting '${this.opts.appPackage}/${this.opts.appActivity} ` +
|
|
694
|
+
`and waiting for '${appWaitPackage}/${appWaitActivity}'`
|
|
695
|
+
);
|
|
577
696
|
|
|
578
697
|
if (this.caps.androidCoverage) {
|
|
579
|
-
this.log.info(
|
|
580
|
-
`
|
|
581
|
-
|
|
698
|
+
this.log.info(
|
|
699
|
+
`androidCoverage is configured. ` +
|
|
700
|
+
` Starting instrumentation of '${this.caps.androidCoverage}'...`
|
|
701
|
+
);
|
|
702
|
+
await this.adb!.androidCoverage(this.caps.androidCoverage, appWaitPackage!, appWaitActivity!);
|
|
582
703
|
return;
|
|
583
704
|
}
|
|
584
|
-
if (
|
|
585
|
-
|
|
586
|
-
this.
|
|
587
|
-
|
|
705
|
+
if (
|
|
706
|
+
this.opts.noReset &&
|
|
707
|
+
!this.opts.forceAppLaunch &&
|
|
708
|
+
(await this.adb!.processExists(this.opts.appPackage!))
|
|
709
|
+
) {
|
|
710
|
+
this.log.info(
|
|
711
|
+
`'${this.opts.appPackage}' is already running and noReset is enabled. ` +
|
|
712
|
+
`Set forceAppLaunch capability to true if the app must be forcefully restarted on session startup.`
|
|
713
|
+
);
|
|
588
714
|
return;
|
|
589
715
|
}
|
|
590
|
-
await this.adb
|
|
591
|
-
pkg: this.opts.appPackage
|
|
716
|
+
await this.adb!.startApp({
|
|
717
|
+
pkg: this.opts.appPackage!,
|
|
592
718
|
activity: this.opts.appActivity,
|
|
593
719
|
action: this.opts.intentAction || 'android.intent.action.MAIN',
|
|
594
720
|
category: this.opts.intentCategory || 'android.intent.category.LAUNCHER',
|
|
@@ -604,22 +730,26 @@ class AndroidUiautomator2Driver extends BaseDriver {
|
|
|
604
730
|
});
|
|
605
731
|
}
|
|
606
732
|
|
|
607
|
-
async deleteSession
|
|
733
|
+
async deleteSession() {
|
|
608
734
|
this.log.debug('Deleting UiAutomator2 session');
|
|
609
735
|
|
|
610
|
-
const screenRecordingStopTasks = [
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
736
|
+
const screenRecordingStopTasks = [
|
|
737
|
+
async () => {
|
|
738
|
+
if (!_.isEmpty(this._screenRecordingProperties)) {
|
|
739
|
+
await this.stopRecordingScreen();
|
|
740
|
+
}
|
|
741
|
+
},
|
|
742
|
+
async () => {
|
|
743
|
+
if (await this.mobileIsMediaProjectionRecordingRunning()) {
|
|
744
|
+
await this.mobileStopMediaProjectionRecording();
|
|
745
|
+
}
|
|
746
|
+
},
|
|
747
|
+
async () => {
|
|
748
|
+
if (!_.isEmpty(this._screenStreamingProps)) {
|
|
749
|
+
await this.mobileStopScreenStreaming();
|
|
750
|
+
}
|
|
751
|
+
},
|
|
752
|
+
];
|
|
623
753
|
|
|
624
754
|
await androidHelpers.removeAllSessionWebSocketHandlers(this.server, this.sessionId);
|
|
625
755
|
|
|
@@ -627,54 +757,65 @@ class AndroidUiautomator2Driver extends BaseDriver {
|
|
|
627
757
|
try {
|
|
628
758
|
await this.stopChromedriverProxies();
|
|
629
759
|
} catch (err) {
|
|
630
|
-
this.log.warn(`Unable to stop ChromeDriver proxies: ${err.message}`);
|
|
760
|
+
this.log.warn(`Unable to stop ChromeDriver proxies: ${(err as Error).message}`);
|
|
631
761
|
}
|
|
632
762
|
if (this.jwpProxyActive) {
|
|
633
763
|
try {
|
|
634
764
|
await this.uiautomator2.deleteSession();
|
|
635
765
|
} catch (err) {
|
|
636
|
-
this.log.warn(`Unable to proxy deleteSession to UiAutomator2: ${err.message}`);
|
|
766
|
+
this.log.warn(`Unable to proxy deleteSession to UiAutomator2: ${(err as Error).message}`);
|
|
637
767
|
}
|
|
638
768
|
}
|
|
639
|
-
this.uiautomator2 =
|
|
769
|
+
this.uiautomator2 = undefined;
|
|
640
770
|
}
|
|
641
771
|
this.jwpProxyActive = false;
|
|
642
772
|
|
|
643
773
|
if (this.adb) {
|
|
644
|
-
await B.all(
|
|
645
|
-
(
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
774
|
+
await B.all(
|
|
775
|
+
screenRecordingStopTasks.map((task) => {
|
|
776
|
+
(async () => {
|
|
777
|
+
try {
|
|
778
|
+
await task();
|
|
779
|
+
} catch (ign) {}
|
|
780
|
+
})();
|
|
781
|
+
})
|
|
782
|
+
);
|
|
651
783
|
|
|
652
784
|
if (this.caps.androidCoverage) {
|
|
653
785
|
this.log.info('Shutting down the adb process of instrumentation...');
|
|
654
786
|
await this.adb.endAndroidCoverage();
|
|
655
787
|
// Use this broadcast intent to notify it's time to dump coverage to file
|
|
656
788
|
if (this.caps.androidCoverageEndIntent) {
|
|
657
|
-
this.log.info(
|
|
789
|
+
this.log.info(
|
|
790
|
+
`Sending intent broadcast '${this.caps.androidCoverageEndIntent}' at the end of instrumenting.`
|
|
791
|
+
);
|
|
658
792
|
await this.adb.broadcast(this.caps.androidCoverageEndIntent);
|
|
659
793
|
} else {
|
|
660
|
-
this.log.warn(
|
|
794
|
+
this.log.warn(
|
|
795
|
+
'No androidCoverageEndIntent is configured in caps. Possibly you cannot get coverage file.'
|
|
796
|
+
);
|
|
661
797
|
}
|
|
662
798
|
}
|
|
663
799
|
if (this.opts.appPackage) {
|
|
664
|
-
if (
|
|
665
|
-
|
|
800
|
+
if (
|
|
801
|
+
!this.isChromeSession &&
|
|
802
|
+
((!this.opts.dontStopAppOnReset && !this.opts.noReset) ||
|
|
803
|
+
(this.opts.noReset && this.opts.shouldTerminateApp))
|
|
804
|
+
) {
|
|
666
805
|
try {
|
|
667
806
|
await this.adb.forceStop(this.opts.appPackage);
|
|
668
807
|
} catch (err) {
|
|
669
|
-
this.log.warn(`Unable to force stop app: ${err.message}`);
|
|
808
|
+
this.log.warn(`Unable to force stop app: ${(err as Error).message}`);
|
|
670
809
|
}
|
|
671
810
|
}
|
|
672
811
|
if (this.opts.fullReset && !this.opts.skipUninstall) {
|
|
673
|
-
this.log.debug(
|
|
812
|
+
this.log.debug(
|
|
813
|
+
`Capability 'fullReset' set to 'true', Uninstalling '${this.opts.appPackage}'`
|
|
814
|
+
);
|
|
674
815
|
try {
|
|
675
816
|
await this.adb.uninstallApk(this.opts.appPackage);
|
|
676
817
|
} catch (err) {
|
|
677
|
-
this.log.warn(`Unable to uninstall app: ${err.message}`);
|
|
818
|
+
this.log.warn(`Unable to uninstall app: ${(err as Error).message}`);
|
|
678
819
|
}
|
|
679
820
|
}
|
|
680
821
|
}
|
|
@@ -687,30 +828,32 @@ class AndroidUiautomator2Driver extends BaseDriver {
|
|
|
687
828
|
try {
|
|
688
829
|
await this.releaseSystemPort();
|
|
689
830
|
} catch (error) {
|
|
690
|
-
this.log.warn(`Unable to remove system port forward: ${error.message}`);
|
|
831
|
+
this.log.warn(`Unable to remove system port forward: ${(error as Error).message}`);
|
|
691
832
|
// Ignore, this block will also be called when we fall in catch block
|
|
692
833
|
// and before even port forward.
|
|
693
834
|
}
|
|
694
835
|
try {
|
|
695
836
|
await this.releaseMjpegServerPort();
|
|
696
837
|
} catch (error) {
|
|
697
|
-
this.log.warn(`Unable to remove MJPEG server port forward: ${error.message}`);
|
|
838
|
+
this.log.warn(`Unable to remove MJPEG server port forward: ${(error as Error).message}`);
|
|
698
839
|
// Ignore, this block will also be called when we fall in catch block
|
|
699
840
|
// and before even port forward.
|
|
700
841
|
}
|
|
701
842
|
|
|
702
|
-
if (await this.adb.getApiLevel() >= 28) {
|
|
843
|
+
if ((await this.adb.getApiLevel()) >= 28) {
|
|
844
|
+
// Android P
|
|
703
845
|
this.log.info('Restoring hidden api policy to the device default configuration');
|
|
704
846
|
await this.adb.setDefaultHiddenApiPolicy(!!this.opts.ignoreHiddenApiPolicyError);
|
|
705
847
|
}
|
|
706
848
|
|
|
849
|
+
// @ts-expect-error unknown option
|
|
707
850
|
if (this.opts.reboot) {
|
|
708
|
-
|
|
851
|
+
const avdName = this.opts.avd!.replace('@', '');
|
|
709
852
|
this.log.debug(`Closing emulator '${avdName}'`);
|
|
710
853
|
try {
|
|
711
854
|
await this.adb.killEmulator(avdName);
|
|
712
855
|
} catch (err) {
|
|
713
|
-
this.log.warn(`Unable to close emulator: ${err.message}`);
|
|
856
|
+
this.log.warn(`Unable to close emulator: ${(err as Error).message}`);
|
|
714
857
|
}
|
|
715
858
|
}
|
|
716
859
|
}
|
|
@@ -718,47 +861,35 @@ class AndroidUiautomator2Driver extends BaseDriver {
|
|
|
718
861
|
this.log.info('Closing MJPEG stream');
|
|
719
862
|
this.mjpegStream.stop();
|
|
720
863
|
}
|
|
721
|
-
await
|
|
864
|
+
await BaseDriver.prototype.deleteSession.call(this);
|
|
722
865
|
}
|
|
723
866
|
|
|
724
|
-
async checkAppPresent
|
|
867
|
+
async checkAppPresent() {
|
|
725
868
|
this.log.debug('Checking whether app is actually present');
|
|
726
869
|
if (!(await fs.exists(this.opts.app))) {
|
|
727
870
|
this.log.errorAndThrow(`Could not find app apk at '${this.opts.app}'`);
|
|
871
|
+
throw new Error(); // unreachable
|
|
728
872
|
}
|
|
729
873
|
}
|
|
730
874
|
|
|
731
|
-
async onSettingsUpdate
|
|
875
|
+
async onSettingsUpdate() {
|
|
732
876
|
// intentionally do nothing here, since commands.updateSettings proxies
|
|
733
877
|
// settings to the uiauto2 server already
|
|
734
878
|
}
|
|
735
879
|
|
|
736
|
-
//
|
|
737
|
-
|
|
738
|
-
// port
|
|
739
|
-
async wrapBootstrapDisconnect (wrapped) {
|
|
740
|
-
await wrapped();
|
|
741
|
-
await this.adb.restart();
|
|
742
|
-
await this.allocateSystemPort();
|
|
743
|
-
await this.allocateMjpegServerPort();
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
proxyActive (sessionId) {
|
|
747
|
-
super.proxyActive(sessionId);
|
|
748
|
-
|
|
880
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
881
|
+
proxyActive(sessionId: string) {
|
|
749
882
|
// we always have an active proxy to the UiAutomator2 server
|
|
750
883
|
return true;
|
|
751
884
|
}
|
|
752
885
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
886
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
887
|
+
canProxy(sessionId: string) {
|
|
756
888
|
// we can always proxy to the uiautomator2 server
|
|
757
889
|
return true;
|
|
758
890
|
}
|
|
759
891
|
|
|
760
|
-
getProxyAvoidList
|
|
761
|
-
super.getProxyAvoidList(sessionId);
|
|
892
|
+
getProxyAvoidList() {
|
|
762
893
|
// we are maintaining two sets of NO_PROXY lists, one for chromedriver(CHROME_NO_PROXY)
|
|
763
894
|
// and one for uiautomator2(NO_PROXY), based on current context will return related NO_PROXY list
|
|
764
895
|
if (util.hasValue(this.chromedriver)) {
|
|
@@ -768,26 +899,30 @@ class AndroidUiautomator2Driver extends BaseDriver {
|
|
|
768
899
|
this.jwpProxyAvoid = NO_PROXY;
|
|
769
900
|
}
|
|
770
901
|
if (this.opts.nativeWebScreenshot) {
|
|
771
|
-
this.jwpProxyAvoid = [
|
|
902
|
+
this.jwpProxyAvoid = [
|
|
903
|
+
...this.jwpProxyAvoid,
|
|
904
|
+
['GET', new RegExp('^/session/[^/]+/screenshot')],
|
|
905
|
+
];
|
|
772
906
|
}
|
|
773
907
|
|
|
774
908
|
return this.jwpProxyAvoid;
|
|
775
909
|
}
|
|
776
910
|
|
|
777
|
-
|
|
778
|
-
|
|
911
|
+
async updateSettings(settings: Uiautomator2Settings) {
|
|
912
|
+
await this.settings.update(settings);
|
|
913
|
+
await this.uiautomator2!.jwproxy.command('/appium/settings', 'POST', {settings});
|
|
779
914
|
}
|
|
780
|
-
}
|
|
781
915
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
916
|
+
async getSettings() {
|
|
917
|
+
const driverSettings = this.settings.getSettings();
|
|
918
|
+
const serverSettings = (await this.uiautomator2!.jwproxy.command(
|
|
919
|
+
'/appium/settings',
|
|
920
|
+
'GET'
|
|
921
|
+
)) as Partial<Uiautomator2Settings>;
|
|
922
|
+
return {...driverSettings, ...serverSettings} as any;
|
|
923
|
+
}
|
|
785
924
|
}
|
|
786
925
|
|
|
787
|
-
|
|
788
|
-
for (let [cmd, fn] of _.toPairs(commands)) {
|
|
789
|
-
AndroidUiautomator2Driver.prototype[cmd] = fn;
|
|
790
|
-
}
|
|
926
|
+
import './commands';
|
|
791
927
|
|
|
792
|
-
export {
|
|
793
|
-
export default AndroidUiautomator2Driver;
|
|
928
|
+
export {AndroidUiautomator2Driver};
|