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:
David Munechika
2021-12-10 11:16:12 -05:00
committed by GitHub
parent f79bdd665a
commit dd0fc9b9f7
22 changed files with 314 additions and 17 deletions

View File

@@ -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
}

View File

@@ -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"
]

View File

@@ -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: \`""\`
`
`

View File

@@ -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)
}
},
}

View File

@@ -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,
},
]

View File

@@ -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)
})
})
})

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)
})

View File

@@ -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: {

View File

@@ -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 () {

View File

@@ -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: {

View File

@@ -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' },

View 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 - -
`

View File

@@ -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)
})

View File

@@ -1,4 +1,5 @@
/// <reference types="cypress" />
window.top.__cySkipValidateConfig = true
Cypress.config('isInteractive', true)
Cypress.config('experimentalSessionSupport', true)

View File

@@ -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:'],

View File

@@ -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:'],

View File

@@ -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:'],

View File

@@ -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:'],

View 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,
})
})
})