appium-uiautomator2-driver 2.29.11 → 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.
Files changed (135) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/build/index.d.ts +4 -0
  3. package/build/index.d.ts.map +1 -0
  4. package/build/index.js +8 -15
  5. package/build/index.js.map +1 -0
  6. package/build/lib/commands/actions.d.ts +2 -0
  7. package/build/lib/commands/actions.d.ts.map +1 -0
  8. package/build/lib/commands/actions.js +67 -62
  9. package/build/lib/commands/actions.js.map +1 -1
  10. package/build/lib/commands/alert.d.ts +2 -0
  11. package/build/lib/commands/alert.d.ts.map +1 -0
  12. package/build/lib/commands/alert.js +28 -26
  13. package/build/lib/commands/alert.js.map +1 -1
  14. package/build/lib/commands/app-strings.d.ts +3 -0
  15. package/build/lib/commands/app-strings.d.ts.map +1 -0
  16. package/build/lib/commands/app-strings.js +86 -57
  17. package/build/lib/commands/app-strings.js.map +1 -1
  18. package/build/lib/commands/battery.d.ts +2 -0
  19. package/build/lib/commands/battery.d.ts.map +1 -0
  20. package/build/lib/commands/battery.js +26 -16
  21. package/build/lib/commands/battery.js.map +1 -1
  22. package/build/lib/commands/element.d.ts +2 -0
  23. package/build/lib/commands/element.d.ts.map +1 -0
  24. package/build/lib/commands/element.js +140 -159
  25. package/build/lib/commands/element.js.map +1 -1
  26. package/build/lib/commands/find.d.ts +2 -0
  27. package/build/lib/commands/find.d.ts.map +1 -0
  28. package/build/lib/commands/find.js +39 -25
  29. package/build/lib/commands/find.js.map +1 -1
  30. package/build/lib/commands/general.d.ts +4 -0
  31. package/build/lib/commands/general.d.ts.map +1 -0
  32. package/build/lib/commands/general.js +217 -215
  33. package/build/lib/commands/general.js.map +1 -1
  34. package/build/lib/commands/gestures.d.ts +2 -0
  35. package/build/lib/commands/gestures.d.ts.map +1 -0
  36. package/build/lib/commands/gestures.js +206 -193
  37. package/build/lib/commands/gestures.js.map +1 -1
  38. package/build/lib/commands/index.d.ts +2 -0
  39. package/build/lib/commands/index.d.ts.map +1 -0
  40. package/build/lib/commands/index.js +13 -22
  41. package/build/lib/commands/index.js.map +1 -1
  42. package/build/lib/commands/mixins.d.ts +84 -0
  43. package/build/lib/commands/mixins.d.ts.map +1 -0
  44. package/build/lib/commands/mixins.js +23 -0
  45. package/build/lib/commands/mixins.js.map +1 -0
  46. package/build/lib/commands/screenshot.d.ts +2 -0
  47. package/build/lib/commands/screenshot.d.ts.map +1 -0
  48. package/build/lib/commands/screenshot.js +77 -62
  49. package/build/lib/commands/screenshot.js.map +1 -1
  50. package/build/lib/commands/touch.d.ts +2 -0
  51. package/build/lib/commands/touch.d.ts.map +1 -0
  52. package/build/lib/commands/touch.js +48 -38
  53. package/build/lib/commands/touch.js.map +1 -1
  54. package/build/lib/commands/types.d.ts +452 -0
  55. package/build/lib/commands/types.d.ts.map +1 -0
  56. package/build/lib/commands/types.js +3 -0
  57. package/build/lib/commands/types.js.map +1 -0
  58. package/build/lib/commands/viewport.d.ts +2 -0
  59. package/build/lib/commands/viewport.d.ts.map +1 -0
  60. package/build/lib/commands/viewport.js +37 -35
  61. package/build/lib/commands/viewport.js.map +1 -1
  62. package/build/lib/constraints.d.ts +334 -0
  63. package/build/lib/constraints.d.ts.map +1 -0
  64. package/build/lib/constraints.js +51 -0
  65. package/build/lib/constraints.js.map +1 -0
  66. package/build/lib/css-converter.d.ts +45 -0
  67. package/build/lib/css-converter.d.ts.map +1 -0
  68. package/build/lib/css-converter.js +272 -175
  69. package/build/lib/css-converter.js.map +1 -1
  70. package/build/lib/driver.d.ts +912 -0
  71. package/build/lib/driver.d.ts.map +1 -0
  72. package/build/lib/driver.js +738 -482
  73. package/build/lib/driver.js.map +1 -1
  74. package/build/lib/execute-method-map.d.ts +477 -0
  75. package/build/lib/execute-method-map.d.ts.map +1 -0
  76. package/build/lib/execute-method-map.js +542 -0
  77. package/build/lib/execute-method-map.js.map +1 -0
  78. package/build/lib/extensions.d.ts +3 -0
  79. package/build/lib/extensions.d.ts.map +1 -0
  80. package/build/lib/extensions.js +7 -9
  81. package/build/lib/extensions.js.map +1 -1
  82. package/build/lib/helpers.d.ts +7 -0
  83. package/build/lib/helpers.d.ts.map +1 -0
  84. package/build/lib/helpers.js +36 -29
  85. package/build/lib/helpers.js.map +1 -1
  86. package/build/lib/logger.d.ts +3 -0
  87. package/build/lib/logger.d.ts.map +1 -0
  88. package/build/lib/logger.js +5 -10
  89. package/build/lib/logger.js.map +1 -1
  90. package/build/lib/method-map.d.ts +389 -0
  91. package/build/lib/method-map.d.ts.map +1 -0
  92. package/build/lib/method-map.js +11 -17
  93. package/build/lib/method-map.js.map +1 -1
  94. package/build/lib/types.d.ts +45 -0
  95. package/build/lib/types.d.ts.map +1 -0
  96. package/build/lib/types.js +3 -0
  97. package/build/lib/types.js.map +1 -0
  98. package/build/lib/uiautomator2.d.ts +45 -0
  99. package/build/lib/uiautomator2.d.ts.map +1 -0
  100. package/build/lib/uiautomator2.js +340 -299
  101. package/build/lib/uiautomator2.js.map +1 -1
  102. package/build/lib/utils.d.ts +10 -0
  103. package/build/lib/utils.d.ts.map +1 -0
  104. package/build/lib/utils.js +23 -16
  105. package/build/lib/utils.js.map +1 -1
  106. package/build/tsconfig.tsbuildinfo +1 -0
  107. package/index.js +5 -3
  108. package/lib/commands/actions.js +115 -101
  109. package/lib/commands/alert.js +36 -44
  110. package/lib/commands/app-strings.js +79 -58
  111. package/lib/commands/battery.js +27 -28
  112. package/lib/commands/element.js +231 -134
  113. package/lib/commands/find.js +40 -21
  114. package/lib/commands/general.js +271 -336
  115. package/lib/commands/gestures.js +252 -366
  116. package/lib/commands/index.js +11 -31
  117. package/lib/commands/mixins.ts +167 -0
  118. package/lib/commands/screenshot.js +80 -76
  119. package/lib/commands/touch.js +64 -31
  120. package/lib/commands/types.ts +473 -0
  121. package/lib/commands/viewport.js +43 -31
  122. package/lib/constraints.ts +53 -0
  123. package/lib/css-converter.js +9 -1
  124. package/lib/{driver.js → driver.ts} +383 -225
  125. package/lib/execute-method-map.ts +573 -0
  126. package/lib/method-map.ts +11 -0
  127. package/lib/types.ts +59 -0
  128. package/lib/uiautomator2.js +21 -2
  129. package/lib/utils.js +2 -2
  130. package/npm-shrinkwrap.json +363 -492
  131. package/package.json +95 -69
  132. package/build/lib/desired-caps.js +0 -71
  133. package/build/lib/desired-caps.js.map +0 -1
  134. package/lib/desired-caps.js +0 -70
  135. package/lib/method-map.js +0 -11
