fix(base-driver,types): For extension commands that use prefix "mobile", ensure logEvents() has the name of executed script (#21416)

* fix(appium): For extension commands that use prefix "mobile", ensure logEvents() has the name of executed script

Related to https://github.com/appium/java-client/issues/2219

* Fix linting

* Address comments

* Update method name

* Address comments

* Address comments

* Address comments

* Add check for accessing method

* Addressing comments
This commit is contained in:
Puja Jagani
2025-08-06 15:16:26 +05:30
committed by GitHub
parent 1943f5a2c4
commit c410201baa
5 changed files with 137 additions and 0 deletions
@@ -25,6 +25,7 @@ import {DELETE_SESSION_COMMAND, determineProtocol, errors} from '../protocol';
import {processCapabilities, validateCaps} from './capabilities';
import {DriverCore} from './core';
import * as helpers from './helpers';
import {resolveExecuteExtensionName} from '../helpers/extension-command-name';
const EVENT_SESSION_INIT = 'newSessionRequested';
const EVENT_SESSION_START = 'newSessionStarted';
@@ -161,6 +162,11 @@ export class BaseDriver<
// log timing information about this command
const endTime = Date.now();
if (this.clarifyCommandName) {
cmd = this.clarifyCommandName(cmd, args);
}
this._eventHistory.commands.push({cmd, startTime, endTime});
if (cmd === 'createSession') {
this.logEvent(EVENT_SESSION_START);
@@ -171,6 +177,17 @@ export class BaseDriver<
return res;
}
clarifyCommandName(cmd: string, args: string[]): string {
if (cmd === 'execute') {
const firstArg = args?.[0];
if (_.isString(firstArg) && firstArg.trim().length > 0) {
return resolveExecuteExtensionName.call(this, firstArg);
}
}
return cmd;
}
async startUnexpectedShutdown(
err: Error = new errors.NoSuchDriverError('The driver was unexpectedly shut down!'),
) {
@@ -0,0 +1,27 @@
import _ from 'lodash';
import type {Constraints, Driver, DriverClass} from '@appium/types';
import type {BaseDriver} from '../basedriver/driver';
/**
* Resolves the name of extension method corresponding to an `execute` command string
* based on the driver's `executeMethodMap`.
*
* @param commandName - The command name to resolve.
* @returns The resolved extension command name if a mapping exists. Otherwise, the original command name.
*/
export function resolveExecuteExtensionName<C extends Constraints>(
this: BaseDriver<C>,
commandName: string
): string {
const Driver = this.constructor as DriverClass<Driver<C>>;
const methodMap = Driver.executeMethodMap;
if (methodMap && _.isPlainObject(methodMap) && commandName in methodMap) {
const command = methodMap[commandName]?.command;
if (typeof command === 'string') {
return command;
}
}
return commandName;
}
@@ -0,0 +1,55 @@
import {server, routeConfiguringFunction} from '../../../lib';
import axios from 'axios';
// eslint-disable-next-line import/named
import {createSandbox} from 'sinon';
import {getTestPort, TEST_HOST} from '@appium/driver-test-support';
import {MockExecuteDriver} from '../protocol/mock-execute-driver';
let port, baseUrl;
describe('Execute Command Test', function () {
let sandbox;
let driver;
let httpServer;
beforeEach(async function () {
const chai = await import('chai');
const chaiAsPromised = await import('chai-as-promised');
chai.use(chaiAsPromised.default);
chai.should();
sandbox = createSandbox();
port = await getTestPort();
baseUrl = `http://${TEST_HOST}:${port}`;
driver = new MockExecuteDriver();
driver.sessionId = 'foo';
httpServer = await server({
routeConfiguringFunction: routeConfiguringFunction(driver),
port,
});
});
afterEach(async function () {
sandbox.restore();
await httpServer.close();
});
it('should rename extended command and log it in event history', async function () {
const script = 'mobile: activateApp';
const args = [{appId: 'io.appium.TestApp'}];
const res = await axios.post(`${baseUrl}/session/foo/execute/sync`, {
script,
args,
});
res.status.should.eql(200);
res.data.should.have.property('value');
res.data.value.should.deep.equal({executed: script, args});
const events = await driver.getLogEvents();
const command = events.commands[0];
command.should.have.property('cmd', 'mobileActivateApp');
});
});
@@ -0,0 +1,25 @@
import {BaseDriver} from '../../../lib';
import {PROTOCOLS} from '../../../lib/constants';
class MockExecuteDriver extends BaseDriver {
static executeMethodMap = {
'mobile: activateApp': {
command: 'mobileActivateApp',
}
};
constructor() {
super();
this.protocol = PROTOCOLS.W3C;
this.sessionId = null;
this.jwpProxyActive = false;
}
async execute(script, args) {
return {executed: script, args};
}
}
export {MockExecuteDriver};
+13
View File
@@ -692,6 +692,19 @@ export interface Driver<
*/
executeCommand(cmd: string, ...args: any[]): Promise<any>;
/**
* A helper method to modify the command name before it's logged.
*
* Useful for resolving generic commands like 'execute' to a more specific
* name based on arguments (e.g., identifying custom extensions).
*
* @param cmd - The original command name
* @param args - Arguments passed to the command
* @returns A potentially updated command name
*/
clarifyCommandName?(cmd: string, args: string[]): string;
/** Execute a driver (WebDriver Bidi protocol) command by its name as defined in the bidi commands file
* @param bidiCmd - the name of the command in the bidi spec
* @param args - arguments to pass to the command