fix: migrate config fields to correct the location (#19940)

* testFiles

* ignoreTestFiles

* supportFile

* use utility functions

* remove setupNodeEvents and supportfile from component

* don't throw for supportFiles query

* custom integrationFolder default testFiles

* add default e2e directory to e2e specPattern

* add missing type import

* work on migration project docs

* update docs

* use prettier to run assertion

* add detailed breakdown of expected config and update code to correspond

* remove unused code

* update comment

* update component testing migration expected configurations

* wip: tests for edge cases

* make plugins require relative

* do not add defineConfig if they do not have cypress 10

* edge case of define config

* system test

* do not show supportfile for CT only project

* fix: set correct config file on migration (#19990)

* fix: set correct config file on migration

* add e2e test

* Fix test

Co-authored-by: Cesar Avitia <cesar@cypress.io>

* fix: allow default support file to be TS

* test: with a ts support file

* refactor: duplicate hasTypeScript in migration

* move migration utils files

* rename functions that returns globs with globs

* fix(launchpad): Support migrating projects w/o pluginsFile  (#19993)

* handle case of no plugins file

* fix readme

* style: add comments

* build: fix types

Co-authored-by: ElevateBart <ledouxb@gmail.com>

* fix types

* fix migration unit-tests

* fix: build for unit types

* fix shouldShowSteps unit tests

* fix: typo

* do not import from data-context in frontend test

* remove non-type import

Co-authored-by: Lachlan Miller <lachlan.miller.1990@outlook.com>
Co-authored-by: Barthélémy Ledoux <bart@cypress.io>
Co-authored-by: Alejandro Estrada <estrada9166@gmail.com>
Co-authored-by: ElevateBart <ledouxb@gmail.com>
This commit is contained in:
Cesar
2022-02-01 17:42:30 -07:00
committed by GitHub
parent 02a669daae
commit b16eb65eb2
65 changed files with 1531 additions and 648 deletions
@@ -1,68 +0,0 @@
exports['migration utils cypress.config.js generation should create a string when passed only a global option 1'] = `
const { defineConfig } = require('cypress')
module.exports = defineConfig({
visualViewport: 300,
})
`
exports['migration utils cypress.config.js generation should create a string when passed only a e2e options 1'] = `
const { defineConfig } = require('cypress')
module.exports = defineConfig({
})
`
exports['migration utils cypress.config.js generation should create a string when passed only a component options 1'] = `
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
return require('/cypress/plugins/index.js')
},
retries: 2
},
})
`
exports['migration utils cypress.config.js generation should create a string for a config with global, component, and e2e options 1'] = `
const { defineConfig } = require('cypress')
module.exports = defineConfig({
visualViewport: 300,
e2e: {
setupNodeEvents(on, config) {
return require('/cypress/plugins/index.js')
},
baseUrl: 'localhost:300',
retries: 2
},
component: {
setupNodeEvents(on, config) {
return require('/cypress/plugins/index.js')
},
retries: 1
},
})
`
exports['migration utils cypress.config.js generation should create a string when passed an empty object 1'] = `
const { defineConfig } = require('cypress')
module.exports = defineConfig({
component: {
setupNodeEvents(on, config) {
return require('/cypress/plugins/index.js')
},
retries: 2
},
})
`
exports['migration utils cypress.config.js generation should exclude fields that are no longer valid 1'] = `
const { defineConfig } = require('cypress')
module.exports = defineConfig({
})
`
@@ -2,8 +2,14 @@ exports['cypress.config.js generation should create a string when passed only a
const { defineConfig } = require('cypress')
module.exports = defineConfig({
visualViewport: 300,
viewportWidth: 300,
e2e: {
setupNodeEvents(on, config) {
return require('./cypress/plugins/index.js')(on, config)
},
},
})
`
exports['cypress.config.js generation should create a string when passed only a e2e options 1'] = `
@@ -12,57 +18,65 @@ const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
return require('/cypress/plugins/index.js')
return require('./cypress/plugins/index.js')(on, config)
},
baseUrl: 'localhost:3000'
baseUrl: 'localhost:3000',
},
})
`
exports['cypress.config.js generation should create a string when passed only a component options 1'] = `
const { defineConfig } = require('cypress')
module.exports = defineConfig({
component: {
e2e: {
setupNodeEvents(on, config) {
return require('/cypress/plugins/index.js')
return require('./cypress/plugins/index.js')(on, config)
},
retries: 2
},
})
`
exports['cypress.config.js generation should create a string for a config with global, component, and e2e options 1'] = `
const { defineConfig } = require('cypress')
module.exports = defineConfig({
visualViewport: 300,
viewportWidth: 300,
e2e: {
setupNodeEvents(on, config) {
return require('/cypress/plugins/index.js')
return require('./cypress/plugins/index.js')(on, config)
},
retries: 2,
baseUrl: 'localhost:300',
retries: 2
},
component: {
setupNodeEvents(on, config) {
return require('/cypress/plugins/index.js')
},
retries: 1
},
})
`
exports['cypress.config.js generation should create a string when passed an empty object 1'] = `
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
return require('./cypress/plugins/index.js')(on, config)
},
},
})
`
exports['cypress.config.js generation should exclude fields that are no longer valid 1'] = `
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
return require('./path/to/plugin/file')(on, config)
},
},
})
`
+2
View File
@@ -37,6 +37,7 @@
"launch-editor": "2.2.1",
"lodash": "4.17.21",
"p-defer": "^3.0.0",
"prettier": "2.5.1",
"randomstring": "1.1.5",
"stringify-object": "^3.0.0",
"underscore.string": "^3.3.5",
@@ -53,6 +54,7 @@
"@types/ejs": "^3.1.0",
"@types/fs-extra": "^8.0.1",
"@types/mocha": "^8.0.3",
"@types/prettier": "2.4.3",
"@types/stringify-object": "^3.0.0",
"mocha": "7.0.1",
"rimraf": "3.0.2",
@@ -44,6 +44,14 @@ export class FileActions {
)
}
async readFileInProject (relative: string) {
if (!this.ctx.currentProject) {
throw new Error(`Cannot check file in project exists without active project`)
}
return this.ctx.fs.readFileSync(path.join(this.ctx.currentProject, relative), 'utf-8')
}
async checkIfFileExists (relativePath: string) {
if (!this.ctx.currentProject) {
throw new Error(`Cannot check file in project exists without active project`)
@@ -1,13 +1,12 @@
import path from 'path'
import fs from 'fs-extra'
import type { DataContext } from '..'
import {
formatConfig,
moveSpecFiles,
NonStandardMigrationError,
SpecToMove,
supportFilesForMigration,
} from '../util'
import type { TestingType } from '@packages/types'
} from '../sources'
export class MigrationActions {
constructor (private ctx: DataContext) { }
@@ -15,7 +14,7 @@ export class MigrationActions {
async createConfigFile () {
const config = await this.ctx.migration.createConfigString()
await this.ctx.actions.file.writeFileInProject('cypress.config.js', config).catch((error) => {
await this.ctx.fs.writeFile(this.ctx.lifecycleManager.configFilePath, config).catch((error) => {
throw error
})
@@ -65,18 +64,16 @@ export class MigrationActions {
throw new NonStandardMigrationError('support')
}
fs.renameSync(
this.ctx.fs.renameSync(
path.join(this.ctx.currentProject, beforeRelative),
path.join(this.ctx.currentProject, afterRelative),
)
}
async startWizardReconfiguration (type?: TestingType) {
async finishReconfigurationWizard () {
this.ctx.lifecycleManager.initializeConfigWatchers()
this.ctx.lifecycleManager.refreshMetaState()
if (type) {
this.ctx.lifecycleManager.setCurrentTestingType(type)
}
await this.ctx.lifecycleManager.reloadConfig()
}
async nextStep () {
@@ -96,7 +93,16 @@ export class MigrationActions {
this.ctx.migration.setStep(nextStep)
}
} else {
await this.startWizardReconfiguration()
await this.finishReconfigurationWizard()
}
}
async assertSuccessfulConfigMigration (configExtension: 'js' | 'ts' = 'js') {
const actual = formatConfig(await this.ctx.actions.file.readFileInProject(`cypress.config.${configExtension}`))
const expected = formatConfig(await this.ctx.actions.file.readFileInProject(`expected-cypress.config.${configExtension}`))
if (actual !== expected) {
throw Error(`Expected ${actual} to equal ${expected}`)
}
}
}
@@ -6,24 +6,22 @@ import {
createConfigString,
initComponentTestingMigration,
ComponentTestingMigrationStatus,
getDefaultLegacySupportFile,
tryGetDefaultLegacyPluginsFile,
supportFilesForMigration,
OldCypressConfig,
hasComponentSpecFile,
} from '../util/migration'
import {
hasSpecFile,
getSpecs,
applyMigrationTransform,
} from './migration/autoRename'
import type { FilePart } from '../util/migrationFormat'
import {
getStepsForMigration,
shouldShowRenameSupport,
getIntegrationFolder,
getPluginsFile,
isDefaultTestFiles,
getComponentTestFiles,
getComponentTestFilesGlobs,
getComponentFolder,
} from './migration/shouldShowSteps'
} from './migration'
import type { FilePart } from './migration/format'
export interface MigrationFile {
testingType: TestingType
@@ -39,19 +37,34 @@ export interface MigrationFile {
type MIGRATION_STEP = typeof MIGRATION_STEPS[number]
const flags = {
hasCustomIntegrationFolder: false,
hasCustomIntegrationTestFiles: false,
hasCustomComponentFolder: false,
hasCustomComponentTestFiles: false,
hasCustomSupportFile: false,
hasComponentTesting: true,
hasE2ESpec: true,
hasPluginsFile: true,
} as const
export class MigrationDataSource {
private _config: OldCypressConfig | null = null
private _step: MIGRATION_STEP = 'renameAuto'
filteredSteps: MIGRATION_STEP[] = MIGRATION_STEPS.filter(() => true)
hasCustomIntegrationFolder: boolean = false
hasCustomIntegrationTestFiles: boolean = false
hasCustomIntegrationFolder: boolean = flags.hasCustomIntegrationFolder
hasCustomIntegrationTestFiles: boolean = flags.hasCustomIntegrationTestFiles
hasCustomComponentFolder: boolean = false
hasCustomComponentTestFiles: boolean = false
hasCustomComponentFolder: boolean = flags.hasCustomComponentFolder
hasCustomComponentTestFiles: boolean = flags.hasCustomComponentTestFiles
hasCustomSupportFile = false
hasComponentTesting: boolean = true
hasCustomSupportFile: boolean = flags.hasCustomSupportFile
hasComponentTesting: boolean = flags.hasComponentTesting
hasE2ESpec: boolean = flags.hasE2ESpec
hasPluginsFile: boolean = flags.hasPluginsFile
private componentTestingMigrationWatcher?: chokidar.FSWatcher
componentTestingMigrationStatus?: ComponentTestingMigrationStatus
@@ -59,6 +72,9 @@ export class MigrationDataSource {
constructor (private ctx: DataContext) { }
async initialize () {
// for testing mainly, we want to ensure the flags are reset each test
this.resetFlags()
if (!this.ctx.currentProject) {
throw Error('cannot do migration without currentProject!')
}
@@ -77,12 +93,10 @@ export class MigrationDataSource {
this.setStep(this.filteredSteps[0])
}
async getDefaultLegacySupportFile (): Promise<string> {
if (!this.ctx.currentProject) {
throw Error(`Need this.ctx.projectRoot!`)
private resetFlags () {
for (const [k, v] of Object.entries(flags)) {
this[k as keyof typeof flags] = v
}
return getDefaultLegacySupportFile(this.ctx.currentProject)
}
async getComponentTestingMigrationStatus () {
@@ -115,7 +129,7 @@ export class MigrationDataSource {
const { status, watcher } = await initComponentTestingMigration(
this.ctx.currentProject,
componentFolder,
getComponentTestFiles(config),
getComponentTestFilesGlobs(config),
onFileMoved,
)
@@ -124,16 +138,20 @@ export class MigrationDataSource {
}
if (!this.componentTestingMigrationStatus) {
throw Error(`Status should have been assigned by the watcher. Somethign is wrong`)
throw Error(`Status should have been assigned by the watcher. Something is wrong`)
}
return this.componentTestingMigrationStatus
}
async supportFilesForMigrationGuide (): Promise<MigrationFile | null> {
if (!this.ctx.currentProject) {
throw Error('Need this.ctx.currentProject')
}
const config = await this.parseCypressConfig()
if (!shouldShowRenameSupport(config)) {
if (!await shouldShowRenameSupport(this.ctx.currentProject, config)) {
return null
}
@@ -141,7 +159,13 @@ export class MigrationDataSource {
throw Error(`Need this.ctx.projectRoot!`)
}
return supportFilesForMigration(this.ctx.currentProject)
try {
const supportFiles = await supportFilesForMigration(this.ctx.currentProject)
return supportFiles
} catch {
return null
}
}
async getSpecsForMigrationGuide (): Promise<MigrationFile[]> {
@@ -172,9 +196,21 @@ export class MigrationDataSource {
}
async createConfigString () {
if (!this.ctx.currentProject) {
throw Error('Need currentProject!')
}
const { hasTypescript } = this.ctx.lifecycleManager.metaState
const config = await this.parseCypressConfig()
return createConfigString(config)
return createConfigString(config, {
hasComponentTesting: this.hasComponentTesting,
hasE2ESpec: this.hasE2ESpec,
hasPluginsFile: this.hasPluginsFile,
projectRoot: this.ctx.currentProject,
hasTypescript,
})
}
async integrationFolder () {
@@ -219,19 +255,41 @@ export class MigrationDataSource {
this.hasCustomComponentFolder = componentFolder !== 'cypress/component'
const componentTestFiles = getComponentTestFiles(config)
const componentTestFiles = getComponentTestFilesGlobs(config)
this.hasCustomComponentTestFiles = !isDefaultTestFiles(config, 'component')
if (componentFolder === false) {
this.hasComponentTesting = false
} else {
this.hasComponentTesting = await hasComponentSpecFile(
this.hasComponentTesting = await hasSpecFile(
this.ctx.currentProject,
componentFolder,
componentTestFiles,
)
}
const integrationFolder = getIntegrationFolder(config)
if (integrationFolder === false) {
this.hasE2ESpec = false
} else {
this.hasE2ESpec = await hasSpecFile(
this.ctx.currentProject,
integrationFolder,
componentTestFiles,
)
}
const pluginsFileMissing = (
(config.e2e?.pluginsFile ?? undefined) === undefined &&
config.pluginsFile === undefined &&
!await tryGetDefaultLegacyPluginsFile(this.ctx.currentProject)
)
if (getPluginsFile(config) === false || pluginsFileMissing) {
this.hasPluginsFile = false
}
}
get step (): MIGRATION_STEP {
@@ -1,16 +1,18 @@
import { FilePart, formatMigrationFile, OldCypressConfig } from '../../util'
import globby from 'globby'
import {
getComponentFolder,
getComponentTestFiles,
getIntegrationFolder,
getIntegrationTestFiles,
isDefaultTestFiles,
} from '.'
import { regexps } from './regexps'
import type { MigrationFile } from '../MigrationDataSource'
import path from 'path'
import type { TestingType } from '@packages/types'
import {
FilePart,
formatMigrationFile,
getComponentFolder,
getComponentTestFilesGlobs,
getIntegrationFolder,
getIntegrationTestFilesGlobs,
isDefaultTestFiles,
OldCypressConfig,
regexps,
} from '.'
import type { MigrationFile } from '../MigrationDataSource'
export interface MigrationSpec {
relative: string
@@ -93,44 +95,32 @@ export function applyMigrationTransform (
export async function getSpecs (projectRoot: string, config: OldCypressConfig): Promise<GetSpecs> {
const integrationFolder = getIntegrationFolder(config)
const integrationTestFiles = getIntegrationTestFiles(config)
const integrationTestFiles = getIntegrationTestFilesGlobs(config)
const componentFolder = getComponentFolder(config)
const componentTestFiles = getComponentTestFiles(config)
const componentTestFiles = getComponentTestFilesGlobs(config)
let integrationSpecs: MigrationSpec[]
let componentSpecs: MigrationSpec[]
let integrationSpecs: MigrationSpec[] = []
let componentSpecs: MigrationSpec[] = []
if (integrationFolder === false) {
const globs = integrationFolder === false
? []
: integrationFolder === 'cypress/integration'
? ['**/*'].map((glob) => path.join(integrationFolder, glob))
: integrationTestFiles.map((glob) => path.join(integrationFolder, glob))
let specs = integrationFolder === false
? []
: (await globby(globs, { onlyFiles: true, cwd: projectRoot }))
const fullyCustom = integrationFolder !== 'cypress/integration' && !isDefaultTestFiles(config, 'integration')
// we cannot do a migration if either integrationFolder is false,
// or if both the integrationFolder and testFiles are custom.
if (integrationFolder === false || fullyCustom) {
integrationSpecs = []
} else if (
// don't care about the testFiles pattern, just get
// everything in the default integration folder
integrationFolder === 'cypress/integration' &&
!isDefaultTestFiles(config, 'integration')
) {
integrationSpecs = (await globby(integrationFolder, {
onlyFiles: true,
cwd: projectRoot,
})).map((relative) => {
return {
relative,
usesDefaultFolder: true,
usesDefaultTestFiles: false,
testingType: 'e2e',
}
})
} else {
// don't care about the testFiles pattern, just get
// everything in the default integration folder
const globs = integrationTestFiles.map((glob) => {
return path.join(integrationFolder, glob)
})
integrationSpecs = (await globby(globs, {
onlyFiles: true,
cwd: projectRoot,
})).map((relative) => {
integrationSpecs = specs.map((relative) => {
return {
relative,
usesDefaultFolder: integrationFolder === 'cypress/integration',
@@ -140,7 +130,7 @@ export async function getSpecs (projectRoot: string, config: OldCypressConfig):
})
}
if (componentFolder === false) {
if (componentFolder === false || !isDefaultTestFiles(config, 'component')) {
componentSpecs = []
} else {
const globs = componentTestFiles.map((glob) => {
@@ -2,5 +2,7 @@
// created by autobarrel, do not modify directly
export * from './autoRename'
export * from './format'
export * from './regexps'
export * from './shouldShowSteps'
export * from './utils'
@@ -1,9 +1,9 @@
import globby from 'globby'
import { MIGRATION_STEPS } from '@packages/types'
import type { OldCypressConfig } from '../../util'
import path from 'path'
import { getSpecs, OldCypressConfig, tryGetDefaultLegacySupportFile } from '.'
function getTestFiles (config: OldCypressConfig, type: 'component' | 'integration'): string[] {
function getTestFilesGlobs (config: OldCypressConfig, type: 'component' | 'integration'): string[] {
// super awkward how we call it integration tests, but the key to override
// the config is `e2e`
const k = type === 'component' ? 'component' : 'e2e'
@@ -21,22 +21,30 @@ function getTestFiles (config: OldCypressConfig, type: 'component' | 'integratio
return ['**/*']
}
export function getIntegrationTestFiles (config: OldCypressConfig): string[] {
return getTestFiles(config, 'integration')
export function getIntegrationTestFilesGlobs (config: OldCypressConfig): string[] {
return getTestFilesGlobs(config, 'integration')
}
export function getComponentTestFiles (config: OldCypressConfig): string[] {
return getTestFiles(config, 'component')
export function getComponentTestFilesGlobs (config: OldCypressConfig): string[] {
return getTestFilesGlobs(config, 'component')
}
export function isDefaultTestFiles (config: OldCypressConfig, type: 'component' | 'integration') {
const testFiles = type === 'component'
? getComponentTestFiles(config)
: getIntegrationTestFiles(config)
? getComponentTestFilesGlobs(config)
: getIntegrationTestFilesGlobs(config)
return testFiles.length === 1 && testFiles[0] === '**/*'
}
export function getPluginsFile (config: OldCypressConfig) {
if (config.e2e?.pluginsFile === false || config.pluginsFile === false) {
return false
}
return config.e2e?.pluginsFile ?? config.pluginsFile ?? 'cypress/plugins/index.js'
}
export function getIntegrationFolder (config: OldCypressConfig) {
if (config.e2e?.integrationFolder === false || config.integrationFolder === false) {
return false
@@ -59,42 +67,54 @@ async function hasSpecFiles (projectRoot: string, dir: string, testFilesGlob: st
return f.length > 0
}
export async function shouldShowAutoRenameStep (projectRoot: string, config: OldCypressConfig) {
const specsToAutoMigrate = await getSpecs(projectRoot, config)
// if we have at least one spec to auto migrate in either Ct or E2E, we return true.
return specsToAutoMigrate.integration.length > 0 || specsToAutoMigrate.component.length > 0
}
async function anyIntegrationSpecsExist (projectRoot: string, config: OldCypressConfig) {
const integrationFolder = getIntegrationFolder(config)
const integrationTestFiles = getIntegrationTestFiles(config)
// default or custom integrationFolder,
// non custom test files glob
// migrate (unless they have no specs, nothing to rename?)
if (
integrationFolder !== false &&
await hasSpecFiles(projectRoot, integrationFolder, integrationTestFiles)
) {
return true
if (integrationFolder === false) {
return false
}
const componentFolder = getComponentFolder(config)
const componentTestFiles = getComponentTestFiles(config)
const integrationTestFiles = getIntegrationTestFilesGlobs(config)
// we can only auto migrate component specs
// if they are using all the defaults (folder and testFiles)
if (
componentFolder !== false &&
isDefaultTestFiles(config, 'component') &&
await hasSpecFiles(projectRoot, componentFolder, componentTestFiles)
) {
return true
}
return false
return hasSpecFiles(projectRoot, integrationFolder, integrationTestFiles)
}
// we only show rename support file if they are using the default
// if they have anything set in their config, we will not try to rename it.
export function shouldShowRenameSupport (config: OldCypressConfig) {
const defaultSupportFile = 'cypress/support/index.js'
const supportFile = config.e2e?.supportFile ?? config.supportFile ?? defaultSupportFile
// Also, if there are no **no** integration specs, we are doing a CT only migration,
// in which case we don't migrate the supportFile - they'll make a new support/component.js
// when they set CT up.
export async function shouldShowRenameSupport (projectRoot: string, config: OldCypressConfig) {
if (!await anyIntegrationSpecsExist(projectRoot, config)) {
return false
}
return supportFile === defaultSupportFile
const defaultSupportFile = 'cypress/support/index.'
let supportFile = config.e2e?.supportFile ?? config.supportFile
if (supportFile === undefined) {
const foundDefaultSupportFile = await tryGetDefaultLegacySupportFile(projectRoot)
if (foundDefaultSupportFile) {
supportFile = foundDefaultSupportFile
}
}
// if the support file is set to false, we don't show the rename step
// if the support file does not exist (value is undefined), we don't show the rename step
if (!supportFile) {
return false
}
// if the support file is custom, we don't show the rename step
// only if the support file matches the default do we show the rename step
return supportFile.includes(defaultSupportFile)
}
// if they have component testing configured, they will need to
@@ -106,7 +126,7 @@ function shouldShowRenameManual (projectRoot: string, config: OldCypressConfig)
return false
}
const componentTestFiles = getComponentTestFiles(config)
const componentTestFiles = getComponentTestFilesGlobs(config)
return hasSpecFiles(projectRoot, componentFolder, componentTestFiles)
}
@@ -133,7 +153,7 @@ export async function getStepsForMigration (
steps.push(step)
}
if (step === 'renameSupport' && shouldShowRenameSupport(config)) {
if (step === 'renameSupport' && await shouldShowRenameSupport(projectRoot, config)) {
steps.push(step)
}
@@ -0,0 +1,428 @@
import chokidar from 'chokidar'
import fs from 'fs-extra'
import path from 'path'
import globby from 'globby'
import {
supportFileRegexps,
formatMigrationFile,
} from './format'
import { substitute } from './autoRename'
import type { TestingType } from '@packages/types'
import prettier from 'prettier'
import type { MigrationFile } from '..'
type ConfigOptions = {
global: Record<string, unknown>
e2e: Record<string, unknown>
component: Record<string, unknown>
}
/**
* config format pre-10.0
*/
export interface OldCypressConfig {
// limited subset of properties, used for unit tests
viewportWidth?: number
baseUrl?: string
retries?: number
component?: Omit<OldCypressConfig, 'component' | 'e2e'>
e2e?: Omit<OldCypressConfig, 'component' | 'e2e'>
pluginsFile?: string | false
supportFile?: string | false
componentFolder?: string | false
integrationFolder?: string | false
testFiles?: string | string[]
ignoreTestFiles?: string
}
export const defaultSupportFiles = {
before: path.join('cypress', 'support', 'index.js'),
}
export class NonStandardMigrationError extends Error {
constructor (fileType: 'support' | 'config') {
super()
this.message = `Failed to find default ${fileType}. Bailing automation migration.`
}
}
export interface CreateConfigOptions {
hasE2ESpec: boolean
hasPluginsFile: boolean
hasComponentTesting: boolean
projectRoot: string
hasTypescript: boolean
}
export async function createConfigString (cfg: OldCypressConfig, options: CreateConfigOptions) {
return createCypressConfig(reduceConfig(cfg), getPluginRelativePath(cfg), options)
}
interface FileToBeMigratedManually {
relative: string
moved: boolean
}
export interface ComponentTestingMigrationStatus {
files: Map<string, FileToBeMigratedManually>
completed: boolean
}
export function initComponentTestingMigration (
projectRoot: string,
componentFolder: string,
testFiles: string[],
onFileMoved: (status: ComponentTestingMigrationStatus) => void,
): Promise<{
status: ComponentTestingMigrationStatus
watcher: chokidar.FSWatcher
}> {
const watchPaths = testFiles.map((glob) => {
return path.join(componentFolder, glob)
})
const watcher = chokidar.watch(
watchPaths, {
cwd: projectRoot,
},
)
let filesToBeMoved: Map<string, FileToBeMigratedManually> = globby.sync(watchPaths, {
cwd: projectRoot,
}).reduce<Map<string, FileToBeMigratedManually>>((acc, relative) => {
acc.set(relative, { relative, moved: false })
return acc
}, new Map())
watcher.on('unlink', (unlinkedPath) => {
const file = filesToBeMoved.get(unlinkedPath)
if (!file) {
throw Error(`Watcher incorrectly triggered while watching ${file}`)
}
file.moved = true
const completed = Array.from(filesToBeMoved.values()).every((value) => value.moved === true)
onFileMoved({
files: filesToBeMoved,
completed,
})
})
return new Promise((resolve) => {
watcher.on('ready', () => {
resolve({
status: {
files: filesToBeMoved,
completed: false,
},
watcher,
})
})
})
}
function getPluginRelativePath (cfg: OldCypressConfig): string {
const DEFAULT_PLUGIN_PATH = path.normalize('cypress/plugins/index.js')
return cfg.pluginsFile ? cfg.pluginsFile : DEFAULT_PLUGIN_PATH
}
// 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.
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, options: CreateConfigOptions): string {
const globalString = Object.keys(config.global).length > 0 ? `${formatObjectForConfig(config.global)},` : ''
const componentString = options.hasComponentTesting ? createComponentTemplate(config.component) : ''
const e2eString = options.hasE2ESpec
? createE2eTemplate(pluginPath, options.hasPluginsFile, config.e2e)
: ''
if (defineConfigAvailable(options.projectRoot)) {
if (options.hasTypescript) {
return formatConfig(
`import { defineConfig } from 'cypress'
export default defineConfig({${globalString}${e2eString}${componentString}})`,
)
}
return formatConfig(
`const { defineConfig } = require('cypress')
module.exports = defineConfig({${globalString}${e2eString}${componentString}})`,
)
}
if (options.hasTypescript) {
return formatConfig(`export default {${globalString}${e2eString}${componentString}}`)
}
return formatConfig(`module.exports = {${globalString}${e2eString}${componentString}}`)
}
function formatObjectForConfig (obj: Record<string, unknown>) {
return JSON.stringify(obj, null, 2).replace(/^[{]|[}]$/g, '') // remove opening and closing {}
}
function createE2eTemplate (pluginPath: string, hasPluginsFile: boolean, options: Record<string, unknown>) {
const requirePlugins = `return require('.${path.sep}${pluginPath}')(on, config)`
const setupNodeEvents = `setupNodeEvents(on, config) {
${hasPluginsFile ? requirePlugins : ''}
}`
return `e2e: {
${setupNodeEvents},${formatObjectForConfig(options)}
},`
}
function createComponentTemplate (options: Record<string, unknown>) {
return `component: {
setupNodeEvents(on, config) {},${formatObjectForConfig(options)}
},`
}
export interface RelativeSpec {
relative: string
}
/**
* Checks that at least one spec file exist for testing type
*
* NOTE: this is what we use to see if CT/E2E is set up
*/
export async function hasSpecFile (projectRoot: string, folder: string, glob: string | string[]): Promise<boolean> {
return (await globby(glob, {
cwd: path.join(projectRoot, folder),
onlyFiles: true,
})).length > 0
}
export async function tryGetDefaultLegacyPluginsFile (projectRoot: string) {
const glob = path.join(projectRoot, 'cypress', 'plugins', 'index.*')
const files = await globby(glob)
return files[0]
}
export async function tryGetDefaultLegacySupportFile (projectRoot: string) {
const files = await globby(path.join(projectRoot, 'cypress', 'support', 'index.*'))
return files[0]
}
export async function getDefaultLegacySupportFile (projectRoot: string) {
const defaultSupportFile = await tryGetDefaultLegacySupportFile(projectRoot)
if (!defaultSupportFile) {
throw new NonStandardMigrationError('support')
}
return defaultSupportFile
}
export async function supportFilesForMigration (projectRoot: string): Promise<MigrationFile> {
const defaultSupportFile = await getDefaultLegacySupportFile(projectRoot)
const defaultOldSupportFile = path.relative(projectRoot, defaultSupportFile)
const defaultNewSupportFile = renameSupportFilePath(defaultOldSupportFile)
const afterParts = formatMigrationFile(
defaultOldSupportFile,
new RegExp(supportFileRegexps.e2e.beforeRegexp),
).map(substitute)
return {
testingType: 'e2e',
before: {
relative: defaultOldSupportFile,
parts: formatMigrationFile(defaultOldSupportFile, new RegExp(supportFileRegexps.e2e.beforeRegexp)),
},
after: {
relative: defaultNewSupportFile,
parts: afterParts,
},
}
}
export interface SpecToMove {
from: string
to: string
}
export function moveSpecFiles (projectRoot: string, specs: SpecToMove[]) {
specs.forEach((spec) => {
const from = path.join(projectRoot, spec.from)
const to = path.join(projectRoot, spec.to)
fs.moveSync(from, to)
})
}
export function renameSupportFilePath (relative: string) {
const re = /cypress\/support\/(?<name>index)\.[j|t|s[x]?/
const res = new RegExp(re).exec(relative)
if (!res?.groups?.name) {
throw new NonStandardMigrationError('support')
}
return relative.replace(res.groups.name, 'e2e')
}
export function reduceConfig (cfg: OldCypressConfig): ConfigOptions {
const excludedFields = ['pluginsFile', '$schema']
return Object.entries(cfg).reduce((acc, [key, val]) => {
if (excludedFields.includes(key)) {
return acc
}
if (key === 'e2e' || key === 'component') {
const value = val as Cypress.ResolvedConfigOptions
if (!value) {
return acc
}
const { testFiles, ignoreTestFiles, ...rest } = value
// don't include if it's the default! No need.
const specPattern = getSpecPattern(cfg, key)
const ext = '**/*.cy.{js,jsx,ts,tsx}'
const isDefaultE2E = key === 'e2e' && specPattern === `cypress/e2e/${ext}`
const isDefaultCT = key === 'component' && specPattern === ext
if (isDefaultE2E || isDefaultCT) {
return {
...acc, [key]: {
...rest,
...acc[key],
},
}
}
return {
...acc, [key]: {
...rest,
...acc[key],
specPattern,
},
}
}
if (key === 'integrationFolder') {
return {
...acc,
e2e: { ...acc.e2e, specPattern: getSpecPattern(cfg, 'e2e') },
}
}
if (key === 'componentFolder') {
return {
...acc,
component: { ...acc.component, specPattern: getSpecPattern(cfg, 'component') },
}
}
if (key === 'testFiles') {
return {
...acc,
e2e: { ...acc.e2e, specPattern: getSpecPattern(cfg, 'e2e') },
component: { ...acc.component, specPattern: getSpecPattern(cfg, 'component') },
}
}
if (key === 'ignoreTestFiles') {
return {
...acc,
e2e: { ...acc.e2e, specExcludePattern: val },
component: { ...acc.component, specExcludePattern: val },
}
}
if (key === 'supportFile') {
return {
...acc,
e2e: { ...acc.e2e, supportFile: val },
}
}
if (key === 'baseUrl') {
return {
...acc,
e2e: { ...acc.e2e, [key]: val },
}
}
return { ...acc, global: { ...acc.global, [key]: val } }
}, { global: {}, e2e: {}, component: {} })
}
// function getSpecPattern (cfg: OldCypressConfig, testingType: TestingType) {
// // `componentFolder` is no longer a thing, we are forcing the user to co-locate
// // component specs.
// if (testingType === 'component') {
// return '**/*.cy.{js,jsx,ts,tsx}'
// }
// const specPattern = cfg.e2e?.testFiles ?? cfg.testFiles ?? '**/*.cy.{js,jsx,ts,tsx}'
// const customIntegrationFolder = cfg.e2e?.integrationFolder ?? cfg.integrationFolder ?? 'cypress/e2e'
// if (customIntegrationFolder) {
// return `${customIntegrationFolder}/${specPattern}`
// }
// return specPattern
// }
function getSpecPattern (cfg: OldCypressConfig, testType: TestingType) {
const specPattern = cfg[testType]?.testFiles ?? cfg.testFiles ?? '**/*.cy.{js,jsx,ts,tsx}'
const customComponentFolder = cfg.component?.componentFolder ?? cfg.componentFolder ?? null
if (testType === 'component' && customComponentFolder) {
return specPattern
}
if (testType === 'e2e') {
const customIntegrationFolder = cfg.e2e?.integrationFolder ?? cfg.integrationFolder ?? null
if (customIntegrationFolder) {
return `${customIntegrationFolder}/${specPattern}`
}
return `cypress/e2e/${specPattern}`
}
return specPattern
}
export function formatConfig (config: string) {
return prettier.format(config, {
semi: false,
singleQuote: true,
endOfLine: 'auto',
parser: 'babel',
})
}
-2
View File
@@ -6,6 +6,4 @@ export * from './cached'
export * from './config-file-updater'
export * from './config-options'
export * from './file'
export * from './migration'
export * from './migrationFormat'
export * from './urqlCacheKeys'
-264
View File
@@ -1,264 +0,0 @@
import chokidar from 'chokidar'
import fs from 'fs-extra'
import stringify from 'stringify-object'
import path from 'path'
import globby from 'globby'
import {
supportFileRegexps,
formatMigrationFile,
} from './migrationFormat'
import type { MigrationFile } from '../sources'
import { substitute } from '../sources/migration/autoRename'
type ConfigOptions = {
global: Record<string, unknown>
e2e: Record<string, unknown>
component: Record<string, unknown>
}
/**
* config format pre-10.0
*/
export interface OldCypressConfig {
component?: Omit<OldCypressConfig, 'component' | 'e2e'>
e2e?: Omit<OldCypressConfig, 'component' | 'e2e'>
pluginsFile?: string | false
supportFile?: string | false
componentFolder?: string | false
integrationFolder?: string | false
testFiles?: string | string[]
}
export const defaultSupportFiles = {
before: path.join('cypress', 'support', 'index.js'),
}
export class NonStandardMigrationError extends Error {
constructor (fileType: 'support' | 'config') {
super()
this.message = `Failed to find default ${fileType}. Bailing automation migration.`
}
}
export async function createConfigString (cfg: OldCypressConfig) {
return createCypressConfigJs(reduceConfig(cfg), getPluginRelativePath(cfg))
}
interface FileToBeMigratedManually {
relative: string
moved: boolean
}
export interface ComponentTestingMigrationStatus {
files: Map<string, FileToBeMigratedManually>
completed: boolean
}
export function initComponentTestingMigration (
projectRoot: string,
componentFolder: string,
testFiles: string[],
onFileMoved: (status: ComponentTestingMigrationStatus) => void,
): Promise<{
status: ComponentTestingMigrationStatus
watcher: chokidar.FSWatcher
}> {
const watchPaths = testFiles.map((glob) => {
return path.join(componentFolder, glob)
})
const watcher = chokidar.watch(
watchPaths, {
cwd: projectRoot,
},
)
let filesToBeMoved: Map<string, FileToBeMigratedManually> = globby.sync(watchPaths, {
cwd: projectRoot,
}).reduce<Map<string, FileToBeMigratedManually>>((acc, relative) => {
acc.set(relative, { relative, moved: false })
return acc
}, new Map())
watcher.on('unlink', (unlinkedPath) => {
const file = filesToBeMoved.get(unlinkedPath)
if (!file) {
throw Error(`Watcher incorrectly triggered while watching ${file}`)
}
file.moved = true
const completed = Array.from(filesToBeMoved.values()).every((value) => value.moved === true)
onFileMoved({
files: filesToBeMoved,
completed,
})
})
return new Promise((resolve) => {
watcher.on('ready', () => {
resolve({
status: {
files: filesToBeMoved,
completed: false,
},
watcher,
})
})
})
}
function getPluginRelativePath (cfg: OldCypressConfig): string {
const DEFAULT_PLUGIN_PATH = path.normalize('/cypress/plugins/index.js')
return cfg.pluginsFile ? cfg.pluginsFile : DEFAULT_PLUGIN_PATH
}
function reduceConfig (cfg: OldCypressConfig): ConfigOptions {
const excludedFields = ['pluginsFile', '$schema', 'componentFolder']
return Object.entries(cfg).reduce((acc, [key, val]) => {
if (excludedFields.includes(key)) {
return acc
}
if (key === 'e2e' || key === 'component') {
const value = val as Record<string, unknown>
return { ...acc, [key]: { ...acc[key], ...value } }
}
if (key === 'testFiles') {
return {
...acc,
e2e: { ...acc.e2e, specPattern: val },
component: { ...acc.component, specPattern: val },
}
}
if (key === 'baseUrl') {
return {
...acc,
e2e: { ...acc.e2e, [key]: val },
}
}
return { ...acc, global: { ...acc.global, [key]: val } }
}, { global: {}, e2e: {}, component: {} })
}
function createCypressConfigJs (config: ConfigOptions, pluginPath: string) {
const globalString = Object.keys(config.global).length > 0 ? `\n${formatObjectForConfig(config.global, 2)},` : ''
const componentString = Object.keys(config.component).length > 0 ? createTestingTypeTemplate('component', pluginPath, config.component) : ''
const e2eString = Object.keys(config.e2e).length > 0 ? createTestingTypeTemplate('e2e', pluginPath, config.e2e) : ''
return `const { defineConfig } = require('cypress')
module.exports = defineConfig({${globalString}${e2eString}${componentString}
})`
}
function formatObjectForConfig (obj: Record<string, unknown>, spaces: number) {
return stringify(obj, {
indent: Array(spaces).fill(' ').join(''),
}).replace(/^[{]|[}]$/g, '') // remove opening and closing {}
.trim() // remove trailing spaces
}
function createTestingTypeTemplate (testingType: 'e2e' | 'component', pluginPath: string, options: Record<string, unknown>) {
return `
${testingType}: {
setupNodeEvents(on, config) {
return require('${pluginPath}')
},
${formatObjectForConfig(options, 4)}
},`
}
export interface RelativeSpec {
relative: string
}
/**
* Checks that at least one spec file exist for component testing
*
* NOTE: this is what we use to see if CT is set up
* @param projectRoot
* @param componentFolder
* @param componentGlob
* @returns
*/
export async function hasComponentSpecFile (projectRoot: string, componentFolder: string, componentGlob: string | string[]): Promise<boolean> {
return (await globby(componentGlob, {
cwd: path.join(projectRoot, componentFolder),
onlyFiles: true,
})).length > 0
}
export async function tryGetDefaultLegacySupportFile (projectRoot: string) {
const files = await globby(path.join(projectRoot, 'cypress', 'support', 'index.*'))
return files[0]
}
export async function getDefaultLegacySupportFile (projectRoot: string) {
const defaultSupportFile = await tryGetDefaultLegacySupportFile(projectRoot)
if (!defaultSupportFile) {
throw new NonStandardMigrationError('support')
}
return defaultSupportFile
}
export async function supportFilesForMigration (projectRoot: string): Promise<MigrationFile> {
const defaultSupportFile = await getDefaultLegacySupportFile(projectRoot)
const defaultOldSupportFile = path.relative(projectRoot, defaultSupportFile)
const defaultNewSupportFile = renameSupportFilePath(defaultOldSupportFile)
const afterParts = formatMigrationFile(
defaultOldSupportFile,
new RegExp(supportFileRegexps.e2e.beforeRegexp),
).map(substitute)
return {
testingType: 'e2e',
before: {
relative: defaultOldSupportFile,
parts: formatMigrationFile(defaultOldSupportFile, new RegExp(supportFileRegexps.e2e.beforeRegexp)),
},
after: {
relative: defaultNewSupportFile,
parts: afterParts,
},
}
}
export interface SpecToMove {
from: string
to: string
}
export function moveSpecFiles (projectRoot: string, specs: SpecToMove[]) {
specs.forEach((spec) => {
const from = path.join(projectRoot, spec.from)
const to = path.join(projectRoot, spec.to)
fs.moveSync(from, to)
})
}
export function renameSupportFilePath (relative: string) {
const re = /cypress\/support\/(?<name>index)\.[j|t|s[x]?/
const res = new RegExp(re).exec(relative)
if (!res?.groups?.name) {
throw new NonStandardMigrationError('support')
}
return relative.replace(res.groups.name, 'e2e')
}
@@ -113,14 +113,14 @@ describe('getSpecs', () => {
},
])
expect(actual.component).to.eql([
{
relative: 'src/Radio.spec.js',
usesDefaultFolder: false,
usesDefaultTestFiles: false,
testingType: 'component',
},
])
// expect(actual.component).to.eql([
// {
// relative: 'src/Radio.spec.js',
// usesDefaultFolder: false,
// usesDefaultTestFiles: false,
// testingType: 'component',
// },
// ])
})
it('handles default folders', async () => {
@@ -17,6 +17,14 @@ describe('shouldShowAutoRenameStep', () => {
expect(actual).to.be.true
})
it('true when testFiles is custom, but default integration folder', async () => {
const cwd = scaffoldMigrationProject('migration-e2e-component-default-test-files')
const config = fs.readJsonSync(path.join(cwd, 'cypress.json'))
const actual = await shouldShowAutoRenameStep(cwd, config)
expect(actual).to.be.true
})
it('false when integrationFolder and testFiles are custom', async () => {
const cwd = scaffoldMigrationProject('migration-e2e-fully-custom')
const config = fs.readJsonSync(path.join(cwd, 'cypress.json'))
@@ -7,8 +7,10 @@ import {
ComponentTestingMigrationStatus,
NonStandardMigrationError,
supportFilesForMigration,
reduceConfig,
renameSupportFilePath,
} from '../../../src/util/migration'
OldCypressConfig,
} from '../../../src/sources/migration'
import { expect } from 'chai'
import tempDir from 'temp-dir'
import type { e2eProjectDirs } from '@packages/frontend-shared/cypress/e2e/support/e2eProjectDirs'
@@ -31,44 +33,64 @@ function scaffoldMigrationProject (project: typeof e2eProjectDirs[number]) {
return cwd
}
const projectRoot = path.join(__dirname, '..', '..', '..', '..', '..')
describe('cypress.config.js generation', () => {
it('should create a string when passed only a global option', async () => {
const config = {
visualViewport: 300,
const config: OldCypressConfig = {
viewportWidth: 300,
}
const generatedConfig = await createConfigString(config)
const generatedConfig = await createConfigString(config, {
hasE2ESpec: true,
hasComponentTesting: false,
hasPluginsFile: true,
projectRoot,
hasTypescript: false,
})
snapshot(generatedConfig)
})
it('should create a string when passed only a e2e options', async () => {
const config = {
const config: OldCypressConfig = {
e2e: {
baseUrl: 'localhost:3000',
},
}
const generatedConfig = await createConfigString(config)
const generatedConfig = await createConfigString(config, {
hasE2ESpec: true,
hasComponentTesting: false,
hasPluginsFile: true,
projectRoot,
hasTypescript: false,
})
snapshot(generatedConfig)
})
it('should create a string when passed only a component options', async () => {
const config = {
const config: OldCypressConfig = {
component: {
retries: 2,
},
}
const generatedConfig = await createConfigString(config)
const generatedConfig = await createConfigString(config, {
hasE2ESpec: true,
hasComponentTesting: false,
hasPluginsFile: true,
projectRoot,
hasTypescript: false,
})
snapshot(generatedConfig)
})
it('should create a string for a config with global, component, and e2e options', async () => {
const config = {
visualViewport: 300,
viewportWidth: 300,
baseUrl: 'localhost:300',
e2e: {
retries: 2,
@@ -78,7 +100,13 @@ describe('cypress.config.js generation', () => {
},
}
const generatedConfig = await createConfigString(config)
const generatedConfig = await createConfigString(config, {
hasE2ESpec: true,
hasComponentTesting: false,
hasPluginsFile: true,
projectRoot,
hasTypescript: false,
})
snapshot(generatedConfig)
})
@@ -86,7 +114,13 @@ describe('cypress.config.js generation', () => {
it('should create a string when passed an empty object', async () => {
const config = {}
const generatedConfig = await createConfigString(config)
const generatedConfig = await createConfigString(config, {
hasE2ESpec: true,
hasComponentTesting: false,
hasPluginsFile: true,
projectRoot,
hasTypescript: false,
})
snapshot(generatedConfig)
})
@@ -98,7 +132,13 @@ describe('cypress.config.js generation', () => {
componentFolder: 'path/to/component/folder',
}
const generatedConfig = await createConfigString(config)
const generatedConfig = await createConfigString(config, {
hasE2ESpec: true,
hasComponentTesting: false,
hasPluginsFile: true,
projectRoot,
hasTypescript: false,
})
snapshot(generatedConfig)
})
@@ -241,3 +281,99 @@ describe('initComponentTestingMigration', () => {
await watcher.close()
})
})
describe('reduceConfig', () => {
it('should move the testFiles field to e2e and component', () => {
const config = { testFiles: '**/**.cy.js' }
const newConfig = reduceConfig(config)
expect(newConfig.e2e.specPattern).to.eq('cypress/e2e/**/**.cy.js')
expect(newConfig.component.specPattern).to.eq('**/**.cy.js')
})
it('should combine componentFolder and integrationFolder with testFiles field in component', () => {
const config = { testFiles: '**/**.cy.js', componentFolder: 'src', integrationFolder: 'cypress/integration' }
const newConfig = reduceConfig(config)
expect(newConfig.component.specPattern).to.eq(config.testFiles)
expect(newConfig.e2e.specPattern).to.eq(`${config.integrationFolder}/${config.testFiles}`)
})
it('should combine nested componentFolder and integrationFolder with testFiles field in component', () => {
const config = {
testFiles: '**/**.cy.js',
component: {
componentFolder: 'src',
},
e2e: {
integrationFolder: 'cypress/integration',
},
}
const newConfig = reduceConfig(config)
expect(newConfig.component.specPattern).to.eq(config.testFiles)
expect(newConfig.e2e.specPattern).to.eq(`${config.e2e.integrationFolder}/${config.testFiles}`)
})
it('should add custom integrationFolder to default testFiles if testFiles is not present', () => {
const config = { integrationFolder: 'cypress/custom-integration' }
const newConfig = reduceConfig(config)
expect(newConfig.e2e.specPattern).to.eq(`${config.integrationFolder}/**/*.cy.{js,jsx,ts,tsx}`)
})
it('should combine testFiles with highest specificity', () => {
const config = {
testFiles: '**/**.cy.js',
componentFolder: 'lower/specificity',
integrationFolder: 'lower/specificity',
component: {
componentFolder: 'higher/specificity',
},
e2e: {
integrationFolder: 'higher/specificity',
},
}
const newConfig = reduceConfig(config)
expect(newConfig.component.specPattern).to.eq(config.testFiles)
expect(newConfig.e2e.specPattern).to.eq(`${config.e2e.integrationFolder}/${config.testFiles}`)
})
it('should exclude integrationFolder and componentFolder', () => {
const config = {
componentFolder: 'src',
integrationFolder: 'cypress/integration',
}
const newConfig = reduceConfig(config)
// @ts-ignore field not on ConfigOptions type
expect(newConfig.global.componentFolder).to.not.exist
// @ts-ignore field not on ConfigOptions type
expect(newConfig.global.integrationFolder).to.not.exist
})
it('should rename ignoreTestFiles to specExcludePattern', () => {
const config = { ignoreTestFiles: 'path/to/**/*.js' }
const newConfig = reduceConfig(config)
expect(newConfig.e2e.specExcludePattern).to.eq(config.ignoreTestFiles)
expect(newConfig.component.specExcludePattern).to.eq(config.ignoreTestFiles)
})
it('should nest supportFile under component and e2e', () => {
const config = { supportFile: 'cypress/support/index.js' }
const newConfig = reduceConfig(config)
expect(newConfig.e2e.supportFile).to.eq(config.supportFile)
})
it('should exclude the pluginsFile', () => {
const config = { pluginsFile: 'cypress/plugins/index.js' }
const newConfig = reduceConfig(config)
// @ts-ignore field not on ConfigOptions type
expect(newConfig.global.pluginsFile).to.not.exist
})
})
@@ -2,7 +2,7 @@ import { expect } from 'chai'
import {
formatMigrationFile,
supportFileRegexps,
} from '../../../src/util/migrationFormat'
} from '../../../src/sources/migration/format'
import { regexps } from '../../../src/sources/migration/regexps'
describe('formatMigrationFile', () => {
@@ -40,6 +40,8 @@ export const e2eProjectDirs = [
'migration-e2e-defaults',
'migration-e2e-defaults-no-specs',
'migration-e2e-fully-custom',
'migration-e2e-no-plugins-support-file',
'migration-typescript-project',
'multiple-config-files-with-json',
'multiple-support-files',
'multiple-task-registrations',
+7 -1
View File
@@ -368,6 +368,9 @@ type CurrentProject implements Node & ProjectLike {
"""
errorLoadingNodeEvents: BaseError
"""Whether the project has Typescript"""
hasTypescript: Boolean
"""Whether the project has a valid config file"""
hasValidConfigFile: Boolean
@@ -611,6 +614,9 @@ type Migration {
"""whether the testFiles member is custom or not in integration"""
hasCustomIntegrationTestFiles: Boolean!
"""Whether the project has Typescript"""
hasTypescript: Boolean
"""the integration folder path used to store e2e tests"""
integrationFolder: String!
@@ -658,7 +664,7 @@ type MigrationRegexp {
"""regexp to use to rename existing specs in e2e"""
afterE2E: String!
"""regexp to identiey existing specs in component"""
"""regexp to identify existing specs in component"""
beforeComponent: String!
"""regexp to identify existing specs in e2e"""
@@ -113,6 +113,13 @@ export const CurrentProject = objectType({
},
})
t.boolean('hasTypescript', {
description: 'Whether the project has Typescript',
resolve (source, args, ctx) {
return ctx.lifecycleManager.metaState.hasTypescript
},
})
t.nonNull.list.nonNull.field('specs', {
description: 'A list of specs for the currently open testing type of a project',
type: Spec,
@@ -131,7 +131,7 @@ export const MigrationRegexp = objectType({
})
t.nonNull.string('beforeComponent', {
description: 'regexp to identiey existing specs in component',
description: 'regexp to identify existing specs in component',
})
t.nonNull.string('afterComponent', {
@@ -253,5 +253,12 @@ export const Migration = objectType({
return ctx.migration.hasComponentTesting
},
})
t.boolean('hasTypescript', {
description: 'Whether the project has Typescript',
resolve (source, args, ctx) {
return ctx.lifecycleManager.metaState.hasTypescript
},
})
},
})
+126 -9
View File
@@ -30,8 +30,20 @@ function skipCTMigration () {
cy.contains(`I'll do this later`).click()
}
function migrateAndVerifyConfig () {
function migrateAndVerifyConfig (configExtension: 'js' | 'ts' = 'js') {
cy.contains('Migrate the configuration for me').click()
cy.withCtx(async (ctx, o) => {
const configStats = await ctx.actions.file.checkIfFileExists(`cypress.config.${o.configExtension}`)
expect(configStats).to.not.be.null.and.not.be.undefined
const oldConfigStats = await ctx.actions.file.checkIfFileExists('cypress.json')
expect(oldConfigStats).to.be.null
await ctx.actions.migration.assertSuccessfulConfigMigration(o.configExtension)
}, { configExtension })
}
function finishMigrationAndContinue () {
@@ -42,17 +54,17 @@ function runAutoRename () {
cy.get('button').contains('Rename these specs for me').click()
}
function renameSupport () {
function renameSupport (lang: 'js' | 'ts' = 'js') {
cy.contains(`Rename the support file for me`).click()
// give to to finish the file rename
cy.wait(200)
cy.withCtx(async (ctx) => {
cy.withCtx(async (ctx, { lang }) => {
expect(
await ctx.actions.file.checkIfFileExists(ctx.path.join('cypress', 'support', 'e2e.js')),
await ctx.actions.file.checkIfFileExists(ctx.path.join('cypress', 'support', `e2e.${lang}`)), 'support file not renamed',
).not.to.be.null
})
}, { lang })
}
describe('Full migration flow for each project', () => {
@@ -300,19 +312,53 @@ describe('Full migration flow for each project', () => {
runAutoRename()
cy.wait(100)
cy.withCtx((ctx) => {
cy.withCtx(async (ctx) => {
['cypress/e2e/foo.cy.js'].forEach(async (spec) => {
const stats = await ctx.actions.file.checkIfFileExists(ctx.path.join(spec))
expect(stats).to.not.be.null
expect(stats, `spec file not renamed ${spec}`).to.not.be.null
})
})
renameSupport()
renameSupport('ts')
migrateAndVerifyConfig()
})
it('completes journey for migration-e2e-defaults-no-specs', () => {
it('completes journey for migration-e2e-no-plugins-support-file', () => {
startMigrationFor('migration-e2e-no-plugins-support-file')
// defaults, rename all the things
// can rename integration->e2e
cy.get(renameAutoStep).should('exist')
// no CT
cy.get(renameManualStep).should('not.exist')
// no supportFile
cy.get(renameSupportStep).should('not.exist')
cy.get(setupComponentStep).should('not.exist')
cy.get(configFileStep).should('exist')
// default testFiles but custom integration - can rename automatically
cy.get(renameAutoStep).should('exist')
// no CT
cy.get(renameManualStep).should('not.exist')
// supportFile is false - cannot migrate
cy.get(renameSupportStep).should('not.exist')
cy.get(setupComponentStep).should('not.exist')
cy.get(configFileStep).should('exist')
// Migration workflow
// before auto migration
cy.contains('cypress/integration/foo.spec.js')
// after auto migration
cy.contains('cypress/e2e/foo.cy.js')
runAutoRename()
migrateAndVerifyConfig()
})
// TODO: Do we need to consider this case?
it.skip('completes journey for migration-e2e-defaults-no-specs', () => {
startMigrationFor('migration-e2e-defaults-no-specs')
// no specs, nothing to rename?
cy.get(renameAutoStep).should('not.exist')
@@ -340,6 +386,67 @@ describe('Full migration flow for each project', () => {
migrateAndVerifyConfig()
})
it('completes journey for migration-component-testing-customized', () => {
startMigrationFor('migration-component-testing-customized')
// cannot rename anything automatically here, testFiles are customized
cy.get(renameAutoStep).should('not.exist')
cy.get(renameManualStep).should('exist')
// no supportFile rename for CT
cy.get(renameSupportStep).should('not.exist')
cy.get(setupComponentStep).should('exist')
cy.get(configFileStep).should('exist')
skipCTMigration()
migrateAndVerifyConfig()
finishMigrationAndContinue()
})
it('completes journey for migration-typescript-project', () => {
startMigrationFor('migration-typescript-project')
// defaults, rename all the things
// can rename integration->e2e
cy.get(renameAutoStep).should('exist')
// no CT
cy.get(renameManualStep).should('not.exist')
// supportFile is false - cannot migrate
cy.get(renameSupportStep).should('exist')
cy.get(setupComponentStep).should('not.exist')
cy.get(configFileStep).should('exist')
// default testFiles but custom integration - can rename automatically
cy.get(renameAutoStep).should('exist')
// no CT
cy.get(renameManualStep).should('not.exist')
// supportFile is false - cannot migrate
cy.get(renameSupportStep).should('exist')
cy.get(setupComponentStep).should('not.exist')
cy.get(configFileStep).should('exist')
// Migration workflow
// before auto migration
cy.contains('cypress/integration/foo.spec.js')
// after auto migration
cy.contains('cypress/e2e/foo.cy.js')
runAutoRename()
cy.wait(100)
cy.withCtx((ctx) => {
['cypress/e2e/foo.cy.js'].forEach(async (spec) => {
const stats = await ctx.actions.file.checkIfFileExists(ctx.path.join(spec))
expect(stats).to.not.be.null
})
})
renameSupport()
migrateAndVerifyConfig('ts')
})
})
describe('component testing migration - defaults', () => {
@@ -399,6 +506,14 @@ describe('component testing migration - defaults', () => {
describe('Migration', { viewportWidth: 1200 }, () => {
it('should create the cypress.config.js file and delete old config', () => {
startMigrationFor('migration')
// all steps
cy.get(renameAutoStep).should('exist')
cy.get(renameManualStep).should('exist')
cy.get(renameSupportStep).should('exist')
cy.get(setupComponentStep).should('exist')
cy.get(configFileStep).should('exist')
runAutoRename()
cy.findByText(`I'll do this later`).click()
cy.findByText(defaultMessages.migration.wizard.step3.button).click()
@@ -412,6 +527,8 @@ describe('Migration', { viewportWidth: 1200 }, () => {
const oldConfigStats = await ctx.actions.file.checkIfFileExists('cypress.json')
expect(oldConfigStats).to.be.null
await ctx.actions.migration.assertSuccessfulConfigMigration()
})
})
})
@@ -9,7 +9,7 @@
</CodeTag>
<i-cy-arrow-right_x16 class="h-16px w-16px inline-block icon-dark-gray-300" />
<CodeTag class="text-jade-500">
cypress.config.js
{{ fileName }}
</CodeTag>
</MigrationListItem>
<MigrationListItem v-if="props.gql.hasCustomIntegrationFolder || props.gql.hasCustomIntegrationTestFiles">
@@ -86,7 +86,7 @@
</template>
<template #jsFile>
<CodeTag class="text-jade-500">
cypress.config.js
{{ fileName }}
</CodeTag>
</template>
</i18n-t>
@@ -108,7 +108,7 @@
bg
class="bg-jade-100 text-jade-600"
>
cypress.config.js
{{ fileName }}
</CodeTag>
</template>
<template #before>
@@ -153,6 +153,7 @@ fragment ConvertConfigFile on Migration {
hasCustomIntegrationTestFiles
hasCustomComponentFolder
hasCustomComponentTestFiles
hasTypescript
}`
const props = defineProps<{
@@ -170,6 +171,8 @@ const codeBefore = computed(() => {
const codeAfter = computed(() => {
return props.gql.configAfterCode + Array(gqlCodeMaxLines.value - gqlCodeAfterLines.value).fill('\n').join('')
})
const fileName = computed(() => props.gql.hasTypescript ? 'cypress.config.ts' : 'cypress.config.js')
</script>
<style lang="scss" scoped>
@@ -1,13 +1,23 @@
import { FilePart, formatMigrationFile } from '@packages/data-context/src/util/migrationFormat'
import { regexps } from '@packages/data-context/src/sources/migration/regexps'
import type { FilePart } from '@packages/data-context/src/sources/migration'
import HighlightedFile from './HighlightedFile.vue'
describe('<HighlightedFile/>', { viewportWidth: 1119 }, () => {
it('renders expected content', () => {
const part: readonly FilePart[] = formatMigrationFile(
'cypress/integration/foo.spec.js',
new RegExp(regexps.e2e.before.defaultFolderDefaultTestFiles),
)
const part: readonly FilePart[] = [
{
highlight: false,
text: 'cypress/',
},
{
highlight: true,
group: 'folder',
text: 'integration',
},
{
highlight: false,
text: '/foo.spec.js',
},
]
cy.mount(() => (<div class="p-16px">
<HighlightedFile
@@ -1,19 +1,27 @@
import HighlightedFilesList from './HighlightedFilesList.vue'
import { FilePart, formatMigrationFile } from '@packages/data-context/src/util/migrationFormat'
import { regexps } from '@packages/data-context/src/sources/migration/regexps'
import type { FilePart } from '@packages/data-context/src/sources/migration'
describe('<HighlightedFilesList/>', { viewportWidth: 1119 }, () => {
it('renders expected content', () => {
type PropType = Readonly<Array<{ parts: readonly FilePart[] }>>
const _files = [
'cypress/integration/app.spec.js',
'cypress/integration/blog-post.spec.js',
'cypress/integration/homeSpec.spec.js',
'cypress/integration/company.spec.js',
'cypress/integration/sign-up.spec.js',
].map((x) => formatMigrationFile(x, new RegExp(regexps.e2e.before.defaultFolderDefaultTestFiles)))
const files: PropType = _files.map((x) => ({ parts: x }))
const parts: readonly FilePart[] = [
{
highlight: false,
text: 'cypress/',
},
{
highlight: true,
group: 'folder',
text: 'integration',
},
{
highlight: false,
text: '/foo.spec.js',
},
]
const files: PropType = [{ parts }]
cy.mount(() => (<div class="border mx-auto my-120px w-400px">
<HighlightedFilesList
@@ -1,4 +1,8 @@
A component tesing project without e2e. We ask the users to migrate their specs manually. `supportFile` is `false`.
# Migration Component Testing Customized
A component tesing project without e2e. We ask the users to migrate their specs manually.
The following migration steps will be used during this migration:
Steps:
@@ -8,16 +12,22 @@ Steps:
- [x] update config file
- [x] setup component testing
Output:
## Automatic Migration
```js
module.exports = {
component: {
supportFile: false,
setupNodeEvents(on, config) {
return require('./cypress/plugins/index.js')
},
specPattern: "src/**/*.cy.{js,tsx}"
}
}
```
Not used.
## Manual Files
The user will need to rename and/or move their only component specs, `button.spec.js` and `input-spec.tsx`, to their new location. As long as these files are removed, this step is considered complete. We do not verify where it went, or what the new name and and extension are.
## Rename supportFile
Not used. `supportFile: false` is set. We woudl normally put this in your `e2e.supportFile` key. We assume a top level `supportFile` is used for E2E testing. Since this project does not have E2E configured, we just skip this step.
## Update Config
The expected output is in [`expected-cypress.config.js`](./expected-cypress.config.js).
## Setup Component Testing
Users are required to reconfigure component testing, since the API is changing so much (we now use a `devServer` key in the config to start their dev server, etc).
@@ -0,0 +1,8 @@
const { defineConfig } = require('cypress')
module.exports = defineConfig({
component: {
setupNodeEvents (on, config) {},
specPattern: '**/*spec.{js,tsx}',
},
})
@@ -1,5 +1,9 @@
## Migration Component Testing Defaults
A component tesing project without e2e, and all defaults for CT. We rename their specs for them in step 1, then we ask them to move them in step 2.
The following migration steps will be used during this migration:
Steps:
- [x] automatic file rename
@@ -8,16 +12,28 @@ Steps:
- [ ] update config file
- [x] setup component testing
Output:
## Automatic Migration
```js
module.exports = {
component: {
supportFile: false,
setupNodeEvents(on, config) {
return require('./cypress/plugins/index.js')
},
specPattern: "**/*.cy.{js,jsx,ts,tsx}"
}
}
```
We rename the component specs, since they are using the default `testFiles` pattern.
Unless the user skips this step, after this step, the filesystem will be:
| Before | After|
|---|---|
| `component/button.spec.js` | `component/button.cy.js` |
| `component/input-spec.tsx` | `component/input-spec.tsx` |
## Manual Files
The user will need to rename and/or move their only component specs, `button.cy.js` and `input.cy.tsx`, to their new location. These were renamed in the previous step.
As long as these files are moved from their current location, this step is considered complete. We do not verify where it went.
## Rename supportFile
Not used. We do not do anything for `supportFile` in a component testing project.
## Update Config
The expected output is in [`expected-cypress.config.js`](./expected-cypress.config.js).
@@ -0,0 +1,7 @@
const { defineConfig } = require('cypress')
module.exports = defineConfig({
component: {
setupNodeEvents (on, config) {},
},
})
@@ -1,4 +1,8 @@
A component tesing project without e2e. We ask the users to migrate their specs manually. `supportFile` is `false`.
## Migration Component Testing
A component testing project without e2e. We ask the users to migrate their specs manually. `supportFile` is `false`.
The following migration steps will be used during this migration:
Steps:
@@ -8,16 +12,28 @@ Steps:
- [x] update config file
- [x] setup component testing
Output:
## Automatic Migration
```js
module.exports = {
component: {
supportFile: false,
setupNodeEvents(on, config) {
return require('./cypress/plugins/index.js')
},
specPattern: "src/**/*.cy.{js,tsx}"
}
}
```
Not used. This is only used for E2E projects with a default `testFiles` or `integrationFolder`, or a component project with a default `testFiles`. This one has a custom `testFiles` pattern.
## Manual Files
The user will need to rename and/or move their only component specs, `button.spec.js` and `input-spec.tsx`, to their new location. As long as these files are removed, this step is considered complete. We do not verify where it went, or what the new name and and extension are.
## Rename supportFile
Not used. `supportFile: false` is set. We woudl normally put this in your `e2e.supportFile` key. We assume a top level `supportFile` is used for E2E testing. Since this project does not have E2E configured, we just skip this step.
## Update Config
We can migrate to the new `cypress.config.js`. The expected output is in `expected-cypress.config.js`. The main points are:
- `specPattern` is nested under `component`. It's a combination of `componentFolder` + `testFiles`.
- `componentFolder` and `testFiles` are gone.
- We add an empty `setupNodeEvents` function in `component`.
The expected output is in [`expected-cypress.config.js`](./expected-cypress.config.js).
## Setup Component Testing
Users are required to reconfigure component testing, since the API is changing so much (we now use a `devServer` key in the config to start their dev server, etc).
@@ -0,0 +1,8 @@
const { defineConfig } = require('cypress')
module.exports = defineConfig({
component: {
setupNodeEvents (on, config) {},
specPattern: '**/*spec.{js,tsx}',
},
})
@@ -1,15 +1,38 @@
## Migration E2E Component Default Everything
A project with E2E and CT, both using default folders. Everything is default.
Steps:
The following migration steps will be used during this migration:
- [x] automatic file rename
- [x] automatic folder rename of cypress/integration to cypress/e2e
- [x] manual file rename
- [x] rename support
- [x] update config file
- [x] setup component testing
Output:
## Automatic Migration
```js
// TODO
```
Unless the user skips this step, after this step, the filesystem will be:
| Before | After|
|---|---|
| `integration/foo.spec.ts` | `e2e/foo.cy.ts` |
| `component/button.spec.js` | `component/button.cy.js` |
## Manual Files
The user will need to rename and/or move their only component spec, `button.cy.js` to its new location.
## Rename supportFile
The project has a default support file, `cypress/support/index.js`. We can rename it for them to `cypress/support/e2e.js`. There is no such `cypress/support/component.js` created here - that's going to part of the componen ttesting reconfiguration workflow.
| Before | After|
|---|---|
| `cypress/support/index.js` | `cypress/support/e2e.js` |
## Update Config
We can migrate to the new `cypress.config.js`. The expected output is in `expected-cypress.config.js`. The main points are:
The expected output is in [`expected-cypress.config.js`](./expected-cypress.config.js).
@@ -0,0 +1,12 @@
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
setupNodeEvents (on, config) {
return require('./cypress/plugins/index.js')(on, config)
},
},
component: {
setupNodeEvents (on, config) {},
},
})
@@ -1,15 +1,38 @@
## Migration E2E Component Default Test Files
A project with E2E and CT, both using custom folders. Everything else default.
Steps:
The following migration steps will be used during this migration:
- [x] automatic file rename
- [x] automatic folder rename of cypress/integration to cypress/e2e
- [x] manual file rename
- [x] rename support
- [x] update config file
- [x] setup component testing
Output:
## Automatic Migration
```js
// TODO
```
Unless the user skips this step, after this step, the filesystem will be:
| Before | After|
|---|---|
| `cypress/custom-integration/foo.spec.ts` | `cypress/custom-integration/foo.cy.ts` |
| `cypress/custom-component/button.spec.js` | `cypress/custom-component/button.cy.js` |
## Manual Files
The user will need to rename and/or move their only component spec, `button.cy.js` to its new location.
## Rename supportFile
The project has a default support file, `cypress/support/index.js`. We can rename it for them to `cypress/support/e2e.js`. There is no such `cypress/support/component.js` created here - that's going to part of the componen ttesting reconfiguration workflow.
| Before | After|
|---|---|
| `cypress/support/index.js` | `cypress/support/e2e.js` |
## Update Config
We can migrate to the new `cypress.config.js`. The expected output is in `expected-cypress.config.js`. The main points are:
The expected output is in [`expected-cypress.config.js`](./expected-cypress.config.js).
@@ -0,0 +1,14 @@
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
setupNodeEvents (on, config) {
return require('./cypress/plugins/index.js')(on, config)
},
specPattern: 'cypress/custom-integration/**/*.cy.{js,jsx,ts,tsx}',
},
component: {
setupNodeEvents (on, config) {},
specPattern: '**/*.cy.{js,jsx,ts,tsx}',
},
})
@@ -1,6 +1,8 @@
An e2e project with a custom testFiles but default integrationFolder. We will rename the intergrationFolder to be e2e. We keep the specs as is.
## Migration E2E Custom Integration
Steps:
An e2e project with a custom `integrationFolder` named `src`. It uses the default `testFiles`. We will keep the custom `intergrationFolder`, but it'll be part of `e2e.specPattern`. We will rename their specs to use the `.cy.js` extension.
The following migration steps will be used during this migration:
- [x] automatic file rename
- [ ] manual file rename
@@ -8,8 +10,30 @@ Steps:
- [x] update config file
- [ ] setup component testing
Output:
```js
// TODO
```
## Automatic Migration
Unless the user skips this step, after this step, the filesystem will be:
| Before | After|
|---|---|
| `src/basic.test.js` | `src/basic.cy.js` |
## Manual Files
This step is not used.
## Rename supportFile
The project has a default support file, `cypress/support/index.js`. We can rename it for them to `cypress/support/e2e.js`.
| Before | After|
|---|---|
| `cypress/support/index.js` | `cypress/support/e2e.js` |
## Update Config
We can migrate to the new `cypress.config.js`. The expected output is in `expected-cypress.config.js`. The main points are:
The expected output is in [`expected-cypress.config.js`](./expected-cypress.config.js).
@@ -0,0 +1,10 @@
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
setupNodeEvents (on, config) {
return require('./cypress/plugins/index.js')(on, config)
},
specPattern: 'src/**/*.cy.{js,jsx,ts,tsx}',
},
})
@@ -1,6 +1,8 @@
## Migration E2E Custom testFiles
An e2e project with a custom testFiles but default integrationFolder. We won't rename the specs, but we will rename `integration` to `e2e`.
Steps:
The following migration steps will be used during this migration:
- [x] automatic file rename
- [ ] manual file rename
@@ -8,16 +10,26 @@ Steps:
- [x] update config file
- [ ] setup component testing
## Automatic Migration
Output:
Unless the user skips this step, after this step, the filesystem will be:
```js
module.exports = {
e2e: {
setupNodeEvents(on, config) {
return require('./cypress/plugins/index.js')
},
specPattern: "cypress/e2e/**/*.test.js"
}
}
```
| Before | After|
|---|---|
| `integration/basic.test.js` | `e2e/basic.test.js` |
## Manual Files
This step is not used.
## Rename supportFile
The project has a default support file, `cypress/support/index.js`. We can rename it for them to `cypress/support/e2e.js`.
| Before | After|
|---|---|
| `cypress/support/index.js` | `cypress/support/e2e.js` |
## Update Config
The expected output is in [`expected-cypress.config.js`](./expected-cypress.config.js).
@@ -0,0 +1,10 @@
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
setupNodeEvents (on, config) {
return require('./cypress/plugins/index.js')(on, config)
},
specPattern: 'cypress/e2e/**/*.test.js',
},
})
@@ -1,6 +1,8 @@
## Migration E2E Defaults No Specs
An e2e project with all defaults, but no spec files. We should not show the auto rename step - nothing to rename.
Steps:
The following migration steps will be used during this migration:
- [ ] automatic file rename
- [ ] manual file rename
@@ -8,14 +10,22 @@ Steps:
- [x] update config file
- [ ] setup component testing
Output:
## Automatic Migration
```js
module.exports = {
e2e: {
setupNodeEvents(on, config) {
return require('./cypress/plugins/index.js')
},
}
}
```
This step is not used, since there are no spec files to rename. For this reason it's highly unlikely `cypress/integration` exists either - this is created only when the initial integration specs are scaffolded (pre 10.x behavior - we no longer scaffold thes automatically).
## Manual Files
This step is not used.
## Rename supportFile
The project has a default support file, `cypress/support/index.js`. We can rename it for them to `cypress/support/e2e.js`.
| Before | After|
|---|---|
| `cypress/support/index.js` | `cypress/support/e2e.js` |
## Update Config
The expected output is in [`expected-cypress.config.js`](./expected-cypress.config.js).
@@ -0,0 +1,9 @@
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
setupNodeEvents (on, config) {
return require('./cypress/plugins/index.js')(on, config)
},
},
})
@@ -1,6 +1,8 @@
An e2e project with all defaults. We should not show the auto rename step - nothing to rename.
## Migration E2E Defaults
Steps:
An e2e project with all defaults. We rename the `integrationFolder` and spec extension.
The following migration steps will be used during this migration:
- [x] automatic file rename
- [ ] manual file rename
@@ -8,14 +10,26 @@ Steps:
- [x] update config file
- [ ] setup component testing
Output:
## Automatic Migration
```js
module.exports = {
e2e: {
setupNodeEvents(on, config) {
return require('./cypress/plugins/index.js')
},
}
}
```
Unless the user skips this step, after this step, the filesystem will be:
| Before | After|
|---|---|
| `integration/basic.test.js` | `e2e/basic.cy.js` |
## Manual Files
This step is not used.
## Rename supportFile
The project has a default support file, `cypress/support/index.js`. We can rename it for them to `cypress/support/e2e.js`.
| Before | After|
|---|---|
| `cypress/support/index.js` | `cypress/support/e2e.js` |
## Update Config
The expected output is in [`expected-cypress.config.js`](./expected-cypress.config.js).
@@ -0,0 +1,9 @@
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
setupNodeEvents (on, config) {
return require('./cypress/plugins/index.js')(on, config)
},
},
})
@@ -1,6 +1,8 @@
## Migration E2E Fully Custom
A fully custom project for migration.
Steps:
The following migration steps will be used during this migration:
- [ ] automatic file rename
- [ ] manual file rename
@@ -8,16 +10,20 @@ Steps:
- [x] update config file
- [ ] setup component testing
Output:
## Automatic Migration
```js
module.exports = {
e2e: {
setupNodeEvents(on, config) {
return require('./src/the-plugin-file.js')
},
specPattern: "src/somewhere/**/*.spec.js"
supportFile: "src/some/support.js"
}
}
```
This step is not used, since both `integrationFolder` and `testFiles` are customized. We will seed the config with a `specPattern` based on these values.
## Manual Files
Not used.
## Rename supportFile
This step is not used.
The project has a custom support file, `src/some-support-file.js`. We insert this value into `e2e.supportFile` in the next step. Same for `pluginsFile`, except that is `require`d inside of `setupNodeEvents`.
## Update Config
The expected output is in [`expected-cypress.config.js`](./expected-cypress.config.js).
@@ -0,0 +1,11 @@
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
setupNodeEvents (on, config) {
return require('./src/the-plugins-file.js')(on, config)
},
specPattern: 'src/**/*.spec.js',
supportFile: 'src/some-support-file.js',
},
})
@@ -0,0 +1,31 @@
## Migration E2E No Plugins No Support
An e2e project without `plugins/index.js` or `support/index.js`. As unlikely as this is, we need to handle this case.
The following migration steps will be used during this migration:
- [x] automatic file rename
- [ ] manual file rename
- [ ] rename support
- [x] update config file
- [ ] setup component testing
## Automatic Migration
Unless the user skips this step, after this step, the filesystem will be:
| Before | After|
|---|---|
| `integration/foo.spec.js` | `e2e/basic.cy.js` |
## Manual Files
This step is not used.
## Rename supportFile
This step is not used. There is no `supportFile` to rename.
## Update Config
The expected output is in [`expected-cypress.config.js`](./expected-cypress.config.js).
@@ -0,0 +1,7 @@
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
setupNodeEvents (on, config) {},
},
})
@@ -0,0 +1,35 @@
## Migration E2E Defaults
An e2e project with all defaults. We rename the `integrationFolder` and spec extension.
The following migration steps will be used during this migration:
- [x] automatic file rename
- [ ] manual file rename
- [x] rename support
- [x] update config file
- [ ] setup component testing
## Automatic Migration
Unless the user skips this step, after this step, the filesystem will be:
| Before | After|
|---|---|
| `integration/basic.test.js` | `e2e/basic.cy.js` |
## Manual Files
This step is not used.
## Rename supportFile
The project has a default support file, `cypress/support/index.js`. We can rename it for them to `cypress/support/e2e.js`.
| Before | After|
|---|---|
| `cypress/support/index.js` | `cypress/support/e2e.js` |
## Update Config
The expected output is in [`expected-cypress.config.js`](./expected-cypress.config.js).
@@ -0,0 +1 @@
{}
@@ -0,0 +1,9 @@
import { defineConfig } from 'cypress'
export default defineConfig({
e2e: {
setupNodeEvents (on, config) {
return require('./cypress/plugins/index.js')(on, config)
},
},
})
@@ -0,0 +1 @@
{}
@@ -0,0 +1,3 @@
{
}
+41 -23
View File
@@ -1,6 +1,8 @@
This is the "kitchen sink" of migrations. It has E2E and Component Testing, a bunch of specs, and requires all the steps.
## Migration
Steps:
This is the "kitchen sink" of migrations. It has E2E and Component Testing, a bunch of specs, a complex `cypress.json`, and requires all the steps.
The following migration steps will be used during this migration:
- [x] automatic folder rename of cypress/integration to cypress/e2e
- [x] manual file rename
@@ -8,25 +10,41 @@ Steps:
- [x] update config file
- [x] setup component testing
Output:
## Automatic Migration
```js
module.exports = {
retries: 2,
baseUrl: "http://localhost:3000",
defaultCommandTimeout: 5000,
fixturesFolder: false,
component: {
specPattern: "src/**/*.spec.{tsx.js}",
slowTestThreshold: 5000,
retries: 1
},
e2e: {
defaultCommandTimeout: 10000,
slowTestThreshold: 5000
setupNodeEvents(on, config) {
return require('./cypress/plugins/index.js')
},
}
}
```
- The project has a custom `testFiles` glob, so we will not attempt to rename their specs to use the `.cy` extension. They ARE using the default `integrationFolder`, `cypress/integration`, which we can automatically rename to `cypress/e2e`.
- Their `specPattern` will be inserted in step #4 for both `component` and `e2e`.
Unless the user skips this step, after this step, the filesystem will be:
| Before | After|
|---|---|
| `integration/app_spec.js` | `e2e/app_spec.js` |
| `integration/bar.spec.js` | `e2e/bar.spec.js` |
| `integration/blog-post-spec.ts` | `e2e/blog-post-spec.ts` |
| `integration/company.js` | `e2e/company.js` |
| `integration/homeSpec.js` | `e2e/homeSpec.js` |
| `integration/sign-up.js` | `e2e/sign-up.js` |
| `integration/someDir/someFile.js` | `e2e/someDir/someFile.js` |
## Manual Files
The user will need to rename and/or move their only component spec, `Radio.spec.js` to its new location.
## Rename supportFile
The project has a default support file, `cypress/support/index.js`. We can rename it for them to `cypress/support/e2e.js`.
| Before | After|
|---|---|
| `cypress/support/index.js` | `cypress/support/e2e.js` |
## Update Config
We can migrate to the new `cypress.config.js`. The expected output is in `expected-cypress.config.js`. The main points are:
- `specPattern` is nested under `e2e` and `component`, and formed by `integrationFolder` + `testFiles` for E2E and `componentFolder` + `testFiles` for component. We do NOT use the new default of `.cy.{js,jsx,ts,tsx}` because the project already has specified a custom testFiles pattern.
- `pluginsFile` is gone - it's now required inside of `setupNodeEvents`.
- `componentFolder` is gone.
The expected output is in [`expected-cypress.config.js`](./expected-cypress.config.js).
@@ -0,0 +1,22 @@
const { defineConfig } = require('cypress')
module.exports = defineConfig({
retries: 2,
defaultCommandTimeout: 5000,
fixturesFolder: false,
e2e: {
setupNodeEvents (on, config) {
return require('./cypress/plugins/index.ts')(on, config)
},
defaultCommandTimeout: 10000,
slowTestThreshold: 5000,
baseUrl: 'http://localhost:3000',
specPattern: 'cypress/e2e/**/*.spec.{tsx,js}',
},
component: {
setupNodeEvents (on, config) {},
slowTestThreshold: 5000,
retries: 1,
specPattern: '**/*.spec.{tsx,js}',
},
})
+10 -5
View File
@@ -9121,6 +9121,11 @@
"@types/node" "*"
xmlbuilder ">=11.0.1"
"@types/prettier@2.4.3":
version "2.4.3"
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.3.tgz#a3c65525b91fca7da00ab1a3ac2b5a2a4afbffbf"
integrity sha512-QzSuZMBuG5u8HqYz01qtMdg/Jfctlnvj1z/lYnIDXs/golxw0fxtRAHd9KrzjR7Yxz1qVeI00o0kiO3PmVdJ9w==
"@types/pretty-hrtime@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/pretty-hrtime/-/pretty-hrtime-1.0.0.tgz#c5a2d644a135e988b2932f99737e67b3c62528d0"
@@ -33510,16 +33515,16 @@ preserve@^0.2.0:
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=
prettier@2.5.1, prettier@^2.2.1:
version "2.5.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a"
integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==
prettier@^1.16.4, prettier@^1.18.2:
version "1.19.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
prettier@^2.2.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.4.1.tgz#671e11c89c14a4cfc876ce564106c4a6726c9f5c"
integrity sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==
prettier@~2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5"