mirror of
https://github.com/cypress-io/cypress.git
synced 2026-05-25 10:19:30 -05:00
fix: handle case of implicit plugins/index.js files during migration (#22501)
* handle case of implicit index.js * fix test error message * fix test
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable no-dupe-class-members */
|
||||
import path from 'path'
|
||||
import debugLib from 'debug'
|
||||
import { fork } from 'child_process'
|
||||
import fs from 'fs-extra'
|
||||
import semver from 'semver'
|
||||
@@ -29,11 +30,14 @@ import {
|
||||
getSpecPattern,
|
||||
legacyOptions,
|
||||
legacyIntegrationFolder,
|
||||
getLegacyPluginsCustomFilePath,
|
||||
} from '../sources/migration'
|
||||
import { makeCoreData } from '../data'
|
||||
import { LegacyPluginsIpc } from '../data/LegacyPluginsIpc'
|
||||
import { hasTypeScriptInstalled } from '../util'
|
||||
|
||||
const debug = debugLib('cypress:data-context:MigrationActions')
|
||||
|
||||
const tsNode = require.resolve('@packages/server/lib/plugins/child/register_ts_node')
|
||||
|
||||
export function getConfigWithDefaults (legacyConfig: any) {
|
||||
@@ -71,7 +75,11 @@ export function getDiff (oldConfig: any, newConfig: any) {
|
||||
}
|
||||
|
||||
export async function processConfigViaLegacyPlugins (projectRoot: string, legacyConfig: LegacyCypressConfigJson): Promise<LegacyCypressConfigJson> {
|
||||
const pluginFile = legacyConfig.pluginsFile ?? await tryGetDefaultLegacyPluginsFile(projectRoot)
|
||||
const pluginFile = legacyConfig.pluginsFile
|
||||
? await getLegacyPluginsCustomFilePath(projectRoot, legacyConfig.pluginsFile)
|
||||
: await tryGetDefaultLegacyPluginsFile(projectRoot)
|
||||
|
||||
debug('found legacy pluginsFile at %s', pluginFile)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// couldn't find a pluginsFile
|
||||
@@ -121,10 +129,12 @@ export async function processConfigViaLegacyPlugins (projectRoot: string, legacy
|
||||
const legacyConfigWithDefaults = getConfigWithDefaults(legacyConfig)
|
||||
|
||||
ipc.on('ready', () => {
|
||||
debug('legacyConfigIpc:ready')
|
||||
ipc.send('loadLegacyPlugins', legacyConfigWithDefaults)
|
||||
})
|
||||
|
||||
ipc.on('loadLegacyPlugins:reply', (modifiedLegacyConfig) => {
|
||||
debug('loadLegacyPlugins:reply')
|
||||
const diff = getDiff(legacyConfigWithDefaults, modifiedLegacyConfig)
|
||||
|
||||
// if env is updated by plugins, avoid adding it to the config file
|
||||
@@ -139,6 +149,7 @@ export async function processConfigViaLegacyPlugins (projectRoot: string, legacy
|
||||
})
|
||||
|
||||
ipc.on('loadLegacyPlugins:error', (error) => {
|
||||
debug('loadLegacyPlugins:error')
|
||||
error = getError('LEGACY_CONFIG_ERROR_DURING_MIGRATION', cwd, error)
|
||||
|
||||
reject(error)
|
||||
@@ -146,6 +157,7 @@ export async function processConfigViaLegacyPlugins (projectRoot: string, legacy
|
||||
})
|
||||
|
||||
ipc.on('childProcess:unhandledError', (error) => {
|
||||
debug('childProcess:unhandledError')
|
||||
reject(error)
|
||||
ipc.killChildProcess()
|
||||
})
|
||||
|
||||
@@ -51,6 +51,8 @@ export async function createConfigString (cfg: LegacyCypressConfigJson, options:
|
||||
const newConfig = reduceConfig(cfg, options)
|
||||
const relativePluginPath = await getPluginRelativePath(cfg, options.projectRoot)
|
||||
|
||||
debug('creating cypress.config from newConfig %o relativePluginPath %s options %o', newConfig, relativePluginPath, options)
|
||||
|
||||
return createCypressConfig(newConfig, relativePluginPath, options)
|
||||
}
|
||||
|
||||
@@ -188,6 +190,67 @@ function formatObjectForConfig (obj: Record<string, unknown>) {
|
||||
return JSON.stringify(obj, null, 2).replace(/^[{]|[}]$/g, '') // remove opening and closing {}
|
||||
}
|
||||
|
||||
// Returns path of `pluginsFile` relative to projectRoot
|
||||
// Considers cases of:
|
||||
// 1. `pluginsFile` pointing to a directory containing an index file
|
||||
// 2. `pluginsFile` pointing to a file
|
||||
//
|
||||
// Example:
|
||||
// - projectRoot
|
||||
// --- cypress
|
||||
// ----- plugins
|
||||
// -------- index.js
|
||||
// Both { "pluginsFile": "cypress/plugins"} and { "pluginsFile": "cypress/plugins/index.js" } are valid.
|
||||
//
|
||||
// Will return `cypress/plugins/index.js` for both cases.
|
||||
export async function getLegacyPluginsCustomFilePath (projectRoot: string, pluginPath: string): Promise<string> {
|
||||
debug('looking for pluginPath %s in projectRoot %s', pluginPath, projectRoot)
|
||||
|
||||
const pluginLoc = path.join(projectRoot, pluginPath)
|
||||
|
||||
debug('fs.stats on %s', pluginLoc)
|
||||
|
||||
let stats: fs.Stats
|
||||
|
||||
try {
|
||||
stats = await fs.stat(pluginLoc)
|
||||
} catch (e) {
|
||||
throw Error(`Looked for pluginsFile at ${pluginPath}, but it was not found.`)
|
||||
}
|
||||
|
||||
if (stats.isFile()) {
|
||||
debug('found pluginsFile %s', pluginLoc)
|
||||
|
||||
return pluginPath
|
||||
}
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
// Although you are supposed to pass a file to `pluginsFile`, we also supported
|
||||
// passing a directory containing an `index` file.
|
||||
// If pluginsFile is a directory, see if there is an index.{js,ts} and grab that.
|
||||
// {
|
||||
// "pluginsFile": "plugins"
|
||||
// }
|
||||
// Where cypress/plugins contains an `index.{js,ts,coffee...}` but NOT `index.d.ts`.
|
||||
const ls = await fs.readdir(pluginLoc)
|
||||
const indexFile = ls.find((file) => file.startsWith('index.') && !file.endsWith('.d.ts'))
|
||||
|
||||
debug('pluginsFile was a directory containing %o, looks like we want %s', ls, indexFile)
|
||||
|
||||
if (indexFile) {
|
||||
const pathToIndex = path.join(pluginPath, indexFile)
|
||||
|
||||
debug('found pluginsFile %s', pathToIndex)
|
||||
|
||||
return pathToIndex
|
||||
}
|
||||
}
|
||||
|
||||
debug('error, could not find path to pluginsFile!')
|
||||
|
||||
throw Error(`Could not find pluginsFile. Received projectRoot ${projectRoot} and pluginPath: ${pluginPath}`)
|
||||
}
|
||||
|
||||
async function createE2ETemplate (pluginPath: string | undefined, createConfigOptions: CreateConfigOptions, options: Record<string, unknown>) {
|
||||
if (createConfigOptions.shouldAddCustomE2ESpecPattern && !options.specPattern) {
|
||||
options.specPattern = 'cypress/e2e/**/*.{js,jsx,ts,tsx}'
|
||||
@@ -201,8 +264,7 @@ async function createE2ETemplate (pluginPath: string | undefined, createConfigOp
|
||||
`
|
||||
}
|
||||
|
||||
const pluginFile = await fs.readFile(path.join(createConfigOptions.projectRoot, pluginPath), 'utf8')
|
||||
let relPluginsPath
|
||||
let relPluginsPath: string
|
||||
|
||||
const startsWithDotSlash = new RegExp(/^.\//)
|
||||
|
||||
@@ -212,6 +274,9 @@ async function createE2ETemplate (pluginPath: string | undefined, createConfigOp
|
||||
relPluginsPath = `'./${pluginPath}'`
|
||||
}
|
||||
|
||||
const legacyPluginFileLoc = await getLegacyPluginsCustomFilePath(createConfigOptions.projectRoot, pluginPath)
|
||||
const pluginFile = await fs.readFile(path.join(createConfigOptions.projectRoot, legacyPluginFileLoc), 'utf8')
|
||||
|
||||
const requirePlugins = hasDefaultExport(pluginFile)
|
||||
? `return require(${relPluginsPath}).default(on, config)`
|
||||
: `return require(${relPluginsPath})(on, config)`
|
||||
|
||||
@@ -1075,6 +1075,22 @@ describe('Full migration flow for each project', { retries: { openMode: 0, runMo
|
||||
checkOutcome()
|
||||
})
|
||||
|
||||
it('completes journey for migration-e2e-plugins-implicit-index-js', () => {
|
||||
startMigrationFor('migration-e2e-plugins-implicit-index-js')
|
||||
// no specs, nothing to rename?
|
||||
cy.get(renameAutoStep).should('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')
|
||||
|
||||
runAutoRename()
|
||||
renameSupport()
|
||||
migrateAndVerifyConfig()
|
||||
checkOutcome()
|
||||
})
|
||||
|
||||
it('completes journey for migration-e2e-fully-custom', () => {
|
||||
startMigrationFor('migration-e2e-fully-custom')
|
||||
// integration folder and testFiles are custom, cannot rename anything
|
||||
@@ -1698,7 +1714,8 @@ describe('Migrate custom config files', () => {
|
||||
it('shows error if plugins file do not exist', () => {
|
||||
scaffoldAndVisitLaunchpad('migration', ['--config-file', 'erroredConfigFiles/incorrectPluginsFile.json'])
|
||||
|
||||
cy.contains(`${getPathForPlatform('foo/bar')} file threw an error.`)
|
||||
cy.contains('Please ensure your pluginsFile is valid and relaunch the migration tool to migrate to Cypress version 10.0.0.')
|
||||
const err = `Looked for pluginsFile at foo/bar, but it was not found.`
|
||||
|
||||
cy.contains(err)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -37,7 +37,7 @@ export async function _e2eTestScaffold (cleanupEmpty = true) {
|
||||
await fs.writeFile(
|
||||
OUTPUT_PATH,
|
||||
`/* eslint-disable */
|
||||
// Auto-generated by ${path.basename(__filename)}
|
||||
// Auto-generated by ${path.basename(__filename)} (run yarn gulp e2eTestScaffold)
|
||||
export const fixtureDirs = [
|
||||
${allDirs
|
||||
.map((dir) => ` '${path.basename(dir)}'`).join(',\n')}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
## Migration E2E Plugins Implicit index.js
|
||||
|
||||
An e2e project with 1 spec file. The purpose of this project is to cover the case where `cypress.json` has `pluginsFile` configured to specify a folder (containing an `index.js`) instead of a file. Technically, `pluginsFile` should point to a file, but Cypress 9 also supported a folder containing an `index.js` file, too.
|
||||
|
||||
Reference issue: https://github.com/cypress-io/cypress/issues/22461
|
||||
|
||||
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
|
||||
|
||||
| Before | After|
|
||||
|---|---|
|
||||
| `cypress/integration/foo.spec.js` | `cypress/e2e/foo.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,3 @@
|
||||
{
|
||||
"pluginsFile": "cypress/plugins"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = (on, config) => config
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
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')(on, config)
|
||||
},
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user