UNPKG

@revoloo/cypress6

Version:

Cypress.io end to end testing tool

448 lines (357 loc) 11.1 kB
/* eslint-disable no-case-declarations */ const _ = require('lodash') const ipc = require('electron').ipcMain const { clipboard } = require('electron') const debug = require('debug')('cypress:server:events') const pluralize = require('pluralize') const stripAnsi = require('strip-ansi') const dialog = require('./dialog') const pkg = require('./package') const logs = require('./logs') const auth = require('./auth') const Windows = require('./windows') const { openExternal } = require('./links') const open = require('../util/open') const user = require('../user') const errors = require('../errors') const Updater = require('../updater') const { ProjectBase } = require('../project-base') const openProject = require('../open_project') const ensureUrl = require('../util/ensure-url') const chromePolicyCheck = require('../util/chrome_policy_check') const browsers = require('../browsers') const konfig = require('../konfig') const editors = require('../util/editors') const fileOpener = require('../util/file-opener') const api = require('../api') const nullifyUnserializableValues = (obj) => { // nullify values that cannot be cloned // https://github.com/cypress-io/cypress/issues/6750 return _.cloneDeepWith(obj, (val) => { if (_.isFunction(val)) { return null } }) } const handleEvent = function (options, bus, event, id, type, arg) { debug('got request for event: %s, %o', type, arg) _.defaults(options, { windowOpenFn: Windows.open, getWindowByWebContentsFn: Windows.getByWebContents, }) const sendResponse = function (originalData = {}) { try { const data = nullifyUnserializableValues(originalData) debug('sending ipc data %o', { type, data, originalData }) return event.sender.send('response', data) } catch (error) {} // eslint-disable-line no-empty } const sendErr = function (err) { debug('send error: %o', err) return sendResponse({ id, __error: errors.clone(err, { html: true }) }) } const send = (data) => { return sendResponse({ id, data }) } const sendNull = () => { return send(null) } const onBus = function (event) { bus.removeAllListeners(event) return bus.on(event, send) } switch (type) { case 'on:menu:clicked': return onBus('menu:item:clicked') case 'on:app:event': return onBus('app:events') case 'on:focus:tests': return onBus('focus:tests') case 'on:spec:changed': return onBus('spec:changed') case 'on:config:changed': return onBus('config:changed') case 'on:project:error': return onBus('project:error') case 'on:auth:message': return onBus('auth:message') case 'on:project:warning': return onBus('project:warning') case 'gui:error': return logs.error(arg) .then(sendNull) .catch(sendErr) case 'show:directory:dialog': return dialog.show() .then(send) .catch(sendErr) case 'log:in': return user.logIn(arg) .then(send) .catch(sendErr) case 'log:out': return user.logOut() .then(send) .catch(sendErr) case 'get:current:user': return user.getSafely() .then(send) .catch(sendErr) case 'external:open': return openExternal(arg) case 'close:browser': return openProject.closeBrowser() .then(send) .catch(sendErr) case 'launch:browser': // is there a way to lint the arguments received? debug('launching browser for \'%s\' spec: %o', arg.specType, arg.spec) debug('full list of options %o', arg) // the "arg" should have objects for // - browser // - spec (with fields) // name, absolute, relative // - specType: "integration" | "component" // - specFilter (optional): the string user searched for const fullSpec = _.merge({}, arg.spec, { specType: arg.specType, specFilter: arg.specFilter, }) return openProject.launch(arg.browser, fullSpec, { projectRoot: options.projectRoot, onBrowserOpen () { return send({ browserOpened: true }) }, onBrowserClose () { return send({ browserClosed: true }) }, }) .catch((err) => { if (err.title == null) { err.title = 'Error launching browser' } return sendErr(err) }) case 'begin:auth': const onMessage = (msg) => { return bus.emit('auth:message', msg) } return auth.start(onMessage, arg) .then(send) .catch(sendErr) case 'window:open': return options.windowOpenFn(options.projectRoot, arg) .then(send) .catch(sendErr) case 'window:close': return options.getWindowByWebContentsFn(event.sender).destroy() case 'open:file': return fileOpener.openFile(arg) case 'open:finder': return open.opn(arg) .then(send) .catch(sendErr) case 'get:options': return pkg(options) .then(send) .catch(sendErr) case 'updater:check': return Updater.check({ onNewVersion ({ version }) { return send(version) }, onNoNewVersion () { return send(false) }, }) case 'get:release:notes': return api.getReleaseNotes(arg) .then(send) .catch(sendNull) case 'get:logs': return logs.get() .then(send) .catch(sendErr) case 'clear:logs': return logs.clear() .then(sendNull) .catch(sendErr) case 'on:log': return logs.onLog(send) case 'off:log': logs.off() return send(null) case 'get:orgs': return ProjectBase.getOrgs() .then(send) .catch(sendErr) case 'get:projects': return ProjectBase.getPathsAndIds() .then(send) .catch(sendErr) case 'get:project:statuses': return ProjectBase.getProjectStatuses(arg) .then(send) .catch(sendErr) case 'get:project:status': return ProjectBase.getProjectStatus(arg) .then(send) .catch(sendErr) case 'get:dashboard:projects': return ProjectBase.getDashboardProjects() .then(send) .catch(sendErr) case 'add:project': return ProjectBase.add(arg, options) .then(send) .catch(sendErr) case 'remove:project': return ProjectBase.remove(arg) .then(() => { return send(arg) }) .catch(sendErr) case 'open:project': debug('open:project') const onSettingsChanged = () => { return bus.emit('config:changed') } const onSpecChanged = (spec) => { return bus.emit('spec:changed', spec) } const onFocusTests = function () { if (_.isFunction(options.onFocusTests)) { options.onFocusTests() } return bus.emit('focus:tests') } const onError = (err) => { return bus.emit('project:error', errors.clone(err, { html: true })) } const onWarning = function (warning) { warning.message = stripAnsi(warning.message) return bus.emit('project:warning', errors.clone(warning, { html: true })) } return browsers.getAllBrowsersWith(options.browser) .then((browsers = []) => { debug('setting found %s on the config', pluralize('browser', browsers.length, true)) options.config = _.assign(options.config, { browsers }) }).then(() => { chromePolicyCheck.run((err) => { return options.config.browsers.forEach((browser) => { if (browser.family === 'chromium') { browser.warning = errors.getMsgByType('BAD_POLICY_WARNING_TOOLTIP') } }) }) return openProject.create(arg, options, { onFocusTests, onSpecChanged, onSettingsChanged, onError, onWarning, }) }).call('getConfig') .then(send) .catch(sendErr) case 'close:project': return openProject.close() .then(send) .catch(sendErr) case 'setup:dashboard:project': return openProject.createCiProject(arg) .then(send) .catch(sendErr) case 'set:project:id': return openProject.writeProjectId(arg) .then(send) .catch(sendErr) case 'get:record:keys': return openProject.getRecordKeys() .then(send) .catch(sendErr) case 'get:user:editor': return editors.getUserEditor(true) .then(send) .catch(sendErr) case 'set:user:editor': return editors.setUserEditor(arg) .then(send) .catch(sendErr) case 'get:specs': return openProject.getSpecChanges({ onChange: send, onError: sendErr, }) case 'get:runs': return openProject.getRuns() .then(send) .catch((err) => { err.type = _.get(err, 'statusCode') === 401 ? 'UNAUTHENTICATED' : _.get(err, 'cause.code') === 'ESOCKETTIMEDOUT' ? 'TIMED_OUT' : _.get(err, 'code') === 'ENOTFOUND' ? 'NO_CONNECTION' : err.type || 'UNKNOWN' return sendErr(err) }) case 'request:access': return openProject.requestAccess(arg) .then(send) .catch((err) => { err.type = _.get(err, 'statusCode') === 403 ? 'ALREADY_MEMBER' : (_.get(err, 'statusCode') === 422) && /existing/.test(err.errors && err.errors.userId, (x) => { return x.join('') }) ? 'ALREADY_REQUESTED' : err.type || 'UNKNOWN' return sendErr(err) }) case 'onboarding:closed': return openProject.getProject() .saveState({ showedOnBoardingModal: true }) .then(sendNull) case 'ping:api:server': const apiUrl = konfig('api_url') return ensureUrl.isListening(apiUrl) .then(send) .catch((err) => { // if it's an aggegrate error, just send the first one if (err.length) { const subErr = err[0] err.name = subErr.name || `${subErr.code} ${subErr.address}:${subErr.port}` err.message = subErr.message || `${subErr.code} ${subErr.address}:${subErr.port}` } err.apiUrl = apiUrl return sendErr(err) }) case 'ping:baseUrl': const baseUrl = arg return ensureUrl.isListening(baseUrl) .then(send) .catch((err) => { const warning = errors.get('CANNOT_CONNECT_BASE_URL_WARNING', baseUrl) return sendErr(warning) }) case 'set:clipboard:text': clipboard.writeText(arg) return sendNull() default: throw new Error(`No ipc event registered for: '${type}'`) } } module.exports = { nullifyUnserializableValues, handleEvent, stop () { return ipc.removeAllListeners() }, start (options, bus) { // curry left options return ipc.on('request', _.partial(this.handleEvent, options, bus)) }, }