@@ -1,24 +1,45 @@
1
- import _ from 'lodash';
2
- import { BaseDriver, DeviceSettings } from 'appium/driver';
3
- import {
4
- UiAutomator2Server, SERVER_PACKAGE_ID, SERVER_TEST_PACKAGE_ID
5
- } from './uiautomator2';
6
- import { newMethodMap } from './method-map';
7
- import { fs, util, mjpeg } from 'appium/support';
8
- import { retryInterval } from 'asyncbox';
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 commands from './commands/index';
11
- import { DEFAULT_ADB_PORT } from 'appium-adb';
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 { androidHelpers, androidCommands, SETTINGS_HELPER_PKG_ID, } from 'appium-android-driver';
14
- import desiredCapConstraints from './desired-caps';
15
- import { findAPortNotInUse, checkPortStatus } from 'portscanner';
16
- import os from 'os';
17
- import path from 'path';
18
- import { APK_EXTENSION, APKS_EXTENSION } from './extensions';
19
-
20
-
21
- const helpers = Object.assign({}, uiautomator2Helpers, androidHelpers);
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
- 'getStatusBarHeight',
144
- 'getDevicePixelRatio',
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
- class AndroidUiautomator2Driver extends BaseDriver {
178
+ static executeMethodMap = executeMethodMap;
148
179
 
149
- static newMethodMap = newMethodMap;
180
+ uiautomator2?: UiAutomator2Server;
181
+
182
+ /**
183
+ * @privateRemarks moved from `this.opts`
184
+ */
185
+ systemPort: number | undefined;
150
186
 
151
- constructor (opts = {}, shouldValidateCaps = true) {
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 = 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({ignoreUnimportantViews: false, allowInvisibleElements: false},
171
- this.onSettingsUpdate.bind(this));
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 (caps) {
183
- return super.validateDesiredCaps(caps) && androidHelpers.validateDesiredCaps(caps);
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 (...args) {
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
- let [sessionId, caps] = await super.createSession(...args);
190
-
191
- let serverDetails = {
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: this.caps,
265
+ desired: caps,
201
266
  };
202
267
 
203
- this.caps = Object.assign(serverDetails, this.caps);
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
- let {pkg, activity} = helpers.getChromePkg(this.opts.browserName);
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(caps);
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, [APK_EXTENSION, APKS_EXTENSION]);
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(`Neither 'app' nor 'appPackage' was set. Starting UiAutomator2 ` +
237
- 'without the target application');
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
- await this.fillDeviceDetails();
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, this.caps];
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 fillDeviceDetails () {
256
- this.caps.pixelRatio = await this.getDevicePixelRatio();
257
- this.caps.statBarHeight = await this.getStatusBarHeight();
258
- this.caps.viewportRect = await this.getViewPortRect();
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
- let sessionData = await super.getSession();
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
- let uia2Data = await this.uiautomator2.jwproxy.command('/', 'GET', {});
270
- return Object.assign({}, sessionData, uia2Data);
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 (caps) {
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('avd or deviceName should be specified when reboot option is enables');
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('avd or platformVersion should be specified when reboot option is enabled');
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
- let avdDevice = caps.deviceName.replace(/[^a-zA-Z0-9_.]/g, '-');
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(`Forwarding UiAutomator2 Server port ${DEVICE_PORT} to local port ${localPort}`);
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(`UiAutomator2 Server cannot start because the local port #${localPort} is busy. ` +
297
- `Make sure the port you provide via 'systemPort' capability is not occupied. ` +
298
- `This situation might often be a result of an inaccurate sessions management, e.g. ` +
299
- `old automation sessions on the same device must always be closed before starting new ones.`);
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.forwardPort(localPort, DEVICE_PORT);
381
+ await this.adb!.forwardPort(localPort, DEVICE_PORT);
302
382
  };
303
383
 
304
- if (this.opts.systemPort) {
384
+ if (this.systemPort) {
305
385
  this._hasSystemPortInCaps = true;
306
- return await forwardPort(this.opts.systemPort);
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.opts.systemPort = await findAPortNotInUse(startPort, endPort);
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
- `Please set the available port number by providing the systemPort capability or ` +
317
- `double check the processes that are locking ports within this range and terminate ` +
318
- `these which are not needed anymore`);
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.opts.systemPort);
402
+ await forwardPort(this.systemPort);
321
403
  });
322
404
  }
