fix: allow migration of pluginsFile using env properties (#20770)

* fix: allow migration of code-coverage plugin

* fix: use 9.X options to default migration

* fix: remove internal values from options

* test: add unit tests to the new model

* test: e2e test all error prone defaultValues

* fix: avoid removing values in pluginsFile

* test: fix tests

* test: fix test

* fix unit test
This commit is contained in:
Barthélémy Ledoux
2022-04-01 03:42:22 -05:00
committed by GitHub
parent 1033e04bd8
commit 771f7654be
8 changed files with 431 additions and 5 deletions

View File

@@ -3,6 +3,7 @@ import path from 'path'
import { fork } from 'child_process'
import type { ForkOptions } from 'child_process'
import assert from 'assert'
import _ from 'lodash'
import type { DataContext } from '..'
import {
cleanUpIntegrationFolder,
@@ -23,10 +24,45 @@ import {
getComponentFolder,
getIntegrationTestFilesGlobs,
getSpecPattern,
legacyOptions,
} from '../sources/migration'
import { makeCoreData } from '../data'
import { LegacyPluginsIpc } from '../data/LegacyPluginsIpc'
export function getConfigWithDefaults (legacyConfig: any) {
const newConfig = _.cloneDeep(legacyConfig)
legacyOptions.forEach(({ defaultValue, name }) => {
if (defaultValue !== undefined && legacyConfig[name] === undefined) {
newConfig[name] = typeof defaultValue === 'function' ? defaultValue() : defaultValue
}
})
return newConfig
}
export function getDiff (oldConfig: any, newConfig: any) {
// get all the values updated
const result: any = _.reduce(oldConfig, (acc: any, value, key) => {
// ignore values that have been removed
if (newConfig[key] && !_.isEqual(value, newConfig[key])) {
acc[key] = newConfig[key]
}
return acc
}, {})
// get all the values added
return _.reduce(newConfig, (acc: any, value, key) => {
// their key is in the new config but not in the old config
if (!oldConfig.hasOwnProperty(key)) {
acc[key] = value
}
return acc
}, result)
}
export async function processConfigViaLegacyPlugins (projectRoot: string, legacyConfig: LegacyCypressConfigJson): Promise<LegacyCypressConfigJson> {
const pluginFile = legacyConfig.pluginsFile ?? await tryGetDefaultLegacyPluginsFile(projectRoot)
@@ -49,12 +85,23 @@ export async function processConfigViaLegacyPlugins (projectRoot: string, legacy
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))
const legacyConfigWithDefaults = getConfigWithDefaults(legacyConfig)
ipc.on('ready', () => {
ipc.send('loadLegacyPlugins', legacyConfig)
ipc.send('loadLegacyPlugins', legacyConfigWithDefaults)
})
ipc.on('loadLegacyPlugins:reply', (modifiedLegacyConfig) => {
resolve(modifiedLegacyConfig)
const diff = getDiff(legacyConfigWithDefaults, modifiedLegacyConfig)
// if env is updated by plugins, avoid adding it to the config file
if (diff.env) {
delete diff.env
}
const legacyConfigWithChanges = _.merge(legacyConfig, diff)
resolve(legacyConfigWithChanges)
ipc.childProcess.kill()
})

View File

@@ -31,6 +31,8 @@ export type LegacyCypressConfigJson = Partial<{
integrationFolder: string
testFiles: string | string[]
ignoreTestFiles: string | string[]
env: { [key: string]: any }
[index: string]: any
}>
export interface MigrationFile {

View File

@@ -4,6 +4,7 @@
export * from './autoRename'
export * from './codegen'
export * from './format'
export * from './legacyOptions'
export * from './parserUtils'
export * from './regexps'
export * from './shouldShowSteps'

View File

@@ -0,0 +1,305 @@
interface ResolvedConfigOption {
name: string
defaultValue?: any
isFolder?: boolean
isExperimental?: boolean
/**
* Can be mutated with Cypress.config() or test-specific configuration overrides
*/
canUpdateDuringTestTime?: boolean
}
interface RuntimeConfigOption {
name: string
defaultValue: any
isInternal?: boolean
/**
* Can be mutated with Cypress.config() or test-specific configuration overrides
*/
canUpdateDuringTestTime?: boolean
}
// NOTE:
// If you add/remove/change a config value, make sure to update the following
// - cli/types/index.d.ts (including allowed config options on TestOptions)
// - cypress.schema.json
//
// Add options in alphabetical order for better readability
// TODO - add boolean attribute to indicate read-only / static vs mutable options
// that can be updated during test executions
const resolvedOptions: Array<ResolvedConfigOption> = [
{
name: 'animationDistanceThreshold',
defaultValue: 5,
canUpdateDuringTestTime: true,
}, {
name: 'baseUrl',
defaultValue: null,
canUpdateDuringTestTime: true,
}, {
name: 'blockHosts',
defaultValue: null,
canUpdateDuringTestTime: true,
}, {
name: 'chromeWebSecurity',
defaultValue: true,
canUpdateDuringTestTime: false,
}, {
name: 'clientCertificates',
defaultValue: [],
canUpdateDuringTestTime: false,
}, {
name: 'component',
// runner-ct overrides
defaultValue: {},
canUpdateDuringTestTime: false,
}, {
name: 'componentFolder',
defaultValue: 'cypress/component',
isFolder: true,
canUpdateDuringTestTime: false,
}, {
name: 'defaultCommandTimeout',
defaultValue: 4000,
canUpdateDuringTestTime: true,
}, {
name: 'downloadsFolder',
defaultValue: 'cypress/downloads',
isFolder: true,
canUpdateDuringTestTime: false,
}, {
name: 'e2e',
// e2e runner overrides
defaultValue: {},
canUpdateDuringTestTime: false,
}, {
name: 'env',
defaultValue: {},
canUpdateDuringTestTime: true,
}, {
name: 'execTimeout',
defaultValue: 60000,
canUpdateDuringTestTime: true,
}, {
name: 'exit',
defaultValue: true,
canUpdateDuringTestTime: false,
}, {
name: 'experimentalFetchPolyfill',
defaultValue: false,
isExperimental: true,
canUpdateDuringTestTime: false,
}, {
name: 'experimentalInteractiveRunEvents',
defaultValue: false,
isExperimental: true,
canUpdateDuringTestTime: false,
}, {
name: 'experimentalSessionSupport',
defaultValue: false,
isExperimental: true,
canUpdateDuringTestTime: true,
}, {
name: 'experimentalSourceRewriting',
defaultValue: false,
isExperimental: true,
canUpdateDuringTestTime: false,
}, {
name: 'experimentalStudio',
defaultValue: false,
isExperimental: true,
canUpdateDuringTestTime: false,
}, {
name: 'fileServerFolder',
defaultValue: '',
isFolder: true,
canUpdateDuringTestTime: false,
}, {
name: 'fixturesFolder',
defaultValue: 'cypress/fixtures',
isFolder: true,
canUpdateDuringTestTime: false,
}, {
name: 'ignoreTestFiles',
defaultValue: '*.hot-update.js',
canUpdateDuringTestTime: true,
}, {
name: 'includeShadowDom',
defaultValue: false,
canUpdateDuringTestTime: true,
}, {
name: 'integrationFolder',
defaultValue: 'cypress/integration',
isFolder: true,
canUpdateDuringTestTime: false,
}, {
name: 'keystrokeDelay',
defaultValue: 0,
canUpdateDuringTestTime: true,
}, {
name: 'modifyObstructiveCode',
defaultValue: true,
canUpdateDuringTestTime: false,
}, {
name: 'nodeVersion',
canUpdateDuringTestTime: false,
}, {
name: 'numTestsKeptInMemory',
defaultValue: 50,
canUpdateDuringTestTime: true,
}, {
name: 'pageLoadTimeout',
defaultValue: 60000,
canUpdateDuringTestTime: true,
}, {
name: 'pluginsFile',
defaultValue: 'cypress/plugins',
isFolder: true,
canUpdateDuringTestTime: false,
}, {
name: 'port',
defaultValue: null,
canUpdateDuringTestTime: true,
}, {
name: 'projectId',
defaultValue: null,
canUpdateDuringTestTime: true,
}, {
name: 'redirectionLimit',
defaultValue: 20,
canUpdateDuringTestTime: true,
}, {
name: 'reporter',
defaultValue: 'spec',
canUpdateDuringTestTime: true,
}, {
name: 'reporterOptions',
defaultValue: null,
canUpdateDuringTestTime: true,
}, {
name: 'requestTimeout',
defaultValue: 5000,
canUpdateDuringTestTime: true,
}, {
name: 'resolvedNodePath',
defaultValue: null,
canUpdateDuringTestTime: false,
}, {
name: 'resolvedNodeVersion',
defaultValue: null,
canUpdateDuringTestTime: false,
}, {
name: 'responseTimeout',
defaultValue: 30000,
canUpdateDuringTestTime: true,
}, {
name: 'retries',
defaultValue: {
runMode: 0,
openMode: 0,
},
canUpdateDuringTestTime: true,
}, {
name: 'screenshotOnRunFailure',
defaultValue: true,
canUpdateDuringTestTime: true,
}, {
name: 'screenshotsFolder',
defaultValue: 'cypress/screenshots',
isFolder: true,
canUpdateDuringTestTime: false,
}, {
name: 'slowTestThreshold',
defaultValue: (options: Record<string, any> = {}) => options.testingType === 'component' ? 250 : 10000,
canUpdateDuringTestTime: true,
}, {
name: 'scrollBehavior',
defaultValue: 'top',
canUpdateDuringTestTime: true,
}, {
name: 'supportFile',
defaultValue: 'cypress/support',
isFolder: true,
canUpdateDuringTestTime: false,
}, {
name: 'supportFolder',
defaultValue: false,
isFolder: true,
canUpdateDuringTestTime: false,
}, {
name: 'taskTimeout',
defaultValue: 60000,
canUpdateDuringTestTime: true,
}, {
name: 'testFiles',
defaultValue: '**/*.*',
canUpdateDuringTestTime: false,
}, {
name: 'trashAssetsBeforeRuns',
defaultValue: true,
canUpdateDuringTestTime: false,
}, {
name: 'userAgent',
defaultValue: null,
canUpdateDuringTestTime: false,
}, {
name: 'video',
defaultValue: true,
canUpdateDuringTestTime: false,
}, {
name: 'videoCompression',
defaultValue: 32,
canUpdateDuringTestTime: false,
}, {
name: 'videosFolder',
defaultValue: 'cypress/videos',
isFolder: true,
canUpdateDuringTestTime: false,
}, {
name: 'videoUploadOnPasses',
defaultValue: true,
canUpdateDuringTestTime: false,
}, {
name: 'viewportHeight',
defaultValue: 660,
canUpdateDuringTestTime: true,
}, {
name: 'viewportWidth',
defaultValue: 1000,
canUpdateDuringTestTime: true,
}, {
name: 'waitForAnimations',
defaultValue: true,
canUpdateDuringTestTime: true,
}, {
name: 'watchForFileChanges',
defaultValue: true,
canUpdateDuringTestTime: false,
},
]
const runtimeOptions: Array<RuntimeConfigOption> = [
{
name: 'browsers',
defaultValue: [],
canUpdateDuringTestTime: false,
}, {
name: 'hosts',
defaultValue: null,
canUpdateDuringTestTime: false,
}, {
name: 'isInteractive',
defaultValue: true,
canUpdateDuringTestTime: false,
}, {
name: 'modifyObstructiveCode',
defaultValue: true,
canUpdateDuringTestTime: false,
},
]
export const legacyOptions: Array<ResolvedConfigOption|RuntimeConfigOption> = [
...resolvedOptions,
...runtimeOptions,
]

View File

@@ -0,0 +1,40 @@
import { expect } from 'chai'
import { getConfigWithDefaults, getDiff } from '../../../src/actions/MigrationActions'
describe('MigrationActions', () => {
context('getConfigWithDefaults', () => {
it('returns a config with defaults without touching the original', () => {
const config = {
foo: 'bar',
}
expect(getConfigWithDefaults(config)).to.have.property('env')
expect(getConfigWithDefaults(config)).to.have.property('browsers')
expect(config).not.to.have.property('env')
expect(config).not.to.have.property('browsers')
})
})
context('getDiff', () => {
it('returns all the updated values', () => {
const oldConfig = {
foo: 'bar',
other: 'config',
removed: 'value',
updated: 'oldValue',
}
const newConfig = {
foo: 'hello',
other: 'config',
updated: 'newValue',
}
const diff = getDiff(oldConfig, newConfig)
expect(diff).to.have.property('foo', 'hello')
expect(diff).to.have.property('updated', 'newValue')
expect(diff).not.to.have.property('removed')
})
})
})

View File

@@ -10,8 +10,18 @@ describe('processConfigViaLegacyPlugins', () => {
const result = await processConfigViaLegacyPlugins(projectRoot, {})
expect(result).to.eql({
integrationFolder: 'tests/e2e',
testFiles: '**/*.spec.js',
'component': {
'testFiles': '**/*.spec.ts',
},
'e2e': {
'testFiles': '**/*.js',
},
'integrationFolder': 'tests/e2e',
'retries': {
'openMode': 0,
'runMode': 1,
},
'testFiles': '**/*.spec.js',
})
})

View File

@@ -1,6 +1,23 @@
module.exports = (on, config) => {
config.integrationFolder = 'tests/e2e'
config.testFiles = '**/*.spec.js'
// In the plugin @cypress/code-coverage this pattern is used
config.env.codeCoverageRegistered = 'true'
// test that browsers is an array
config.browsers.length
// test that retries is an object
config.retries.runMode = 1
// test component is an object
config.component.testFiles = '**/*.spec.ts'
// test e2e is an object
config.e2e.testFiles = '**/*.js'
// test clientCertificates is an array
config.clientCertificates.length
return config
}

View File

@@ -1,12 +1,16 @@
const { defineConfig } = require('cypress')
module.exports = defineConfig({
retries: {
runMode: 1,
openMode: 0,
},
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',
specPattern: 'tests/e2e/**/*.js',
},
})