UNPKG

@revoloo/cypress6

Version:

Cypress.io end to end testing tool

240 lines (183 loc) 5.88 kB
const _ = require('lodash') const path = require('path') const Promise = require('bluebird') const debug = require('debug')('cypress:server:browsers') const utils = require('./utils') const errors = require('../errors') const check = require('check-more-types') // returns true if the passed string is a known browser family name const isBrowserFamily = check.oneOf(['chromium', 'firefox']) let instance = null const kill = function (unbind) { // cleanup our running browser // instance if (!instance) { return Promise.resolve() } return new Promise((resolve) => { if (unbind) { instance.removeAllListeners() } instance.once('exit', (...args) => { debug('browser process killed') return resolve.apply(null, args) }) debug('killing browser process') instance.kill() return cleanup() }) } const cleanup = () => { return instance = null } const getBrowserLauncher = function (browser) { debug('getBrowserLauncher %o', { browser }) if (!isBrowserFamily(browser.family)) { debug('unknown browser family', browser.family) } if (browser.name === 'electron') { return require('./electron') } if (browser.family === 'chromium') { return require('./chrome') } if (browser.family === 'firefox') { return require('./firefox') } } const isValidPathToBrowser = (str) => { return path.basename(str) !== str } const parseBrowserOption = (opt) => { // it's a name or a path if (!_.isString(opt) || !opt.includes(':')) { return { name: opt, channel: 'stable', } } // it's in name:channel format const split = opt.indexOf(':') return { name: opt.slice(0, split), channel: opt.slice(split + 1), } } const ensureAndGetByNameOrPath = function (nameOrPath, returnAll = false, browsers = null) { const findBrowsers = Array.isArray(browsers) ? Promise.resolve(browsers) : utils.getBrowsers() return findBrowsers .then((browsers = []) => { const filter = parseBrowserOption(nameOrPath) debug('searching for browser %o', { nameOrPath, filter, knownBrowsers: browsers }) // try to find the browser by name with the highest version property const sortedBrowsers = _.sortBy(browsers, ['version']) const browser = _.findLast(sortedBrowsers, filter) if (browser) { // short circuit if found if (returnAll) { return browsers } return browser } // did the user give a bad name, or is this actually a path? if (isValidPathToBrowser(nameOrPath)) { // looks like a path - try to resolve it to a FoundBrowser return utils.getBrowserByPath(nameOrPath) .then((browser) => { if (returnAll) { return [browser].concat(browsers) } return browser }).catch((err) => { return errors.throw('BROWSER_NOT_FOUND_BY_PATH', nameOrPath, err.message) }) } // not a path, not found by name return throwBrowserNotFound(nameOrPath, browsers) }) } const formatBrowsersToOptions = (browsers) => { return browsers.map((browser) => { if (browser.channel !== 'stable') { return [browser.name, browser.channel].join(':') } return browser.name }) } const throwBrowserNotFound = function (browserName, browsers = []) { const names = `- ${formatBrowsersToOptions(browsers).join('\n- ')}` return errors.throw('BROWSER_NOT_FOUND_BY_NAME', browserName, names) } process.once('exit', kill) module.exports = { ensureAndGetByNameOrPath, isBrowserFamily, removeOldProfiles: utils.removeOldProfiles, get: utils.getBrowsers, launch: utils.launch, close: kill, _setInstance (_instance) { // for testing instance = _instance }, // note: does not guarantee that `browser` is still running // note: electron will return a list of pids for each webContent getBrowserInstance () { return instance }, getAllBrowsersWith (nameOrPath) { debug('getAllBrowsersWith %o', { nameOrPath }) if (nameOrPath) { return ensureAndGetByNameOrPath(nameOrPath, true) } return utils.getBrowsers() }, open (browser, options = {}, automation) { return kill(true) .then(() => { let browserLauncher; let url _.defaults(options, { onBrowserOpen () {}, onBrowserClose () {}, }) if (!(browserLauncher = getBrowserLauncher(browser))) { return throwBrowserNotFound(browser.name, options.browsers) } if (!(url = options.url)) { throw new Error('options.url must be provided when opening a browser. You passed:', options) } debug('opening browser %o', browser) return browserLauncher.open(browser, url, options, automation) .then((i) => { debug('browser opened') // TODO: bind to process.exit here // or move this functionality into cypress-core-launder i.browser = browser instance = i // TODO: normalizing opening and closing / exiting // so that there is a default for each browser but // enable the browser to configure the interface instance.once('exit', () => { options.onBrowserClose() return cleanup() }) // TODO: instead of waiting an arbitrary // amount of time here we could instead // wait for the socket.io connect event // which would mean that our browser is // completely rendered and open. that would // mean moving this code out of here and // into the project itself // (just like headless code) // ---------------------------- // give a little padding around // the browser opening return Promise.delay(1000) .then(() => { options.onBrowserOpen() return instance }) }) }) }, }