mirror of
https://github.com/appium/appium.git
synced 2026-10-27 03:25:54 +08:00
76 lines
2.6 KiB
TypeScript
76 lines
2.6 KiB
TypeScript
import type {Element} from '@xmldom/xmldom';
|
|
import {DOMParser, MIME_TYPE} from '@xmldom/xmldom';
|
|
import {select as xpathQuery} from 'xpath';
|
|
|
|
/**
|
|
* Runs an XPath query against an XML string.
|
|
* @param query XPath query.
|
|
* @param xmlStr XML source.
|
|
*/
|
|
export function runQuery(query: string, xmlStr: string): Element[] {
|
|
const dom = new DOMParser().parseFromString(xmlStr, MIME_TYPE.XML_TEXT);
|
|
// @ts-expect-error Missing Node properties are not needed.
|
|
// https://github.com/xmldom/xmldom/issues/724
|
|
const nodes = xpathQuery(query, dom);
|
|
// the xpath typings describe lib.dom nodes rather than xmldom ones, so this is the one place
|
|
// where the two type worlds have to be bridged
|
|
return nodes as unknown as Element[];
|
|
}
|
|
|
|
/**
|
|
* Transforms an XPath query to work with the original platform-specific XML
|
|
*
|
|
* @param query - The XPath query to transform
|
|
* @param xmlStr - The transformed XML string
|
|
* @param multiple - Whether to return multiple matches
|
|
* @returns The transformed query string or null if no matches found
|
|
*/
|
|
export function transformQuery(query: string, xmlStr: string, multiple: boolean): string | null {
|
|
const nodes = runQuery(query, xmlStr);
|
|
if (!Array.isArray(nodes)) {
|
|
return null;
|
|
}
|
|
|
|
const newQueries = nodes
|
|
// nodes with no indexPath cannot be located on the device, so skip them deliberately.
|
|
// the only such node is the ios AppiumAUT root (see transformSourceXml)
|
|
.filter((node) => node.hasAttribute('indexPath'))
|
|
.map((node) => {
|
|
const indexPath = getNodeAttrVal(node, 'indexPath');
|
|
// at this point indexPath will look like /0/0/1/1/0/1/0/2
|
|
const newQuery = indexPath
|
|
.substring(1) // remove leading / so we can split
|
|
.split('/') // split into indexes
|
|
.map((indexStr) => {
|
|
// map to xpath node indexes (1-based)
|
|
const xpathIndex = parseInt(indexStr, 10) + 1;
|
|
return `*[${xpathIndex}]`;
|
|
})
|
|
.join('/'); // reapply /
|
|
|
|
// now to make this a valid xpath from the root, prepend the / we removed earlier
|
|
return `/${newQuery}`;
|
|
});
|
|
|
|
if (newQueries.length === 0) {
|
|
return null;
|
|
}
|
|
return multiple ? newQueries.join(' | ') : newQueries[0];
|
|
}
|
|
|
|
/**
|
|
* Gets the value of a node attribute
|
|
*
|
|
* @param node - The XML node
|
|
* @param attr - The attribute name
|
|
* @returns The attribute value
|
|
* @throws {Error} If the attribute doesn't exist
|
|
*/
|
|
export function getNodeAttrVal(node: Element, attr: string): string {
|
|
const value = node.getAttribute(attr);
|
|
if (value === null) {
|
|
throw new Error(`Tried to retrieve a node attribute '${attr}' but the node didn't have it`);
|
|
}
|
|
return value;
|
|
}
|