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