appium-uiautomator2-driver 1.70.1 → 1.74.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.
@@ -1,6 +1,6 @@
1
1
  import CssConverter from '../css-converter';
2
2
 
3
- let helpers = {}, extensions = {};
3
+ const helpers = {};
4
4
 
5
5
  // we override the xpath search for this first-visible-child selector, which
6
6
  // looks like /*[@firstVisible="true"]
@@ -24,15 +24,11 @@ helpers.doFindElementOrEls = async function (params) {
24
24
  }
25
25
  if (params.strategy === 'css selector') {
26
26
  params.strategy = '-android uiautomator';
27
- params.selector = CssConverter.toUiAutomatorSelector(params.selector);
28
- }
29
- if (params.multiple) {
30
- return await this.uiautomator2.jwproxy.command(`/elements`, 'POST', params);
31
- } else {
32
- return await this.uiautomator2.jwproxy.command(`/element`, 'POST', params);
27
+ params.selector = new CssConverter(params.selector, this.opts.appPackage)
28
+ .toUiAutomatorSelector();
33
29
  }
30
+ return await this.uiautomator2.jwproxy.command(`/element${params.multiple ? 's' : ''}`, 'POST', params);
34
31
  };
35
32
 
36
- Object.assign(extensions, helpers);
37
33
  export { helpers };
