mirror of
https://github.com/cypress-io/cypress.git
synced 2026-03-09 10:09:52 -05:00
fix(launchpad): correctly migrate projects settings config in plugins (#20509)
Co-authored-by: Barthélémy Ledoux <bart@cypress.io>
This commit is contained in:
@@ -1,22 +1,166 @@
|
||||
/* eslint-disable no-dupe-class-members */
|
||||
import path from 'path'
|
||||
import { fork } from 'child_process'
|
||||
import type { ForkOptions } from 'child_process'
|
||||
import assert from 'assert'
|
||||
import type { DataContext } from '..'
|
||||
import {
|
||||
cleanUpIntegrationFolder,
|
||||
formatConfig,
|
||||
LegacyCypressConfigJson,
|
||||
moveSpecFiles,
|
||||
NonStandardMigrationError,
|
||||
SpecToMove,
|
||||
supportFilesForMigration,
|
||||
} from '../sources'
|
||||
import {
|
||||
tryGetDefaultLegacyPluginsFile,
|
||||
supportFilesForMigration,
|
||||
hasSpecFile,
|
||||
getStepsForMigration,
|
||||
getIntegrationFolder,
|
||||
isDefaultTestFiles,
|
||||
getComponentTestFilesGlobs,
|
||||
getComponentFolder,
|
||||
getIntegrationTestFilesGlobs,
|
||||
getSpecPattern,
|
||||
} from '../sources/migration'
|
||||
import { makeCoreData } from '../data'
|
||||
import { LegacyPluginsIpc } from '../data/LegacyPluginsIpc'
|
||||
|
||||
export async function processConfigViaLegacyPlugins (projectRoot: string, legacyConfig: LegacyCypressConfigJson): Promise<LegacyCypressConfigJson> {
|
||||
const pluginFile = legacyConfig.pluginsFile ?? await tryGetDefaultLegacyPluginsFile(projectRoot)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// couldn't find a pluginsFile
|
||||
// just bail with initial config
|
||||
if (!pluginFile) {
|
||||
return resolve(legacyConfig)
|
||||
}
|
||||
|
||||
const cwd = path.join(projectRoot, pluginFile)
|
||||
|
||||
const childOptions: ForkOptions = {
|
||||
stdio: 'inherit',
|
||||
cwd: path.dirname(cwd),
|
||||
env: process.env,
|
||||
}
|
||||
|
||||
const configProcessArgs = ['--projectRoot', projectRoot, '--file', cwd]
|
||||
const CHILD_PROCESS_FILE_PATH = require.resolve('@packages/server/lib/plugins/child/require_async_child')
|
||||
const ipc = new LegacyPluginsIpc(fork(CHILD_PROCESS_FILE_PATH, configProcessArgs, childOptions))
|
||||
|
||||
ipc.on('ready', () => {
|
||||
ipc.send('loadLegacyPlugins', legacyConfig)
|
||||
})
|
||||
|
||||
ipc.on('loadLegacyPlugins:reply', (modifiedLegacyConfig) => {
|
||||
resolve(modifiedLegacyConfig)
|
||||
ipc.childProcess.kill()
|
||||
})
|
||||
|
||||
ipc.on('loadLegacyPlugins:error', (error) => {
|
||||
reject(error)
|
||||
ipc.childProcess.kill()
|
||||
})
|
||||
|
||||
ipc.on('childProcess:unhandledError', (error) => {
|
||||
reject(error)
|
||||
ipc.childProcess.kill()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export class MigrationActions {
|
||||
constructor (private ctx: DataContext) { }
|
||||
|
||||
async initialize (config: LegacyCypressConfigJson) {
|
||||
const legacyConfigForMigration = await this.setLegacyConfigForMigration(config)
|
||||
|
||||
// for testing mainly, we want to ensure the flags are reset each test
|
||||
this.resetFlags()
|
||||
|
||||
if (!this.ctx.currentProject || !legacyConfigForMigration) {
|
||||
throw Error('cannot do migration without currentProject!')
|
||||
}
|
||||
|
||||
await this.initializeFlags()
|
||||
|
||||
const legacyConfigFileExist = await this.ctx.lifecycleManager.checkIfLegacyConfigFileExist()
|
||||
const filteredSteps = await getStepsForMigration(this.ctx.currentProject, legacyConfigForMigration, Boolean(legacyConfigFileExist))
|
||||
|
||||
this.ctx.update((coreData) => {
|
||||
if (!filteredSteps[0]) {
|
||||
throw Error(`Impossible to initialize a migration. No steps fit the configuration of this project.`)
|
||||
}
|
||||
|
||||
coreData.migration.filteredSteps = filteredSteps
|
||||
coreData.migration.step = filteredSteps[0]
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Figure out all the data required for the migration UI.
|
||||
* This drives which migration steps need be shown and performed.
|
||||
*/
|
||||
private async initializeFlags () {
|
||||
const legacyConfigForMigration = this.ctx.coreData.migration.legacyConfigForMigration
|
||||
|
||||
if (!this.ctx.currentProject || !legacyConfigForMigration) {
|
||||
throw Error('Need currentProject to do migration')
|
||||
}
|
||||
|
||||
const integrationFolder = getIntegrationFolder(legacyConfigForMigration)
|
||||
const integrationTestFiles = getIntegrationTestFilesGlobs(legacyConfigForMigration)
|
||||
|
||||
const hasCustomIntegrationFolder = getIntegrationFolder(legacyConfigForMigration) !== 'cypress/integration'
|
||||
const hasCustomIntegrationTestFiles = !isDefaultTestFiles(legacyConfigForMigration, 'integration')
|
||||
|
||||
let hasE2ESpec = integrationFolder
|
||||
? await hasSpecFile(this.ctx.currentProject, integrationFolder, integrationTestFiles)
|
||||
: false
|
||||
|
||||
// if we don't find specs in the 9.X scope,
|
||||
// let's check already migrated files.
|
||||
// this allows users to stop migration halfway,
|
||||
// then to pick up where they left migration off
|
||||
if (!hasE2ESpec && (!hasCustomIntegrationTestFiles || !hasCustomIntegrationFolder)) {
|
||||
const newE2eSpecPattern = getSpecPattern(legacyConfigForMigration, 'e2e')
|
||||
|
||||
hasE2ESpec = await hasSpecFile(this.ctx.currentProject, '', newE2eSpecPattern)
|
||||
}
|
||||
|
||||
const componentFolder = getComponentFolder(legacyConfigForMigration)
|
||||
const componentTestFiles = getComponentTestFilesGlobs(legacyConfigForMigration)
|
||||
|
||||
const hasCustomComponentFolder = componentFolder !== 'cypress/component'
|
||||
const hasCustomComponentTestFiles = !isDefaultTestFiles(legacyConfigForMigration, 'component')
|
||||
|
||||
const hasComponentTesting = componentFolder
|
||||
? await hasSpecFile(this.ctx.currentProject, componentFolder, componentTestFiles)
|
||||
: false
|
||||
|
||||
this.ctx.update((coreData) => {
|
||||
coreData.migration.flags = {
|
||||
hasCustomIntegrationFolder,
|
||||
hasCustomIntegrationTestFiles,
|
||||
hasCustomComponentFolder,
|
||||
hasCustomComponentTestFiles,
|
||||
hasCustomSupportFile: false,
|
||||
hasComponentTesting,
|
||||
hasE2ESpec,
|
||||
hasPluginsFile: true,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
get configFileNameAfterMigration () {
|
||||
return this.ctx.lifecycleManager.legacyConfigFile.replace('.json', `.config.${this.ctx.lifecycleManager.metaState.hasTypescript ? 'ts' : 'js'}`)
|
||||
}
|
||||
|
||||
async createConfigFile () {
|
||||
const config = await this.ctx.migration.createConfigString()
|
||||
|
||||
this.ctx.lifecycleManager.setConfigFilePath(this.ctx.migration.configFileNameAfterMigration)
|
||||
this.ctx.lifecycleManager.setConfigFilePath(this.configFileNameAfterMigration)
|
||||
|
||||
await this.ctx.fs.writeFile(this.ctx.lifecycleManager.configFilePath, config).catch((error) => {
|
||||
throw error
|
||||
@@ -31,8 +175,15 @@ export class MigrationActions {
|
||||
this.ctx.modeOptions.configFile = this.ctx.migration.configFileNameAfterMigration
|
||||
}
|
||||
|
||||
initialize () {
|
||||
return this.ctx.migration.initialize()
|
||||
async setLegacyConfigForMigration (config: LegacyCypressConfigJson) {
|
||||
assert(this.ctx.currentProject)
|
||||
const legacyConfigForMigration = await processConfigViaLegacyPlugins(this.ctx.currentProject, config)
|
||||
|
||||
this.ctx.update((coreData) => {
|
||||
coreData.migration.legacyConfigForMigration = legacyConfigForMigration
|
||||
})
|
||||
|
||||
return legacyConfigForMigration
|
||||
}
|
||||
|
||||
async renameSpecsFolder () {
|
||||
@@ -98,8 +249,8 @@ export class MigrationActions {
|
||||
}
|
||||
|
||||
async nextStep () {
|
||||
const filteredSteps = this.ctx.migration.filteredSteps
|
||||
const index = filteredSteps.indexOf(this.ctx.migration.step)
|
||||
const filteredSteps = this.ctx.coreData.migration.filteredSteps
|
||||
const index = filteredSteps.indexOf(this.ctx.coreData.migration.step)
|
||||
|
||||
if (index === -1) {
|
||||
throw new Error('Invalid step')
|
||||
@@ -111,7 +262,9 @@ export class MigrationActions {
|
||||
const nextStep = filteredSteps[nextIndex]
|
||||
|
||||
if (nextStep) {
|
||||
this.ctx.migration.setStep(nextStep)
|
||||
this.ctx.update((coreData) => {
|
||||
coreData.migration.step = nextStep
|
||||
})
|
||||
}
|
||||
} else {
|
||||
await this.finishReconfigurationWizard()
|
||||
@@ -152,4 +305,12 @@ export class MigrationActions {
|
||||
throw Error(`Expected ${actual} to equal ${expected}`)
|
||||
}
|
||||
}
|
||||
|
||||
resetFlags () {
|
||||
this.ctx.update((coreData) => {
|
||||
const defaultFlags = makeCoreData().migration.flags
|
||||
|
||||
coreData.migration.flags = defaultFlags
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ export class ProjectActions {
|
||||
}
|
||||
|
||||
if (args.open) {
|
||||
await this.setCurrentProject(projectRoot)
|
||||
this.setCurrentProject(projectRoot).catch(this.ctx.onError)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
35
packages/data-context/src/data/LegacyPluginsIpc.ts
Normal file
35
packages/data-context/src/data/LegacyPluginsIpc.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/* eslint-disable no-dupe-class-members */
|
||||
import type { ChildProcess } from 'child_process'
|
||||
import EventEmitter from 'events'
|
||||
import type { CypressError } from '@packages/errors'
|
||||
import type { LegacyCypressConfigJson } from '../sources'
|
||||
|
||||
export class LegacyPluginsIpc extends EventEmitter {
|
||||
constructor (readonly childProcess: ChildProcess) {
|
||||
super()
|
||||
childProcess.on('message', (msg: { event: string, args: any[] }) => {
|
||||
this.emit(msg.event, ...msg.args)
|
||||
})
|
||||
|
||||
childProcess.once('disconnect', () => {
|
||||
this.emit('disconnect')
|
||||
})
|
||||
}
|
||||
|
||||
send(event: 'loadLegacyPlugins', legacyConfig: LegacyCypressConfigJson): boolean
|
||||
send (event: string, ...args: any[]) {
|
||||
if (this.childProcess.killed) {
|
||||
return false
|
||||
}
|
||||
|
||||
return this.childProcess.send({ event, args })
|
||||
}
|
||||
|
||||
on(event: 'ready', listener: () => void): this
|
||||
on(event: 'loadLegacyPlugins:error', listener: (error: CypressError) => void): this
|
||||
on(event: 'childProcess:unhandledError', listener: (legacyConfig: LegacyCypressConfigJson) => void): this
|
||||
on(event: 'loadLegacyPlugins:reply', listener: (legacyConfig: LegacyCypressConfigJson) => void): this
|
||||
on (evt: string, listener: (...args: any[]) => void) {
|
||||
return super.on(evt, listener)
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import { LoadConfigReply, SetupNodeEventsReply, ProjectConfigIpc, IpcHandler } f
|
||||
import assert from 'assert'
|
||||
import type { AllModeOptions, BreakingErrResult, BreakingOption, FoundBrowser, FullConfig, TestingType } from '@packages/types'
|
||||
import { autoBindDebug } from '../util/autoBindDebug'
|
||||
import type { LegacyCypressConfigJson } from '../sources'
|
||||
|
||||
const debug = debugLib(`cypress:lifecycle:ProjectLifecycleManager`)
|
||||
|
||||
@@ -118,6 +119,7 @@ export class ProjectLifecycleManager {
|
||||
private _cachedFullConfig: FullConfig | undefined
|
||||
|
||||
private _projectMetaState: ProjectMetaState = { ...PROJECT_META_STATE }
|
||||
_pendingMigrationInitialize?: pDefer.DeferredPromise<void>
|
||||
|
||||
constructor (private ctx: DataContext) {
|
||||
this._handlers = this.ctx._apis.configApi.getServerPluginHandlers()
|
||||
@@ -259,6 +261,7 @@ export class ProjectLifecycleManager {
|
||||
|
||||
this._projectRoot = projectRoot
|
||||
this._initializedProject = undefined
|
||||
this._pendingMigrationInitialize = pDefer()
|
||||
this.legacyPluginGuard()
|
||||
Promise.resolve(this.ctx.browser.machineBrowsers()).catch(this.onLoadError)
|
||||
this.verifyProjectRoot(projectRoot)
|
||||
@@ -272,6 +275,22 @@ export class ProjectLifecycleManager {
|
||||
|
||||
const { needsCypressJsonMigration } = this.refreshMetaState()
|
||||
|
||||
const legacyConfigPatah = path.join(projectRoot, this.legacyConfigFile)
|
||||
|
||||
if (needsCypressJsonMigration && !this.ctx.isRunMode && this.ctx.fs.existsSync(legacyConfigPatah)) {
|
||||
// we run the legacy plugins/index.js in a child process
|
||||
// and mutate the config based on the return value for migration
|
||||
// only used in open mode (cannot migrate via terminal)
|
||||
const legacyConfig = this.ctx.fs.readJsonSync(legacyConfigPatah) as LegacyCypressConfigJson
|
||||
|
||||
// should never throw, unless there existing pluginsFile errors out,
|
||||
// in which case they are attempting to migrate an already broken project.
|
||||
this.ctx.actions.migration.initialize(legacyConfig)
|
||||
.then(this._pendingMigrationInitialize?.resolve)
|
||||
.finally(() => this._pendingMigrationInitialize = undefined)
|
||||
.catch(this.onLoadError)
|
||||
}
|
||||
|
||||
this.configFileWarningCheck()
|
||||
|
||||
if (this.metaState.hasValidConfigFile) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { FoundBrowser, Editor, AllowedState, AllModeOptions, TestingType, BrowserStatus, PACKAGE_MANAGERS, AuthStateName } from '@packages/types'
|
||||
import { FoundBrowser, Editor, AllowedState, AllModeOptions, TestingType, BrowserStatus, PACKAGE_MANAGERS, AuthStateName, MIGRATION_STEPS, MigrationStep } from '@packages/types'
|
||||
import type { Bundler, FRONTEND_FRAMEWORKS } from '@packages/scaffold-config'
|
||||
import type { NexusGenEnums, NexusGenObjects } from '@packages/graphql/src/gen/nxs.gen'
|
||||
import type { App, BrowserWindow } from 'electron'
|
||||
@@ -6,6 +6,7 @@ import type { ChildProcess } from 'child_process'
|
||||
import type { SocketIOServer } from '@packages/socket'
|
||||
import type { Server } from 'http'
|
||||
import type { ErrorWrapperSource } from '@packages/errors'
|
||||
import type { LegacyCypressConfigJson } from '../sources'
|
||||
|
||||
export type Maybe<T> = T | null | undefined
|
||||
|
||||
@@ -72,9 +73,23 @@ export interface WizardDataShape {
|
||||
detectedFramework: typeof FRONTEND_FRAMEWORKS[number]['type'] | null
|
||||
}
|
||||
|
||||
export interface MigrationDataShape{
|
||||
export interface MigrationDataShape {
|
||||
// TODO: have the model of migration here
|
||||
step: NexusGenEnums['MigrationStepEnum']
|
||||
step: MigrationStep
|
||||
legacyConfigForMigration?: LegacyCypressConfigJson | null
|
||||
filteredSteps: MigrationStep[]
|
||||
flags: {
|
||||
hasCustomIntegrationFolder: boolean
|
||||
hasCustomIntegrationTestFiles: boolean
|
||||
|
||||
hasCustomComponentFolder: boolean
|
||||
hasCustomComponentTestFiles: boolean
|
||||
|
||||
hasCustomSupportFile: boolean
|
||||
hasComponentTesting: boolean
|
||||
hasE2ESpec: boolean
|
||||
hasPluginsFile: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface ElectronShape {
|
||||
@@ -115,7 +130,7 @@ export interface CoreDataShape {
|
||||
currentProject: string | null
|
||||
currentTestingType: TestingType | null
|
||||
wizard: WizardDataShape
|
||||
migration: MigrationDataShape | null
|
||||
migration: MigrationDataShape
|
||||
user: AuthenticatedUserShape | null
|
||||
electron: ElectronShape
|
||||
authState: AuthStateShape
|
||||
@@ -171,6 +186,18 @@ export function makeCoreData (modeOptions: Partial<AllModeOptions> = {}): CoreDa
|
||||
},
|
||||
migration: {
|
||||
step: 'renameAuto',
|
||||
legacyConfigForMigration: null,
|
||||
filteredSteps: [...MIGRATION_STEPS],
|
||||
flags: {
|
||||
hasCustomIntegrationFolder: false,
|
||||
hasCustomIntegrationTestFiles: false,
|
||||
hasCustomComponentFolder: false,
|
||||
hasCustomComponentTestFiles: false,
|
||||
hasCustomSupportFile: false,
|
||||
hasComponentTesting: true,
|
||||
hasE2ESpec: true,
|
||||
hasPluginsFile: true,
|
||||
},
|
||||
},
|
||||
warnings: [],
|
||||
chosenBrowser: null,
|
||||
|
||||
@@ -1,26 +1,18 @@
|
||||
import { TestingType, MIGRATION_STEPS } from '@packages/types'
|
||||
import type { TestingType } from '@packages/types'
|
||||
import type chokidar from 'chokidar'
|
||||
import path from 'path'
|
||||
import type { DataContext } from '..'
|
||||
import {
|
||||
createConfigString,
|
||||
initComponentTestingMigration,
|
||||
ComponentTestingMigrationStatus,
|
||||
tryGetDefaultLegacyPluginsFile,
|
||||
supportFilesForMigration,
|
||||
OldCypressConfig,
|
||||
hasSpecFile,
|
||||
getSpecs,
|
||||
applyMigrationTransform,
|
||||
getStepsForMigration,
|
||||
shouldShowRenameSupport,
|
||||
getIntegrationFolder,
|
||||
getPluginsFile,
|
||||
isDefaultTestFiles,
|
||||
getComponentTestFilesGlobs,
|
||||
getComponentFolder,
|
||||
getIntegrationTestFilesGlobs,
|
||||
getSpecPattern,
|
||||
} from './migration'
|
||||
|
||||
import type { FilePart } from './migration/format'
|
||||
@@ -28,6 +20,17 @@ import Debug from 'debug'
|
||||
|
||||
const debug = Debug('cypress:data-context:sources:MigrationDataSource')
|
||||
|
||||
export type LegacyCypressConfigJson = Partial<{
|
||||
component: Omit<LegacyCypressConfigJson, 'component' | 'e2e'>
|
||||
e2e: Omit<LegacyCypressConfigJson, 'component' | 'e2e'>
|
||||
pluginsFile: string | false
|
||||
supportFile: string | false
|
||||
componentFolder: string | false
|
||||
integrationFolder: string
|
||||
testFiles: string | string[]
|
||||
ignoreTestFiles: string | string[]
|
||||
}>
|
||||
|
||||
export interface MigrationFile {
|
||||
testingType: TestingType
|
||||
before: {
|
||||
@@ -40,81 +43,25 @@ 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 = flags.hasCustomIntegrationFolder
|
||||
hasCustomIntegrationTestFiles: boolean = flags.hasCustomIntegrationTestFiles
|
||||
|
||||
hasCustomComponentFolder: boolean = flags.hasCustomComponentFolder
|
||||
hasCustomComponentTestFiles: boolean = flags.hasCustomComponentTestFiles
|
||||
|
||||
hasCustomSupportFile: boolean = flags.hasCustomSupportFile
|
||||
hasComponentTesting: boolean = flags.hasComponentTesting
|
||||
hasE2ESpec: boolean = flags.hasE2ESpec
|
||||
hasPluginsFile: boolean = flags.hasPluginsFile
|
||||
|
||||
private componentTestingMigrationWatcher: chokidar.FSWatcher | null = null
|
||||
componentTestingMigrationStatus?: ComponentTestingMigrationStatus
|
||||
private _oldConfigPromise: Promise<OldCypressConfig> | null = null
|
||||
|
||||
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!')
|
||||
get legacyConfig () {
|
||||
if (!this.ctx.coreData.migration.legacyConfigForMigration) {
|
||||
throw Error(`Expected _legacyConfig to be set. Did you forget to call MigrationDataSource#initialize?`)
|
||||
}
|
||||
|
||||
this._config = null
|
||||
this._oldConfigPromise = null
|
||||
const config = await this.parseCypressConfig()
|
||||
|
||||
await this.initializeFlags()
|
||||
|
||||
const legacyConfigFileExist = await this.ctx.lifecycleManager.checkIfLegacyConfigFileExist()
|
||||
|
||||
this.filteredSteps = await getStepsForMigration(this.ctx.currentProject, config, Boolean(legacyConfigFileExist))
|
||||
|
||||
if (!this.filteredSteps[0]) {
|
||||
throw Error(`Impossible to initialize a migration. No steps fit the configuration of this project.`)
|
||||
}
|
||||
|
||||
this.setStep(this.filteredSteps[0])
|
||||
}
|
||||
|
||||
private resetFlags () {
|
||||
for (const [k, v] of Object.entries(flags)) {
|
||||
this[k as keyof typeof flags] = v
|
||||
}
|
||||
return this.ctx.coreData.migration.legacyConfigForMigration
|
||||
}
|
||||
|
||||
async getComponentTestingMigrationStatus () {
|
||||
debug('getComponentTestingMigrationStatus: start')
|
||||
const config = await this.parseCypressConfig()
|
||||
const componentFolder = getComponentFolder(this.legacyConfig)
|
||||
|
||||
const componentFolder = getComponentFolder(config)
|
||||
|
||||
if (!config || !this.ctx.currentProject) {
|
||||
if (!this.legacyConfig || !this.ctx.currentProject) {
|
||||
throw Error('Need currentProject and config to continue')
|
||||
}
|
||||
|
||||
@@ -145,7 +92,7 @@ export class MigrationDataSource {
|
||||
const { status, watcher } = await initComponentTestingMigration(
|
||||
this.ctx.currentProject,
|
||||
componentFolder,
|
||||
getComponentTestFilesGlobs(config),
|
||||
getComponentTestFilesGlobs(this.legacyConfig),
|
||||
onFileMoved,
|
||||
)
|
||||
|
||||
@@ -166,10 +113,8 @@ export class MigrationDataSource {
|
||||
throw Error('Need this.ctx.currentProject')
|
||||
}
|
||||
|
||||
const config = await this.parseCypressConfig()
|
||||
|
||||
debug('supportFilesForMigrationGuide: config %O', config)
|
||||
if (!await shouldShowRenameSupport(this.ctx.currentProject, config)) {
|
||||
debug('supportFilesForMigrationGuide: config %O', this.legacyConfig)
|
||||
if (!await shouldShowRenameSupport(this.ctx.currentProject, this.legacyConfig)) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -195,13 +140,11 @@ export class MigrationDataSource {
|
||||
throw Error(`Need this.ctx.projectRoot!`)
|
||||
}
|
||||
|
||||
const config = await this.parseCypressConfig()
|
||||
|
||||
const specs = await getSpecs(this.ctx.currentProject, config)
|
||||
const specs = await getSpecs(this.ctx.currentProject, this.legacyConfig)
|
||||
|
||||
const canBeAutomaticallyMigrated: MigrationFile[] = specs.integration.map(applyMigrationTransform).filter((spec) => spec.before.relative !== spec.after.relative)
|
||||
|
||||
const defaultComponentPattern = isDefaultTestFiles(await this.parseCypressConfig(), 'component')
|
||||
const defaultComponentPattern = isDefaultTestFiles(this.legacyConfig, 'component')
|
||||
|
||||
// Can only migration component specs if they use the default testFiles pattern.
|
||||
if (defaultComponentPattern) {
|
||||
@@ -211,12 +154,6 @@ export class MigrationDataSource {
|
||||
return canBeAutomaticallyMigrated
|
||||
}
|
||||
|
||||
async getConfig () {
|
||||
const config = await this.parseCypressConfig()
|
||||
|
||||
return JSON.stringify(config, null, 2)
|
||||
}
|
||||
|
||||
async createConfigString () {
|
||||
if (!this.ctx.currentProject) {
|
||||
throw Error('Need currentProject!')
|
||||
@@ -224,136 +161,30 @@ export class MigrationDataSource {
|
||||
|
||||
const { hasTypescript } = this.ctx.lifecycleManager.metaState
|
||||
|
||||
const config = await this.parseCypressConfig()
|
||||
|
||||
return createConfigString(config, {
|
||||
hasComponentTesting: this.hasComponentTesting,
|
||||
hasE2ESpec: this.hasE2ESpec,
|
||||
hasPluginsFile: this.hasPluginsFile,
|
||||
return createConfigString(this.legacyConfig, {
|
||||
hasComponentTesting: this.ctx.coreData.migration.flags.hasComponentTesting,
|
||||
hasE2ESpec: this.ctx.coreData.migration.flags.hasE2ESpec,
|
||||
hasPluginsFile: this.ctx.coreData.migration.flags.hasPluginsFile,
|
||||
projectRoot: this.ctx.currentProject,
|
||||
hasTypescript,
|
||||
})
|
||||
}
|
||||
|
||||
async integrationFolder () {
|
||||
const config = await this.parseCypressConfig()
|
||||
|
||||
return getIntegrationFolder(config)
|
||||
return getIntegrationFolder(this.legacyConfig)
|
||||
}
|
||||
|
||||
async componentFolder () {
|
||||
const config = await this.parseCypressConfig()
|
||||
|
||||
return getComponentFolder(config)
|
||||
}
|
||||
|
||||
private async parseCypressConfig (): Promise<OldCypressConfig> {
|
||||
if (this._config) {
|
||||
return this._config
|
||||
}
|
||||
|
||||
// avoid reading the same file over and over again before it was finished reading
|
||||
if (this.ctx.lifecycleManager.metaState.hasLegacyCypressJson && !this._oldConfigPromise) {
|
||||
const cfgPath = path.join(this.ctx.lifecycleManager?.projectRoot, this.ctx.lifecycleManager.legacyConfigFile)
|
||||
|
||||
this._oldConfigPromise = this.ctx.file.readJsonFile(cfgPath) as Promise<OldCypressConfig>
|
||||
}
|
||||
|
||||
if (this._oldConfigPromise) {
|
||||
this._config = await this._oldConfigPromise
|
||||
|
||||
this._oldConfigPromise = null
|
||||
|
||||
return this._config
|
||||
}
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
private async initializeFlags () {
|
||||
if (!this.ctx.currentProject) {
|
||||
throw Error('Need currentProject to do migration')
|
||||
}
|
||||
|
||||
const config = await this.parseCypressConfig()
|
||||
|
||||
const integrationFolder = getIntegrationFolder(config)
|
||||
const integrationTestFiles = getIntegrationTestFilesGlobs(config)
|
||||
|
||||
this.hasCustomIntegrationFolder = getIntegrationFolder(config) !== 'cypress/integration'
|
||||
this.hasCustomIntegrationTestFiles = !isDefaultTestFiles(config, 'integration')
|
||||
|
||||
if (integrationFolder === false) {
|
||||
this.hasE2ESpec = false
|
||||
} else {
|
||||
this.hasE2ESpec = await hasSpecFile(
|
||||
this.ctx.currentProject,
|
||||
integrationFolder,
|
||||
integrationTestFiles,
|
||||
)
|
||||
|
||||
// if we don't find specs in the 9.X scope,
|
||||
// let's check already migrated files.
|
||||
// this allows users to stop migration halfway,
|
||||
// then to pick up where they left migration off
|
||||
if (!this.hasE2ESpec && (!this.hasCustomIntegrationTestFiles || !this.hasCustomIntegrationFolder)) {
|
||||
const newE2eSpecPattern = getSpecPattern(config, 'e2e')
|
||||
|
||||
this.hasE2ESpec = await hasSpecFile(
|
||||
this.ctx.currentProject,
|
||||
'',
|
||||
newE2eSpecPattern,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const componentFolder = getComponentFolder(config)
|
||||
const componentTestFiles = getComponentTestFilesGlobs(config)
|
||||
|
||||
this.hasCustomComponentFolder = componentFolder !== 'cypress/component'
|
||||
this.hasCustomComponentTestFiles = !isDefaultTestFiles(config, 'component')
|
||||
|
||||
if (componentFolder === false) {
|
||||
this.hasComponentTesting = false
|
||||
} else {
|
||||
this.hasComponentTesting = await hasSpecFile(
|
||||
this.ctx.currentProject,
|
||||
componentFolder,
|
||||
componentTestFiles,
|
||||
)
|
||||
|
||||
// We cannot check already migrated component specs since it would pick up e2e specs as well
|
||||
// the default specPattern for CT is **/*.cy.js.
|
||||
// since component testing has to be re-installed anyway, we can just skip this
|
||||
}
|
||||
|
||||
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 {
|
||||
return this._step
|
||||
return getComponentFolder(this.legacyConfig)
|
||||
}
|
||||
|
||||
async closeManualRenameWatcher () {
|
||||
if (this.componentTestingMigrationWatcher) {
|
||||
debug('setStep: stopping watcher')
|
||||
await this.componentTestingMigrationWatcher.close()
|
||||
this.componentTestingMigrationWatcher = null
|
||||
}
|
||||
}
|
||||
|
||||
setStep (step: MIGRATION_STEP) {
|
||||
this._step = step
|
||||
}
|
||||
|
||||
get configFileNameAfterMigration () {
|
||||
return this.ctx.lifecycleManager.legacyConfigFile.replace('.json', `.config.${this.ctx.lifecycleManager.metaState.hasTypescript ? 'ts' : 'js'}`)
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@ import {
|
||||
getIntegrationFolder,
|
||||
getIntegrationTestFilesGlobs,
|
||||
isDefaultTestFiles,
|
||||
OldCypressConfig,
|
||||
regexps,
|
||||
} from '.'
|
||||
import type { MigrationFile } from '../MigrationDataSource'
|
||||
import type { LegacyCypressConfigJson } from '..'
|
||||
|
||||
export interface MigrationSpec {
|
||||
relative: string
|
||||
@@ -102,7 +102,7 @@ export function applyMigrationTransform (
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSpecs (projectRoot: string, config: OldCypressConfig): Promise<GetSpecs> {
|
||||
export async function getSpecs (projectRoot: string, config: LegacyCypressConfigJson): Promise<GetSpecs> {
|
||||
const integrationFolder = getIntegrationFolder(config)
|
||||
const integrationTestFiles = getIntegrationTestFilesGlobs(config)
|
||||
|
||||
@@ -112,21 +112,21 @@ export async function getSpecs (projectRoot: string, config: OldCypressConfig):
|
||||
let integrationSpecs: MigrationSpec[] = []
|
||||
let componentSpecs: MigrationSpec[] = []
|
||||
|
||||
const globs = integrationFolder === false
|
||||
? []
|
||||
: integrationFolder === 'cypress/integration'
|
||||
const globs = integrationFolder
|
||||
? integrationFolder === 'cypress/integration'
|
||||
? ['**/*.{js,ts,jsx,tsx,coffee}'].map((glob) => `${integrationFolder}/${glob}`)
|
||||
: integrationTestFiles.map((glob) => `${integrationFolder}/${glob}`)
|
||||
: []
|
||||
|
||||
let specs = integrationFolder === false
|
||||
? []
|
||||
: (await globby(globs, { onlyFiles: true, cwd: projectRoot }))
|
||||
let specs = integrationFolder
|
||||
? (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) {
|
||||
if (fullyCustom) {
|
||||
integrationSpecs = []
|
||||
} else {
|
||||
integrationSpecs = specs.map((relative) => {
|
||||
|
||||
@@ -12,6 +12,7 @@ import { toPosix } from '../../util'
|
||||
import Debug from 'debug'
|
||||
import dedent from 'dedent'
|
||||
import { hasDefaultExport } from './parserUtils'
|
||||
import type { LegacyCypressConfigJson } from '..'
|
||||
|
||||
const debug = Debug('cypress:data-context:sources:migration:codegen')
|
||||
|
||||
@@ -26,25 +27,6 @@ type ResolvedConfigOptions = Cypress.ResolvedConfigOptions & {
|
||||
ignoreTestFiles: string | string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 class NonStandardMigrationError extends Error {
|
||||
constructor (fileType: 'support' | 'config') {
|
||||
super()
|
||||
@@ -60,7 +42,7 @@ export interface CreateConfigOptions {
|
||||
hasTypescript: boolean
|
||||
}
|
||||
|
||||
export async function createConfigString (cfg: OldCypressConfig, options: CreateConfigOptions) {
|
||||
export async function createConfigString (cfg: LegacyCypressConfigJson, options: CreateConfigOptions) {
|
||||
const newConfig = reduceConfig(cfg)
|
||||
const relativePluginPath = await getPluginRelativePath(cfg, options.projectRoot)
|
||||
|
||||
@@ -156,8 +138,8 @@ export async function initComponentTestingMigration (
|
||||
})
|
||||
}
|
||||
|
||||
async function getPluginRelativePath (cfg: OldCypressConfig, projectRoot: string): Promise<string> {
|
||||
return cfg.pluginsFile ? cfg.pluginsFile : await tryGetDefaultLegacyPluginsFile(projectRoot) || ''
|
||||
async function getPluginRelativePath (cfg: LegacyCypressConfigJson, projectRoot: string): Promise<string | undefined> {
|
||||
return cfg.pluginsFile ? cfg.pluginsFile : await tryGetDefaultLegacyPluginsFile(projectRoot)
|
||||
}
|
||||
|
||||
// If they are running an old version of Cypress
|
||||
@@ -178,7 +160,7 @@ function defineConfigAvailable (projectRoot: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function createCypressConfig (config: ConfigOptions, pluginPath: string, options: CreateConfigOptions): string {
|
||||
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) : ''
|
||||
const e2eString = options.hasE2ESpec
|
||||
@@ -212,8 +194,8 @@ function formatObjectForConfig (obj: Record<string, unknown>) {
|
||||
return JSON.stringify(obj, null, 2).replace(/^[{]|[}]$/g, '') // remove opening and closing {}
|
||||
}
|
||||
|
||||
function createE2ETemplate (pluginPath: string, createConfigOptions: CreateConfigOptions, options: Record<string, unknown>) {
|
||||
if (!createConfigOptions.hasPluginsFile) {
|
||||
function createE2ETemplate (pluginPath: string | undefined, createConfigOptions: CreateConfigOptions, options: Record<string, unknown>) {
|
||||
if (!createConfigOptions.hasPluginsFile || !pluginPath) {
|
||||
return dedent`
|
||||
e2e: {
|
||||
setupNodeEvents(on, config) {},${formatObjectForConfig(options)}
|
||||
@@ -357,7 +339,7 @@ export function renameSupportFilePath (relative: string) {
|
||||
return relative.slice(0, res.index) + relative.slice(res.index).replace(res.groups.supportFileName, 'e2e')
|
||||
}
|
||||
|
||||
export function reduceConfig (cfg: OldCypressConfig): ConfigOptions {
|
||||
export function reduceConfig (cfg: LegacyCypressConfigJson): ConfigOptions {
|
||||
const excludedFields = ['pluginsFile', '$schema']
|
||||
|
||||
return Object.entries(cfg).reduce((acc, [key, val]) => {
|
||||
@@ -446,7 +428,7 @@ export function reduceConfig (cfg: OldCypressConfig): ConfigOptions {
|
||||
}, { global: {}, e2e: {}, component: {} })
|
||||
}
|
||||
|
||||
export function getSpecPattern (cfg: OldCypressConfig, testType: TestingType) {
|
||||
export function getSpecPattern (cfg: LegacyCypressConfigJson, testType: TestingType) {
|
||||
const specPattern = cfg[testType]?.testFiles ?? cfg.testFiles ?? '**/*.cy.{js,jsx,ts,tsx}'
|
||||
const customComponentFolder = cfg.component?.componentFolder ?? cfg.componentFolder ?? null
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import globby from 'globby'
|
||||
import path from 'path'
|
||||
import { MIGRATION_STEPS } from '@packages/types'
|
||||
import { applyMigrationTransform, getSpecs, OldCypressConfig, tryGetDefaultLegacySupportFile } from '.'
|
||||
import { applyMigrationTransform, getSpecs, tryGetDefaultLegacySupportFile } from '.'
|
||||
import type { LegacyCypressConfigJson } from '..'
|
||||
|
||||
function getTestFilesGlobs (config: OldCypressConfig, type: 'component' | 'integration'): string[] {
|
||||
function getTestFilesGlobs (config: LegacyCypressConfigJson, 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'
|
||||
@@ -17,15 +18,15 @@ function getTestFilesGlobs (config: OldCypressConfig, type: 'component' | 'integ
|
||||
return ['**/*.{js,ts,jsx,tsx,coffee}']
|
||||
}
|
||||
|
||||
export function getIntegrationTestFilesGlobs (config: OldCypressConfig): string[] {
|
||||
export function getIntegrationTestFilesGlobs (config: LegacyCypressConfigJson): string[] {
|
||||
return getTestFilesGlobs(config, 'integration')
|
||||
}
|
||||
|
||||
export function getComponentTestFilesGlobs (config: OldCypressConfig): string[] {
|
||||
export function getComponentTestFilesGlobs (config: LegacyCypressConfigJson): string[] {
|
||||
return getTestFilesGlobs(config, 'component')
|
||||
}
|
||||
|
||||
export function isDefaultTestFiles (config: OldCypressConfig, type: 'component' | 'integration') {
|
||||
export function isDefaultTestFiles (config: LegacyCypressConfigJson, type: 'component' | 'integration') {
|
||||
const testFiles = type === 'component'
|
||||
? getComponentTestFilesGlobs(config)
|
||||
: getIntegrationTestFilesGlobs(config)
|
||||
@@ -33,7 +34,7 @@ export function isDefaultTestFiles (config: OldCypressConfig, type: 'component'
|
||||
return testFiles.length === 1 && testFiles[0] === '**/*.{js,ts,jsx,tsx,coffee}'
|
||||
}
|
||||
|
||||
export function getPluginsFile (config: OldCypressConfig) {
|
||||
export function getPluginsFile (config: LegacyCypressConfigJson) {
|
||||
if (config.e2e?.pluginsFile === false || config.pluginsFile === false) {
|
||||
return false
|
||||
}
|
||||
@@ -41,15 +42,11 @@ export function getPluginsFile (config: OldCypressConfig) {
|
||||
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
|
||||
}
|
||||
|
||||
export function getIntegrationFolder (config: LegacyCypressConfigJson) {
|
||||
return config.e2e?.integrationFolder ?? config.integrationFolder ?? 'cypress/integration'
|
||||
}
|
||||
|
||||
export function getComponentFolder (config: OldCypressConfig) {
|
||||
export function getComponentFolder (config: LegacyCypressConfigJson) {
|
||||
if (config.component?.componentFolder === false || config.componentFolder === false) {
|
||||
return false
|
||||
}
|
||||
@@ -63,7 +60,7 @@ async function hasSpecFiles (projectRoot: string, dir: string, testFilesGlob: st
|
||||
return f.length > 0
|
||||
}
|
||||
|
||||
export async function shouldShowAutoRenameStep (projectRoot: string, config: OldCypressConfig) {
|
||||
export async function shouldShowAutoRenameStep (projectRoot: string, config: LegacyCypressConfigJson) {
|
||||
const specsToAutoMigrate = await getSpecs(projectRoot, config)
|
||||
|
||||
const integrationCleaned = specsToAutoMigrate.integration.filter((spec) => {
|
||||
@@ -82,7 +79,7 @@ export async function shouldShowAutoRenameStep (projectRoot: string, config: Old
|
||||
return integrationCleaned.length > 0 || componentCleaned.length > 0
|
||||
}
|
||||
|
||||
async function anyComponentSpecsExist (projectRoot: string, config: OldCypressConfig) {
|
||||
async function anyComponentSpecsExist (projectRoot: string, config: LegacyCypressConfigJson) {
|
||||
const componentFolder = getComponentFolder(config)
|
||||
|
||||
if (componentFolder === false) {
|
||||
@@ -94,13 +91,9 @@ async function anyComponentSpecsExist (projectRoot: string, config: OldCypressCo
|
||||
return hasSpecFiles(projectRoot, componentFolder, componentTestFiles)
|
||||
}
|
||||
|
||||
async function anyIntegrationSpecsExist (projectRoot: string, config: OldCypressConfig) {
|
||||
async function anyIntegrationSpecsExist (projectRoot: string, config: LegacyCypressConfigJson) {
|
||||
const integrationFolder = getIntegrationFolder(config)
|
||||
|
||||
if (integrationFolder === false) {
|
||||
return false
|
||||
}
|
||||
|
||||
const integrationTestFiles = getIntegrationTestFilesGlobs(config)
|
||||
|
||||
return hasSpecFiles(projectRoot, integrationFolder, integrationTestFiles)
|
||||
@@ -111,7 +104,7 @@ async function anyIntegrationSpecsExist (projectRoot: string, config: OldCypress
|
||||
// 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) {
|
||||
export async function shouldShowRenameSupport (projectRoot: string, config: LegacyCypressConfigJson) {
|
||||
if (!await anyIntegrationSpecsExist(projectRoot, config)) {
|
||||
return false
|
||||
}
|
||||
@@ -140,7 +133,7 @@ export async function shouldShowRenameSupport (projectRoot: string, config: OldC
|
||||
|
||||
// if they have component testing configured using the defaults, they will need to
|
||||
// rename/move their specs.
|
||||
async function shouldShowRenameManual (projectRoot: string, config: OldCypressConfig) {
|
||||
async function shouldShowRenameManual (projectRoot: string, config: LegacyCypressConfigJson) {
|
||||
const componentFolder = getComponentFolder(config)
|
||||
|
||||
const usingAllDefaults = componentFolder === 'cypress/component' && isDefaultTestFiles(config, 'component')
|
||||
@@ -152,11 +145,16 @@ async function shouldShowRenameManual (projectRoot: string, config: OldCypressCo
|
||||
return anyComponentSpecsExist(projectRoot, config)
|
||||
}
|
||||
|
||||
// All projects must move from cypress.json to cypress.config.js!
|
||||
export function shouldShowConfigFileStep (config: LegacyCypressConfigJson) {
|
||||
return true
|
||||
}
|
||||
|
||||
export type Step = typeof MIGRATION_STEPS[number]
|
||||
|
||||
export async function getStepsForMigration (
|
||||
projectRoot: string,
|
||||
config: OldCypressConfig,
|
||||
config: LegacyCypressConfigJson,
|
||||
configFileExists: boolean,
|
||||
): Promise<Step[]> {
|
||||
const steps: Step[] = []
|
||||
|
||||
@@ -9,8 +9,11 @@ import type { BrowserApiShape } from '../../src/sources/BrowserDataSource'
|
||||
import type { AppApiShape, AuthApiShape, ElectronApiShape, LocalSettingsApiShape, ProjectApiShape } from '../../src/actions'
|
||||
import { InjectedConfigApi } from '../../src/data'
|
||||
|
||||
export function getSystemTestProject (project: typeof e2eProjectDirs[number]) {
|
||||
return path.join(__dirname, '..', '..', '..', '..', 'system-tests', 'projects', project)
|
||||
type SystemTestProject = typeof e2eProjectDirs[number]
|
||||
type SystemTestProjectPath<T extends SystemTestProject> = `${string}/system-tests/projects/${T}`
|
||||
|
||||
export function getSystemTestProject<T extends typeof e2eProjectDirs[number]> (project: T): SystemTestProjectPath<T> {
|
||||
return path.join(__dirname, '..', '..', '..', '..', 'system-tests', 'projects', project) as SystemTestProjectPath<T>
|
||||
}
|
||||
|
||||
export async function scaffoldMigrationProject (project: typeof e2eProjectDirs[number]) {
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
supportFilesForMigration,
|
||||
reduceConfig,
|
||||
renameSupportFilePath,
|
||||
OldCypressConfig,
|
||||
} from '../../../../src/sources/migration'
|
||||
import { expect } from 'chai'
|
||||
import { MigrationFile } from '../../../../src/sources'
|
||||
@@ -19,7 +18,7 @@ const projectRoot = getSystemTestProject('migration-e2e-defaults')
|
||||
|
||||
describe('cypress.config.js generation', () => {
|
||||
it('should create a string when passed only a global option', async () => {
|
||||
const config: OldCypressConfig = {
|
||||
const config: Partial<Cypress.Config> = {
|
||||
viewportWidth: 300,
|
||||
}
|
||||
|
||||
@@ -35,7 +34,7 @@ describe('cypress.config.js generation', () => {
|
||||
})
|
||||
|
||||
it('should create a string when passed only a e2e options', async () => {
|
||||
const config: OldCypressConfig = {
|
||||
const config: Partial<Cypress.Config> = {
|
||||
e2e: {
|
||||
baseUrl: 'localhost:3000',
|
||||
},
|
||||
@@ -53,7 +52,7 @@ describe('cypress.config.js generation', () => {
|
||||
})
|
||||
|
||||
it('should create a string when passed only a component options', async () => {
|
||||
const config: OldCypressConfig = {
|
||||
const config: Partial<Cypress.Config> = {
|
||||
component: {
|
||||
retries: 2,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { expect } from 'chai'
|
||||
import fs from 'fs-extra'
|
||||
import path from 'path'
|
||||
import { processConfigViaLegacyPlugins } from '../../../../src/actions'
|
||||
import { getSystemTestProject } from '../../helper'
|
||||
|
||||
describe('processConfigViaLegacyPlugins', () => {
|
||||
it('executes legacy plugins and returns modified config', async () => {
|
||||
const projectRoot = getSystemTestProject('migration-e2e-plugins-modify-config')
|
||||
const result = await processConfigViaLegacyPlugins(projectRoot, {})
|
||||
|
||||
expect(result).to.eql({
|
||||
integrationFolder: 'tests/e2e',
|
||||
testFiles: '**/*.spec.js',
|
||||
})
|
||||
})
|
||||
|
||||
it('executes legacy plugins and returns without change if pluginsFile returns nothing', async () => {
|
||||
const projectRoot = getSystemTestProject('migration-e2e-defaults')
|
||||
const configFile = fs.readJsonSync(path.join(projectRoot, 'cypress.json'))
|
||||
const result = await processConfigViaLegacyPlugins(projectRoot, configFile)
|
||||
|
||||
expect(result).to.eql(configFile)
|
||||
})
|
||||
|
||||
it('works with cypress/plugins/index.ts and export default', async () => {
|
||||
const projectRoot = getSystemTestProject('migration-e2e-export-default')
|
||||
const result = await processConfigViaLegacyPlugins(projectRoot, {
|
||||
retries: 10,
|
||||
viewportWidth: 8888,
|
||||
})
|
||||
|
||||
expect(result).to.eql({
|
||||
retries: 10,
|
||||
viewportWidth: 1111, // mutated in plugins file
|
||||
})
|
||||
})
|
||||
|
||||
it('catches error', (done) => {
|
||||
const projectRoot = getSystemTestProject('migration-e2e-legacy-plugins-throws-error')
|
||||
|
||||
processConfigViaLegacyPlugins(projectRoot, {})
|
||||
.catch((e) => {
|
||||
expect(e.originalError.message).to.eq('Uh oh, there was an error!')
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('handles pluginsFile: false', async () => {
|
||||
const projectRoot = getSystemTestProject('launchpad')
|
||||
const result = await processConfigViaLegacyPlugins(projectRoot, {
|
||||
retries: 10,
|
||||
viewportWidth: 8888,
|
||||
})
|
||||
|
||||
expect(result).to.eql({
|
||||
retries: 10,
|
||||
viewportWidth: 8888,
|
||||
})
|
||||
})
|
||||
})
|
||||
45
packages/errors/__snapshot-html__/LEGACY_CONFIG_ERROR_DURING_MIGRATION.html
generated
Normal file
45
packages/errors/__snapshot-html__/LEGACY_CONFIG_ERROR_DURING_MIGRATION.html
generated
Normal file
@@ -0,0 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Courier+Prime&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
|
||||
body {
|
||||
font-family: "Courier Prime", Courier, monospace;
|
||||
padding: 0 1em;
|
||||
line-height: 1.4;
|
||||
color: #eee;
|
||||
background-color: #111;
|
||||
}
|
||||
pre {
|
||||
padding: 0 0;
|
||||
margin: 0 0;
|
||||
font-family: "Courier Prime", Courier, monospace;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 5px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body><pre><span style="color:#e05561">Your <span style="color:#e5e510">cypress/plugins/index.js<span style="color:#e05561"> at <span style="color:#4ec4ff">cypress/plugins/index.js<span style="color:#e05561"> threw an error. <span style="color:#e6e6e6">
|
||||
<span style="color:#e05561"><span style="color:#e6e6e6">
|
||||
<span style="color:#e05561">Please ensure your pluginsFile is valid and relaunch the migration tool to migrate to Cypress version 10.0.0.<span style="color:#e6e6e6">
|
||||
<span style="color:#e05561"><span style="color:#e6e6e6">
|
||||
<span style="color:#c062de"><span style="color:#e6e6e6">
|
||||
<span style="color:#c062de">Error: fail whale<span style="color:#e6e6e6">
|
||||
<span style="color:#c062de"> at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)<span style="color:#e6e6e6">
|
||||
<span style="color:#c062de"> at LEGACY_CONFIG_ERROR_DURING_MIGRATION (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)<span style="color:#e6e6e6"></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>
|
||||
</pre></body></html>
|
||||
@@ -1166,6 +1166,14 @@ export const AllCypressErrors = {
|
||||
`
|
||||
},
|
||||
|
||||
LEGACY_CONFIG_ERROR_DURING_MIGRATION: (file: string, error: Error) => {
|
||||
return errTemplate`
|
||||
Your ${fmt.highlight(file)} at ${fmt.path(`${file}`)} threw an error. ${fmt.stackTrace(error)}
|
||||
|
||||
Please ensure your pluginsFile is valid and relaunch the migration tool to migrate to ${fmt.cypressVersion('10.0.0')}.
|
||||
`
|
||||
},
|
||||
|
||||
LEGACY_CONFIG_FILE: (baseFileName: string, projectRoot: string, legacyConfigFile: string = 'cypress.json') => {
|
||||
return errTemplate`
|
||||
There is both a ${fmt.highlight(baseFileName)} and a ${fmt.highlight(legacyConfigFile)} file at the location below:
|
||||
|
||||
@@ -297,6 +297,13 @@ describe('visual error templates', () => {
|
||||
|
||||
// testVisualErrors('CANNOT_RECORD_NO_PROJECT_ID', {
|
||||
testVisualErrors(errorType, {
|
||||
LEGACY_CONFIG_ERROR_DURING_MIGRATION: () => {
|
||||
const err = makeErr()
|
||||
|
||||
return {
|
||||
default: ['cypress/plugins/index.js', err],
|
||||
}
|
||||
},
|
||||
CANNOT_TRASH_ASSETS: () => {
|
||||
const err = makeErr()
|
||||
|
||||
|
||||
@@ -58,7 +58,9 @@ export const e2eProjectDirs = [
|
||||
'migration-e2e-export-default',
|
||||
'migration-e2e-false-plugins-support-file',
|
||||
'migration-e2e-fully-custom',
|
||||
'migration-e2e-legacy-plugins-throws-error',
|
||||
'migration-e2e-no-plugins-support-file',
|
||||
'migration-e2e-plugins-modify-config',
|
||||
'migration-specs-already-migrated',
|
||||
'migration-typescript-project',
|
||||
'module-api',
|
||||
|
||||
@@ -550,6 +550,7 @@ enum ErrorTypeEnum {
|
||||
INVALID_CYPRESS_INTERNAL_ENV
|
||||
INVALID_REPORTER_NAME
|
||||
INVOKED_BINARY_OUTSIDE_NPM_MODULE
|
||||
LEGACY_CONFIG_ERROR_DURING_MIGRATION
|
||||
LEGACY_CONFIG_FILE
|
||||
MIGRATION_ALREADY_OCURRED
|
||||
MULTIPLE_SUPPORT_FILES_FOUND
|
||||
|
||||
@@ -20,15 +20,15 @@ export const MigrationStep = objectType({
|
||||
t.nonNull.boolean('isCurrentStep', {
|
||||
description: 'This is the current step',
|
||||
resolve: (source, args, ctx) => {
|
||||
return ctx.migration.step === source.name
|
||||
return ctx.coreData.migration.step === source.name
|
||||
},
|
||||
})
|
||||
|
||||
t.nonNull.boolean('isCompleted', {
|
||||
description: 'Has the current step been completed',
|
||||
resolve: (source, args, ctx) => {
|
||||
const indexOfObservedStep = ctx.migration.filteredSteps.indexOf(source.name)
|
||||
const indexOfCurrentStep = ctx.migration.filteredSteps.indexOf(ctx.migration.step)
|
||||
const indexOfObservedStep = ctx.coreData.migration.filteredSteps.indexOf(source.name)
|
||||
const indexOfCurrentStep = ctx.coreData.migration.filteredSteps.indexOf(ctx.coreData.migration.step)
|
||||
|
||||
return indexOfObservedStep < indexOfCurrentStep
|
||||
},
|
||||
@@ -37,7 +37,7 @@ export const MigrationStep = objectType({
|
||||
t.nonNull.int('index', {
|
||||
description: 'Index of the step in the list',
|
||||
resolve: (source, args, ctx) => {
|
||||
return ctx.migration.filteredSteps.indexOf(source.name) + 1
|
||||
return ctx.coreData.migration.filteredSteps.indexOf(source.name) + 1
|
||||
},
|
||||
})
|
||||
},
|
||||
@@ -148,7 +148,7 @@ export const Migration = objectType({
|
||||
type: MigrationStep,
|
||||
description: 'Steps filtered with the current context',
|
||||
resolve: (source, args, ctx) => {
|
||||
return ctx.migration.filteredSteps.map((name) => {
|
||||
return ctx.coreData.migration.filteredSteps.map((name) => {
|
||||
return {
|
||||
name,
|
||||
}
|
||||
@@ -171,7 +171,7 @@ export const Migration = objectType({
|
||||
type: ManualMigration,
|
||||
resolve: async (source, args, ctx) => {
|
||||
// avoid starting the watcher when not on this step
|
||||
if (ctx.migration.step !== 'renameManual') {
|
||||
if (ctx.coreData.migration.step !== 'renameManual') {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ export const Migration = objectType({
|
||||
t.nonNull.string('configBeforeCode', {
|
||||
description: 'contents of the cypress.json file before conversion',
|
||||
resolve: (source, args, ctx) => {
|
||||
return ctx.migration.getConfig()
|
||||
return JSON.stringify(ctx.coreData.migration.legacyConfigForMigration, null, 2)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -240,7 +240,7 @@ export const Migration = objectType({
|
||||
t.nonNull.boolean('hasCustomIntegrationFolder', {
|
||||
description: 'whether the integration folder is custom or not',
|
||||
resolve: (source, args, ctx) => {
|
||||
return ctx.migration.hasCustomIntegrationFolder
|
||||
return ctx.coreData.migration.flags.hasCustomIntegrationFolder
|
||||
}
|
||||
,
|
||||
})
|
||||
@@ -248,28 +248,28 @@ export const Migration = objectType({
|
||||
t.nonNull.boolean('hasCustomIntegrationTestFiles', {
|
||||
description: 'whether the testFiles member is custom or not in integration',
|
||||
resolve: (source, args, ctx) => {
|
||||
return ctx.migration.hasCustomIntegrationTestFiles
|
||||
return ctx.coreData.migration.flags.hasCustomIntegrationTestFiles
|
||||
},
|
||||
})
|
||||
|
||||
t.nonNull.boolean('hasCustomComponentFolder', {
|
||||
description: 'whether the component folder is custom or not',
|
||||
resolve: (source, args, ctx) => {
|
||||
return ctx.migration.hasCustomComponentFolder
|
||||
return ctx.coreData.migration.flags.hasCustomComponentFolder
|
||||
},
|
||||
})
|
||||
|
||||
t.nonNull.boolean('hasCustomComponentTestFiles', {
|
||||
description: 'whether the testFiles member is custom or not in component testing',
|
||||
resolve: (source, args, ctx) => {
|
||||
return ctx.migration.hasCustomComponentTestFiles
|
||||
return ctx.coreData.migration.flags.hasCustomComponentTestFiles
|
||||
},
|
||||
})
|
||||
|
||||
t.nonNull.boolean('hasComponentTesting', {
|
||||
description: 'whether component testing is set up in the migrated config or not',
|
||||
resolve: (source, args, ctx) => {
|
||||
return ctx.migration.hasComponentTesting
|
||||
return ctx.coreData.migration.flags.hasComponentTesting
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -475,7 +475,7 @@ export const mutation = mutationType({
|
||||
description: 'Initialize the migration wizard to the first step',
|
||||
type: Query,
|
||||
resolve: async (_, args, ctx) => {
|
||||
await ctx.actions.migration.initialize()
|
||||
await ctx.lifecycleManager._pendingMigrationInitialize?.promise
|
||||
|
||||
return {}
|
||||
},
|
||||
|
||||
@@ -18,10 +18,14 @@ Cypress.Commands.add('waitForWizard', () => {
|
||||
return cy.get('[data-cy="migration-wizard"]')
|
||||
})
|
||||
|
||||
function startMigrationFor (project: typeof e2eProjectDirs[number], argv?: string[]) {
|
||||
function scaffoldAndVisitLaunchpad (project: typeof e2eProjectDirs[number], argv?: string[]) {
|
||||
cy.scaffoldProject(project)
|
||||
cy.openProject(project, argv)
|
||||
cy.visitLaunchpad()
|
||||
}
|
||||
|
||||
function startMigrationFor (project: typeof e2eProjectDirs[number], argv?: string[]) {
|
||||
scaffoldAndVisitLaunchpad(project, argv)
|
||||
cy.waitForWizard()
|
||||
}
|
||||
|
||||
@@ -523,6 +527,21 @@ describe('Full migration flow for each project', { retries: { openMode: 2, runMo
|
||||
checkOutcome()
|
||||
})
|
||||
|
||||
it('completes journey for migration-e2e-plugins-modify-config', () => {
|
||||
startMigrationFor('migration-e2e-plugins-modify-config')
|
||||
// No rename, integrationFolder and testFiles are custom (via plugins)
|
||||
cy.get(renameAutoStep).should('not.exist')
|
||||
// no CT
|
||||
cy.get(renameManualStep).should('not.exist')
|
||||
cy.get(renameSupportStep).should('exist')
|
||||
cy.get(setupComponentStep).should('not.exist')
|
||||
cy.get(configFileStep).should('exist')
|
||||
|
||||
renameSupport()
|
||||
migrateAndVerifyConfig()
|
||||
checkOutcome()
|
||||
})
|
||||
|
||||
it('completes journey for migration-e2e-no-plugins-support-file', () => {
|
||||
startMigrationFor('migration-e2e-no-plugins-support-file')
|
||||
// defaults, rename all the things
|
||||
@@ -813,6 +832,22 @@ describe('Full migration flow for each project', { retries: { openMode: 2, runMo
|
||||
migrateAndVerifyConfig()
|
||||
})
|
||||
})
|
||||
|
||||
it('completes journey for migration-e2e-legacy-plugins-throws-error', () => {
|
||||
scaffoldAndVisitLaunchpad('migration-e2e-legacy-plugins-throws-error')
|
||||
// no steps are shown - we show the error that surfaced when executing pluginsFile.
|
||||
cy.get(renameAutoStep).should('not.exist')
|
||||
cy.get(renameManualStep).should('not.exist')
|
||||
cy.get(renameSupportStep).should('not.exist')
|
||||
cy.get(setupComponentStep).should('not.exist')
|
||||
cy.get(configFileStep).should('not.exist')
|
||||
|
||||
cy.contains('Error Loading Config')
|
||||
// correct location of error
|
||||
cy.get('[data-testid="error-code-frame"]').contains(`cypress/plugins/index.js:2:9`)
|
||||
// correct error from pluginsFile
|
||||
cy.contains(`throw Error('Uh oh, there was an error!')`)
|
||||
})
|
||||
})
|
||||
|
||||
// TODO: toLaunchpad emitter not working in Cypress in Cypress.
|
||||
@@ -1095,18 +1130,14 @@ describe('Migrate custom config files', () => {
|
||||
})
|
||||
|
||||
it('shows error for migration-custom-config-file-migration-already-ocurred', () => {
|
||||
cy.scaffoldProject('migration-custom-config-file-migration-already-ocurred')
|
||||
cy.openProject('migration-custom-config-file-migration-already-ocurred', ['--config-file', 'customConfig.json'])
|
||||
cy.visitLaunchpad()
|
||||
scaffoldAndVisitLaunchpad('migration-custom-config-file-migration-already-ocurred', ['--config-file', 'customConfig.json'])
|
||||
|
||||
cy.contains('You are attempting to use Cypress with an older config file: customConfig.json')
|
||||
cy.contains('When you upgraded to Cypress v10.0 the config file was updated and moved to a new location: customConfig.config.js')
|
||||
})
|
||||
|
||||
it('shows error for migration-custom-config-file-with-existing-v10-config-file', () => {
|
||||
cy.scaffoldProject('migration-custom-config-file-with-existing-v10-config-file')
|
||||
cy.openProject('migration-custom-config-file-with-existing-v10-config-file', ['--config-file', 'customConfig.json'])
|
||||
cy.visitLaunchpad()
|
||||
scaffoldAndVisitLaunchpad('migration-custom-config-file-with-existing-v10-config-file', ['--config-file', 'customConfig.json'])
|
||||
|
||||
cy.contains('There is both a customConfig.config.js and a customConfig.json file at the location below:')
|
||||
cy.contains('ypress no longer supports customConfig.json, please remove it from your project.')
|
||||
|
||||
@@ -8,14 +8,14 @@ const { RunPlugins } = require('./run_plugins')
|
||||
let tsRegistered = false
|
||||
|
||||
/**
|
||||
* Executes and returns the passed `configFile` file in the ipc `loadConfig` event
|
||||
* Executes and returns the passed `file` (usually `configFile`) file in the ipc `loadConfig` event
|
||||
* @param {*} ipc Inter Process Communication protocol
|
||||
* @param {*} configFile the file we are trying to load
|
||||
* @param {*} file the file we are trying to load
|
||||
* @param {*} projectRoot the root of the typescript project (useful mainly for tsnode)
|
||||
* @returns
|
||||
*/
|
||||
function run (ipc, configFile, projectRoot) {
|
||||
debug('configFile:', configFile)
|
||||
function run (ipc, file, projectRoot) {
|
||||
debug('configFile:', file)
|
||||
debug('projectRoot:', projectRoot)
|
||||
if (!projectRoot) {
|
||||
throw new Error('Unexpected: projectRoot should be a string')
|
||||
@@ -23,7 +23,7 @@ function run (ipc, configFile, projectRoot) {
|
||||
|
||||
if (!tsRegistered) {
|
||||
debug('register typescript for required file')
|
||||
tsNodeUtil.register(projectRoot, configFile)
|
||||
tsNodeUtil.register(projectRoot, file)
|
||||
|
||||
// ensure typescript is only registered once
|
||||
tsRegistered = true
|
||||
@@ -55,7 +55,7 @@ function run (ipc, configFile, projectRoot) {
|
||||
const isValidSetupNodeEvents = (config, testingType) => {
|
||||
if (config[testingType] && config[testingType].setupNodeEvents && typeof config[testingType].setupNodeEvents !== 'function') {
|
||||
ipc.send('setupTestingType:error', util.serializeError(
|
||||
require('@packages/errors').getError('SETUP_NODE_EVENTS_IS_NOT_FUNCTION', configFile, testingType, config[testingType].setupNodeEvents),
|
||||
require('@packages/errors').getError('SETUP_NODE_EVENTS_IS_NOT_FUNCTION', file, testingType, config[testingType].setupNodeEvents),
|
||||
))
|
||||
|
||||
return false
|
||||
@@ -72,16 +72,73 @@ function run (ipc, configFile, projectRoot) {
|
||||
}
|
||||
|
||||
ipc.send('setupTestingType:error', util.serializeError(
|
||||
require('@packages/errors').getError('CONFIG_FILE_DEV_SERVER_IS_NOT_A_FUNCTION', configFile, config),
|
||||
require('@packages/errors').getError('CONFIG_FILE_DEV_SERVER_IS_NOT_A_FUNCTION', file, config),
|
||||
))
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
ipc.on('loadLegacyPlugins', async (legacyConfig) => {
|
||||
try {
|
||||
let legacyPlugins = require(file)
|
||||
|
||||
if (legacyPlugins && typeof legacyPlugins.default === 'function') {
|
||||
legacyPlugins = legacyPlugins.default
|
||||
}
|
||||
|
||||
// invalid or empty plugins file
|
||||
if (typeof legacyPlugins !== 'function') {
|
||||
ipc.send('loadLegacyPlugins:reply', legacyConfig)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// we do not want to execute any tasks - the purpose
|
||||
// of this is to get any modified config returned
|
||||
// by plugins.
|
||||
const noop = () => {}
|
||||
const legacyPluginsConfig = await legacyPlugins(noop, legacyConfig)
|
||||
|
||||
// pluginsFile did not return the config - this is allowed, although
|
||||
// we recommend returning it in our docs.
|
||||
if (!legacyPluginsConfig) {
|
||||
ipc.send('loadLegacyPlugins:reply', legacyConfig)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// match merging strategy from 9.x
|
||||
const mergedLegacyConfig = {
|
||||
...legacyConfig,
|
||||
...legacyPluginsConfig,
|
||||
}
|
||||
|
||||
if (legacyConfig.e2e || legacyPluginsConfig.e2e) {
|
||||
mergedLegacyConfig.e2e = {
|
||||
...(legacyConfig.e2e || {}),
|
||||
...(legacyPluginsConfig.e2e || {}),
|
||||
}
|
||||
}
|
||||
|
||||
if (legacyConfig.component || legacyPluginsConfig.component) {
|
||||
mergedLegacyConfig.component = {
|
||||
...(legacyConfig.component || {}),
|
||||
...(legacyPluginsConfig.component || {}),
|
||||
}
|
||||
}
|
||||
|
||||
ipc.send('loadLegacyPlugins:reply', mergedLegacyConfig)
|
||||
} catch (e) {
|
||||
ipc.send('loadLegacyPlugins:error', util.serializeError(
|
||||
require('@packages/errors').getError('LEGACY_CONFIG_ERROR_DURING_MIGRATION', file, e),
|
||||
))
|
||||
}
|
||||
})
|
||||
|
||||
ipc.on('loadConfig', () => {
|
||||
try {
|
||||
debug('try loading', configFile)
|
||||
const exp = require(configFile)
|
||||
debug('try loading', file)
|
||||
const exp = require(file)
|
||||
|
||||
const result = exp.default || exp
|
||||
|
||||
@@ -102,7 +159,7 @@ function run (ipc, configFile, projectRoot) {
|
||||
|
||||
debug(`setupTestingType %s %o`, testingType, options)
|
||||
|
||||
const runPlugins = new RunPlugins(ipc, projectRoot, configFile)
|
||||
const runPlugins = new RunPlugins(ipc, projectRoot, file)
|
||||
|
||||
if (!isValidSetupNodeEvents(result, testingType)) {
|
||||
return
|
||||
@@ -134,7 +191,7 @@ function run (ipc, configFile, projectRoot) {
|
||||
}
|
||||
})
|
||||
|
||||
debug('loaded config from %s %o', configFile, result)
|
||||
debug('loaded config from %s %o', file, result)
|
||||
} catch (err) {
|
||||
if (err.name === 'TSError') {
|
||||
// because of this https://github.com/TypeStrong/ts-node/issues/1418
|
||||
@@ -153,7 +210,7 @@ function run (ipc, configFile, projectRoot) {
|
||||
}
|
||||
|
||||
ipc.send('loadConfig:error', util.serializeError(
|
||||
require('@packages/errors').getError('CONFIG_FILE_REQUIRE_ERROR', configFile, err),
|
||||
require('@packages/errors').getError('CONFIG_FILE_REQUIRE_ERROR', file, err),
|
||||
))
|
||||
}
|
||||
})
|
||||
|
||||
@@ -17,4 +17,6 @@ export type CodeLanguage = typeof CODE_LANGUAGES[number]
|
||||
|
||||
export const MIGRATION_STEPS = ['renameAuto', 'renameManual', 'renameSupport', 'configFile', 'setupComponent'] as const
|
||||
|
||||
export type MigrationStep = typeof MIGRATION_STEPS[number]
|
||||
|
||||
export const PACKAGE_MANAGERS = ['npm', 'yarn', 'pnpm'] as const
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const { defineConfig } = require('cypress')
|
||||
|
||||
module.exports = defineConfig({
|
||||
test: 'value',
|
||||
e2e: {
|
||||
// We've imported your old cypress plugins here.
|
||||
// You may want to clean this up later by importing these.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const { defineConfig } = require('cypress')
|
||||
|
||||
module.exports = defineConfig({
|
||||
test: 'value',
|
||||
e2e: {
|
||||
// We've imported your old cypress plugins here.
|
||||
// You may want to clean this up later by importing these.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const { defineConfig } = require('cypress')
|
||||
|
||||
module.exports = defineConfig({
|
||||
test: 'value',
|
||||
e2e: {
|
||||
// We've imported your old cypress plugins here.
|
||||
// You may want to clean this up later by importing these.
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
module.exports = (on, config) => {
|
||||
return {
|
||||
test: 'value',
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = (on, config) => {
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
export default function (on, config) {
|
||||
export default async function (on, config) {
|
||||
// eslint-disable-next-line
|
||||
const foo: number = 123 // to make sure the parser handles TS
|
||||
|
||||
const asyncViewport = () => Promise.resolve(1111)
|
||||
|
||||
// make sure we consider that config can be mutated
|
||||
// asynchronously in plugins
|
||||
config.viewportWidth = await asyncViewport()
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { defineConfig } from 'cypress'
|
||||
|
||||
export default defineConfig({
|
||||
viewportWidth: 1111,
|
||||
e2e: {
|
||||
// We've imported your old cypress plugins here.
|
||||
// You may want to clean this up later by importing these.
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
## Migration E2E Legact Plugins Throws Error
|
||||
|
||||
An e2e project where `cypress/plugins/index.js` throws an error during migration.
|
||||
|
||||
The following migration steps will be used during this migration:
|
||||
|
||||
- [ ] automatic file rename
|
||||
- [ ] manual file rename
|
||||
- [ ] rename support
|
||||
- [ ] update config file
|
||||
- [ ] setup component testing
|
||||
|
||||
## Automatic Migration
|
||||
|
||||
Not shown - we don't get this far, an error is throw.
|
||||
|
||||
## Manual Files
|
||||
|
||||
Not shown - we don't get this far, an error is throw.
|
||||
|
||||
## Rename supportFile
|
||||
|
||||
Not shown - we don't get this far, an error is throw.
|
||||
|
||||
## Update Config
|
||||
|
||||
Not shown - we don't get this far, an error is throw.
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = (on, config) => {
|
||||
throw Error('Uh oh, there was an error!')
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
const { defineConfig } = require('cypress')
|
||||
|
||||
module.exports = defineConfig({
|
||||
e2e: {
|
||||
// We've imported your old cypress plugins here.
|
||||
// You may want to clean this up later by importing these.
|
||||
setupNodeEvents (on, config) {
|
||||
return require('./cypress/plugins/index.js')(on, config)
|
||||
},
|
||||
specPattern: 'tests/e2e/**/*.spec.js',
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,31 @@
|
||||
## Migration E2E Plugins Modify Config
|
||||
|
||||
An e2e project where `cypress/plugins/index.js` modifies the `config`, specifically `integrationFolder` and `testFiles`.
|
||||
|
||||
The following migration steps will be used during this migration:
|
||||
|
||||
- [ ] automatic file rename
|
||||
- [ ] manual file rename
|
||||
- [x] rename support
|
||||
- [x] update config file
|
||||
- [ ] setup component testing
|
||||
|
||||
## Automatic Migration
|
||||
|
||||
We do not show this step because both `integrationFolder` and `testFiles` are custom (via plugins).
|
||||
|
||||
## 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,6 @@
|
||||
module.exports = (on, config) => {
|
||||
config.integrationFolder = 'tests/e2e'
|
||||
config.testFiles = '**/*.spec.js'
|
||||
|
||||
return config
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
const { defineConfig } = require('cypress')
|
||||
|
||||
module.exports = defineConfig({
|
||||
e2e: {
|
||||
// We've imported your old cypress plugins here.
|
||||
// You may want to clean this up later by importing these.
|
||||
setupNodeEvents (on, config) {
|
||||
return require('./cypress/plugins/index.js')(on, config)
|
||||
},
|
||||
specPattern: 'tests/e2e/**/*.spec.js',
|
||||
},
|
||||
})
|
||||
@@ -4,6 +4,7 @@ module.exports = defineConfig({
|
||||
retries: 2,
|
||||
defaultCommandTimeout: 5000,
|
||||
fixturesFolder: false,
|
||||
test: 'value',
|
||||
e2e: {
|
||||
// We've imported your old cypress plugins here.
|
||||
// You may want to clean this up later by importing these.
|
||||
|
||||
Reference in New Issue
Block a user