diff --git a/cli/schema/cypress.schema.json b/cli/schema/cypress.schema.json index 1eb9c7afa7d8..23a1ad2502a8 100644 --- a/cli/schema/cypress.schema.json +++ b/cli/schema/cypress.schema.json @@ -147,7 +147,7 @@ "string", "boolean" ], - "default": "cypress/support/e2e.js", + "default": "cypress/support/e2e.{js,jsx,ts,tsx}", "description": "Path to file to load before test files load. This file is compiled and bundled. (Pass false to disable)" }, "videosFolder": { diff --git a/npm/angular/cypress.config.ts b/npm/angular/cypress.config.ts index ea2868657ac2..93024d329527 100644 --- a/npm/angular/cypress.config.ts +++ b/npm/angular/cypress.config.ts @@ -7,7 +7,6 @@ export default defineConfig({ 'fileServerFolder': 'src', 'projectId': 'nf7zag', 'component': { - 'supportFile': 'cypress/support/component.ts', setupNodeEvents (on, config) { return require('./cypress/plugins')(on, config) }, diff --git a/npm/react/examples/find-webpack/cypress.config.ts b/npm/react/examples/find-webpack/cypress.config.ts index 1cac074dae15..7d10f2dfd8af 100644 --- a/npm/react/examples/find-webpack/cypress.config.ts +++ b/npm/react/examples/find-webpack/cypress.config.ts @@ -4,7 +4,6 @@ export default defineConfig({ 'video': true, 'projectId': 'jq5xpp', 'component': { - 'supportFile': 'cypress/support/component.ts', devServer (cypressConfig) { const findReactScriptsWebpackConfig = require('@cypress/react/plugins/react-scripts/findReactScriptsWebpackConfig') const { startDevServer } = require('@cypress/webpack-dev-server') diff --git a/npm/react/examples/react-scripts-typescript/cypress.config.js b/npm/react/examples/react-scripts-typescript/cypress.config.js index cd49bac34d2a..1588b4a829b5 100644 --- a/npm/react/examples/react-scripts-typescript/cypress.config.js +++ b/npm/react/examples/react-scripts-typescript/cypress.config.js @@ -5,7 +5,6 @@ module.exports = defineConfig({ 'viewportWidth': 500, 'viewportHeight': 800, 'component': { - 'supportFile': 'cypress/support/component.ts', devServer: require('@cypress/react/plugins/react-scripts'), }, }) diff --git a/packages/config/__snapshots__/index_spec.js b/packages/config/__snapshots__/index_spec.js index e64ab0ed3312..1c418858f677 100644 --- a/packages/config/__snapshots__/index_spec.js +++ b/packages/config/__snapshots__/index_spec.js @@ -58,7 +58,7 @@ exports['src/index .getDefaultValues returns list of public config keys 1'] = { "screenshotsFolder": "cypress/screenshots", "slowTestThreshold": 10000, "scrollBehavior": "top", - "supportFile": "cypress/support/e2e.js", + "supportFile": "cypress/support/e2e.{js,jsx,ts,tsx}", "supportFolder": false, "taskTimeout": 60000, "trashAssetsBeforeRuns": true, diff --git a/packages/config/lib/options.ts b/packages/config/lib/options.ts index 147e3a334d15..c3a69adc91ba 100644 --- a/packages/config/lib/options.ts +++ b/packages/config/lib/options.ts @@ -314,7 +314,7 @@ const resolvedOptions: Array = [ canUpdateDuringTestTime: true, }, { name: 'supportFile', - defaultValue: (options: Record = {}) => options.testingType === 'component' ? 'cypress/support/component.js' : 'cypress/support/e2e.js', + defaultValue: (options: Record = {}) => options.testingType === 'component' ? 'cypress/support/component.{js,jsx,ts,tsx}' : 'cypress/support/e2e.{js,jsx,ts,tsx}', validation: validate.isStringOrFalse, isFolder: true, canUpdateDuringTestTime: false, diff --git a/packages/data-context/src/actions/WizardActions.ts b/packages/data-context/src/actions/WizardActions.ts index c1ed4c84ec0d..c57afded55c9 100644 --- a/packages/data-context/src/actions/WizardActions.ts +++ b/packages/data-context/src/actions/WizardActions.ts @@ -6,7 +6,6 @@ import fs from 'fs' import path from 'path' import type { DataContext } from '..' -import { getDefaultSpecPatterns } from '../util/config-options' interface WizardGetCodeComponent { chosenLanguage: CodeLanguage @@ -144,6 +143,7 @@ export class WizardActions { } private async scaffoldConfig (configCode: string): Promise { + this.ctx.lifecycleManager.setConfigFilePath(this.ctx.coreData.wizard.chosenLanguage) if (!fs.existsSync(this.ctx.lifecycleManager.configFilePath)) { return this.scaffoldFile( this.ctx.lifecycleManager.configFilePath, @@ -196,9 +196,7 @@ export class WizardActions { codeBlocks.push(lang === 'ts' ? `import { defineConfig } from 'cypress'` : `const { defineConfig } = require('cypress')`) codeBlocks.push('') codeBlocks.push(lang === 'ts' ? `export default defineConfig({` : `module.exports = defineConfig({`) - codeBlocks.push(` ${E2E_SCAFFOLD_BODY({ - lang, - }).replace(/\n/g, '\n ')}`) + codeBlocks.push(` ${E2E_SCAFFOLD_BODY.replace(/\n/g, '\n ')}`) codeBlocks.push('})\n') @@ -212,7 +210,7 @@ export class WizardActions { codeBlocks.push(chosenLanguage.type === 'ts' ? `import { defineConfig } from 'cypress'` : `const { defineConfig } = require('cypress')`) codeBlocks.push('') codeBlocks.push(chosenLanguage.type === 'ts' ? `export default defineConfig({` : `module.exports = defineConfig({`) - codeBlocks.push(`// Component testing, ${chosenLanguage.name}, ${chosenFramework.name}, ${chosenBundler.name}`) + codeBlocks.push(` // Component testing, ${chosenLanguage.name}, ${chosenFramework.name}, ${chosenBundler.name}`) codeBlocks.push(` ${COMPONENT_SCAFFOLD_BODY({ lang: chosenLanguage.type, @@ -319,23 +317,13 @@ export class WizardActions { } } -interface E2eScaffoldOpts { - lang: CodeLanguageEnum -} - -const E2E_SCAFFOLD_BODY = (opts: E2eScaffoldOpts) => { - return dedent` +const E2E_SCAFFOLD_BODY = dedent` e2e: { - supportFile: 'cypress/support/e2e.${opts.lang}', - specPattern: '${getDefaultSpecPatterns().e2e}', - viewportHeight: 660, - viewportWidth: 1000, setupNodeEvents(on, config) { // implement node event listeners here }, }, - ` -} +` interface ComponentScaffoldOpts { lang: CodeLanguageEnum @@ -346,13 +334,11 @@ interface ComponentScaffoldOpts { const COMPONENT_SCAFFOLD_BODY = (opts: ComponentScaffoldOpts) => { return dedent` - component: { - supportFile: 'cypress/support/component.${opts.lang}', - specPattern: '${getDefaultSpecPatterns().component}', - devServer: import('${opts.requirePath}'), - devServerConfig: ${opts.configOptionsString} - }, -` + component: { + devServer: import('${opts.requirePath}'), + devServerConfig: ${opts.configOptionsString} + }, + ` } const FIXTURE_DATA = { diff --git a/packages/data-context/src/data/ProjectLifecycleManager.ts b/packages/data-context/src/data/ProjectLifecycleManager.ts index 55676491a23f..ede6a53b928c 100644 --- a/packages/data-context/src/data/ProjectLifecycleManager.ts +++ b/packages/data-context/src/data/ProjectLifecycleManager.ts @@ -1105,7 +1105,7 @@ export class ProjectLifecycleManager { if (fs.existsSync(configFileTs)) { metaState.hasValidConfigFile = true - this._configFilePath = configFileTs + this.setConfigFilePath('ts') } if (fs.existsSync(configFileJs)) { @@ -1113,12 +1113,12 @@ export class ProjectLifecycleManager { if (this._configFilePath) { metaState.hasMultipleConfigPaths = true } else { - this._configFilePath = configFileJs + this.setConfigFilePath('js') } } if (!this._configFilePath) { - this._configFilePath = metaState.hasTypescript ? configFileTs : configFileJs + this.setConfigFilePath(metaState.hasTypescript ? 'ts' : 'js') } if (metaState.hasLegacyCypressJson && !metaState.hasValidConfigFile) { @@ -1130,6 +1130,10 @@ export class ProjectLifecycleManager { return metaState } + setConfigFilePath (lang: 'ts' | 'js') { + this._configFilePath = this._pathToFile(`cypress.config.${lang}`) + } + private _pathToFile (file: string) { return path.isAbsolute(file) ? file : path.join(this.projectRoot, file) } diff --git a/packages/frontend-shared/cypress.config.ts b/packages/frontend-shared/cypress.config.ts index cac62105e600..3fd034bcd5e9 100644 --- a/packages/frontend-shared/cypress.config.ts +++ b/packages/frontend-shared/cypress.config.ts @@ -17,7 +17,6 @@ export default defineConfig({ 'configFile': '../../mocha-reporter-config.json', }, 'component': { - 'supportFile': 'cypress/support/component.ts', devServer (cypressConfig, devServerConfig) { const { startDevServer } = require('@cypress/vite-dev-server') diff --git a/packages/frontend-shared/cypress/e2e/support/e2eProjectDirs.ts b/packages/frontend-shared/cypress/e2e/support/e2eProjectDirs.ts index e1e7aab5863c..06a1dca8bbda 100644 --- a/packages/frontend-shared/cypress/e2e/support/e2eProjectDirs.ts +++ b/packages/frontend-shared/cypress/e2e/support/e2eProjectDirs.ts @@ -31,7 +31,8 @@ export const e2eProjectDirs = [ 'max-listeners', 'migration', 'multiple-task-registrations', - 'multiples-config-files-with-json', + 'multiple-config-files-with-json', + 'multiple-support-files', 'no-scaffolding', 'no-server', 'no-specs-found', diff --git a/packages/launchpad/cypress/e2e/onboarding-flow.cy.ts b/packages/launchpad/cypress/e2e/onboarding-flow.cy.ts index d1ba7e691128..fad08c5f68f1 100644 --- a/packages/launchpad/cypress/e2e/onboarding-flow.cy.ts +++ b/packages/launchpad/cypress/e2e/onboarding-flow.cy.ts @@ -24,15 +24,39 @@ describe('Launchpad: Onboarding Flow', () => { cy.findByText('I\'ve installed them').click() cy.findByText('We added the following files to your project.') cy.findByText('Continue').click() - cy.withCtx(async (ctx) => { + cy.withCtx((ctx) => { return ctx.file.readFileInProject('cypress.config.js') - }).then((str) => { - cy.log(str) }) cy.findByText('Choose a Browser', { timeout: 10000 }) }) + it('can setup component testing with TS', () => { + cy.visitLaunchpad() + cy.get('[data-cy-testingType=component]').click() + cy.get('[data-testid=select-framework]').click() + cy.findByText('React.js').click() + cy.get('[data-testid=select-framework]').should('contain', 'React.js') + cy.get('[data-testid=select-bundler]') + .findByText(cy.i18n.setupPage.projectSetup.bundlerPlaceholder) + .click() + + cy.findByText('Webpack').click() + cy.get('[data-testid=select-bundler]').should('contain', 'Webpack') + cy.reload() + cy.get('[data-testid=select-framework]').should('contain', 'React.js') + cy.get('[data-testid=select-bundler]').should('contain', 'Webpack') + cy.findByText('TypeScript').click() + cy.findByText('Next Step').click() + cy.get('h1').should('contain', 'Dependencies') + cy.findByText('I\'ve installed them').click() + cy.findByText('We added the following files to your project.') + cy.findByText('Continue').click() + cy.withCtx((ctx) => { + return ctx.file.readFileInProject('cypress.config.ts') + }) + }) + it('can setup e2e testing', () => { cy.visitLaunchpad() cy.get('[data-cy-testingType=e2e]').click() diff --git a/packages/reporter/cypress.config.ts b/packages/reporter/cypress.config.ts index 91df2d19797a..98a7a2e291d0 100644 --- a/packages/reporter/cypress.config.ts +++ b/packages/reporter/cypress.config.ts @@ -14,7 +14,6 @@ export default defineConfig({ 'openMode': 0, }, 'e2e': { - 'supportFile': 'cypress/support/e2e.ts', setupNodeEvents (on, config) { const express = require('express') diff --git a/packages/server/lib/config.ts b/packages/server/lib/config.ts index 4b15788f0886..02dcc23becd3 100644 --- a/packages/server/lib/config.ts +++ b/packages/server/lib/config.ts @@ -348,15 +348,29 @@ export const setNodeBinary = (obj, userNodePath, userNodeVersion) => { } // async function -export function setSupportFileAndFolder (obj, defaults) { +export async function setSupportFileAndFolder (obj, defaults) { if (!obj.supportFile) { return Bluebird.resolve(obj) } obj = _.clone(obj) + const ctx = getCtx() + + const supportFilesByGlob = await ctx.file.getFilesByGlob(obj.projectRoot, obj.supportFile, { absolute: false }) + + if (supportFilesByGlob.length > 1) { + return errors.throw('MULTIPLE_SUPPORT_FILES_FOUND', obj.supportFile, supportFilesByGlob.join(', ')) + } + + if (supportFilesByGlob.length === 0) { + const configFile = obj.configFile || defaults.configFile + + return errors.throw('SUPPORT_FILE_NOT_FOUND', path.resolve(obj.projectRoot, obj.supportFile), configFile) + } + // TODO move this logic to find support file into util/path_helpers - const sf = obj.supportFile + const sf = supportFilesByGlob[0] debug(`setting support file ${sf}`) debug(`for project root ${obj.projectRoot}`) diff --git a/packages/server/lib/errors.js b/packages/server/lib/errors.js index 06016b7805fb..30e6bbd987ae 100644 --- a/packages/server/lib/errors.js +++ b/packages/server/lib/errors.js @@ -542,6 +542,15 @@ const getMsgByType = function (type, ...args) { https://on.cypress.io/renderer-process-crashed` case 'AUTOMATION_SERVER_DISCONNECTED': return 'The automation client disconnected. Cannot continue running tests.' + + case 'MULTIPLE_SUPPORT_FILES_FOUND': + return stripIndent`\ + There are multiple support files. + + Your \`supportFile\` is set to \`${arg1}\`, and we found \`${arg2}\`. + + Correct your supportFile config or merge the files into one.` + case 'SUPPORT_FILE_NOT_FOUND': return stripIndent`\ The support file is missing or invalid. diff --git a/packages/server/test/unit/config_spec.js b/packages/server/test/unit/config_spec.js index cf6bd3a0b391..8c03b9d54d03 100644 --- a/packages/server/test/unit/config_spec.js +++ b/packages/server/test/unit/config_spec.js @@ -1997,7 +1997,6 @@ describe('lib/config', () => { context('.setSupportFileAndFolder', () => { const mockSupportDefaults = { - supportFile: 'cypress/support/e2e.ts', supportFolder: false, configFile: 'cypress.json', } diff --git a/system-tests/__snapshots__/config_spec.js b/system-tests/__snapshots__/config_spec.js index 2c8a612b32f1..39ab961dd30b 100644 --- a/system-tests/__snapshots__/config_spec.js +++ b/system-tests/__snapshots__/config_spec.js @@ -176,7 +176,7 @@ Cypress no longer supports 'cypress.json', please migrate to 'cypress.config.{ts exports['e2e config throws error when cypress.json is found in project and cypress.config.{ts|js} exists as well 1'] = ` There is both a \`cypress.config.js\` and a cypress.json file at the location below: -/foo/bar/.projects/multiples-config-files-with-json +/foo/bar/.projects/multiple-config-files-with-json Cypress no longer supports 'cypress.json' config, please remove it from your project. diff --git a/system-tests/__snapshots__/multiple_support_files_spec.js b/system-tests/__snapshots__/multiple_support_files_spec.js new file mode 100644 index 000000000000..208a563ab506 --- /dev/null +++ b/system-tests/__snapshots__/multiple_support_files_spec.js @@ -0,0 +1,8 @@ +exports['e2e multiple support files passes 1'] = ` +There are multiple support files. + +Your \`supportFile\` is set to \`/foo/bar/.projects/multiple-support-files/cypress/support/e2e.{js,jsx,ts,tsx}\`, and we found \`/foo/bar/.projects/multiple-support-files/cypress/support/e2e.js, /foo/bar/.projects/multiple-support-files/cypress/support/e2e.ts\`. + +Correct your supportFile config or merge the files into one. + +` diff --git a/system-tests/projects/multiples-config-files-with-json/cypress.config.js b/system-tests/projects/multiple-config-files-with-json/cypress.config.js similarity index 100% rename from system-tests/projects/multiples-config-files-with-json/cypress.config.js rename to system-tests/projects/multiple-config-files-with-json/cypress.config.js diff --git a/system-tests/projects/multiples-config-files-with-json/cypress.json b/system-tests/projects/multiple-config-files-with-json/cypress.json similarity index 100% rename from system-tests/projects/multiples-config-files-with-json/cypress.json rename to system-tests/projects/multiple-config-files-with-json/cypress.json diff --git a/system-tests/projects/multiple-support-files/cypress.config.js b/system-tests/projects/multiple-support-files/cypress.config.js new file mode 100644 index 000000000000..4ba52ba2c8df --- /dev/null +++ b/system-tests/projects/multiple-support-files/cypress.config.js @@ -0,0 +1 @@ +module.exports = {} diff --git a/system-tests/projects/multiple-support-files/cypress/e2e/app.cy.js b/system-tests/projects/multiple-support-files/cypress/e2e/app.cy.js new file mode 100644 index 000000000000..7258e472bd59 --- /dev/null +++ b/system-tests/projects/multiple-support-files/cypress/e2e/app.cy.js @@ -0,0 +1,3 @@ +it('is true', () => { + expect(true).to.be.true +}) diff --git a/system-tests/projects/multiple-support-files/cypress/support/e2e.js b/system-tests/projects/multiple-support-files/cypress/support/e2e.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/system-tests/projects/multiple-support-files/cypress/support/e2e.ts b/system-tests/projects/multiple-support-files/cypress/support/e2e.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/system-tests/projects/ts-proj/cypress.config.ts b/system-tests/projects/ts-proj/cypress.config.ts index ec0a60ca3b3c..4ba52ba2c8df 100644 --- a/system-tests/projects/ts-proj/cypress.config.ts +++ b/system-tests/projects/ts-proj/cypress.config.ts @@ -1,5 +1 @@ -module.exports = { - 'e2e': { - 'supportFile': 'cypress/support/e2e.ts', - }, -} +module.exports = {} diff --git a/system-tests/test/config_spec.js b/system-tests/test/config_spec.js index d1fb97c03fc9..3fe84249a35a 100644 --- a/system-tests/test/config_spec.js +++ b/system-tests/test/config_spec.js @@ -103,11 +103,11 @@ describe('e2e config', () => { }) it('throws error when cypress.json is found in project and cypress.config.{ts|js} exists as well', function () { - Fixtures.scaffoldProject('multiples-config-files-with-json') - Fixtures.projectPath('multiples-config-files-with-json') + Fixtures.scaffoldProject('multiple-config-files-with-json') + Fixtures.projectPath('multiple-config-files-with-json') return systemTests.exec(this, { - project: 'multiples-config-files-with-json', + project: 'multiple-config-files-with-json', expectedExitCode: 1, snapshot: true, }) diff --git a/system-tests/test/multiple_support_files_spec.js b/system-tests/test/multiple_support_files_spec.js new file mode 100644 index 000000000000..98929ecc6cd7 --- /dev/null +++ b/system-tests/test/multiple_support_files_spec.js @@ -0,0 +1,15 @@ +const systemTests = require('../lib/system-tests').default + +describe('e2e multiple support files', () => { + systemTests.setup() + + it('passes', function () { + return systemTests.exec(this, { + project: 'multiple-support-files', + sanitizeScreenshotDimensions: true, + snapshot: true, + expectedExitCode: 1, + onStdout: systemTests.normalizeWebpackErrors, + }) + }) +})