appium-uiautomator2-driver 2.29.11 → 2.31.0

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