Skip to content

Commit

Permalink
fix: Don't include defineConfig if cypress isn't available from proje…
Browse files Browse the repository at this point in the history
…ct 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>
  • Loading branch information
Blue F and mjhenkes authored Jun 1, 2022
1 parent 369a865 commit e12e0d9
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 31 deletions.
2 changes: 1 addition & 1 deletion packages/config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
54 changes: 44 additions & 10 deletions packages/config/src/ast-utils/addToCypressConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export interface AddTestingTypeToCypressConfigOptions {
info: ASTComponentDefinitionConfig | {
testingType: 'e2e'
}
projectRoot: string
}

export async function addTestingTypeToCypressConfig (options: AddTestingTypeToCypressConfigOptions): Promise<AddToCypressConfigResult> {
Expand All @@ -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)
Expand All @@ -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) {
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`
import { defineConfig } from 'cypress'
const { defineConfig } = require('cypress')
module.exports = defineConfig({
export default defineConfig({
})
`
}

if (outputType === '.ts' || outputType === '.mjs' || isProjectUsingESModules) {
return dedent`
export default {
}
`
}

return dedent`
const { defineConfig } = require('cypress')
module.exports = {
module.exports = defineConfig({
})
}
`
}

Expand Down
2 changes: 1 addition & 1 deletion packages/config/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
51 changes: 51 additions & 0 deletions packages/config/test/ast-utils/addToCypressConfig.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ describe('addToCypressConfig', () => {
testingType: 'e2e',
},
isProjectUsingESModules: false,
projectRoot: __dirname,
})

expect(stub.firstCall.args[1].trim()).to.eq(dedent`
Expand All @@ -50,6 +51,7 @@ describe('addToCypressConfig', () => {
testingType: 'e2e',
},
isProjectUsingESModules: true,
projectRoot: __dirname,
})

expect(stub.firstCall.args[1].trim()).to.eq(dedent`
Expand All @@ -74,6 +76,7 @@ describe('addToCypressConfig', () => {
testingType: 'e2e',
},
isProjectUsingESModules: false,
projectRoot: __dirname,
})

expect(stub.firstCall.args[1].trim()).to.eq(dedent`
Expand All @@ -91,13 +94,60 @@ 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'),
info: {
testingType: 'e2e',
},
isProjectUsingESModules: false,
projectRoot: __dirname,
})

expect(result.result).to.eq('NEEDS_MERGE')
Expand All @@ -111,6 +161,7 @@ describe('addToCypressConfig', () => {
testingType: 'e2e',
},
isProjectUsingESModules: false,
projectRoot: __dirname,
})

expect(result.result).to.eq('NEEDS_MERGE')
Expand Down
1 change: 1 addition & 0 deletions packages/data-context/src/actions/WizardActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ export class WizardActions {
isProjectUsingESModules: this.ctx.lifecycleManager.metaState.isProjectUsingESModules,
filePath: configFilePath,
info: testingTypeInfo,
projectRoot: this.projectRoot,
})

const description = (testingType === 'e2e')
Expand Down
20 changes: 1 addition & 19 deletions packages/data-context/src/sources/migration/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down Expand Up @@ -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) : ''
Expand Down
2 changes: 2 additions & 0 deletions packages/launchpad/cypress.config.ts
Original file line number Diff line number Diff line change
@@ -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')

Expand Down Expand Up @@ -41,6 +42,7 @@ export default defineConfig({

on('task', {
snapshotCypressDirectory,
uninstallDependenciesInScaffoldedProject,
})

return await e2ePluginSetup(on, config)
Expand Down
13 changes: 13 additions & 0 deletions packages/launchpad/cypress/e2e/scaffold-project.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
})
})
Original file line number Diff line number Diff line change
@@ -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
}

3 comments on commit e12e0d9

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on e12e0d9 Jun 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the linux x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.0.1/linux-x64/develop-e12e0d96dd97ce352099bb258f498077ac39bd74/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on e12e0d9 Jun 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the win32 x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.0.1/win32-x64/develop-e12e0d96dd97ce352099bb258f498077ac39bd74/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on e12e0d9 Jun 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the darwin x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.0.1/darwin-x64/develop-e12e0d96dd97ce352099bb258f498077ac39bd74/cypress.tgz

Please sign in to comment.