UNPKG

@wdio/cli

Version:
316 lines (315 loc) 13.4 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getDefaultFiles = exports.getPathForFileGeneration = exports.getAnswers = exports.generateTestFiles = exports.hasFile = exports.getCapabilities = exports.validateServiceAnswers = exports.renderConfigurationFile = exports.convertPackageHashToObject = exports.addServiceDeps = exports.replaceConfig = exports.findInConfig = exports.getRunnerName = exports.runOnCompleteHook = exports.runLauncherHook = exports.runServiceHook = void 0; const fs_extra_1 = __importDefault(require("fs-extra")); const ejs_1 = __importDefault(require("ejs")); const path_1 = __importDefault(require("path")); const inquirer_1 = __importDefault(require("inquirer")); const logger_1 = __importDefault(require("@wdio/logger")); const recursive_readdir_1 = __importDefault(require("recursive-readdir")); const webdriverio_1 = require("webdriverio"); const child_process_1 = require("child_process"); const util_1 = require("util"); const constants_1 = require("./constants"); const log = logger_1.default('@wdio/cli:utils'); const TEMPLATE_ROOT_DIR = path_1.default.join(__dirname, 'templates', 'exampleFiles'); const renderFile = util_1.promisify(ejs_1.default.renderFile); /** * run service launch sequences */ async function runServiceHook(launcher, hookName, ...args) { const start = Date.now(); return Promise.all(launcher.map(async (service) => { try { if (typeof service[hookName] === 'function') { await service[hookName](...args); } } catch (e) { const message = `A service failed in the '${hookName}' hook\n${e.stack}\n\n`; if (e instanceof webdriverio_1.SevereServiceError) { return { status: 'rejected', reason: message }; } log.error(`${message}Continue...`); } })).then(results => { if (launcher.length) { log.debug(`Finished to run "${hookName}" hook in ${Date.now() - start}ms`); } const rejectedHooks = results.filter(p => p && p.status === 'rejected'); if (rejectedHooks.length) { return Promise.reject(new Error(`\n${rejectedHooks.map(p => p && p.reason).join()}\n\nStopping runner...`)); } }); } exports.runServiceHook = runServiceHook; /** * Run hook in service launcher * @param {Array|Function} hook - can be array of functions or single function * @param {Object} config * @param {Object} capabilities */ async function runLauncherHook(hook, ...args) { const catchFn = (e) => log.error(`Error in hook: ${e.stack}`); if (typeof hook === 'function') { hook = [hook]; } return Promise.all(hook.map((hook) => { try { return hook(...args); } catch (e) { return catchFn(e); } })).catch(catchFn); } exports.runLauncherHook = runLauncherHook; /** * Run onCompleteHook in Launcher * @param {Array|Function} onCompleteHook - can be array of functions or single function * @param {*} config * @param {*} capabilities * @param {*} exitCode * @param {*} results */ async function runOnCompleteHook(onCompleteHook, config, capabilities, exitCode, results) { if (typeof onCompleteHook === 'function') { onCompleteHook = [onCompleteHook]; } return Promise.all(onCompleteHook.map(async (hook) => { try { await hook(exitCode, config, capabilities, results); return 0; } catch (e) { log.error(`Error in onCompleteHook: ${e.stack}`); return 1; } })); } exports.runOnCompleteHook = runOnCompleteHook; /** * get runner identification by caps */ function getRunnerName(caps = {}) { let runner = caps.browserName || caps.appPackage || caps.appWaitActivity || caps.app || caps.platformName; // MultiRemote if (!runner) { runner = Object.values(caps).length === 0 || Object.values(caps).some(cap => !cap.capabilities) ? 'undefined' : 'MultiRemote'; } return runner; } exports.getRunnerName = getRunnerName; function buildNewConfigArray(str, type, change) { var _a; const newStr = str .split(`${type}s: `)[1] .replace(/'/g, ''); let newArray = ((_a = newStr.match(/(\w*)/gmi)) === null || _a === void 0 ? void 0 : _a.filter(e => !!e).concat([change])) || []; return str .replace('// ', '') .replace(new RegExp(`(${type}s: )((.*\\s*)*)`), `$1[${newArray.map(e => `'${e}'`)}]`); } function buildNewConfigString(str, type, change) { return str.replace(new RegExp(`(${type}: )('\\w*')`), `$1'${change}'`); } function findInConfig(config, type) { let regexStr = `[\\/\\/]*[\\s]*${type}s: [\\s]*\\[([\\s]*['|"]\\w*['|"],*)*[\\s]*\\]`; if (type === 'framework') { regexStr = `[\\/\\/]*[\\s]*${type}: ([\\s]*['|"]\\w*['|"])`; } const regex = new RegExp(regexStr, 'gmi'); return config.match(regex); } exports.findInConfig = findInConfig; function replaceConfig(config, type, name) { if (type === 'framework') { return buildNewConfigString(config, type, name); } const match = findInConfig(config, type); if (!match || match.length === 0) { return; } const text = match.pop() || ''; return config.replace(text, buildNewConfigArray(text, type, name)); } exports.replaceConfig = replaceConfig; function addServiceDeps(names, packages, update = false) { /** * automatically install latest Chromedriver if `wdio-chromedriver-service` * was selected for install */ if (names.some(({ short }) => short === 'chromedriver')) { packages.push('chromedriver'); if (update) { // eslint-disable-next-line no-console console.log('\n=======', '\nPlease change path to / in your wdio.conf.js:', "\npath: '/'", '\n=======\n'); } } /** * install Appium if it is not installed globally if `@wdio/appium-service` * was selected for install */ if (names.some(({ short }) => short === 'appium')) { const result = child_process_1.execSync('appium --version || echo APPIUM_MISSING').toString().trim(); if (result === 'APPIUM_MISSING') { packages.push('appium'); } else if (update) { // eslint-disable-next-line no-console console.log('\n=======', '\nUsing globally installed appium', result, '\nPlease add the following to your wdio.conf.js:', "\nappium: { command: 'appium' }", '\n=======\n'); } } } exports.addServiceDeps = addServiceDeps; /** * @todo add JSComments */ function convertPackageHashToObject(pkg, hash = '$--$') { const splitHash = pkg.split(hash); return { package: splitHash[0], short: splitHash[1] }; } exports.convertPackageHashToObject = convertPackageHashToObject; async function renderConfigurationFile(answers) { const tplPath = path_1.default.join(__dirname, 'templates/wdio.conf.tpl.ejs'); const renderedTpl = await renderFile(tplPath, { answers }); fs_extra_1.default.writeFileSync(path_1.default.join(process.cwd(), 'wdio.conf.js'), renderedTpl); } exports.renderConfigurationFile = renderConfigurationFile; exports.validateServiceAnswers = (answers) => { let result = true; Object.entries(constants_1.EXCLUSIVE_SERVICES).forEach(([name, { services, message }]) => { const exists = answers.some(answer => answer.includes(name)); const hasExclusive = services.some(service => answers.some(answer => answer.includes(service))); if (exists && hasExclusive) { result = `${name} cannot work together with ${services.join(', ')}\n${message}\nPlease uncheck one of them.`; } }); return result; }; function getCapabilities(arg) { const optionalCapabilites = { platformVersion: arg.platformVersion, udid: arg.udid, ...(arg.deviceName && { deviceName: arg.deviceName }) }; /** * Parsing of option property and constructing desiredCapabilities * for Appium session. Could be application(1) or browser(2-3) session. */ if (/.*\.(apk|app|ipa)$/.test(arg.option)) { return { capabilities: { app: arg.option, ...(arg.option.endsWith('apk') ? constants_1.ANDROID_CONFIG : constants_1.IOS_CONFIG), ...optionalCapabilites, } }; } else if (/android/.test(arg.option)) { return { capabilities: { browserName: 'Chrome', ...constants_1.ANDROID_CONFIG, ...optionalCapabilites } }; } else if (/ios/.test(arg.option)) { return { capabilities: { browserName: 'Safari', ...constants_1.IOS_CONFIG, ...optionalCapabilites } }; } return { capabilities: { browserName: arg.option } }; } exports.getCapabilities = getCapabilities; /** * Check if file exists in current work directory * @param {string} filename to check existance for */ function hasFile(filename) { return fs_extra_1.default.existsSync(path_1.default.join(process.cwd(), filename)); } exports.hasFile = hasFile; /** * generate test files based on CLI answers */ async function generateTestFiles(answers) { const testFiles = answers.framework === 'cucumber' ? [path_1.default.join(TEMPLATE_ROOT_DIR, 'cucumber')] : [path_1.default.join(TEMPLATE_ROOT_DIR, 'mochaJasmine')]; if (answers.usePageObjects) { testFiles.push(path_1.default.join(TEMPLATE_ROOT_DIR, 'pageobjects')); } const files = (await Promise.all(testFiles.map((dirPath) => recursive_readdir_1.default(dirPath, [(file, stats) => !stats.isDirectory() && !(file.endsWith('.ejs') || file.endsWith('.feature'))])))).reduce((cur, acc) => [...acc, ...(cur)], []); for (const file of files) { const renderedTpl = await renderFile(file, answers); let destPath = (file.endsWith('page.js.ejs') ? `${answers.destPageObjectRootPath}/${path_1.default.basename(file)}` : file.includes('step_definition') ? `${answers.stepDefinitions}` : `${answers.destSpecRootPath}/${path_1.default.basename(file)}`).replace(/\.ejs$/, '').replace(/\.js$/, answers.isUsingTypeScript ? '.ts' : '.js'); fs_extra_1.default.ensureDirSync(path_1.default.dirname(destPath)); fs_extra_1.default.writeFileSync(destPath, renderedTpl); } } exports.generateTestFiles = generateTestFiles; async function getAnswers(yes) { return yes ? constants_1.QUESTIONNAIRE.reduce((answers, question) => Object.assign(answers, question.when && !question.when(answers) /** * set nothing if question doesn't apply */ ? {} : { [question.name]: typeof question.default !== 'undefined' /** * set default value if existing */ ? typeof question.default === 'function' ? question.default(answers) : question.default : question.choices && question.choices.length /** * pick first choice, select value if it exists */ ? question.choices[0].value ? question.choices[0].value : question.choices[0] : {} }), {}) : await inquirer_1.default.prompt(constants_1.QUESTIONNAIRE); } exports.getAnswers = getAnswers; function getPathForFileGeneration(answers) { const destSpecRootPath = path_1.default.join(process.cwd(), path_1.default.dirname(answers.specs || '').replace(/\*\*$/, '')); const destStepRootPath = path_1.default.join(process.cwd(), path_1.default.dirname(answers.stepDefinitions || '')); const destPageObjectRootPath = answers.usePageObjects ? path_1.default.join(process.cwd(), path_1.default.dirname(answers.pages || '').replace(/\*\*$/, '')) : ''; let relativePath = (answers.generateTestFiles && answers.usePageObjects) ? !(convertPackageHashToObject(answers.framework).short === 'cucumber') ? path_1.default.relative(destSpecRootPath, destPageObjectRootPath) : path_1.default.relative(destStepRootPath, destPageObjectRootPath) : ''; /** * On Windows, path.relative can return backslashes that could be interpreted as espace sequences in strings */ if (process.platform === 'win32') { relativePath = relativePath.replace(/\\/g, '/'); } return { destSpecRootPath: destSpecRootPath, destStepRootPath: destStepRootPath, destPageObjectRootPath: destPageObjectRootPath, relativePath: relativePath }; } exports.getPathForFileGeneration = getPathForFileGeneration; function getDefaultFiles(answers, filePath) { var _a; return ((_a = answers === null || answers === void 0 ? void 0 : answers.isUsingCompiler) === null || _a === void 0 ? void 0 : _a.toString().includes('TypeScript')) ? `${filePath}.ts` : `${filePath}.js`; } exports.getDefaultFiles = getDefaultFiles;