38
- export default extensions;
34
+ export default helpers;
@@ -116,6 +116,7 @@ extensions.executeMobile = async function (mobileCommand, opts = {}) {
116
116
  dragGesture: 'mobileDragGesture',
117
117
  flingGesture: 'mobileFlingGesture',
118
118
  doubleClickGesture: 'mobileDoubleClickGesture',
119
+ clickGesture: 'mobileClickGesture',
119
120
  longClickGesture: 'mobileLongClickGesture',
120
121
  pinchCloseGesture: 'mobilePinchCloseGesture',
121
122
  pinchOpenGesture: 'mobilePinchOpenGesture',
@@ -169,6 +170,8 @@ extensions.executeMobile = async function (mobileCommand, opts = {}) {
169
170
  installMultipleApks: 'mobileInstallMultipleApks',
170
171
 
171
172
  unlock: 'mobileUnlock',
173
+
174
+ refreshGpsCache: 'mobileRefreshGpsCache',
172
175
  };
173
176
 
174
177
  if (!_.has(mobileCommandsMapping, mobileCommand)) {
@@ -276,6 +279,7 @@ helpers.wrapBootstrapDisconnect = async function (wrapped) {
276
279
  helpers.suspendChromedriverProxy = function () {
277
280
  this.chromedriver = null;
278
281
  this.proxyReqRes = this.uiautomator2.proxyReqRes.bind(this.uiautomator2);
282
+ this.proxyCommand = this.uiautomator2.proxyCommand.bind(this.uiautomator2);
279
283
  this.jwpProxyActive = true;
280
284
  };
281
285
 
@@ -14,12 +14,37 @@ function toPoint (x, y) {
14
14
  }
15
15
 
16
16
  function toRect (left, top, width, height) {
17
- if ([left, top, width, height].some((v) => !_.isFinite(v))) {
18
- return undefined;
19
- }
20
- return {left, top, width, height};
17
+ return [left, top, width, height].some((v) => !_.isFinite(v))
18
+ ? undefined
19
+ : {left, top, width, height};
21
20
  }
22
21
 
22
+ /**
23
+ * @typedef {Object} ClickOptions
24
+ * @property {?string} elementId - The id of the element to be clicked.
25
+ * If the element is missing then both click offset coordinates must be provided.
26
+ * If both the element id and offset are provided then the coordinates
27
+ * are parsed as relative offsets from the top left corner of the element.
28
+ * @property {?number} x - The x coordinate to click on
29
+ * @property {?number} y - The y coordinate to click on
30
+ */
31
+
32
+ /**
33
+ * Performs a simple click/tap gesture
34
+ *
35
+ * @param {?ClickOptions} opts
36
+ * @throws {Error} if provided options are not valid
37
+ */
38
+ commands.mobileClickGesture = async function mobileClickGesture (opts = {}) {
39
+ const {
40
+ elementId,
41
+ x, y,
42
+ } = opts;
43
+ return await this.uiautomator2.jwproxy.command('/appium/gestures/click', 'POST', {
44
+ origin: toOrigin(elementId),
45
+ offset: toPoint(x, y),
46
+ });
47
+ };
23
48
 
24
49
  /**
25
50
  * @typedef {Object} LongClickOptions
@@ -2,8 +2,6 @@ import { CssSelectorParser } from 'css-selector-parser';
2
2
  import { escapeRegExp } from 'lodash';
3
3
  import { errors } from 'appium-base-driver';
4
4
 
5
- const CssConverter = {};
6
-
7
5
  const parser = new CssSelectorParser();
8
6
  parser.registerSelectorPseudos('has');
9
7
  parser.registerNestingOperators('>', '+', '~');
@@ -113,16 +111,6 @@ function getWordMatcherRegex (word) {
113
111
  return `\\b(\\w*${escapeRegExp(word)}\\w*)\\b`;
114
112
  }
115
113
 
116
- /**
117
- * Add android:id/ to beginning of string if it's not there already
118
- *
119
- * @param {string} locator The initial locator
120
- * @returns {string} String with `android:id/` prepended (if it wasn't already)
121
- */
122
- function formatIdLocator (locator) {
123
- return ID_LOCATOR_PATTERN.test(locator) ? locator : `android:id/${locator}`;
124
- }
125
-
126
114
  /**
127
115
  * @typedef {Object} CssAttr
128
116
  * @property {?string} valueType Type of attribute (must be string or empty)
@@ -131,62 +119,20 @@ function formatIdLocator (locator) {
131
119
  */
132
120
 
133
121
  /**
134
- * Convert a CSS attribute into a UiSelector method call
135
- *
136
- * @param {CssAttr} cssAttr CSS attribute object
137
- * @returns {string} CSS attribute parsed as UiSelector
122
+ * @typedef {Object} CssRule
123
+ * @property {?string} nestingOperator The nesting operator (aka: combinator https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors)
124
+ * @property {?string} tagName The tag name (aka: type selector https://developer.mozilla.org/en-US/docs/Web/CSS/Type_selectors)
125
+ * @property {?string[]} classNames An array of CSS class names
126
+ * @property {?CssAttr[]} attrs An array of CSS attributes
127
+ * @property {?CssPseudo[]} attrs An array of CSS pseudos
128
+ * @property {?string} id CSS identifier
129
+ * @property {?CssRule} rule A descendant of this CSS rule
138
130
  */
139
- function parseAttr (cssAttr) {
140
- if (cssAttr.valueType && cssAttr.valueType !== 'string') {
141
- throw new Error(`'${cssAttr.name}=${cssAttr.value}' is an invalid attribute. ` +
142
- `Only 'string' and empty attribute types are supported. Found '${cssAttr.valueType}'`);
143
- }
144
- const attrName = assertGetAttrName(cssAttr);
145
- const methodName = toSnakeCase(attrName);
146
-
147
- // Validate that it's a supported attribute
148
- if (!STR_ATTRS.includes(attrName) && !BOOLEAN_ATTRS.includes(attrName)) {
149
- throw new Error(`'${attrName}' is not supported. Supported attributes are ` +
150
- `'${[...STR_ATTRS, ...BOOLEAN_ATTRS].join(', ')}'`);
151
- }
152
-
153
- // Parse boolean, if it's a boolean attribute
154
- if (BOOLEAN_ATTRS.includes(attrName)) {
155
- return `.${methodName}(${assertGetBool(cssAttr)})`;
156
- }
157
-
158
- // Otherwise parse as string
159
- let value = cssAttr.value || '';
160
- if (attrName === RESOURCE_ID) {
161
- value = formatIdLocator(value);
162
- }
163
- if (value === '') {
164
- return `.${methodName}Matches("")`;
165
- }
166
131
 
167
- switch (cssAttr.operator) {
168
- case '=':
169
- return `.${methodName}("${value}")`;
170
- case '*=':
171
- if (['description', 'text'].includes(attrName)) {
172
- return `.${methodName}Contains("${value}")`;
173
- }
174
- return `.${methodName}Matches("${escapeRegExp(value)}")`;
175
- case '^=':
176
- if (['description', 'text'].includes(attrName)) {
177
- return `.${methodName}StartsWith("${value}")`;
178
- }
179
- return `.${methodName}Matches("^${escapeRegExp(value)}")`;
180
- case '$=':
181
- return `.${methodName}Matches("${escapeRegExp(value)}$")`;
182
- case '~=':
183
- return `.${methodName}Matches("${getWordMatcherRegex(value)}")`;
184
- default:
185
- // Unreachable, but adding error in case a new CSS attribute is added.
186
- throw new Error(`Unsupported CSS attribute operator '${cssAttr.operator}'. ` +
187
- ` '=', '*=', '^=', '$=' and '~=' are supported.`);
188
- }
189
- }
132
+ /**
133
+ * @typedef {Object} CssObject
134
+ * @property {?string} type Type of CSS object. 'rule', 'ruleset' or 'selectors'
135
+ */
190
136
 
191
137
  /**
192
138
  * @typedef {Object} CssPseudo
@@ -195,126 +141,188 @@ function parseAttr (cssAttr) {
195
141
  * @property {?string} value The value of the pseudo selector
196
142
  */
197
143
 
198
- /**
199
- * Convert a CSS pseudo class to a UiSelector
200
- *
201
- * @param {CssPseudo} cssPseudo CSS Pseudo class
202
- * @returns {string} Pseudo selector parsed as UiSelector
203
- */
204
- function parsePseudo (cssPseudo) {
205
- if (cssPseudo.valueType && cssPseudo.valueType !== 'string') {
206
- throw new Error(`'${cssPseudo.name}=${cssPseudo.value}'. ` +
207
- `Unsupported css pseudo class value type: '${cssPseudo.valueType}'. Only 'string' type or empty is supported.`);
208
- }
209
-
210
- const pseudoName = assertGetAttrName(cssPseudo);
144
+ class CssConverter {
211
145
 
212
- if (BOOLEAN_ATTRS.includes(pseudoName)) {
213
- return `.${toSnakeCase(pseudoName)}(${assertGetBool(cssPseudo)})`;
146
+ constructor (selector, pkg) {
147
+ this.selector = selector;
148
+ this.pkg = pkg;
214
149
  }
215
150
 
216
- if (NUMERIC_ATTRS.includes(pseudoName)) {
217
- return `.${pseudoName}(${cssPseudo.value})`;
151
+ /**
152
+ * Add `<pkgName>:id/` prefix to beginning of string if it's not there already
153
+ *
154
+ * @param {string} locator The initial locator
155
+ * @returns {string} String with `<pkgName>:id/` prepended (if it wasn't already)
156
+ */
157
+ formatIdLocator (locator) {
158
+ return ID_LOCATOR_PATTERN.test(locator)
159
+ ? locator
160
+ : `${this.pkg || 'android'}:id/${locator}`;
218
161
  }
219
- }
220
162
 
221
- /**
222
- * @typedef {Object} CssRule
223
- * @property {?string} nestingOperator The nesting operator (aka: combinator https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors)
224
- * @property {?string} tagName The tag name (aka: type selector https://developer.mozilla.org/en-US/docs/Web/CSS/Type_selectors)
225
- * @property {?string[]} classNames An array of CSS class names
226
- * @property {?CssAttr[]} attrs An array of CSS attributes
227
- * @property {?CssPseudo[]} attrs An array of CSS pseudos
228
- * @property {?string} id CSS identifier
229
- * @property {?CssRule} rule A descendant of this CSS rule
230
- */
163
+ /**
164
+ * Convert a CSS attribute into a UiSelector method call
165
+ *
166
+ * @param {CssAttr} cssAttr CSS attribute object
167
+ * @returns {string} CSS attribute parsed as UiSelector
168
+ */
169
+ parseAttr (cssAttr) {
170
+ if (cssAttr.valueType && cssAttr.valueType !== 'string') {
171
+ throw new Error(`'${cssAttr.name}=${cssAttr.value}' is an invalid attribute. ` +
172
+ `Only 'string' and empty attribute types are supported. Found '${cssAttr.valueType}'`);
173
+ }
174
+ const attrName = assertGetAttrName(cssAttr);
175
+ const methodName = toSnakeCase(attrName);
231
176
 
232
- /**
233
- * Convert a CSS rule to a UiSelector
234
- * @param {CssRule} cssRule CSS rule definition
235
- */
236
- function parseCssRule (cssRule) {
237
- const { nestingOperator } = cssRule;
238
- if (nestingOperator && nestingOperator !== ' ') {
239
- throw new Error(`'${nestingOperator}' is not a supported combinator. ` +
240
- `Only child combinator (>) and descendant combinator are supported.`);
241
- }
177
+ // Validate that it's a supported attribute
178
+ if (!STR_ATTRS.includes(attrName) && !BOOLEAN_ATTRS.includes(attrName)) {
179
+ throw new Error(`'${attrName}' is not supported. Supported attributes are ` +
180
+ `'${[...STR_ATTRS, ...BOOLEAN_ATTRS].join(', ')}'`);
181
+ }
242
182
 
243
- let uiAutomatorSelector = 'new UiSelector()';
244
- if (cssRule.tagName && cssRule.tagName !== '*') {
245
- let androidClass = [cssRule.tagName];
246
- if (cssRule.classNames) {
247
- for (const cssClassNames of cssRule.classNames) {
248
- androidClass.push(cssClassNames);
249
- }
250
- uiAutomatorSelector += `.className("${androidClass.join('.')}")`;
251
- } else {
252
- uiAutomatorSelector += `.classNameMatches("${cssRule.tagName}")`;
183
+ // Parse boolean, if it's a boolean attribute
184
+ if (BOOLEAN_ATTRS.includes(attrName)) {
185
+ return `.${methodName}(${assertGetBool(cssAttr)})`;
253
186
  }
254
- } else if (cssRule.classNames) {
255
- uiAutomatorSelector += `.classNameMatches("${cssRule.classNames.join('\\.')}")`;
256
- }
257
- if (cssRule.id) {
258
- uiAutomatorSelector += `.resourceId("${formatIdLocator(cssRule.id)}")`;
259
- }
260
- if (cssRule.attrs) {
261
- for (const attr of cssRule.attrs) {
262
- uiAutomatorSelector += parseAttr(attr);
187
+
188
+ // Otherwise parse as string
189
+ let value = cssAttr.value || '';
190
+ if (attrName === RESOURCE_ID) {
191
+ value = this.formatIdLocator(value);
263
192
  }
264
- }
265
- if (cssRule.pseudos) {
266
- for (const pseudo of cssRule.pseudos) {
267
- uiAutomatorSelector += parsePseudo(pseudo);
193
+ if (value === '') {
194
+ return `.${methodName}Matches("")`;
195
+ }
196
+
197
+ switch (cssAttr.operator) {
198
+ case '=':
199
+ return `.${methodName}("${value}")`;
200
+ case '*=':
201
+ if (['description', 'text'].includes(attrName)) {
202
+ return `.${methodName}Contains("${value}")`;
203
+ }
204
+ return `.${methodName}Matches("${escapeRegExp(value)}")`;
205
+ case '^=':
206
+ if (['description', 'text'].includes(attrName)) {
207
+ return `.${methodName}StartsWith("${value}")`;
208
+ }
209
+ return `.${methodName}Matches("^${escapeRegExp(value)}")`;
210
+ case '$=':
211
+ return `.${methodName}Matches("${escapeRegExp(value)}$")`;
212
+ case '~=':
213
+ return `.${methodName}Matches("${getWordMatcherRegex(value)}")`;
214
+ default:
215
+ // Unreachable, but adding error in case a new CSS attribute is added.
216
+ throw new Error(`Unsupported CSS attribute operator '${cssAttr.operator}'. ` +
217
+ ` '=', '*=', '^=', '$=' and '~=' are supported.`);
268
218
  }
269
219
  }
270
- if (cssRule.rule) {
271
- uiAutomatorSelector += `.childSelector(${parseCssRule(cssRule.rule)})`;
220
+
221
+ /**
222
+ * Convert a CSS pseudo class to a UiSelector
223
+ *
224
+ * @param {CssPseudo} cssPseudo CSS Pseudo class
225
+ * @returns {string} Pseudo selector parsed as UiSelector
226
+ */
227
+ parsePseudo (cssPseudo) {
228
+ if (cssPseudo.valueType && cssPseudo.valueType !== 'string') {
229
+ throw new Error(`'${cssPseudo.name}=${cssPseudo.value}'. ` +
230
+ `Unsupported css pseudo class value type: '${cssPseudo.valueType}'. Only 'string' type or empty is supported.`);
231
+ }
232
+
233
+ const pseudoName = assertGetAttrName(cssPseudo);
234
+
235
+ if (BOOLEAN_ATTRS.includes(pseudoName)) {
236
+ return `.${toSnakeCase(pseudoName)}(${assertGetBool(cssPseudo)})`;
237
+ }
238
+
239
+ if (NUMERIC_ATTRS.includes(pseudoName)) {
240
+ return `.${pseudoName}(${cssPseudo.value})`;
241
+ }
272
242
  }
273
- return uiAutomatorSelector;
274
- }
275
243
 
276
- /**
277
- * @typedef {Object} CssObject
278
- * @property {?string} type Type of CSS object. 'rule', 'ruleset' or 'selectors'
279
- */
244
+ /**
245
+ * Convert a CSS rule to a UiSelector
246
+ * @param {CssRule} cssRule CSS rule definition
247
+ */
248
+ parseCssRule (cssRule) {
249
+ const { nestingOperator } = cssRule;
250
+ if (nestingOperator && nestingOperator !== ' ') {
251
+ throw new Error(`'${nestingOperator}' is not a supported combinator. ` +
252
+ `Only child combinator (>) and descendant combinator are supported.`);
253
+ }
280
254
 
281
- /**
282
- * Convert CSS object to UiAutomator2 selector
283
- * @param {CssObject} css CSS object
284
- * @returns {string} The CSS object parsed as a UiSelector
285
- */
286
- function parseCssObject (css) {
287
- switch (css.type) {
288
- case 'rule':
289
- return parseCssRule(css);
290
- case 'ruleSet':
291
- return parseCssObject(css.rule);
292
- case 'selectors':
293
- return css.selectors.map((selector) => parseCssObject(selector)).join('; ');
294
-
295
- default:
296
- // This is never reachable, but if it ever is do this.
297
- throw new Error(`UiAutomator does not support '${css.type}' css. Only supports 'rule', 'ruleSet', 'selectors' `);
255
+ let uiAutomatorSelector = 'new UiSelector()';
256
+ if (cssRule.tagName && cssRule.tagName !== '*') {
257
+ let androidClass = [cssRule.tagName];
258
+ if (cssRule.classNames) {
259
+ for (const cssClassNames of cssRule.classNames) {
260
+ androidClass.push(cssClassNames);
261
+ }
262
+ uiAutomatorSelector += `.className("${androidClass.join('.')}")`;
263
+ } else {
264
+ uiAutomatorSelector += `.classNameMatches("${cssRule.tagName}")`;
265
+ }
266
+ } else if (cssRule.classNames) {
267
+ uiAutomatorSelector += `.classNameMatches("${cssRule.classNames.join('\\.')}")`;
268
+ }
269
+ if (cssRule.id) {
270
+ uiAutomatorSelector += `.resourceId("${this.formatIdLocator(cssRule.id)}")`;
271
+ }
272
+ if (cssRule.attrs) {
273
+ for (const attr of cssRule.attrs) {
274
+ uiAutomatorSelector += this.parseAttr(attr);
275
+ }
276
+ }
277
+ if (cssRule.pseudos) {
278
+ for (const pseudo of cssRule.pseudos) {
279
+ uiAutomatorSelector += this.parsePseudo(pseudo);
280
+ }
281
+ }
282
+ if (cssRule.rule) {
283
+ uiAutomatorSelector += `.childSelector(${this.parseCssRule(cssRule.rule)})`;
284
+ }
285
+ return uiAutomatorSelector;
298
286
  }
299
- }
300
287
 
301
- /**
302
- * Convert a CSS selector to a UiAutomator2 selector
303
- * @param {string} cssSelector CSS Selector
304
- * @returns {string} The CSS selector converted to a UiSelector
305
- */
306
- CssConverter.toUiAutomatorSelector = function toUiAutomatorSelector (cssSelector) {
307
- let cssObj;
308
- try {
309
- cssObj = parser.parse(cssSelector);
310
- } catch (e) {
311
- throw new errors.InvalidSelectorError(`Invalid CSS selector '${cssSelector}'. Reason: '${e}'`);
288
+ /**
289
+ * Convert CSS object to UiAutomator2 selector
290
+ * @param {CssObject} css CSS object
291
+ * @returns {string} The CSS object parsed as a UiSelector
292
+ */
293
+ parseCssObject (css) {
294
+ switch (css.type) {
295
+ case 'rule':
296
+ return this.parseCssRule(css);
297
+ case 'ruleSet':
298
+ return this.parseCssObject(css.rule);
299
+ case 'selectors':
300
+ return css.selectors.map((selector) => this.parseCssObject(selector)).join('; ');
301
+
302
+ default:
303
+ // This is never reachable, but if it ever is do this.
304
+ throw new Error(`UiAutomator does not support '${css.type}' css. Only supports 'rule', 'ruleSet', 'selectors' `);
305
+ }
312
306
  }
313
- try {
314
- return parseCssObject(cssObj);
315
- } catch (e) {
316
- throw new errors.InvalidSelectorError(`Unsupported CSS selector '${cssSelector}'. Reason: '${e}'`);
307
+
308
+ /**
309
+ * Convert a CSS selector to a UiAutomator2 selector
310
+ *
311
+ * @returns {string} The CSS selector converted to a UiSelector
312
+ */
313
+ toUiAutomatorSelector () {
314
+ let cssObj;
315
+ try {
316
+ cssObj = parser.parse(this.selector);
317
+ } catch (e) {
318
+ throw new errors.InvalidSelectorError(`Invalid CSS selector '${this.selector}'. Reason: '${e}'`);
319
+ }
320
+ try {
321
+ return this.parseCssObject(cssObj);
322
+ } catch (e) {
323
+ throw new errors.InvalidSelectorError(`Unsupported CSS selector '${this.selector}'. Reason: '${e}'`);
324
+ }
317
325
  }
318
- };
326
+ }
319
327
 
320
328
  export default CssConverter;
package/lib/driver.js CHANGED
@@ -67,7 +67,6 @@ const NO_PROXY = [
67
67
  ['GET', new RegExp('^/session/[^/]+/element/[^/]+/selected')],
68
68
  ['GET', new RegExp('^/session/[^/]+/ime/[^/]+')],
69
69
  ['GET', new RegExp('^/session/[^/]+/location')],
70
- ['GET', new RegExp('^/session/[^/]+/log/types')],
71
70
  ['GET', new RegExp('^/session/[^/]+/network_connection')],
72
71
  ['GET', new RegExp('^/session/[^/]+/screenshot')],
73
72
  ['GET', new RegExp('^/session/[^/]+/timeouts')],
@@ -94,7 +93,6 @@ const NO_PROXY = [
94
93
  ['POST', new RegExp('^/session/[^/]+/ime/[^/]+')],
95
94
  ['POST', new RegExp('^/session/[^/]+/keys')],
96
95
  ['POST', new RegExp('^/session/[^/]+/location')],
97
- ['POST', new RegExp('^/session/[^/]+/log')],
98
96
  ['POST', new RegExp('^/session/[^/]+/network_connection')],
99
97
  ['POST', new RegExp('^/session/[^/]+/timeouts')],
100
98
  ['POST', new RegExp('^/session/[^/]+/touch/multi/perform')],
@@ -102,12 +100,18 @@ const NO_PROXY = [
102
100
  ['POST', new RegExp('^/session/[^/]+/url')],
103
101
 
104
102
  // MJSONWP commands
103
+ ['GET', new RegExp('^/session/[^/]+/log/types')],
105
104
  ['POST', new RegExp('^/session/[^/]+/execute')],
106
105
  ['POST', new RegExp('^/session/[^/]+/execute_async')],
106
+ ['POST', new RegExp('^/session/[^/]+/log')],
107
107
  // W3C commands
108
+ // For Selenium v4 (W3C does not have this route)
109
+ ['GET', new RegExp('^/session/[^/]+/se/log/types')],
108
110
  ['GET', new RegExp('^/session/[^/]+/window/rect')],
109
111
  ['POST', new RegExp('^/session/[^/]+/execute/async')],
110
112
  ['POST', new RegExp('^/session/[^/]+/execute/sync')],
113
+ // For Selenium v4 (W3C does not have this route)
114
+ ['POST', new RegExp('^/session/[^/]+/se/log')],
111
115
  ];
112
116
 
113
117
  // This is a set of methods and paths that we never want to proxy to Chromedriver.
@@ -115,11 +119,9 @@ const CHROME_NO_PROXY = [
115
119
  ['GET', new RegExp('^/session/[^/]+/appium')],
116
120
  ['GET', new RegExp('^/session/[^/]+/context')],
117
121
  ['GET', new RegExp('^/session/[^/]+/element/[^/]+/rect')],
118
- ['GET', new RegExp('^/session/[^/]+/log/types$')],
119
122
  ['GET', new RegExp('^/session/[^/]+/orientation')],
120
123
  ['POST', new RegExp('^/session/[^/]+/appium')],
121
124
  ['POST', new RegExp('^/session/[^/]+/context')],
122
- ['POST', new RegExp('^/session/[^/]+/log$')],
123
125
  ['POST', new RegExp('^/session/[^/]+/orientation')],
124
126
  ['POST', new RegExp('^/session/[^/]+/touch/multi/perform')],
125
127
  ['POST', new RegExp('^/session/[^/]+/touch/perform')],
@@ -127,6 +129,15 @@ const CHROME_NO_PROXY = [
127
129
  // this is needed to make the mobile: commands working in web context
128
130
  ['POST', new RegExp('^/session/[^/]+/execute$')],
129
131
  ['POST', new RegExp('^/session/[^/]+/execute/sync')],
132
+
133
+ // MJSONWP commands
134
+ ['GET', new RegExp('^/session/[^/]+/log/types$')],
135
+ ['POST', new RegExp('^/session/[^/]+/log$')],
136
+ // W3C commands
137
+ // For Selenium v4 (W3C does not have this route)
138
+ ['GET', new RegExp('^/session/[^/]+/se/log/types$')],
139
+ // For Selenium v4 (W3C does not have this route)
140
+ ['POST', new RegExp('^/session/[^/]+/se/log$')],
130
141
  ];
131
142
 
132
143
  const MEMOIZED_FUNCTIONS = [
@@ -487,6 +498,7 @@ class AndroidUiautomator2Driver extends BaseDriver {
487
498
  // uiautomator2 with the appropriate options
488
499
  this.uiautomator2 = new UiAutomator2Server(uiautomator2Opts);
489
500
  this.proxyReqRes = this.uiautomator2.proxyReqRes.bind(this.uiautomator2);
501
+ this.proxyCommand = this.uiautomator2.proxyCommand.bind(this.uiautomator2);
490
502
 
491
503
  if (this.opts.skipServerInstallation) {
492
504
  logger.info(`'skipServerInstallation' is set. Skipping UIAutomator2 server installation.`);
@@ -527,16 +539,20 @@ class AndroidUiautomator2Driver extends BaseDriver {
527
539
  }
528
540
 
529
541
  if (this.opts.app) {
530
- if (!this.opts.noSign
531
- && !await this.adb.checkApkCert(this.opts.app, this.opts.appPackage, {
542
+ if (this.opts.noReset && !(await this.adb.isAppInstalled(this.opts.appPackage))
543
+ || !this.opts.noReset) {
544
+ if (!this.opts.noSign && !await this.adb.checkApkCert(this.opts.app, this.opts.appPackage, {
532
545
  requireDefaultCert: false,
533
546
  })) {
534
- await helpers.signApp(this.adb, this.opts.app);
535
- }
536
- if (!this.opts.skipUninstall) {
537
- await this.adb.uninstallApk(this.opts.appPackage);
547
+ await helpers.signApp(this.adb, this.opts.app);
548
+ }
549
+ if (!this.opts.skipUninstall) {
550
+ await this.adb.uninstallApk(this.opts.appPackage);
551
+ }
552
+ await helpers.installApk(this.adb, this.opts);
553
+ } else {
554
+ logger.debug('noReset has been requested and the app is already installed. Doing nothing');
538
555
  }
539
- await helpers.installApk(this.adb, this.opts);
540
556
  } else {
541
557
  if (this.opts.fullReset) {
542
558
  logger.errorAndThrow('Full reset requires an app capability, use fastReset if app is not provided');
@@ -55,6 +55,7 @@ class UiAutomator2Server {
55
55
  }
56
56
  this.jwproxy = new UIA2Proxy(proxyOpts);
57
57
  this.proxyReqRes = this.jwproxy.proxyReqRes.bind(this.jwproxy);
58
+ this.proxyCommand = this.jwproxy.command.bind(this.jwproxy);
58
59
  this.jwproxy.didInstrumentationExit = false;
59
60
  }
60
61
 
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "automated testing",
8
8
  "android"
9
9
  ],
10
- "version": "1.70.1",
10
+ "version": "1.74.0",
11
11
  "author": "appium",
12
12
  "license": "Apache-2.0",
13
13
  "repository": {
@@ -41,19 +41,19 @@
41
41
  ],
42
42
  "dependencies": {
43
43
  "@babel/runtime": "^7.0.0",
44
- "appium-adb": "^8.10.0",
45
- "appium-android-driver": "^4.50.0",
44
+ "appium-adb": "^8.18.0",
45
+ "appium-android-driver": "^4.54.0",
46
46
  "appium-base-driver": "^7.0.0",
47
47
  "appium-chromedriver": "^4.23.1",
48
- "appium-support": "^2.49.0",
49
- "appium-uiautomator2-server": "^4.26.0",
48
+ "appium-support": "^2.54.4",
49
+ "appium-uiautomator2-server": "^4.28.0",
50
50
  "asyncbox": "^2.3.1",
51
51
  "axios": "^0.x",
52
52
  "bluebird": "^3.5.1",
53
53
  "css-selector-parser": "^1.4.1",
54
54
  "lodash": "^4.17.4",
55
- "portscanner": "2.2.0",
56
- "source-map-support": "^0.5.5",
55
+ "portscanner": "^2.2.0",
56
+ "source-map-support": "^0.x",
57
57
  "teen_process": "^1.3.1"
58
58
  },
59
59
  "scripts": {
@@ -81,6 +81,7 @@
81
81
  "test"
82
82
  ],
83
83
  "devDependencies": {
84
+ "@xmldom/xmldom": "^0.x",
84
85
  "android-apidemos": "^3.0.0",
85
86
  "appium-gulp-plugins": "^5.4.0",
86
87
  "appium-test-support": "^1.0.0",
@@ -95,10 +96,9 @@
95
96
  "pngjs": "^6.0.0",
96
97
  "pre-commit": "^1.2.2",
97
98
  "rimraf": "^3.0.0",
98
- "sinon": "^11.0.0",
99
+ "sinon": "^12.0.0",
99
100
  "unzipper": "^0.10.0",
100
101
  "wd": "^1.10.3",
101
- "xmldom": "^0.x",
102
102
  "xpath": "^0.x"
103
103
  }
104
104
  }