diff --git a/packages/@vue/cli-plugin-e2e-nightwatch/generator/index.js b/packages/@vue/cli-plugin-e2e-nightwatch/generator/index.js index db739d73a6..f9a8f9560e 100644 --- a/packages/@vue/cli-plugin-e2e-nightwatch/generator/index.js +++ b/packages/@vue/cli-plugin-e2e-nightwatch/generator/index.js @@ -1,15 +1,29 @@ +const { installedBrowsers } = require('@vue/cli-shared-utils') + module.exports = api => { api.render('./template', { hasTS: api.hasPlugin('typescript') }) + // Use devDependencies to store latest version number so as to automate update + const devDeps = require('../package.json').devDependencies + const geckodriver = devDeps.geckodriver + + // chromedriver major version bumps every 6 weeks following Chrome + // so there may be a mismatch between + // user's installed browser version and the default provided version + // fallback to the devDependencies version in case detection fails + const chromedriver = installedBrowsers.chrome + ? installedBrowsers.chrome.match(/^(\d+)\./)[1] + : devDeps.chromedriver + api.extendPackage({ scripts: { 'test:e2e': 'vue-cli-service test:e2e' }, devDependencies: { - chromedriver: '^76.0.1', - geckodriver: '^1.16.2' + chromedriver, + geckodriver } }) } diff --git a/packages/@vue/cli-plugin-e2e-nightwatch/index.js b/packages/@vue/cli-plugin-e2e-nightwatch/index.js index 6c56657ad2..c06a7a0f3b 100644 --- a/packages/@vue/cli-plugin-e2e-nightwatch/index.js +++ b/packages/@vue/cli-plugin-e2e-nightwatch/index.js @@ -1,8 +1,7 @@ const fs = require('fs') +const { installedBrowsers, info, warn, error, chalk, execa } = require('@vue/cli-shared-utils') module.exports = (api, options) => { - const { info, chalk, execa } = require('@vue/cli-shared-utils') - api.registerCommand('test:e2e', { description: 'run end-to-end tests with nightwatch', usage: 'vue-cli-service test:e2e [options]', @@ -20,6 +19,28 @@ module.exports = (api, options) => { `All Nightwatch CLI options are also supported.\n` + chalk.yellow(`https://nightwatchjs.org/guide/running-tests/#command-line-options`) }, (args, rawArgs) => { + if (args.env && args.env.includes('firefox')) { + try { + require('geckodriver') + } catch (e) { + error(`To run e2e tests in Firefox, you need to install ${chalk.yellow.bold('geckodriver')} first.`) + process.exit(1) + } + } + if (installedBrowsers.chrome) { + const userVersion = installedBrowsers.chrome + const driverVersion = require('chromedriver').version + + const userMajor = userVersion.match(/^(\d+)\./)[1] + const driverMajor = driverVersion.match(/^(\d+)\./)[1] + + if (userMajor !== driverMajor) { + warn(`Local ${chalk.cyan.bold('Chrome')} version is ${chalk.cyan.bold(userMajor)}, but the installed ${chalk.cyan.bold('chromedriver')} is for version ${chalk.cyan.bold(driverMajor)}.`) + warn(`There may be incompatibilities between them.`) + warn(`Please update your ${chalk.cyan.bold('chromedriver')} dependency to match the ${chalk.cyan.bold('Chrome')} version.`) + } + } + const argsToRemove = ['url', 'mode', 'headless', 'use-selenium', 'parallel'] argsToRemove.forEach((toRemove) => removeArg(rawArgs, toRemove)) diff --git a/packages/@vue/cli-plugin-e2e-nightwatch/nightwatch.config.js b/packages/@vue/cli-plugin-e2e-nightwatch/nightwatch.config.js index a0fd12cf62..c4ad7af8c6 100644 --- a/packages/@vue/cli-plugin-e2e-nightwatch/nightwatch.config.js +++ b/packages/@vue/cli-plugin-e2e-nightwatch/nightwatch.config.js @@ -3,7 +3,12 @@ const path = require('path') const deepmerge = require('deepmerge') const chromedriver = require('chromedriver') -const geckodriver = require('geckodriver') + +// user may have not installed geckodriver +let geckodriver = {} +try { + geckodriver = require('geckodriver') +} catch (e) {} const userOptions = JSON.parse(process.env.VUE_NIGHTWATCH_USER_OPTIONS || '{}') const useSelenium = process.env.VUE_NIGHTWATCH_USE_SELENIUM === '1' @@ -51,7 +56,7 @@ const defaultSettings = { } }, webdriver: useSelenium ? {} : { - server_path: require('geckodriver').path, + server_path: geckodriver.path, port: 4444 } } diff --git a/packages/@vue/cli-plugin-e2e-nightwatch/package.json b/packages/@vue/cli-plugin-e2e-nightwatch/package.json index 4a19899490..f5e3647b1e 100644 --- a/packages/@vue/cli-plugin-e2e-nightwatch/package.json +++ b/packages/@vue/cli-plugin-e2e-nightwatch/package.json @@ -42,6 +42,9 @@ "peerDependenciesMeta": { "selenium-server": { "optional": true + }, + "geckodriver": { + "optional": true } } } diff --git a/packages/@vue/cli-plugin-e2e-nightwatch/prompts.js b/packages/@vue/cli-plugin-e2e-nightwatch/prompts.js new file mode 100644 index 0000000000..9ad6867aba --- /dev/null +++ b/packages/@vue/cli-plugin-e2e-nightwatch/prompts.js @@ -0,0 +1,22 @@ +const { installedBrowsers } = require('@vue/cli-shared-utils') + +module.exports = [ + { + name: 'webdrivers', + type: `checkbox`, + message: `Pick browsers to run end-to-end test on`, + choices: [ + { + name: `Chrome`, + value: 'chrome', + checked: true + }, + { + name: 'Firefox', + value: 'firefox', + // check the "Firefox" option if user has installed it + checked: !!installedBrowsers.firefox + } + ] + } +] diff --git a/packages/@vue/cli-shared-utils/lib/env.js b/packages/@vue/cli-shared-utils/lib/env.js index 506ce35ecb..3c66418034 100644 --- a/packages/@vue/cli-shared-utils/lib/env.js +++ b/packages/@vue/cli-shared-utils/lib/env.js @@ -129,3 +129,72 @@ function checkPnpm (result) { exports.isWindows = process.platform === 'win32' exports.isMacintosh = process.platform === 'darwin' exports.isLinux = process.platform === 'linux' + +const browsers = {} +let hasCheckedBrowsers = false + +function tryRun (cmd) { + try { + return execSync(cmd, { + stdio: [0, 'pipe', 'ignore'] + }).toString().trim() + } catch (e) { + return '' + } +} + +function getLinuxAppVersion (binary) { + return tryRun(`${binary} --version`).replace(/^.* ([^ ]*)/g, '$1') +} + +function getMacAppVersion (bundleIdentifier) { + const bundlePath = tryRun(`mdfind "kMDItemCFBundleIdentifier=='${bundleIdentifier}'"`) + + if (bundlePath) { + return tryRun(`/usr/libexec/PlistBuddy -c Print:CFBundleShortVersionString ${ + bundlePath.replace(/(\s)/g, '\\ ') + }/Contents/Info.plist`) + } +} + +Object.defineProperty(exports, 'installedBrowsers', { + enumerable: true, + get () { + if (hasCheckedBrowsers) { + return browsers + } + hasCheckedBrowsers = true + + if (exports.isLinux) { + browsers.chrome = getLinuxAppVersion('google-chrome') + browsers.firefox = getLinuxAppVersion('firefox') + } else if (exports.isMacintosh) { + browsers.chrome = getMacAppVersion('com.google.Chrome') + browsers.firefox = getMacAppVersion('org.mozilla.firefox') + } else if (exports.isWindows) { + // get chrome stable version + // https://stackoverflow.com/a/51773107/2302258 + const chromeQueryResult = tryRun( + 'reg query "HKLM\\Software\\Google\\Update\\Clients\\{8A69D345-D564-463c-AFF1-A69D9E530F96}" /v pv /reg:32' + ) || tryRun( + 'reg query "HKCU\\Software\\Google\\Update\\Clients\\{8A69D345-D564-463c-AFF1-A69D9E530F96}" /v pv /reg:32' + ) + if (chromeQueryResult) { + const matched = chromeQueryResult.match(/REG_SZ\s+(\S*)$/) + browsers.chrome = matched && matched[1] + } + + // get firefox version + // https://community.spiceworks.com/topic/111518-how-to-determine-version-of-installed-firefox-in-windows-batchscript + const ffQueryResult = tryRun( + 'reg query "HKLM\\Software\\Mozilla\\Mozilla Firefox" /v CurrentVersion' + ) + if (ffQueryResult) { + const matched = ffQueryResult.match(/REG_SZ\s+(\S*)$/) + browsers.firefox = matched && matched[1] + } + } + + return browsers + } +}) diff --git a/packages/@vue/cli/lib/promptModules/__tests__/e2e.spec.js b/packages/@vue/cli/lib/promptModules/__tests__/e2e.spec.js index 52f25394b1..40994e5a85 100644 --- a/packages/@vue/cli/lib/promptModules/__tests__/e2e.spec.js +++ b/packages/@vue/cli/lib/promptModules/__tests__/e2e.spec.js @@ -44,6 +44,11 @@ test('nightwatch', async () => { message: 'Pick a E2E testing solution', choices: ['Cypress', 'Nightwatch'], choose: 1 + }, + { + message: 'Pick browsers to run end-to-end test on', + choice: ['Chrome', 'Firefox'], + check: [0, 1] } ] diff --git a/packages/@vue/cli/lib/promptModules/e2e.js b/packages/@vue/cli/lib/promptModules/e2e.js index c7a21520dd..09bc05bece 100644 --- a/packages/@vue/cli/lib/promptModules/e2e.js +++ b/packages/@vue/cli/lib/promptModules/e2e.js @@ -1,3 +1,5 @@ +const { installedBrowsers } = require('@vue/cli-shared-utils') + module.exports = cli => { cli.injectFeature({ name: 'E2E Testing', @@ -20,13 +22,33 @@ module.exports = cli => { short: 'Cypress' }, { - name: 'Nightwatch (Selenium-based)', + name: 'Nightwatch (WebDriver-based)', value: 'nightwatch', short: 'Nightwatch' } ] }) + cli.injectPrompt({ + name: 'webdrivers', + when: answers => answers.e2e === 'nightwatch', + type: `checkbox`, + message: `Pick browsers to run end-to-end test on`, + choices: [ + { + name: `Chrome`, + value: 'chrome', + checked: true + }, + { + name: 'Firefox', + value: 'firefox', + // check the "Firefox" option if user has installed it + checked: !!installedBrowsers.firefox + } + ] + }) + cli.onPromptComplete((answers, options) => { if (answers.e2e === 'cypress') { options.plugins['@vue/cli-plugin-e2e-cypress'] = {}