mirror of
https://github.com/cypress-io/cypress.git
synced 2026-03-13 12:59:07 -05:00
fix: Don't include defineConfig if cypress isn't available from project root (#22005)
* fix: Don't include defineConfig in generated config if cypress isn't available from project root * Add e2e test * Trying to figure out why test fails in CI * Fix ts error, more specific timeout * Once more with feeling Co-authored-by: Matt Henkes <mjhenkes@gmail.com>
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
"test": "yarn test-unit",
|
||||
"test:clean": "find ./test/__fixtures__ -depth -name 'output.*' -type f -exec rm {} \\;",
|
||||
"test-debug": "yarn test-unit --inspect-brk=5566",
|
||||
"test-unit": "mocha --configFile=../../mocha-reporter-config.json -r @packages/ts/register 'test/**/*.spec.ts' --exit"
|
||||
"test-unit": "mocha --configFile=../../mocha-reporter-config.json -r @packages/ts/register 'test/**/*.spec.ts' --exit --timeout 5000"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7",
|
||||
|
||||
@@ -93,6 +93,7 @@ export interface AddTestingTypeToCypressConfigOptions {
|
||||
info: ASTComponentDefinitionConfig | {
|
||||
testingType: 'e2e'
|
||||
}
|
||||
projectRoot: string
|
||||
}
|
||||
|
||||
export async function addTestingTypeToCypressConfig (options: AddTestingTypeToCypressConfigOptions): Promise<AddToCypressConfigResult> {
|
||||
@@ -114,7 +115,7 @@ export async function addTestingTypeToCypressConfig (options: AddTestingTypeToCy
|
||||
// gracefully by adding some default code to use as the AST here, based on the extension
|
||||
if (!result || result.trim() === '') {
|
||||
resultStatus = 'ADDED'
|
||||
result = getEmptyCodeBlock({ outputType: pathExt as OutputExtension, isProjectUsingESModules: options.isProjectUsingESModules })
|
||||
result = getEmptyCodeBlock({ outputType: pathExt as OutputExtension, isProjectUsingESModules: options.isProjectUsingESModules, projectRoot: options.projectRoot })
|
||||
}
|
||||
|
||||
const toPrint = await addToCypressConfig(options.filePath, result, toAdd)
|
||||
@@ -133,27 +134,60 @@ export async function addTestingTypeToCypressConfig (options: AddTestingTypeToCy
|
||||
}
|
||||
}
|
||||
|
||||
// If they are running Cypress that isn't installed in their
|
||||
// project's node_modules, we don't want to include
|
||||
// defineConfig(/***/) in their cypress.config.js,
|
||||
// since it won't exist.
|
||||
export function defineConfigAvailable (projectRoot: string) {
|
||||
try {
|
||||
const cypress = require.resolve('cypress', {
|
||||
paths: [projectRoot],
|
||||
})
|
||||
const api = require(cypress)
|
||||
|
||||
return 'defineConfig' in api
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type OutputExtension = '.ts' | '.mjs' | '.js'
|
||||
|
||||
// Necessary to handle the edge case of them deleting the contents of their Cypress
|
||||
// config file, just before we merge in the testing type
|
||||
function getEmptyCodeBlock ({ outputType, isProjectUsingESModules }: { outputType: OutputExtension, isProjectUsingESModules: boolean}) {
|
||||
if (outputType === '.ts' || outputType === '.mjs' || isProjectUsingESModules) {
|
||||
return dedent`
|
||||
import { defineConfig } from 'cypress'
|
||||
function getEmptyCodeBlock ({ outputType, isProjectUsingESModules, projectRoot }: { outputType: OutputExtension, isProjectUsingESModules: boolean, projectRoot: string}) {
|
||||
if (defineConfigAvailable(projectRoot)) {
|
||||
if (outputType === '.ts' || outputType === '.mjs' || isProjectUsingESModules) {
|
||||
return dedent`
|
||||
import { defineConfig } from 'cypress'
|
||||
|
||||
export default defineConfig({
|
||||
|
||||
})
|
||||
`
|
||||
}
|
||||
|
||||
return dedent`
|
||||
const { defineConfig } = require('cypress')
|
||||
|
||||
module.exports = defineConfig({
|
||||
|
||||
export default defineConfig({
|
||||
|
||||
})
|
||||
`
|
||||
}
|
||||
|
||||
return dedent`
|
||||
const { defineConfig } = require('cypress')
|
||||
if (outputType === '.ts' || outputType === '.mjs' || isProjectUsingESModules) {
|
||||
return dedent`
|
||||
export default {
|
||||
|
||||
module.exports = defineConfig({
|
||||
|
||||
})
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
return dedent`
|
||||
module.exports = {
|
||||
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
// babel transforms, etc. into client-side usage of the config code
|
||||
export * from './browser'
|
||||
|
||||
export { addProjectIdToCypressConfig, addToCypressConfig, addTestingTypeToCypressConfig, AddTestingTypeToCypressConfigOptions } from './ast-utils/addToCypressConfig'
|
||||
export { addProjectIdToCypressConfig, addToCypressConfig, addTestingTypeToCypressConfig, AddTestingTypeToCypressConfigOptions, defineConfigAvailable } from './ast-utils/addToCypressConfig'
|
||||
|
||||
@@ -26,6 +26,7 @@ describe('addToCypressConfig', () => {
|
||||
testingType: 'e2e',
|
||||
},
|
||||
isProjectUsingESModules: false,
|
||||
projectRoot: __dirname,
|
||||
})
|
||||
|
||||
expect(stub.firstCall.args[1].trim()).to.eq(dedent`
|
||||
@@ -50,6 +51,7 @@ describe('addToCypressConfig', () => {
|
||||
testingType: 'e2e',
|
||||
},
|
||||
isProjectUsingESModules: true,
|
||||
projectRoot: __dirname,
|
||||
})
|
||||
|
||||
expect(stub.firstCall.args[1].trim()).to.eq(dedent`
|
||||
@@ -74,6 +76,7 @@ describe('addToCypressConfig', () => {
|
||||
testingType: 'e2e',
|
||||
},
|
||||
isProjectUsingESModules: false,
|
||||
projectRoot: __dirname,
|
||||
})
|
||||
|
||||
expect(stub.firstCall.args[1].trim()).to.eq(dedent`
|
||||
@@ -91,6 +94,52 @@ describe('addToCypressConfig', () => {
|
||||
expect(result.result).to.eq('ADDED')
|
||||
})
|
||||
|
||||
it('will exclude defineConfig if cypress can\'t be imported from the projectRoot', async () => {
|
||||
const result = await addTestingTypeToCypressConfig({
|
||||
filePath: path.join(__dirname, '../__fixtures__/empty.config.js'),
|
||||
info: {
|
||||
testingType: 'e2e',
|
||||
},
|
||||
isProjectUsingESModules: false,
|
||||
projectRoot: '/foo',
|
||||
})
|
||||
|
||||
expect(stub.firstCall.args[1].trim()).to.eq(dedent`
|
||||
module.exports = {
|
||||
e2e: {
|
||||
setupNodeEvents(on, config) {
|
||||
// implement node event listeners here
|
||||
},
|
||||
},
|
||||
};
|
||||
`)
|
||||
|
||||
expect(result.result).to.eq('ADDED')
|
||||
})
|
||||
|
||||
it('will exclude defineConfig if cypress can\'t be imported from the projectRoot for an ECMA Script project', async () => {
|
||||
const result = await addTestingTypeToCypressConfig({
|
||||
filePath: path.join(__dirname, '../__fixtures__/empty.config.js'),
|
||||
info: {
|
||||
testingType: 'e2e',
|
||||
},
|
||||
isProjectUsingESModules: true,
|
||||
projectRoot: '/foo',
|
||||
})
|
||||
|
||||
expect(stub.firstCall.args[1].trim()).to.eq(dedent`
|
||||
export default {
|
||||
e2e: {
|
||||
setupNodeEvents(on, config) {
|
||||
// implement node event listeners here
|
||||
},
|
||||
},
|
||||
};
|
||||
`)
|
||||
|
||||
expect(result.result).to.eq('ADDED')
|
||||
})
|
||||
|
||||
it('will error if we are unable to add to the config', async () => {
|
||||
const result = await addTestingTypeToCypressConfig({
|
||||
filePath: path.join(__dirname, '../__fixtures__/invalid.config.ts'),
|
||||
@@ -98,6 +147,7 @@ describe('addToCypressConfig', () => {
|
||||
testingType: 'e2e',
|
||||
},
|
||||
isProjectUsingESModules: false,
|
||||
projectRoot: __dirname,
|
||||
})
|
||||
|
||||
expect(result.result).to.eq('NEEDS_MERGE')
|
||||
@@ -111,6 +161,7 @@ describe('addToCypressConfig', () => {
|
||||
testingType: 'e2e',
|
||||
},
|
||||
isProjectUsingESModules: false,
|
||||
projectRoot: __dirname,
|
||||
})
|
||||
|
||||
expect(result.result).to.eq('NEEDS_MERGE')
|
||||
|
||||
@@ -233,6 +233,7 @@ export class WizardActions {
|
||||
isProjectUsingESModules: this.ctx.lifecycleManager.metaState.isProjectUsingESModules,
|
||||
filePath: configFilePath,
|
||||
info: testingTypeInfo,
|
||||
projectRoot: this.projectRoot,
|
||||
})
|
||||
|
||||
const description = (testingType === 'e2e')
|
||||
|
||||
@@ -15,7 +15,7 @@ import { LegacyCypressConfigJson, legacyIntegrationFolder } from '..'
|
||||
import { parse } from '@babel/parser'
|
||||
import generate from '@babel/generator'
|
||||
import _ from 'lodash'
|
||||
import { getBreakingKeys } from '@packages/config'
|
||||
import { defineConfigAvailable, getBreakingKeys } from '@packages/config'
|
||||
|
||||
const debug = Debug('cypress:data-context:sources:migration:codegen')
|
||||
|
||||
@@ -148,24 +148,6 @@ async function getPluginRelativePath (cfg: LegacyCypressConfigJson, projectRoot:
|
||||
return cfg.pluginsFile ? cfg.pluginsFile : await tryGetDefaultLegacyPluginsFile(projectRoot)
|
||||
}
|
||||
|
||||
// If they are running an old version of Cypress
|
||||
// or running Cypress that isn't installed in their
|
||||
// project's node_modules, we don't want to include
|
||||
// defineConfig(/***/) in their cypress.config.js,
|
||||
// since it won't exist.
|
||||
export function defineConfigAvailable (projectRoot: string) {
|
||||
try {
|
||||
const cypress = require.resolve('cypress', {
|
||||
paths: [projectRoot],
|
||||
})
|
||||
const api = require(cypress)
|
||||
|
||||
return 'defineConfig' in api
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function createCypressConfig (config: ConfigOptions, pluginPath: string | undefined, options: CreateConfigOptions): string {
|
||||
const globalString = Object.keys(config.global).length > 0 ? `${formatObjectForConfig(config.global)},` : ''
|
||||
const componentString = options.hasComponentTesting ? createComponentTemplate(config.component) : ''
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { defineConfig } from 'cypress'
|
||||
import getenv from 'getenv'
|
||||
import { snapshotCypressDirectory } from './cypress/tasks/snapshotsScaffold'
|
||||
import { uninstallDependenciesInScaffoldedProject } from './cypress/tasks/uninstallDependenciesInScaffoldedProject'
|
||||
|
||||
const CYPRESS_INTERNAL_CLOUD_ENV = getenv('CYPRESS_INTERNAL_CLOUD_ENV', process.env.CYPRESS_INTERNAL_ENV || 'development')
|
||||
|
||||
@@ -41,6 +42,7 @@ export default defineConfig({
|
||||
|
||||
on('task', {
|
||||
snapshotCypressDirectory,
|
||||
uninstallDependenciesInScaffoldedProject,
|
||||
})
|
||||
|
||||
return await e2ePluginSetup(on, config)
|
||||
|
||||
@@ -158,4 +158,17 @@ describe('scaffolding new projects', { defaultCommandTimeout: 7000 }, () => {
|
||||
scaffoldAndOpenCTProject({ name: 'pristine', framework: 'Create React App', removeFixturesFolder: false })
|
||||
assertScaffoldedFilesAreCorrect({ language, testingType: 'component', ctFramework: 'Create React App (v5)', customDirectory: 'without-fixtures' })
|
||||
})
|
||||
|
||||
it('generates valid config file for pristine project without cypress installed', () => {
|
||||
cy.scaffoldProject('pristine')
|
||||
cy.openProject('pristine')
|
||||
cy.withCtx((ctx) => ctx.currentProject).then((currentProject) => {
|
||||
cy.task('uninstallDependenciesInScaffoldedProject', { currentProject })
|
||||
})
|
||||
|
||||
cy.visitLaunchpad()
|
||||
cy.contains('button', cy.i18n.testingType.e2e.name).click()
|
||||
cy.contains('button', cy.i18n.setupPage.step.continue).click()
|
||||
cy.contains('h1', cy.i18n.setupPage.testingCard.chooseABrowser).should('be.visible')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
export async function uninstallDependenciesInScaffoldedProject ({ currentProject }) {
|
||||
// @ts-ignore
|
||||
fs.rmdirSync(path.resolve(currentProject, '../node_modules'), { recursive: true, force: true })
|
||||
|
||||
return null
|
||||
}
|
||||
Reference in New Issue
Block a user