UNPKG

@revoloo/cypress6

Version:

Cypress.io end to end testing tool

1,715 lines (1,373 loc) 66 kB
require('../spec_helper') const _ = require('lodash') const path = require('path') const R = require('ramda') const debug = require('debug')('test') const config = require(`${root}lib/config`) const errors = require(`${root}lib/errors`) const configUtil = require(`${root}lib/util/config`) const findSystemNode = require(`${root}lib/util/find_system_node`) const scaffold = require(`${root}lib/scaffold`) let settings = require(`${root}lib/util/settings`) describe('lib/config', () => { beforeEach(function () { this.env = process.env process.env = _.omit(process.env, 'CYPRESS_DEBUG') }) afterEach(function () { process.env = this.env }) context('environment name check', () => { it('throws an error for unknown CYPRESS_INTERNAL_ENV', () => { sinon.stub(errors, 'throw').withArgs('INVALID_CYPRESS_INTERNAL_ENV', 'foo-bar') process.env.CYPRESS_INTERNAL_ENV = 'foo-bar' const cfg = { projectRoot: '/foo/bar/', } const options = {} config.mergeDefaults(cfg, options) expect(errors.throw).have.been.calledOnce }) it('allows production CYPRESS_INTERNAL_ENV', () => { sinon.stub(errors, 'throw') process.env.CYPRESS_INTERNAL_ENV = 'production' const cfg = { projectRoot: '/foo/bar/', } const options = {} config.mergeDefaults(cfg, options) expect(errors.throw).not.to.be.called }) }) context('.get', () => { beforeEach(function () { this.projectRoot = '/_test-output/path/to/project' this.setup = (cypressJson = {}, cypressEnvJson = {}) => { sinon.stub(settings, 'read').withArgs(this.projectRoot).resolves(cypressJson) sinon.stub(settings, 'readEnv').withArgs(this.projectRoot).resolves(cypressEnvJson) } }) it('sets projectRoot', function () { this.setup({}, { foo: 'bar' }) return config.get(this.projectRoot) .then((obj) => { expect(obj.projectRoot).to.eq(this.projectRoot) expect(obj.env).to.deep.eq({ foo: 'bar' }) }) }) it('sets projectName', function () { this.setup({}, { foo: 'bar' }) return config.get(this.projectRoot) .then((obj) => { expect(obj.projectName).to.eq('project') }) }) it('clones settings and env settings, so they are not mutated', function () { const settings = { foo: 'bar' } const envSettings = { baz: 'qux' } this.setup(settings, envSettings) return config.get(this.projectRoot) .then(() => { expect(settings).to.deep.equal({ foo: 'bar' }) expect(envSettings).to.deep.equal({ baz: 'qux' }) }) }) context('port', () => { beforeEach(function () { return this.setup({}, { foo: 'bar' }) }) it('can override default port', function () { return config.get(this.projectRoot, { port: 8080 }) .then((obj) => { expect(obj.port).to.eq(8080) }) }) it('updates browserUrl', function () { return config.get(this.projectRoot, { port: 8080 }) .then((obj) => { expect(obj.browserUrl).to.eq('http://localhost:8080/__/') }) }) it('updates proxyUrl', function () { return config.get(this.projectRoot, { port: 8080 }) .then((obj) => { expect(obj.proxyUrl).to.eq('http://localhost:8080') }) }) }) context('validation', () => { beforeEach(function () { this.expectValidationPasses = () => { return config.get(this.projectRoot) // shouldn't throw } this.expectValidationFails = (errorMessage = 'validation error') => { return config.get(this.projectRoot) .then(() => { throw new Error('should throw validation error') }).catch((err) => { expect(err.message).to.include(errorMessage) }) } }) it('values are optional', function () { this.setup() return this.expectValidationPasses() }) it('validates cypress.json', function () { this.setup({ reporter: 5 }) return this.expectValidationFails('cypress.json') }) it('validates cypress.env.json', function () { this.setup({}, { reporter: 5 }) return this.expectValidationFails('cypress.env.json') }) it('only validates known values', function () { this.setup({ foo: 'bar' }) return this.expectValidationPasses() }) context('animationDistanceThreshold', () => { it('passes if a number', function () { this.setup({ animationDistanceThreshold: 10 }) return this.expectValidationPasses() }) it('fails if not a number', function () { this.setup({ animationDistanceThreshold: { foo: 'bar' } }) this.expectValidationFails('be a number') return this.expectValidationFails('the value was: \`{"foo":"bar"}\`') }) }) context('baseUrl', () => { it('passes if begins with http://', function () { this.setup({ baseUrl: 'http://example.com' }) return this.expectValidationPasses() }) it('passes if begins with https://', function () { this.setup({ baseUrl: 'https://example.com' }) return this.expectValidationPasses() }) it('fails if not a string', function () { this.setup({ baseUrl: false }) return this.expectValidationFails('be a fully qualified URL') }) it('fails if not a fully qualified url', function () { this.setup({ baseUrl: 'localhost' }) return this.expectValidationFails('be a fully qualified URL') }) }) context('chromeWebSecurity', () => { it('passes if a boolean', function () { this.setup({ chromeWebSecurity: false }) return this.expectValidationPasses() }) it('fails if not a boolean', function () { this.setup({ chromeWebSecurity: 42 }) this.expectValidationFails('be a boolean') return this.expectValidationFails('the value was: `42`') }) }) context('modifyObstructiveCode', () => { it('passes if a boolean', function () { this.setup({ modifyObstructiveCode: false }) return this.expectValidationPasses() }) it('fails if not a boolean', function () { this.setup({ modifyObstructiveCode: 42 }) this.expectValidationFails('be a boolean') return this.expectValidationFails('the value was: `42`') }) }) context('defaultCommandTimeout', () => { it('passes if a number', function () { this.setup({ defaultCommandTimeout: 10 }) return this.expectValidationPasses() }) it('fails if not a number', function () { this.setup({ defaultCommandTimeout: 'foo' }) this.expectValidationFails('be a number') return this.expectValidationFails('the value was: `"foo"`') }) }) context('env', () => { it('passes if an object', function () { this.setup({ env: {} }) return this.expectValidationPasses() }) it('fails if not an object', function () { this.setup({ env: 'not an object that\'s for sure' }) return this.expectValidationFails('a plain object') }) }) context('execTimeout', () => { it('passes if a number', function () { this.setup({ execTimeout: 10 }) return this.expectValidationPasses() }) it('fails if not a number', function () { this.setup({ execTimeout: 'foo' }) return this.expectValidationFails('be a number') }) }) context('taskTimeout', () => { it('passes if a number', function () { this.setup({ taskTimeout: 10 }) return this.expectValidationPasses() }) it('fails if not a number', function () { this.setup({ taskTimeout: 'foo' }) return this.expectValidationFails('be a number') }) }) context('fileServerFolder', () => { it('passes if a string', function () { this.setup({ fileServerFolder: '_files' }) return this.expectValidationPasses() }) it('fails if not a string', function () { this.setup({ fileServerFolder: true }) this.expectValidationFails('be a string') return this.expectValidationFails('the value was: `true`') }) }) context('fixturesFolder', () => { it('passes if a string', function () { this.setup({ fixturesFolder: '_fixtures' }) return this.expectValidationPasses() }) it('passes if false', function () { this.setup({ fixturesFolder: false }) return this.expectValidationPasses() }) it('fails if not a string or false', function () { this.setup({ fixturesFolder: true }) return this.expectValidationFails('be a string or false') }) }) context('ignoreTestFiles', () => { it('passes if a string', function () { this.setup({ ignoreTestFiles: '*.jsx' }) return this.expectValidationPasses() }) it('passes if an array of strings', function () { this.setup({ ignoreTestFiles: ['*.jsx'] }) return this.expectValidationPasses() }) it('fails if not a string or array', function () { this.setup({ ignoreTestFiles: 5 }) return this.expectValidationFails('be a string or an array of strings') }) it('fails if not an array of strings', function () { this.setup({ ignoreTestFiles: [5] }) this.expectValidationFails('be a string or an array of strings') return this.expectValidationFails('the value was: `[5]`') }) }) context('integrationFolder', () => { it('passes if a string', function () { this.setup({ integrationFolder: '_tests' }) return this.expectValidationPasses() }) it('fails if not a string', function () { this.setup({ integrationFolder: true }) return this.expectValidationFails('be a string') }) }) context('downloadsFolder', () => { it('passes if a string', function () { this.setup({ downloadsFolder: '_downloads' }) return this.expectValidationPasses() }) it('fails if not a string', function () { this.setup({ downloadsFolder: true }) return this.expectValidationFails('be a string') }) }) context('userAgent', () => { it('passes if a string', function () { this.setup({ userAgent: '_tests' }) return this.expectValidationPasses() }) it('fails if not a string', function () { this.setup({ userAgent: true }) return this.expectValidationFails('be a string') }) }) context('numTestsKeptInMemory', () => { it('passes if a number', function () { this.setup({ numTestsKeptInMemory: 10 }) return this.expectValidationPasses() }) it('fails if not a number', function () { this.setup({ numTestsKeptInMemory: 'foo' }) return this.expectValidationFails('be a number') }) }) context('pageLoadTimeout', () => { it('passes if a number', function () { this.setup({ pageLoadTimeout: 10 }) return this.expectValidationPasses() }) it('fails if not a number', function () { this.setup({ pageLoadTimeout: 'foo' }) return this.expectValidationFails('be a number') }) }) context('pluginsFile', () => { it('passes if a string', function () { this.setup({ pluginsFile: 'cypress/plugins' }) return this.expectValidationPasses() }) it('passes if false', function () { this.setup({ pluginsFile: false }) return this.expectValidationPasses() }) it('fails if not a string or false', function () { this.setup({ pluginsFile: 42 }) return this.expectValidationFails('be a string') }) }) context('port', () => { it('passes if a number', function () { this.setup({ port: 10 }) return this.expectValidationPasses() }) it('fails if not a number', function () { this.setup({ port: 'foo' }) return this.expectValidationFails('be a number') }) }) context('reporter', () => { it('passes if a string', function () { this.setup({ reporter: '_custom' }) return this.expectValidationPasses() }) it('fails if not a string', function () { this.setup({ reporter: true }) return this.expectValidationFails('be a string') }) }) context('requestTimeout', () => { it('passes if a number', function () { this.setup({ requestTimeout: 10 }) return this.expectValidationPasses() }) it('fails if not a number', function () { this.setup({ requestTimeout: 'foo' }) return this.expectValidationFails('be a number') }) }) context('responseTimeout', () => { it('passes if a number', function () { this.setup({ responseTimeout: 10 }) return this.expectValidationPasses() }) it('fails if not a number', function () { this.setup({ responseTimeout: 'foo' }) return this.expectValidationFails('be a number') }) }) context('testFiles', () => { it('passes if a string', function () { this.setup({ testFiles: '**/*.coffee' }) return this.expectValidationPasses() }) it('passes if an array of strings', function () { this.setup({ testFiles: ['**/*.coffee', '**/*.jsx'] }) return this.expectValidationPasses() }) it('fails if not a string or array', function () { this.setup({ testFiles: 42 }) return this.expectValidationFails('be a string or an array of strings') }) it('fails if not an array of strings', function () { this.setup({ testFiles: [5] }) this.expectValidationFails('be a string or an array of strings') return this.expectValidationFails('the value was: `[5]`') }) }) context('supportFile', () => { it('passes if a string', function () { this.setup({ supportFile: 'cypress/support' }) return this.expectValidationPasses() }) it('passes if false', function () { this.setup({ supportFile: false }) return this.expectValidationPasses() }) it('fails if not a string or false', function () { this.setup({ supportFile: true }) return this.expectValidationFails('be a string or false') }) }) context('trashAssetsBeforeRuns', () => { it('passes if a boolean', function () { this.setup({ trashAssetsBeforeRuns: false }) return this.expectValidationPasses() }) it('fails if not a boolean', function () { this.setup({ trashAssetsBeforeRuns: 42 }) return this.expectValidationFails('be a boolean') }) }) context('videoCompression', () => { it('passes if a number', function () { this.setup({ videoCompression: 10 }) return this.expectValidationPasses() }) it('passes if false', function () { this.setup({ videoCompression: false }) return this.expectValidationPasses() }) it('fails if not a number', function () { this.setup({ videoCompression: 'foo' }) return this.expectValidationFails('be a number or false') }) }) context('video', () => { it('passes if a boolean', function () { this.setup({ video: false }) return this.expectValidationPasses() }) it('fails if not a boolean', function () { this.setup({ video: 42 }) return this.expectValidationFails('be a boolean') }) }) context('videoUploadOnPasses', () => { it('passes if a boolean', function () { this.setup({ videoUploadOnPasses: false }) return this.expectValidationPasses() }) it('fails if not a boolean', function () { this.setup({ videoUploadOnPasses: 99 }) return this.expectValidationFails('be a boolean') }) }) context('videosFolder', () => { it('passes if a string', function () { this.setup({ videosFolder: '_videos' }) return this.expectValidationPasses() }) it('fails if not a string', function () { this.setup({ videosFolder: true }) return this.expectValidationFails('be a string') }) }) context('screenshotOnRunFailure', () => { it('passes if a boolean', function () { this.setup({ screenshotOnRunFailure: false }) return this.expectValidationPasses() }) it('fails if not a boolean', function () { this.setup({ screenshotOnRunFailure: 42 }) return this.expectValidationFails('be a boolean') }) }) context('viewportHeight', () => { it('passes if a number', function () { this.setup({ viewportHeight: 10 }) return this.expectValidationPasses() }) it('fails if not a number', function () { this.setup({ viewportHeight: 'foo' }) return this.expectValidationFails('be a number') }) }) context('viewportWidth', () => { it('passes if a number', function () { this.setup({ viewportWidth: 10 }) return this.expectValidationPasses() }) it('fails if not a number', function () { this.setup({ viewportWidth: 'foo' }) return this.expectValidationFails('be a number') }) }) context('waitForAnimations', () => { it('passes if a boolean', function () { this.setup({ waitForAnimations: false }) return this.expectValidationPasses() }) it('fails if not a boolean', function () { this.setup({ waitForAnimations: 42 }) return this.expectValidationFails('be a boolean') }) }) context('scrollBehavior', () => { it('passes if false', function () { this.setup({ scrollBehavior: false }) return this.expectValidationPasses() }) it('passes if an enum (center)', function () { this.setup({ scrollBehavior: 'center' }) return this.expectValidationPasses() }) it('passes if an enum (top)', function () { this.setup({ scrollBehavior: 'top' }) return this.expectValidationPasses() }) it('passes if an enum (bottom)', function () { this.setup({ scrollBehavior: 'bottom' }) return this.expectValidationPasses() }) it('passes if an enum (nearest)', function () { this.setup({ scrollBehavior: 'nearest' }) return this.expectValidationPasses() }) it('fails if not valid (number)', function () { this.setup({ scrollBehavior: 42 }) return this.expectValidationFails('be one of these values') }) it('fails if not a valid (null)', function () { this.setup({ scrollBehavior: null }) return this.expectValidationFails('be one of these values') }) it('fails if not a valid (true)', function () { this.setup({ scrollBehavior: true }) return this.expectValidationFails('be one of these values') }) }) context('watchForFileChanges', () => { it('passes if a boolean', function () { this.setup({ watchForFileChanges: false }) return this.expectValidationPasses() }) it('fails if not a boolean', function () { this.setup({ watchForFileChanges: 42 }) return this.expectValidationFails('be a boolean') }) }) context('blockHosts', () => { it('passes if a string', function () { this.setup({ blockHosts: 'google.com' }) return this.expectValidationPasses() }) it('passes if an array of strings', function () { this.setup({ blockHosts: ['google.com'] }) return this.expectValidationPasses() }) it('fails if not a string or array', function () { this.setup({ blockHosts: 5 }) return this.expectValidationFails('be a string or an array of strings') }) it('fails if not an array of strings', function () { this.setup({ blockHosts: [5] }) this.expectValidationFails('be a string or an array of strings') return this.expectValidationFails('the value was: `[5]`') }) }) context('retries', () => { const retriesError = 'a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls' // need to keep the const here or it'll get stripped by the build // eslint-disable-next-line no-unused-vars const cases = [ [{ retries: null }, 'with null', true], [{ retries: 3 }, 'when a number', true], [{ retries: 3.2 }, 'when a float', false], [{ retries: -1 }, 'with a negative number', false], [{ retries: true }, 'when true', false], [{ retries: false }, 'when false', false], [{ retries: {} }, 'with an empty object', true], [{ retries: { runMode: 3 } }, 'when runMode is a positive number', true], [{ retries: { runMode: -1 } }, 'when runMode is a negative number', false], [{ retries: { openMode: 3 } }, 'when openMode is a positive number', true], [{ retries: { openMode: -1 } }, 'when openMode is a negative number', false], [{ retries: { openMode: 3, TypoRunMode: 3 } }, 'when there is an additional unknown key', false], [{ retries: { openMode: 3, runMode: 3 } }, 'when both runMode and openMode are positive numbers', true], ].forEach(([config, expectation, shouldPass]) => { it(`${shouldPass ? 'passes' : 'fails'} ${expectation}`, function () { this.setup(config) return shouldPass ? this.expectValidationPasses() : this.expectValidationFails(retriesError) }) }) }) context('firefoxGcInterval', () => { it('passes if a number', function () { this.setup({ firefoxGcInterval: 1 }) return this.expectValidationPasses() }) it('passes if null', function () { this.setup({ firefoxGcInterval: null }) return this.expectValidationPasses() }) it('passes if correctly shaped object', function () { this.setup({ firefoxGcInterval: { runMode: 1, openMode: null } }) return this.expectValidationPasses() }) it('fails if string', function () { this.setup({ firefoxGcInterval: 'foo' }) return this.expectValidationFails('a positive number or null or an object') }) it('fails if invalid object', function () { this.setup({ firefoxGcInterval: { foo: 'bar' } }) return this.expectValidationFails('a positive number or null or an object') }) }) }) }) context('.getConfigKeys', () => { beforeEach(function () { this.includes = (key) => { expect(config.getConfigKeys()).to.include(key) } }) it('includes blockHosts', function () { return this.includes('blockHosts') }) }) context('.resolveConfigValues', () => { beforeEach(function () { this.expected = function (obj) { const merged = config.resolveConfigValues(obj.config, obj.defaults, obj.resolved) expect(merged).to.deep.eq(obj.final) } }) it('sets baseUrl to default', function () { return this.expected({ config: { baseUrl: null }, defaults: { baseUrl: null }, resolved: {}, final: { baseUrl: { value: null, from: 'default', }, }, }) }) it('sets baseUrl to config', function () { return this.expected({ config: { baseUrl: 'localhost' }, defaults: { baseUrl: null }, resolved: {}, final: { baseUrl: { value: 'localhost', from: 'config', }, }, }) }) it('does not change existing resolved values', function () { return this.expected({ config: { baseUrl: 'localhost' }, defaults: { baseUrl: null }, resolved: { baseUrl: 'cli' }, final: { baseUrl: { value: 'localhost', from: 'cli', }, }, }) }) it('ignores values not found in configKeys', function () { return this.expected({ config: { baseUrl: 'localhost', foo: 'bar' }, defaults: { baseUrl: null }, resolved: { baseUrl: 'cli' }, final: { baseUrl: { value: 'localhost', from: 'cli', }, }, }) }) }) context('.mergeDefaults', () => { beforeEach(function () { this.defaults = (prop, value, cfg = {}, options = {}) => { cfg.projectRoot = '/foo/bar/' return config.mergeDefaults(cfg, options) .then(R.prop(prop)) .then((result) => { expect(result).to.deep.eq(value) }) } }) it('port=null', function () { return this.defaults('port', null) }) it('projectId=null', function () { return this.defaults('projectId', null) }) it('autoOpen=false', function () { return this.defaults('autoOpen', false) }) it('browserUrl=http://localhost:2020/__/', function () { return this.defaults('browserUrl', 'http://localhost:2020/__/', { port: 2020 }) }) it('proxyUrl=http://localhost:2020', function () { return this.defaults('proxyUrl', 'http://localhost:2020', { port: 2020 }) }) it('namespace=__cypress', function () { return this.defaults('namespace', '__cypress') }) it('baseUrl=http://localhost:8000/app/', function () { return this.defaults('baseUrl', 'http://localhost:8000/app/', { baseUrl: 'http://localhost:8000/app///', }) }) it('baseUrl=http://localhost:8000/app/', function () { return this.defaults('baseUrl', 'http://localhost:8000/app/', { baseUrl: 'http://localhost:8000/app//', }) }) it('baseUrl=http://localhost:8000/app', function () { return this.defaults('baseUrl', 'http://localhost:8000/app', { baseUrl: 'http://localhost:8000/app', }) }) it('baseUrl=http://localhost:8000/', function () { return this.defaults('baseUrl', 'http://localhost:8000/', { baseUrl: 'http://localhost:8000//', }) }) it('baseUrl=http://localhost:8000/', function () { return this.defaults('baseUrl', 'http://localhost:8000/', { baseUrl: 'http://localhost:8000/', }) }) it('baseUrl=http://localhost:8000', function () { return this.defaults('baseUrl', 'http://localhost:8000', { baseUrl: 'http://localhost:8000', }) }) it('javascripts=[]', function () { return this.defaults('javascripts', []) }) it('viewportWidth=1000', function () { return this.defaults('viewportWidth', 1000) }) it('viewportHeight=660', function () { return this.defaults('viewportHeight', 660) }) it('userAgent=null', function () { return this.defaults('userAgent', null) }) it('baseUrl=null', function () { return this.defaults('baseUrl', null) }) it('defaultCommandTimeout=4000', function () { return this.defaults('defaultCommandTimeout', 4000) }) it('pageLoadTimeout=60000', function () { return this.defaults('pageLoadTimeout', 60000) }) it('requestTimeout=5000', function () { return this.defaults('requestTimeout', 5000) }) it('responseTimeout=30000', function () { return this.defaults('responseTimeout', 30000) }) it('execTimeout=60000', function () { return this.defaults('execTimeout', 60000) }) it('waitForAnimations=true', function () { return this.defaults('waitForAnimations', true) }) it('scrollBehavior=start', function () { return this.defaults('scrollBehavior', 'top') }) it('animationDistanceThreshold=5', function () { return this.defaults('animationDistanceThreshold', 5) }) it('video=true', function () { return this.defaults('video', true) }) it('videoCompression=32', function () { return this.defaults('videoCompression', 32) }) it('videoUploadOnPasses=true', function () { return this.defaults('videoUploadOnPasses', true) }) it('trashAssetsBeforeRuns=32', function () { return this.defaults('trashAssetsBeforeRuns', true) }) it('morgan=true', function () { return this.defaults('morgan', true) }) it('isTextTerminal=false', function () { return this.defaults('isTextTerminal', false) }) it('socketId=null', function () { return this.defaults('socketId', null) }) it('reporter=spec', function () { return this.defaults('reporter', 'spec') }) it('watchForFileChanges=true', function () { return this.defaults('watchForFileChanges', true) }) it('numTestsKeptInMemory=50', function () { return this.defaults('numTestsKeptInMemory', 50) }) it('modifyObstructiveCode=true', function () { return this.defaults('modifyObstructiveCode', true) }) it('supportFile=false', function () { return this.defaults('supportFile', false, { supportFile: false }) }) it('blockHosts=null', function () { return this.defaults('blockHosts', null) }) it('blockHosts=[a,b]', function () { return this.defaults('blockHosts', ['a', 'b'], { blockHosts: ['a', 'b'], }) }) it('blockHosts=a|b', function () { return this.defaults('blockHosts', ['a', 'b'], { blockHosts: ['a', 'b'], }) }) it('hosts=null', function () { return this.defaults('hosts', null) }) it('hosts={}', function () { return this.defaults('hosts', { foo: 'bar', baz: 'quux', }, { hosts: { foo: 'bar', baz: 'quux', }, }) }) it('resets numTestsKeptInMemory to 0 when runMode', () => { return config.mergeDefaults({ projectRoot: '/foo/bar/' }, { isTextTerminal: true }) .then((cfg) => { expect(cfg.numTestsKeptInMemory).to.eq(0) }) }) it('resets watchForFileChanges to false when runMode', () => { return config.mergeDefaults({ projectRoot: '/foo/bar/' }, { isTextTerminal: true }) .then((cfg) => { expect(cfg.watchForFileChanges).to.be.false }) }) it('can override morgan in options', () => { return config.mergeDefaults({ projectRoot: '/foo/bar/' }, { morgan: false }) .then((cfg) => { expect(cfg.morgan).to.be.false }) }) it('can override isTextTerminal in options', () => { return config.mergeDefaults({ projectRoot: '/foo/bar/' }, { isTextTerminal: true }) .then((cfg) => { expect(cfg.isTextTerminal).to.be.true }) }) it('can override socketId in options', () => { return config.mergeDefaults({ projectRoot: '/foo/bar/' }, { socketId: 1234 }) .then((cfg) => { expect(cfg.socketId).to.eq(1234) }) }) it('deletes envFile', () => { const obj = { projectRoot: '/foo/bar/', env: { foo: 'bar', version: '0.5.2', }, envFile: { bar: 'baz', version: '1.0.1', }, } return config.mergeDefaults(obj) .then((cfg) => { expect(cfg.env).to.deep.eq({ foo: 'bar', bar: 'baz', version: '1.0.1', }) expect(cfg.cypressEnv).to.eq(process.env['CYPRESS_INTERNAL_ENV']) expect(cfg).not.to.have.property('envFile') }) }) it('merges env into @config.env', () => { const obj = { projectRoot: '/foo/bar/', env: { host: 'localhost', user: 'brian', version: '0.12.2', }, } const options = { env: { version: '0.13.1', foo: 'bar', }, } return config.mergeDefaults(obj, options) .then((cfg) => { expect(cfg.env).to.deep.eq({ host: 'localhost', user: 'brian', version: '0.13.1', foo: 'bar', }) }) }) // @see https://github.com/cypress-io/cypress/issues/6892 it('warns if experimentalGetCookiesSameSite is passed', async function () { const warning = sinon.spy(errors, 'warning') await this.defaults('experimentalGetCookiesSameSite', true, { experimentalGetCookiesSameSite: true, }) expect(warning).to.be.calledWith('EXPERIMENTAL_SAMESITE_REMOVED') }) it('warns if experimentalShadowDomSupport is passed', async function () { const warning = sinon.spy(errors, 'warning') await this.defaults('experimentalShadowDomSupport', true, { experimentalShadowDomSupport: true, }) expect(warning).to.be.calledWith('EXPERIMENTAL_SHADOW_DOM_REMOVED') }) it('warns if experimentalRunEvents is passed', async function () { const warning = sinon.spy(errors, 'warning') await this.defaults('experimentalRunEvents', true, { experimentalRunEvents: true, }) expect(warning).to.be.calledWith('EXPERIMENTAL_RUN_EVENTS_REMOVED') }) // @see https://github.com/cypress-io/cypress/pull/9185 it('warns if experimentalNetworkStubbing is passed', async function () { const warning = sinon.spy(errors, 'warning') await this.defaults('experimentalNetworkStubbing', true, { experimentalNetworkStubbing: true, }) expect(warning).to.be.calledWith('EXPERIMENTAL_NETWORK_STUBBING_REMOVED') }) describe('.resolved', () => { it('sets reporter and port to cli', () => { const obj = { projectRoot: '/foo/bar', } const options = { reporter: 'json', port: 1234, } return config.mergeDefaults(obj, options) .then((cfg) => { expect(cfg.resolved).to.deep.eq({ animationDistanceThreshold: { value: 5, from: 'default' }, baseUrl: { value: null, from: 'default' }, blockHosts: { value: null, from: 'default' }, browsers: { value: [], from: 'default' }, chromeWebSecurity: { value: true, from: 'default' }, componentFolder: { value: 'cypress/component', from: 'default' }, defaultCommandTimeout: { value: 4000, from: 'default' }, downloadsFolder: { value: 'cypress/downloads', from: 'default' }, env: {}, execTimeout: { value: 60000, from: 'default' }, experimentalComponentTesting: { value: false, from: 'default' }, experimentalFetchPolyfill: { value: false, from: 'default' }, experimentalSourceRewriting: { value: false, from: 'default' }, experimentalStudio: { value: false, from: 'default' }, fileServerFolder: { value: '', from: 'default' }, firefoxGcInterval: { value: { openMode: null, runMode: 1 }, from: 'default' }, fixturesFolder: { value: 'cypress/fixtures', from: 'default' }, hosts: { value: null, from: 'default' }, ignoreTestFiles: { value: '*.hot-update.js', from: 'default' }, includeShadowDom: { value: false, from: 'default' }, integrationFolder: { value: 'cypress/integration', from: 'default' }, modifyObstructiveCode: { value: true, from: 'default' }, nodeVersion: { value: 'default', from: 'default' }, numTestsKeptInMemory: { value: 50, from: 'default' }, pageLoadTimeout: { value: 60000, from: 'default' }, pluginsFile: { value: 'cypress/plugins', from: 'default' }, port: { value: 1234, from: 'cli' }, projectId: { value: null, from: 'default' }, reporter: { value: 'json', from: 'cli' }, reporterOptions: { value: null, from: 'default' }, requestTimeout: { value: 5000, from: 'default' }, responseTimeout: { value: 30000, from: 'default' }, retries: { value: { runMode: 0, openMode: 0 }, from: 'default' }, screenshotOnRunFailure: { value: true, from: 'default' }, screenshotsFolder: { value: 'cypress/screenshots', from: 'default' }, supportFile: { value: 'cypress/support', from: 'default' }, taskTimeout: { value: 60000, from: 'default' }, testFiles: { value: '**/*.*', from: 'default' }, trashAssetsBeforeRuns: { value: true, from: 'default' }, userAgent: { value: null, from: 'default' }, video: { value: true, from: 'default' }, videoCompression: { value: 32, from: 'default' }, videosFolder: { value: 'cypress/videos', from: 'default' }, videoUploadOnPasses: { value: true, from: 'default' }, viewportHeight: { value: 660, from: 'default' }, viewportWidth: { value: 1000, from: 'default' }, waitForAnimations: { value: true, from: 'default' }, scrollBehavior: { value: 'top', from: 'default' }, watchForFileChanges: { value: true, from: 'default' }, }) }) }) it('sets config, envFile and env', () => { sinon.stub(config, 'getProcessEnvVars').returns({ quux: 'quux', RECORD_KEY: 'foobarbazquux', PROJECT_ID: 'projectId123', }) const obj = { projectRoot: '/foo/bar', baseUrl: 'http://localhost:8080', port: 2020, env: { foo: 'foo', }, envFile: { bar: 'bar', }, } const options = { env: { baz: 'baz', }, } return config.mergeDefaults(obj, options) .then((cfg) => { expect(cfg.resolved).to.deep.eq({ animationDistanceThreshold: { value: 5, from: 'default' }, baseUrl: { value: 'http://localhost:8080', from: 'config' }, blockHosts: { value: null, from: 'default' }, browsers: { value: [], from: 'default' }, chromeWebSecurity: { value: true, from: 'default' }, componentFolder: { value: 'cypress/component', from: 'default' }, defaultCommandTimeout: { value: 4000, from: 'default' }, downloadsFolder: { value: 'cypress/downloads', from: 'default' }, execTimeout: { value: 60000, from: 'default' }, experimentalComponentTesting: { value: false, from: 'default' }, experimentalFetchPolyfill: { value: false, from: 'default' }, experimentalSourceRewriting: { value: false, from: 'default' }, experimentalStudio: { value: false, from: 'default' }, env: { foo: { value: 'foo', from: 'config', }, bar: { value: 'bar', from: 'envFile', }, baz: { value: 'baz', from: 'cli', }, quux: { value: 'quux', from: 'env', }, RECORD_KEY: { value: 'fooba...zquux', from: 'env', }, }, fileServerFolder: { value: '', from: 'default' }, firefoxGcInterval: { value: { openMode: null, runMode: 1 }, from: 'default' }, fixturesFolder: { value: 'cypress/fixtures', from: 'default' }, hosts: { value: null, from: 'default' }, ignoreTestFiles: { value: '*.hot-update.js', from: 'default' }, includeShadowDom: { value: false, from: 'default' }, integrationFolder: { value: 'cypress/integration', from: 'default' }, modifyObstructiveCode: { value: true, from: 'default' }, nodeVersion: { value: 'default', from: 'default' }, numTestsKeptInMemory: { value: 50, from: 'default' }, pageLoadTimeout: { value: 60000, from: 'default' }, pluginsFile: { value: 'cypress/plugins', from: 'default' }, port: { value: 2020, from: 'config' }, projectId: { value: 'projectId123', from: 'env' }, reporter: { value: 'spec', from: 'default' }, reporterOptions: { value: null, from: 'default' }, requestTimeout: { value: 5000, from: 'default' }, responseTimeout: { value: 30000, from: 'default' }, retries: { value: { runMode: 0, openMode: 0 }, from: 'default' }, screenshotOnRunFailure: { value: true, from: 'default' }, screenshotsFolder: { value: 'cypress/screenshots', from: 'default' }, supportFile: { value: 'cypress/support', from: 'default' }, taskTimeout: { value: 60000, from: 'default' }, testFiles: { value: '**/*.*', from: 'default' }, trashAssetsBeforeRuns: { value: true, from: 'default' }, userAgent: { value: null, from: 'default' }, video: { value: true, from: 'default' }, videoCompression: { value: 32, from: 'default' }, videosFolder: { value: 'cypress/videos', from: 'default' }, videoUploadOnPasses: { value: true, from: 'default' }, viewportHeight: { value: 660, from: 'default' }, viewportWidth: { value: 1000, from: 'default' }, waitForAnimations: { value: true, from: 'default' }, scrollBehavior: { value: 'top', from: 'default' }, watchForFileChanges: { value: true, from: 'default' }, }) }) }) }) }) context('.setPluginResolvedOn', () => { it('resolves an object with single property', () => { const cfg = {} const obj = { foo: 'bar', } config.setPluginResolvedOn(cfg, obj) expect(cfg).to.deep.eq({ foo: { value: 'bar', from: 'plugin', }, }) }) it('resolves an object with multiple properties', () => { const cfg = {} const obj = { foo: 'bar', baz: [1, 2, 3], } config.setPluginResolvedOn(cfg, obj) expect(cfg).to.deep.eq({ foo: { value: 'bar', from: 'plugin', }, baz: { value: [1, 2, 3], from: 'plugin', }, }) }) it('resolves a nested object', () => { // we need at least the structure const cfg = { foo: { bar: 1, }, } const obj = { foo: { bar: 42, }, } config.setPluginResolvedOn(cfg, obj) expect(cfg, 'foo.bar gets value').to.deep.eq({ foo: { bar: { value: 42, from: 'plugin', }, }, }) }) // https://github.com/cypress-io/cypress/issues/7959 it('resolves a single object', () => { const cfg = { } const obj = { foo: { bar: { baz: 42, }, }, } config.setPluginResolvedOn(cfg, obj) expect(cfg).to.deep.eq({ foo: { from: 'plugin', value: { bar: { baz: 42, }, }, }, }) }) }) context('_.defaultsDeep', () => { it('merges arrays', () => { // sanity checks to confirm how Lodash merges arrays in defaultsDeep const diffs = { list: [1], } const cfg = { list: [1, 2], } const merged = _.defaultsDeep({}, diffs, cfg) expect(merged, 'arrays are combined').to.deep.eq({ list: [1, 2], }) }) }) context('.updateWithPluginValues', () => { it('is noop when no overrides', () => { expect(config.updateWithPluginValues({ foo: 'bar' }, null)).to.deep.eq({ foo: 'bar', }) }) it('is noop with empty overrides', () => { expect(config.updateWithPluginValues({ foo: 'bar' }, {})).to.deep.eq({ foo: 'bar', }) }) it('updates resolved config values and returns config with overrides', () => { const cfg = { foo: 'bar', baz: 'quux', quux: 'foo', lol: 1234, env: { a: 'a', b: 'b', }, // previously resolved values resolved: { foo: { value: 'bar', from: 'default' }, baz: { value: 'quux', from: 'cli' }, quux: { value: 'foo', from: 'default' }, lol: { value: 1234, from: 'env' }, env: { a: { value: 'a', from: 'config' }, b: { value: 'b', from: 'config' }, }, }, } const overrides = { baz: 'baz', quux: ['bar', 'quux'], env: { b: 'bb', c: 'c', }, } expect(config.updateWithPluginValues(cfg, overrides)).to.deep.eq({ foo: 'bar', baz: 'baz', lol: 1234, quux: ['bar', 'quux'], env: { a: 'a', b: 'bb', c: 'c', }, resolved: { foo: { value: 'bar', from: 'default' }, baz: { value: 'baz', from: 'plugin' }, quux: { value: ['bar', 'quux'], from: 'plugin' }, lol: { value: 1234, from: 'env' }, env: { a: { value: 'a', from: 'config' }, b: { value: 'bb', from: 'plugin' }, c: { value: 'c', from: 'plugin' }, }, }, }) }) it('keeps the list of browsers if the plugins returns empty object', () => { const browser = { name: 'fake browser name', family: 'chromium', displayName: 'My browser', version: 'x.y.z', path: '/path/to/browser', majorVersion: 'x', } const cfg = { browsers: [browser], resolved: { browsers: { value: [browser], from: 'default', }, }, } const overrides = {} expect(config.updateWithPluginValues(cfg, overrides)).to.deep.eq({ browsers: [browser], resolved: { browsers: { value: [browser], from: 'default', }, }, }) }) it('catches browsers=null returned from plugins', () => { const browser = { name: 'fake browser name', family: 'chromium', displayName: 'My browser', version: 'x.y.z', path: '/path/to/browser', majorVersion: 'x', } const cfg = { browsers: [browser], resolved: { browsers: { value: [browser], from: 'default', }, }, } const overrides = { browsers: null, } sinon.stub(errors, 'throw') config.updateWithPluginValues(cfg, overrides) expect(errors.throw).to.have.been.calledWith('CONFIG_VALIDATION_ERROR') }) it('allows user to filter browsers', () => { const browserOne = { name: 'fake browser name', family: 'chromium', displayName: 'My browser', version: 'x.y.z', path: '/path/to/browser', majorVersion: 'x', } const browserTwo = { name: 'fake electron', family: 'chromium', displayName: 'Electron', version: 'x.y.z', // Electron browser is built-in, no external path path: '', majorVersion: 'x', } const cfg = { browsers: [browserOne, browserTwo], resolved: { browsers: { value: [browserOne, browserTwo], from: 'default', }, }, } const overrides = { browsers: [browserTwo], } const updated = config.updateWithPluginValues(cfg, overrides) expect(updated.resolved, 'resolved values').to.deep.eq({ browsers: { value: [browserTwo], from: 'plugin', }, }) expect(updated, 'all values').to.deep.eq({ browsers: [browserTwo], resolved: { browsers: { value: [browserTwo], from: 'plugin', }, }, }) }) }) context('.parseEnv', () => { it('merges together env from config, env from file, env from process, and env from CLI', () => { sinon.stub(config, 'getProcessEnvVars').returns({ version: '0.12.1', user: 'bob',