diff --git a/cli/types/cypress.d.ts b/cli/types/cypress.d.ts index 8a04619ad569..ee24dae8e158 100644 --- a/cli/types/cypress.d.ts +++ b/cli/types/cypress.d.ts @@ -328,7 +328,7 @@ declare namespace Cypress { // 60000 ``` */ - config(key: K): ResolvedConfigOptions[K] + 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(key: K, value: ResolvedConfigOptions[K]): void + config(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> { + interface TestConfigOverrides extends Partial> { browser?: IsBrowserMatcher | IsBrowserMatcher[] keystrokeDelay?: number } diff --git a/packages/config/__snapshots__/index_spec.js b/packages/config/__snapshots__/index_spec.js index 68d882a7d293..0e8d783fced8 100644 --- a/packages/config/__snapshots__/index_spec.js +++ b/packages/config/__snapshots__/index_spec.js @@ -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" ] diff --git a/packages/config/__snapshots__/validation_spec.js b/packages/config/__snapshots__/validation_spec.js index 538d5d145fa1..1d3991be77d5 100644 --- a/packages/config/__snapshots__/validation_spec.js +++ b/packages/config/__snapshots__/validation_spec.js @@ -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: \`""\` -` +` \ No newline at end of file diff --git a/packages/config/lib/index.js b/packages/config/lib/index.js index a67a97246bcc..20fd1d3ca7f5 100644 --- a/packages/config/lib/index.js +++ b/packages/config/lib/index.js @@ -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) + } + }, } diff --git a/packages/config/lib/options.ts b/packages/config/lib/options.ts index a3d3e1cadb84..a548b345659c 100644 --- a/packages/config/lib/options.ts +++ b/packages/config/lib/options.ts @@ -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 = [ 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 = [ 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 = {}) => 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 = [ 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 = [ // 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, }, ] diff --git a/packages/config/test/unit/index_spec.js b/packages/config/test/unit/index_spec.js index d180ac3c2741..6fbd8b573b02 100644 --- a/packages/config/test/unit/index_spec.js +++ b/packages/config/test/unit/index_spec.js @@ -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) + }) + }) }) diff --git a/packages/driver/cypress/integration/commands/fixtures_spec.js b/packages/driver/cypress/integration/commands/fixtures_spec.js index f7570ad252a8..744825e6ebc8 100644 --- a/packages/driver/cypress/integration/commands/fixtures_spec.js +++ b/packages/driver/cypress/integration/commands/fixtures_spec.js @@ -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 diff --git a/packages/driver/cypress/integration/e2e/testConfigOverrides.spec.js b/packages/driver/cypress/integration/e2e/testConfigOverrides.spec.js index cace5ae27275..4a00d050b0eb 100644 --- a/packages/driver/cypress/integration/e2e/testConfigOverrides.spec.js +++ b/packages/driver/cypress/integration/e2e/testConfigOverrides.spec.js @@ -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 diff --git a/packages/driver/cypress/support/defaults.js b/packages/driver/cypress/support/defaults.js index e4bac5f20ad2..dd5b7b030f6e 100644 --- a/packages/driver/cypress/support/defaults.js +++ b/packages/driver/cypress/support/defaults.js @@ -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 diff --git a/packages/driver/src/cypress.ts b/packages/driver/src/cypress.ts index f52954f5189e..0399591034e6 100644 --- a/packages/driver/src/cypress.ts +++ b/packages/driver/src/cypress.ts @@ -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) }) diff --git a/packages/driver/src/cypress/error_messages.ts b/packages/driver/src/cypress/error_messages.ts index 36bc19ee475b..d7b671cef748 100644 --- a/packages/driver/src/cypress/error_messages.ts +++ b/packages/driver/src/cypress/error_messages.ts @@ -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: { diff --git a/packages/runner/cypress/integration/reporter.errors.spec.js b/packages/runner/cypress/integration/reporter.errors.spec.js index 8515cfc08ab6..b92403c550c9 100644 --- a/packages/runner/cypress/integration/reporter.errors.spec.js +++ b/packages/runner/cypress/integration/reporter.errors.spec.js @@ -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 () { diff --git a/packages/runner/cypress/integration/retries.ui.spec.js b/packages/runner/cypress/integration/retries.ui.spec.js index 3b609a7c2d01..b7cb5802afbf 100644 --- a/packages/runner/cypress/integration/retries.ui.spec.js +++ b/packages/runner/cypress/integration/retries.ui.spec.js @@ -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: { diff --git a/packages/server/test/unit/config_spec.js b/packages/server/test/unit/config_spec.js index 68cad03484de..b5b1bc9e900a 100644 --- a/packages/server/test/unit/config_spec.js +++ b/packages/server/test/unit/config_spec.js @@ -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' }, diff --git a/system-tests/__snapshots__/issue_6407_spec.js b/system-tests/__snapshots__/issue_6407_spec.js new file mode 100644 index 000000000000..9fe3ed1e1d80 --- /dev/null +++ b/system-tests/__snapshots__/issue_6407_spec.js @@ -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 - - + + +` diff --git a/system-tests/projects/e2e/cypress/integration/issue_6407_spec.js b/system-tests/projects/e2e/cypress/integration/issue_6407_spec.js new file mode 100644 index 000000000000..ba2d9c247c26 --- /dev/null +++ b/system-tests/projects/e2e/cypress/integration/issue_6407_spec.js @@ -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) +}) diff --git a/system-tests/projects/e2e/cypress/integration/session_spec.js b/system-tests/projects/e2e/cypress/integration/session_spec.js index 539a46cf5797..599fdd9b87d1 100644 --- a/system-tests/projects/e2e/cypress/integration/session_spec.js +++ b/system-tests/projects/e2e/cypress/integration/session_spec.js @@ -1,4 +1,5 @@ /// +window.top.__cySkipValidateConfig = true Cypress.config('isInteractive', true) Cypress.config('experimentalSessionSupport', true) diff --git a/system-tests/projects/integration-outside-project-root/integration/failing_spec.js b/system-tests/projects/integration-outside-project-root/integration/failing_spec.js index 414ce4f7051f..a15c054443cc 100644 --- a/system-tests/projects/integration-outside-project-root/integration/failing_spec.js +++ b/system-tests/projects/integration-outside-project-root/integration/failing_spec.js @@ -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:'], diff --git a/system-tests/projects/webpack-preprocessor-ts-loader-compiler-options/cypress/integration/failing_spec.ts b/system-tests/projects/webpack-preprocessor-ts-loader-compiler-options/cypress/integration/failing_spec.ts index 9626cb342d7a..3447e9cabafe 100644 --- a/system-tests/projects/webpack-preprocessor-ts-loader-compiler-options/cypress/integration/failing_spec.ts +++ b/system-tests/projects/webpack-preprocessor-ts-loader-compiler-options/cypress/integration/failing_spec.ts @@ -1,5 +1,4 @@ /// - /** * 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:'], diff --git a/system-tests/projects/webpack-preprocessor-ts-loader/cypress/integration/failing_spec.ts b/system-tests/projects/webpack-preprocessor-ts-loader/cypress/integration/failing_spec.ts index 9626cb342d7a..3447e9cabafe 100644 --- a/system-tests/projects/webpack-preprocessor-ts-loader/cypress/integration/failing_spec.ts +++ b/system-tests/projects/webpack-preprocessor-ts-loader/cypress/integration/failing_spec.ts @@ -1,5 +1,4 @@ /// - /** * 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:'], diff --git a/system-tests/projects/webpack-preprocessor/cypress/integration/failing_spec.js b/system-tests/projects/webpack-preprocessor/cypress/integration/failing_spec.js index 03efddb77617..76c0b0975c79 100644 --- a/system-tests/projects/webpack-preprocessor/cypress/integration/failing_spec.js +++ b/system-tests/projects/webpack-preprocessor/cypress/integration/failing_spec.js @@ -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:'], diff --git a/system-tests/test/issue_6407_spec.js b/system-tests/test/issue_6407_spec.js new file mode 100644 index 000000000000..524a601c06f3 --- /dev/null +++ b/system-tests/test/issue_6407_spec.js @@ -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, + }) + }) +})