323
405
 
324
- async releaseSystemPort () {
325
- if (!this.opts.systemPort || !this.adb) {
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.opts.systemPort);
412
+ await this.adb.removePortForward(this.systemPort);
331
413
  } else {
332
- await DEVICE_PORT_ALLOCATION_GUARD(async () => await this.adb.removePortForward(this.opts.systemPort));
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(`MJPEG broadcasting requested, forwarding MJPEG server port ${MJPEG_SERVER_DEVICE_PORT} ` +
339
- `to local port ${this.opts.mjpegServerPort}`);
340
- await this.adb.forwardPort(this.opts.mjpegServerPort, MJPEG_SERVER_DEVICE_PORT);
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.removePortForward(this.opts.mjpegServerPort);
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
- let {udid, emPort} = await helpers.getDeviceInfoFromCaps(this.opts);
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('UIAutomator2 is only supported since Android 5.0 (Lollipop). ' +
364
- 'You could still use other supported backends in order to automate older Android versions.');
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) { // Android P
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(`Trying to ${this.opts.gpsEnabled ? 'enable' : 'disable'} gps location provider`);
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
- Object.assign(this.opts, appInfo || {});
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
- this.caps.deviceName = this.adb.curDeviceId;
389
- this.caps.deviceUDID = this.opts.udid;
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.opts.systemPort if it is not set explicitly
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)) { // API level 26 is Android 8.0.
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 (!this.caps.appPackage && appInfo) {
426
- this.caps.appPackage = appInfo.appPackage;
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 this.uiautomator2.startSession(this.caps);
532
+ await uiautomator2.startSession(capsWithSessionInfo);
431
533
 
432
- await this.addDeviceInfoToCaps();
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) { // start a chromedriver session
443
- await this.startChromeSession(this);
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 addDeviceInfoToCaps () {
469
- const {
470
- apiVersion,
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
- } = await this.mobileGetDeviceInfo();
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.opts.systemPort,
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(this.uiautomator2);
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.addToDeviceIdleWhitelist(
512
- SETTINGS_HELPER_PKG_ID, SERVER_PACKAGE_ID, SERVER_TEST_PACKAGE_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
- this.log.warn(`Cannot add server packages to the Doze whitelist. Original error: ` +
516
- (e.stderr || e.message));
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(otherApps
540
- .map((app) => this.helpers.configureApp(app, [APK_EXTENSION, APKS_EXTENSION])));
541
- await helpers.installOtherApks(otherApps, this.adb, this.opts);
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 (this.opts.noReset && !(await this.adb.isAppInstalled(this.opts.appPackage))
546
- || !this.opts.noReset) {
547
- if (!this.opts.noSign && !await this.adb.checkApkCert(this.opts.app, this.opts.appPackage, {
548
- requireDefaultCert: false,
549
- })) {
550
- await helpers.signApp(this.adb, this.opts.app);
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.uninstallApk(this.opts.appPackage);
674
+ await this.adb!.uninstallApk(this.opts.appPackage!);
554
675
  }
555
- await helpers.installApk(this.adb, this.opts);
676
+ await helpers.installApk(this.adb!, this.opts);
556
677
  } else {
557
- this.log.debug('noReset has been requested and the app is already installed. Doing nothing');
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('Full reset requires an app capability, use fastReset if app is not provided');
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, this.opts);
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
- this.log.info(`Starting '${this.opts.appPackage}/${this.opts.appActivity} ` +
576
- `and waiting for '${appWaitPackage}/${appWaitActivity}'`);
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(`androidCoverage is configured. ` +
580
- ` Starting instrumentation of '${this.caps.androidCoverage}'...`);
581
- await this.adb.androidCoverage(this.caps.androidCoverage, appWaitPackage, appWaitActivity);
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 (this.opts.noReset && !this.opts.forceAppLaunch
585
- && await this.adb.processExists(this.opts.appPackage)) {
586
- this.log.info(`'${this.opts.appPackage}' is already running and noReset is enabled. ` +
587
- `Set forceAppLaunch capability to true if the app must be forcefully restarted on session startup.`);
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.startApp({
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 = [async () => {
611
- if (!_.isEmpty(this._screenRecordingProperties)) {
612
- await this.stopRecordingScreen();
613
- }
614
- }, async () => {
615
- if (await this.mobileIsMediaProjectionRecordingRunning()) {
616
- await this.mobileStopMediaProjectionRecording();
617
- }
618
- }, async () => {
619
- if (!_.isEmpty(this._screenStreamingProps)) {
620
- await this.mobileStopScreenStreaming();
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 = null;
776
+ this.uiautomator2 = undefined;
640
777
  }
641
778
  this.jwpProxyActive = false;
642
779
 
643
780
  if (this.adb) {
644
- await B.all(screenRecordingStopTasks.map((task) => {
645
- (async () => {
646
- try {
647
- await task();
648
- } catch (ign) {}
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(`Sending intent broadcast '${this.caps.androidCoverageEndIntent}' at the end of instrumenting.`);
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('No androidCoverageEndIntent is configured in caps. Possibly you cannot get coverage file.');
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 (!this.isChromeSession && ((!this.opts.dontStopAppOnReset && !this.opts.noReset)
665
- || (this.opts.noReset && this.opts.shouldTerminateApp))) {
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(`Capability 'fullReset' set to 'true', Uninstalling '${this.opts.appPackage}'`);
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) { // Android P
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
- let avdName = this.opts.avd.replace('@', '');
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 super.deleteSession();
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 (wrapped) {
890
+ override async wrapBootstrapDisconnect(wrapped: () => Promise<void>) {
740
891
  await wrapped();
741
- await this.adb.restart();
892
+ this.adb!.restart();
742
893
  await this.allocateSystemPort();
743
894
  await this.allocateMjpegServerPort();
744
895
  }
745
896
 
746
- proxyActive (sessionId) {
747
- super.proxyActive(sessionId);
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 (sessionId) {
754
- super.canProxy(sessionId);
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 (sessionId) {
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 = [...this.jwpProxyAvoid, ['GET', new RegExp('^/session/[^/]+/screenshot')]];
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
- // first add the android-driver commands which we will fall back to
783
- for (let [cmd, fn] of _.toPairs(androidCommands)) {
784
- AndroidUiautomator2Driver.prototype[cmd] = fn;
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
- // then overwrite with any uiautomator2-specific commands
788
- for (let [cmd, fn] of _.toPairs(commands)) {
789
- AndroidUiautomator2Driver.prototype[cmd] = fn;
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
- export { AndroidUiautomator2Driver };
793
- export default AndroidUiautomator2Driver;
949
+ import './commands';
950
+
951
+ export {AndroidUiautomator2Driver};