feat(appium): support plugin-specific args via --plugin-args
MODIFY
package.json
package.json
},
"devDependencies": {
"@appium/eslint-config-appium": "file:./packages/eslint-config-appium",
- "@appium/fake-plugin": "^1.2.0",
+ "@appium/fake-plugin": "^1.2.2",
"@babel/core": "^7.15.0",
"@babel/eslint-parser": "^7.15.0",
"@babel/plugin-proposal-class-properties": "^7.14.5",

MODIFY
packages/appium/lib/appium.js
packages/appium/lib/appium.js
@@ -202,7 +202,6 @@ class AppiumDriver extends BaseDriver {
// parse/verify CLI args and merge them to this.args if OK or throw an exception
validateExtensionArgs(driverCliArgs, InnerDriver.argsConstraints);
driverInstance.cliArgs = driverCliArgs;
- delete this.args.driverArgs;
log.debug(`Set CLI arguments on ${driverName} driver instance: ${JSON.stringify(driverCliArgs)}`);
}
@@ -422,11 +421,21 @@ class AppiumDriver extends BaseDriver {
}
sessionId = _.truncate(sessionId, {length: 11});
return this.pluginClasses.map((PluginClass) => {
- const name = `${PluginClass.pluginName} (${sessionId})`;
- const opts = {
- pluginArgs: getExtensionArgs(this.args.pluginArgs, PluginClass.pluginName),
- };
- return new PluginClass(name, opts);
+ const generalName = PluginClass.pluginName;
+ const name = `${generalName} (${sessionId})`;
+ const plugin = new PluginClass(name);
+ const pluginCliArgs = getExtensionArgs(this.args.pluginArgs, generalName);
+ if (!_.isEmpty(pluginCliArgs)) {
+ if (!_.has(PluginClass, 'argsConstraints')) {
+ throw new Error(`You sent in CLI args for the ${generalName} plugin, but the plugin ` +
+ `does not define any`);
+ }
+ validateExtensionArgs(pluginCliArgs, PluginClass.argsConstraints);
+ plugin.cliArgs = pluginCliArgs;
+ log.debug(`Set CLI arguments on ${name} plugin for session '${sessionId}': ` +
+ JSON.stringify(pluginCliArgs));
+ }
+ return plugin;
});
}

