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

This commit is contained in:
Jonathan Lipps
2020-07-08 13:30:17 -07:00
parent 9a44b9baf5
commit 6eafdf63a6
10 changed files with 126 additions and 87 deletions

View File

@@ -1,11 +1,9 @@
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,
};

View File

@@ -3,8 +3,8 @@
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();
}
}

View File

@@ -1,6 +1,6 @@
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) {

View File

@@ -1,7 +1,8 @@
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) {

View File

@@ -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`);

View File

@@ -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');
extensionDesc (driverName, {version, automationName}) {
return `${driverName}@${version} (automationName '${automationName}')`;
}
}
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);
}
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,
};

View File

@@ -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({

View File

@@ -2,7 +2,7 @@
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 () {

View File

@@ -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';

View File

@@ -1,7 +1,7 @@
// 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();