diff --git a/npm/angular/cypress.config.ts b/npm/angular/cypress.config.ts index b445b0ffa2d..69e14d50a43 100644 --- a/npm/angular/cypress.config.ts +++ b/npm/angular/cypress.config.ts @@ -10,5 +10,14 @@ export default defineConfig({ 'componentFolder': 'src/app', 'testFiles': '**/*cy-spec.ts', 'setupNodeEvents': require('./cypress/plugins'), + devServer (cypressConfig) { + const { startDevServer } = require('@cypress/webpack-dev-server') + const webpackConfig = require('./cypress/plugins/webpack.config') + + return startDevServer({ + options: cypressConfig, + webpackConfig, + }) + }, }, }) diff --git a/npm/angular/cypress/plugins/index.ts b/npm/angular/cypress/plugins/index.ts index 19515cec728..703cf4278fe 100644 --- a/npm/angular/cypress/plugins/index.ts +++ b/npm/angular/cypress/plugins/index.ts @@ -1,16 +1,7 @@ import { addMatchImageSnapshotPlugin } from 'cypress-image-snapshot/plugin' -import * as webpackConfig from './webpack.config' module.exports = (on, config) => { addMatchImageSnapshotPlugin(on, config) - const { startDevServer } = require('@cypress/webpack-dev-server') - - on('dev-server:start', (options) => { - return startDevServer({ - options, - webpackConfig, - }) - }) require('@cypress/code-coverage/task')(on, config) diff --git a/npm/design-system/cypress.config.js b/npm/design-system/cypress.config.js index 9305503c4b3..ca69fe413ff 100644 --- a/npm/design-system/cypress.config.js +++ b/npm/design-system/cypress.config.js @@ -14,12 +14,10 @@ module.exports = { componentFolder: 'src', fixturesFolder: false, component: { - setupNodeEvents (on, config) { + devServer (cypressConfig) { const { startDevServer } = require('@cypress/vite-dev-server') - on('dev-server:start', (options) => startDevServer({ options })) - - return config + return startDevServer({ options: cypressConfig }) }, }, } diff --git a/npm/react/cypress.config.js b/npm/react/cypress.config.js index 45d6630bd58..e6c48059139 100644 --- a/npm/react/cypress.config.js +++ b/npm/react/cypress.config.js @@ -15,7 +15,7 @@ module.exports = { ], 'experimentalFetchPolyfill': true, 'component': { - setupNodeEvents (on, config) { + devServer (cypressConfig, devServerConfig) { const { startDevServer } = require('@cypress/webpack-dev-server') const path = require('path') const babelConfig = require('./babel.config.js') @@ -73,11 +73,7 @@ module.exports = { }, } - on('dev-server:start', (options) => { - return startDevServer({ options, webpackConfig, disableLazyCompilation: false }) - }) - - return config + return startDevServer({ options: cypressConfig, disableLazyCompilation: false, webpackConfig }) }, }, } diff --git a/npm/react/examples/find-webpack/cypress.config.ts b/npm/react/examples/find-webpack/cypress.config.ts index c373fc2f894..6d3c4bb3aee 100644 --- a/npm/react/examples/find-webpack/cypress.config.ts +++ b/npm/react/examples/find-webpack/cypress.config.ts @@ -6,7 +6,7 @@ export default defineConfig({ 'component': { 'testFiles': '**/*.spec.{js,ts,jsx,tsx}', 'componentFolder': 'src', - setupNodeEvents (on, config) { + devServer (cypressConfig) { const findReactScriptsWebpackConfig = require('@cypress/react/plugins/react-scripts/findReactScriptsWebpackConfig') const { startDevServer } = require('@cypress/webpack-dev-server') const _ = require('lodash') @@ -14,17 +14,17 @@ export default defineConfig({ const map = _.map([4, 8], (n) => n * 2) console.log(map) - require('@cypress/code-coverage/task')(on, config) - const webpackConfig = findReactScriptsWebpackConfig(config) + const webpackConfig = findReactScriptsWebpackConfig(cypressConfig.config) const rules = webpackConfig.module.rules.find((rule) => !!rule.oneOf).oneOf const babelRule = rules.find((rule) => typeof rule.loader === 'string' && /babel-loader/.test(rule.loader)) typeof babelRule.options !== 'string' && babelRule.options.plugins.push(require.resolve('babel-plugin-istanbul')) - on('dev-server:start', (options) => { - return startDevServer({ options, webpackConfig }) - }) + return startDevServer({ options: cypressConfig, webpackConfig }) + }, + setupNodeEvents (on, config) { + require('@cypress/code-coverage/task')(on, config) // IMPORTANT to return the config object // with the any changed environment variables diff --git a/npm/react/examples/webpack-options/cypress.config.js b/npm/react/examples/webpack-options/cypress.config.js index 7cb6fd22725..2f184291aba 100644 --- a/npm/react/examples/webpack-options/cypress.config.js +++ b/npm/react/examples/webpack-options/cypress.config.js @@ -5,7 +5,7 @@ module.exports = { 'viewportWidth': 500, 'viewportHeight': 500, 'component': { - setupNodeEvents (on, config) { + devServer (cypressConfig) { const path = require('path') const { startDevServer } = require('@cypress/webpack-dev-server') const babelConfig = require('./babel.config') @@ -34,10 +34,8 @@ module.exports = { } process.env.BABEL_ENV = 'test' // this is required to load commonjs babel plugin - on('dev-server:start', (options) => startDevServer({ options, webpackConfig })) - // if adding code coverage, important to return updated config - return config + return startDevServer({ options: cypressConfig, webpackConfig }) }, }, } diff --git a/npm/vite-dev-server/cypress.config.ts b/npm/vite-dev-server/cypress.config.ts index 0cfe9b4257f..d315c4aab2e 100644 --- a/npm/vite-dev-server/cypress.config.ts +++ b/npm/vite-dev-server/cypress.config.ts @@ -7,20 +7,16 @@ export default defineConfig({ 'testFiles': '**/*.spec.*', 'componentFolder': 'cypress/components', 'component': { - setupNodeEvents (on, config) { + devServer (cypressConfig) { const path = require('path') const { startDevServer } = require('./dist') - on('dev-server:start', async (options) => { - return startDevServer({ - options, - viteConfig: { - configFile: path.resolve(__dirname, 'vite.config.ts'), - }, - }) + return startDevServer({ + options: cypressConfig, + viteConfig: { + configFile: path.resolve(__dirname, 'vite.config.ts'), + }, }) - - return config }, }, }) diff --git a/npm/vue/cypress.config.ts b/npm/vue/cypress.config.ts index cc8d4831aa7..8e8bb3b69cb 100644 --- a/npm/vue/cypress.config.ts +++ b/npm/vue/cypress.config.ts @@ -9,7 +9,7 @@ export default defineConfig({ 'testFiles': '**/*spec.{js,ts,tsx}', 'experimentalFetchPolyfill': true, 'component': { - setupNodeEvents (on, config) { + devServer (cypressConfig) { const { startDevServer } = require('@cypress/webpack-dev-server') const webpackConfig = require('./webpack.config') @@ -22,8 +22,10 @@ export default defineConfig({ '@vue/compiler-core$': '@vue/compiler-core/dist/compiler-core.cjs.js', } + return startDevServer({ options: cypressConfig, webpackConfig }) + }, + setupNodeEvents (on, config) { require('@cypress/code-coverage/task')(on, config) - on('dev-server:start', (options) => startDevServer({ options, webpackConfig })) return config }, diff --git a/npm/vue/examples/code-coverage/cypress.config.js b/npm/vue/examples/code-coverage/cypress.config.js index 03ef54daced..ce8c5fdab74 100644 --- a/npm/vue/examples/code-coverage/cypress.config.js +++ b/npm/vue/examples/code-coverage/cypress.config.js @@ -4,17 +4,16 @@ module.exports = { 'testFiles': '**/*.spec.js', 'video': false, 'component': { - setupNodeEvents (on, config) { + devServer (cypressConfig) { const { startDevServer } = require('@cypress/webpack-dev-server') const webpackConfig = require('./webpack.config') - on('dev-server:start', (options) => { - return startDevServer({ - options, - webpackConfig, - }) + return startDevServer({ + options: cypressConfig, + webpackConfig, }) - + }, + setupNodeEvents (on, config) { require('@cypress/code-coverage/task')(on, config) return config diff --git a/npm/vue/examples/vue-cli/cypress.config.js b/npm/vue/examples/vue-cli/cypress.config.js index 14175e60c88..c2a7c127e7b 100644 --- a/npm/vue/examples/vue-cli/cypress.config.js +++ b/npm/vue/examples/vue-cli/cypress.config.js @@ -4,29 +4,28 @@ module.exports = { 'testFiles': '**/*spec.js', 'componentFolder': 'src', 'component': { - setupNodeEvents (on, config) { + devServer (cypressConfig) { const { startDevServer } = require('@cypress/webpack-dev-server') const webpackConfig = require('@vue/cli-service/webpack.config') - on('dev-server:start', (options) => { - // HtmlPwaPlugin is coupled to a hook in HtmlWebpackPlugin - // that was deprecated after 3.x. We currently only support - // HtmlWebpackPlugin 4.x and 5.x. - // TODO: Figure out how to deal with 2 major versions old HtmlWebpackPlugin - // which is still in widespread usage. - const modifiedWebpackConfig = { - ...webpackConfig, - plugins: (webpackConfig.plugins || []).filter((x) => { - return x.constructor.name !== 'HtmlPwaPlugin' - }), - } + // HtmlPwaPlugin is coupled to a hook in HtmlWebpackPlugin + // that was deprecated after 3.x. We currently only support + // HtmlWebpackPlugin 4.x and 5.x. + // TODO: Figure out how to deal with 2 major versions old HtmlWebpackPlugin + // which is still in widespread usage. + const modifiedWebpackConfig = { + ...webpackConfig, + plugins: (webpackConfig.plugins || []).filter((x) => { + return x.constructor.name !== 'HtmlPwaPlugin' + }), + } - return startDevServer({ - options, - webpackConfig: modifiedWebpackConfig, - }) + return startDevServer({ + options: cypressConfig, + webpackConfig: modifiedWebpackConfig, }) - + }, + setupNodeEvents (on, config) { require('@cypress/code-coverage/task')(on, config) return config diff --git a/packages/app/cypress.config.ts b/packages/app/cypress.config.ts index 3aad293d45f..11f3cef11d5 100644 --- a/packages/app/cypress.config.ts +++ b/packages/app/cypress.config.ts @@ -20,28 +20,24 @@ export default defineConfig({ 'component': { 'testFiles': '**/*.{spec,cy}.{js,ts,tsx,jsx}', 'supportFile': 'cypress/component/support/index.ts', - setupNodeEvents (on, config) { + devServer (cypressConfig, devServerConfig) { const { startDevServer } = require('@cypress/vite-dev-server') - on('dev-server:start', async (options) => { - return startDevServer({ - options, - viteConfig: { - // TODO(tim): Figure out why this isn't being picked up - optimizeDeps: { - include: [ - '@headlessui/vue', - 'vue3-file-selector', - 'just-my-luck', - 'combine-properties', - 'faker', - ], - }, - }, - }) - }) - - return config + return startDevServer({ options: cypressConfig, ...devServerConfig }) + }, + devServerConfig: { + viteConfig: { + // TODO(tim): Figure out why this isn't being picked up + optimizeDeps: { + include: [ + '@headlessui/vue', + 'vue3-file-selector', + 'just-my-luck', + 'combine-properties', + 'faker', + ], + }, + }, }, }, 'e2e': { diff --git a/packages/data-context/src/sources/WizardDataSource.ts b/packages/data-context/src/sources/WizardDataSource.ts index eeb0b012c60..82e9f6bcb42 100644 --- a/packages/data-context/src/sources/WizardDataSource.ts +++ b/packages/data-context/src/sources/WizardDataSource.ts @@ -267,14 +267,12 @@ const getFrameworkConfigFile = (opts: GetCodeOptsCt) => { const { getWebpackConfig } = require('nuxt') module.exports = { - component (on, config) { - on('dev-server:start', async (options) => { - let webpackConfig = await getWebpackConfig('modern', 'dev') + async devServer (cypressConfig, devServerConfig) { + let webpackConfig = await getWebpackConfig('modern', 'dev') - return startDevServer({ - options, - webpackConfig, - }) + return startDevServer({ + options, + webpackConfig, }) }, } diff --git a/packages/desktop-gui/cypress.config.js b/packages/desktop-gui/cypress.config.js index 0c6be76f2f8..a390853f445 100644 --- a/packages/desktop-gui/cypress.config.js +++ b/packages/desktop-gui/cypress.config.js @@ -32,16 +32,11 @@ module.exports = { }, }, 'component': { - setupNodeEvents (on, config) { + devServer (cypressConfig) { const { startDevServer } = require('@cypress/webpack-dev-server') - const webpackConfig = require('./webpack.config').default - on('dev-server:start', (options) => { - return startDevServer({ options, webpackConfig }) - }) - - return config + return startDevServer({ options: cypressConfig, webpackConfig }) }, }, } diff --git a/packages/frontend-shared/cypress.config.ts b/packages/frontend-shared/cypress.config.ts index 99a4a998dce..af7f47d2b45 100644 --- a/packages/frontend-shared/cypress.config.ts +++ b/packages/frontend-shared/cypress.config.ts @@ -18,33 +18,13 @@ export default defineConfig({ 'componentFolder': 'src', 'component': { 'testFiles': '**/*.spec.{js,ts,tsx,jsx}', - setupNodeEvents (on, config) { + devServer (cypressConfig) { const { startDevServer } = require('@cypress/vite-dev-server') - // `on` is used to hook into various events Cypress emits - // `config` is the resolved Cypress config - - if (config.testingType === 'component') { - on('dev-server:start', async (options) => { - return startDevServer({ - options, - viteConfig: { - // TODO(tim): Figure out why this isn't being picked up - optimizeDeps: { - include: [ - '@headlessui/vue', - 'vue3-file-selector', - 'just-my-luck', - 'combine-properties', - 'faker', - ], - }, - }, - }) - }) - } - - return config // IMPORTANT to return a config + return startDevServer({ + options: cypressConfig, + viteConfig: require('./vite.config'), + }) }, }, 'e2e': { diff --git a/packages/launchpad/cypress.config.ts b/packages/launchpad/cypress.config.ts index 47edecf21cd..a83595acb73 100644 --- a/packages/launchpad/cypress.config.ts +++ b/packages/launchpad/cypress.config.ts @@ -1,5 +1,4 @@ import { defineConfig } from 'cypress' -import { e2ePluginSetup } from '@packages/frontend-shared/cypress/e2e/e2ePluginSetup' export default defineConfig({ 'projectId': 'sehy69', @@ -20,33 +19,13 @@ export default defineConfig({ 'testFiles': '**/*.spec.{js,ts,tsx,jsx}', 'supportFile': 'cypress/component/support/index.ts', 'pluginsFile': 'cypress/component/plugins/index.js', - setupNodeEvents (on, config) { + devServer (cypressConfig) { const { startDevServer } = require('@cypress/vite-dev-server') - // `on` is used to hook into various events Cypress emits - // `config` is the resolved Cypress config - - if (config.testingType === 'component') { - on('dev-server:start', async (options) => { - return startDevServer({ - options, - viteConfig: { - // TODO(tim): Figure out why this isn't being picked up - optimizeDeps: { - include: [ - '@headlessui/vue', - 'vue3-file-selector', - 'just-my-luck', - 'combine-properties', - 'faker', - ], - }, - }, - }) - }) - } - - return config // IMPORTANT to return a config + return startDevServer({ + options: cypressConfig, + viteConfig: require('./vite.config'), + }) }, }, 'e2e': { @@ -54,6 +33,7 @@ export default defineConfig({ 'integrationFolder': 'cypress/e2e/integration', 'pluginsFile': 'cypress/e2e/plugins/index.ts', async setupNodeEvents (on, config) { + const { e2ePluginSetup } = require('@packages/frontend-shared/cypress/e2e/e2ePluginSetup') const { monorepoPaths } = require('../../scripts/gulp/monorepoPaths') return await e2ePluginSetup(monorepoPaths.pkgLaunchpad, on, config) diff --git a/packages/runner-ct/cypress.config.ts b/packages/runner-ct/cypress.config.ts index de46538eb84..af458bded39 100644 --- a/packages/runner-ct/cypress.config.ts +++ b/packages/runner-ct/cypress.config.ts @@ -12,7 +12,7 @@ export default defineConfig({ }, component: { testFiles: '**/*spec.{ts,tsx}', - setupNodeEvents (on, config) { + devServer (cypressConfig) { const { startDevServer } = require('@cypress/webpack-dev-server') function injectStylesInlineForPercyInPlace (webpackConfig) { @@ -30,18 +30,14 @@ export default defineConfig({ }) } - on('dev-server:start', (options) => { - const { default: webpackConfig } = require('./webpack.config.ts') + const { default: webpackConfig } = require('./webpack.config.ts') - injectStylesInlineForPercyInPlace(webpackConfig) + injectStylesInlineForPercyInPlace(webpackConfig) - return startDevServer({ - webpackConfig, - options, - }) + return startDevServer({ + webpackConfig, + options: cypressConfig, }) - - return config }, }, }) diff --git a/packages/server/__snapshots__/run_plugins_spec.js b/packages/server/__snapshots__/run_plugins_spec.js deleted file mode 100644 index 17b05c9ab8e..00000000000 --- a/packages/server/__snapshots__/run_plugins_spec.js +++ /dev/null @@ -1,3 +0,0 @@ -exports['lib/plugins/child/run_plugins sends error message if setupNodeEvents is not a function 1'] = ` -plugins-file -` diff --git a/packages/server/lib/errors.js b/packages/server/lib/errors.js index c3f84191855..8c2910d9a3c 100644 --- a/packages/server/lib/errors.js +++ b/packages/server/lib/errors.js @@ -575,6 +575,23 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { It exported:` + return { msg, details: JSON.stringify(arg2) } + case 'SETUP_NODE_EVENTS_DO_NOT_SUPPORT_DEV_SERVER': + msg = stripIndent`\ + The \`setupNodeEvents\` method do not support \`dev-server:start\`, use \`devServer\` instead: + + \`\`\` + devServer (cypressConfig, devServerConfig) { + // configure plugins here + } + \`\`\` + + Learn more: https://on.cypress.io/plugins-api + + We loaded the \`setupNodeEvents\` from: \`${arg1}\` + + It exported:` + return { msg, details: JSON.stringify(arg2) } case 'PLUGINS_FUNCTION_ERROR': msg = stripIndent`\ diff --git a/packages/server/lib/plugins/child/run_plugins.js b/packages/server/lib/plugins/child/run_plugins.js index fa3984a65bf..f0e59e96881 100644 --- a/packages/server/lib/plugins/child/run_plugins.js +++ b/packages/server/lib/plugins/child/run_plugins.js @@ -15,155 +15,158 @@ const validateEvent = require('./validate_event') let registeredEventsById = {} let registeredEventsByName = {} -const invoke = (eventId, args = []) => { - const event = registeredEventsById[eventId] - - return event.handler(...args) -} - -const getDefaultPreprocessor = function (config) { - const tsPath = resolve.typescript(config.projectRoot) - const options = { - typescript: tsPath, +class RunPlugins { + constructor (ipc, projectRoot, requiredFile) { + this.ipc = ipc + this.projectRoot = projectRoot + this.requiredFile = requiredFile + this.eventIdCount = 0 + this.registrations = [] } - debug('creating webpack preprocessor with options %o', options) + invoke (eventId, args = []) { + const event = registeredEventsById[eventId] - const webpackPreprocessor = require('@cypress/webpack-batteries-included-preprocessor') + return event.handler(...args) + } - return webpackPreprocessor(options) -} + getDefaultPreprocessor (config) { + const tsPath = resolve.typescript(config.projectRoot) + const options = { + typescript: tsPath, + } -let setupNodeEvents + debug('creating webpack preprocessor with options %o', options) -const load = (ipc, config, requiredFile) => { - debug('run plugins function') + const webpackPreprocessor = require('@cypress/webpack-batteries-included-preprocessor') - let eventIdCount = 0 - const registrations = [] + return webpackPreprocessor(options) + } - // we track the register calls and then send them all at once - // to the parent process - const register = (event, handler) => { - const { isValid, error } = validateEvent(event, handler, config) + load (config, setupNodeEvents) { + debug('run plugins function') - if (!isValid) { - ipc.send('load:error:plugins', 'PLUGINS_VALIDATION_ERROR', requiredFile, error.stack) + // we track the register calls and then send them all at once + // to the parent process + const register = (event, handler) => { + const { isValid, error } = validateEvent(event, handler, config) - return - } + if (!isValid) { + this.ipc.send('load:error:plugins', 'PLUGINS_VALIDATION_ERROR', this.requiredFile, error.stack) - if (event === 'task') { - const existingEventId = registeredEventsByName[event] + return + } - if (existingEventId) { - handler = task.merge(registeredEventsById[existingEventId].handler, handler) - registeredEventsById[existingEventId] = { event, handler } - debug('extend task events with id', existingEventId) + if (event === 'dev-server:start' && registeredEventsByName[event]) { + this.ipc.send('load:error:plugins', 'SETUP_NODE_EVENTS_DO_NOT_SUPPORT_DEV_SERVER', this.requiredFile) return } - } - const eventId = eventIdCount++ + if (event === 'task') { + const existingEventId = registeredEventsByName[event] - registeredEventsById[eventId] = { event, handler } - registeredEventsByName[event] = eventId + if (existingEventId) { + handler = task.merge(registeredEventsById[existingEventId].handler, handler) + registeredEventsById[existingEventId] = { event, handler } + debug('extend task events with id', existingEventId) - debug('register event', event, 'with id', eventId) + return + } + } - registrations.push({ - event, - eventId, - }) - } + const eventId = this.eventIdCount++ - // events used for parent/child communication - register('_get:task:body', () => {}) - register('_get:task:keys', () => {}) + registeredEventsById[eventId] = { event, handler } + registeredEventsByName[event] = eventId - Promise - .try(() => { - debug('run plugins function') + debug('register event', event, 'with id', eventId) - return setupNodeEvents(register, config) - }) - .tap(() => { - if (!registeredEventsByName['file:preprocessor']) { - debug('register default preprocessor') - register('file:preprocessor', getDefaultPreprocessor(config)) + this.registrations.push({ + event, + eventId, + }) } - }) - .then((modifiedCfg) => { - debug('plugins file successfully loaded') - ipc.send('loaded:plugins', modifiedCfg, registrations) - }) - .catch((err) => { - debug('plugins file errored:', err && err.stack) - ipc.send('load:error:plugins', 'PLUGINS_FUNCTION_ERROR', err.stack) - }) -} -const execute = (ipc, event, ids, args = []) => { - debug(`execute plugin event: ${event} (%o)`, ids) + // events used for parent/child communication + register('_get:task:body', () => {}) + register('_get:task:keys', () => {}) - const wrapChildPromise = () => { - util.wrapChildPromise(ipc, invoke, ids, args) - } + Promise + .try(() => { + debug('run plugins function') - switch (event) { - case 'dev-server:start': - return devServer.wrap(ipc, invoke, ids, args) - case 'file:preprocessor': - return preprocessor.wrap(ipc, invoke, ids, args) - case 'before:run': - case 'before:spec': - case 'after:run': - case 'after:spec': - case 'after:screenshot': - return wrapChildPromise() - case 'task': - return task.wrap(ipc, registeredEventsById, ids, args) - case '_get:task:keys': - return task.getKeys(ipc, registeredEventsById, ids) - case '_get:task:body': - return task.getBody(ipc, registeredEventsById, ids, args) - case 'before:browser:launch': - return browserLaunch.wrap(ipc, invoke, ids, args) - default: - debug('unexpected execute message:', event, args) - - return + return setupNodeEvents(register, config) + }) + .tap(() => { + if (!registeredEventsByName['file:preprocessor']) { + debug('register default preprocessor') + register('file:preprocessor', this.getDefaultPreprocessor(config)) + } + }) + .then((modifiedCfg) => { + debug('plugins file successfully loaded') + this.ipc.send('loaded:plugins', modifiedCfg, this.registrations) + }) + .catch((err) => { + debug('plugins file errored:', err && err.stack) + this.ipc.send('load:error:plugins', 'PLUGINS_FUNCTION_ERROR', err.stack) + }) } -} -const runSetupNodeEvents = (ipc, _setupNodeEvents, projectRoot, requiredFile) => { - if (_setupNodeEvents && typeof _setupNodeEvents !== 'function') { - ipc.send('load:error:plugins', 'SETUP_NODE_EVENTS_IS_NOT_FUNCTION', requiredFile, _setupNodeEvents) - } + execute (event, ids, args = []) { + debug(`execute plugin event: ${event} (%o)`, ids) + + const wrapChildPromise = () => { + util.wrapChildPromise(this.ipc, this.invoke, ids, args) + } - // Set a default handler to successfully register `file:preprocessor` - setupNodeEvents = _setupNodeEvents ?? ((on, config) => {}) + switch (event) { + case 'dev-server:start': + return devServer.wrap(this.ipc, this.invoke, ids, args) + case 'file:preprocessor': + return preprocessor.wrap(this.ipc, this.invoke, ids, args) + case 'before:run': + case 'before:spec': + case 'after:run': + case 'after:spec': + case 'after:screenshot': + return wrapChildPromise() + case 'task': + return task.wrap(this.ipc, registeredEventsById, ids, args) + case '_get:task:keys': + return task.getKeys(this.ipc, registeredEventsById, ids) + case '_get:task:body': + return task.getBody(this.ipc, registeredEventsById, ids, args) + case 'before:browser:launch': + return browserLaunch.wrap(this.ipc, this.invoke, ids, args) + default: + debug('unexpected execute message:', event, args) - debug('project root:', projectRoot) - if (!projectRoot) { - throw new Error('Unexpected: projectRoot should be a string') + return + } } - ipc.on('load:plugins', (config) => { - debug('passing config %o', config) - load(ipc, config, requiredFile) - }) + runSetupNodeEvents (setupNodeEvents) { + debug('project root:', this.projectRoot) + if (!this.projectRoot) { + throw new Error('Unexpected: projectRoot should be a string') + } - ipc.on('execute:plugins', (event, ids, args) => { - execute(ipc, event, ids, args) - }) -} + this.ipc.on('load:plugins', (config) => { + debug('passing config %o', config) + this.load(config, setupNodeEvents) + }) -// for testing purposes -runSetupNodeEvents.__reset = () => { - registeredEventsById = {} - registeredEventsByName = {} + this.ipc.on('execute:plugins', (event, ids, args) => { + this.execute(event, ids, args) + }) + } + + __reset () { + registeredEventsById = {} + registeredEventsByName = {} + } } -module.exports = runSetupNodeEvents +module.exports = RunPlugins diff --git a/packages/server/lib/util/run_require_async_child.js b/packages/server/lib/util/run_require_async_child.js index a580739cb45..65305822c35 100644 --- a/packages/server/lib/util/run_require_async_child.js +++ b/packages/server/lib/util/run_require_async_child.js @@ -3,7 +3,7 @@ const stripAnsi = require('strip-ansi') const debug = require('debug')('cypress:server:require_async:child') const tsNodeUtil = require('./ts_node') const util = require('../plugins/util') -const runSetupNodeEvents = require('../plugins/child/run_plugins') +const RunPlugins = require('../plugins/child/run_plugins') let tsRegistered = false @@ -47,6 +47,16 @@ function run (ipc, requiredFile, projectRoot) { return false }) + const isValidSetupNodeEvents = (setupNodeEvents) => { + if (setupNodeEvents && typeof setupNodeEvents !== 'function') { + ipc.send('load:error:plugins', 'SETUP_NODE_EVENTS_IS_NOT_FUNCTION', requiredFile, setupNodeEvents) + + return false + } + + return true + } + ipc.on('load', () => { try { debug('try loading', requiredFile) @@ -57,11 +67,31 @@ function run (ipc, requiredFile, projectRoot) { ipc.send('loaded', result) ipc.on('plugins', (testingType) => { + const runPlugins = new RunPlugins(ipc, projectRoot, requiredFile) + areSetupNodeEventsLoaded = true if (testingType === 'component') { - runSetupNodeEvents(ipc, result.component?.setupNodeEvents, projectRoot, requiredFile) + if (!isValidSetupNodeEvents(result.component?.setupNodeEvents)) { + return + } + + runPlugins.runSetupNodeEvents((on, config) => { + if (result.component?.devServer) { + on('dev-server:start', (options) => result.component.devServer(options, result.component?.devServerConfig)) + } + + const setupNodeEvents = result.component?.setupNodeEvents ?? ((on, config) => {}) + + return setupNodeEvents(on, config) + }) } else if (testingType === 'e2e') { - runSetupNodeEvents(ipc, result.e2e?.setupNodeEvents, projectRoot, requiredFile) + if (!isValidSetupNodeEvents(result.e2e?.setupNodeEvents)) { + return + } + + const setupNodeEvents = result.e2e?.setupNodeEvents ?? ((on, config) => {}) + + runPlugins.runSetupNodeEvents(setupNodeEvents) } else { // Notify the plugins init that there's no plugins to resolve ipc.send('empty:plugins') diff --git a/packages/server/test/unit/plugins/child/run_plugins_spec.js b/packages/server/test/unit/plugins/child/run_plugins_spec.js index 5cf70b6edf3..1a5fd828aee 100644 --- a/packages/server/test/unit/plugins/child/run_plugins_spec.js +++ b/packages/server/test/unit/plugins/child/run_plugins_spec.js @@ -1,7 +1,6 @@ require('../../../spec_helper') const _ = require('lodash') -const snapshot = require('snap-shot-it') const Promise = require('bluebird') const preprocessor = require(`${root}../../lib/plugins/child/preprocessor`) @@ -10,7 +9,7 @@ const util = require(`${root}../../lib/plugins/util`) const resolve = require(`${root}../../lib/util/resolve`) const browserUtils = require(`${root}../../lib/browsers/utils`) -const runSetupNodeEvents = require(`${root}../../lib/plugins/child/run_plugins`) +const RunPlugins = require(`${root}../../lib/plugins/child/run_plugins`) const deferred = () => { let reject @@ -24,14 +23,18 @@ const deferred = () => { } describe('lib/plugins/child/run_plugins', () => { - beforeEach(function () { - runSetupNodeEvents.__reset() + let runPlugins + beforeEach(function () { this.ipc = { send: sinon.spy(), on: sinon.stub(), removeListener: sinon.spy(), } + + runPlugins = new RunPlugins(this.ipc, 'proj-root', 'cypress.config.js') + + runPlugins.__reset() }) afterEach(() => { @@ -39,10 +42,32 @@ describe('lib/plugins/child/run_plugins', () => { }) it('sends error message if setupNodeEvents is not a function', function () { - runSetupNodeEvents(this.ipc, 'plugins-file', 'proj-root', 'cypress.config.js') - expect(this.ipc.send).to.be.calledWith('load:error:plugins', 'SETUP_NODE_EVENTS_IS_NOT_FUNCTION', 'cypress.config.js') + const config = { projectRoot: '/project/root' } + + const setupNodeEventsFn = (on, config) => { + on('dev-server:start', (options) => {}) + on('after:screenshot', () => {}) + on('task', {}) + + return config + } + + const foo = ((on, config) => { + on('dev-server:start', (options) => {}) - return snapshot(this.ipc.send.lastCall.args[3].split('\n')[0]) + return setupNodeEventsFn(on, config) + }) + + runPlugins.runSetupNodeEvents(foo) + + this.ipc.on.withArgs('load:plugins').yield(config) + + return Promise + .delay(10) + .then(() => { + expect(this.ipc.send).to.be.calledWith('loaded:plugins', config) + expect(this.ipc.send).to.be.calledWith('load:error:plugins', 'SETUP_NODE_EVENTS_DO_NOT_SUPPORT_DEV_SERVER', 'cypress.config.js') + }) }) describe('on \'load\' message', () => { @@ -57,7 +82,7 @@ describe('lib/plugins/child/run_plugins', () => { return config } - runSetupNodeEvents(this.ipc, setupNodeEventsFn, 'proj-root', 'cypress.config.js') + runPlugins.runSetupNodeEvents(setupNodeEventsFn) this.ipc.on.withArgs('load:plugins').yield(config) @@ -97,7 +122,8 @@ describe('lib/plugins/child/run_plugins', () => { } mockery.registerMock('@cypress/webpack-batteries-included-preprocessor', webpackPreprocessor) - runSetupNodeEvents(this.ipc, setupNodeEventsFn, 'proj-root', 'cypress.config.js') + + runPlugins.runSetupNodeEvents(setupNodeEventsFn) this.ipc.on.withArgs('load:plugins').yield(config) @@ -139,7 +165,7 @@ describe('lib/plugins/child/run_plugins', () => { } mockery.registerMock('@cypress/webpack-batteries-included-preprocessor', webpackPreprocessor) - runSetupNodeEvents(this.ipc, setupNodeEventsFn, 'proj-root', 'cypress.config.js') + runPlugins.runSetupNodeEvents(setupNodeEventsFn) this.ipc.on.withArgs('load:plugins').yield(config) @@ -167,7 +193,7 @@ describe('lib/plugins/child/run_plugins', () => { const setupNodeEventsFn = sinon.stub().rejects(err) this.ipc.on.withArgs('load:plugins').yields({}) - runSetupNodeEvents(this.ipc, setupNodeEventsFn, 'proj-root', 'cypress.config.js') + runPlugins.runSetupNodeEvents(setupNodeEventsFn) this.ipc.send = _.once((event, errorType, stack) => { expect(event).to.eq('load:error:plugins') @@ -181,7 +207,8 @@ describe('lib/plugins/child/run_plugins', () => { it('calls function exported by pluginsFile with register function and config', function () { const setupNodeEventsFn = sinon.spy() - runSetupNodeEvents(this.ipc, setupNodeEventsFn, 'proj-root', 'cypress.config.js') + runPlugins.runSetupNodeEvents(setupNodeEventsFn) + const config = {} this.ipc.on.withArgs('load:plugins').yield(config) @@ -198,7 +225,8 @@ describe('lib/plugins/child/run_plugins', () => { throw err } - runSetupNodeEvents(this.ipc, setupNodeEventsFn, 'proj-root', 'cypress.config.js') + runPlugins.runSetupNodeEvents(setupNodeEventsFn) + this.ipc.on.withArgs('load:plugins').yield({}) this.ipc.send = _.once((event, errorType, stack) => { @@ -226,7 +254,7 @@ describe('lib/plugins/child/run_plugins', () => { return register('task', this.taskRequested) } - runSetupNodeEvents(this.ipc, setupNodeEventsFn, 'proj-root', 'cypress.config.js') + runPlugins.runSetupNodeEvents(setupNodeEventsFn) return this.ipc.on.withArgs('load:plugins').yield({}) }) diff --git a/system-tests/projects/component-tests/cypress.config.js b/system-tests/projects/component-tests/cypress.config.js index 75b63add668..8f1b5df68b0 100644 --- a/system-tests/projects/component-tests/cypress.config.js +++ b/system-tests/projects/component-tests/cypress.config.js @@ -2,17 +2,20 @@ module.exports = { 'projectId': 'abc123', 'componentFolder': 'cypress/component-tests', 'component': { - setupNodeEvents (on, config) { + devServer (cypressConfig, devServerConfig) { const { startDevServer } = require('@cypress/webpack-dev-server') - const webpackConfig = { + return startDevServer({ options: cypressConfig, ...devServerConfig }) + }, + devServerConfig: { + webpackConfig: { output: { publicPath: '/', }, - } - + }, + }, + setupNodeEvents (on, config) { require('@cypress/code-coverage/task')(on, config) - on('dev-server:start', (options) => startDevServer({ options, webpackConfig })) return config }, diff --git a/system-tests/projects/e2e/cypress.config.js b/system-tests/projects/e2e/cypress.config.js index 592503e4c85..ccb424781c2 100644 --- a/system-tests/projects/e2e/cypress.config.js +++ b/system-tests/projects/e2e/cypress.config.js @@ -8,17 +8,19 @@ module.exports = { }, }, 'component': { - setupNodeEvents (on, config) { + devServer (cypressConfig, devServerConfig) { const { startDevServer } = require('@cypress/webpack-dev-server') - const webpackConfig = { + return startDevServer({ options: cypressConfig, ...devServerConfig }) + }, + devServerConfig: { + webpackConfig: { output: { publicPath: '/', }, - } - - on('dev-server:start', (options) => startDevServer({ options, webpackConfig })) - + }, + }, + setupNodeEvents (on, config) { return plugin(on, config) }, }, diff --git a/system-tests/projects/spec-generation/cypress.config.js b/system-tests/projects/spec-generation/cypress.config.js index 4b5f79f5d15..a65b49b7a1f 100644 --- a/system-tests/projects/spec-generation/cypress.config.js +++ b/system-tests/projects/spec-generation/cypress.config.js @@ -1,19 +1,17 @@ module.exports = { 'componentFolder': 'src', - 'e2e': { - setupNodeEvents (on, config) { + 'component': { + devServer (cypressConfig, devServerConfig) { const { startDevServer } = require('@cypress/webpack-dev-server') - const webpackConfig = { + return startDevServer({ options: cypressConfig, ...devServerConfig }) + }, + devServerConfig: { + webpackConfig: { output: { publicPath: '/', }, - } - - require('@cypress/code-coverage/task')(on, config) - on('dev-server:start', (options) => startDevServer({ options, webpackConfig })) - - return config + }, }, }, } diff --git a/system-tests/projects/unify-onboarding-with-config/cypress.config.js b/system-tests/projects/unify-onboarding-with-config/cypress.config.js index 7e9c8f2fc19..408cee5af62 100644 --- a/system-tests/projects/unify-onboarding-with-config/cypress.config.js +++ b/system-tests/projects/unify-onboarding-with-config/cypress.config.js @@ -2,18 +2,16 @@ module.exports = { component: { testFiles: '**/*cy-spec.{js,jsx,ts,tsx}', componentFolder: 'src', - setupNodeEvents (on, config) { + devServer (cypressConfig) { const { startDevServer } = require('@cypress/webpack-dev-server') - const webpackConfig = { - output: { - publicPath: '/', - }, - } - - on('dev-server:start', (options) => startDevServer({ options, webpackConfig })) - - return config + return startDevServer({ + options: cypressConfig, + webpackConfig: { + output: { + publicPath: '/', + }, + } }) }, }, } diff --git a/system-tests/projects/unify-onboarding/cypress.config.js b/system-tests/projects/unify-onboarding/cypress.config.js index cbdaad74f8b..bc43c559634 100644 --- a/system-tests/projects/unify-onboarding/cypress.config.js +++ b/system-tests/projects/unify-onboarding/cypress.config.js @@ -1,17 +1,15 @@ module.exports = { 'component': { - setupNodeEvents (on, config) { + devServer (cypressConfig) { const { startDevServer } = require('@cypress/webpack-dev-server') - const webpackConfig = { - output: { - publicPath: '/', - }, - } - - on('dev-server:start', (options) => startDevServer({ options, webpackConfig })) - - return config + return startDevServer({ + options: cypressConfig, + webpackConfig: { + output: { + publicPath: '/', + }, + } }) }, }, }