appium-uiautomator2-driver 3.6.1 → 3.7.1

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.
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @this {AndroidUiautomator2Driver}
3
+ * @returns {Promise<string>} Base64-encoded content of the clipboard
4
+ * or an empty string if the clipboard is empty.
5
+ */
6
+ export async function getClipboard() {
7
+ return String(
8
+ (await this.adb.getApiLevel()) < 29
9
+ ? await this.uiautomator2.jwproxy.command(
10
+ '/appium/device/get_clipboard',
11
+ 'POST',
12
+ {}
13
+ )
14
+ : await this.settingsApp.getClipboard()
15
+ );
16
+ }
17
+
18
+ /**
19
+ * @this {AndroidUiautomator2Driver}
20
+ * @returns {Promise<string>} Base64-encoded content of the clipboard
21
+ * or an empty string if the clipboard is empty.
22
+ */
23
+ export async function mobileGetClipboard() {
24
+ return await this.getClipboard();
25
+ }
26
+
27
+ /**
28
+ * @this {AndroidUiautomator2Driver}
29
+ * @param {string} content Base64-encoded clipboard payload
30
+ * @param {'plaintext'} [contentType='plaintext'] Only a single
31
+ * content type is supported, which is 'plaintext'
32
+ * @param {string} [label] Optinal label to identify the current
33
+ * clipboard payload
34
+ * @returns {Promise<void>}
35
+ */
36
+ export async function setClipboard(content, contentType, label) {
37
+ await this.uiautomator2.jwproxy.command(
38
+ '/appium/device/set_clipboard',
39
+ 'POST',
40
+ {content, contentType, label}
41
+ );
42
+ }
43
+
44
+ /**
45
+ * @this {AndroidUiautomator2Driver}
46
+ * @param {import('./types').SetClipboardOpts} opts
47
+ * @returns {Promise<void>}
48
+ */
49
+ export async function mobileSetClipboard(opts) {
50
+ await this.setClipboard(opts.content, opts.contentType, opts.label);
51
+ }
52
+
53
+ /**
54
+ * @typedef {import('../driver').AndroidUiautomator2Driver} AndroidUiautomator2Driver
55
+ */
@@ -49,6 +49,9 @@ export function mobileCommandsMapping() {
49
49
  scheduleAction: 'mobileScheduleAction',
50
50
  getActionHistory: 'mobileGetActionHistory',
51
51
  unscheduleAction: 'mobileUnscheduleAction',
52
+
53
+ setClipboard: 'mobileSetClipboard',
54
+ getClipboard: 'mobileGetClipboard',
52
55
  };
53
56
  }
54
57
 
@@ -40,22 +40,6 @@ export async function setOrientation(orientation) {
40
40
  );
41
41
  }
42
42
 
