refactor: generalize DriverConfig into ExtensionConfig and make DriverConfig a special case of it
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 { INSTALL_TYPES, DEFAULT_APPIUM_HOME } from '../driver-config';
+import { INSTALL_TYPES, DEFAULT_APPIUM_HOME, DRIVER_TYPE, PLUGIN_TYPE } from '../extension-config';
const DRIVER_EXAMPLE = 'xcuitest';
const PLUGIN_EXAMPLE = 'find_by_image';
-const DRIVER_TYPE = 'driver';
-const PLUGIN_TYPE = 'plugin';
// sharedArgs will be added to every subcommand
const sharedArgs = [
@@ -419,6 +417,4 @@ export {
sharedArgs,
serverArgs,
extensionArgs,
- DRIVER_TYPE,
- PLUGIN_TYPE,
};

MODIFY
lib/cli/driver.js
lib/cli/driver.js
import _ from 'lodash';
import path from 'path';
import { KNOWN_DRIVERS } from '../drivers';
-import DriverConfig, { INSTALL_TYPE_NPM, INSTALL_TYPE_GIT, INSTALL_TYPE_GITHUB,
- INSTALL_TYPE_LOCAL } from '../driver-config';
+import { DriverConfig, INSTALL_TYPE_NPM, INSTALL_TYPE_GIT, INSTALL_TYPE_GITHUB,
+ INSTALL_TYPE_LOCAL } from '../extension-config';
import NPM from './npm';
import { errAndQuit, log, spinWith, JSON_SPACES } from './utils';
import { util, fs } from 'appium-support';
@@ -61,12 +61,12 @@ class DriverCommand {
async list ({showInstalled, showUpdates}) {
const lsMsg = `Listing ${showInstalled ? 'installed' : 'available'} drivers`;
- const installedNames = Object.keys(this.config.installedDrivers);
+ const installedNames = Object.keys(this.config.installedExtensions);
const knownNames = Object.keys(KNOWN_DRIVERS);
const drivers = [...installedNames, ...knownNames].reduce((acc, name) => {
if (!acc[name]) {
if (installedNames.includes(name)) {
- acc[name] = {...this.config.installedDrivers[name], installed: true};
+ acc[name] = {...this.config.installedExtensions[name], installed: true};
} else if (!showInstalled) {
acc[name] = {pkgName: KNOWN_DRIVERS[name], installed: false};
}
@@ -200,14 +200,14 @@ class DriverCommand {
driverData.installType = installType;
driverData.installSpec = installSpec;
- await this.config.addDriver(driverName, driverData);
+ await this.config.addExtension(driverName, driverData);
// log info for the user
log(this.isJsonOutput, `Driver ${driverName}@${driverData.version} successfully installed`.green);
log(this.isJsonOutput, `- automationName: ${driverData.automationName.green}`);
log(this.isJsonOutput, `- platformNames: ${JSON.stringify(driverData.platformNames).green}`);
- return this.config.installedDrivers;
+ return this.config.installedExtensions;
}
async installViaNpm ({driver, pkgName, pkgVer}) {
@@ -268,10 +268,10 @@ class DriverCommand {
try {
await fs.rimraf(this.config.getInstallPath(driver));
} finally {
- await this.config.removeDriver(driver);
+ await this.config.removeExtension(driver);
}
log(this.isJsonOutput, `Successfully uninstalled driver '${driver}'`.green);
- return this.config.installedDrivers;
+ return this.config.installedExtensions;
}
/**
@@ -305,7 +305,7 @@ class DriverCommand {
if (!shouldUpdateAll && !this.config.isInstalled(driver)) {
throw new Error(`Driver '${driver}' was not installed, so can't be updated`);
}
- const driversToUpdate = shouldUpdateAll ? Object.keys(this.config.installedDrivers) : [driver];
+ const driversToUpdate = shouldUpdateAll ? Object.keys(this.config.installedExtensions) : [driver];
// 'errors' will have driver names as keys and error objects as values
const errors = {};
@@ -317,7 +317,7 @@ class DriverCommand {
for (const d of driversToUpdate) {
try {
await spinWith(this.isJsonOutput, `Checking if driver '${d}' is updatable`, () => {
- if (this.config.installedDrivers[d].installType !== INSTALL_TYPE_NPM) {
+ if (this.config.installedExtensions[d].installType !== INSTALL_TYPE_NPM) {
throw new NotUpdatableError();
}
});
@@ -382,7 +382,7 @@ class DriverCommand {
// TODO decide how we want to handle beta versions?
// this is a helper method, 'driver' is assumed to already be installed here, and of the npm
// install type
- const {version, pkgName} = this.config.installedDrivers[driver];
+ const {version, pkgName} = this.config.installedExtensions[driver];
let unsafeUpdate = await this.npm.getLatestVersion(pkgName);
let safeUpdate = await this.npm.getLatestSafeUpgradeVersion(pkgName, version);
if (!util.compareVersions(unsafeUpdate, '>', version)) {
@@ -409,9 +409,9 @@ class DriverCommand {
* @param {string} version - version string identifier to update driver to
*/
async updateDriver (driver, version) {
- const {pkgName} = this.config.installedDrivers[driver];
+ const {pkgName} = this.config.installedExtensions[driver];
await this.installViaNpm({driver, pkgName, pkgVer: version});
- this.config.installedDrivers[driver].version = version;
+ this.config.installedExtensions[driver].version = version;
await this.config.write();
}
}

MODIFY
lib/cli/parser-helpers.js
lib/cli/parser-helpers.js
import fs from 'fs';
import _ from 'lodash';
-import { INSTALL_TYPES } from '../driver-config';
+import { INSTALL_TYPES } from '../extension-config';
// serverArgs will be added to the `server` (default) subcommand
function parseSecurityFeatures (features) {

MODIFY
lib/cli/parser.js
lib/cli/parser.js
import path from 'path';
import _ from 'lodash';
import { ArgumentParser } from 'argparse';
-import { sharedArgs, serverArgs, extensionArgs, DRIVER_TYPE, PLUGIN_TYPE } from './args';
+import { sharedArgs, serverArgs, extensionArgs } from './args';
+import { DRIVER_TYPE, PLUGIN_TYPE } from '../extension-config';
import { rootDir } from '../utils';
function getParser (debug = false) {

MODIFY
lib/drivers.js
lib/drivers.js
@@ -57,9 +57,9 @@ function findMatchingDriver (config, {automationName, platformName}) {
driverName,
mainClass,
version,
- } = getDriverBySupport(config.installedDrivers, automationName, platformName);
+ } = getDriverBySupport(config.installedExtensions, automationName, platformName);
log.info(`The '${driverName}' driver was installed and matched caps.`);
- log.info(`Will require it at ${config.getDriverRequirePath(driverName)}`);
+ log.info(`Will require it at ${config.getExtensionRequirePath(driverName)}`);
const driver = config.require(driverName);
if (!driver) {
throw new Error(`MainClass ${mainClass} did not result in a driver object`);

RENAME
lib/driver-config.js
lib/extension-config.js
@@ -4,10 +4,12 @@ import { fs, mkdirp } from 'appium-support';
import path from 'path';
import YAML from 'yaml';
+const DRIVER_TYPE = 'driver';
+const PLUGIN_TYPE = 'plugin';
const DEFAULT_APPIUM_HOME = path.resolve(process.env.HOME, '.appium');
-const CONFIG_FILE_NAME = 'drivers.yaml';
-const CONFIG_SCHEMA_REV = 1;
+const CONFIG_FILE_NAME = 'extensions.yaml';
+const CONFIG_SCHEMA_REV = 2;
const INSTALL_TYPE_NPM = 'npm';
const INSTALL_TYPE_LOCAL = 'local';
@@ -21,31 +23,44 @@ const INSTALL_TYPES = [
];
-export default class DriverConfig {
- constructor (appiumHome, logFn = null) {
+export default class ExtensionConfig {
+ constructor (appiumHome, extensionType, logFn = null) {
if (logFn === null) {
logFn = log.error.bind(log);
}
this.appiumHome = appiumHome;
this.configFile = path.resolve(this.appiumHome, CONFIG_FILE_NAME);
- this.installedDrivers = {};
+ this.installedExtensions = {};
+ this.extensionType = extensionType;
+ this.configKey = `${extensionType}s`;
+ this.yamlData = {[`${DRIVER_TYPE}s`]: {}, [`${PLUGIN_TYPE}s`]: {}};
this.log = logFn;
}
+ validate () {
+ throw new Error('This method must be implemented in a final class');
+ }
+
+ applySchemaMigrations () {
+ if (this.yamlData.schemaRev < 2 && _.isUndefined(this.yamlData[PLUGIN_TYPE])) {
+ // at schema revision 2, we started including plugins as well as drivers in the file,
+ // so make sure we at least have an empty section for it
+ this.yamlData[PLUGIN_TYPE] = {};
+ }
+ }
+
async read () {
await mkdirp(this.appiumHome); // ensure appium home exists
try {
- const yamlData = YAML.parse(await fs.readFile(this.configFile, 'utf8'));
- // in the future if we need to do anything specific based on schema
- // revision, we can check it as follows
- // const schemaRev = yamlData.schemaRev;
+ this.yamlData = YAML.parse(await fs.readFile(this.configFile, 'utf8'));
+ this.applySchemaMigrations();
// set the list of drivers the user has installed
- this.installedDrivers = this.validate(yamlData.drivers);
+ this.installedExtensions = this.validate(this.yamlData[this.configKey]);
} catch (err) {
if (await fs.exists(this.configFile)) {
// if the file exists and we couldn't parse it, that's a problem
- throw new Error(`Appium had trouble loading the driver installation ` +
+ throw new Error(`Appium had trouble loading the extension installation ` +
`cache file (${this.configFile}). Ensure it exists and is ` +
`readable. Specific error: ${err.message}`);
}
@@ -59,7 +74,70 @@ export default class DriverConfig {
`(${this.appiumHome}). Please ensure it is writable.`);
}
}
- return this.installedDrivers;
+ return this.installedExtensions;
+ }
+
+
+ async write () {
+ const newYamlData = {
+ ...this.yamlData,
+ schemaRev: CONFIG_SCHEMA_REV,
+ [this.configKey]: this.installedExtensions
+ };
+ await fs.writeFile(this.configFile, YAML.stringify(newYamlData), 'utf8');
+ }
+
+ async addExtension (extName, extData) {
+ this.installedExtensions[extName] = extData;
+ await this.write();
+ }
+
+ async removeExtension (extName) {
+ delete this.installedExtensions[extName];
+ await this.write();
+ }
+
+ print () {
+ const extNames = Object.keys(this.installedExtensions);
+ if (_.isEmpty(extNames)) {
+ log.info(`No ${this.configKey} have been installed. Use the "appium ${this.extensionType}" ` +
+ 'command to install the one(s) you want to use.');
+ return;
+ }
+
+ log.info(`Available ${this.configKey}:`);
+ for (const [extName, extData] of _.toPairs(this.installedExtensions)) {
+ log.info(` - ${this.extensionDesc(extName, extData)}`);
+ }
+ }
+
+ extensionDesc () {
+ throw new Error('This must be implemented in a final class');
+ }
+
+ getExtensionRequirePath (extName) {
+ const {pkgName, installPath} = this.installedExtensions[extName];
+ return path.resolve(this.appiumHome, installPath, 'node_modules', pkgName);
+ }
+
+ getInstallPath (extName) {
+ const {installPath} = this.installedExtensions[extName];
+ return path.resolve(this.appiumHome, installPath);
+ }
+
+ require (extName) {
+ const {mainClass} = this.installedExtensions[extName];
+ return require(this.getExtensionRequirePath(extName))[mainClass];
+ }
+
+ isInstalled (extName) {
+ return _.includes(Object.keys(this.installedExtensions), extName);
+ }
+}
+
+class DriverConfig extends ExtensionConfig {
+ constructor (appiumHome, logFn = null) {
+ super(appiumHome, DRIVER_TYPE, logFn);
}
validate (drivers) {
@@ -134,7 +212,7 @@ export default class DriverConfig {
}
// remove this driver from the list since it's not valid
delete drivers[driverName];
- problemSummaries.push(`Driver ${driverName} had errors and will not ` +
+ problemSummaries.push(`Extension ${driverName} had errors and will not ` +
`be available. Errors:`);
for (const problem of problems) {
problemSummaries.push(` - ${problem.err} (Actual value: ` +
@@ -153,56 +231,19 @@ export default class DriverConfig {
return drivers;
}
- async write () {
- const yamlData = {schemaRev: CONFIG_SCHEMA_REV, drivers: this.installedDrivers};
- await fs.writeFile(this.configFile, YAML.stringify(yamlData), 'utf8');
- }
-
- async addDriver (driverName, driverData) {
- this.installedDrivers[driverName] = driverData;
- await this.write();
- }
-
- async removeDriver (driverName) {
- delete this.installedDrivers[driverName];
- await this.write();
- }
-
- print () {
- const driverNames = Object.keys(this.installedDrivers);
- if (_.isEmpty(driverNames)) {
- log.info('No drivers have been installed. Use the "appium driver" ' +
- 'command to install the one(s) you want to use.');
- return;
- }
-
- log.info('Available drivers:');
- for (const [driverName, {version, automationName}] of _.toPairs(this.installedDrivers)) {
- log.info(` - ${driverName}@${version} (automationName '${automationName}')`);
- }
- }
-
- getDriverRequirePath (driverName) {
- const {pkgName, installPath} = this.installedDrivers[driverName];
- return path.resolve(this.appiumHome, installPath, 'node_modules', pkgName);
- }
-
- getInstallPath (driverName) {
- const {installPath} = this.installedDrivers[driverName];
- return path.resolve(this.appiumHome, installPath);
+ extensionDesc (driverName, {version, automationName}) {
+ return `${driverName}@${version} (automationName '${automationName}')`;
}
-
- require (driverName) {
- const {mainClass} = this.installedDrivers[driverName];
- return require(this.getDriverRequirePath(driverName))[mainClass];
}
- isInstalled (driverName) {
- return _.includes(Object.keys(this.installedDrivers), driverName);
+class PluginConfig extends ExtensionConfig {
+ constructor (appiumHome, logFn = null) {
+ super(appiumHome, PLUGIN_TYPE, logFn);
}
}
export {
INSTALL_TYPE_NPM, INSTALL_TYPE_GIT, INSTALL_TYPE_LOCAL, INSTALL_TYPE_GITHUB,
- INSTALL_TYPES, DEFAULT_APPIUM_HOME
+ INSTALL_TYPES, DEFAULT_APPIUM_HOME, DriverConfig, PluginConfig, DRIVER_TYPE,
+ PLUGIN_TYPE,
};

MODIFY
lib/main.js
lib/main.js
@@ -13,7 +13,7 @@ import {
warnNodeDeprecations, validateTmpDir, getNonDefaultArgs,
getGitRev, APPIUM_VER
} from './config';
-import DriverConfig from './driver-config';
+import { DriverConfig } from './extension-config';
import { runDriverCommand } from './cli/driver';
import { AppiumDriver } from './appium';
import registerNode from './grid-register';
@@ -128,8 +128,9 @@ async function main (args = null) {
}
let appiumDriver = new AppiumDriver(args);
- appiumDriver.driverConfig = new DriverConfig(args.appiumHome);
- await preflightChecks({parser, args, driverConfig: appiumDriver.driverConfig, throwInsteadOfExit});
+ const driverConfig = new DriverConfig(args.appiumHome);
+ appiumDriver.driverConfig = driverConfig;
+ await preflightChecks({parser, args, driverConfig, throwInsteadOfExit});
await logStartupInfo(parser, args);
let routeConfiguringFunction = makeRouter(appiumDriver);
let server = await baseServer({

MODIFY
test/cli-specs.js
test/cli-specs.js
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
-import DriverConfig, { DEFAULT_APPIUM_HOME } from '../lib/driver-config';
+import { DriverConfig, DEFAULT_APPIUM_HOME } from '../lib/extension-config';
import { DriverCommand } from '../lib/cli/driver';
import sinon from 'sinon';
@@ -13,7 +13,7 @@ describe('DriverCommand', function () {
const config = new DriverConfig(DEFAULT_APPIUM_HOME);
const driver = 'fake';
const pkgName = 'appium-fake-driver';
- config.installedDrivers = {[driver]: {version: '1.0.0', pkgName}};
+ config.installedExtensions = {[driver]: {version: '1.0.0', pkgName}};
const dc = new DriverCommand({config, json: true});
describe('#checkForDriverUpdate', function () {

MODIFY
test/driver-e2e-specs.js
test/driver-e2e-specs.js
@@ -7,7 +7,7 @@ import chaiAsPromised from 'chai-as-promised';
import wd from 'wd';
import axios from 'axios';
import { main as appiumServer } from '../lib/main';
-import { DEFAULT_APPIUM_HOME, INSTALL_TYPE_NPM } from '../lib/driver-config';
+import { DEFAULT_APPIUM_HOME, INSTALL_TYPE_NPM } from '../lib/extension-config';
import { TEST_FAKE_APP, TEST_HOST, TEST_PORT } from './helpers';
import { BaseDriver } from 'appium-base-driver';
import { FakeDriver } from 'appium-fake-driver';

MODIFY
test/parser-specs.js
test/parser-specs.js
// transpile:mocha
import { getParser } from '../lib/cli/parser';
-import { INSTALL_TYPES, DEFAULT_APPIUM_HOME } from '../lib/driver-config';
+import { INSTALL_TYPES, DEFAULT_APPIUM_HOME } from '../lib/extension-config';
import chai from 'chai';
const should = chai.should();

refactor: generalize DriverConfig into ExtensionConfig and make DriverConfig a special case of it