appium-uiautomator2-driver 6.7.3 → 6.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/build/lib/commands/find.js +2 -5
  3. package/build/lib/commands/find.js.map +1 -1
  4. package/build/lib/css-converter.d.ts +9 -42
  5. package/build/lib/css-converter.d.ts.map +1 -1
  6. package/build/lib/css-converter.js +73 -148
  7. package/build/lib/css-converter.js.map +1 -1
  8. package/build/lib/extensions.d.ts +2 -2
  9. package/build/lib/extensions.d.ts.map +1 -1
  10. package/build/lib/extensions.js +2 -4
  11. package/build/lib/extensions.js.map +1 -1
  12. package/build/lib/helpers.d.ts +3 -12
  13. package/build/lib/helpers.d.ts.map +1 -1
  14. package/build/lib/helpers.js +1 -11
  15. package/build/lib/helpers.js.map +1 -1
  16. package/build/lib/logger.d.ts +1 -2
  17. package/build/lib/logger.d.ts.map +1 -1
  18. package/build/lib/logger.js +2 -2
  19. package/build/lib/logger.js.map +1 -1
  20. package/build/lib/uiautomator2.d.ts +1 -1
  21. package/build/lib/uiautomator2.d.ts.map +1 -1
  22. package/build/lib/uiautomator2.js +0 -1
  23. package/build/lib/uiautomator2.js.map +1 -1
  24. package/build/test/unit/css-converter-specs.js +3 -6
  25. package/build/test/unit/css-converter-specs.js.map +1 -1
  26. package/build/test/unit/uiautomator2-specs.js +4 -4
  27. package/build/test/unit/uiautomator2-specs.js.map +1 -1
  28. package/build/tsconfig.tsbuildinfo +1 -1
  29. package/lib/commands/find.js +1 -1
  30. package/lib/css-converter.ts +283 -0
  31. package/lib/extensions.ts +3 -0
  32. package/lib/helpers.ts +31 -0
  33. package/lib/logger.ts +4 -0
  34. package/lib/uiautomator2.ts +0 -1
  35. package/npm-shrinkwrap.json +47 -59
  36. package/package.json +2 -2
  37. package/lib/css-converter.js +0 -329
  38. package/lib/extensions.js +0 -4
  39. package/lib/helpers.js +0 -37
  40. package/lib/logger.js +0 -6
