diff --git a/packages/server/index.js b/packages/server/index.js index 96f51f467a..19a22cf49b 100644 --- a/packages/server/index.js +++ b/packages/server/index.js @@ -34,7 +34,7 @@ process.noDeprecation = process.env.CYPRESS_INTERNAL_ENV === 'production' // always show stack traces for Electron deprecation warnings process.traceDeprecation = true -require('./lib/util/suppress_unauthorized_warning').suppress() +require('./lib/util/suppress_warnings').suppress() function launchOrFork () { const nodeOptions = require('./lib/util/node_options') diff --git a/packages/server/lib/browsers/electron.js b/packages/server/lib/browsers/electron.js index 54cd0f88ae..febeb27542 100644 --- a/packages/server/lib/browsers/electron.js +++ b/packages/server/lib/browsers/electron.js @@ -235,7 +235,7 @@ module.exports = { return this._enableDebugger(win.webContents) }) .then(() => { - return this._handleDownloads(win.webContents, options.downloadsFolder, automation) + return this._handleDownloads(win, options.downloadsFolder, automation) }) .return(win) }, @@ -291,8 +291,8 @@ module.exports = { return webContents.debugger.sendCommand('Console.enable') }, - _handleDownloads (webContents, dir, automation) { - webContents.session.on('will-download', (event, downloadItem) => { + _handleDownloads (win, dir, automation) { + const onWillDownload = (event, downloadItem) => { const savePath = path.join(dir, downloadItem.getFilename()) automation.push('create:download', { @@ -307,9 +307,16 @@ module.exports = { id: downloadItem.getETag(), }) }) - }) + } - return webContents.debugger.sendCommand('Page.setDownloadBehavior', { + const { session } = win.webContents + + session.on('will-download', onWillDownload) + + // avoid adding redundant `will-download` handlers if session is reused for next spec + win.on('closed', () => session.removeListener('will-download', onWillDownload)) + + return win.webContents.debugger.sendCommand('Page.setDownloadBehavior', { behavior: 'allow', downloadPath: dir, }) diff --git a/packages/server/lib/plugins/child/index.js b/packages/server/lib/plugins/child/index.js index 224259ce09..9687e28819 100644 --- a/packages/server/lib/plugins/child/index.js +++ b/packages/server/lib/plugins/child/index.js @@ -1,6 +1,6 @@ require('graceful-fs').gracefulify(require('fs')) -require('../../util/suppress_unauthorized_warning').suppress() +require('../../util/suppress_warnings').suppress() const ipc = require('../util').wrapIpc(process) const { file: pluginsFile, projectRoot } = require('minimist')(process.argv.slice(2)) diff --git a/packages/server/lib/plugins/util.js b/packages/server/lib/plugins/util.js index ebb0e2a39e..29182c142a 100644 --- a/packages/server/lib/plugins/util.js +++ b/packages/server/lib/plugins/util.js @@ -19,6 +19,10 @@ module.exports = { return emitter.emit(message.event, ...message.args) }) + // prevent max listeners warning on ipc + // @see https://github.com/cypress-io/cypress/issues/1305#issuecomment-780895569 + emitter.setMaxListeners(Infinity) + return { send (event, ...args) { if (aProcess.killed) { diff --git a/packages/server/lib/util/suppress_unauthorized_warning.js b/packages/server/lib/util/suppress_warnings.js similarity index 62% rename from packages/server/lib/util/suppress_unauthorized_warning.js rename to packages/server/lib/util/suppress_warnings.js index 7cde591898..964ef170ed 100644 --- a/packages/server/lib/util/suppress_unauthorized_warning.js +++ b/packages/server/lib/util/suppress_warnings.js @@ -1,6 +1,7 @@ const _ = require('lodash') +const Debug = require('debug') -const originalEmitWarning = process.emitWarning +const debug = Debug('cypress:server:lib:util:suppress_warnings') let suppressed = false @@ -10,7 +11,8 @@ let suppressed = false * https://github.com/cypress-io/cypress/issues/5248 */ const suppress = () => { - if (suppressed) { + if (suppressed || process.env.CYPRESS_INTERNAL_ENV === 'production') { + // in development, still log warnings since they are helpful return } @@ -18,7 +20,7 @@ const suppress = () => { process.emitWarning = (warning, type, code, ...args) => { if (_.isString(warning) && _.includes(warning, 'NODE_TLS_REJECT_UNAUTHORIZED')) { - // https://github.com/nodejs/node/blob/82f89ec8c1554964f5029fab1cf0f4fad1fa55a8/lib/_tls_wrap.js#L1378-L1384 + // https://github.com/nodejs/node/blob/85e6089c4db4da23dd88358fe0a12edefcd411f2/lib/internal/options.js#L17 return } @@ -31,7 +33,7 @@ const suppress = () => { return } - return originalEmitWarning.call(process, warning, type, code, ...args) + debug('suppressed emitWarning from node: %o', { process, warning, type, code, args }) } } diff --git a/packages/server/test/e2e/0_max_listeners_spec.ts b/packages/server/test/e2e/0_max_listeners_spec.ts new file mode 100644 index 0000000000..acc964f332 --- /dev/null +++ b/packages/server/test/e2e/0_max_listeners_spec.ts @@ -0,0 +1,33 @@ +import _ from 'lodash' +import path from 'path' +import fs from 'fs-extra' + +import e2e from '../support/helpers/e2e' +import Fixtures from '../support/helpers/fixtures' + +const projectPath = Fixtures.projectPath('max-listeners') + +describe('max listeners warning spec', () => { + e2e.setup() + + // @see https://github.com/cypress-io/cypress/issues/1305 + e2e.it('does not log MaxEventListeners error', { + browser: 'electron', + project: projectPath, + spec: '*', + onRun: async (exec) => { + const integrationPath = path.join(projectPath, 'cypress/integration') + + // create a bunch of dummy test files to reproduce #1305 + await fs.mkdirp(integrationPath) + await Promise.all( + _.times(15, (i) => fs.writeFile(path.join(integrationPath, `${i}.spec.js`), `it('test', () => {})`)), + ) + + const { stderr } = await exec() + + expect(stderr).to.not.include('setMaxListeners') + expect(stderr).to.not.include('preprocessor:close') + }, + }) +}) diff --git a/packages/server/test/integration/cypress_spec.js b/packages/server/test/integration/cypress_spec.js index ef3ed30e1d..cc49cb3dd0 100644 --- a/packages/server/test/integration/cypress_spec.js +++ b/packages/server/test/integration/cypress_spec.js @@ -1143,6 +1143,7 @@ describe('lib/cypress', () => { setProxy: sinon.stub().resolves(), setUserAgent: sinon.stub(), on: sinon.stub(), + removeListener: sinon.stub(), }, } diff --git a/packages/server/test/support/fixtures/projects/max-listeners/cypress.json b/packages/server/test/support/fixtures/projects/max-listeners/cypress.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/packages/server/test/support/fixtures/projects/max-listeners/cypress.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/packages/server/test/support/helpers/e2e.ts b/packages/server/test/support/helpers/e2e.ts index 762bb0235f..362375a2c3 100644 --- a/packages/server/test/support/helpers/e2e.ts +++ b/packages/server/test/support/helpers/e2e.ts @@ -416,7 +416,6 @@ const e2e = { if (process.env.NO_EXIT) { Fixtures.scaffoldWatch() - process.env.CYPRESS_INTERNAL_E2E_TESTS } sinon.stub(process, 'exit') diff --git a/packages/server/test/unit/suppress_unauthorized_warning_spec.ts b/packages/server/test/unit/suppress_warnings_spec.ts similarity index 87% rename from packages/server/test/unit/suppress_unauthorized_warning_spec.ts rename to packages/server/test/unit/suppress_warnings_spec.ts index b76b26fe02..7509cf48da 100644 --- a/packages/server/test/unit/suppress_unauthorized_warning_spec.ts +++ b/packages/server/test/unit/suppress_warnings_spec.ts @@ -6,9 +6,9 @@ import proxyquire from 'proxyquire' const ERROR_MESSAGE = 'Setting the NODE_TLS_REJECT_UNAUTHORIZED' const TLS_CONNECT = `require('tls').connect().on('error', ()=>{});` -const SUPPRESS_WARNING = `require('${__dirname}/../../lib/util/suppress_unauthorized_warning').suppress();` +const SUPPRESS_WARNING = `require('${__dirname}/../../lib/util/suppress_warnings').suppress();` -describe('lib/util/suppress_unauthorized_warning', function () { +describe('lib/util/suppress_warnings', function () { it('tls.connect emits warning if NODE_TLS_REJECT_UNAUTHORIZED=0 and not suppressed', function () { return execa.shell(`node -e "${TLS_CONNECT}"`, { env: { @@ -36,7 +36,7 @@ describe('lib/util/suppress_unauthorized_warning', function () { const emitWarning = sinon.spy(process, 'emitWarning') // force typescript to always be non-requireable - const { suppress } = proxyquire('../../lib/util/suppress_unauthorized_warning', {}) + const { suppress } = proxyquire('../../lib/util/suppress_warnings', {}) suppress()