mirror of
https://github.com/appium/appium.git
synced 2026-02-21 02:39:30 -06:00
feat: Add BiDi commands to the listCommands API output (#20925)
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
promoteAppiumOptionsForObject,
|
||||
generateDriverLogPrefix,
|
||||
METHOD_MAP as BASE_METHOD_MAP,
|
||||
BIDI_COMMANDS as BASE_BIDI_COMMANDS,
|
||||
} from '@appium/base-driver';
|
||||
import AsyncLock from 'async-lock';
|
||||
import {
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
makeNonW3cCapsError,
|
||||
validateFeatures,
|
||||
toRestCommandsMap,
|
||||
toBiDiCommandsMap,
|
||||
} from './utils';
|
||||
import {util} from '@appium/support';
|
||||
import {getDefaultsForExtension} from './schema';
|
||||
@@ -574,22 +576,27 @@ class AppiumDriver extends DriverCore {
|
||||
*/
|
||||
listCommands(sessionId) {
|
||||
/** @type {import('@appium/types').MethodMap<any>} */
|
||||
let driverMethodMap = {};
|
||||
let driverRestMethodMap = {};
|
||||
/** @type {import('@appium/types').BidiModuleMap} */
|
||||
let driverBiDiCommands = {};
|
||||
/** @type {Record<string, import('@appium/types').MethodMap<any>>} */
|
||||
let pluginMethodMaps = {};
|
||||
let pluginRestMethodMaps = {};
|
||||
/** @type {Record<string, import('@appium/types').BidiModuleMap>} */
|
||||
let pluginBiDiCommands = {};
|
||||
if (sessionId) {
|
||||
// @ts-ignore It's ok if the newMethodMap property is not there
|
||||
driverMethodMap = this.driverForSession(sessionId)?.constructor?.newMethodMap ?? {};
|
||||
// @ts-ignore It's ok if the newMethodMap property is not there
|
||||
pluginMethodMaps = _.fromPairs(
|
||||
this.pluginsForSession(sessionId)
|
||||
.map((p) => p.constructor)
|
||||
// @ts-ignore It's ok if the newMethodMap property is not there
|
||||
.map((c) => [c.name, c.newMethodMap ?? {}])
|
||||
const driverClass = /** @type {import('@appium/types').DriverClass | undefined} */ (
|
||||
this.driverForSession(sessionId)?.constructor
|
||||
);
|
||||
driverRestMethodMap = driverClass?.newMethodMap ?? {};
|
||||
driverBiDiCommands = driverClass?.newBidiCommands ?? {};
|
||||
const pluginClasses = this.pluginsForSession(sessionId)
|
||||
.map((p) => /** @type {import('@appium/types').PluginClass} */ (p.constructor));
|
||||
pluginRestMethodMaps = _.fromPairs(pluginClasses.map((c) => [c.name, c.newMethodMap ?? {}]));
|
||||
pluginBiDiCommands = _.fromPairs(pluginClasses.map((c) => [c.name, c.newBidiCommands ?? {}]));
|
||||
}
|
||||
return {
|
||||
rest: toRestCommandsMap(BASE_METHOD_MAP, driverMethodMap, pluginMethodMaps),
|
||||
rest: toRestCommandsMap(BASE_METHOD_MAP, driverRestMethodMap, pluginRestMethodMaps),
|
||||
bidi: toBiDiCommandsMap(BASE_BIDI_COMMANDS, driverBiDiCommands, pluginBiDiCommands),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -578,6 +578,90 @@ export function toRestCommandsMap(baseMethodMap, driverMethodMap, pluginMethodMa
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('@appium/types').BidiModuleMap} baseModuleMap
|
||||
* @param {import('@appium/types').BidiModuleMap} driverModuleMap
|
||||
* @param {Record<string, import('@appium/types').BidiModuleMap>} [pluginModuleMaps]
|
||||
* @returns {import('@appium/types').BiDiCommandsMap}
|
||||
*/
|
||||
export function toBiDiCommandsMap(baseModuleMap, driverModuleMap, pluginModuleMaps) {
|
||||
/**
|
||||
* @param {import("@appium/types").BidiMethodParams | undefined} params
|
||||
* @returns {import("@appium/types").BiDiCommandItemParam[] | undefined}
|
||||
*/
|
||||
const toBiDiCommandParams = (params) => {
|
||||
if (!params) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {any} x
|
||||
* @param {boolean} isRequired
|
||||
* @returns {import("@appium/types").BiDiCommandItemParam | undefined}
|
||||
*/
|
||||
const toBiDiCommandItemParam = (x, isRequired) => {
|
||||
const isNameAnArray = _.isArray(x);
|
||||
const name = isNameAnArray ? x[0] : x;
|
||||
if (!_.isString(name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If parameter names are arrays then this means
|
||||
// either of them is required.
|
||||
// Not sure we could reflect that in here.
|
||||
const required = isRequired && !isNameAnArray;
|
||||
return {
|
||||
name,
|
||||
required,
|
||||
};
|
||||
};
|
||||
|
||||
/** @type {import("@appium/types").BiDiCommandItemParam[]} */
|
||||
const requiredParams = (params.required ?? [])
|
||||
.map((name) => toBiDiCommandItemParam(name, true))
|
||||
.filter((x) => !_.isUndefined(x));
|
||||
/** @type {import("@appium/types").BiDiCommandItemParam[]} */
|
||||
const optionalParams = (params.optional ?? [])
|
||||
.map((name) => toBiDiCommandItemParam(name, false))
|
||||
.filter((x) => !_.isUndefined(x));
|
||||
return requiredParams.length || optionalParams.length
|
||||
? [...requiredParams, ...optionalParams]
|
||||
: undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('@appium/types').BidiModuleMap} mm
|
||||
* @returns {Record<string, import('@appium/types').BiDiCommandNamesToInfosMap>}
|
||||
*/
|
||||
const moduleMapToBiDiCommandsInfo = (mm) => {
|
||||
/** @type {Record<string, import('@appium/types').BiDiCommandNamesToInfosMap>} */
|
||||
const res = {};
|
||||
for (const [domain, commands] of _.toPairs(mm)) {
|
||||
const commandsMap = {};
|
||||
for (const [name, spec] of _.toPairs(commands)) {
|
||||
commandsMap[name] = {
|
||||
command: spec.command,
|
||||
deprecated: spec.deprecated,
|
||||
info: spec.info,
|
||||
params: toBiDiCommandParams(spec.params),
|
||||
};
|
||||
}
|
||||
// @ts-ignore this is OK
|
||||
res[domain] = commandsMap;
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
return {
|
||||
base: moduleMapToBiDiCommandsInfo(baseModuleMap),
|
||||
driver: moduleMapToBiDiCommandsInfo(driverModuleMap),
|
||||
plugins: pluginModuleMaps ? _.mapValues(pluginModuleMaps, moduleMapToBiDiCommandsInfo) : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {import('@appium/types').StringRecord} StringRecord
|
||||
* @typedef {import('@appium/types').BaseDriverCapConstraints} BaseDriverCapConstraints
|
||||
|
||||
@@ -231,11 +231,30 @@ describe('FakeDriver via HTTP', function () {
|
||||
);
|
||||
|
||||
const commands = await driver.listCommands();
|
||||
|
||||
JSON.stringify(commands.rest.base['/session/:sessionId/frame'])
|
||||
.should.eql(JSON.stringify({POST: {command: 'setFrame', params: [
|
||||
{name: 'id', required: true}
|
||||
]}}));
|
||||
_.size(commands.rest.driver).should.be.greaterThan(1);
|
||||
|
||||
JSON.stringify(commands.bidi.base.session.subscribe).should.eql(
|
||||
JSON.stringify({
|
||||
command: 'bidiSubscribe',
|
||||
'params': [
|
||||
{
|
||||
name: 'events',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
name: 'contexts',
|
||||
required: false
|
||||
}
|
||||
]
|
||||
})
|
||||
);
|
||||
_.size(commands.bidi.base).should.be.greaterThan(1);
|
||||
_.size(commands.bidi.driver).should.be.greaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -49,6 +49,9 @@ export {
|
||||
// Web socket helpers
|
||||
export {DEFAULT_WS_PATHNAME_PREFIX} from './express/websocket';
|
||||
|
||||
// BiDi exports
|
||||
export {BIDI_COMMANDS} from './protocol/bidi-commands';
|
||||
|
||||
export {generateDriverLogPrefix} from './basedriver/helpers';
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@ const SUBSCRIPTION_REQUEST_PARAMS = /** @type {const} */ ({
|
||||
optional: ['contexts'],
|
||||
});
|
||||
|
||||
const BIDI_COMMANDS = /** @type {const} */ ({
|
||||
export const BIDI_COMMANDS = /** @type {const} */ ({
|
||||
session: {
|
||||
subscribe: {
|
||||
command: 'bidiSubscribe',
|
||||
@@ -31,5 +31,3 @@ const BIDI_COMMANDS = /** @type {const} */ ({
|
||||
|
||||
// TODO add definitions for all bidi commands.
|
||||
// spec link: https://w3c.github.io/webdriver-bidi/
|
||||
|
||||
export {BIDI_COMMANDS};
|
||||
|
||||
@@ -243,10 +243,62 @@ export interface RestCommandsMap {
|
||||
plugins?: Record<string, Record<string, RestMethodsToCommandsMap>>;
|
||||
}
|
||||
|
||||
export interface BiDiCommandItemParam {
|
||||
/**
|
||||
* Command paremeter name
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* True if the paramter is required for the given command
|
||||
*/
|
||||
required: boolean;
|
||||
}
|
||||
|
||||
export interface BiDiCommandItem {
|
||||
/**
|
||||
* Command name
|
||||
*/
|
||||
command?: string;
|
||||
/**
|
||||
* Whether the command is marked for deprecation
|
||||
*/
|
||||
deprecated?: boolean;
|
||||
/**
|
||||
* Optinal infostring about the command's purpose or a comment
|
||||
*/
|
||||
info?: string;
|
||||
/**
|
||||
* List of command parameters
|
||||
*/
|
||||
params?: BiDiCommandItemParam[];
|
||||
}
|
||||
|
||||
export interface BiDiCommandNamesToInfosMap {
|
||||
[name: string]: Record<string, BiDiCommandItem>;
|
||||
}
|
||||
|
||||
export interface BiDiCommandsMap {
|
||||
/**
|
||||
* Domains to BiDi commands mapping in the base driver
|
||||
*/
|
||||
base: Record<string, BiDiCommandNamesToInfosMap>;
|
||||
/**
|
||||
* Domains to BiDi commands mapping in the session-specific driver
|
||||
*/
|
||||
driver: Record<string, BiDiCommandNamesToInfosMap>;
|
||||
/**
|
||||
* Plugin name to domains to BiDi commands mapping
|
||||
*/
|
||||
plugins?: Record<string, Record<string, BiDiCommandNamesToInfosMap>>;
|
||||
}
|
||||
|
||||
export interface ListCommandsResponse {
|
||||
/**
|
||||
* REST APIs mapping
|
||||
*/
|
||||
rest?: RestCommandsMap;
|
||||
bidi?: any;
|
||||
/**
|
||||
* BiDi APIs mapping
|
||||
*/
|
||||
bidi?: BiDiCommandsMap;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user