@@ -1,329 +0,0 @@
1
- import { createParser } from 'css-selector-parser';
2
- import _ from 'lodash';
3
- import { errors } from 'appium/driver';
4
- import log from './logger';
5
-
6
- const parseCssSelector = createParser({
7
- syntax: {
8
- pseudoClasses: {
9
- unknown: 'accept',
10
- definitions: {
11
- Selector: ['has'],
12
- }
13
- },
14
- combinators: ['>', '+', '~'],
15
- attributes: {
16
- operators: ['^=', '$=', '*=', '~=', '=']
17
- },
18
- ids: true,
19
- classNames: true,
20
- tag: {
21
- wildcard: true
22
- },
23
- },
24
- substitutes: true
25
- });
26
-
27
- const RESOURCE_ID = 'resource-id';
28
- const ID_LOCATOR_PATTERN = /^[a-zA-Z_][a-zA-Z0-9._]*:id\/[\S]+$/;
29
-
30
- const BOOLEAN_ATTRS = [
31
- 'checkable', 'checked', 'clickable', 'enabled', 'focusable',
32
- 'focused', 'long-clickable', 'scrollable', 'selected',
33
- ];
34
-
35
- const NUMERIC_ATTRS = [
36
- 'index', 'instance',
37
- ];
38
-
39
- const STR_ATTRS = [
40
- 'description', RESOURCE_ID, 'text', 'class-name', 'package-name'
41
- ];
42
-
43
- const ALL_ATTRS = [
44
- ...BOOLEAN_ATTRS,
45
- ...NUMERIC_ATTRS,
46
- ...STR_ATTRS,
47
- ];
48
-
49
- /** @type {[string, string[]][]} */
50
- const ATTRIBUTE_ALIASES = [
51
- [RESOURCE_ID, ['id']],
52
- ['description', [
53
- 'content-description', 'content-desc',
54
- 'desc', 'accessibility-id',
55
- ]],
56
- ['index', ['nth-child']],
57
- ];
58
-
59
- /**
60
- * Convert hyphen separated word to snake case
61
- *
62
- * @param {string?} str
63
- * @returns {string} The hyphen separated word translated to snake case
64
- */
65
- function toSnakeCase (str) {
66
- if (!str) {
67
- return '';
68
- }
69
- const tokens = str.split('-').map((str) => str.charAt(0).toUpperCase() + str.slice(1).toLowerCase());
70
- const out = tokens.join('');
71
- return out.charAt(0).toLowerCase() + out.slice(1);
72
- }
73
-
74
- /**
75
- * Get the boolean from a CSS object. If empty, return true. If not true/false/empty, throw exception
76
- *
77
- * @param {import('css-selector-parser').AstAttribute|import('css-selector-parser').AstPseudoClass} css A
78
- * CSS object that has 'name' and 'value'
79
- * @returns {string} Either 'true' or 'false'. If value is empty, return 'true'
80
- */
81
- function requireBoolean (css) {
82
- // @ts-ignore Attributes should exist
83
- const val = _.toLower((css.value ?? css.argument)?.value) || 'true'; // an omitted boolean attribute means 'true' (e.g.: input[checked] means checked is true)
84
- if (['true', 'false'].includes(val)) {
85
- return val;
86
- }
87
- // @ts-ignore The attribute should exist
88
- throw new Error(`'${css.name}' must be true, false or empty. Found '${css.value}'`);
89
- }
90
-
91
- /**
92
- * Get the canonical form of a CSS attribute name
93
- *
94
- * Converts to lowercase and if an attribute name is an alias for something else, return
95
- * what it is an alias for
96
- *
97
- * @param {import('css-selector-parser').AstAttribute|import('css-selector-parser').AstPseudoClass} cssEntity CSS object
98
- * @returns {string} The canonical attribute name
99
- */
100
- function requireEntityName (cssEntity) {
101
- const attrName = cssEntity.name.toLowerCase();
102
-
103
- // Check if it's supported and if it is, return it
104
- if (ALL_ATTRS.includes(attrName)) {
105
- return attrName.toLowerCase();
106
- }
107
-
108
- // If attrName is an alias for something else, return that
109
- for (const [officialAttr, aliasAttrs] of ATTRIBUTE_ALIASES) {
110
- if (aliasAttrs.includes(attrName)) {
111
- return officialAttr;
112
- }
113
- }
114
- throw new Error(`'${attrName}' is not a valid attribute. ` +
115
- `Supported attributes are '${ALL_ATTRS.join(', ')}'`);
116
- }
117
-
118
- /**
119
- * Get a regex that matches a whole word. For the ~= CSS attribute selector.
120
- *
121
- * @param {string} word
122
- * @returns {string} A regex "word" matcher
123
- */
124
- function getWordMatcherRegex (word) {
125
- return `\\b(\\w*${_.escapeRegExp(word)}\\w*)\\b`;
126
- }
127
-
128
-
129
- class CssConverter {
130
-
131
- constructor (selector, pkg) {
132
- this.selector = selector;
133
- this.pkg = pkg;
134
- }
135
-
136
- /**
137
- * Add `<pkgName>:id/` prefix to beginning of string if it's not there already
138
- *
139
- * @param {string} locator The initial locator
140
- * @returns {string} String with `<pkgName>:id/` prepended (if it wasn't already)
141
- */
142
- formatIdLocator (locator) {
143
- return ID_LOCATOR_PATTERN.test(locator)
144
- ? locator
145
- : `${this.pkg || 'android'}:id/${locator}`;
146
- }
147
-
148
- /**
149
- * Convert a CSS attribute into a UiSelector method call
150
- *
151
- * @param {import('css-selector-parser').AstAttribute} cssAttr CSS attribute object
152
- * @returns {string} CSS attribute parsed as UiSelector
153
- */
154
- parseAttr (cssAttr) {
155
- // @ts-ignore Value should be present
156
- const attrValue = cssAttr.value?.value;
157
- if (!_.isString(attrValue) && !_.isEmpty(attrValue)) {
158
- throw new Error(`'${cssAttr.name}=${attrValue}' is an invalid attribute. ` +
159
- `Only 'string' and empty attribute types are supported. Found '${attrValue}'`);
160
- }
161
- const attrName = requireEntityName(cssAttr);
162
- const methodName = toSnakeCase(attrName);
163
-
164
- // Validate that it's a supported attribute
165
- if (!STR_ATTRS.includes(attrName) && !BOOLEAN_ATTRS.includes(attrName)) {
166
- throw new Error(`'${attrName}' is not supported. Supported attributes are ` +
167
- `'${[...STR_ATTRS, ...BOOLEAN_ATTRS].join(', ')}'`);
168
- }
169
-
170
- // Parse boolean, if it's a boolean attribute
171
- if (BOOLEAN_ATTRS.includes(attrName)) {
172
- return `.${methodName}(${requireBoolean(cssAttr)})`;
173
- }
174
-
175
- // Otherwise parse as string
176
- let value = attrValue || '';
177
- if (attrName === RESOURCE_ID) {
178
- value = this.formatIdLocator(value);
179
- }
180
- if (value === '') {
181
- return `.${methodName}Matches("")`;
182
- }
183
-
184
- switch (cssAttr.operator) {
185
- case '=':
186
- return `.${methodName}("${value}")`;
187
- case '*=':
188
- if (['description', 'text'].includes(attrName)) {
189
- return `.${methodName}Contains("${value}")`;
190
- }
191
- return `.${methodName}Matches("${_.escapeRegExp(value)}")`;
192
- case '^=':
193
- if (['description', 'text'].includes(attrName)) {
194
- return `.${methodName}StartsWith("${value}")`;
195
- }
196
- return `.${methodName}Matches("^${_.escapeRegExp(value)}")`;
197
- case '$=':
198
- return `.${methodName}Matches("${_.escapeRegExp(value)}$")`;
199
- case '~=':
200
- return `.${methodName}Matches("${getWordMatcherRegex(value)}")`;
201
- default:
202
- // Unreachable, but adding error in case a new CSS attribute is added.
203
- throw new Error(`Unsupported CSS attribute operator '${cssAttr.operator}'. ` +
204
- ` '=', '*=', '^=', '$=' and '~=' are supported.`);
205
- }
206
- }
207
-
208
- /**
209
- * Convert a CSS pseudo class to a UiSelector
210
- *
211
- * @param {import('css-selector-parser').AstPseudoClass} cssPseudo CSS Pseudo class
212
- * @returns {string|null|undefined} Pseudo selector parsed as UiSelector
213
- */
214
- parsePseudo (cssPseudo) {
215
- // @ts-ignore The attribute should exist
216
- const argValue = cssPseudo.argument?.value;
217
- if (!_.isString(argValue) && !_.isEmpty(argValue)) {
218
- throw new Error(`'${cssPseudo.name}=${argValue}'. ` +
219
- `Unsupported css pseudo class value: '${argValue}'. Only 'string' type or empty is supported.`);
220
- }
221
-
222
- const pseudoName = requireEntityName(cssPseudo);
223
-
224
- if (BOOLEAN_ATTRS.includes(pseudoName)) {
225
- return `.${toSnakeCase(pseudoName)}(${requireBoolean(cssPseudo)})`;
226
- }
227
-
228
- if (NUMERIC_ATTRS.includes(pseudoName)) {
229
- return `.${pseudoName}(${argValue})`;
230
- }
231
- }
232
-
233
- /**
234
- * Convert a CSS rule to a UiSelector
235
- * @param {import('css-selector-parser').AstRule} cssRule CSS rule definition
236
- */
237
- parseCssRule (cssRule) {
238
- if (cssRule.combinator && ![' ', '>'].includes(cssRule.combinator)) {
239
- throw new Error(`'${cssRule.combinator}' is not a supported combinator. ` +
240
- `Only child combinator (>) and descendant combinator are supported.`);
241
- }
242
-
243
- /** @type {string[]} */
244
- const uiAutomatorSelector = ['new UiSelector()'];
245
- /** @type {import('css-selector-parser').AstClassName[]} */
246
- // @ts-ignore This should work
247
- const astClassNames = cssRule.items.filter(({type}) => type === 'ClassName');
248
- const classNames = astClassNames.map(({name}) => name);
249
- /** @type {import('css-selector-parser').AstTagName|undefined} */
250
- // @ts-ignore This should work
251
- const astTag = cssRule.items.find(({type}) => type === 'TagName');
252
- const tagName = astTag?.name;
253
- if (tagName && tagName !== '*') {
254
- const androidClass = [tagName];
255
- if (classNames.length) {
256
- for (const cssClassNames of classNames) {
257
- androidClass.push(cssClassNames);
258
- }
259
- uiAutomatorSelector.push(`.className("${androidClass.join('.')}")`);
260
- } else {
261
- uiAutomatorSelector.push(`.classNameMatches("${tagName}")`);
262
- }
263
- } else if (classNames.length) {
264
- uiAutomatorSelector.push(`.classNameMatches("${classNames.join('\\.')}")`);
265
- }
266
- /** @type {import('css-selector-parser').AstId[]} */
267
- // @ts-ignore This should work
268
- const astIds = cssRule.items.filter(({type}) => type === 'Id');
269
- const ids = astIds.map(({name}) => name);
270
- if (ids.length) {
271
- uiAutomatorSelector.push(`.resourceId("${this.formatIdLocator(ids[0])}")`);
272
- }
273
- /** @type {import('css-selector-parser').AstAttribute[]} */
274
- // @ts-ignore This should work
275
- const attributes = cssRule.items.filter(({type}) => type === 'Attribute');
276
- for (const attr of attributes) {
277
- uiAutomatorSelector.push(this.parseAttr(attr));
278
- }
279
- /** @type {import('css-selector-parser').AstPseudoClass[]} */
280
- // @ts-ignore This should work
281
- const pseudoClasses = cssRule.items.filter(({type}) => type === 'PseudoClass');
282
- for (const pseudo of pseudoClasses) {
283
- const sel = this.parsePseudo(pseudo);
284
- if (sel) {
285
- uiAutomatorSelector.push(sel);
286
- }
287
- }
288
- if (cssRule.nestedRule) {
289
- uiAutomatorSelector.push(`.childSelector(${this.parseCssRule(cssRule.nestedRule)})`);
290
- }
291
- return uiAutomatorSelector.join('');
292
- }
293
-
294
- /**
295
- * Convert CSS object to UiAutomator2 selector
296
- * @param {import('css-selector-parser').AstSelector} css CSS object
297
- * @returns {string} The CSS object parsed as a UiSelector
298
- */
299
- parseCssObject (css) {
300
- if (!_.isEmpty(css.rules)) {
301
- return this.parseCssRule(css.rules[0]);
302
- }
303
-
304
- throw new Error('No rules could be parsed out of the current selector');
305
- }
306
-
307
- /**
308
- * Convert a CSS selector to a UiAutomator2 selector
309
- *
310
- * @returns {string} The CSS selector converted to a UiSelector
311
- */
312
- toUiAutomatorSelector () {
313
- let cssObj;
314
- try {
315
- cssObj = parseCssSelector(this.selector);
316
- } catch (e) {
317
- log.debug(e.stack);
318
- throw new errors.InvalidSelectorError(`Invalid CSS selector '${this.selector}'. Reason: '${e.message}'`);
319
- }
320
- try {
321
- return this.parseCssObject(cssObj);
322
- } catch (e) {
323
- log.debug(e.stack);
324
- throw new errors.InvalidSelectorError(`Unsupported CSS selector '${this.selector}'. Reason: '${e.message}'`);
325
- }
326
- }
327
- }
328
-
329
- export default CssConverter;
package/lib/extensions.js DELETED
@@ -1,4 +0,0 @@
1
- const APK_EXTENSION = '.apk';
2
- const APKS_EXTENSION = '.apks';
3
-
4
- export { APK_EXTENSION, APKS_EXTENSION };
package/lib/helpers.js DELETED
@@ -1,37 +0,0 @@
1
- import path from 'path';
2
- import { fs, system } from 'appium/support';
3
-
4
- /**
5
- * @param {string} filePath
6
- * @returns {Promise<boolean>}
7
- */
8
- export async function isWriteable(filePath) {
9
- try {
10
- await fs.access(filePath, fs.constants.W_OK);
11
- if (system.isWindows()) {
12
- // On operating systems, where access-control policies may
13
- // limit access to the file system, `fs.access` does not work
14
- // as expected. See https://groups.google.com/forum/#!topic/nodejs/qmZtIwDRSYo
15
- // for more details
16
- await fs.close(await fs.open(filePath, 'r+'));
17
- }
18
- return true;
19
- } catch {
20
- return false;
21
- }
22
- }
23
-
24
- /**
25
- *
26
- * @param {import('appium-adb').ADB} adb
27
- * @param {string} appPath
28
- * @returns {Promise<void>}
29
- */
30
- export async function signApp(adb, appPath) {
31
- if (!await isWriteable(appPath)) {
32
- throw new Error(`The application at '${appPath}' is not writeable. ` +
33
- `Please grant write permissions to this file or to its parent folder '${path.dirname(appPath)}' ` +
34
- `for the Appium process, so it could sign the application`);
35
- }
36
- await adb.sign(appPath);
37
- }
package/lib/logger.js DELETED
@@ -1,6 +0,0 @@
1
- import { logger } from 'appium/support';
2
-
3
-
4
- const log = logger.getLogger('UiAutomator2');
5
-
6
- export default log;