appium-uiautomator2-driver 8.2.0 → 8.2.2
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/actions.d.ts.map +1 -1
- package/build/lib/commands/actions.js.map +1 -1
- package/build/lib/commands/alert.d.ts.map +1 -1
- package/build/lib/commands/alert.js.map +1 -1
- package/build/lib/commands/app-management.d.ts.map +1 -1
- package/build/lib/commands/app-management.js.map +1 -1
- package/build/lib/commands/aut.d.ts.map +1 -1
- package/build/lib/commands/aut.js +1 -2
- package/build/lib/commands/aut.js.map +1 -1
- package/build/lib/commands/battery.d.ts.map +1 -1
- package/build/lib/commands/battery.js.map +1 -1
- package/build/lib/commands/element.d.ts +1 -1
- package/build/lib/commands/element.d.ts.map +1 -1
- package/build/lib/commands/element.js.map +1 -1
- package/build/lib/commands/find.d.ts.map +1 -1
- package/build/lib/commands/find.js.map +1 -1
- package/build/lib/commands/gestures.d.ts.map +1 -1
- package/build/lib/commands/gestures.js.map +1 -1
- package/build/lib/commands/keyboard.d.ts +1 -1
- package/build/lib/commands/keyboard.d.ts.map +1 -1
- package/build/lib/commands/keyboard.js.map +1 -1
- package/build/lib/commands/misc.d.ts.map +1 -1
- package/build/lib/commands/misc.js.map +1 -1
- package/build/lib/commands/screenshot.d.ts +1 -1
- package/build/lib/commands/screenshot.d.ts.map +1 -1
- package/build/lib/commands/screenshot.js +1 -3
- package/build/lib/commands/screenshot.js.map +1 -1
- package/build/lib/commands/viewport.d.ts.map +1 -1
- package/build/lib/commands/viewport.js.map +1 -1
- package/build/lib/commands/windows.d.ts.map +1 -1
- package/build/lib/commands/windows.js.map +1 -1
- package/build/lib/css/index.d.ts.map +1 -1
- package/build/lib/css/index.js.map +1 -1
- package/build/lib/css/ui-automator-emitter.d.ts.map +1 -1
- package/build/lib/css/ui-automator-emitter.js +1 -3
- package/build/lib/css/ui-automator-emitter.js.map +1 -1
- package/build/lib/doctor/optional-checks.d.ts.map +1 -1
- package/build/lib/doctor/optional-checks.js.map +1 -1
- package/build/lib/doctor/required-checks.d.ts.map +1 -1
- package/build/lib/doctor/required-checks.js.map +1 -1
- package/build/lib/driver.d.ts +6 -6
- package/build/lib/driver.d.ts.map +1 -1
- package/build/lib/driver.js +9 -20
- package/build/lib/driver.js.map +1 -1
- package/build/lib/logger.d.ts.map +1 -1
- package/build/lib/logger.js.map +1 -1
- package/build/lib/session-claim-handler.d.ts.map +1 -1
- package/build/lib/session-claim-handler.js +1 -1
- package/build/lib/session-claim-handler.js.map +1 -1
- package/build/lib/types.d.ts.map +1 -1
- package/build/lib/uiautomator2-server/core.d.ts +1 -1
- package/build/lib/uiautomator2-server/core.d.ts.map +1 -1
- package/build/lib/uiautomator2-server/core.js +8 -22
- package/build/lib/uiautomator2-server/core.js.map +1 -1
- package/build/lib/uiautomator2-server/session.d.ts.map +1 -1
- package/build/lib/uiautomator2-server/session.js +8 -6
- package/build/lib/uiautomator2-server/session.js.map +1 -1
- package/build/lib/utils/app.d.ts.map +1 -1
- package/build/lib/utils/app.js.map +1 -1
- package/build/lib/utils/mjpeg.d.ts.map +1 -1
- package/build/lib/utils/mjpeg.js +1 -1
- package/build/lib/utils/mjpeg.js.map +1 -1
- package/build/lib/utils/object.d.ts.map +1 -1
- package/build/lib/utils/object.js.map +1 -1
- package/lib/commands/actions.ts +4 -12
- package/lib/commands/alert.ts +2 -8
- package/lib/commands/app-management.ts +3 -4
- package/lib/commands/aut.ts +4 -10
- package/lib/commands/battery.ts +1 -5
- package/lib/commands/element.ts +15 -51
- package/lib/commands/find.ts +5 -6
- package/lib/commands/gestures.ts +5 -11
- package/lib/commands/keyboard.ts +10 -19
- package/lib/commands/misc.ts +3 -7
- package/lib/commands/screenshot.ts +8 -15
- package/lib/commands/viewport.ts +3 -10
- package/lib/commands/windows.ts +1 -5
- package/lib/css/index.ts +3 -5
- package/lib/css/ui-automator-emitter.ts +3 -9
- package/lib/doctor/optional-checks.ts +1 -1
- package/lib/doctor/required-checks.ts +1 -1
- package/lib/driver.ts +34 -61
- package/lib/logger.ts +1 -1
- package/lib/session-claim-handler.ts +21 -45
- package/lib/types.ts +2 -2
- package/lib/uiautomator2-server/core.ts +24 -60
- package/lib/uiautomator2-server/session.ts +13 -22
- package/lib/utils/app.ts +1 -0
- package/lib/utils/mjpeg.ts +8 -28
- package/lib/utils/object.ts +1 -4
- package/npm-shrinkwrap.json +74 -95
- package/package.json +21 -35
- package/scripts/reset.mjs +2 -8
package/lib/logger.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import {setTimeout as delay} from 'node:timers/promises';
|
|
2
|
+
|
|
1
3
|
import type {AppiumLogger, IAppiumIpc, IIpcSubscription, IpcMessage} from '@appium/types';
|
|
2
4
|
import {node, util} from 'appium/support.js';
|
|
3
5
|
import {waitForCondition} from 'asyncbox';
|
|
4
|
-
|
|
6
|
+
|
|
5
7
|
import type {AndroidUiautomator2Driver} from './driver.js';
|
|
6
8
|
import {memoize} from './utils/index.js';
|
|
7
9
|
|
|
@@ -21,10 +23,7 @@ export class SessionClaimHandler {
|
|
|
21
23
|
private static readonly CONTENTION_PROBE_MS = 10;
|
|
22
24
|
private static readonly RELEASE_WAIT_MS = 15000;
|
|
23
25
|
|
|
24
|
-
private readonly subscriptionsBySessionId = new Map<
|
|
25
|
-
string,
|
|
26
|
-
IIpcSubscription<SessionUdidIpcMessage>
|
|
27
|
-
>();
|
|
26
|
+
private readonly subscriptionsBySessionId = new Map<string, IIpcSubscription<SessionUdidIpcMessage>>();
|
|
28
27
|
|
|
29
28
|
constructor(private readonly getIpc: IpcProvider) {}
|
|
30
29
|
|
|
@@ -64,9 +63,7 @@ export class SessionClaimHandler {
|
|
|
64
63
|
async claimSessionUdid(driver: AndroidUiautomator2Driver): Promise<void> {
|
|
65
64
|
const ipc = await this.getIpc();
|
|
66
65
|
if (!ipc) {
|
|
67
|
-
driver.log.debug(
|
|
68
|
-
'Driver-instance IPC is unavailable. Skipping publication of the session udid.',
|
|
69
|
-
);
|
|
66
|
+
driver.log.debug('Driver-instance IPC is unavailable. Skipping publication of the session udid.');
|
|
70
67
|
return;
|
|
71
68
|
}
|
|
72
69
|
|
|
@@ -99,14 +96,10 @@ export class SessionClaimHandler {
|
|
|
99
96
|
});
|
|
100
97
|
|
|
101
98
|
try {
|
|
102
|
-
await ipc.publish<SessionUdidIpcMessage>(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
udid,
|
|
107
|
-
sessionId,
|
|
108
|
-
},
|
|
109
|
-
);
|
|
99
|
+
await ipc.publish<SessionUdidIpcMessage>(SessionClaimHandler.CLAIMED_TOPIC, this.getPublisherId(driver), {
|
|
100
|
+
udid,
|
|
101
|
+
sessionId,
|
|
102
|
+
});
|
|
110
103
|
await delay(SessionClaimHandler.CONTENTION_PROBE_MS);
|
|
111
104
|
|
|
112
105
|
if (contendingSessionIds.size === 0) {
|
|
@@ -114,21 +107,16 @@ export class SessionClaimHandler {
|
|
|
114
107
|
}
|
|
115
108
|
|
|
116
109
|
try {
|
|
117
|
-
await waitForCondition(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
intervalMs: 50,
|
|
122
|
-
},
|
|
123
|
-
);
|
|
110
|
+
await waitForCondition(() => [...contendingSessionIds].every((id) => releasedSessionIds.has(id)), {
|
|
111
|
+
waitMs: SessionClaimHandler.RELEASE_WAIT_MS,
|
|
112
|
+
intervalMs: 50,
|
|
113
|
+
});
|
|
124
114
|
driver.log.debug(
|
|
125
115
|
`Received release confirmation from ` +
|
|
126
116
|
`${util.pluralize('session', contendingSessionIds.size, true)} for udid '${udid}'`,
|
|
127
117
|
);
|
|
128
118
|
} catch {
|
|
129
|
-
const pendingSessionIds = [...contendingSessionIds].filter(
|
|
130
|
-
(id) => !releasedSessionIds.has(id),
|
|
131
|
-
);
|
|
119
|
+
const pendingSessionIds = [...contendingSessionIds].filter((id) => !releasedSessionIds.has(id));
|
|
132
120
|
driver.log.warn(
|
|
133
121
|
`Timed out after ${SessionClaimHandler.RELEASE_WAIT_MS}ms waiting for ` +
|
|
134
122
|
`${util.pluralize('session', pendingSessionIds.length, true)} ` +
|
|
@@ -177,9 +165,7 @@ export class SessionClaimHandler {
|
|
|
177
165
|
await this.publishSessionUdidContended(driver, udid);
|
|
178
166
|
} catch (err) {
|
|
179
167
|
const msg = err instanceof Error ? err.message : String(err);
|
|
180
|
-
driver.log.warn(
|
|
181
|
-
`Could not publish udid contention message for session '${driver.sessionId}': ${msg}`,
|
|
182
|
-
);
|
|
168
|
+
driver.log.warn(`Could not publish udid contention message for session '${driver.sessionId}': ${msg}`);
|
|
183
169
|
}
|
|
184
170
|
|
|
185
171
|
driver.log.warn(
|
|
@@ -192,30 +178,20 @@ export class SessionClaimHandler {
|
|
|
192
178
|
await this.terminateSessionOnRequest(driver, udid);
|
|
193
179
|
}
|
|
194
180
|
|
|
195
|
-
private async publishSessionUdidContended(
|
|
196
|
-
driver: AndroidUiautomator2Driver,
|
|
197
|
-
udid: string,
|
|
198
|
-
): Promise<void> {
|
|
181
|
+
private async publishSessionUdidContended(driver: AndroidUiautomator2Driver, udid: string): Promise<void> {
|
|
199
182
|
const ipc = await this.getIpc();
|
|
200
183
|
const sessionId = driver.sessionId ?? undefined;
|
|
201
184
|
if (!ipc || !udid || !sessionId) {
|
|
202
185
|
return;
|
|
203
186
|
}
|
|
204
187
|
|
|
205
|
-
await ipc.publish<SessionUdidIpcMessage>(
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
udid,
|
|
210
|
-
sessionId,
|
|
211
|
-
},
|
|
212
|
-
);
|
|
188
|
+
await ipc.publish<SessionUdidIpcMessage>(SessionClaimHandler.CONTENDED_TOPIC, this.getPublisherId(driver), {
|
|
189
|
+
udid,
|
|
190
|
+
sessionId,
|
|
191
|
+
});
|
|
213
192
|
}
|
|
214
193
|
|
|
215
|
-
private async terminateSessionOnRequest(
|
|
216
|
-
driver: AndroidUiautomator2Driver,
|
|
217
|
-
udid: string,
|
|
218
|
-
): Promise<void> {
|
|
194
|
+
private async terminateSessionOnRequest(driver: AndroidUiautomator2Driver, udid: string): Promise<void> {
|
|
219
195
|
const sessionId = driver.sessionId ?? undefined;
|
|
220
196
|
const publisherId = sessionId ? this.getPublisherId(driver) : undefined;
|
|
221
197
|
const {log} = driver;
|
package/lib/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {DriverCaps, DriverOpts, W3CDriverCaps} from '@appium/types';
|
|
2
|
+
|
|
2
3
|
import type {RelativeRect} from './commands/types.js';
|
|
3
4
|
import type {Uiautomator2Constraints} from './constraints.js';
|
|
4
5
|
|
|
@@ -41,8 +42,7 @@ export interface Uiautomator2ServerInfo {
|
|
|
41
42
|
desired: Uiautomator2DriverCaps;
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
export interface Uiautomator2StartSessionOpts
|
|
45
|
-
extends Uiautomator2DriverCaps, Uiautomator2ServerInfo {}
|
|
45
|
+
export interface Uiautomator2StartSessionOpts extends Uiautomator2DriverCaps, Uiautomator2ServerInfo {}
|
|
46
46
|
|
|
47
47
|
export interface Uiautomator2SessionCaps
|
|
48
48
|
extends Uiautomator2ServerInfo, Uiautomator2SessionInfo, Partial<Uiautomator2DeviceDetails> {}
|
|
@@ -1,22 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import type {AppiumLogger, StringRecord, HTTPMethod, HTTPBody, ProxyResponse, ProxyOptions} from '@appium/types';
|
|
2
|
+
import type {ADB, InstallState} from 'appium-adb';
|
|
3
3
|
import {
|
|
4
4
|
SERVER_APK_PATH as apkPath,
|
|
5
5
|
TEST_APK_PATH as testApkPath,
|
|
6
6
|
version as serverVersion,
|
|
7
7
|
} from 'appium-uiautomator2-server';
|
|
8
|
+
import {JWProxy, errors} from 'appium/driver.js';
|
|
8
9
|
import {util, timing} from 'appium/support.js';
|
|
9
|
-
import
|
|
10
|
-
AppiumLogger,
|
|
11
|
-
StringRecord,
|
|
12
|
-
HTTPMethod,
|
|
13
|
-
HTTPBody,
|
|
14
|
-
ProxyResponse,
|
|
15
|
-
ProxyOptions,
|
|
16
|
-
} from '@appium/types';
|
|
10
|
+
import {sleep, waitForCondition} from 'asyncbox';
|
|
17
11
|
import axios from 'axios';
|
|
18
|
-
import type {ADB, InstallState} from 'appium-adb';
|
|
19
12
|
import type {SubProcess} from 'teen_process';
|
|
13
|
+
|
|
20
14
|
import {SERVER_PACKAGE_ID, SERVER_TEST_PACKAGE_ID} from './packages.js';
|
|
21
15
|
|
|
22
16
|
const SERVER_LAUNCH_TIMEOUT_MS = 30000;
|
|
@@ -147,11 +141,8 @@ export class UiAutomator2Server {
|
|
|
147
141
|
const shouldUninstallServerPackages = this.shouldUninstallServerPackages(packagesInfo);
|
|
148
142
|
// Install must always follow uninstall. Also, perform the install if
|
|
149
143
|
// any of server packages is not installed or is outdated
|
|
150
|
-
const shouldInstallServerPackages =
|
|
151
|
-
|
|
152
|
-
this.log.info(
|
|
153
|
-
`Server packages are ${shouldInstallServerPackages ? '' : 'not '}going to be (re)installed`,
|
|
154
|
-
);
|
|
144
|
+
const shouldInstallServerPackages = shouldUninstallServerPackages || this.shouldInstallServerPackages(packagesInfo);
|
|
145
|
+
this.log.info(`Server packages are ${shouldInstallServerPackages ? '' : 'not '}going to be (re)installed`);
|
|
155
146
|
if (shouldInstallServerPackages && shouldUninstallServerPackages) {
|
|
156
147
|
this.log.info('Full packages reinstall is going to be performed');
|
|
157
148
|
}
|
|
@@ -183,9 +174,7 @@ export class UiAutomator2Server {
|
|
|
183
174
|
async startSession(caps: StringRecord): Promise<void> {
|
|
184
175
|
await this.cleanupAutomationLeftovers();
|
|
185
176
|
if (caps.skipServerInstallation) {
|
|
186
|
-
this.log.info(
|
|
187
|
-
`'skipServerInstallation' is set. Attempting to use UIAutomator2 server from the device`,
|
|
188
|
-
);
|
|
177
|
+
this.log.info(`'skipServerInstallation' is set. Attempting to use UIAutomator2 server from the device`);
|
|
189
178
|
} else {
|
|
190
179
|
this.log.info(`Starting UIAutomator2 server ${serverVersion}`);
|
|
191
180
|
this.log.info(`Using UIAutomator2 server from '${apkPath}' and test from '${testApkPath}'`);
|
|
@@ -248,8 +237,7 @@ export class UiAutomator2Server {
|
|
|
248
237
|
}
|
|
249
238
|
|
|
250
239
|
this.log.debug(
|
|
251
|
-
`The initialization of the instrumentation process took ` +
|
|
252
|
-
`${timer.getDuration().asMilliSeconds.toFixed(0)}ms`,
|
|
240
|
+
`The initialization of the instrumentation process took ` + `${timer.getDuration().asMilliSeconds.toFixed(0)}ms`,
|
|
253
241
|
);
|
|
254
242
|
await this.jwproxy.command('/session', 'POST', {
|
|
255
243
|
capabilities: {
|
|
@@ -266,8 +254,7 @@ export class UiAutomator2Server {
|
|
|
266
254
|
await this.jwproxy.command('/', 'DELETE');
|
|
267
255
|
} catch (err: any) {
|
|
268
256
|
this.log.warn(
|
|
269
|
-
`Did not get the confirmation of UiAutomator2 server session deletion. ` +
|
|
270
|
-
`Original error: ${err.message}`,
|
|
257
|
+
`Did not get the confirmation of UiAutomator2 server session deletion. ` + `Original error: ${err.message}`,
|
|
271
258
|
);
|
|
272
259
|
}
|
|
273
260
|
|
|
@@ -283,10 +270,7 @@ export class UiAutomator2Server {
|
|
|
283
270
|
}
|
|
284
271
|
|
|
285
272
|
try {
|
|
286
|
-
await Promise.all([
|
|
287
|
-
this.adb.forceStop(SERVER_PACKAGE_ID),
|
|
288
|
-
this.adb.forceStop(SERVER_TEST_PACKAGE_ID),
|
|
289
|
-
]);
|
|
273
|
+
await Promise.all([this.adb.forceStop(SERVER_PACKAGE_ID), this.adb.forceStop(SERVER_TEST_PACKAGE_ID)]);
|
|
290
274
|
} catch {}
|
|
291
275
|
}
|
|
292
276
|
|
|
@@ -302,10 +286,7 @@ export class UiAutomator2Server {
|
|
|
302
286
|
// since it does not contain any version info
|
|
303
287
|
resultInfo.installState = this.adb.APP_INSTALL_STATE.SAME_VERSION_INSTALLED;
|
|
304
288
|
} else if (appId === SERVER_PACKAGE_ID) {
|
|
305
|
-
resultInfo.installState = await this.adb.getApplicationInstallState(
|
|
306
|
-
resultInfo.appPath,
|
|
307
|
-
appId,
|
|
308
|
-
);
|
|
289
|
+
resultInfo.installState = await this.adb.getApplicationInstallState(resultInfo.appPath, appId);
|
|
309
290
|
}
|
|
310
291
|
|
|
311
292
|
return resultInfo;
|
|
@@ -330,10 +311,9 @@ export class UiAutomator2Server {
|
|
|
330
311
|
({installState}) => installState !== this.adb.APP_INSTALL_STATE.NOT_INSTALLED,
|
|
331
312
|
);
|
|
332
313
|
const isAnyComponentNotInstalledOrNewer = packagesInfo.some(({installState}) =>
|
|
333
|
-
[
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
].includes(installState),
|
|
314
|
+
[this.adb.APP_INSTALL_STATE.NOT_INSTALLED, this.adb.APP_INSTALL_STATE.NEWER_VERSION_INSTALLED].includes(
|
|
315
|
+
installState,
|
|
316
|
+
),
|
|
337
317
|
);
|
|
338
318
|
return isAnyComponentInstalled && isAnyComponentNotInstalledOrNewer;
|
|
339
319
|
}
|
|
@@ -347,10 +327,9 @@ export class UiAutomator2Server {
|
|
|
347
327
|
*/
|
|
348
328
|
private shouldInstallServerPackages(packagesInfo: PackageInfo[] = []): boolean {
|
|
349
329
|
return packagesInfo.some(({installState}) =>
|
|
350
|
-
[
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
].includes(installState),
|
|
330
|
+
[this.adb.APP_INSTALL_STATE.NOT_INSTALLED, this.adb.APP_INSTALL_STATE.OLDER_VERSION_INSTALLED].includes(
|
|
331
|
+
installState,
|
|
332
|
+
),
|
|
354
333
|
);
|
|
355
334
|
}
|
|
356
335
|
|
|
@@ -390,9 +369,7 @@ export class UiAutomator2Server {
|
|
|
390
369
|
);
|
|
391
370
|
} catch {
|
|
392
371
|
const errorMessage = (pmError as any)?.message || 'Unknown error';
|
|
393
|
-
this.log.error(
|
|
394
|
-
`Unable to find instrumentation target '${INSTRUMENTATION_TARGET}': ${errorMessage}`,
|
|
395
|
-
);
|
|
372
|
+
this.log.error(`Unable to find instrumentation target '${INSTRUMENTATION_TARGET}': ${errorMessage}`);
|
|
396
373
|
if (pmOutput) {
|
|
397
374
|
this.log.debug('Available targets:');
|
|
398
375
|
for (const line of pmOutput.split('\n')) {
|
|
@@ -408,11 +385,7 @@ export class UiAutomator2Server {
|
|
|
408
385
|
cmd.push('--no-window-animation');
|
|
409
386
|
}
|
|
410
387
|
if (typeof this.disableSuppressAccessibilityService === 'boolean') {
|
|
411
|
-
cmd.push(
|
|
412
|
-
'-e',
|
|
413
|
-
'DISABLE_SUPPRESS_ACCESSIBILITY_SERVICES',
|
|
414
|
-
`${this.disableSuppressAccessibilityService}`,
|
|
415
|
-
);
|
|
388
|
+
cmd.push('-e', 'DISABLE_SUPPRESS_ACCESSIBILITY_SERVICES', `${this.disableSuppressAccessibilityService}`);
|
|
416
389
|
}
|
|
417
390
|
// Disable Google analytics to prevent possible fatal exception
|
|
418
391
|
cmd.push('-e', 'disableAnalytics', 'true');
|
|
@@ -424,9 +397,7 @@ export class UiAutomator2Server {
|
|
|
424
397
|
);
|
|
425
398
|
}
|
|
426
399
|
this.instrumentationProcess.once('exit', (code: number | null, signal: string | null) => {
|
|
427
|
-
this.log.debug(
|
|
428
|
-
`[Instrumentation] The process has exited with code ${code}, signal ${signal}`,
|
|
429
|
-
);
|
|
400
|
+
this.log.debug(`[Instrumentation] The process has exited with code ${code}, signal ${signal}`);
|
|
430
401
|
this.jwproxy.didInstrumentationExit = true;
|
|
431
402
|
});
|
|
432
403
|
await this.instrumentationProcess.start(0);
|
|
@@ -444,9 +415,7 @@ export class UiAutomator2Server {
|
|
|
444
415
|
}
|
|
445
416
|
|
|
446
417
|
private async cleanupAutomationLeftovers(strictCleanup: boolean = false): Promise<void> {
|
|
447
|
-
this.log.debug(
|
|
448
|
-
`Performing ${strictCleanup ? 'strict' : 'shallow'} cleanup of automation leftovers`,
|
|
449
|
-
);
|
|
418
|
+
this.log.debug(`Performing ${strictCleanup ? 'strict' : 'shallow'} cleanup of automation leftovers`);
|
|
450
419
|
|
|
451
420
|
const serverBase = `http://${this.host}:${this.systemPort}`;
|
|
452
421
|
try {
|
|
@@ -459,9 +428,7 @@ export class UiAutomator2Server {
|
|
|
459
428
|
const activeSessionIds = value.map(({id}) => id).filter(Boolean);
|
|
460
429
|
if (activeSessionIds.length) {
|
|
461
430
|
this.log.debug(`The following obsolete sessions are still running: ${activeSessionIds}`);
|
|
462
|
-
this.log.debug(
|
|
463
|
-
`Cleaning up ${util.pluralize('obsolete session', activeSessionIds.length, true)}`,
|
|
464
|
-
);
|
|
431
|
+
this.log.debug(`Cleaning up ${util.pluralize('obsolete session', activeSessionIds.length, true)}`);
|
|
465
432
|
await Promise.all(
|
|
466
433
|
activeSessionIds.map((id: string) =>
|
|
467
434
|
axios.delete(`${serverBase}/session/${id}`, {
|
|
@@ -479,10 +446,7 @@ export class UiAutomator2Server {
|
|
|
479
446
|
}
|
|
480
447
|
|
|
481
448
|
try {
|
|
482
|
-
await Promise.all([
|
|
483
|
-
this.adb.forceStop(SERVER_PACKAGE_ID),
|
|
484
|
-
this.adb.forceStop(SERVER_TEST_PACKAGE_ID),
|
|
485
|
-
]);
|
|
449
|
+
await Promise.all([this.adb.forceStop(SERVER_PACKAGE_ID), this.adb.forceStop(SERVER_TEST_PACKAGE_ID)]);
|
|
486
450
|
} catch {}
|
|
487
451
|
if (strictCleanup) {
|
|
488
452
|
// https://github.com/appium/appium/issues/10749
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
1
4
|
import type {Orientation, StringRecord} from '@appium/types';
|
|
2
|
-
import {SETTINGS_HELPER_ID} from 'io.appium.settings';
|
|
3
5
|
import {util} from 'appium/support.js';
|
|
4
6
|
import {retryInterval} from 'asyncbox';
|
|
5
|
-
import
|
|
6
|
-
import path from 'node:path';
|
|
7
|
+
import {SETTINGS_HELPER_ID} from 'io.appium.settings';
|
|
7
8
|
import {checkPortStatus, findAPortNotInUse} from 'portscanner';
|
|
8
9
|
import type {ExecError} from 'teen_process';
|
|
10
|
+
|
|
9
11
|
import type {AndroidUiautomator2Driver} from '../driver.js';
|
|
10
12
|
import type {
|
|
11
13
|
EmptyObject,
|
|
@@ -23,10 +25,10 @@ const DEVICE_PORT_RANGE = [8200, 8299];
|
|
|
23
25
|
|
|
24
26
|
// The guard is needed to avoid dynamic system port allocation conflicts for
|
|
25
27
|
// parallel driver sessions
|
|
26
|
-
const DEVICE_PORT_ALLOCATION_GUARD = util.getLockFileGuard(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
);
|
|
28
|
+
const DEVICE_PORT_ALLOCATION_GUARD = util.getLockFileGuard(path.resolve(os.tmpdir(), 'uia2_device_port_guard'), {
|
|
29
|
+
timeout: 25,
|
|
30
|
+
tryRecovery: true,
|
|
31
|
+
});
|
|
30
32
|
|
|
31
33
|
// This is the port that UiAutomator2 listens to on the device. We will forward
|
|
32
34
|
// one of the ports above on the system to this port on the device.
|
|
@@ -112,9 +114,7 @@ export async function releaseMjpegServerPort(this: AndroidUiautomator2Driver) {
|
|
|
112
114
|
}
|
|
113
115
|
|
|
114
116
|
/** Runs device preparation steps before the UiAutomator2 server session starts. */
|
|
115
|
-
export async function performPreExecSetup(
|
|
116
|
-
this: AndroidUiautomator2Driver,
|
|
117
|
-
): Promise<StringRecord | undefined> {
|
|
117
|
+
export async function performPreExecSetup(this: AndroidUiautomator2Driver): Promise<StringRecord | undefined> {
|
|
118
118
|
const apiLevel = await this.adb.getApiLevel();
|
|
119
119
|
if (apiLevel < MIN_SUPPORTED_API_LEVEL) {
|
|
120
120
|
throw this.log.errorWithException('UIAutomator2 only supports Android 8.0 (Oreo) and above');
|
|
@@ -144,9 +144,7 @@ export async function performPreExecSetup(
|
|
|
144
144
|
if (util.hasValue(this.opts.gpsEnabled)) {
|
|
145
145
|
preflightPromises.push(
|
|
146
146
|
(async () => {
|
|
147
|
-
this.log.info(
|
|
148
|
-
`Trying to ${this.opts.gpsEnabled ? 'enable' : 'disable'} gps location provider`,
|
|
149
|
-
);
|
|
147
|
+
this.log.info(`Trying to ${this.opts.gpsEnabled ? 'enable' : 'disable'} gps location provider`);
|
|
150
148
|
await this.adb.toggleGPSLocationProvider(Boolean(this.opts.gpsEnabled));
|
|
151
149
|
})(),
|
|
152
150
|
);
|
|
@@ -327,17 +325,10 @@ export async function initServer(this: AndroidUiautomator2Driver) {
|
|
|
327
325
|
} else {
|
|
328
326
|
await this.uiautomator2.installServerApk(this.opts.uiautomator2ServerInstallTimeout);
|
|
329
327
|
try {
|
|
330
|
-
await this.requireAdb().addToDeviceIdleWhitelist(
|
|
331
|
-
SETTINGS_HELPER_ID,
|
|
332
|
-
SERVER_PACKAGE_ID,
|
|
333
|
-
SERVER_TEST_PACKAGE_ID,
|
|
334
|
-
);
|
|
328
|
+
await this.requireAdb().addToDeviceIdleWhitelist(SETTINGS_HELPER_ID, SERVER_PACKAGE_ID, SERVER_TEST_PACKAGE_ID);
|
|
335
329
|
} catch (e) {
|
|
336
330
|
const err = e as ExecError;
|
|
337
|
-
this.log.warn(
|
|
338
|
-
`Cannot add server packages to the Doze whitelist. Original error: ` +
|
|
339
|
-
(err.stderr || err.message),
|
|
340
|
-
);
|
|
331
|
+
this.log.warn(`Cannot add server packages to the Doze whitelist. Original error: ` + (err.stderr || err.message));
|
|
341
332
|
}
|
|
342
333
|
}
|
|
343
334
|
|
package/lib/utils/app.ts
CHANGED
package/lib/utils/mjpeg.ts
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Transform,
|
|
3
|
-
Writable,
|
|
4
|
-
type Readable,
|
|
5
|
-
type TransformCallback,
|
|
6
|
-
type WritableOptions,
|
|
7
|
-
} from 'node:stream';
|
|
1
|
+
import {Transform, Writable, type Readable, type TransformCallback, type WritableOptions} from 'node:stream';
|
|
8
2
|
|
|
9
3
|
import {logger} from 'appium/support.js';
|
|
10
4
|
import axios from 'axios';
|
|
@@ -144,11 +138,7 @@ export class MJpegStream extends Writable {
|
|
|
144
138
|
* @param errorHandler - additional function that will be called in the case of any errors
|
|
145
139
|
* @param options - Options to pass to the Writable constructor
|
|
146
140
|
*/
|
|
147
|
-
constructor(
|
|
148
|
-
mJpegUrl: string,
|
|
149
|
-
errorHandler: (err: Error) => void = noop,
|
|
150
|
-
options: WritableOptions = {},
|
|
151
|
-
) {
|
|
141
|
+
constructor(mJpegUrl: string, errorHandler: (err: Error) => void = noop, options: WritableOptions = {}) {
|
|
152
142
|
super(options);
|
|
153
143
|
this.errorHandler = errorHandler;
|
|
154
144
|
this.url = mJpegUrl;
|
|
@@ -216,12 +206,9 @@ export class MJpegStream extends Writable {
|
|
|
216
206
|
} else {
|
|
217
207
|
message = String(e);
|
|
218
208
|
}
|
|
219
|
-
throw new Error(
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
cause: e,
|
|
223
|
-
},
|
|
224
|
-
);
|
|
209
|
+
throw new Error(`Cannot connect to the MJPEG stream at ${url}. Original error: ${message}`, {
|
|
210
|
+
cause: e,
|
|
211
|
+
});
|
|
225
212
|
}
|
|
226
213
|
} finally {
|
|
227
214
|
clearTimeout(connectTimeoutId);
|
|
@@ -238,9 +225,7 @@ export class MJpegStream extends Writable {
|
|
|
238
225
|
this.lastChunk = null;
|
|
239
226
|
// No-op if start() has already resolved; only rejects a still-pending start().
|
|
240
227
|
this.registerStartFailure?.(
|
|
241
|
-
new Error(
|
|
242
|
-
`The connection to the MJPEG stream at ${url} has been closed before any frame was received`,
|
|
243
|
-
),
|
|
228
|
+
new Error(`The connection to the MJPEG stream at ${url} has been closed before any frame was received`),
|
|
244
229
|
);
|
|
245
230
|
};
|
|
246
231
|
|
|
@@ -249,8 +234,7 @@ export class MJpegStream extends Writable {
|
|
|
249
234
|
this.registerStartSuccess = resolve;
|
|
250
235
|
this.registerStartFailure = reject;
|
|
251
236
|
timeoutId = setTimeout(
|
|
252
|
-
() =>
|
|
253
|
-
reject(new Error(`Waited ${serverTimeout}ms but the MJPEG server never sent any images`)),
|
|
237
|
+
() => reject(new Error(`Waited ${serverTimeout}ms but the MJPEG server never sent any images`)),
|
|
254
238
|
serverTimeout,
|
|
255
239
|
);
|
|
256
240
|
});
|
|
@@ -286,11 +270,7 @@ export class MJpegStream extends Writable {
|
|
|
286
270
|
}
|
|
287
271
|
|
|
288
272
|
/* eslint-disable promise/prefer-await-to-callbacks -- Writable._write is callback-based */
|
|
289
|
-
override _write(
|
|
290
|
-
chunk: Buffer | string,
|
|
291
|
-
_encoding: BufferEncoding,
|
|
292
|
-
callback: (error?: Error | null) => void,
|
|
293
|
-
): void {
|
|
273
|
+
override _write(chunk: Buffer | string, _encoding: BufferEncoding, callback: (error?: Error | null) => void): void {
|
|
294
274
|
this.lastChunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
295
275
|
this.updateCount++;
|
|
296
276
|
if (this.registerStartSuccess) {
|
package/lib/utils/object.ts
CHANGED
|
@@ -2,10 +2,7 @@
|
|
|
2
2
|
* Assigns own enumerable properties of `source` onto `target` only where `target[key] === undefined`
|
|
3
3
|
* (lodash `defaults` semantics).
|
|
4
4
|
*/
|
|
5
|
-
export function assignDefaults<T extends Record<string, unknown>>(
|
|
6
|
-
target: T,
|
|
7
|
-
source: Record<string, unknown>,
|
|
8
|
-
): void {
|
|
5
|
+
export function assignDefaults<T extends Record<string, unknown>>(target: T, source: Record<string, unknown>): void {
|
|
9
6
|
for (const key of Object.keys(source)) {
|
|
10
7
|
if (target[key] === undefined) {
|
|
11
8
|
(target as Record<string, unknown>)[key] = source[key];
|