feat: restrict plugin activation to plugins enabled using --plugins=p1,p2 flag
MODIFY
lib/cli/args.js
lib/cli/args.js
import { DEFAULT_BASE_PATH } from 'appium-base-driver';
-import { parseSecurityFeatures, parseDefaultCaps, parseInstallTypes } from './parser-helpers';
+import { parseSecurityFeatures, parseDefaultCaps, parsePluginNames, parseInstallTypes } from './parser-helpers';
import { INSTALL_TYPES, DEFAULT_APPIUM_HOME, DRIVER_TYPE, PLUGIN_TYPE } from '../extension-config';
const DRIVER_EXAMPLE = 'xcuitest';
@@ -32,6 +32,14 @@ const serverArgs = [
dest: 'shell',
}],
+ [['--plugins'], {
+ required: false,
+ defaultValue: [],
+ help: 'A comma-separated list of installed plugin names that should be active for this server',
+ type: parsePluginNames,
+ dest: 'plugins',
+ }],
+
[['--allow-cors'], {
required: false,
defaultValue: false,

MODIFY
lib/cli/parser-helpers.js
lib/cli/parser-helpers.js
@@ -28,6 +28,17 @@ function parseSecurityFeatures (features) {
return parsedFeatures;
}
+function parsePluginNames (names) {
+ const splitter = (splitOn, str) => `${str}`.split(splitOn).map((s) => s.trim()).filter(Boolean);
+ try {
+ return splitter(',', names);
+ } catch (err) {
+ throw new Error('Could not parse value of --plugins. Should be a list of plugin names ' +
+ 'separated by commas. Plugin names are those found when running `appium ' +
+ 'plugin list`');
+ }
+}
+
function parseDefaultCaps (caps) {
try {
// use synchronous file access, as `argparse` provides no way of either
@@ -61,4 +72,5 @@ export {
parseSecurityFeatures,
parseDefaultCaps,
parseInstallTypes,
+ parsePluginNames,
};

MODIFY
lib/main.js
lib/main.js
@@ -137,14 +137,19 @@ async function main (args = null) {
await preflightChecks({parser, args, driverConfig, pluginConfig, throwInsteadOfExit});
await logStartupInfo(parser, args);
let routeConfiguringFunction = makeRouter(appiumDriver);
- // turn all of the installed plugins into instantiated plugin class objects, so we can send them
- // as objects to the server init. we also want to send/assign them to the umbrella driver so it can
- // activate them on a per-request basis as well
- const plugins = Object.keys(pluginConfig.installedExtensions).map((plugin) => {
- const PluginClass = pluginConfig.require(plugin);
- return new PluginClass(plugin);
+
+ // find any plugin name which has been installed, and which has been requested for activation by
+ // using the --plugins flag, and turn each one into an instantiated plugin object, so we can send
+ // them as objects to the server init. we also want to send/assign them to the umbrella driver so
+ // it can use them to wrap command execution
+ const plugins = Object.keys(pluginConfig.installedExtensions).filter((pluginName) =>
+ _.includes(args.plugins, pluginName)
+ ).map((pluginName) => {
+ const PluginClass = pluginConfig.require(pluginName);
+ return new PluginClass(pluginName);
});
appiumDriver.plugins = plugins;
+
let server = await baseServer({
routeConfiguringFunction,
port: args.port,

MODIFY
test/plugin-e2e-specs.js
test/plugin-e2e-specs.js
@@ -11,11 +11,11 @@ import { DEFAULT_APPIUM_HOME, INSTALL_TYPE_LOCAL, DRIVER_TYPE, PLUGIN_TYPE } fro
import { TEST_FAKE_APP, TEST_HOST, TEST_PORT } from './helpers';
import { runExtensionCommand } from '../lib/cli/extension';
+chai.should();
chai.use(chaiAsPromised);
const FAKE_PLUGIN_DIR = path.resolve(__dirname, '..', '..', 'node_modules', '@appium', 'fake-plugin');
const FAKE_DRIVER_DIR = path.resolve(__dirname, '..', '..', 'node_modules', 'appium-fake-driver');
-const shouldStartServer = process.env.USE_RUNNING_SERVER !== '0';
const caps = {
automationName: 'Fake',
platformName: 'Fake',
@@ -24,7 +24,6 @@ const caps = {
};
describe('FakePlugin', function () {
- let server = null;
const appiumHome = DEFAULT_APPIUM_HOME;
const baseUrl = `http://${TEST_HOST}:${TEST_PORT}/wd/hub/session`;
before(async function () {
@@ -56,19 +55,56 @@ describe('FakePlugin', function () {
installType: INSTALL_TYPE_LOCAL,
}, PLUGIN_TYPE);
}
+ });
+
+ describe('without plugin registered', function () {
+ let server = null;
+ before(async function () {
// then start server if we need to
- if (shouldStartServer) {
- let args = {port: TEST_PORT, host: TEST_HOST, appiumHome};
+ const args = {port: TEST_PORT, host: TEST_HOST, appiumHome, plugins: ['other1', 'other2']};
server = await appiumServer(args);
+ });
+ after(async function () {
+ if (server) {
+ await server.close();
+ }
+ });
+ it('should not update the server if plugin is not activated', async function () {
+ await axios.post(`http://${TEST_HOST}:${TEST_PORT}/fake`).should.eventually.be.rejectedWith(/404/);
+ });
+ it('should not update method map if plugin is not activated', async function () {
+ const driver = wd.promiseChainRemote(TEST_HOST, TEST_PORT);
+ const [sessionId] = await driver.init(caps);
+ try {
+ await axios.post(`${baseUrl}/${sessionId}/fake_data`, {data: {fake: 'data'}}).should.eventually.be.rejectedWith(/404/);
+ } finally {
+ await driver.quit();
}
});
+ it('should not handle commands if plugin is not activated', async function () {
+ const driver = wd.promiseChainRemote(TEST_HOST, TEST_PORT);
+ const [sessionId] = await driver.init(caps);
+ try {
+ const el = (await axios.post(`${baseUrl}/${sessionId}/element`, {using: 'xpath', value: '//MockWebView'})).data.value;
+ el.should.not.have.property('fake');
+ } finally {
+ await driver.quit();
+ }
+ });
+ });
+ describe('with plugin registered', function () {
+ let server = null;
+ before(async function () {
+ // then start server if we need to
+ const args = {port: TEST_PORT, host: TEST_HOST, appiumHome, plugins: ['fake']};
+ server = await appiumServer(args);
+ });
after(async function () {
if (server) {
await server.close();
}
});
-
it('should update the server', async function () {
const res = {fake: 'fakeResponse'};
(await axios.post(`http://${TEST_HOST}:${TEST_PORT}/fake`)).data.should.eql(res);
@@ -106,3 +142,4 @@ describe('FakePlugin', function () {
}
});
});
+});

feat: restrict plugin activation to plugins enabled using --plugins=p1,p2 flag