mirror of
https://github.com/cypress-io/cypress.git
synced 2026-02-21 22:50:49 -06:00
fix: throw when writing to 'read only' properties of config (#18896)
Co-authored-by: Emily Rohrbough <emilyrohrbough@users.noreply.github.com>
This commit is contained in:
6
cli/types/cypress.d.ts
vendored
6
cli/types/cypress.d.ts
vendored
@@ -328,7 +328,7 @@ declare namespace Cypress {
|
||||
// 60000
|
||||
```
|
||||
*/
|
||||
config<K extends keyof ConfigOptions>(key: K): ResolvedConfigOptions[K]
|
||||
config<K extends keyof Config>(key: K): Config[K]
|
||||
/**
|
||||
* Sets one configuration value.
|
||||
* @see https://on.cypress.io/config
|
||||
@@ -337,7 +337,7 @@ declare namespace Cypress {
|
||||
Cypress.config('viewportWidth', 800)
|
||||
```
|
||||
*/
|
||||
config<K extends keyof ConfigOptions>(key: K, value: ResolvedConfigOptions[K]): void
|
||||
config<K extends keyof TestConfigOverrides>(key: K, value: TestConfigOverrides[K]): void
|
||||
/**
|
||||
* Sets multiple configuration values at once.
|
||||
* @see https://on.cypress.io/config
|
||||
@@ -2879,7 +2879,7 @@ declare namespace Cypress {
|
||||
xhrUrl: string
|
||||
}
|
||||
|
||||
interface TestConfigOverrides extends Partial<Pick<ConfigOptions, 'animationDistanceThreshold' | 'baseUrl' | 'defaultCommandTimeout' | 'env' | 'execTimeout' | 'includeShadowDom' | 'requestTimeout' | 'responseTimeout' | 'retries' | 'scrollBehavior' | 'taskTimeout' | 'viewportHeight' | 'viewportWidth' | 'waitForAnimations'>> {
|
||||
interface TestConfigOverrides extends Partial<Pick<ConfigOptions, 'animationDistanceThreshold' | 'baseUrl' | 'blockHosts' | 'defaultCommandTimeout' | 'env' | 'execTimeout' | 'includeShadowDom' | 'numTestsKeptInMemory' | 'pageLoadTimeout' | 'redirectionLimit' | 'requestTimeout' | 'responseTimeout' | 'retries' | 'screenshotOnRunFailure' | 'slowTestThreshold' | 'scrollBehavior' | 'taskTimeout' | 'viewportHeight' | 'viewportWidth' | 'waitForAnimations'>> {
|
||||
browser?: IsBrowserMatcher | IsBrowserMatcher[]
|
||||
keystrokeDelay?: number
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ exports['src/index .getDefaultValues returns list of public config keys 1'] = {
|
||||
"e2e": {},
|
||||
"env": {},
|
||||
"execTimeout": 60000,
|
||||
"exit": true,
|
||||
"experimentalFetchPolyfill": false,
|
||||
"experimentalInteractiveRunEvents": false,
|
||||
"experimentalSessionSupport": false,
|
||||
@@ -33,6 +34,8 @@ exports['src/index .getDefaultValues returns list of public config keys 1'] = {
|
||||
"ignoreTestFiles": "*.hot-update.js",
|
||||
"includeShadowDom": false,
|
||||
"integrationFolder": "cypress/integration",
|
||||
"isInteractive": true,
|
||||
"keystrokeDelay": 0,
|
||||
"modifyObstructiveCode": true,
|
||||
"numTestsKeptInMemory": 50,
|
||||
"pageLoadTimeout": 60000,
|
||||
@@ -97,6 +100,7 @@ exports['src/index .getPublicConfigKeys returns list of public config keys 1'] =
|
||||
"e2e",
|
||||
"env",
|
||||
"execTimeout",
|
||||
"exit",
|
||||
"experimentalFetchPolyfill",
|
||||
"experimentalInteractiveRunEvents",
|
||||
"experimentalSessionSupport",
|
||||
@@ -107,6 +111,7 @@ exports['src/index .getPublicConfigKeys returns list of public config keys 1'] =
|
||||
"ignoreTestFiles",
|
||||
"includeShadowDom",
|
||||
"integrationFolder",
|
||||
"keystrokeDelay",
|
||||
"modifyObstructiveCode",
|
||||
"nodeVersion",
|
||||
"numTestsKeptInMemory",
|
||||
@@ -142,5 +147,6 @@ exports['src/index .getPublicConfigKeys returns list of public config keys 1'] =
|
||||
"watchForFileChanges",
|
||||
"browsers",
|
||||
"hosts",
|
||||
"isInteractive",
|
||||
"modifyObstructiveCode"
|
||||
]
|
||||
|
||||
@@ -154,4 +154,4 @@ Expected \`mockConfigKey\` to be a fully qualified URL (starting with \`http://\
|
||||
|
||||
exports['empty string'] = `
|
||||
Expected \`mockConfigKey\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`""\`
|
||||
`
|
||||
`
|
||||
@@ -20,6 +20,7 @@ const breakingKeys = _.map(breakingOptions, 'name')
|
||||
const defaultValues = createIndex(options, 'name', 'defaultValue')
|
||||
const publicConfigKeys = _(options).reject({ isInternal: true }).map('name').value()
|
||||
const validationRules = createIndex(options, 'name', 'validation')
|
||||
const testConfigOverrideOptions = createIndex(options, 'name', 'canUpdateDuringTestTime')
|
||||
|
||||
module.exports = {
|
||||
allowed: (obj = {}) => {
|
||||
@@ -101,4 +102,16 @@ module.exports = {
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
validateNoReadOnlyConfig: (config, onErr) => {
|
||||
let errProperty
|
||||
|
||||
Object.keys(config).some((option) => {
|
||||
return errProperty = testConfigOverrideOptions[option] === false ? option : undefined
|
||||
})
|
||||
|
||||
if (errProperty) {
|
||||
return onErr(errProperty)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@@ -6,6 +6,10 @@ interface ResolvedConfigOption {
|
||||
validation: Function
|
||||
isFolder?: boolean
|
||||
isExperimental?: boolean
|
||||
/**
|
||||
* Can be mutated with Cypress.config() or test-specific configuration overrides
|
||||
*/
|
||||
canUpdateDuringTestTime?: boolean
|
||||
}
|
||||
|
||||
interface RuntimeConfigOption {
|
||||
@@ -13,6 +17,10 @@ interface RuntimeConfigOption {
|
||||
defaultValue: any
|
||||
validation: Function
|
||||
isInternal?: boolean
|
||||
/**
|
||||
* Can be mutated with Cypress.config() or test-specific configuration overrides
|
||||
*/
|
||||
canUpdateDuringTestTime?: boolean
|
||||
}
|
||||
|
||||
interface BreakingOption {
|
||||
@@ -72,158 +80,204 @@ const resolvedOptions: Array<ResolvedConfigOption> = [
|
||||
name: 'animationDistanceThreshold',
|
||||
defaultValue: 5,
|
||||
validation: validate.isNumber,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'baseUrl',
|
||||
defaultValue: null,
|
||||
validation: validate.isFullyQualifiedUrl,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'blockHosts',
|
||||
defaultValue: null,
|
||||
validation: validate.isStringOrArrayOfStrings,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'chromeWebSecurity',
|
||||
defaultValue: true,
|
||||
validation: validate.isBoolean,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'clientCertificates',
|
||||
defaultValue: [],
|
||||
validation: validate.isValidClientCertificatesSet,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'component',
|
||||
// runner-ct overrides
|
||||
defaultValue: {},
|
||||
validation: isValidConfig,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'componentFolder',
|
||||
defaultValue: 'cypress/component',
|
||||
validation: validate.isStringOrFalse,
|
||||
isFolder: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'defaultCommandTimeout',
|
||||
defaultValue: 4000,
|
||||
validation: validate.isNumber,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'downloadsFolder',
|
||||
defaultValue: 'cypress/downloads',
|
||||
validation: validate.isString,
|
||||
isFolder: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'e2e',
|
||||
// e2e runner overrides
|
||||
defaultValue: {},
|
||||
validation: isValidConfig,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'env',
|
||||
defaultValue: {},
|
||||
validation: validate.isPlainObject,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'execTimeout',
|
||||
defaultValue: 60000,
|
||||
validation: validate.isNumber,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'exit',
|
||||
defaultValue: true,
|
||||
validation: validate.isBoolean,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'experimentalFetchPolyfill',
|
||||
defaultValue: false,
|
||||
validation: validate.isBoolean,
|
||||
isExperimental: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'experimentalInteractiveRunEvents',
|
||||
defaultValue: false,
|
||||
validation: validate.isBoolean,
|
||||
isExperimental: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'experimentalSessionSupport',
|
||||
defaultValue: false,
|
||||
validation: validate.isBoolean,
|
||||
isExperimental: true,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'experimentalSourceRewriting',
|
||||
defaultValue: false,
|
||||
validation: validate.isBoolean,
|
||||
isExperimental: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'experimentalStudio',
|
||||
defaultValue: false,
|
||||
validation: validate.isBoolean,
|
||||
isExperimental: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'fileServerFolder',
|
||||
defaultValue: '',
|
||||
validation: validate.isString,
|
||||
isFolder: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'fixturesFolder',
|
||||
defaultValue: 'cypress/fixtures',
|
||||
validation: validate.isStringOrFalse,
|
||||
isFolder: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'ignoreTestFiles',
|
||||
defaultValue: '*.hot-update.js',
|
||||
validation: validate.isStringOrArrayOfStrings,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'includeShadowDom',
|
||||
defaultValue: false,
|
||||
validation: validate.isBoolean,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'integrationFolder',
|
||||
defaultValue: 'cypress/integration',
|
||||
validation: validate.isString,
|
||||
isFolder: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'keystrokeDelay',
|
||||
defaultValue: 0,
|
||||
validation: validate.isNumberOrFalse,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'modifyObstructiveCode',
|
||||
defaultValue: true,
|
||||
validation: validate.isBoolean,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'nodeVersion',
|
||||
validation: validate.isOneOf('bundled', 'system'),
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'numTestsKeptInMemory',
|
||||
defaultValue: 50,
|
||||
validation: validate.isNumber,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'pageLoadTimeout',
|
||||
defaultValue: 60000,
|
||||
validation: validate.isNumber,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'pluginsFile',
|
||||
defaultValue: 'cypress/plugins',
|
||||
validation: validate.isStringOrFalse,
|
||||
isFolder: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'port',
|
||||
defaultValue: null,
|
||||
validation: validate.isNumber,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'projectId',
|
||||
defaultValue: null,
|
||||
validation: validate.isString,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'redirectionLimit',
|
||||
defaultValue: 20,
|
||||
validation: validate.isNumber,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'reporter',
|
||||
defaultValue: 'spec',
|
||||
validation: validate.isString,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'reporterOptions',
|
||||
defaultValue: null,
|
||||
validation: validate.isPlainObject,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'requestTimeout',
|
||||
defaultValue: 5000,
|
||||
validation: validate.isNumber,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'resolvedNodePath',
|
||||
defaultValue: null,
|
||||
validation: validate.isString,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'resolvedNodeVersion',
|
||||
defaultValue: null,
|
||||
validation: validate.isString,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'responseTimeout',
|
||||
defaultValue: 30000,
|
||||
validation: validate.isNumber,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'retries',
|
||||
defaultValue: {
|
||||
@@ -231,82 +285,101 @@ const resolvedOptions: Array<ResolvedConfigOption> = [
|
||||
openMode: 0,
|
||||
},
|
||||
validation: validate.isValidRetriesConfig,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'screenshotOnRunFailure',
|
||||
defaultValue: true,
|
||||
validation: validate.isBoolean,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'screenshotsFolder',
|
||||
defaultValue: 'cypress/screenshots',
|
||||
validation: validate.isStringOrFalse,
|
||||
isFolder: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'slowTestThreshold',
|
||||
defaultValue: (options: Record<string, any> = {}) => options.testingType === 'component' ? 250 : 10000,
|
||||
validation: validate.isNumber,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'scrollBehavior',
|
||||
defaultValue: 'top',
|
||||
validation: validate.isOneOf('center', 'top', 'bottom', 'nearest', false),
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'supportFile',
|
||||
defaultValue: 'cypress/support',
|
||||
validation: validate.isStringOrFalse,
|
||||
isFolder: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'supportFolder',
|
||||
defaultValue: false,
|
||||
validation: validate.isStringOrFalse,
|
||||
isFolder: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'taskTimeout',
|
||||
defaultValue: 60000,
|
||||
validation: validate.isNumber,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'testFiles',
|
||||
defaultValue: '**/*.*',
|
||||
validation: validate.isStringOrArrayOfStrings,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'trashAssetsBeforeRuns',
|
||||
defaultValue: true,
|
||||
validation: validate.isBoolean,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'userAgent',
|
||||
defaultValue: null,
|
||||
validation: validate.isString,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'video',
|
||||
defaultValue: true,
|
||||
validation: validate.isBoolean,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'videoCompression',
|
||||
defaultValue: 32,
|
||||
validation: validate.isNumberOrFalse,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'videosFolder',
|
||||
defaultValue: 'cypress/videos',
|
||||
validation: validate.isString,
|
||||
isFolder: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'videoUploadOnPasses',
|
||||
defaultValue: true,
|
||||
validation: validate.isBoolean,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'viewportHeight',
|
||||
defaultValue: 660,
|
||||
validation: validate.isNumber,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'viewportWidth',
|
||||
defaultValue: 1000,
|
||||
validation: validate.isNumber,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'waitForAnimations',
|
||||
defaultValue: true,
|
||||
validation: validate.isBoolean,
|
||||
canUpdateDuringTestTime: true,
|
||||
}, {
|
||||
name: 'watchForFileChanges',
|
||||
defaultValue: true,
|
||||
validation: validate.isBoolean,
|
||||
canUpdateDuringTestTime: false,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -316,15 +389,18 @@ const runtimeOptions: Array<RuntimeConfigOption> = [
|
||||
defaultValue: false,
|
||||
validation: validate.isBoolean,
|
||||
isInternal: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'browsers',
|
||||
defaultValue: [],
|
||||
validation: validate.isValidBrowserList,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'clientRoute',
|
||||
defaultValue: '/__/',
|
||||
validation: validate.isString,
|
||||
isInternal: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'configFile',
|
||||
defaultValue: 'cypress.json',
|
||||
@@ -332,59 +408,76 @@ const runtimeOptions: Array<RuntimeConfigOption> = [
|
||||
// not truly internal, but can only be set via cli,
|
||||
// so we don't consider it a "public" option
|
||||
isInternal: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'devServerPublicPathRoute',
|
||||
defaultValue: '/__cypress/src',
|
||||
validation: validate.isString,
|
||||
isInternal: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'hosts',
|
||||
defaultValue: null,
|
||||
validation: validate.isPlainObject,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'isInteractive',
|
||||
defaultValue: true,
|
||||
validation: validate.isBoolean,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'isTextTerminal',
|
||||
defaultValue: false,
|
||||
validation: validate.isBoolean,
|
||||
isInternal: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'morgan',
|
||||
defaultValue: true,
|
||||
validation: validate.isBoolean,
|
||||
isInternal: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'modifyObstructiveCode',
|
||||
defaultValue: true,
|
||||
validation: validate.isBoolean,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'namespace',
|
||||
defaultValue: '__cypress',
|
||||
validation: validate.isString,
|
||||
isInternal: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'reporterRoute',
|
||||
defaultValue: '/__cypress/reporter',
|
||||
validation: validate.isString,
|
||||
isInternal: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'socketId',
|
||||
defaultValue: null,
|
||||
validation: validate.isString,
|
||||
isInternal: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'socketIoCookie',
|
||||
defaultValue: '__socket.io',
|
||||
validation: validate.isString,
|
||||
isInternal: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'socketIoRoute',
|
||||
defaultValue: '/__socket.io',
|
||||
validation: validate.isString,
|
||||
isInternal: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
}, {
|
||||
name: 'xhrRoute',
|
||||
defaultValue: '/xhrs/',
|
||||
validation: validate.isString,
|
||||
isInternal: true,
|
||||
canUpdateDuringTestTime: false,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ describe('src/index', () => {
|
||||
'baseUrl': 'https://',
|
||||
}, errorFn)
|
||||
|
||||
expect(errorFn).to.have.been.callCount(0)
|
||||
expect(errorFn).to.have.callCount(0)
|
||||
})
|
||||
|
||||
it('calls error callback if config is invalid', () => {
|
||||
@@ -125,7 +125,7 @@ describe('src/index', () => {
|
||||
configFile: 'config.js',
|
||||
})
|
||||
|
||||
expect(errorFn).to.have.been.callCount(0)
|
||||
expect(errorFn).to.have.callCount(0)
|
||||
})
|
||||
|
||||
it('calls error callback if config contains breaking option that should throw an error', () => {
|
||||
@@ -146,4 +146,31 @@ describe('src/index', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('.validateNoReadOnlyConfig', () => {
|
||||
it('returns an error if validation fails', () => {
|
||||
const errorFn = sinon.spy()
|
||||
|
||||
configUtil.validateNoReadOnlyConfig({ chromeWebSecurity: false }, errorFn)
|
||||
|
||||
expect(errorFn).to.have.callCount(1)
|
||||
expect(errorFn).to.have.been.calledWithMatch(/chromeWebSecurity/)
|
||||
})
|
||||
|
||||
it('does not return an error if validation succeeds', () => {
|
||||
const errorFn = sinon.spy()
|
||||
|
||||
configUtil.validateNoReadOnlyConfig({ requestTimeout: 1000 }, errorFn)
|
||||
|
||||
expect(errorFn).to.have.callCount(0)
|
||||
})
|
||||
|
||||
it('does not return an error if configuration is a non-Cypress config option', () => {
|
||||
const errorFn = sinon.spy()
|
||||
|
||||
configUtil.validateNoReadOnlyConfig({ foo: 'bar' }, errorFn)
|
||||
|
||||
expect(errorFn).to.have.callCount(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -101,9 +101,7 @@ describe('src/cy/commands/fixtures', () => {
|
||||
return null
|
||||
})
|
||||
|
||||
it('throws if fixturesFolder is set to false', {
|
||||
fixturesFolder: false,
|
||||
}, function (done) {
|
||||
it('throws if fixturesFolder is set to false', { fixturesFolder: false }, function (done) {
|
||||
cy.on('fail', () => {
|
||||
const { lastLog } = this
|
||||
|
||||
|
||||
@@ -377,6 +377,28 @@ describe('testConfigOverrides baseUrl @slow', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('cannot set read-only properties', () => {
|
||||
afterEach(() => {
|
||||
window.top.__cySkipValidateConfig = true
|
||||
})
|
||||
|
||||
it('throws if mutating read-only config with Cypress.config()', (done) => {
|
||||
window.top.__cySkipValidateConfig = false
|
||||
cy.on('fail', (err) => {
|
||||
expect(err.message).to.include('`Cypress.config()` cannot mutate option `chromeWebSecurity` because it is a read-only property.')
|
||||
done()
|
||||
})
|
||||
|
||||
Cypress.config('chromeWebSecurity', false)
|
||||
})
|
||||
|
||||
it('does not throw for non-Cypress config values', () => {
|
||||
expect(() => {
|
||||
Cypress.config('foo', 'bar')
|
||||
}).to.not.throw()
|
||||
})
|
||||
})
|
||||
|
||||
function hasOnly (test) {
|
||||
let curSuite = test.parent
|
||||
let hasOnly = false
|
||||
|
||||
@@ -2,6 +2,8 @@ const { $ } = Cypress
|
||||
|
||||
const isActuallyInteractive = Cypress.config('isInteractive')
|
||||
|
||||
window.top.__cySkipValidateConfig = true
|
||||
|
||||
if (!isActuallyInteractive) {
|
||||
// we want to only enable retries in runMode
|
||||
// and because we set `isInteractive` above
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import { validate } from '@packages/config'
|
||||
import { validate, validateNoReadOnlyConfig } from '@packages/config'
|
||||
import _ from 'lodash'
|
||||
import $ from 'jquery'
|
||||
import * as blobUtil from 'blob-util'
|
||||
@@ -144,6 +144,24 @@ class $Cypress {
|
||||
this.state = $SetterGetter.create({})
|
||||
this.originalConfig = _.cloneDeep(config)
|
||||
this.config = $SetterGetter.create(config, (config) => {
|
||||
if (!window.top.__cySkipValidateConfig) {
|
||||
validateNoReadOnlyConfig(config, (errProperty) => {
|
||||
let errMessage
|
||||
|
||||
if (this.state('runnable')) {
|
||||
errMessage = $errUtils.errByPath('config.invalid_cypress_config_override', {
|
||||
errProperty,
|
||||
})
|
||||
} else {
|
||||
errMessage = $errUtils.errByPath('config.invalid_test_config_override', {
|
||||
errProperty,
|
||||
})
|
||||
}
|
||||
|
||||
throw new this.state('specWindow').Error(errMessage)
|
||||
})
|
||||
}
|
||||
|
||||
validate(config, (errMsg) => {
|
||||
throw new this.state('specWindow').Error(errMsg)
|
||||
})
|
||||
|
||||
@@ -250,10 +250,16 @@ export default {
|
||||
message: `Setting the config via ${cmd('Cypress.config')} failed with the following validation error:\n\n{{errMsg}}`,
|
||||
docsUrl: 'https://on.cypress.io/config',
|
||||
},
|
||||
'invalid_test_override': {
|
||||
invalid_test_override: {
|
||||
message: `The config override passed to your test has the following validation error:\n\n{{errMsg}}`,
|
||||
docsUrl: 'https://on.cypress.io/config',
|
||||
},
|
||||
invalid_cypress_config_override: {
|
||||
message: `\`Cypress.config()\` cannot mutate option \`{{errProperty}}\` because it is a read-only property.`,
|
||||
},
|
||||
invalid_test_config_override: {
|
||||
message: `Cypress test configuration cannot mutate option \`{{errProperty}}\` because it is a read-only property.`,
|
||||
},
|
||||
},
|
||||
|
||||
contains: {
|
||||
|
||||
@@ -728,9 +728,14 @@ describe('errors ui', () => {
|
||||
})
|
||||
|
||||
describe('docs url', () => {
|
||||
after(() => {
|
||||
window.top.__cySkipValidateConfig = false
|
||||
})
|
||||
|
||||
const file = 'docs_url_spec.js'
|
||||
const docsUrl = 'https://on.cypress.io/viewport'
|
||||
|
||||
window.top.__cySkipValidateConfig = true
|
||||
verify.it('displays as button in interactive mode', { retries: 1 }, {
|
||||
file,
|
||||
verifyFn () {
|
||||
|
||||
@@ -401,8 +401,13 @@ describe('runner/cypress retries.ui.spec', { viewportWidth: 600, viewportHeight:
|
||||
})
|
||||
|
||||
describe('can configure retries', () => {
|
||||
after(() => {
|
||||
window.top.__cySkipValidateConfig = false
|
||||
})
|
||||
|
||||
const haveCorrectError = ($el) => cy.wrap($el).last().parentsUntil('.collapsible').last().parent().find('.runnable-err').should('contain', 'Unspecified AssertionError')
|
||||
|
||||
window.top.__cySkipValidateConfig = true
|
||||
it('via config value', () => {
|
||||
runIsolatedCypress({
|
||||
suites: {
|
||||
|
||||
@@ -1430,6 +1430,7 @@ describe('lib/config', () => {
|
||||
e2e: { from: 'default', value: {} },
|
||||
env: {},
|
||||
execTimeout: { value: 60000, from: 'default' },
|
||||
exit: { value: true, from: 'default' },
|
||||
experimentalFetchPolyfill: { value: false, from: 'default' },
|
||||
experimentalInteractiveRunEvents: { value: false, from: 'default' },
|
||||
experimentalSourceRewriting: { value: false, from: 'default' },
|
||||
@@ -1441,6 +1442,8 @@ describe('lib/config', () => {
|
||||
ignoreTestFiles: { value: '*.hot-update.js', from: 'default' },
|
||||
includeShadowDom: { value: false, from: 'default' },
|
||||
integrationFolder: { value: 'cypress/integration', from: 'default' },
|
||||
isInteractive: { value: true, from: 'default' },
|
||||
keystrokeDelay: { value: 0, from: 'default' },
|
||||
modifyObstructiveCode: { value: true, from: 'default' },
|
||||
numTestsKeptInMemory: { value: 50, from: 'default' },
|
||||
pageLoadTimeout: { value: 60000, from: 'default' },
|
||||
@@ -1517,6 +1520,7 @@ describe('lib/config', () => {
|
||||
downloadsFolder: { value: 'cypress/downloads', from: 'default' },
|
||||
e2e: { from: 'default', value: {} },
|
||||
execTimeout: { value: 60000, from: 'default' },
|
||||
exit: { value: true, from: 'default' },
|
||||
experimentalFetchPolyfill: { value: false, from: 'default' },
|
||||
experimentalInteractiveRunEvents: { value: false, from: 'default' },
|
||||
experimentalSourceRewriting: { value: false, from: 'default' },
|
||||
@@ -1550,6 +1554,8 @@ describe('lib/config', () => {
|
||||
ignoreTestFiles: { value: '*.hot-update.js', from: 'default' },
|
||||
includeShadowDom: { value: false, from: 'default' },
|
||||
integrationFolder: { value: 'cypress/integration', from: 'default' },
|
||||
isInteractive: { value: true, from: 'default' },
|
||||
keystrokeDelay: { value: 0, from: 'default' },
|
||||
modifyObstructiveCode: { value: true, from: 'default' },
|
||||
numTestsKeptInMemory: { value: 50, from: 'default' },
|
||||
pageLoadTimeout: { value: 60000, from: 'default' },
|
||||
|
||||
70
system-tests/__snapshots__/issue_6407_spec.js
Normal file
70
system-tests/__snapshots__/issue_6407_spec.js
Normal file
@@ -0,0 +1,70 @@
|
||||
exports['e2e issue 6407 throws if mutating read-only config with test configuration 1'] = `
|
||||
|
||||
====================================================================================================
|
||||
|
||||
(Run Starting)
|
||||
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Cypress: 1.2.3 │
|
||||
│ Browser: FooBrowser 88 │
|
||||
│ Specs: 1 found (issue_6407_spec.js) │
|
||||
│ Searched: cypress/integration/issue_6407_spec.js │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
Running: issue_6407_spec.js (1 of 1)
|
||||
|
||||
|
||||
1) throws if mutating read-only config with test configuration
|
||||
|
||||
0 passing
|
||||
1 failing
|
||||
|
||||
1) throws if mutating read-only config with test configuration:
|
||||
CypressError: The config override passed to your test has the following validation error:
|
||||
|
||||
CypressError: Cypress test configuration cannot mutate option \`chromeWebSecurity\` because it is a read-only property.
|
||||
|
||||
https://on.cypress.io/config
|
||||
Error
|
||||
[stack trace lines]
|
||||
|
||||
|
||||
|
||||
|
||||
(Results)
|
||||
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Tests: 1 │
|
||||
│ Passing: 0 │
|
||||
│ Failing: 1 │
|
||||
│ Pending: 0 │
|
||||
│ Skipped: 0 │
|
||||
│ Screenshots: 0 │
|
||||
│ Video: true │
|
||||
│ Duration: X seconds │
|
||||
│ Spec Ran: issue_6407_spec.js │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
||||
(Video)
|
||||
|
||||
- Started processing: Compressing to 32 CRF
|
||||
- Finished processing: /XXX/XXX/XXX/cypress/videos/issue_6407_spec.js.mp4 (X second)
|
||||
|
||||
|
||||
====================================================================================================
|
||||
|
||||
(Run Finished)
|
||||
|
||||
|
||||
Spec Tests Passing Failing Pending Skipped
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ ✖ issue_6407_spec.js XX:XX 1 - 1 - - │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
✖ 1 of 1 failed (100%) XX:XX 1 - 1 - -
|
||||
|
||||
|
||||
`
|
||||
@@ -0,0 +1,4 @@
|
||||
/* eslint-disable mocha/no-global-tests, no-undef */
|
||||
it('throws if mutating read-only config with test configuration', { chromeWebSecurity: false }, () => {
|
||||
expect(true)
|
||||
})
|
||||
@@ -1,4 +1,5 @@
|
||||
/// <reference types="cypress" />
|
||||
window.top.__cySkipValidateConfig = true
|
||||
Cypress.config('isInteractive', true)
|
||||
Cypress.config('experimentalSessionSupport', true)
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import { fail, verify } from '../../e2e/cypress/support/util'
|
||||
|
||||
context('validation errors', function () {
|
||||
beforeEach(() => {
|
||||
// @ts-ignore
|
||||
window.top.__cySkipValidateConfig = true
|
||||
Cypress.config('isInteractive', true)
|
||||
})
|
||||
|
||||
@@ -16,7 +18,7 @@ context('validation errors', function () {
|
||||
})
|
||||
|
||||
verify(this, {
|
||||
line: 15,
|
||||
line: 17,
|
||||
column: 8,
|
||||
message: 'can only accept a string preset or',
|
||||
stack: ['throwErrBadArgs', 'From Your Spec Code:'],
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
/**
|
||||
* This tests the error UI for a certain webpack preprocessor setup.
|
||||
* It does this by having a test fail and then a subsequent test run that
|
||||
@@ -16,6 +15,8 @@ import { fail, verify } from '../../../e2e/cypress/support/util'
|
||||
|
||||
context('validation errors', function () {
|
||||
beforeEach(() => {
|
||||
// @ts-ignore
|
||||
window.top.__cySkipValidateConfig = true
|
||||
// @ts-ignore
|
||||
Cypress.config('isInteractive', true)
|
||||
})
|
||||
@@ -26,7 +27,7 @@ context('validation errors', function () {
|
||||
})
|
||||
|
||||
verify(this, {
|
||||
line: 25,
|
||||
line: 26,
|
||||
column: 8,
|
||||
message: 'can only accept a string preset or',
|
||||
stack: ['throwErrBadArgs', 'From Your Spec Code:'],
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
/**
|
||||
* This tests the error UI for a certain webpack preprocessor setup.
|
||||
* It does this by having a test fail and then a subsequent test run that
|
||||
@@ -16,6 +15,8 @@ import { fail, verify } from '../../../e2e/cypress/support/util'
|
||||
|
||||
context('validation errors', function () {
|
||||
beforeEach(() => {
|
||||
// @ts-ignore
|
||||
window.top.__cySkipValidateConfig = true
|
||||
// @ts-ignore
|
||||
Cypress.config('isInteractive', true)
|
||||
})
|
||||
@@ -26,7 +27,7 @@ context('validation errors', function () {
|
||||
})
|
||||
|
||||
verify(this, {
|
||||
line: 25,
|
||||
line: 26,
|
||||
column: 8,
|
||||
message: 'can only accept a string preset or',
|
||||
stack: ['throwErrBadArgs', 'From Your Spec Code:'],
|
||||
|
||||
@@ -6,8 +6,11 @@
|
||||
|
||||
import { fail, verify } from '../../../e2e/cypress/support/util'
|
||||
|
||||
window.top.__cySkipValidateConfig = true
|
||||
context('validation errors', function () {
|
||||
beforeEach(() => {
|
||||
// @ts-ignore
|
||||
window.top.__cySkipValidateConfig = true
|
||||
Cypress.config('isInteractive', true)
|
||||
})
|
||||
|
||||
@@ -16,7 +19,7 @@ context('validation errors', function () {
|
||||
})
|
||||
|
||||
verify(this, {
|
||||
line: 15,
|
||||
line: 18,
|
||||
column: 8,
|
||||
message: 'can only accept a string preset or',
|
||||
stack: ['throwErrBadArgs', 'From Your Spec Code:'],
|
||||
|
||||
14
system-tests/test/issue_6407_spec.js
Normal file
14
system-tests/test/issue_6407_spec.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const systemTests = require('../lib/system-tests').default
|
||||
|
||||
describe('e2e issue 6407', () => {
|
||||
systemTests.setup()
|
||||
|
||||
// https://github.com/cypress-io/cypress/issues/6407
|
||||
it('throws if mutating read-only config with test configuration', function () {
|
||||
return systemTests.exec(this, {
|
||||
spec: 'issue_6407_spec.js',
|
||||
snapshot: true,
|
||||
expectedExitCode: 1,
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user