Files

119 lines
3.8 KiB
TypeScript

import type {Element, ExternalDriver, NextPluginCallback} from '@appium/types';
import {errors} from 'appium/driver.js';
import {BasePlugin} from 'appium/plugin.js';
import {transformSourceXml} from './source.js';
import type {TransformMetadata} from './types.js';
import {transformQuery} from './xpath.js';
export class UniversalXMLPlugin extends BasePlugin {
async getPageSource(
next: NextPluginCallback | null,
driver: ExternalDriver,
sessId?: any,
addIndexPath: boolean = false,
): Promise<string> {
void sessId;
const source = (next ? await next() : await driver.getPageSource()) as string;
const metadata: TransformMetadata = {};
const platformName = getPlatformName(driver);
if (platformName.toLowerCase() === 'android') {
metadata.appPackage = (driver.opts as Record<string, unknown>)?.appPackage as string;
}
const {xml, unknowns} = await transformSourceXml(source, platformName.toLowerCase(), {
metadata,
addIndexPath,
});
if (unknowns.nodes.length) {
this.log.warn(
`The XML mapper found ${unknowns.nodes.length} node(s) / ` +
`tag name(s) that it didn't know about. These should be ` +
`reported to improve the quality of the plugin: ` +
unknowns.nodes.join(', '),
);
}
if (unknowns.attrs.length) {
this.log.warn(
`The XML mapper found ${unknowns.attrs.length} attributes ` +
`that it didn't know about. These should be reported to ` +
`improve the quality of the plugin: ` +
unknowns.attrs.join(', '),
);
}
return xml;
}
async findElement(
next: NextPluginCallback,
driver: ExternalDriver,
strategy: string,
selector: string,
): Promise<Element> {
return (await this._find(false, next, driver, strategy, selector)) as Element;
}
async findElements(
next: NextPluginCallback,
driver: ExternalDriver,
strategy: string,
selector: string,
): Promise<Element[]> {
return (await this._find(true, next, driver, strategy, selector)) as Element[];
}
private async _find(
multiple: false,
next: NextPluginCallback,
driver: ExternalDriver,
strategy: string,
selector: string,
): Promise<Element>;
private async _find(
multiple: true,
next: NextPluginCallback,
driver: ExternalDriver,
strategy: string,
selector: string,
): Promise<Element[]>;
private async _find(
multiple: boolean,
next: NextPluginCallback,
driver: ExternalDriver,
strategy: string,
selector: string,
): Promise<Element | Element[]> {
if (
strategy.toLowerCase() !== 'xpath' ||
!driver.getCurrentContext ||
(await driver.getCurrentContext()) !== 'NATIVE_APP'
) {
return (await next()) as Element | Element[];
}
const xml = await this.getPageSource(null, driver, null, true);
const newSelector = transformQuery(selector, xml, multiple);
// if the selector was not able to be transformed, that means no elements were found that
// matched, so do the appropriate thing based on element vs elements
if (newSelector === null) {
this.log.warn(
`Selector was not able to be translated to underlying XML. Either the requested ` +
`element does not exist or there was an error in translation`,
);
if (multiple) {
return [];
}
throw new errors.NoSuchElementError();
}
this.log.info(`Selector was translated to: ${newSelector}`);
// otherwise just run the transformed query!
const finder = multiple ? 'findElements' : 'findElement';
return (await driver[finder](strategy, newSelector)) as Element | Element[];
}
}
function getPlatformName(driver: ExternalDriver): string {
return ((driver.caps as Record<string, unknown>)?.platformName as string) || '';
}