appium-uiautomator2-driver 7.2.3 → 7.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/build/lib/commands/app-management.d.ts.map +1 -1
- package/build/lib/commands/app-management.js +1 -5
- package/build/lib/commands/app-management.js.map +1 -1
- package/build/lib/commands/element.d.ts.map +1 -1
- package/build/lib/commands/element.js +2 -6
- package/build/lib/commands/element.js.map +1 -1
- package/build/lib/commands/gestures.d.ts.map +1 -1
- package/build/lib/commands/gestures.js +2 -6
- package/build/lib/commands/gestures.js.map +1 -1
- package/build/lib/commands/keyboard.d.ts.map +1 -1
- package/build/lib/commands/keyboard.js +0 -8
- package/build/lib/commands/keyboard.js.map +1 -1
- package/build/lib/commands/screenshot.js +7 -8
- package/build/lib/commands/screenshot.js.map +1 -1
- package/build/lib/commands/types.d.ts +1 -1
- package/build/lib/commands/types.d.ts.map +1 -1
- package/build/lib/css-converter.js +9 -12
- package/build/lib/css-converter.js.map +1 -1
- package/build/lib/driver.d.ts +31 -30
- package/build/lib/driver.d.ts.map +1 -1
- package/build/lib/driver.js +72 -43
- package/build/lib/driver.js.map +1 -1
- package/build/lib/uiautomator2.d.ts.map +1 -1
- package/build/lib/uiautomator2.js +1 -2
- package/build/lib/uiautomator2.js.map +1 -1
- package/build/lib/{helpers.d.ts → utils/app.d.ts} +1 -1
- package/build/lib/utils/app.d.ts.map +1 -0
- package/build/lib/{helpers.js → utils/app.js} +1 -1
- package/build/lib/utils/app.js.map +1 -0
- package/build/lib/utils/index.d.ts +5 -0
- package/build/lib/utils/index.d.ts.map +1 -0
- package/build/lib/utils/index.js +21 -0
- package/build/lib/utils/index.js.map +1 -0
- package/build/lib/utils/lang.d.ts +15 -0
- package/build/lib/utils/lang.d.ts.map +1 -0
- package/build/lib/utils/lang.js +37 -0
- package/build/lib/utils/lang.js.map +1 -0
- package/build/lib/utils/memoize.d.ts +11 -0
- package/build/lib/utils/memoize.d.ts.map +1 -0
- package/build/lib/utils/memoize.js +26 -0
- package/build/lib/utils/memoize.js.map +1 -0
- package/build/lib/utils/object.d.ts +6 -0
- package/build/lib/utils/object.d.ts.map +1 -0
- package/build/lib/utils/object.js +15 -0
- package/build/lib/utils/object.js.map +1 -0
- package/lib/commands/app-management.ts +1 -2
- package/lib/commands/element.ts +2 -3
- package/lib/commands/gestures.ts +2 -3
- package/lib/commands/keyboard.ts +0 -5
- package/lib/commands/screenshot.ts +7 -7
- package/lib/commands/types.ts +1 -1
- package/lib/constraints.ts +1 -1
- package/lib/css-converter.ts +9 -9
- package/lib/driver.ts +124 -91
- package/lib/execute-method-map.ts +1 -1
- package/lib/uiautomator2.ts +5 -6
- package/lib/utils/index.ts +4 -0
- package/lib/utils/lang.ts +34 -0
- package/lib/utils/memoize.ts +26 -0
- package/lib/utils/object.ts +14 -0
- package/npm-shrinkwrap.json +95 -29
- package/package.json +7 -5
- package/build/lib/helpers.d.ts.map +0 -1
- package/build/lib/helpers.js.map +0 -1
- /package/lib/{helpers.ts → utils/app.ts} +0 -0
package/lib/driver.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
2
1
|
import type {
|
|
3
2
|
DefaultCreateSessionResult,
|
|
4
3
|
DriverData,
|
|
@@ -10,13 +9,12 @@ import type {
|
|
|
10
9
|
StringRecord,
|
|
11
10
|
SessionCapabilities,
|
|
12
11
|
} from '@appium/types';
|
|
13
|
-
import {DEFAULT_ADB_PORT} from 'appium-adb';
|
|
12
|
+
import {DEFAULT_ADB_PORT, type ADB} from 'appium-adb';
|
|
14
13
|
import {AndroidDriver, utils} from 'appium-android-driver';
|
|
15
14
|
import {SETTINGS_HELPER_ID} from 'io.appium.settings';
|
|
16
15
|
import {BaseDriver, DeviceSettings} from 'appium/driver';
|
|
17
16
|
import {fs, mjpeg, util} from 'appium/support';
|
|
18
17
|
import {retryInterval} from 'asyncbox';
|
|
19
|
-
import _ from 'lodash';
|
|
20
18
|
import os from 'node:os';
|
|
21
19
|
import path from 'node:path';
|
|
22
20
|
import {checkPortStatus, findAPortNotInUse} from 'portscanner';
|
|
@@ -24,7 +22,7 @@ import type {ExecError} from 'teen_process';
|
|
|
24
22
|
import UIAUTOMATOR2_CONSTRAINTS, {type Uiautomator2Constraints} from './constraints';
|
|
25
23
|
import {APKS_EXTENSION, APK_EXTENSION} from './extensions';
|
|
26
24
|
import {newMethodMap} from './method-map';
|
|
27
|
-
import {signApp} from './
|
|
25
|
+
import {assignDefaults, memoize, signApp} from './utils';
|
|
28
26
|
import type {
|
|
29
27
|
Uiautomator2Settings,
|
|
30
28
|
Uiautomator2DeviceDetails,
|
|
@@ -241,8 +239,6 @@ const CHROME_NO_PROXY: RouteMatcher[] = [
|
|
|
241
239
|
['POST', new RegExp('^/session/[^/]+/se/log$')],
|
|
242
240
|
];
|
|
243
241
|
|
|
244
|
-
const MEMOIZED_FUNCTIONS = ['getStatusBarHeight', 'getDevicePixelRatio'] as const;
|
|
245
|
-
|
|
246
242
|
class AndroidUiautomator2Driver
|
|
247
243
|
extends AndroidDriver
|
|
248
244
|
implements ExternalDriver<Uiautomator2Constraints, string, StringRecord>
|
|
@@ -250,7 +246,7 @@ class AndroidUiautomator2Driver
|
|
|
250
246
|
static newMethodMap = newMethodMap;
|
|
251
247
|
static executeMethodMap = executeMethodMap;
|
|
252
248
|
|
|
253
|
-
uiautomator2
|
|
249
|
+
uiautomator2!: UiAutomator2Server;
|
|
254
250
|
|
|
255
251
|
systemPort: number | undefined;
|
|
256
252
|
|
|
@@ -267,7 +263,7 @@ class AndroidUiautomator2Driver
|
|
|
267
263
|
mobileGetActionHistory = mobileGetActionHistory;
|
|
268
264
|
mobileScheduleAction = mobileScheduleAction;
|
|
269
265
|
mobileUnscheduleAction = mobileUnscheduleAction;
|
|
270
|
-
performActions = performActions;
|
|
266
|
+
performActions = performActions as AndroidDriver['performActions'];
|
|
271
267
|
releaseActions = releaseActions;
|
|
272
268
|
|
|
273
269
|
getAlertText = getAlertText;
|
|
@@ -281,23 +277,23 @@ class AndroidUiautomator2Driver
|
|
|
281
277
|
mobileGetBatteryInfo = mobileGetBatteryInfo;
|
|
282
278
|
|
|
283
279
|
active = active;
|
|
284
|
-
getAttribute = getAttribute;
|
|
285
|
-
elementEnabled = elementEnabled;
|
|
286
|
-
elementDisplayed = elementDisplayed;
|
|
287
|
-
elementSelected = elementSelected;
|
|
288
|
-
getName = getName;
|
|
289
|
-
getLocation = getLocation;
|
|
290
|
-
getSize = getSize;
|
|
280
|
+
getAttribute = getAttribute as AndroidDriver['getAttribute'];
|
|
281
|
+
elementEnabled = elementEnabled as AndroidDriver['elementEnabled'];
|
|
282
|
+
elementDisplayed = elementDisplayed as AndroidDriver['elementDisplayed'];
|
|
283
|
+
elementSelected = elementSelected as AndroidDriver['elementSelected'];
|
|
284
|
+
getName = getName as AndroidDriver['getName'];
|
|
285
|
+
getLocation = getLocation as AndroidDriver['getLocation'];
|
|
286
|
+
getSize = getSize as AndroidDriver['getSize'];
|
|
291
287
|
getElementRect = getElementRect;
|
|
292
288
|
getElementScreenshot = getElementScreenshot;
|
|
293
|
-
getText = getText;
|
|
294
|
-
setValueImmediate = setValueImmediate;
|
|
295
|
-
doSetElementValue = doSetElementValue;
|
|
296
|
-
click = click;
|
|
289
|
+
getText = getText as AndroidDriver['getText'];
|
|
290
|
+
setValueImmediate = setValueImmediate as AndroidDriver['setValueImmediate'];
|
|
291
|
+
doSetElementValue = doSetElementValue as AndroidDriver['doSetElementValue'];
|
|
292
|
+
click = click as AndroidDriver['click'];
|
|
297
293
|
clear = clear;
|
|
298
294
|
mobileReplaceElementValue = mobileReplaceElementValue;
|
|
299
295
|
|
|
300
|
-
doFindElementOrEls = doFindElementOrEls;
|
|
296
|
+
doFindElementOrEls = doFindElementOrEls as AndroidDriver['doFindElementOrEls'];
|
|
301
297
|
|
|
302
298
|
mobileClickGesture = mobileClickGesture;
|
|
303
299
|
mobileDoubleClickGesture = mobileDoubleClickGesture;
|
|
@@ -311,18 +307,18 @@ class AndroidUiautomator2Driver
|
|
|
311
307
|
mobileScrollGesture = mobileScrollGesture;
|
|
312
308
|
mobileSwipeGesture = mobileSwipeGesture;
|
|
313
309
|
|
|
314
|
-
pressKeyCode = pressKeyCode;
|
|
315
|
-
longPressKeyCode = longPressKeyCode;
|
|
310
|
+
pressKeyCode = pressKeyCode as AndroidDriver['pressKeyCode'];
|
|
311
|
+
longPressKeyCode = longPressKeyCode as AndroidDriver['longPressKeyCode'];
|
|
316
312
|
mobilePressKey = mobilePressKey;
|
|
317
313
|
mobileType = mobileType;
|
|
318
|
-
doSendKeys = doSendKeys;
|
|
314
|
+
doSendKeys = doSendKeys as AndroidDriver['doSendKeys'];
|
|
319
315
|
keyevent = keyevent;
|
|
320
316
|
|
|
321
317
|
getPageSource = getPageSource;
|
|
322
318
|
getOrientation = getOrientation;
|
|
323
319
|
setOrientation = setOrientation;
|
|
324
|
-
openNotifications = openNotifications;
|
|
325
|
-
suspendChromedriverProxy = suspendChromedriverProxy as
|
|
320
|
+
openNotifications = openNotifications as AndroidDriver['openNotifications'];
|
|
321
|
+
suspendChromedriverProxy = suspendChromedriverProxy as AndroidDriver['suspendChromedriverProxy'];
|
|
326
322
|
mobileGetDeviceInfo = mobileGetDeviceInfo;
|
|
327
323
|
mobileResetAccessibilityCache = mobileResetAccessibilityCache;
|
|
328
324
|
mobileListWindows = mobileListWindows;
|
|
@@ -331,7 +327,7 @@ class AndroidUiautomator2Driver
|
|
|
331
327
|
getClipboard = getClipboard;
|
|
332
328
|
setClipboard = setClipboard;
|
|
333
329
|
|
|
334
|
-
setUrl = setUrl;
|
|
330
|
+
setUrl = setUrl as AndroidDriver['setUrl'];
|
|
335
331
|
mobileDeepLink = mobileDeepLink;
|
|
336
332
|
back = back;
|
|
337
333
|
|
|
@@ -342,10 +338,10 @@ class AndroidUiautomator2Driver
|
|
|
342
338
|
|
|
343
339
|
getStatusBarHeight = getStatusBarHeight;
|
|
344
340
|
getDevicePixelRatio = getDevicePixelRatio;
|
|
345
|
-
getDisplayDensity = getDisplayDensity;
|
|
341
|
+
getDisplayDensity = getDisplayDensity as AndroidDriver['getDisplayDensity'];
|
|
346
342
|
getViewPortRect = getViewPortRect;
|
|
347
|
-
getWindowRect = getWindowRect;
|
|
348
|
-
getWindowSize = getWindowSize;
|
|
343
|
+
getWindowRect = getWindowRect as AndroidDriver['getWindowRect'];
|
|
344
|
+
getWindowSize = getWindowSize as AndroidDriver['getWindowSize'];
|
|
349
345
|
mobileViewPortRect = mobileViewPortRect;
|
|
350
346
|
|
|
351
347
|
constructor(opts: InitialOpts = {} as InitialOpts, shouldValidateCaps = true) {
|
|
@@ -363,7 +359,7 @@ class AndroidUiautomator2Driver
|
|
|
363
359
|
'css selector',
|
|
364
360
|
'-android uiautomator',
|
|
365
361
|
];
|
|
366
|
-
this.desiredCapConstraints =
|
|
362
|
+
this.desiredCapConstraints = structuredClone(UIAUTOMATOR2_CONSTRAINTS);
|
|
367
363
|
this.jwpProxyActive = false;
|
|
368
364
|
this.jwpProxyAvoid = NO_PROXY;
|
|
369
365
|
this._originalIme = null;
|
|
@@ -378,9 +374,8 @@ class AndroidUiautomator2Driver
|
|
|
378
374
|
this.caps = {} as Uiautomator2DriverCaps;
|
|
379
375
|
this.opts = opts as Uiautomator2DriverOpts;
|
|
380
376
|
// memoize functions here, so that they are done on a per-instance basis
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
}
|
|
377
|
+
this.getStatusBarHeight = memoize(this.getStatusBarHeight);
|
|
378
|
+
this.getDevicePixelRatio = memoize(this.getDevicePixelRatio);
|
|
384
379
|
}
|
|
385
380
|
|
|
386
381
|
override get driverData() {
|
|
@@ -427,7 +422,7 @@ class AndroidUiautomator2Driver
|
|
|
427
422
|
adbPort: DEFAULT_ADB_PORT,
|
|
428
423
|
androidInstallTimeout: 90000,
|
|
429
424
|
};
|
|
430
|
-
|
|
425
|
+
assignDefaults(this.opts as Record<string, unknown>, defaultOpts);
|
|
431
426
|
|
|
432
427
|
this.opts.adbPort = this.opts.adbPort || DEFAULT_ADB_PORT;
|
|
433
428
|
// get device udid for this session
|
|
@@ -439,15 +434,15 @@ class AndroidUiautomator2Driver
|
|
|
439
434
|
// ADB instance
|
|
440
435
|
this.adb = await this.createADB();
|
|
441
436
|
|
|
442
|
-
if (this.isChromeSession) {
|
|
437
|
+
if (this.isChromeSession && this.opts.browserName) {
|
|
443
438
|
this.log.info(`We're going to run a Chrome-based session`);
|
|
444
|
-
const {pkg, activity: defaultActivity} = utils.getChromePkg(this.opts.browserName
|
|
439
|
+
const {pkg, activity: defaultActivity} = utils.getChromePkg(this.opts.browserName);
|
|
445
440
|
let activity: string = defaultActivity;
|
|
446
441
|
try {
|
|
447
442
|
activity = await this.adb.resolveLaunchableActivity(pkg);
|
|
448
443
|
} catch (e) {
|
|
449
444
|
this.log.warn(
|
|
450
|
-
`Using the default ${pkg} activity ${activity}. Original error: ${e.message}`,
|
|
445
|
+
`Using the default ${pkg} activity ${activity}. Original error: ${(e as Error).message}`,
|
|
451
446
|
);
|
|
452
447
|
}
|
|
453
448
|
this.opts.appPackage = this.caps.appPackage = pkg;
|
|
@@ -504,7 +499,7 @@ class AndroidUiautomator2Driver
|
|
|
504
499
|
pixelRatio,
|
|
505
500
|
statBarHeight,
|
|
506
501
|
viewportRect,
|
|
507
|
-
deviceApiLevel:
|
|
502
|
+
deviceApiLevel: Number.parseInt(String(apiVersion), 10),
|
|
508
503
|
platformVersion,
|
|
509
504
|
deviceManufacturer: manufacturer,
|
|
510
505
|
deviceModel: model,
|
|
@@ -516,11 +511,16 @@ class AndroidUiautomator2Driver
|
|
|
516
511
|
override async getSession(): Promise<SingularSessionData<Uiautomator2Constraints>> {
|
|
517
512
|
const sessionData = await BaseDriver.prototype.getSession.call(this);
|
|
518
513
|
this.log.debug('Getting session details from server to mix in');
|
|
519
|
-
const uia2Data = (await this.
|
|
514
|
+
const uia2Data = (await this.requireUiautomator2().jwproxy.command(
|
|
515
|
+
'/',
|
|
516
|
+
'GET',
|
|
517
|
+
{},
|
|
518
|
+
)) as StringRecord;
|
|
520
519
|
return {...sessionData, ...uia2Data};
|
|
521
520
|
}
|
|
522
521
|
|
|
523
522
|
async allocateSystemPort() {
|
|
523
|
+
const adb = this.requireAdb();
|
|
524
524
|
const forwardPort = async (localPort: number) => {
|
|
525
525
|
this.log.debug(
|
|
526
526
|
`Forwarding UiAutomator2 Server port ${DEVICE_PORT} to local port ${localPort}`,
|
|
@@ -533,7 +533,7 @@ class AndroidUiautomator2Driver
|
|
|
533
533
|
`old automation sessions on the same device must always be closed before starting new ones.`,
|
|
534
534
|
);
|
|
535
535
|
}
|
|
536
|
-
await
|
|
536
|
+
await adb.forwardPort(localPort, DEVICE_PORT);
|
|
537
537
|
};
|
|
538
538
|
|
|
539
539
|
if (this.opts.systemPort) {
|
|
@@ -558,34 +558,35 @@ class AndroidUiautomator2Driver
|
|
|
558
558
|
}
|
|
559
559
|
|
|
560
560
|
async releaseSystemPort() {
|
|
561
|
-
|
|
561
|
+
const adb = this.adb;
|
|
562
|
+
const systemPort = this.systemPort;
|
|
563
|
+
if (!systemPort || !adb) {
|
|
562
564
|
return;
|
|
563
565
|
}
|
|
564
566
|
|
|
565
567
|
if (this.opts.systemPort) {
|
|
566
568
|
// We assume if the systemPort is provided manually then it must be unique,
|
|
567
569
|
// so there is no need for the explicit synchronization
|
|
568
|
-
await
|
|
570
|
+
await adb.removePortForward(systemPort);
|
|
569
571
|
} else {
|
|
570
|
-
await DEVICE_PORT_ALLOCATION_GUARD(
|
|
571
|
-
async () => await this.adb!.removePortForward(this.systemPort!),
|
|
572
|
-
);
|
|
572
|
+
await DEVICE_PORT_ALLOCATION_GUARD(async () => await adb.removePortForward(systemPort));
|
|
573
573
|
}
|
|
574
574
|
}
|
|
575
575
|
|
|
576
576
|
async allocateMjpegServerPort() {
|
|
577
577
|
if (this.opts.mjpegServerPort) {
|
|
578
|
+
const adb = this.requireAdb();
|
|
578
579
|
this.log.debug(
|
|
579
580
|
`MJPEG broadcasting requested, forwarding MJPEG server port ${MJPEG_SERVER_DEVICE_PORT} ` +
|
|
580
581
|
`to local port ${this.opts.mjpegServerPort}`,
|
|
581
582
|
);
|
|
582
|
-
await
|
|
583
|
+
await adb.forwardPort(this.opts.mjpegServerPort, MJPEG_SERVER_DEVICE_PORT);
|
|
583
584
|
}
|
|
584
585
|
}
|
|
585
586
|
|
|
586
587
|
async releaseMjpegServerPort() {
|
|
587
|
-
if (this.opts.mjpegServerPort) {
|
|
588
|
-
await this.adb
|
|
588
|
+
if (this.opts.mjpegServerPort && this.adb) {
|
|
589
|
+
await this.adb.removePortForward(this.opts.mjpegServerPort);
|
|
589
590
|
}
|
|
590
591
|
}
|
|
591
592
|
|
|
@@ -610,7 +611,7 @@ class AndroidUiautomator2Driver
|
|
|
610
611
|
'Please check https://github.com/appium/appium/issues/13802 for more details. ' +
|
|
611
612
|
'You could also set the "appium:ignoreHiddenApiPolicyError" capability to true in order to ' +
|
|
612
613
|
'ignore this error, which might later lead to unexpected crashes or behavior of ' +
|
|
613
|
-
`the automation server. Original error: ${err.message}`,
|
|
614
|
+
`the automation server. Original error: ${(err as Error).message}`,
|
|
614
615
|
);
|
|
615
616
|
}
|
|
616
617
|
})(),
|
|
@@ -732,9 +733,19 @@ class AndroidUiautomator2Driver
|
|
|
732
733
|
): Promise<Uiautomator2SessionCaps> {
|
|
733
734
|
const appInfo = await this.performSessionPreExecSetup();
|
|
734
735
|
// set actual device name, udid, platform version, screen size, screen density, model and manufacturer details
|
|
736
|
+
const deviceName = this.adb?.curDeviceId;
|
|
737
|
+
const deviceUDID = this.opts.udid;
|
|
738
|
+
if (!deviceName) {
|
|
739
|
+
throw this.log.errorWithException(
|
|
740
|
+
'Could not determine device name (ADB curDeviceId is empty)',
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
if (!deviceUDID) {
|
|
744
|
+
throw this.log.errorWithException('Device UDID is not set in session options');
|
|
745
|
+
}
|
|
735
746
|
const sessionInfo: Uiautomator2SessionInfo = {
|
|
736
|
-
deviceName
|
|
737
|
-
deviceUDID
|
|
747
|
+
deviceName,
|
|
748
|
+
deviceUDID,
|
|
738
749
|
};
|
|
739
750
|
const capsWithSessionInfo = {
|
|
740
751
|
...caps,
|
|
@@ -742,9 +753,9 @@ class AndroidUiautomator2Driver
|
|
|
742
753
|
};
|
|
743
754
|
// Adding AUT info in the capabilities if it does not exist in caps
|
|
744
755
|
if (appInfo) {
|
|
745
|
-
for (const capName of ['appPackage', 'appActivity']) {
|
|
746
|
-
if (!capsWithSessionInfo[capName] && appInfo[capName]) {
|
|
747
|
-
capsWithSessionInfo[capName] = appInfo[capName];
|
|
756
|
+
for (const capName of ['appPackage', 'appActivity'] as const) {
|
|
757
|
+
if (!(capsWithSessionInfo as StringRecord)[capName] && appInfo[capName]) {
|
|
758
|
+
(capsWithSessionInfo as StringRecord)[capName] = appInfo[capName];
|
|
748
759
|
}
|
|
749
760
|
}
|
|
750
761
|
}
|
|
@@ -755,7 +766,7 @@ class AndroidUiautomator2Driver
|
|
|
755
766
|
try {
|
|
756
767
|
return await this.getDeviceDetails();
|
|
757
768
|
} catch (e) {
|
|
758
|
-
this.log.warn(`Cannot fetch device details. Original error: ${e.message}`);
|
|
769
|
+
this.log.warn(`Cannot fetch device details. Original error: ${(e as Error).message}`);
|
|
759
770
|
return {};
|
|
760
771
|
}
|
|
761
772
|
})();
|
|
@@ -786,7 +797,7 @@ class AndroidUiautomator2Driver
|
|
|
786
797
|
} else {
|
|
787
798
|
await this.uiautomator2.installServerApk(this.opts.uiautomator2ServerInstallTimeout);
|
|
788
799
|
try {
|
|
789
|
-
await this.
|
|
800
|
+
await this.requireAdb().addToDeviceIdleWhitelist(
|
|
790
801
|
SETTINGS_HELPER_ID,
|
|
791
802
|
SERVER_PACKAGE_ID,
|
|
792
803
|
SERVER_TEST_PACKAGE_ID,
|
|
@@ -829,21 +840,20 @@ class AndroidUiautomator2Driver
|
|
|
829
840
|
await this.installOtherApks(otherApps);
|
|
830
841
|
}
|
|
831
842
|
|
|
832
|
-
if (this.opts.app) {
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
) {
|
|
843
|
+
if (this.opts.app && this.opts.appPackage) {
|
|
844
|
+
const adb = this.requireAdb();
|
|
845
|
+
const appPackage = this.opts.appPackage;
|
|
846
|
+
if ((this.opts.noReset && !(await adb.isAppInstalled(appPackage))) || !this.opts.noReset) {
|
|
837
847
|
if (
|
|
838
848
|
!this.opts.noSign &&
|
|
839
|
-
!(await
|
|
849
|
+
!(await adb.checkApkCert(this.opts.app, appPackage, {
|
|
840
850
|
requireDefaultCert: false,
|
|
841
851
|
}))
|
|
842
852
|
) {
|
|
843
|
-
await signApp(
|
|
853
|
+
await signApp(adb, this.opts.app);
|
|
844
854
|
}
|
|
845
855
|
if (!this.opts.skipUninstall) {
|
|
846
|
-
await
|
|
856
|
+
await adb.uninstallApk(appPackage);
|
|
847
857
|
}
|
|
848
858
|
await this.installAUT();
|
|
849
859
|
} else {
|
|
@@ -865,28 +875,32 @@ class AndroidUiautomator2Driver
|
|
|
865
875
|
}
|
|
866
876
|
|
|
867
877
|
async ensureAppStarts() {
|
|
878
|
+
const adb = this.requireAdb();
|
|
879
|
+
const appPackage = this.opts.appPackage;
|
|
880
|
+
const appActivity = this.opts.appActivity;
|
|
881
|
+
if (!appPackage) {
|
|
882
|
+
throw this.log.errorWithException(
|
|
883
|
+
'appPackage capability is required to start the application',
|
|
884
|
+
);
|
|
885
|
+
}
|
|
868
886
|
// make sure we have an activity and package to wait for
|
|
869
|
-
const appWaitPackage = this.opts.appWaitPackage ||
|
|
870
|
-
const appWaitActivity = this.opts.appWaitActivity ||
|
|
887
|
+
const appWaitPackage = this.opts.appWaitPackage || appPackage;
|
|
888
|
+
const appWaitActivity = this.opts.appWaitActivity || appActivity;
|
|
871
889
|
this.log.info(
|
|
872
|
-
`Starting '${
|
|
873
|
-
`and waiting for '${appWaitPackage}
|
|
890
|
+
`Starting '${appPackage}${appActivity ? '/' + appActivity : ''}` +
|
|
891
|
+
`and waiting for '${appWaitPackage}${appWaitActivity ? '/' + appWaitActivity : ''}'`,
|
|
874
892
|
);
|
|
875
893
|
|
|
876
|
-
if (
|
|
877
|
-
this.opts.noReset &&
|
|
878
|
-
!this.opts.forceAppLaunch &&
|
|
879
|
-
(await this.adb!.processExists(this.opts.appPackage!))
|
|
880
|
-
) {
|
|
894
|
+
if (this.opts.noReset && !this.opts.forceAppLaunch && (await adb.processExists(appPackage))) {
|
|
881
895
|
this.log.info(
|
|
882
|
-
`'${
|
|
896
|
+
`'${appPackage}' is already running and noReset is enabled. ` +
|
|
883
897
|
`Set forceAppLaunch capability to true if the app must be forcefully restarted on session startup.`,
|
|
884
898
|
);
|
|
885
899
|
return;
|
|
886
900
|
}
|
|
887
|
-
await
|
|
888
|
-
pkg:
|
|
889
|
-
activity:
|
|
901
|
+
await adb.startApp({
|
|
902
|
+
pkg: appPackage,
|
|
903
|
+
activity: appActivity,
|
|
890
904
|
action: this.opts.intentAction || 'android.intent.action.MAIN',
|
|
891
905
|
category: this.opts.intentCategory || 'android.intent.category.LAUNCHER',
|
|
892
906
|
flags: this.opts.intentFlags || '0x10200000', // FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
|
|
@@ -906,7 +920,7 @@ class AndroidUiautomator2Driver
|
|
|
906
920
|
|
|
907
921
|
const screenRecordingStopTasks = [
|
|
908
922
|
async () => {
|
|
909
|
-
if (
|
|
923
|
+
if (this._screenRecordingProperties) {
|
|
910
924
|
await this.stopRecordingScreen();
|
|
911
925
|
}
|
|
912
926
|
},
|
|
@@ -916,7 +930,7 @@ class AndroidUiautomator2Driver
|
|
|
916
930
|
}
|
|
917
931
|
},
|
|
918
932
|
async () => {
|
|
919
|
-
if (
|
|
933
|
+
if (this._screenStreamingProps) {
|
|
920
934
|
await this.mobileStopScreenStreaming();
|
|
921
935
|
}
|
|
922
936
|
},
|
|
@@ -980,7 +994,7 @@ class AndroidUiautomator2Driver
|
|
|
980
994
|
try {
|
|
981
995
|
await this.adb.setIME(this._originalIme);
|
|
982
996
|
} catch (e) {
|
|
983
|
-
this.log.warn(`Cannot restore the original IME: ${e.message}`);
|
|
997
|
+
this.log.warn(`Cannot restore the original IME: ${(e as Error).message}`);
|
|
984
998
|
}
|
|
985
999
|
}
|
|
986
1000
|
try {
|
|
@@ -1023,19 +1037,19 @@ class AndroidUiautomator2Driver
|
|
|
1023
1037
|
// settings to the uiauto2 server already
|
|
1024
1038
|
}
|
|
1025
1039
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1040
|
+
override proxyActive(sessionId: string): boolean {
|
|
1041
|
+
void sessionId;
|
|
1028
1042
|
// we always have an active proxy to the UiAutomator2 server
|
|
1029
1043
|
return true;
|
|
1030
1044
|
}
|
|
1031
1045
|
|
|
1032
|
-
|
|
1033
|
-
|
|
1046
|
+
override canProxy(sessionId: string): boolean {
|
|
1047
|
+
void sessionId;
|
|
1034
1048
|
// we can always proxy to the uiautomator2 server
|
|
1035
1049
|
return true;
|
|
1036
1050
|
}
|
|
1037
1051
|
|
|
1038
|
-
getProxyAvoidList(): RouteMatcher[] {
|
|
1052
|
+
override getProxyAvoidList(): RouteMatcher[] {
|
|
1039
1053
|
// we are maintaining two sets of NO_PROXY lists, one for chromedriver(CHROME_NO_PROXY)
|
|
1040
1054
|
// and one for uiautomator2(NO_PROXY), based on current context will return related NO_PROXY list
|
|
1041
1055
|
if (util.hasValue(this.chromedriver)) {
|
|
@@ -1054,24 +1068,43 @@ class AndroidUiautomator2Driver
|
|
|
1054
1068
|
return this.jwpProxyAvoid;
|
|
1055
1069
|
}
|
|
1056
1070
|
|
|
1057
|
-
|
|
1071
|
+
// @ts-expect-error narrower parameter type than the base class override allows
|
|
1072
|
+
override async updateSettings(settings: Uiautomator2Settings) {
|
|
1058
1073
|
await this.settings.update(settings);
|
|
1059
|
-
await this.
|
|
1074
|
+
await this.requireUiautomator2().jwproxy.command('/appium/settings', 'POST', {settings});
|
|
1060
1075
|
}
|
|
1061
1076
|
|
|
1062
|
-
async getSettings() {
|
|
1077
|
+
override async getSettings(): Promise<StringRecord> {
|
|
1063
1078
|
const driverSettings = this.settings.getSettings();
|
|
1064
|
-
const serverSettings = (await this.
|
|
1079
|
+
const serverSettings = (await this.requireUiautomator2().jwproxy.command(
|
|
1065
1080
|
'/appium/settings',
|
|
1066
1081
|
'GET',
|
|
1067
1082
|
)) as Partial<Uiautomator2Settings>;
|
|
1068
|
-
return {...driverSettings, ...serverSettings}
|
|
1083
|
+
return {...driverSettings, ...serverSettings};
|
|
1069
1084
|
}
|
|
1070
1085
|
|
|
1071
1086
|
// needed to make the typechecker happy
|
|
1072
|
-
async getAppiumSessionCapabilities(): Promise<
|
|
1087
|
+
override async getAppiumSessionCapabilities(): Promise<
|
|
1088
|
+
SessionCapabilities<Uiautomator2Constraints>
|
|
1089
|
+
> {
|
|
1073
1090
|
return (await super.getAppiumSessionCapabilities()) as SessionCapabilities<Uiautomator2Constraints>;
|
|
1074
1091
|
}
|
|
1092
|
+
|
|
1093
|
+
private requireAdb(): ADB {
|
|
1094
|
+
const adb = this.adb;
|
|
1095
|
+
if (!adb) {
|
|
1096
|
+
throw this.log.errorWithException('ADB must be initialized before this operation');
|
|
1097
|
+
}
|
|
1098
|
+
return adb;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
private requireUiautomator2(): UiAutomator2Server {
|
|
1102
|
+
const server = this.uiautomator2;
|
|
1103
|
+
if (!server) {
|
|
1104
|
+
throw this.log.errorWithException('UiAutomator2 server is not initialized');
|
|
1105
|
+
}
|
|
1106
|
+
return server;
|
|
1107
|
+
}
|
|
1075
1108
|
}
|
|
1076
1109
|
|
|
1077
1110
|
export {AndroidUiautomator2Driver};
|
package/lib/uiautomator2.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import _ from 'lodash';
|
|
2
1
|
import {JWProxy, errors} from 'appium/driver';
|
|
3
2
|
import {sleep, waitForCondition} from 'asyncbox';
|
|
4
3
|
import {
|
|
@@ -88,10 +87,10 @@ export class UiAutomator2Server {
|
|
|
88
87
|
public readonly proxyReqRes: typeof UIA2Proxy.prototype.proxyReqRes;
|
|
89
88
|
public readonly proxyCommand: typeof UIA2Proxy.prototype.command;
|
|
90
89
|
|
|
91
|
-
private readonly host
|
|
92
|
-
private readonly systemPort
|
|
93
|
-
private readonly adb
|
|
94
|
-
private readonly disableWindowAnimation
|
|
90
|
+
private readonly host!: string;
|
|
91
|
+
private readonly systemPort!: number;
|
|
92
|
+
private readonly adb!: ADB;
|
|
93
|
+
private readonly disableWindowAnimation!: boolean;
|
|
95
94
|
private readonly disableSuppressAccessibilityService?: boolean;
|
|
96
95
|
private readonly log: AppiumLogger;
|
|
97
96
|
private instrumentationProcess: SubProcess | null = null;
|
|
@@ -409,7 +408,7 @@ export class UiAutomator2Server {
|
|
|
409
408
|
if (this.disableWindowAnimation) {
|
|
410
409
|
cmd.push('--no-window-animation');
|
|
411
410
|
}
|
|
412
|
-
if (
|
|
411
|
+
if (typeof this.disableSuppressAccessibilityService === 'boolean') {
|
|
413
412
|
cmd.push(
|
|
414
413
|
'-e',
|
|
415
414
|
'DISABLE_SUPPRESS_ACCESSIBILITY_SERVICES',
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// TODO(appium server 3.4.1+): Replace local `isEmpty` / `escapeRegExp` with imports from `appium/support`
|
|
2
|
+
// once this driver declares that minimum server version.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns true when the value has no elements/properties.
|
|
6
|
+
*
|
|
7
|
+
* @param value - Value to check
|
|
8
|
+
* @returns `true` if the value is empty
|
|
9
|
+
*/
|
|
10
|
+
export function isEmpty(value: unknown): boolean {
|
|
11
|
+
if (value == null) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
if (typeof value === 'string' || Array.isArray(value) || Buffer.isBuffer(value)) {
|
|
15
|
+
return value.length === 0;
|
|
16
|
+
}
|
|
17
|
+
if (value instanceof Map || value instanceof Set) {
|
|
18
|
+
return value.size === 0;
|
|
19
|
+
}
|
|
20
|
+
if (typeof value === 'object' || typeof value === 'function') {
|
|
21
|
+
return Object.keys(value).length === 0;
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Escapes RegExp special characters in a string.
|
|
28
|
+
*
|
|
29
|
+
* @param value - Input string
|
|
30
|
+
* @returns Escaped string safe for RegExp source
|
|
31
|
+
*/
|
|
32
|
+
export function escapeRegExp(value: string): string {
|
|
33
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
34
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// TODO(appium server 3.4.1+): Replace local `memoize` with imports from `appium/support`
|
|
2
|
+
// once this driver declares that minimum server version.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a memoized version of a function.
|
|
6
|
+
*
|
|
7
|
+
* @param fn - Function to memoize
|
|
8
|
+
* @param resolver - Optional cache key resolver. If omitted, the first argument is used as the cache key.
|
|
9
|
+
* @returns Memoized function with a mutable `.cache` map (compatible with lodash-style cache resets in tests).
|
|
10
|
+
*/
|
|
11
|
+
export function memoize<Fn extends (...args: any[]) => any>(
|
|
12
|
+
fn: Fn,
|
|
13
|
+
resolver?: (...args: Parameters<Fn>) => unknown,
|
|
14
|
+
): Fn & {cache: Map<unknown, ReturnType<Fn>>} {
|
|
15
|
+
const memoizedFn = function (this: unknown, ...args: Parameters<Fn>) {
|
|
16
|
+
const key = resolver ? resolver.apply(this, args) : args[0];
|
|
17
|
+
if (memoizedFn.cache.has(key)) {
|
|
18
|
+
return memoizedFn.cache.get(key) as ReturnType<Fn>;
|
|
19
|
+
}
|
|
20
|
+
const result = fn.apply(this, args);
|
|
21
|
+
memoizedFn.cache.set(key, result);
|
|
22
|
+
return result;
|
|
23
|
+
} as unknown as Fn & {cache: Map<unknown, ReturnType<Fn>>};
|
|
24
|
+
memoizedFn.cache = new Map<unknown, ReturnType<Fn>>();
|
|
25
|
+
return memoizedFn;
|
|
26
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assigns own enumerable properties of `source` onto `target` only where `target[key] === undefined`
|
|
3
|
+
* (lodash `defaults` semantics).
|
|
4
|
+
*/
|
|
5
|
+
export function assignDefaults<T extends Record<string, unknown>>(
|
|
6
|
+
target: T,
|
|
7
|
+
source: Record<string, unknown>,
|
|
8
|
+
): void {
|
|
9
|
+
for (const key of Object.keys(source)) {
|
|
10
|
+
if (target[key] === undefined) {
|
|
11
|
+
(target as Record<string, unknown>)[key] = source[key];
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|