MODIFY
packages/appium/lib/utils.js
packages/appium/lib/utils.js
import _ from 'lodash';
import logger from './logger';
-import { processCapabilities, PROTOCOLS, validateCaps } from '@appium/base-driver';
+import { processCapabilities, PROTOCOLS, validateCaps as validateArgs } from '@appium/base-driver';
import { fs } from '@appium/support';
const W3C_APPIUM_PREFIX = 'appium';
@@ -46,11 +46,10 @@ function getExtensionArgs (extensionArgs, extensionName) {
function ensureNoUnknownArgs (extensionArgs, argsConstraints) {
const knownArgNames = Object.keys(argsConstraints);
- for (const argName of Object.keys(extensionArgs)) {
- if (!knownArgNames.includes(argName)) {
- throw new Error(`"${argName}" is not a recognized arg. Are you sure it's in the list ` +
- `of supported args? ${JSON.stringify(knownArgNames)}`);
- }
+ const unknownArgs = _.difference(Object.keys(extensionArgs), Object.keys(argsConstraints));
+ if (unknownArgs.length > 0) {
+ throw new Error(`Some arguments were not recognized: ${JSON.stringify(unknownArgs)}. ` +
+ `Are you sure they are in the list of supported args? ${JSON.stringify(knownArgNames)}`);
}
}
@@ -64,7 +63,7 @@ function ensureNoUnknownArgs (extensionArgs, argsConstraints) {
function validateExtensionArgs (extensionArgs, argsConstraints) {
if (!_.isEmpty(extensionArgs) && !_.isEmpty(argsConstraints)) {
ensureNoUnknownArgs(extensionArgs, argsConstraints);
- validateCaps(extensionArgs, argsConstraints);
+ validateArgs(extensionArgs, argsConstraints);
}
}

MODIFY
packages/appium/test/driver-specs.js
packages/appium/test/driver-specs.js
@@ -178,7 +178,7 @@ describe('AppiumDriver', function () {
const args = {driverArgs: {fake: {diffArg: 1234}}};
[appium, mockFakeDriver] = getDriverAndFakeDriver(args, DiffArgsDriver);
const {error} = await appium.createSession(undefined, undefined, W3C_CAPS);
- error.should.match(/not a recognized arg/);
+ error.should.match(/arguments were not recognized/);
});
it('should validate args and put them on driver.cliArgs', async function () {
class ArgsDriver extends BaseDriver {
@@ -195,7 +195,6 @@ describe('AppiumDriver', function () {
const {value} = await appium.createSession(undefined, undefined, W3C_CAPS);
try {
mockFakeDriver._fakeDriver.cliArgs.should.eql({randomArg: 1234});
- should.not.exist(appium.args.driverArgs);
} finally {
await appium.deleteSession(value[0]);
}
@@ -309,4 +308,47 @@ describe('AppiumDriver', function () {
});
});
});
+ describe('#createPluginInstances', function () {
+ class NoArgsPlugin {}
+ NoArgsPlugin.pluginName = 'noargs';
+
+ class ArgsPlugin {
+ static get argsConstraints () {
+ return {
+ randomArg: {
+ isNumber: true,
+ }
+ };
+ }
+ }
+ ArgsPlugin.pluginName = 'args';
+ it('should not set CLI args if none are sent', function () {
+ const appium = new AppiumDriver({});
+ appium.pluginClasses = [NoArgsPlugin, ArgsPlugin];
+ for (const plugin of appium.createPluginInstances()) {
+ should.not.exist(plugin.cliArgs);
+ }
+ });
+ it('should throw if a plugin arg is sent but the plugin doesnt support args', function () {
+ const appium = new AppiumDriver({pluginArgs: {noargs: {foo: 'bar'}}});
+ appium.pluginClasses = [NoArgsPlugin];
+ should.throw(() => appium.createPluginInstances(), /does not define any/);
+ });
+ it('should throw if a plugin arg is sent but the plugin doesnt know about it', function () {
+ const appium = new AppiumDriver({pluginArgs: {args: {foo: 'bar'}}});
+ appium.pluginClasses = [ArgsPlugin];
+ should.throw(() => appium.createPluginInstances(), /arguments were not recognized/);
+ });
+ it('should throw if a plugin arg is sent of the wrong type', function () {
+ const appium = new AppiumDriver({pluginArgs: {args: {randomArg: 'bar'}}});
+ appium.pluginClasses = [ArgsPlugin];
+ should.throw(() => appium.createPluginInstances(), /must be of type number/);
+ });
+ it('should add cliArgs to the plugin once validated', function () {
+ const appium = new AppiumDriver({pluginArgs: {args: {randomArg: 1234}}});
+ appium.pluginClasses = [ArgsPlugin];
+ const plugin = appium.createPluginInstances()[0];
+ plugin.cliArgs.should.eql({randomArg: 1234});
+ });
+ });
});

MODIFY
packages/appium/test/parser-specs.js
packages/appium/test/parser-specs.js
@@ -80,17 +80,17 @@ describe('Server Parser', function () {
parsed.denyInsecure.should.eql(['nofeature1', 'nofeature2', 'nofeature3']);
});
it('should parse default driver args correctly from a string', function () {
- let fakeDriverArgs = {'fake': {'sillyWebServerPort': 1234, 'host': 'hey'}};
+ let fakeDriverArgs = {fake: {sillyWebServerPort: 1234, host: 'hey'}};
let args = p.parse_args(['--driver-args', JSON.stringify(fakeDriverArgs)]);
args.driverArgs.should.eql(fakeDriverArgs);
});
it('should parse default driver args correctly from a file', function () {
- let fakeDriverArgs = {'fake': {'sillyWebServerPort': 1234, 'host': 'hey'}};
+ let fakeDriverArgs = {fake: {sillyWebServerPort: 1234, host: 'hey'}};
let args = p.parse_args(['--driver-args', FAKE_DRIVER_ARGS_FIXTURE]);
args.driverArgs.should.eql(fakeDriverArgs);
});
it('should parse default plugin args correctly from a string', function () {
- let fakePluginArgs = {'fake': {'sillyWebServerPort': 1234, 'host': 'hey'}};
+ let fakePluginArgs = {fake: {sillyWebServerPort: 1234, host: 'hey'}};
let args = p.parse_args(['--plugin-args', JSON.stringify(fakePluginArgs)]);
args.pluginArgs.should.eql(fakePluginArgs);
});

MODIFY
packages/appium/test/plugin-e2e-specs.js
packages/appium/test/plugin-e2e-specs.js
@@ -9,8 +9,8 @@ import { W3C_PREFIXED_CAPS, TEST_HOST, getTestPort, PROJECT_ROOT } from './helpe
import { runExtensionCommand } from '../lib/cli/extension';
-const FAKE_ARGS = {'sillyWebServerPort': 1234, 'host': 'hey'};
-const FAKE_PLUGIN_ARGS = JSON.stringify({'fake': FAKE_ARGS});
+const FAKE_ARGS = {sillyWebServerPort: 1234, host: 'hey'};
+const FAKE_PLUGIN_ARGS = {fake: FAKE_ARGS};
const wdOpts = {
hostname: TEST_HOST,
@@ -164,7 +164,7 @@ describe('FakePlugin', function () {
}
});
- it('should recieve user cli args for plugin if passed in', async function () {
+ it('should receive user cli args for plugin if passed in', async function () {
const driver = await wdio(wdOpts);
const {sessionId} = driver;
try {
@@ -187,12 +187,12 @@ describe('FakePlugin', function () {
}
});
- it('should not recieve user cli args for plugin if none were passed in', async function () {
+ it('should not receive user cli args for plugin if none were passed in', async function () {
const driver = await wdio(wdOpts);
const {sessionId} = driver;
try {
const {data} = await axios.get(`${baseUrl}/${sessionId}/fakepluginargs`);
- data.value.should.eql({});
+ should.not.exist(data.value);
} finally {
await driver.deleteSession();
}

feat(appium): support plugin-specific args via --plugin-args

Co-authored-by: Ayan Khan <ayan@headspin.io>