43
- /**
44
- * @this {AndroidUiautomator2Driver}
45
- * @returns {Promise<string>}
46
- */
47
- export async function getClipboard() {
48
- return String(
49
- (await this.adb.getApiLevel()) < 29
50
- ? await this.uiautomator2.jwproxy.command(
51
- '/appium/device/get_clipboard',
52
- 'POST',
53
- {}
54
- )
55
- : await this.settingsApp.getClipboard()
56
- );
57
- }
58
-
59
43
  /**
60
44
  * @this {AndroidUiautomator2Driver}
61
45
  * @returns {Promise<void>}
@@ -471,3 +471,18 @@ export interface ActionResult {
471
471
  export interface ActionArgs {
472
472
  name: string;
473
473
  }
474
+
475
+ export interface SetClipboardOpts {
476
+ /**
477
+ * Base64-encoded clipboard payload
478
+ */
479
+ content: string;
480
+ /**
481
+ * Only a single content type is supported, which is 'plaintext'
482
+ */
483
+ contentType?: 'plaintext';
484
+ /**
485
+ * Optinal label to identify the current clipboard payload
486
+ */
487
+ label?: string;
488
+ }
package/lib/driver.ts CHANGED
@@ -61,6 +61,12 @@ import {
61
61
  import {
62
62
  mobileGetBatteryInfo,
63
63
  } from './commands/battery';
64
+ import {
65
+ getClipboard,
66
+ mobileGetClipboard,
67
+ setClipboard,
68
+ mobileSetClipboard,
69
+ } from './commands/clipboard';
64
70
  import {
65
71
  active,
66
72
  getAttribute,
@@ -111,7 +117,6 @@ import {
111
117
  getPageSource,
112
118
  getOrientation,
113
119
  setOrientation,
114
- getClipboard,
115
120
  openNotifications,
116
121
  suspendChromedriverProxy,
117
122
  mobileGetDeviceInfo,
@@ -1054,11 +1059,15 @@ class AndroidUiautomator2Driver
1054
1059
  getPageSource = getPageSource;
1055
1060
  getOrientation = getOrientation;
1056
1061
  setOrientation = setOrientation;
1057
- getClipboard = getClipboard;
1058
1062
  openNotifications = openNotifications;
1059
1063
  suspendChromedriverProxy = suspendChromedriverProxy as any;
1060
1064
  mobileGetDeviceInfo = mobileGetDeviceInfo;
1061
1065
 
1066
+ getClipboard = getClipboard;
1067
+ mobileGetClipboard = mobileGetClipboard;
1068
+ setClipboard = setClipboard;
1069
+ mobileSetClipboard = mobileSetClipboard;
1070
+
1062
1071
  setUrl = setUrl;
1063
1072
  mobileDeepLink = mobileDeepLink;
1064
1073
  back = back;
@@ -6,7 +6,7 @@ import {
6
6
  TEST_APK_PATH as testApkPath,
7
7
  version as serverVersion
8
8
  } from 'appium-uiautomator2-server';
9
- import { util, logger, timing } from 'appium/support';
9
+ import { util, timing } from 'appium/support';
10
10
  import B from 'bluebird';
11
11
  import axios from 'axios';
12
12
 
@@ -17,7 +17,6 @@ const SERVICES_LAUNCH_TIMEOUT = 30000;
17
17
  const SERVER_PACKAGE_ID = 'io.appium.uiautomator2.server';
18
18
  const SERVER_TEST_PACKAGE_ID = `${SERVER_PACKAGE_ID}.test`;
19
19
  const INSTRUMENTATION_TARGET = `${SERVER_TEST_PACKAGE_ID}/androidx.test.runner.AndroidJUnitRunner`;
20
- const instrumentationLogger = logger.getLogger('Instrumentation');
21
20
 
22
21
  class UIA2Proxy extends JWProxy {
23
22
  /** @type {boolean} */
@@ -50,6 +49,9 @@ class UiAutomator2Server {
50
49
  /** @type {boolean|undefined} */
51
50
  disableSuppressAccessibilityService;
52
51
 
52
+ /** @type {import('teen_process').SubProcess|null} */
53
+ instrumentationProcess;
54
+
53
55
  constructor (log, opts = {}) {
54
56
  for (let req of REQD_PARAMS) {
55
57
  if (!opts || !util.hasValue(opts[req])) {
@@ -72,6 +74,7 @@ class UiAutomator2Server {
72
74
  this.proxyReqRes = this.jwproxy.proxyReqRes.bind(this.jwproxy);
73
75
  this.proxyCommand = this.jwproxy.command.bind(this.jwproxy);
74
76
  this.jwproxy.didInstrumentationExit = false;
77
+ this.instrumentationProcess = null;
75
78
  }
76
79
 
77
80
  /**
@@ -255,6 +258,9 @@ class UiAutomator2Server {
255
258
  while (retries < maxRetries) {
256
259
  this.log.info(`Waiting up to ${timeout}ms for UiAutomator2 to be online...`);
257
260
  this.jwproxy.didInstrumentationExit = false;
261
+ try {
262
+ await this.stopInstrumentationProcess();
263
+ } catch (ign) {}
258
264
  await this.startInstrumentationProcess();
259
265
  if (!this.jwproxy.didInstrumentationExit) {
260
266
  try {
@@ -312,18 +318,30 @@ class UiAutomator2Server {
312
318
  // Disable Google analytics to prevent possible fatal exception
313
319
  cmd.push('-e', 'disableAnalytics', 'true');
314
320
  cmd.push(INSTRUMENTATION_TARGET);
315
- const instrumentationProcess = this.adb.createSubProcess(['shell', ...cmd]);
316
- instrumentationProcess.on('output', (stdout, stderr) => {
317
- const output = _.trim(stdout || stderr);
318
- if (output) {
319
- instrumentationLogger.debug(output);
320
- }
321
- });
322
- instrumentationProcess.on('exit', (code) => {
323
- instrumentationLogger.debug(`The process has exited with code ${code}`);
321
+ this.instrumentationProcess = this.adb.createSubProcess(['shell', ...cmd]);
322
+ for (const streamName of ['stderr', 'stdout']) {
323
+ this.instrumentationProcess.on(`line-${streamName}`, (line) => this.log.debug(`[Instrumentation] ${line}`));
324
+ }
325
+ this.instrumentationProcess.once('exit', (code, signal) => {
326
+ this.log.debug(`[Instrumentation] The process has exited with code ${code}, signal ${signal}`);
324
327
  this.jwproxy.didInstrumentationExit = true;
325
328
  });
326
- await instrumentationProcess.start(0);
329
+ await this.instrumentationProcess.start(0);
330
+ }
331
+
332
+ async stopInstrumentationProcess () {
333
+ if (!this.instrumentationProcess) {
334
+ return;
335
+ }
336
+
337
+ try {
338
+ if (this.instrumentationProcess.isRunning) {
339
+ await this.instrumentationProcess.stop();
340
+ }
341
+ } finally {
342
+ this.instrumentationProcess.removeAllListeners();
343
+ this.instrumentationProcess = null;
344
+ }
327
345
  }
328
346
 
329
347
  async deleteSession () {
@@ -336,6 +354,11 @@ class UiAutomator2Server {
336
354
  this.log.warn(`Did not get confirmation UiAutomator2 deleteSession worked; ` +
337
355
  `Error was: ${err}`);
338
356
  }
357
+ try {
358
+ await this.stopInstrumentationProcess();
359
+ } catch (err) {
360
+ this.log.warn(`Could not stop the instrumentation process. Original error: ${err.message}`);
361
+ }
339
362
  }
340
363
 
341
364
  async cleanupAutomationLeftovers (strictCleanup = false) {