@appium/universal-xml-plugin 2.0.3 → 2.1.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/build/lib/attr-map.d.ts +3 -57
- package/build/lib/attr-map.d.ts.map +1 -1
- package/build/lib/attr-map.js +2 -5
- package/build/lib/attr-map.js.map +1 -1
- package/build/lib/index.d.ts +4 -3
- package/build/lib/index.d.ts.map +1 -1
- package/build/lib/index.js +20 -5
- package/build/lib/index.js.map +1 -1
- package/build/lib/node-map.d.ts +3 -199
- package/build/lib/node-map.d.ts.map +1 -1
- package/build/lib/node-map.js +2 -9
- package/build/lib/node-map.js.map +1 -1
- package/build/lib/plugin.d.ts +7 -6
- package/build/lib/plugin.d.ts.map +1 -1
- package/build/lib/plugin.js +16 -18
- package/build/lib/plugin.js.map +1 -1
- package/build/lib/source.d.ts +41 -47
- package/build/lib/source.d.ts.map +1 -1
- package/build/lib/source.js +104 -47
- package/build/lib/source.js.map +1 -1
- package/build/lib/transformers.d.ts +3 -7
- package/build/lib/transformers.d.ts.map +1 -1
- package/build/lib/transformers.js +9 -10
- package/build/lib/transformers.js.map +1 -1
- package/build/lib/types.d.ts +29 -0
- package/build/lib/types.d.ts.map +1 -0
- package/build/lib/types.js +3 -0
- package/build/lib/types.js.map +1 -0
- package/build/lib/xpath.d.ts +16 -7
- package/build/lib/xpath.d.ts.map +1 -1
- package/build/lib/xpath.js +19 -16
- package/build/lib/xpath.js.map +1 -1
- package/lib/{attr-map.js → attr-map.ts} +5 -5
- package/lib/{index.js → index.ts} +22 -4
- package/lib/{node-map.js → node-map.ts} +5 -1
- package/lib/plugin.ts +119 -0
- package/lib/source.ts +265 -0
- package/lib/{transformers.js → transformers.ts} +9 -12
- package/lib/types.ts +33 -0
- package/lib/{xpath.js → xpath.ts} +24 -20
- package/package.json +10 -10
- package/tsconfig.json +3 -2
- package/build/lib/logger.d.ts +0 -3
- package/build/lib/logger.d.ts.map +0 -1
- package/build/lib/logger.js +0 -6
- package/build/lib/logger.js.map +0 -1
- package/index.js +0 -7
- package/lib/logger.js +0 -3
- package/lib/plugin.js +0 -81
- package/lib/source.js +0 -226
package/build/lib/logger.js
DELETED
package/build/lib/logger.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../lib/logger.js"],"names":[],"mappings":";;AAAA,4CAAsC;AACtC,MAAM,GAAG,GAAG,gBAAM,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;AACnD,kBAAe,GAAG,CAAC"}
|
package/index.js
DELETED
package/lib/logger.js
DELETED
package/lib/plugin.js
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import {BasePlugin} from 'appium/plugin';
|
|
2
|
-
import {errors} from 'appium/driver';
|
|
3
|
-
import {transformSourceXml} from './source';
|
|
4
|
-
import {transformQuery} from './xpath';
|
|
5
|
-
import log from './logger';
|
|
6
|
-
|
|
7
|
-
export default class UniversalXMLPlugin extends BasePlugin {
|
|
8
|
-
async getPageSource(next, driver, sessId, addIndexPath = false) {
|
|
9
|
-
const source = next ? await next() : await driver.getPageSource();
|
|
10
|
-
const metadata = {};
|
|
11
|
-
const {platformName} = driver.caps;
|
|
12
|
-
if (platformName.toLowerCase() === 'android') {
|
|
13
|
-
metadata.appPackage = driver.opts.appPackage;
|
|
14
|
-
}
|
|
15
|
-
const {xml, unknowns} = await transformSourceXml(source, platformName.toLowerCase(), {
|
|
16
|
-
metadata,
|
|
17
|
-
addIndexPath,
|
|
18
|
-
});
|
|
19
|
-
if (unknowns.nodes.length) {
|
|
20
|
-
log.warn(
|
|
21
|
-
`The XML mapper found ${unknowns.nodes.length} node(s) / ` +
|
|
22
|
-
`tag name(s) that it didn't know about. These should be ` +
|
|
23
|
-
`reported to improve the quality of the plugin: ` +
|
|
24
|
-
unknowns.nodes.join(', '),
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
if (unknowns.attrs.length) {
|
|
28
|
-
log.warn(
|
|
29
|
-
`The XML mapper found ${unknowns.attrs.length} attributes ` +
|
|
30
|
-
`that it didn't know about. These should be reported to ` +
|
|
31
|
-
`improve the quality of the plugin: ` +
|
|
32
|
-
unknowns.attrs.join(', '),
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
return xml;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async findElement(...args) {
|
|
39
|
-
return await this._find(false, ...args);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async findElements(...args) {
|
|
43
|
-
return await this._find(true, ...args);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async _find(multiple, next, driver, strategy, selector) {
|
|
47
|
-
const {platformName} = driver.caps;
|
|
48
|
-
if (strategy.toLowerCase() !== 'xpath' || (await driver.getCurrentContext()) !== 'NATIVE_APP') {
|
|
49
|
-
return await next();
|
|
50
|
-
}
|
|
51
|
-
const xml = await this.getPageSource(null, driver, null, true);
|
|
52
|
-
let newSelector = transformQuery(selector, xml, multiple);
|
|
53
|
-
|
|
54
|
-
// if the selector was not able to be transformed, that means no elements were found that
|
|
55
|
-
// matched, so do the appropriate thing based on element vs elements
|
|
56
|
-
if (newSelector === null) {
|
|
57
|
-
log.warn(
|
|
58
|
-
`Selector was not able to be translated to underlying XML. Either the requested ` +
|
|
59
|
-
`element does not exist or there was an error in translation`,
|
|
60
|
-
);
|
|
61
|
-
if (multiple) {
|
|
62
|
-
return [];
|
|
63
|
-
}
|
|
64
|
-
throw new errors.NoSuchElementError();
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (platformName.toLowerCase() === 'ios') {
|
|
68
|
-
// with the XCUITest driver, the <AppiumAUT> wrapper element is present in the source but is
|
|
69
|
-
// not present in the source considered by WDA, so our index path based xpath queries will
|
|
70
|
-
// not work with WDA as-is. We need to remove the first path segment.
|
|
71
|
-
newSelector = newSelector.replace(/^\/\*\[1\]/, '');
|
|
72
|
-
}
|
|
73
|
-
log.info(`Selector was translated to: ${newSelector}`);
|
|
74
|
-
|
|
75
|
-
// otherwise just run the transformed query!
|
|
76
|
-
const finder = multiple ? 'findElements' : 'findElement';
|
|
77
|
-
return await driver[finder](strategy, newSelector);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export {UniversalXMLPlugin};
|
package/lib/source.js
DELETED
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
import _ from 'lodash';
|
|
2
|
-
import NODE_MAP from './node-map';
|
|
3
|
-
import {ATTR_MAP, REMOVE_ATTRS} from './attr-map';
|
|
4
|
-
import TRANSFORMS from './transformers';
|
|
5
|
-
|
|
6
|
-
export const ATTR_PREFIX = '@_';
|
|
7
|
-
export const IDX_PATH_PREFIX = `${ATTR_PREFIX}indexPath`;
|
|
8
|
-
export const IDX_PREFIX = `${ATTR_PREFIX}index`;
|
|
9
|
-
|
|
10
|
-
const isAttr = (/** @type {string} */ k) => k.startsWith(ATTR_PREFIX);
|
|
11
|
-
const isNode = (/** @type {string} */ k) => !isAttr(k);
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
*
|
|
15
|
-
* @param {string} xmlStr
|
|
16
|
-
* @param {string} platform
|
|
17
|
-
* @param {{metadata?: Object, addIndexPath?: boolean}} opts
|
|
18
|
-
* @returns {Promise<{xml: string, unknowns: NodesAndAttributes}>}
|
|
19
|
-
*/
|
|
20
|
-
export async function transformSourceXml(xmlStr, platform, {metadata = {}, addIndexPath = false} = {}) {
|
|
21
|
-
// first thing we want to do is modify the ios source root node, because it doesn't include the
|
|
22
|
-
// necessary index attribute, so we add it if it's not there
|
|
23
|
-
xmlStr = xmlStr.replace('<AppiumAUT>', '<AppiumAUT index="0">');
|
|
24
|
-
const xmlObj = (await singletonXmlParser()).parse(xmlStr);
|
|
25
|
-
const unknowns = transformNode(xmlObj, platform, {
|
|
26
|
-
metadata,
|
|
27
|
-
addIndexPath,
|
|
28
|
-
parentPath: '',
|
|
29
|
-
});
|
|
30
|
-
let transformedXml = (await singletonXmlBuilder()).build(xmlObj).trim();
|
|
31
|
-
transformedXml = `<?xml version="1.0" encoding="UTF-8"?>\n${transformedXml}`;
|
|
32
|
-
return {xml: transformedXml, unknowns};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
*
|
|
37
|
-
* @param {Object} nameMap
|
|
38
|
-
* @param {string} name
|
|
39
|
-
* @param {string} platform
|
|
40
|
-
* @returns {string | null}
|
|
41
|
-
*/
|
|
42
|
-
function getUniversalName(nameMap, name, platform) {
|
|
43
|
-
for (const translatedName of Object.keys(nameMap)) {
|
|
44
|
-
const sourceNodes = nameMap[translatedName]?.[platform];
|
|
45
|
-
if (_.isArray(sourceNodes) && sourceNodes.includes(name)) {
|
|
46
|
-
return translatedName;
|
|
47
|
-
}
|
|
48
|
-
if (sourceNodes === name) {
|
|
49
|
-
return translatedName;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
*
|
|
57
|
-
* @param {any} nodeName
|
|
58
|
-
* @param {string} platform
|
|
59
|
-
* @returns {string?}
|
|
60
|
-
*/
|
|
61
|
-
export function getUniversalNodeName(nodeName, platform) {
|
|
62
|
-
return getUniversalName(NODE_MAP, nodeName, platform);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
*
|
|
67
|
-
* @param {string} attrName
|
|
68
|
-
* @param {string} platform
|
|
69
|
-
* @returns {string?}
|
|
70
|
-
*/
|
|
71
|
-
export function getUniversalAttrName(attrName, platform) {
|
|
72
|
-
return getUniversalName(ATTR_MAP, attrName, platform);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
*
|
|
77
|
-
* @param {any} nodeObj
|
|
78
|
-
* @param {string} platform
|
|
79
|
-
* @param {{metadata?: Object, addIndexPath?: boolean, parentPath?: string}} opts
|
|
80
|
-
* @returns {NodesAndAttributes}
|
|
81
|
-
*/
|
|
82
|
-
export function transformNode(nodeObj, platform, {metadata, addIndexPath, parentPath}) {
|
|
83
|
-
const unknownNodes = [];
|
|
84
|
-
const unknownAttrs = [];
|
|
85
|
-
if (_.isPlainObject(nodeObj)) {
|
|
86
|
-
const keys = Object.keys(nodeObj);
|
|
87
|
-
const childNodeNames = keys.filter(isNode);
|
|
88
|
-
const attrs = keys.filter(isAttr);
|
|
89
|
-
let thisIndexPath = parentPath;
|
|
90
|
-
|
|
91
|
-
if (attrs.length && addIndexPath) {
|
|
92
|
-
if (!attrs.includes(IDX_PREFIX)) {
|
|
93
|
-
throw new Error(`Index path is required but node found with no 'index' attribute`);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
thisIndexPath = `${parentPath}/${nodeObj[IDX_PREFIX]}`;
|
|
97
|
-
nodeObj[IDX_PATH_PREFIX] = thisIndexPath;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
TRANSFORMS[platform]?.(nodeObj, metadata);
|
|
101
|
-
unknownAttrs.push(...transformAttrs(nodeObj, attrs, platform));
|
|
102
|
-
const unknowns = transformChildNodes(nodeObj, childNodeNames, platform, {
|
|
103
|
-
metadata,
|
|
104
|
-
addIndexPath,
|
|
105
|
-
parentPath: thisIndexPath,
|
|
106
|
-
});
|
|
107
|
-
unknownAttrs.push(...unknowns.attrs);
|
|
108
|
-
unknownNodes.push(...unknowns.nodes);
|
|
109
|
-
} else if (_.isArray(nodeObj)) {
|
|
110
|
-
for (const childObj of nodeObj) {
|
|
111
|
-
const {nodes, attrs} = transformNode(childObj, platform, {
|
|
112
|
-
metadata,
|
|
113
|
-
addIndexPath,
|
|
114
|
-
parentPath,
|
|
115
|
-
});
|
|
116
|
-
unknownNodes.push(...nodes);
|
|
117
|
-
unknownAttrs.push(...attrs);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return {
|
|
121
|
-
nodes: _.uniq(unknownNodes),
|
|
122
|
-
attrs: _.uniq(unknownAttrs),
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
*
|
|
128
|
-
* @param {any} nodeObj
|
|
129
|
-
* @param {string[]} childNodeNames
|
|
130
|
-
* @param {string} platform
|
|
131
|
-
* @param {{metadata?: Object, addIndexPath?: boolean, parentPath?: string}} opts
|
|
132
|
-
* @returns {NodesAndAttributes}
|
|
133
|
-
*/
|
|
134
|
-
export function transformChildNodes(
|
|
135
|
-
nodeObj,
|
|
136
|
-
childNodeNames,
|
|
137
|
-
platform,
|
|
138
|
-
{metadata, addIndexPath, parentPath}
|
|
139
|
-
) {
|
|
140
|
-
const unknownNodes = [];
|
|
141
|
-
const unknownAttrs = [];
|
|
142
|
-
for (const nodeName of childNodeNames) {
|
|
143
|
-
// before modifying the name of this child node, recurse down and modify the subtree
|
|
144
|
-
const {nodes, attrs} = transformNode(nodeObj[nodeName], platform, {
|
|
145
|
-
metadata,
|
|
146
|
-
addIndexPath,
|
|
147
|
-
parentPath,
|
|
148
|
-
});
|
|
149
|
-
unknownNodes.push(...nodes);
|
|
150
|
-
unknownAttrs.push(...attrs);
|
|
151
|
-
|
|
152
|
-
// now translate the node name and replace the subtree with this node
|
|
153
|
-
const universalName = getUniversalNodeName(nodeName, platform);
|
|
154
|
-
if (universalName === null) {
|
|
155
|
-
unknownNodes.push(nodeName);
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// since multiple child node names could map to the same new transformed node name, we can't
|
|
160
|
-
// simply assign nodeObj[universalName] = nodeObj[nodeName]; we need to be sensitive to the
|
|
161
|
-
// situation where the end result is an array of children having the same node name
|
|
162
|
-
if (nodeObj[universalName]) {
|
|
163
|
-
// if we already have a node with the universal name, that means we are mapping a second
|
|
164
|
-
// original node name to the same universal node name, so we just push all its children into
|
|
165
|
-
// the list
|
|
166
|
-
nodeObj[universalName].push(...nodeObj[nodeName]);
|
|
167
|
-
} else {
|
|
168
|
-
nodeObj[universalName] = nodeObj[nodeName];
|
|
169
|
-
}
|
|
170
|
-
delete nodeObj[nodeName];
|
|
171
|
-
}
|
|
172
|
-
return {nodes: unknownNodes, attrs: unknownAttrs};
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
*
|
|
177
|
-
* @param {any} nodeObj
|
|
178
|
-
* @param {string[]} attrs
|
|
179
|
-
* @param {string} platform
|
|
180
|
-
* @returns {string[]}
|
|
181
|
-
*/
|
|
182
|
-
export function transformAttrs(nodeObj, attrs, platform) {
|
|
183
|
-
const unknownAttrs = [];
|
|
184
|
-
for (const attr of attrs) {
|
|
185
|
-
const cleanAttr = attr.substring(2);
|
|
186
|
-
if (REMOVE_ATTRS.includes(cleanAttr)) {
|
|
187
|
-
delete nodeObj[attr];
|
|
188
|
-
continue;
|
|
189
|
-
}
|
|
190
|
-
const universalAttr = getUniversalAttrName(cleanAttr, platform);
|
|
191
|
-
if (universalAttr === null) {
|
|
192
|
-
unknownAttrs.push(cleanAttr);
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
195
|
-
const newAttr = `${ATTR_PREFIX}${universalAttr}`;
|
|
196
|
-
if (newAttr !== attr) {
|
|
197
|
-
nodeObj[newAttr] = nodeObj[attr];
|
|
198
|
-
delete nodeObj[attr];
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
return unknownAttrs;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const singletonXmlBuilder = _.memoize(async function makeXmlBuilder() {
|
|
205
|
-
const { XMLBuilder } = await import('fast-xml-parser');
|
|
206
|
-
return new XMLBuilder({
|
|
207
|
-
ignoreAttributes: false,
|
|
208
|
-
attributeNamePrefix: ATTR_PREFIX,
|
|
209
|
-
suppressBooleanAttributes: false,
|
|
210
|
-
format: true,
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
const singletonXmlParser = _.memoize(async function makeXmlParser() {
|
|
215
|
-
const { XMLParser } = await import('fast-xml-parser');
|
|
216
|
-
return new XMLParser({
|
|
217
|
-
ignoreAttributes: false,
|
|
218
|
-
ignoreDeclaration: true,
|
|
219
|
-
attributeNamePrefix: ATTR_PREFIX,
|
|
220
|
-
isArray: (name, jPath, isLeafNode, isAttribute) => !isAttribute,
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* @typedef {{nodes: string[], attrs: string[]}} NodesAndAttributes
|
|
226
|
-
*/
|