UNPKG

@revoloo/cypress6

Version:

Cypress.io end to end testing tool

484 lines (370 loc) 12 kB
import _ from 'lodash' import { EventEmitter } from 'events' import Promise from 'bluebird' import { action } from 'mobx' import { client } from '@packages/socket' import automation from './automation' import logger from './logger' import $Cypress, { $ } from '@packages/driver' const ws = client.connect({ path: '/__socket.io', transports: ['websocket'], }) ws.on('connect', () => { ws.emit('runner:connected') }) const driverToReporterEvents = 'paused before:firefox:force:gc after:firefox:force:gc'.split(' ') const driverToLocalAndReporterEvents = 'run:start run:end'.split(' ') const driverToSocketEvents = 'backend:request automation:request mocha recorder:frame'.split(' ') const driverTestEvents = 'test:before:run:async test:after:run'.split(' ') const driverToLocalEvents = 'viewport:changed config stop url:changed page:loading visit:failed'.split(' ') const socketRerunEvents = 'runner:restart watched:file:changed'.split(' ') const socketToDriverEvents = 'net:event script:error'.split(' ') const localBus = new EventEmitter() const reporterBus = new EventEmitter() // NOTE: this is exposed for testing, ideally we should only expose this if a test flag is set window.runnerWs = ws // NOTE: this is for testing Cypress-in-Cypress, window.Cypress is undefined here // unless Cypress has been loaded into the AUT frame if (window.Cypress) { window.eventManager = { reporterBus, localBus } } /** * @type {Cypress.Cypress} */ let Cypress const eventManager = { reporterBus, getCypress () { return Cypress }, addGlobalListeners (state, connectionInfo) { const rerun = () => { if (!this) { // if the tests have been reloaded // then nothing to rerun return } return this._reRun(state) } ws.emit('is:automation:client:connected', connectionInfo, action('automationEnsured', (isConnected) => { state.automation = isConnected ? automation.CONNECTED : automation.MISSING ws.on('automation:disconnected', action('automationDisconnected', () => { state.automation = automation.DISCONNECTED })) })) ws.on('change:to:url', (url) => { window.location.href = url }) ws.on('automation:push:message', (msg, data = {}) => { if (!Cypress) return switch (msg) { case 'change:cookie': Cypress.Cookies.log(data.message, data.cookie, data.removed) break default: break } }) ws.on('component:specs:changed', (specs) => { state.setSpecs(specs) }) ws.on('dev-server:hmr:error', (error) => { Cypress.stop() localBus.emit('script:error', error) }) _.each(socketRerunEvents, (event) => { ws.on(event, rerun) }) _.each(socketToDriverEvents, (event) => { ws.on(event, (...args) => { Cypress.emit(event, ...args) }) }) const logCommand = (logId) => { const consoleProps = Cypress.runner.getConsolePropsForLogById(logId) logger.logFormatted(consoleProps) } reporterBus.on('runner:console:error', ({ err, commandId }) => { if (!Cypress) return if (commandId || err) logger.clearLog() if (commandId) logCommand(commandId) if (err) logger.logError(err.stack) }) reporterBus.on('runner:console:log', (logId) => { if (!Cypress) return logger.clearLog() logCommand(logId) }) reporterBus.on('focus:tests', this.focusTests) reporterBus.on('get:user:editor', (cb) => { ws.emit('get:user:editor', cb) }) reporterBus.on('set:user:editor', (editor) => { ws.emit('set:user:editor', editor) }) reporterBus.on('runner:restart', rerun) function sendEventIfSnapshotProps (logId, event) { if (!Cypress) return const snapshotProps = Cypress.runner.getSnapshotPropsForLogById(logId) if (snapshotProps) { localBus.emit(event, snapshotProps) } } reporterBus.on('runner:show:snapshot', (logId) => { sendEventIfSnapshotProps(logId, 'show:snapshot') }) reporterBus.on('runner:hide:snapshot', this._hideSnapshot.bind(this)) reporterBus.on('runner:pin:snapshot', (logId) => { sendEventIfSnapshotProps(logId, 'pin:snapshot') }) reporterBus.on('runner:unpin:snapshot', this._unpinSnapshot.bind(this)) reporterBus.on('runner:resume', () => { if (!Cypress) return Cypress.emit('resume:all') }) reporterBus.on('runner:next', () => { if (!Cypress) return Cypress.emit('resume:next') }) reporterBus.on('runner:stop', () => { if (!Cypress) return Cypress.stop() }) reporterBus.on('save:state', (state) => { this.saveState(state) }) reporterBus.on('external:open', (url) => { ws.emit('external:open', url) }) reporterBus.on('open:file', (url) => { ws.emit('open:file', url) }) const $window = $(window) // when we actually unload then // nuke all of the cookies again // so we clear out unload $window.on('unload', () => { this._clearAllCookies() }) // when our window triggers beforeunload // we know we've change the URL and we need // to clear our cookies // additionally we set unload to true so // that Cypress knows not to set any more // cookies $window.on('beforeunload', () => { reporterBus.emit('reporter:restart:test:run') this._clearAllCookies() this._setUnload() }) }, start (config) { if (config.socketId) { ws.emit('app:connect', config.socketId) } }, setup (config) { Cypress = this.Cypress = $Cypress.create(config) // expose Cypress globally window.Cypress = Cypress this._addCypressListeners(Cypress) ws.emit('watch:test:file', config.spec) }, isBrowser (browserName) { if (!this.Cypress) return false return this.Cypress.isBrowser(browserName) }, initialize ($autIframe, config) { performance.mark('initialize-start') return Cypress.initialize({ $autIframe, onSpecReady: () => { // get the current runnable in case we reran mid-test due to a visit // to a new domain ws.emit('get:existing:run:state', (state = {}) => { if (!Cypress.runner) { // the tests have been reloaded return } const runnables = Cypress.runner.normalizeAll(state.tests) const run = () => { performance.mark('initialize-end') performance.measure('initialize', 'initialize-start', 'initialize-end') this._runDriver(state) } reporterBus.emit('runnables:ready', runnables) if (state.numLogs) { Cypress.runner.setNumLogs(state.numLogs) } if (state.startTime) { Cypress.runner.setStartTime(state.startTime) } if (config.isTextTerminal && !state.currentId) { // we are in run mode and it's the first load // store runnables in backend and maybe send to dashboard return ws.emit('set:runnables:and:maybe:record:tests', runnables, run) } if (state.currentId) { // if we have a currentId it means // we need to tell the Cypress to skip // ahead to that test Cypress.runner.resumeAtTest(state.currentId, state.emissions) } run() }) }, }) }, _addCypressListeners (Cypress) { Cypress.on('message', (msg, data, cb) => { ws.emit('client:request', msg, data, cb) }) _.each(driverToSocketEvents, (event) => { Cypress.on(event, (...args) => { return ws.emit(event, ...args) }) }) Cypress.on('collect:run:state', () => { if (Cypress.env('NO_COMMAND_LOG')) { return Promise.resolve() } return new Promise((resolve) => { reporterBus.emit('reporter:collect:run:state', resolve) }) }) Cypress.on('log:added', (log) => { const displayProps = Cypress.runner.getDisplayPropsForLog(log) reporterBus.emit('reporter:log:add', displayProps) }) Cypress.on('log:changed', (log) => { const displayProps = Cypress.runner.getDisplayPropsForLog(log) reporterBus.emit('reporter:log:state:changed', displayProps) }) Cypress.on('before:screenshot', (config, cb) => { const beforeThenCb = () => { localBus.emit('before:screenshot', config) cb() } if (Cypress.env('NO_COMMAND_LOG')) { return beforeThenCb() } const wait = !config.appOnly && config.waitForCommandSynchronization if (!config.appOnly) { reporterBus.emit('test:set:state', _.pick(config, 'id', 'isOpen'), wait ? beforeThenCb : undefined) } if (!wait) beforeThenCb() }) Cypress.on('after:screenshot', (config) => { localBus.emit('after:screenshot', config) }) _.each(driverToReporterEvents, (event) => { Cypress.on(event, (...args) => { reporterBus.emit(event, ...args) }) }) _.each(driverTestEvents, (event) => { Cypress.on(event, (test, cb) => { reporterBus.emit(event, test, cb) }) }) _.each(driverToLocalAndReporterEvents, (event) => { Cypress.on(event, (...args) => { localBus.emit(event, ...args) reporterBus.emit(event, ...args) }) }) _.each(driverToLocalEvents, (event) => { Cypress.on(event, (...args) => { return localBus.emit(event, ...args) }) }) Cypress.on('script:error', (err) => { Cypress.stop() localBus.emit('script:error', err) }) }, _runDriver (state) { performance.mark('run-s') Cypress.run(() => { performance.mark('run-e') performance.measure('run', 'run-s', 'run-e') }) reporterBus.emit('reporter:start', { firefoxGcInterval: Cypress.getFirefoxGcInterval(), startTime: Cypress.runner.getStartTime(), numPassed: state.passed, numFailed: state.failed, numPending: state.pending, autoScrollingEnabled: state.autoScrollingEnabled, scrollTop: state.scrollTop, }) }, stop () { localBus.removeAllListeners() ws.off() }, _reRun (state) { if (!Cypress) return state.setIsLoading(true) // when we are re-running we first // need to stop cypress always Cypress.stop() return this._restart() .then(() => { // this probably isn't 100% necessary // since Cypress will fall out of scope // but we want to be aggressive here // and force GC early and often Cypress.removeAllListeners() localBus.emit('restart') }) }, _restart () { return new Promise((resolve) => { reporterBus.once('reporter:restarted', resolve) reporterBus.emit('reporter:restart:test:run') }) }, emit (event, ...args) { localBus.emit(event, ...args) }, on (event, ...args) { localBus.on(event, ...args) }, notifyRunningSpec (specFile) { ws.emit('spec:changed', specFile) }, focusTests () { ws.emit('focus:tests') }, snapshotUnpinned () { this._unpinSnapshot() this._hideSnapshot() reporterBus.emit('reporter:snapshot:unpinned') }, _unpinSnapshot () { localBus.emit('unpin:snapshot') }, _hideSnapshot () { localBus.emit('hide:snapshot') }, launchBrowser (browser) { ws.emit('reload:browser', window.location.toString(), browser && browser.name) }, // clear all the cypress specific cookies // whenever our app starts // and additional when we stop running our tests _clearAllCookies () { if (!Cypress) return Cypress.Cookies.clearCypressCookies() }, _setUnload () { if (!Cypress) return Cypress.Cookies.setCy('unload', true) }, saveState (state) { ws.emit('save:app:state', state) }, } export default eventManager