mirror of
https://github.com/cypress-io/cypress.git
synced 2026-05-06 23:10:22 -05:00
feat: create config package for config validation (#18589)
This commit is contained in:
@@ -34,6 +34,7 @@ system-tests/projects/e2e/cypress/integration/typescript_syntax_error_spec.ts
|
||||
|
||||
# cli/types is linted by tslint/dtslint
|
||||
cli/types
|
||||
|
||||
# packages/example is not linted (think about changing this)
|
||||
packages/example
|
||||
|
||||
|
||||
+3
-1
@@ -13,6 +13,9 @@ cypress.zip
|
||||
Cached Theme.pak
|
||||
Cached Theme Material Design.pak
|
||||
|
||||
# from config, compiled .js files
|
||||
packages/config/lib/*.js
|
||||
|
||||
# from data-context, compiled .js files
|
||||
packages/data-context/src/**/*.js
|
||||
|
||||
@@ -20,7 +23,6 @@ packages/data-context/src/**/*.js
|
||||
packages/desktop-gui/cypress/videos
|
||||
packages/desktop-gui/src/jsconfig.json
|
||||
|
||||
|
||||
# from driver
|
||||
packages/driver/cypress/videos
|
||||
packages/driver/cypress/screenshots
|
||||
|
||||
Vendored
+3
-1
@@ -1,6 +1,8 @@
|
||||
{
|
||||
// To see these extensions in VS Code:
|
||||
// 1. Open the Command Palette (Ctrl+Shift+P)
|
||||
// 1. Open the Command Palette:
|
||||
// - Non-Mac Users: (Ctrl+Shift+P)
|
||||
// - Mac Users: (Cmd+Shift+P)
|
||||
// 2. Select "Extensions: Show Recommended Extensions"
|
||||
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
||||
|
||||
+1
-1
@@ -963,7 +963,7 @@ jobs:
|
||||
desktop-gui-component-tests,
|
||||
cli-visual-tests,
|
||||
runner-integration-tests-chrome,
|
||||
runner-ct-integration-tests-chrome
|
||||
runner-ct-integration-tests-chrome,
|
||||
reporter-integration-tests,
|
||||
- run: yarn percy build:finalize
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"properties": {
|
||||
"baseUrl": {
|
||||
"type": "string",
|
||||
"default": null,
|
||||
"description": "Url used as prefix for cy.visit() or cy.request() command’s url. Example http://localhost:3030 or https://test.my-domain.com"
|
||||
},
|
||||
"env": {
|
||||
|
||||
+2
-2
@@ -17,7 +17,7 @@
|
||||
"bump": "node ./scripts/binary.js bump",
|
||||
"check-node-version": "node scripts/check-node-version.js",
|
||||
"check-terminal": "node scripts/check-terminal.js",
|
||||
"clean": "lerna run clean --parallel",
|
||||
"clean": "lerna run clean --parallel --no-bail",
|
||||
"clean-deps": "find . -depth -name node_modules -type d -exec rm -rf {} \\;",
|
||||
"clean-untracked-files": "git clean -d -f",
|
||||
"precypress:open": "yarn ensure-deps",
|
||||
@@ -46,7 +46,7 @@
|
||||
"stop-only": "npx stop-only --skip .cy,.publish,.projects,node_modules,dist,dist-test,fixtures,lib,bower_components,src,__snapshots__ --exclude e2e.ts,cypress-tests.ts",
|
||||
"stop-only-all": "yarn stop-only --folder packages",
|
||||
"pretest": "yarn ensure-deps",
|
||||
"test": "yarn lerna exec yarn test --scope cypress --scope \"'@packages/{electron,extension,https-proxy,launcher,net-stubbing,network,proxy,rewriter,runner,runner-shared,socket}'\"",
|
||||
"test": "yarn lerna exec yarn test --scope cypress --scope \"'@packages/{config,electron,extension,https-proxy,launcher,net-stubbing,network,proxy,rewriter,runner,runner-shared,socket}'\"",
|
||||
"test-debug": "lerna exec yarn test-debug --ignore \"'@packages/{desktop-gui,driver,root,static,web-config}'\"",
|
||||
"pretest-e2e": "yarn ensure-deps",
|
||||
"test-integration": "lerna exec yarn test-integration --ignore \"'@packages/{desktop-gui,driver,root,static,web-config}'\"",
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": [
|
||||
"../../.eslintrc.json"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
# Config
|
||||
|
||||
The `config` package contains the configuration types and validation used in both the `server` and the `driver` for setting the Cypress configuration values.
|
||||
|
||||
## Testing
|
||||
|
||||
### Unit Tests
|
||||
|
||||
```bash
|
||||
yarn workspace @packages/config test-unit
|
||||
```
|
||||
@@ -0,0 +1,146 @@
|
||||
exports['src/index .getBreakingKeys returns list of breaking config keys 1'] = [
|
||||
"blacklistHosts",
|
||||
"experimentalComponentTesting",
|
||||
"experimentalGetCookiesSameSite",
|
||||
"experimentalNetworkStubbing",
|
||||
"experimentalRunEvents",
|
||||
"experimentalShadowDomSupport",
|
||||
"firefoxGcInterval",
|
||||
"nodeVersion",
|
||||
"nodeVersion"
|
||||
]
|
||||
|
||||
exports['src/index .getDefaultValues returns list of public config keys 1'] = {
|
||||
"animationDistanceThreshold": 5,
|
||||
"baseUrl": null,
|
||||
"blockHosts": null,
|
||||
"chromeWebSecurity": true,
|
||||
"clientCertificates": [],
|
||||
"component": {},
|
||||
"componentFolder": "cypress/component",
|
||||
"defaultCommandTimeout": 4000,
|
||||
"downloadsFolder": "cypress/downloads",
|
||||
"e2e": {},
|
||||
"env": {},
|
||||
"execTimeout": 60000,
|
||||
"experimentalFetchPolyfill": false,
|
||||
"experimentalInteractiveRunEvents": false,
|
||||
"experimentalSessionSupport": false,
|
||||
"experimentalSourceRewriting": false,
|
||||
"experimentalStudio": false,
|
||||
"fileServerFolder": "",
|
||||
"fixturesFolder": "cypress/fixtures",
|
||||
"ignoreTestFiles": "*.hot-update.js",
|
||||
"includeShadowDom": false,
|
||||
"integrationFolder": "cypress/integration",
|
||||
"modifyObstructiveCode": true,
|
||||
"numTestsKeptInMemory": 50,
|
||||
"pageLoadTimeout": 60000,
|
||||
"pluginsFile": "cypress/plugins",
|
||||
"port": null,
|
||||
"projectId": null,
|
||||
"redirectionLimit": 20,
|
||||
"reporter": "spec",
|
||||
"reporterOptions": null,
|
||||
"requestTimeout": 5000,
|
||||
"resolvedNodePath": null,
|
||||
"resolvedNodeVersion": null,
|
||||
"responseTimeout": 30000,
|
||||
"retries": {
|
||||
"runMode": 0,
|
||||
"openMode": 0
|
||||
},
|
||||
"screenshotOnRunFailure": true,
|
||||
"screenshotsFolder": "cypress/screenshots",
|
||||
"slowTestThreshold": 10000,
|
||||
"scrollBehavior": "top",
|
||||
"supportFile": "cypress/support",
|
||||
"supportFolder": false,
|
||||
"taskTimeout": 60000,
|
||||
"testFiles": "**/*.*",
|
||||
"trashAssetsBeforeRuns": true,
|
||||
"userAgent": null,
|
||||
"video": true,
|
||||
"videoCompression": 32,
|
||||
"videosFolder": "cypress/videos",
|
||||
"videoUploadOnPasses": true,
|
||||
"viewportHeight": 660,
|
||||
"viewportWidth": 1000,
|
||||
"waitForAnimations": true,
|
||||
"watchForFileChanges": true,
|
||||
"autoOpen": false,
|
||||
"browsers": [],
|
||||
"clientRoute": "/__/",
|
||||
"configFile": "cypress.json",
|
||||
"devServerPublicPathRoute": "/__cypress/src",
|
||||
"hosts": null,
|
||||
"isTextTerminal": false,
|
||||
"morgan": true,
|
||||
"namespace": "__cypress",
|
||||
"reporterRoute": "/__cypress/reporter",
|
||||
"socketId": null,
|
||||
"socketIoCookie": "__socket.io",
|
||||
"socketIoRoute": "/__socket.io",
|
||||
"xhrRoute": "/xhrs/"
|
||||
}
|
||||
|
||||
exports['src/index .getPublicConfigKeys returns list of public config keys 1'] = [
|
||||
"animationDistanceThreshold",
|
||||
"baseUrl",
|
||||
"blockHosts",
|
||||
"chromeWebSecurity",
|
||||
"clientCertificates",
|
||||
"component",
|
||||
"componentFolder",
|
||||
"defaultCommandTimeout",
|
||||
"downloadsFolder",
|
||||
"e2e",
|
||||
"env",
|
||||
"execTimeout",
|
||||
"experimentalFetchPolyfill",
|
||||
"experimentalInteractiveRunEvents",
|
||||
"experimentalSessionSupport",
|
||||
"experimentalSourceRewriting",
|
||||
"experimentalStudio",
|
||||
"fileServerFolder",
|
||||
"fixturesFolder",
|
||||
"ignoreTestFiles",
|
||||
"includeShadowDom",
|
||||
"integrationFolder",
|
||||
"modifyObstructiveCode",
|
||||
"nodeVersion",
|
||||
"numTestsKeptInMemory",
|
||||
"pageLoadTimeout",
|
||||
"pluginsFile",
|
||||
"port",
|
||||
"projectId",
|
||||
"redirectionLimit",
|
||||
"reporter",
|
||||
"reporterOptions",
|
||||
"requestTimeout",
|
||||
"resolvedNodePath",
|
||||
"resolvedNodeVersion",
|
||||
"responseTimeout",
|
||||
"retries",
|
||||
"screenshotOnRunFailure",
|
||||
"screenshotsFolder",
|
||||
"slowTestThreshold",
|
||||
"scrollBehavior",
|
||||
"supportFile",
|
||||
"supportFolder",
|
||||
"taskTimeout",
|
||||
"testFiles",
|
||||
"trashAssetsBeforeRuns",
|
||||
"userAgent",
|
||||
"video",
|
||||
"videoCompression",
|
||||
"videosFolder",
|
||||
"videoUploadOnPasses",
|
||||
"viewportHeight",
|
||||
"viewportWidth",
|
||||
"waitForAnimations",
|
||||
"watchForFileChanges",
|
||||
"browsers",
|
||||
"hosts",
|
||||
"modifyObstructiveCode"
|
||||
]
|
||||
@@ -0,0 +1,157 @@
|
||||
exports['undefined browsers'] = `
|
||||
Missing browsers list
|
||||
`
|
||||
|
||||
exports['empty list of browsers'] = `
|
||||
Expected at least one browser
|
||||
`
|
||||
|
||||
exports['browsers list with a string'] = `
|
||||
Found an error while validating the \`browsers\` list. Expected \`name\` to be a non-empty string. Instead the value was: \`"foo"\`
|
||||
`
|
||||
|
||||
exports['src/validation .isValidBrowser passes valid browsers and forms error messages for invalid ones isValidBrowser 1'] = {
|
||||
"name": "isValidBrowser",
|
||||
"behavior": [
|
||||
{
|
||||
"given": {
|
||||
"name": "Chrome",
|
||||
"displayName": "Chrome Browser",
|
||||
"family": "chromium",
|
||||
"path": "/path/to/chrome",
|
||||
"version": "1.2.3",
|
||||
"majorVersion": 1
|
||||
},
|
||||
"expect": true
|
||||
},
|
||||
{
|
||||
"given": {
|
||||
"name": "FF",
|
||||
"displayName": "Firefox",
|
||||
"family": "firefox",
|
||||
"path": "/path/to/firefox",
|
||||
"version": "1.2.3",
|
||||
"majorVersion": "1"
|
||||
},
|
||||
"expect": true
|
||||
},
|
||||
{
|
||||
"given": {
|
||||
"name": "Electron",
|
||||
"displayName": "Electron",
|
||||
"family": "chromium",
|
||||
"path": "",
|
||||
"version": "99.101.3",
|
||||
"majorVersion": 99
|
||||
},
|
||||
"expect": true
|
||||
},
|
||||
{
|
||||
"given": {
|
||||
"name": "No display name",
|
||||
"family": "chromium"
|
||||
},
|
||||
"expect": "Expected `displayName` to be a non-empty string. Instead the value was: `{\"name\":\"No display name\",\"family\":\"chromium\"}`"
|
||||
},
|
||||
{
|
||||
"given": {
|
||||
"name": "bad family",
|
||||
"displayName": "Bad family browser",
|
||||
"family": "unknown family"
|
||||
},
|
||||
"expect": "Expected `family` to be either chromium or firefox. Instead the value was: `{\"name\":\"bad family\",\"displayName\":\"Bad family browser\",\"family\":\"unknown family\"}`"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
exports['not one of the strings error message'] = `
|
||||
Expected \`test\` to be one of these values: "foo", "bar". Instead the value was: \`"nope"\`
|
||||
`
|
||||
|
||||
exports['number instead of string'] = `
|
||||
Expected \`test\` to be one of these values: "foo", "bar". Instead the value was: \`42\`
|
||||
`
|
||||
|
||||
exports['null instead of string'] = `
|
||||
Expected \`test\` to be one of these values: "foo", "bar". Instead the value was: \`null\`
|
||||
`
|
||||
|
||||
exports['not one of the numbers error message'] = `
|
||||
Expected \`test\` to be one of these values: 1, 2, 3. Instead the value was: \`4\`
|
||||
`
|
||||
|
||||
exports['string instead of a number'] = `
|
||||
Expected \`test\` to be one of these values: 1, 2, 3. Instead the value was: \`"foo"\`
|
||||
`
|
||||
|
||||
exports['null instead of a number'] = `
|
||||
Expected \`test\` to be one of these values: 1, 2, 3. Instead the value was: \`null\`
|
||||
`
|
||||
|
||||
exports['src/validation .isStringOrFalse returns error message when value is neither string nor false 1'] = `
|
||||
Expected \`mockConfigKey\` to be a string or false. Instead the value was: \`null\`
|
||||
`
|
||||
|
||||
exports['src/validation .isBoolean returns error message when value is a not a string 1'] = `
|
||||
Expected \`mockConfigKey\` to be a string. Instead the value was: \`1\`
|
||||
`
|
||||
|
||||
exports['src/validation .isString returns error message when value is a not a string 1'] = `
|
||||
Expected \`mockConfigKey\` to be a string. Instead the value was: \`1\`
|
||||
`
|
||||
|
||||
exports['src/validation .isArray returns error message when value is a non-array 1'] = `
|
||||
Expected \`mockConfigKey\` to be an array. Instead the value was: \`1\`
|
||||
`
|
||||
|
||||
exports['not string or array'] = `
|
||||
Expected \`mockConfigKey\` to be a string or an array of strings. Instead the value was: \`null\`
|
||||
`
|
||||
|
||||
exports['array of non-strings'] = `
|
||||
Expected \`mockConfigKey\` to be a string or an array of strings. Instead the value was: \`[1,2,3]\`
|
||||
`
|
||||
|
||||
exports['src/validation .isNumberOrFalse returns error message when value is a not number or false 1'] = `
|
||||
Expected \`mockConfigKey\` to be a number or false. Instead the value was: \`null\`
|
||||
`
|
||||
|
||||
exports['src/validation .isPlainObject returns error message when value is a not an object 1'] = `
|
||||
Expected \`mockConfigKey\` to be a plain object. Instead the value was: \`1\`
|
||||
`
|
||||
|
||||
exports['src/validation .isNumber returns error message when value is a not a number 1'] = `
|
||||
Expected \`mockConfigKey\` to be a number. Instead the value was: \`"string"\`
|
||||
`
|
||||
|
||||
exports['invalid retry value'] = `
|
||||
Expected \`mockConfigKey\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\`
|
||||
`
|
||||
|
||||
exports['invalid retry object'] = `
|
||||
Expected \`mockConfigKey\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`{"fakeMode":1}\`
|
||||
`
|
||||
|
||||
exports['src/validation .isValidClientCertificatesSet returns error message for certs not passed as an array array 1'] = `
|
||||
Expected \`mockConfigKey\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\`
|
||||
`
|
||||
|
||||
exports['src/validation .isValidClientCertificatesSet returns error message for certs object without url 1'] = `
|
||||
Expected \`clientCertificates[0].url\` to be a URL matcher. Instead the value was: \`undefined\`
|
||||
`
|
||||
|
||||
exports['missing https protocol'] = `
|
||||
Expected \`clientCertificates[0].url\` to be an https protocol. Instead the value was: \`"http://url.com"\`
|
||||
`
|
||||
|
||||
exports['invalid url'] = `
|
||||
Expected \`clientCertificates[0].url\` to be a valid URL. Instead the value was: \`"not *"\`
|
||||
`
|
||||
|
||||
exports['not qualified url'] = `
|
||||
Expected \`mockConfigKey\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`"url.com"\`
|
||||
`
|
||||
|
||||
exports['empty string'] = `
|
||||
Expected \`mockConfigKey\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`""\`
|
||||
`
|
||||
@@ -0,0 +1,104 @@
|
||||
const _ = require('lodash')
|
||||
const debug = require('debug')('cypress:config:validator')
|
||||
|
||||
const { options, breakingOptions } = require('./options')
|
||||
|
||||
const dashesOrUnderscoresRe = /^(_-)+/
|
||||
|
||||
// takes an array and creates an index object of [keyKey]: [valueKey]
|
||||
const createIndex = (arr, keyKey, valueKey) => {
|
||||
return _.reduce(arr, (memo, item) => {
|
||||
if (item[valueKey] !== undefined) {
|
||||
memo[item[keyKey]] = item[valueKey]
|
||||
}
|
||||
|
||||
return memo
|
||||
}, {})
|
||||
}
|
||||
|
||||
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')
|
||||
|
||||
module.exports = {
|
||||
allowed: (obj = {}) => {
|
||||
const propertyNames = publicConfigKeys.concat(breakingKeys)
|
||||
|
||||
return _.pick(obj, propertyNames)
|
||||
},
|
||||
|
||||
getBreakingKeys: () => {
|
||||
return breakingKeys
|
||||
},
|
||||
|
||||
getDefaultValues: (runtimeOptions = {}) => {
|
||||
// Default values can be functions, in which case they are evaluated
|
||||
// at runtime - for example, slowTestThreshold where the default value
|
||||
// varies between e2e and component testing.
|
||||
return _.mapValues(defaultValues, (value) => (typeof value === 'function' ? value(runtimeOptions) : value))
|
||||
},
|
||||
|
||||
getPublicConfigKeys: () => {
|
||||
return publicConfigKeys
|
||||
},
|
||||
|
||||
matchesConfigKey: (key) => {
|
||||
if (_.has(defaultValues, key)) {
|
||||
return key
|
||||
}
|
||||
|
||||
key = key.toLowerCase().replace(dashesOrUnderscoresRe, '')
|
||||
key = _.camelCase(key)
|
||||
|
||||
if (_.has(defaultValues, key)) {
|
||||
return key
|
||||
}
|
||||
},
|
||||
|
||||
options,
|
||||
|
||||
validate: (cfg, onErr) => {
|
||||
debug('validating configuration')
|
||||
|
||||
return _.each(cfg, (value, key) => {
|
||||
const validationFn = validationRules[key]
|
||||
|
||||
// key has a validation rule & value different from the default
|
||||
if (validationFn && value !== defaultValues[key]) {
|
||||
const result = validationFn(key, value)
|
||||
|
||||
if (result !== true) {
|
||||
return onErr(result)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
validateNoBreakingConfig: (cfg, onWarning, onErr) => {
|
||||
breakingOptions.forEach(({ name, errorKey, newName, isWarning, value }) => {
|
||||
if (cfg.hasOwnProperty(name)) {
|
||||
if (value && cfg[name] !== value) {
|
||||
// Bail if a value is specified but the config does not have that value.
|
||||
return
|
||||
}
|
||||
|
||||
if (isWarning) {
|
||||
return onWarning(errorKey, {
|
||||
name,
|
||||
newName,
|
||||
value,
|
||||
configFile: cfg.configFile,
|
||||
})
|
||||
}
|
||||
|
||||
return onErr(errorKey, {
|
||||
name,
|
||||
newName,
|
||||
value,
|
||||
configFile: cfg.configFile,
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -1,4 +1,62 @@
|
||||
const v = require('./util/validation')
|
||||
const validate = require('./validation')
|
||||
|
||||
interface ResolvedConfigOption {
|
||||
name: string
|
||||
defaultValue?: any
|
||||
validation: Function
|
||||
isFolder?: boolean
|
||||
isExperimental?: boolean
|
||||
}
|
||||
|
||||
interface RuntimeConfigOption {
|
||||
name: string
|
||||
defaultValue: any
|
||||
validation: Function
|
||||
isInternal?: boolean
|
||||
}
|
||||
|
||||
interface BreakingOption {
|
||||
/**
|
||||
* The non-passive configuration option.
|
||||
*/
|
||||
name: string
|
||||
/**
|
||||
* String to summarize the error messaging that is logged.
|
||||
*/
|
||||
errorKey: string
|
||||
/**
|
||||
* Configuration value of the configuration option to check against.
|
||||
*/
|
||||
value?: string
|
||||
/**
|
||||
* The new configuration key that is replacing the existing configuration key.
|
||||
*/
|
||||
newName?: string
|
||||
/**
|
||||
* Whether to log the error message as a warning instead of throwing an error.
|
||||
*/
|
||||
isWarning?: boolean
|
||||
}
|
||||
|
||||
const isValidConfig = (key, config) => {
|
||||
const status = validate.isPlainObject(key, config)
|
||||
|
||||
if (status !== true) {
|
||||
return status
|
||||
}
|
||||
|
||||
for (const rule of options) {
|
||||
if (rule.name in config && rule.validation) {
|
||||
const status = rule.validation(`${key}.${rule.name}`, config[rule.name])
|
||||
|
||||
if (status !== true) {
|
||||
return status
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// NOTE:
|
||||
// If you add/remove/change a config value, make sure to update the following
|
||||
@@ -7,303 +65,335 @@ const v = require('./util/validation')
|
||||
//
|
||||
// Add options in alphabetical order for better readability
|
||||
|
||||
export const options = [
|
||||
// TODO - add boolean attribute to indicate read-only / static vs mutable options
|
||||
// that can be updated during test executions
|
||||
const resolvedOptions: Array<ResolvedConfigOption> = [
|
||||
{
|
||||
name: 'animationDistanceThreshold',
|
||||
defaultValue: 5,
|
||||
validation: v.isNumber,
|
||||
}, {
|
||||
name: 'autoOpen',
|
||||
defaultValue: false,
|
||||
isInternal: true,
|
||||
validation: validate.isNumber,
|
||||
}, {
|
||||
name: 'baseUrl',
|
||||
defaultValue: null,
|
||||
validation: v.isFullyQualifiedUrl,
|
||||
validation: validate.isFullyQualifiedUrl,
|
||||
}, {
|
||||
name: 'blockHosts',
|
||||
defaultValue: null,
|
||||
validation: v.isStringOrArrayOfStrings,
|
||||
}, {
|
||||
name: 'browsers',
|
||||
defaultValue: [],
|
||||
validation: v.isValidBrowserList,
|
||||
validation: validate.isStringOrArrayOfStrings,
|
||||
}, {
|
||||
name: 'chromeWebSecurity',
|
||||
defaultValue: true,
|
||||
validation: v.isBoolean,
|
||||
}, {
|
||||
name: 'clientRoute',
|
||||
defaultValue: '/__/',
|
||||
isInternal: true,
|
||||
validation: validate.isBoolean,
|
||||
}, {
|
||||
name: 'clientCertificates',
|
||||
defaultValue: [],
|
||||
validation: v.isValidClientCertificatesSet,
|
||||
validation: validate.isValidClientCertificatesSet,
|
||||
}, {
|
||||
name: 'component',
|
||||
// runner-ct overrides
|
||||
defaultValue: {},
|
||||
validation: v.isValidConfig,
|
||||
validation: isValidConfig,
|
||||
}, {
|
||||
name: 'componentFolder',
|
||||
defaultValue: 'cypress/component',
|
||||
validation: v.isStringOrFalse,
|
||||
validation: validate.isStringOrFalse,
|
||||
isFolder: true,
|
||||
}, {
|
||||
name: 'configFile',
|
||||
defaultValue: 'cypress.json',
|
||||
validation: v.isStringOrFalse,
|
||||
// not truly internal, but can only be set via cli,
|
||||
// so we don't consider it a "public" option
|
||||
isInternal: true,
|
||||
}, {
|
||||
name: 'defaultCommandTimeout',
|
||||
defaultValue: 4000,
|
||||
validation: v.isNumber,
|
||||
}, {
|
||||
name: 'devServerPublicPathRoute',
|
||||
defaultValue: '/__cypress/src',
|
||||
isInternal: true,
|
||||
validation: validate.isNumber,
|
||||
}, {
|
||||
name: 'downloadsFolder',
|
||||
defaultValue: 'cypress/downloads',
|
||||
validation: v.isString,
|
||||
validation: validate.isString,
|
||||
isFolder: true,
|
||||
}, {
|
||||
name: 'e2e',
|
||||
// e2e runner overrides
|
||||
defaultValue: {},
|
||||
validation: v.isValidConfig,
|
||||
validation: isValidConfig,
|
||||
}, {
|
||||
name: 'env',
|
||||
validation: v.isPlainObject,
|
||||
defaultValue: {},
|
||||
validation: validate.isPlainObject,
|
||||
}, {
|
||||
name: 'execTimeout',
|
||||
defaultValue: 60000,
|
||||
validation: v.isNumber,
|
||||
validation: validate.isNumber,
|
||||
}, {
|
||||
name: 'experimentalFetchPolyfill',
|
||||
defaultValue: false,
|
||||
validation: v.isBoolean,
|
||||
validation: validate.isBoolean,
|
||||
isExperimental: true,
|
||||
}, {
|
||||
name: 'experimentalInteractiveRunEvents',
|
||||
defaultValue: false,
|
||||
validation: v.isBoolean,
|
||||
isExperimental: true,
|
||||
}, {
|
||||
name: 'experimentalSourceRewriting',
|
||||
defaultValue: false,
|
||||
validation: v.isBoolean,
|
||||
isExperimental: true,
|
||||
}, {
|
||||
name: 'experimentalStudio',
|
||||
defaultValue: false,
|
||||
validation: v.isBoolean,
|
||||
validation: validate.isBoolean,
|
||||
isExperimental: true,
|
||||
}, {
|
||||
name: 'experimentalSessionSupport',
|
||||
defaultValue: false,
|
||||
validation: v.isBoolean,
|
||||
validation: validate.isBoolean,
|
||||
isExperimental: true,
|
||||
}, {
|
||||
name: 'experimentalSourceRewriting',
|
||||
defaultValue: false,
|
||||
validation: validate.isBoolean,
|
||||
isExperimental: true,
|
||||
}, {
|
||||
name: 'experimentalStudio',
|
||||
defaultValue: false,
|
||||
validation: validate.isBoolean,
|
||||
isExperimental: true,
|
||||
}, {
|
||||
name: 'fileServerFolder',
|
||||
defaultValue: '',
|
||||
validation: v.isString,
|
||||
validation: validate.isString,
|
||||
isFolder: true,
|
||||
}, {
|
||||
name: 'fixturesFolder',
|
||||
defaultValue: 'cypress/fixtures',
|
||||
validation: v.isStringOrFalse,
|
||||
validation: validate.isStringOrFalse,
|
||||
isFolder: true,
|
||||
}, {
|
||||
name: 'hosts',
|
||||
defaultValue: null,
|
||||
}, {
|
||||
name: 'ignoreTestFiles',
|
||||
defaultValue: '*.hot-update.js',
|
||||
validation: v.isStringOrArrayOfStrings,
|
||||
validation: validate.isStringOrArrayOfStrings,
|
||||
}, {
|
||||
name: 'includeShadowDom',
|
||||
defaultValue: false,
|
||||
validation: v.isBoolean,
|
||||
validation: validate.isBoolean,
|
||||
}, {
|
||||
name: 'integrationFolder',
|
||||
defaultValue: 'cypress/integration',
|
||||
validation: v.isString,
|
||||
validation: validate.isString,
|
||||
isFolder: true,
|
||||
}, {
|
||||
name: 'isTextTerminal',
|
||||
defaultValue: false,
|
||||
isInternal: true,
|
||||
}, {
|
||||
name: 'morgan',
|
||||
defaultValue: true,
|
||||
isInternal: true,
|
||||
}, {
|
||||
name: 'modifyObstructiveCode',
|
||||
defaultValue: true,
|
||||
validation: v.isBoolean,
|
||||
}, {
|
||||
name: 'namespace',
|
||||
defaultValue: '__cypress',
|
||||
isInternal: true,
|
||||
validation: validate.isBoolean,
|
||||
}, {
|
||||
name: 'nodeVersion',
|
||||
validation: v.isOneOf('bundled', 'system'),
|
||||
validation: validate.isOneOf('bundled', 'system'),
|
||||
}, {
|
||||
name: 'numTestsKeptInMemory',
|
||||
defaultValue: 50,
|
||||
validation: v.isNumber,
|
||||
validation: validate.isNumber,
|
||||
}, {
|
||||
name: 'pageLoadTimeout',
|
||||
defaultValue: 60000,
|
||||
validation: v.isNumber,
|
||||
validation: validate.isNumber,
|
||||
}, {
|
||||
name: 'pluginsFile',
|
||||
defaultValue: 'cypress/plugins',
|
||||
validation: v.isStringOrFalse,
|
||||
validation: validate.isStringOrFalse,
|
||||
isFolder: true,
|
||||
}, {
|
||||
name: 'port',
|
||||
defaultValue: null,
|
||||
validation: v.isNumber,
|
||||
validation: validate.isNumber,
|
||||
}, {
|
||||
name: 'projectId',
|
||||
defaultValue: null,
|
||||
validation: v.isString,
|
||||
validation: validate.isString,
|
||||
}, {
|
||||
name: 'redirectionLimit',
|
||||
defaultValue: 20,
|
||||
validation: validate.isNumber,
|
||||
}, {
|
||||
name: 'reporter',
|
||||
defaultValue: 'spec',
|
||||
validation: v.isString,
|
||||
validation: validate.isString,
|
||||
}, {
|
||||
name: 'reporterOptions',
|
||||
defaultValue: null,
|
||||
}, {
|
||||
name: 'reporterRoute',
|
||||
defaultValue: '/__cypress/reporter',
|
||||
isInternal: true,
|
||||
validation: validate.isPlainObject,
|
||||
}, {
|
||||
name: 'requestTimeout',
|
||||
defaultValue: 5000,
|
||||
validation: v.isNumber,
|
||||
validation: validate.isNumber,
|
||||
}, {
|
||||
name: 'resolvedNodePath',
|
||||
defaultValue: null,
|
||||
validation: validate.isString,
|
||||
}, {
|
||||
name: 'resolvedNodeVersion',
|
||||
defaultValue: null,
|
||||
validation: validate.isString,
|
||||
}, {
|
||||
name: 'responseTimeout',
|
||||
defaultValue: 30000,
|
||||
validation: v.isNumber,
|
||||
validation: validate.isNumber,
|
||||
}, {
|
||||
name: 'retries',
|
||||
defaultValue: {
|
||||
runMode: 0,
|
||||
openMode: 0,
|
||||
},
|
||||
validation: v.isValidRetriesConfig,
|
||||
validation: validate.isValidRetriesConfig,
|
||||
}, {
|
||||
name: 'screenshotOnRunFailure',
|
||||
defaultValue: true,
|
||||
validation: v.isBoolean,
|
||||
validation: validate.isBoolean,
|
||||
}, {
|
||||
name: 'screenshotsFolder',
|
||||
defaultValue: 'cypress/screenshots',
|
||||
validation: v.isStringOrFalse,
|
||||
validation: validate.isStringOrFalse,
|
||||
isFolder: true,
|
||||
}, {
|
||||
name: 'slowTestThreshold',
|
||||
defaultValue: (options: Record<string, any>) => options.testingType === 'component' ? 250 : 10000,
|
||||
validation: v.isNumber,
|
||||
}, {
|
||||
name: 'socketId',
|
||||
defaultValue: null,
|
||||
isInternal: true,
|
||||
}, {
|
||||
name: 'socketIoRoute',
|
||||
defaultValue: '/__socket.io',
|
||||
isInternal: true,
|
||||
defaultValue: (options: Record<string, any> = {}) => options.testingType === 'component' ? 250 : 10000,
|
||||
validation: validate.isNumber,
|
||||
}, {
|
||||
name: 'scrollBehavior',
|
||||
defaultValue: 'top',
|
||||
validation: v.isOneOf('center', 'top', 'bottom', 'nearest', false),
|
||||
}, {
|
||||
name: 'socketIoCookie',
|
||||
defaultValue: '__socket.io',
|
||||
isInternal: true,
|
||||
validation: validate.isOneOf('center', 'top', 'bottom', 'nearest', false),
|
||||
}, {
|
||||
name: 'supportFile',
|
||||
defaultValue: 'cypress/support',
|
||||
validation: v.isStringOrFalse,
|
||||
validation: validate.isStringOrFalse,
|
||||
isFolder: true,
|
||||
}, {
|
||||
name: 'supportFolder',
|
||||
defaultValue: false,
|
||||
validation: validate.isStringOrFalse,
|
||||
isFolder: true,
|
||||
}, {
|
||||
name: 'taskTimeout',
|
||||
defaultValue: 60000,
|
||||
validation: v.isNumber,
|
||||
validation: validate.isNumber,
|
||||
}, {
|
||||
name: 'testFiles',
|
||||
defaultValue: '**/*.*',
|
||||
validation: v.isStringOrArrayOfStrings,
|
||||
validation: validate.isStringOrArrayOfStrings,
|
||||
}, {
|
||||
name: 'trashAssetsBeforeRuns',
|
||||
defaultValue: true,
|
||||
validation: v.isBoolean,
|
||||
}, {
|
||||
name: 'unitFolder',
|
||||
isFolder: true,
|
||||
isInternal: true,
|
||||
validation: validate.isBoolean,
|
||||
}, {
|
||||
name: 'userAgent',
|
||||
defaultValue: null,
|
||||
validation: v.isString,
|
||||
validation: validate.isString,
|
||||
}, {
|
||||
name: 'video',
|
||||
defaultValue: true,
|
||||
validation: v.isBoolean,
|
||||
validation: validate.isBoolean,
|
||||
}, {
|
||||
name: 'videoCompression',
|
||||
defaultValue: 32,
|
||||
validation: v.isNumberOrFalse,
|
||||
validation: validate.isNumberOrFalse,
|
||||
}, {
|
||||
name: 'videosFolder',
|
||||
defaultValue: 'cypress/videos',
|
||||
validation: v.isString,
|
||||
validation: validate.isString,
|
||||
isFolder: true,
|
||||
}, {
|
||||
name: 'videoUploadOnPasses',
|
||||
defaultValue: true,
|
||||
validation: v.isBoolean,
|
||||
validation: validate.isBoolean,
|
||||
}, {
|
||||
name: 'viewportHeight',
|
||||
defaultValue: 660,
|
||||
validation: v.isNumber,
|
||||
validation: validate.isNumber,
|
||||
}, {
|
||||
name: 'viewportWidth',
|
||||
defaultValue: 1000,
|
||||
validation: v.isNumber,
|
||||
validation: validate.isNumber,
|
||||
}, {
|
||||
name: 'waitForAnimations',
|
||||
defaultValue: true,
|
||||
validation: v.isBoolean,
|
||||
validation: validate.isBoolean,
|
||||
}, {
|
||||
name: 'watchForFileChanges',
|
||||
defaultValue: true,
|
||||
validation: v.isBoolean,
|
||||
validation: validate.isBoolean,
|
||||
},
|
||||
]
|
||||
|
||||
const runtimeOptions: Array<RuntimeConfigOption> = [
|
||||
{
|
||||
name: 'autoOpen',
|
||||
defaultValue: false,
|
||||
validation: validate.isBoolean,
|
||||
isInternal: true,
|
||||
}, {
|
||||
name: 'browsers',
|
||||
defaultValue: [],
|
||||
validation: validate.isValidBrowserList,
|
||||
}, {
|
||||
name: 'clientRoute',
|
||||
defaultValue: '/__/',
|
||||
validation: validate.isString,
|
||||
isInternal: true,
|
||||
}, {
|
||||
name: 'configFile',
|
||||
defaultValue: 'cypress.json',
|
||||
validation: validate.isStringOrFalse,
|
||||
// not truly internal, but can only be set via cli,
|
||||
// so we don't consider it a "public" option
|
||||
isInternal: true,
|
||||
}, {
|
||||
name: 'devServerPublicPathRoute',
|
||||
defaultValue: '/__cypress/src',
|
||||
validation: validate.isString,
|
||||
isInternal: true,
|
||||
}, {
|
||||
name: 'hosts',
|
||||
defaultValue: null,
|
||||
validation: validate.isPlainObject,
|
||||
}, {
|
||||
name: 'isTextTerminal',
|
||||
defaultValue: false,
|
||||
validation: validate.isBoolean,
|
||||
isInternal: true,
|
||||
}, {
|
||||
name: 'morgan',
|
||||
defaultValue: true,
|
||||
validation: validate.isBoolean,
|
||||
isInternal: true,
|
||||
}, {
|
||||
name: 'modifyObstructiveCode',
|
||||
defaultValue: true,
|
||||
validation: validate.isBoolean,
|
||||
}, {
|
||||
name: 'namespace',
|
||||
defaultValue: '__cypress',
|
||||
validation: validate.isString,
|
||||
isInternal: true,
|
||||
}, {
|
||||
name: 'reporterRoute',
|
||||
defaultValue: '/__cypress/reporter',
|
||||
validation: validate.isString,
|
||||
isInternal: true,
|
||||
}, {
|
||||
name: 'socketId',
|
||||
defaultValue: null,
|
||||
validation: validate.isString,
|
||||
isInternal: true,
|
||||
}, {
|
||||
name: 'socketIoCookie',
|
||||
defaultValue: '__socket.io',
|
||||
validation: validate.isString,
|
||||
isInternal: true,
|
||||
}, {
|
||||
name: 'socketIoRoute',
|
||||
defaultValue: '/__socket.io',
|
||||
validation: validate.isString,
|
||||
isInternal: true,
|
||||
}, {
|
||||
name: 'xhrRoute',
|
||||
defaultValue: '/xhrs/',
|
||||
validation: validate.isString,
|
||||
isInternal: true,
|
||||
},
|
||||
]
|
||||
|
||||
export const breakingOptions = [
|
||||
export const options: Array<ResolvedConfigOption|RuntimeConfigOption> = [
|
||||
...resolvedOptions,
|
||||
...runtimeOptions,
|
||||
]
|
||||
|
||||
export const breakingOptions: Array<BreakingOption> = [
|
||||
{
|
||||
name: 'blacklistHosts',
|
||||
errorKey: 'RENAMED_CONFIG_OPTION',
|
||||
@@ -2,7 +2,6 @@ const _ = require('lodash')
|
||||
const debug = require('debug')('cypress:server:validation')
|
||||
const is = require('check-more-types')
|
||||
const { commaListsOr } = require('common-tags')
|
||||
const configOptions = require('../config_options')
|
||||
const path = require('path')
|
||||
|
||||
// validation functions take a key and a value and should:
|
||||
@@ -10,6 +9,7 @@ const path = require('path')
|
||||
// - return a error message if it fails validation
|
||||
|
||||
const str = JSON.stringify
|
||||
const { isArray, isString, isFinite: isNumber } = _
|
||||
|
||||
/**
|
||||
* Forms good Markdown-like string message.
|
||||
@@ -36,10 +36,6 @@ const isFalse = (value) => {
|
||||
return value === false
|
||||
}
|
||||
|
||||
const { isArray } = _
|
||||
const isNumber = _.isFinite
|
||||
const { isString } = _
|
||||
|
||||
/**
|
||||
* Validates a single browser object.
|
||||
* @returns {string|true} Returns `true` if the object is matching browser object schema. Returns an error message if it does not.
|
||||
@@ -129,26 +125,6 @@ const isPlainObject = (key, value) => {
|
||||
return errMsg(key, value, 'a plain object')
|
||||
}
|
||||
|
||||
const isValidConfig = (key, config) => {
|
||||
const status = isPlainObject(key, config)
|
||||
|
||||
if (status !== true) {
|
||||
return status
|
||||
}
|
||||
|
||||
for (const rule of configOptions.options) {
|
||||
if (rule.name in config && rule.validation) {
|
||||
const status = rule.validation(`${key}.${rule.name}`, config[rule.name])
|
||||
|
||||
if (status !== true) {
|
||||
return status
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const isOneOf = (...values) => {
|
||||
return (key, value) => {
|
||||
if (values.some((v) => {
|
||||
@@ -171,7 +147,7 @@ const isOneOf = (...values) => {
|
||||
* Validates whether the supplied set of cert information is valid
|
||||
* @returns {string|true} Returns `true` if the information set is valid. Returns an error message if it is not.
|
||||
*/
|
||||
const isValidClientCertificatesSet = (key, certsForUrls) => {
|
||||
const isValidClientCertificatesSet = (_key, certsForUrls) => {
|
||||
debug('clientCerts: %o', certsForUrls)
|
||||
|
||||
if (!Array.isArray(certsForUrls)) {
|
||||
@@ -266,8 +242,6 @@ module.exports = {
|
||||
|
||||
isValidRetriesConfig,
|
||||
|
||||
isValidConfig,
|
||||
|
||||
isPlainObject,
|
||||
|
||||
isNumber (key, value) {
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "@packages/config",
|
||||
"version": "0.0.0-development",
|
||||
"description": "Config contains the configuration types and validation function used in the cypress electron application.",
|
||||
"private": true,
|
||||
"main": "lib/index.js",
|
||||
"scripts": {
|
||||
"build-prod": "tsc --project .",
|
||||
"clean": "rm lib/options.js",
|
||||
"test": "yarn test-unit",
|
||||
"test-debug": "yarn test-unit --inspect-brk=5566",
|
||||
"test-unit": "mocha --configFile=../../mocha-reporter-config.json -r @packages/ts/register -extension=.js,.ts test/unit/*spec.* --exit"
|
||||
},
|
||||
"dependencies": {
|
||||
"check-more-types": "2.24.0",
|
||||
"common-tags": "1.8.0",
|
||||
"debug": "4.3.2",
|
||||
"lodash": "4.17.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@packages/ts": "0.0.0-development",
|
||||
"chai": "1.10.0",
|
||||
"mocha": "7.0.1",
|
||||
"sinon": "7.3.1",
|
||||
"sinon-chai": "3.3.0",
|
||||
"snap-shot-it": "7.9.3"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
const chai = require('chai')
|
||||
const snapshot = require('snap-shot-it')
|
||||
const sinon = require('sinon')
|
||||
const sinonChai = require('sinon-chai')
|
||||
|
||||
const configUtil = require('../../lib/index')
|
||||
|
||||
chai.use(sinonChai)
|
||||
const { expect } = chai
|
||||
|
||||
describe('src/index', () => {
|
||||
describe('.allowed', () => {
|
||||
it('returns filter config only containing allowed keys', () => {
|
||||
const keys = configUtil.allowed({
|
||||
'baseUrl': 'https://url.com',
|
||||
'blacklistHosts': 'breaking option',
|
||||
'devServerPublicPathRoute': 'internal key',
|
||||
'random': 'not a config option',
|
||||
})
|
||||
|
||||
expect(keys).to.deep.eq({
|
||||
'baseUrl': 'https://url.com',
|
||||
'blacklistHosts': 'breaking option',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('.getBreakingKeys', () => {
|
||||
it('returns list of breaking config keys', () => {
|
||||
const breakingKeys = configUtil.getBreakingKeys()
|
||||
|
||||
expect(breakingKeys).to.include('blacklistHosts')
|
||||
snapshot(breakingKeys)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.getDefaultValues', () => {
|
||||
it('returns list of public config keys', () => {
|
||||
const defaultValues = configUtil.getDefaultValues()
|
||||
|
||||
expect(defaultValues).to.deep.include({
|
||||
defaultCommandTimeout: 4000,
|
||||
scrollBehavior: 'top',
|
||||
watchForFileChanges: true,
|
||||
})
|
||||
|
||||
expect(defaultValues.env).to.deep.eq({})
|
||||
snapshot(defaultValues)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.getPublicConfigKeys', () => {
|
||||
it('returns list of public config keys', () => {
|
||||
const publicConfigKeys = configUtil.getPublicConfigKeys()
|
||||
|
||||
expect(publicConfigKeys).to.include('blockHosts')
|
||||
expect(publicConfigKeys).to.not.include('devServerPublicPathRoute')
|
||||
snapshot(publicConfigKeys)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.matchesConfigKey', () => {
|
||||
it('returns key when config key has a default value', () => {
|
||||
const normalizedKey = configUtil.matchesConfigKey('testFiles')
|
||||
|
||||
expect(normalizedKey).to.eq('testFiles')
|
||||
})
|
||||
|
||||
it('returns normalized key when config key has a default value', () => {
|
||||
let normalizedKey = configUtil.matchesConfigKey('EXEC_TIMEOUT')
|
||||
|
||||
expect(normalizedKey).to.eq('execTimeout')
|
||||
|
||||
normalizedKey = configUtil.matchesConfigKey('Base-url')
|
||||
expect(normalizedKey).to.eq('baseUrl')
|
||||
|
||||
normalizedKey = configUtil.matchesConfigKey('TEST-FILES')
|
||||
|
||||
expect(normalizedKey).to.eq('testFiles')
|
||||
})
|
||||
|
||||
it('returns nothing when config key does not has a default value', () => {
|
||||
let normalizedKey = configUtil.matchesConfigKey('random')
|
||||
|
||||
expect(normalizedKey).to.be.undefined
|
||||
})
|
||||
})
|
||||
|
||||
describe('.validate', () => {
|
||||
it('validates config', () => {
|
||||
const errorFn = sinon.spy()
|
||||
|
||||
configUtil.validate({
|
||||
'baseUrl': 'https://',
|
||||
}, errorFn)
|
||||
|
||||
expect(errorFn).to.have.been.callCount(0)
|
||||
})
|
||||
|
||||
it('calls error callback if config is invalid', () => {
|
||||
const errorFn = sinon.spy()
|
||||
|
||||
configUtil.validate({
|
||||
'baseUrl': ' ',
|
||||
}, errorFn)
|
||||
|
||||
expect(errorFn).to.have.been.calledWithMatch(/Expected `baseUrl`/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.validateNoBreakingConfig', () => {
|
||||
it('calls warning callback if config contains breaking option that warns', () => {
|
||||
const warningFn = sinon.spy()
|
||||
const errorFn = sinon.spy()
|
||||
|
||||
configUtil.validateNoBreakingConfig({
|
||||
'experimentalNetworkStubbing': 'should break',
|
||||
configFile: 'config.js',
|
||||
}, warningFn, errorFn)
|
||||
|
||||
expect(warningFn).to.have.been.calledOnceWith('EXPERIMENTAL_NETWORK_STUBBING_REMOVED', {
|
||||
name: 'experimentalNetworkStubbing',
|
||||
newName: undefined,
|
||||
value: undefined,
|
||||
configFile: 'config.js',
|
||||
})
|
||||
|
||||
expect(errorFn).to.have.been.callCount(0)
|
||||
})
|
||||
|
||||
it('calls error callback if config contains breaking option that should throw an error', () => {
|
||||
const warningFn = sinon.spy()
|
||||
const errorFn = sinon.spy()
|
||||
|
||||
configUtil.validateNoBreakingConfig({
|
||||
'blacklistHosts': 'should break',
|
||||
configFile: 'config.js',
|
||||
}, warningFn, errorFn)
|
||||
|
||||
expect(warningFn).to.have.been.callCount(0)
|
||||
expect(errorFn).to.have.been.calledOnceWith('RENAMED_CONFIG_OPTION', {
|
||||
name: 'blacklistHosts',
|
||||
newName: 'blockHosts',
|
||||
value: undefined,
|
||||
configFile: 'config.js',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,391 @@
|
||||
const snapshot = require('snap-shot-it')
|
||||
const { expect } = require('chai')
|
||||
|
||||
const validation = require('../../lib/validation')
|
||||
|
||||
describe('src/validation', () => {
|
||||
const mockKey = 'mockConfigKey'
|
||||
|
||||
describe('.isValidClientCertificatesSet', () => {
|
||||
it('returns error message for certs not passed as an array array', () => {
|
||||
const result = validation.isValidRetriesConfig(mockKey, '1')
|
||||
|
||||
expect(result).to.not.be.true
|
||||
snapshot(result)
|
||||
})
|
||||
|
||||
it('returns error message for certs object without url', () => {
|
||||
const result = validation.isValidClientCertificatesSet(mockKey, [
|
||||
{ name: 'cert' },
|
||||
])
|
||||
|
||||
expect(result).to.not.be.true
|
||||
snapshot(result)
|
||||
})
|
||||
|
||||
it('returns error message for certs url not matching *', () => {
|
||||
let result = validation.isValidClientCertificatesSet(mockKey, [
|
||||
{ url: 'http://url.com' },
|
||||
])
|
||||
|
||||
expect(result).to.not.be.true
|
||||
snapshot('missing https protocol', result)
|
||||
|
||||
result = validation.isValidClientCertificatesSet(mockKey, [
|
||||
{ url: 'not *' },
|
||||
])
|
||||
|
||||
expect(result).to.not.be.true
|
||||
snapshot('invalid url', result)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.isValidBrowser', () => {
|
||||
it('passes valid browsers and forms error messages for invalid ones', () => {
|
||||
const browsers = [
|
||||
// valid browser
|
||||
{
|
||||
name: 'Chrome',
|
||||
displayName: 'Chrome Browser',
|
||||
family: 'chromium',
|
||||
path: '/path/to/chrome',
|
||||
version: '1.2.3',
|
||||
majorVersion: 1,
|
||||
},
|
||||
// another valid browser
|
||||
{
|
||||
name: 'FF',
|
||||
displayName: 'Firefox',
|
||||
family: 'firefox',
|
||||
path: '/path/to/firefox',
|
||||
version: '1.2.3',
|
||||
majorVersion: '1',
|
||||
},
|
||||
// Electron is a valid browser
|
||||
{
|
||||
name: 'Electron',
|
||||
displayName: 'Electron',
|
||||
family: 'chromium',
|
||||
path: '',
|
||||
version: '99.101.3',
|
||||
majorVersion: 99,
|
||||
},
|
||||
// invalid browser, missing displayName
|
||||
{
|
||||
name: 'No display name',
|
||||
family: 'chromium',
|
||||
},
|
||||
{
|
||||
name: 'bad family',
|
||||
displayName: 'Bad family browser',
|
||||
family: 'unknown family',
|
||||
},
|
||||
]
|
||||
|
||||
// data-driven testing - computers snapshot value for each item in the list passed through the function
|
||||
// https://github.com/bahmutov/snap-shot-it#data-driven-testing
|
||||
return snapshot.apply(null, [validation.isValidBrowser].concat(browsers))
|
||||
})
|
||||
})
|
||||
|
||||
describe('.isValidBrowserList', () => {
|
||||
it('does not allow empty or not browsers', () => {
|
||||
snapshot('undefined browsers', validation.isValidBrowserList('browsers'))
|
||||
snapshot('empty list of browsers', validation.isValidBrowserList('browsers', []))
|
||||
|
||||
return snapshot('browsers list with a string', validation.isValidBrowserList('browsers', ['foo']))
|
||||
})
|
||||
})
|
||||
|
||||
describe('.isValidRetriesConfig', () => {
|
||||
it('returns true for valid retry value', () => {
|
||||
let result = validation.isValidRetriesConfig(mockKey, null)
|
||||
|
||||
expect(result).to.be.true
|
||||
|
||||
result = validation.isValidRetriesConfig(mockKey, 2)
|
||||
expect(result).to.be.true
|
||||
})
|
||||
|
||||
it('returns true for valid retry objects', () => {
|
||||
let result = validation.isValidRetriesConfig(mockKey, { runMode: 1 })
|
||||
|
||||
expect(result).to.be.true
|
||||
|
||||
result = validation.isValidRetriesConfig(mockKey, { openMode: 1 })
|
||||
expect(result).to.be.true
|
||||
|
||||
result = validation.isValidRetriesConfig(mockKey, {
|
||||
runMode: 3,
|
||||
openMode: 0,
|
||||
})
|
||||
|
||||
expect(result).to.be.true
|
||||
})
|
||||
|
||||
it('returns error message for invalid retry config', () => {
|
||||
let result = validation.isValidRetriesConfig(mockKey, '1')
|
||||
|
||||
expect(result).to.not.be.true
|
||||
snapshot('invalid retry value', result)
|
||||
|
||||
result = validation.isValidRetriesConfig(mockKey, { fakeMode: 1 })
|
||||
expect(result).to.not.be.true
|
||||
snapshot('invalid retry object', result)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.isPlainObject', () => {
|
||||
it('returns true for value=null', () => {
|
||||
const result = validation.isPlainObject(mockKey, null)
|
||||
|
||||
expect(result).to.be.true
|
||||
})
|
||||
|
||||
it('returns true for value=number', () => {
|
||||
const result = validation.isPlainObject(mockKey, { foo: 'bar' })
|
||||
|
||||
expect(result).to.be.true
|
||||
})
|
||||
|
||||
it('returns error message when value is a not an object', () => {
|
||||
const result = validation.isPlainObject(mockKey, 1)
|
||||
|
||||
expect(result).to.not.be.true
|
||||
snapshot(result)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.isNumber', () => {
|
||||
it('returns true for value=null', () => {
|
||||
const result = validation.isNumber(mockKey, null)
|
||||
|
||||
expect(result).to.be.true
|
||||
})
|
||||
|
||||
it('returns true for value=number', () => {
|
||||
const result = validation.isNumber(mockKey, 1)
|
||||
|
||||
expect(result).to.be.true
|
||||
})
|
||||
|
||||
it('returns error message when value is a not a number', () => {
|
||||
const result = validation.isNumber(mockKey, 'string')
|
||||
|
||||
expect(result).to.not.be.true
|
||||
snapshot(result)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.isNumberOrFalse', () => {
|
||||
it('returns true for value=number', () => {
|
||||
const result = validation.isNumberOrFalse(mockKey, 1)
|
||||
|
||||
expect(result).to.be.true
|
||||
})
|
||||
|
||||
it('returns true for value=false', () => {
|
||||
const result = validation.isNumberOrFalse(mockKey, false)
|
||||
|
||||
expect(result).to.be.true
|
||||
})
|
||||
|
||||
it('returns error message when value is a not number or false', () => {
|
||||
const result = validation.isNumberOrFalse(mockKey, null)
|
||||
|
||||
expect(result).to.not.be.true
|
||||
snapshot(result)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.isFullyQualifiedUrl', () => {
|
||||
it('returns true for value=null', () => {
|
||||
const result = validation.isFullyQualifiedUrl(mockKey, null)
|
||||
|
||||
expect(result).to.be.true
|
||||
})
|
||||
|
||||
it('returns true for value=qualified urls', () => {
|
||||
let result = validation.isFullyQualifiedUrl(mockKey, 'https://url.com')
|
||||
|
||||
expect(result).to.be.true
|
||||
result = validation.isFullyQualifiedUrl(mockKey, 'http://url.com')
|
||||
expect(result).to.be.true
|
||||
})
|
||||
|
||||
it('returns error message when value is a not qualified url', () => {
|
||||
let result = validation.isFullyQualifiedUrl(mockKey, 'url.com')
|
||||
|
||||
expect(result).to.not.be.true
|
||||
snapshot('not qualified url', result)
|
||||
|
||||
result = validation.isFullyQualifiedUrl(mockKey, '')
|
||||
expect(result).to.not.be.true
|
||||
snapshot('empty string', result)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.isBoolean', () => {
|
||||
it('returns true for value=null', () => {
|
||||
const result = validation.isBoolean(mockKey, null)
|
||||
|
||||
expect(result).to.be.true
|
||||
})
|
||||
|
||||
it('returns true for value=true', () => {
|
||||
const result = validation.isBoolean(mockKey, true)
|
||||
|
||||
expect(result).to.be.true
|
||||
})
|
||||
|
||||
it('returns true for value=false', () => {
|
||||
const result = validation.isBoolean(mockKey, false)
|
||||
|
||||
expect(result).to.be.true
|
||||
})
|
||||
|
||||
it('returns error message when value is a not a string', () => {
|
||||
const result = validation.isString(mockKey, 1)
|
||||
|
||||
expect(result).to.not.be.true
|
||||
snapshot(result)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.isString', () => {
|
||||
it('returns true for value=null', () => {
|
||||
const result = validation.isString(mockKey, null)
|
||||
|
||||
expect(result).to.be.true
|
||||
})
|
||||
|
||||
it('returns true for value=array', () => {
|
||||
const result = validation.isString(mockKey, 'string')
|
||||
|
||||
expect(result).to.be.true
|
||||
})
|
||||
|
||||
it('returns error message when value is a not a string', () => {
|
||||
const result = validation.isString(mockKey, 1)
|
||||
|
||||
expect(result).to.not.be.true
|
||||
snapshot(result)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.isArray', () => {
|
||||
it('returns true for value=null', () => {
|
||||
const result = validation.isArray(mockKey, null)
|
||||
|
||||
expect(result).to.be.true
|
||||
})
|
||||
|
||||
it('returns true for value=array', () => {
|
||||
const result = validation.isArray(mockKey, [1, 2, 3])
|
||||
|
||||
expect(result).to.be.true
|
||||
})
|
||||
|
||||
it('returns error message when value is a non-array', () => {
|
||||
const result = validation.isArray(mockKey, 1)
|
||||
|
||||
expect(result).to.not.be.true
|
||||
snapshot(result)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.isStringOrFalse', () => {
|
||||
it('returns true for value=string', () => {
|
||||
const result = validation.isStringOrFalse(mockKey, 'string')
|
||||
|
||||
expect(result).to.be.true
|
||||
})
|
||||
|
||||
it('returns true for value=false', () => {
|
||||
const result = validation.isStringOrFalse(mockKey, false)
|
||||
|
||||
expect(result).to.be.true
|
||||
})
|
||||
|
||||
it('returns error message when value is neither string nor false', () => {
|
||||
const result = validation.isStringOrFalse(mockKey, null)
|
||||
|
||||
expect(result).to.not.be.true
|
||||
snapshot(result)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.isStringOrArrayOfStrings', () => {
|
||||
it('returns true for value=string', () => {
|
||||
const result = validation.isStringOrArrayOfStrings(mockKey, 'string')
|
||||
|
||||
expect(result).to.be.true
|
||||
})
|
||||
|
||||
it('returns true for value=array of strings', () => {
|
||||
const result = validation.isStringOrArrayOfStrings(mockKey, ['string', 'other'])
|
||||
|
||||
expect(result).to.be.true
|
||||
})
|
||||
|
||||
it('returns error message when value is neither string nor array of string', () => {
|
||||
let result = validation.isStringOrArrayOfStrings(mockKey, null)
|
||||
|
||||
expect(result).to.not.be.true
|
||||
snapshot('not string or array', result)
|
||||
|
||||
result = validation.isStringOrArrayOfStrings(mockKey, [1, 2, 3])
|
||||
|
||||
expect(result).to.not.be.true
|
||||
snapshot('array of non-strings', result)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.isOneOf', () => {
|
||||
it('validates a string', () => {
|
||||
const validate = validation.isOneOf('foo', 'bar')
|
||||
|
||||
expect(validate).to.be.a('function')
|
||||
expect(validate('test', 'foo')).to.be.true
|
||||
expect(validate('test', 'bar')).to.be.true
|
||||
|
||||
// different value
|
||||
let msg = validate('test', 'nope')
|
||||
|
||||
expect(msg).to.not.be.true
|
||||
snapshot('not one of the strings error message', msg)
|
||||
|
||||
msg = validate('test', 42)
|
||||
expect(msg).to.not.be.true
|
||||
snapshot('number instead of string', msg)
|
||||
|
||||
msg = validate('test', null)
|
||||
expect(msg).to.not.be.true
|
||||
|
||||
return snapshot('null instead of string', msg)
|
||||
})
|
||||
|
||||
it('validates a number', () => {
|
||||
const validate = validation.isOneOf(1, 2, 3)
|
||||
|
||||
expect(validate).to.be.a('function')
|
||||
expect(validate('test', 1)).to.be.true
|
||||
expect(validate('test', 3)).to.be.true
|
||||
|
||||
// different value
|
||||
let msg = validate('test', 4)
|
||||
|
||||
expect(msg).to.not.be.true
|
||||
snapshot('not one of the numbers error message', msg)
|
||||
|
||||
msg = validate('test', 'foo')
|
||||
expect(msg).to.not.be.true
|
||||
snapshot('string instead of a number', msg)
|
||||
|
||||
msg = validate('test', null)
|
||||
expect(msg).to.not.be.true
|
||||
|
||||
return snapshot('null instead of a number', msg)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "../ts/tsconfig.json",
|
||||
"include": [
|
||||
"lib/*",
|
||||
],
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
const Cookie = require('js-cookie')
|
||||
const { stripIndent } = require('common-tags')
|
||||
const helpers = require('../../support/helpers')
|
||||
|
||||
const { _, Promise, $ } = Cypress
|
||||
|
||||
@@ -694,7 +693,7 @@ describe('src/cy/commands/navigation', () => {
|
||||
})
|
||||
|
||||
it('does not support file:// protocol', {
|
||||
baseUrl: '',
|
||||
baseUrl: null,
|
||||
}, (done) => {
|
||||
cy.on('fail', (err) => {
|
||||
expect(err.message).to.contain('`cy.visit()` failed because the \'file://...\' protocol is not supported by Cypress.')
|
||||
@@ -1439,7 +1438,7 @@ describe('src/cy/commands/navigation', () => {
|
||||
cy.visit('http://google.com:3500/fixtures/generic.html')
|
||||
})
|
||||
|
||||
it('throws attemping to visit 2 unique ip addresses', function (done) {
|
||||
it('throws attempting to visit 2 unique ip addresses', function (done) {
|
||||
const $autIframe = cy.state('$autIframe')
|
||||
|
||||
const load = () => {
|
||||
@@ -1971,12 +1970,10 @@ describe('src/cy/commands/navigation', () => {
|
||||
})
|
||||
|
||||
describe('errors', () => {
|
||||
helpers.registerCypressConfigBackupRestore()
|
||||
|
||||
beforeEach(function () {
|
||||
this.logs = []
|
||||
|
||||
cy.on('log:added', (attrs, log) => {
|
||||
cy.on('log:added', (_attrs, log) => {
|
||||
this.lastLog = log
|
||||
this.logs.push(log)
|
||||
})
|
||||
@@ -1984,42 +1981,53 @@ describe('src/cy/commands/navigation', () => {
|
||||
return null
|
||||
})
|
||||
|
||||
it('can time out', function (done) {
|
||||
let thenCalled = false
|
||||
describe('can time out', () => {
|
||||
let pageLoadTimeout
|
||||
|
||||
cy.on('fail', (err) => {
|
||||
const { lastLog } = this
|
||||
|
||||
// visit, window, page loading
|
||||
expect(this.logs.length).to.eq(3)
|
||||
expect(err.message).to.include('Your page did not fire its `load` event within `50ms`.')
|
||||
expect(lastLog.get('name')).to.eq('page load')
|
||||
expect(lastLog.get('error')).to.eq(err)
|
||||
|
||||
return Promise
|
||||
.delay(100)
|
||||
.then(() => {
|
||||
expect(cy.state('onPageLoadErr')).to.be.null
|
||||
expect(cy.isStopped()).to.be.true // make sure we ran our cleanup routine
|
||||
expect(thenCalled).to.be.false
|
||||
|
||||
done()
|
||||
})
|
||||
before(() => {
|
||||
pageLoadTimeout = Cypress.config().pageLoadTimeout
|
||||
})
|
||||
|
||||
cy
|
||||
.visit('/fixtures/jquery.html')
|
||||
.window().then((win) => {
|
||||
Cypress.config('pageLoadTimeout', 50)
|
||||
after(() => {
|
||||
Cypress.config('pageLoadTimeout', pageLoadTimeout)
|
||||
})
|
||||
|
||||
const $a = win.$('<a href=\'/timeout?ms=500\'>jquery</a>')
|
||||
.appendTo(win.document.body)
|
||||
it('times out', function (done) {
|
||||
let thenCalled = false
|
||||
|
||||
causeSynchronousBeforeUnload($a)
|
||||
cy.on('fail', (err) => {
|
||||
const { lastLog } = this
|
||||
|
||||
return null
|
||||
}).wrap(null).then(() => {
|
||||
thenCalled = true
|
||||
// visit, window, page loading
|
||||
expect(this.logs.length).to.eq(3)
|
||||
expect(err.message).to.include('Your page did not fire its `load` event within `50ms`.')
|
||||
expect(lastLog.get('name')).to.eq('page load')
|
||||
expect(lastLog.get('error')).to.eq(err)
|
||||
|
||||
return Promise
|
||||
.delay(100)
|
||||
.then(() => {
|
||||
expect(cy.state('onPageLoadErr')).to.be.null
|
||||
expect(cy.isStopped()).to.be.true // make sure we ran our cleanup routine
|
||||
expect(thenCalled).to.be.false
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
cy
|
||||
.visit('/fixtures/jquery.html')
|
||||
.window().then((win) => {
|
||||
Cypress.config('pageLoadTimeout', 50)
|
||||
const $a = win.$('<a href=\'/timeout?ms=500\'>jquery</a>')
|
||||
.appendTo(win.document.body)
|
||||
|
||||
causeSynchronousBeforeUnload($a)
|
||||
|
||||
return null
|
||||
}).wrap(null).then(() => {
|
||||
thenCalled = true
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2086,8 +2094,7 @@ describe('src/cy/commands/navigation', () => {
|
||||
}
|
||||
})
|
||||
|
||||
cy
|
||||
.visit('/fixtures/jquery.html')
|
||||
cy.visit('/fixtures/jquery.html')
|
||||
|
||||
// make get timeout after only 200ms
|
||||
.get('#does-not-exist', { timeout: 200 }).should('have.class', 'foo')
|
||||
|
||||
@@ -820,7 +820,7 @@ describe('src/cy/commands/request', () => {
|
||||
})
|
||||
|
||||
it('throws when url is not FQDN', {
|
||||
baseUrl: '',
|
||||
baseUrl: null,
|
||||
}, function (done) {
|
||||
cy.stub(cy, 'getRemoteLocation').withArgs('origin').returns('')
|
||||
|
||||
@@ -840,7 +840,7 @@ describe('src/cy/commands/request', () => {
|
||||
})
|
||||
|
||||
it('throws when url is not FQDN, notes that configFile is disabled', {
|
||||
baseUrl: '',
|
||||
baseUrl: null,
|
||||
configFile: false,
|
||||
}, function (done) {
|
||||
cy.stub(cy, 'getRemoteLocation').withArgs('origin').returns('')
|
||||
@@ -860,7 +860,7 @@ describe('src/cy/commands/request', () => {
|
||||
})
|
||||
|
||||
it('throws when url is not FQDN, notes that configFile is non-default', {
|
||||
baseUrl: '',
|
||||
baseUrl: null,
|
||||
configFile: 'foo.json',
|
||||
}, function (done) {
|
||||
cy.stub(cy, 'getRemoteLocation').withArgs('origin').returns('')
|
||||
|
||||
@@ -274,7 +274,7 @@ describe('per-test config', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('in mulitple nested suites', () => {
|
||||
describe('in multiple nested suites', () => {
|
||||
describe('config in suite', {
|
||||
foo: true,
|
||||
}, () => {
|
||||
@@ -295,7 +295,7 @@ describe('per-test config', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('emtpy config', {}, () => {
|
||||
describe('empty config', {}, () => {
|
||||
it('empty config in test', {}, () => {
|
||||
expect(true).ok
|
||||
})
|
||||
|
||||
@@ -8,18 +8,6 @@ const getQueueNames = () => {
|
||||
return _.map(cy.queue, 'name')
|
||||
}
|
||||
|
||||
const registerCypressConfigBackupRestore = () => {
|
||||
let originalConfig
|
||||
|
||||
beforeEach(() => {
|
||||
originalConfig = _.clone(Cypress.config())
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
Cypress.config(originalConfig)
|
||||
})
|
||||
}
|
||||
|
||||
function allowTsModuleStubbing () {
|
||||
// eslint-disable-next-line no-undef
|
||||
__webpack_require__.d = function (exports, name, getter) {
|
||||
@@ -41,6 +29,5 @@ function allowTsModuleStubbing () {
|
||||
module.exports = {
|
||||
getQueueNames,
|
||||
getFirstSubjectByName,
|
||||
registerCypressConfigBackupRestore,
|
||||
allowTsModuleStubbing,
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
"cypress:open": "node ../../scripts/cypress open",
|
||||
"cypress:run": "node ../../scripts/cypress run",
|
||||
"postinstall": "patch-package",
|
||||
"start": "node -e 'console.log(require(`chalk`).red(`\nError:\n\tRunning \\`yarn start\\` is no longer needed for driver/cypress tests.\n\tWe now automatically spawn the server in the pluginsFile.\n\tChanges to the server will be watched and reloaded automatically.`))'"
|
||||
"start": "node -e 'console.log(require(`chalk`).red(`\nError:\n\tRunning \\`yarn start\\` is no longer needed for driver/cypress tests.\n\tWe now automatically spawn the server in the pluginsFile.\n\tChanges to the server will be watched and reloaded automatically.`))'",
|
||||
"test": "yarn cypress:run"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
@@ -17,6 +18,7 @@
|
||||
"@cypress/unique-selector": "0.4.2",
|
||||
"@cypress/webpack-preprocessor": "0.0.0-development",
|
||||
"@cypress/what-is-circular": "1.0.1",
|
||||
"@packages/config": "0.0.0-development",
|
||||
"@packages/network": "0.0.0-development",
|
||||
"@packages/resolve-dist": "0.0.0-development",
|
||||
"@packages/runner": "0.0.0-development",
|
||||
|
||||
@@ -510,7 +510,7 @@ export default {
|
||||
}
|
||||
|
||||
const assert = function (...args) {
|
||||
// if we've temporarily overriden assertions
|
||||
// if we've temporarily overridden assertions
|
||||
// then just bail early with this function
|
||||
const fn = cy.state('overrideAssert') || assertFn
|
||||
|
||||
|
||||
@@ -281,7 +281,6 @@ export default function (Commands, Cypress, cy, state) {
|
||||
|
||||
return Commands.addAllSync({
|
||||
spy,
|
||||
|
||||
stub,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ export default function (Commands, Cypress, cy, state, config) {
|
||||
// stuff, or handling this in the runner itself?
|
||||
// Cypress sessions will clear cookies on its own before each test
|
||||
Cypress.on('test:before:run:async', () => {
|
||||
if (!Cypress.config.experimentalSessionSupport) {
|
||||
if (!Cypress.config('experimentalSessionSupport')) {
|
||||
return getAndClear()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -427,7 +427,7 @@ export default function (Commands, Cypress, cy, state, config) {
|
||||
|
||||
// if a screenshot has not been taken (by cy.screenshot()) in the test
|
||||
// that failed, we can bypass UI-changing and pixel-checking (simple: true)
|
||||
// otheriwse, we need to do all the standard checks
|
||||
// otherwise, we need to do all the standard checks
|
||||
// to make sure the UI is in the right place (simple: false)
|
||||
screenshotConfig.capture = 'runner'
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ const eventHasReturnValue = (e) => {
|
||||
const val = e.returnValue
|
||||
|
||||
// return false if val is an empty string
|
||||
// of if its undinefed
|
||||
// of if its undefined
|
||||
if (val === '' || _.isUndefined(val)) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ export default {
|
||||
// are not and the retry code is happening between
|
||||
// runnables which is bad likely due to the issue below
|
||||
//
|
||||
// bug in bluebird with not propagating cancelations
|
||||
// bug in bluebird with not propagating cancellations
|
||||
// fast enough in a series of promises
|
||||
// https://github.com/petkaantonov/bluebird/issues/1424
|
||||
return state('canceled') || state('error') || runnableHasChanged()
|
||||
|
||||
@@ -1,17 +1,72 @@
|
||||
import _ from 'lodash'
|
||||
import $errUtils from '../cypress/error_utils'
|
||||
|
||||
function mutateConfiguration (testConfigOverride, config, env) {
|
||||
const globalConfig = _.clone(config())
|
||||
// See Test Config Overrides in ../../../../cli/types/cypress.d.ts
|
||||
|
||||
type ResolvedTestConfigOverride = {
|
||||
/**
|
||||
* The list of test config overrides and the invocation details used to add helpful
|
||||
* error messaging to consumers if a test override fails validation.
|
||||
*/
|
||||
testConfigList: Array<TestConfig>
|
||||
/**
|
||||
* The test config overrides that will apply to the test if it passes validation.
|
||||
* */
|
||||
unverifiedTestConfig: Object
|
||||
}
|
||||
|
||||
type TestConfig = {
|
||||
overrides: {
|
||||
browser?: Object
|
||||
}
|
||||
invocationDetails: {
|
||||
stack: Object
|
||||
}
|
||||
};
|
||||
|
||||
type ConfigOverrides = {
|
||||
env: Object | undefined
|
||||
};
|
||||
|
||||
function setConfig (testConfigList: Array<TestConfig>, config, localConfigOverrides: ConfigOverrides = { env: undefined }) {
|
||||
testConfigList.forEach(({ overrides: testConfigOverride, invocationDetails }) => {
|
||||
if (_.isArray(testConfigOverride)) {
|
||||
setConfig(testConfigOverride, config, localConfigOverrides)
|
||||
} else {
|
||||
delete testConfigOverride.browser
|
||||
|
||||
try {
|
||||
config(testConfigOverride)
|
||||
} catch (e) {
|
||||
let err = $errUtils.errByPath('config.invalid_test_override', {
|
||||
errMsg: e.message,
|
||||
})
|
||||
|
||||
err.stack = $errUtils.stackWithReplacedProps({ stack: invocationDetails.stack }, err)
|
||||
throw err
|
||||
}
|
||||
localConfigOverrides = { ...localConfigOverrides, ...testConfigOverride }
|
||||
}
|
||||
})
|
||||
|
||||
return localConfigOverrides
|
||||
}
|
||||
|
||||
function mutateConfiguration (testConfig: ResolvedTestConfigOverride, config, env) {
|
||||
const { testConfigList } = testConfig || []
|
||||
|
||||
let globalConfig = _.clone(config())
|
||||
|
||||
const localConfigOverrides = setConfig(testConfigList, config)
|
||||
|
||||
// only store the global config values that updated
|
||||
globalConfig = _.pick(globalConfig, Object.keys(localConfigOverrides))
|
||||
const globalEnv = _.clone(env())
|
||||
|
||||
delete testConfigOverride.browser
|
||||
config(testConfigOverride)
|
||||
const localConfigOverridesBackup = _.clone(localConfigOverrides)
|
||||
|
||||
const localTestConfig = config()
|
||||
const localTestConfigBackup = _.clone(localTestConfig)
|
||||
|
||||
if (testConfigOverride.env) {
|
||||
env(testConfigOverride.env)
|
||||
if (localConfigOverrides.env) {
|
||||
env(localConfigOverrides.env)
|
||||
}
|
||||
|
||||
const localTestEnv = env()
|
||||
@@ -23,10 +78,15 @@ function mutateConfiguration (testConfigOverride, config, env) {
|
||||
// TODO: (NEXT_BREAKING) always restore configuration
|
||||
// do not allow global mutations inside test
|
||||
const restoreConfigFn = function () {
|
||||
_.each(localTestConfig, (val, key) => {
|
||||
if (localTestConfigBackup[key] !== val) {
|
||||
_.each(localConfigOverrides, (val, key) => {
|
||||
if (localConfigOverridesBackup[key] !== val) {
|
||||
globalConfig[key] = val
|
||||
}
|
||||
|
||||
// explicitly set to undefined if config wasn't previously defined
|
||||
if (!globalConfig.hasOwnProperty(key)) {
|
||||
globalConfig[key] = undefined
|
||||
}
|
||||
})
|
||||
|
||||
_.each(localTestEnv, (val, key) => {
|
||||
@@ -35,8 +95,9 @@ function mutateConfiguration (testConfigOverride, config, env) {
|
||||
}
|
||||
})
|
||||
|
||||
config.reset()
|
||||
// reset test config overrides
|
||||
config(globalConfig)
|
||||
|
||||
env.reset()
|
||||
env(globalEnv)
|
||||
}
|
||||
@@ -46,30 +107,45 @@ function mutateConfiguration (testConfigOverride, config, env) {
|
||||
|
||||
// this is called during test onRunnable time
|
||||
// in order to resolve the test config upfront before test runs
|
||||
export function getResolvedTestConfigOverride (test) {
|
||||
// note: must return as an object to meet the dashboard recording API
|
||||
export function getResolvedTestConfigOverride (test): ResolvedTestConfigOverride {
|
||||
let curParent = test.parent
|
||||
|
||||
const testConfig = [test._testConfig]
|
||||
const testConfigList = [{
|
||||
overrides: test._testConfig,
|
||||
invocationDetails: test.invocationDetails,
|
||||
}]
|
||||
|
||||
while (curParent) {
|
||||
if (curParent._testConfig) {
|
||||
testConfig.push(curParent._testConfig)
|
||||
testConfigList.unshift({
|
||||
overrides: curParent._testConfig,
|
||||
invocationDetails: curParent.invocationDetails,
|
||||
})
|
||||
}
|
||||
|
||||
curParent = curParent.parent
|
||||
}
|
||||
|
||||
return _.reduceRight(testConfig, (acc, opts) => _.extend(acc, opts), {})
|
||||
const testConfig = {
|
||||
testConfigList: testConfigList.filter((opt) => opt.overrides !== undefined),
|
||||
// collect test overrides to send to the dashboard api when @packages/server is ran in record mode
|
||||
unverifiedTestConfig: _.reduce(testConfigList, (acc, opts) => _.extend(acc, opts.overrides), {}),
|
||||
}
|
||||
|
||||
return testConfig
|
||||
}
|
||||
|
||||
class TestConfigOverride {
|
||||
private restoreTestConfigFn: Nullable<() => void> = null
|
||||
|
||||
restoreAndSetTestConfigOverrides (test, config, env) {
|
||||
if (this.restoreTestConfigFn) this.restoreTestConfigFn()
|
||||
|
||||
const resolvedTestConfig = test._testConfig || {}
|
||||
|
||||
this.restoreTestConfigFn = mutateConfiguration(resolvedTestConfig, config, env)
|
||||
if (Object.keys(resolvedTestConfig.unverifiedTestConfig).length > 0) {
|
||||
this.restoreTestConfigFn = mutateConfiguration(resolvedTestConfig, config, env)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import { validate } from '@packages/config'
|
||||
import _ from 'lodash'
|
||||
import $ from 'jquery'
|
||||
import * as blobUtil from 'blob-util'
|
||||
@@ -116,7 +117,7 @@ class $Cypress {
|
||||
// normalize this into boolean
|
||||
config.isTextTerminal = !!config.isTextTerminal
|
||||
|
||||
// we asumme we're interactive based on whether or
|
||||
// we assume we're interactive based on whether or
|
||||
// not we're in a text terminal, but we keep this
|
||||
// as a separate property so we can potentially
|
||||
// slice up the behavior
|
||||
@@ -142,7 +143,12 @@ class $Cypress {
|
||||
|
||||
this.state = $SetterGetter.create({})
|
||||
this.originalConfig = _.cloneDeep(config)
|
||||
this.config = $SetterGetter.create(config)
|
||||
this.config = $SetterGetter.create(config, (config) => {
|
||||
validate(config, (errMsg) => {
|
||||
throw new this.state('specWindow').Error(errMsg)
|
||||
})
|
||||
})
|
||||
|
||||
this.env = $SetterGetter.create(env)
|
||||
this.getTestRetries = function () {
|
||||
const testRetries = this.config('retries')
|
||||
@@ -269,6 +275,8 @@ class $Cypress {
|
||||
return this.emit('stop')
|
||||
|
||||
case 'cypress:config':
|
||||
// emit config event used to:
|
||||
// - trigger iframe viewport update
|
||||
return this.emit('config', args[0])
|
||||
|
||||
case 'runner:start':
|
||||
@@ -390,16 +398,12 @@ class $Cypress {
|
||||
return this.runner.onRunnableRun(...args)
|
||||
|
||||
case 'runner:test:before:run':
|
||||
// get back to a clean slate
|
||||
this.cy.reset(...args)
|
||||
|
||||
if (this.config('isTextTerminal')) {
|
||||
// needed for handling test retries
|
||||
this.emit('mocha', 'test:before:run', args[0])
|
||||
}
|
||||
|
||||
this.emit('test:before:run', ...args)
|
||||
|
||||
break
|
||||
|
||||
case 'runner:test:before:run:async':
|
||||
@@ -423,7 +427,6 @@ class $Cypress {
|
||||
}
|
||||
|
||||
break
|
||||
|
||||
case 'cy:before:all:screenshots':
|
||||
return this.emit('before:all:screenshots', ...args)
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ export default {
|
||||
return enqueuedCmd = obj
|
||||
}
|
||||
|
||||
// only check for command enqueing when none
|
||||
// only check for command enqueuing when none
|
||||
// of our args are functions else commands
|
||||
// like cy.then or cy.each would always fail
|
||||
// since they return promises and queue more
|
||||
@@ -224,8 +224,7 @@ export default {
|
||||
|
||||
command.set({ subject })
|
||||
|
||||
// end / snapshot our logs
|
||||
// if they need it
|
||||
// end / snapshot our logs if they need it
|
||||
command.finishLogs()
|
||||
|
||||
// reset the nestedIndex back to null
|
||||
@@ -234,8 +233,7 @@ export default {
|
||||
// also reset recentlyReady back to null
|
||||
state('recentlyReady', null)
|
||||
|
||||
// we're finished with the current command
|
||||
// so set it back to null
|
||||
// we're finished with the current command so set it back to null
|
||||
state('current', null)
|
||||
|
||||
state('subject', subject)
|
||||
|
||||
@@ -3,11 +3,14 @@
|
||||
/* eslint-disable prefer-rest-params */
|
||||
import _ from 'lodash'
|
||||
import Promise from 'bluebird'
|
||||
import debugFn from 'debug'
|
||||
import { registerFetch } from 'unfetch'
|
||||
|
||||
import $dom from '../dom'
|
||||
import $utils from './utils'
|
||||
import $errUtils from './error_utils'
|
||||
import $stackUtils from './stack_utils'
|
||||
|
||||
import $Chai from '../cy/chai'
|
||||
import $Xhrs from '../cy/xhrs'
|
||||
import $jQuery from '../cy/jquery'
|
||||
@@ -31,8 +34,6 @@ import { $Command } from './command'
|
||||
import $CommandQueue from './command_queue'
|
||||
import $VideoRecorder from '../cy/video-recorder'
|
||||
import $TestConfigOverrides from '../cy/testConfigOverrides'
|
||||
import debugFn from 'debug'
|
||||
import { registerFetch } from 'unfetch'
|
||||
|
||||
const debugErrors = debugFn('cypress:driver:errors')
|
||||
|
||||
@@ -222,7 +223,7 @@ export default {
|
||||
Cypress.action('app:window:before:unload', e)
|
||||
|
||||
// return undefined so our beforeunload handler
|
||||
// doesnt trigger a confirmation dialog
|
||||
// doesn't trigger a confirmation dialog
|
||||
return undefined
|
||||
},
|
||||
onUnload (e) {
|
||||
@@ -330,7 +331,7 @@ export default {
|
||||
|
||||
// we look at whether or not nestedIndex is a number, because if it
|
||||
// is then we need to insert inside of our commands, else just push
|
||||
// it onto the end of the queu
|
||||
// it onto the end of the queue
|
||||
const index = _.isNumber(nestedIndex) ? nestedIndex : queue.length
|
||||
|
||||
queue.insert(index, $Command.create(obj))
|
||||
@@ -541,7 +542,7 @@ export default {
|
||||
_.extend(cy, {
|
||||
id: _.uniqueId('cy'),
|
||||
|
||||
// synchrounous querying
|
||||
// synchronous querying
|
||||
$$,
|
||||
|
||||
state,
|
||||
@@ -704,29 +705,35 @@ export default {
|
||||
return doneEarly()
|
||||
},
|
||||
|
||||
reset (attrs, test) {
|
||||
const s = state()
|
||||
// reset is called before each test
|
||||
reset (test) {
|
||||
try {
|
||||
const s = state()
|
||||
|
||||
const backup = {
|
||||
window: s.window,
|
||||
document: s.document,
|
||||
$autIframe: s.$autIframe,
|
||||
specWindow: s.specWindow,
|
||||
activeSessions: s.activeSessions,
|
||||
const backup = {
|
||||
window: s.window,
|
||||
document: s.document,
|
||||
$autIframe: s.$autIframe,
|
||||
specWindow: s.specWindow,
|
||||
activeSessions: s.activeSessions,
|
||||
}
|
||||
|
||||
// reset state back to empty object
|
||||
state.reset()
|
||||
|
||||
// and then restore these backed up props
|
||||
state(backup)
|
||||
|
||||
queue.reset()
|
||||
queue.clear()
|
||||
timers.reset()
|
||||
|
||||
testConfigOverrides.restoreAndSetTestConfigOverrides(test, Cypress.config, Cypress.env)
|
||||
|
||||
cy.removeAllListeners()
|
||||
} catch (err) {
|
||||
fail(err)
|
||||
}
|
||||
|
||||
// reset state back to empty object
|
||||
state.reset()
|
||||
|
||||
// and then restore these backed up props
|
||||
state(backup)
|
||||
|
||||
queue.reset()
|
||||
queue.clear()
|
||||
timers.reset()
|
||||
testConfigOverrides.restoreAndSetTestConfigOverrides(test, Cypress.config, Cypress.env)
|
||||
|
||||
return cy.removeAllListeners()
|
||||
},
|
||||
|
||||
addCommandSync (name, fn) {
|
||||
@@ -1034,8 +1041,7 @@ export default {
|
||||
const originalDone = arguments[0]
|
||||
|
||||
arguments[0] = (done = function (err) {
|
||||
// TODO: handle no longer error
|
||||
// when ended early
|
||||
// TODO: handle no longer error when ended early
|
||||
doneEarly()
|
||||
|
||||
originalDone(err)
|
||||
@@ -1056,7 +1062,7 @@ export default {
|
||||
|
||||
// if we returned a value from fn
|
||||
// and enqueued some new commands
|
||||
// and the value isnt currently cy
|
||||
// and the value isn't currently cy
|
||||
// or a promise
|
||||
if (ret &&
|
||||
(queue.length > currentLength) &&
|
||||
|
||||
@@ -245,6 +245,17 @@ export default {
|
||||
},
|
||||
},
|
||||
|
||||
config: {
|
||||
invalid_argument: {
|
||||
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': {
|
||||
message: `The config override passed to your test has the following validation error:\n\n{{errMsg}}`,
|
||||
docsUrl: 'https://on.cypress.io/config',
|
||||
},
|
||||
},
|
||||
|
||||
contains: {
|
||||
empty_string: {
|
||||
message: `${cmd('contains')} cannot be passed an empty string.`,
|
||||
|
||||
@@ -205,7 +205,7 @@ const throwErr = (err, options = {}) => {
|
||||
let { onFail, errProps } = options
|
||||
|
||||
// assume onFail is a command if
|
||||
//# onFail is present and isnt a function
|
||||
//# onFail is present and isn't a function
|
||||
if (onFail && !_.isFunction(onFail)) {
|
||||
const command = onFail
|
||||
|
||||
@@ -543,6 +543,7 @@ const logError = (Cypress, handlerType, err, handled = false) => {
|
||||
}
|
||||
|
||||
export default {
|
||||
stackWithReplacedProps,
|
||||
appendErrMsg,
|
||||
createUncaughtException,
|
||||
cypressErr,
|
||||
|
||||
@@ -50,7 +50,7 @@ function overloadMochaFnForConfig (fnName, specWindow) {
|
||||
|
||||
const fnType = fnName === 'it' || fnName === 'specify' ? 'Test' : 'Suite'
|
||||
|
||||
function overrideFn (fn) {
|
||||
function overrideMochaFn (fn) {
|
||||
specWindow[fnName] = fn()
|
||||
specWindow[fnName]['only'] = fn('only')
|
||||
specWindow[fnName]['skip'] = fn('skip')
|
||||
@@ -58,7 +58,7 @@ function overloadMochaFnForConfig (fnName, specWindow) {
|
||||
if (specWindow[`x${fnName}`]) specWindow[`x${fnName}`] = specWindow[fnName]['skip']
|
||||
}
|
||||
|
||||
overrideFn(function (subFn) {
|
||||
const replacementFn = function (subFn) {
|
||||
return function (...args) {
|
||||
/**
|
||||
* @type {Cypress.Cypress}
|
||||
@@ -94,6 +94,7 @@ function overloadMochaFnForConfig (fnName, specWindow) {
|
||||
|
||||
const ret = origFn.apply(this, mochaArgs)
|
||||
|
||||
// attached testConfigOverrides will executes on `runner:test:before:run` event
|
||||
ret._testConfig = _testConfig
|
||||
|
||||
return ret
|
||||
@@ -101,7 +102,9 @@ function overloadMochaFnForConfig (fnName, specWindow) {
|
||||
|
||||
return origFn.apply(this, args)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
overrideMochaFn(replacementFn)
|
||||
}
|
||||
|
||||
const ui = (specWindow, _mocha, config) => {
|
||||
@@ -355,7 +358,7 @@ const patchSuiteAddTest = (specWindow, config) => {
|
||||
const test = args[0]
|
||||
|
||||
if (!test.invocationDetails) {
|
||||
test.invocationDetails = $stackUtils.getInvocationDetails(specWindow, config).details
|
||||
test.invocationDetails = $stackUtils.getInvocationDetails(specWindow, config)
|
||||
}
|
||||
|
||||
const ret = suiteAddTest.apply(this, args)
|
||||
@@ -387,7 +390,7 @@ const patchSuiteAddSuite = (specWindow, config) => {
|
||||
const suite = args[0]
|
||||
|
||||
if (!suite.invocationDetails) {
|
||||
suite.invocationDetails = $stackUtils.getInvocationDetails(specWindow, config).details
|
||||
suite.invocationDetails = $stackUtils.getInvocationDetails(specWindow, config)
|
||||
}
|
||||
|
||||
return suiteAddSuite.apply(this, args)
|
||||
@@ -439,7 +442,7 @@ const patchSuiteHooks = (specWindow, config) => {
|
||||
if (!hook.invocationDetails) {
|
||||
const invocationDetails = $stackUtils.getInvocationDetails(specWindow, config)
|
||||
|
||||
hook.invocationDetails = invocationDetails.details
|
||||
hook.invocationDetails = invocationDetails
|
||||
invocationStack = invocationDetails.stack
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ const mochaCtxKeysRe = /^(_runnable|test)$/
|
||||
const betweenQuotesRe = /\"(.+?)\"/
|
||||
|
||||
const HOOKS = 'beforeAll beforeEach afterEach afterAll'.split(' ')
|
||||
const TEST_BEFORE_RUN_ASYNC_EVENT = 'runner:test:before:run:async'
|
||||
// event fired before hooks and test execution
|
||||
const TEST_BEFORE_RUN_EVENT = 'runner:test:before:run'
|
||||
const TEST_AFTER_RUN_EVENT = 'runner:test:after:run'
|
||||
|
||||
@@ -48,8 +50,8 @@ const fired = (event, runnable) => {
|
||||
|
||||
const testBeforeRunAsync = (test, Cypress) => {
|
||||
return Promise.try(() => {
|
||||
if (!fired('runner:test:before:run:async', test)) {
|
||||
return fire('runner:test:before:run:async', test, Cypress)
|
||||
if (!fired(TEST_BEFORE_RUN_ASYNC_EVENT, test)) {
|
||||
return fire(TEST_BEFORE_RUN_ASYNC_EVENT, test, Cypress)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -73,7 +75,7 @@ const testAfterRun = (test, Cypress) => {
|
||||
// if the test:after:run listener throws it's likely spec code
|
||||
// Since the test status has already been emitted this can't affect the test status.
|
||||
// Let's just log the error to console
|
||||
// TODO: revist when we handle uncaught exceptions/rejections between tests
|
||||
// TODO: revisit when we handle uncaught exceptions/rejections between tests
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(e)
|
||||
}
|
||||
@@ -336,7 +338,7 @@ const isRootSuite = (suite) => {
|
||||
}
|
||||
|
||||
const overrideRunnerHook = (Cypress, _runner, getTestById, getTest, setTest, getTests) => {
|
||||
// bail if our _runner doesnt have a hook.
|
||||
// bail if our _runner doesn't have a hook.
|
||||
// useful in tests
|
||||
if (!_runner.hook) {
|
||||
return
|
||||
@@ -581,10 +583,6 @@ const normalize = (runnable, tests, initialTests, onRunnable, onLogsById, getRun
|
||||
wrappedRunnable._testConfig = cfg
|
||||
}
|
||||
|
||||
if (cfg.slowTestThreshold) {
|
||||
runnable.slow(cfg.slowTestThreshold)
|
||||
}
|
||||
|
||||
wrappedRunnable._titlePath = runnable.titlePath()
|
||||
}
|
||||
|
||||
@@ -918,9 +916,9 @@ const _runnerListeners = (_runner, Cypress, _emissions, getTestById, getTest, se
|
||||
// })
|
||||
|
||||
/**
|
||||
* Mocha retry event is only fired in Mocha version 6+
|
||||
* https://github.com/mochajs/mocha/commit/2a76dd7589e4a1ed14dd2a33ab89f182e4c4a050
|
||||
*/
|
||||
* Mocha retry event is only fired in Mocha version 6+
|
||||
* https://github.com/mochajs/mocha/commit/2a76dd7589e4a1ed14dd2a33ab89f182e4c4a050
|
||||
*/
|
||||
_runner.on('retry', (test, err) => {
|
||||
test.err = $errUtils.wrapErr(err)
|
||||
Cypress.action('runner:retry', wrap(test), test.err)
|
||||
@@ -1137,7 +1135,7 @@ export default {
|
||||
}
|
||||
|
||||
const onRunnable = (r) => {
|
||||
// set defualt retries at onRunnable time instead of onRunnableRun
|
||||
// set default retries at onRunnable time instead of onRunnableRun
|
||||
return _runnables.push(r)
|
||||
}
|
||||
|
||||
@@ -1217,6 +1215,7 @@ export default {
|
||||
const isAfterEachHook = isHook && !!hookName.match(/after each/)
|
||||
const retryAbleRunnable = isTest || isBeforeEachHook || isAfterEachHook
|
||||
const willRetry = (test._currentRetry < test._retries) && retryAbleRunnable
|
||||
const isTestConfigOverride = !fired(TEST_BEFORE_RUN_EVENT, test)
|
||||
|
||||
const fail = function () {
|
||||
return err
|
||||
@@ -1231,13 +1230,19 @@ export default {
|
||||
test.final = false
|
||||
}
|
||||
|
||||
if (willRetry && isBeforeEachHook) {
|
||||
if (isTestConfigOverride) {
|
||||
// let the runner handle the error
|
||||
delete runnable.err
|
||||
}
|
||||
|
||||
if ((willRetry || isTestConfigOverride) && isBeforeEachHook) {
|
||||
delete runnable.err
|
||||
test._retriesBeforeEachFailedTestFn = test.fn
|
||||
|
||||
// this prevents afterEach hooks that exist at a deeper level than the failing one from running
|
||||
// we will always skip remaining beforeEach hooks since they will always be same level or deeper
|
||||
test._skipHooksWithLevelGreaterThan = runnable.titlePath().length
|
||||
|
||||
setHookFailureProps(test, runnable, err)
|
||||
test.fn = function () {
|
||||
throw err
|
||||
@@ -1247,7 +1252,7 @@ export default {
|
||||
}
|
||||
|
||||
if (willRetry && isAfterEachHook) {
|
||||
// if we've already failed this attempt from an afterEach hook then we've already enqueud another attempt
|
||||
// if we've already failed this attempt from an afterEach hook then we've already enqueued another attempt
|
||||
// so return early
|
||||
if (test._retriedFromAfterEachHook) {
|
||||
return noFail()
|
||||
@@ -1374,10 +1379,9 @@ export default {
|
||||
}
|
||||
|
||||
// closure for calculating the actual
|
||||
// runtime of a runnables fn exection duration
|
||||
// runtime of a runnables fn execution duration
|
||||
// and also the run of the runnable:after:run:async event
|
||||
let lifecycleStart
|
||||
let wallClockStartedAt = null
|
||||
let wallClockEnd = null
|
||||
let fnDurationStart = null
|
||||
let fnDurationEnd = null
|
||||
@@ -1387,7 +1391,7 @@ export default {
|
||||
// when this is a hook, capture the real start
|
||||
// date so we can calculate our test's duration
|
||||
// including all of its hooks
|
||||
wallClockStartedAt = new Date()
|
||||
const wallClockStartedAt = new Date()
|
||||
|
||||
if (!test.wallClockStartedAt) {
|
||||
// if we don't have lifecycle timings yet
|
||||
@@ -1398,27 +1402,13 @@ export default {
|
||||
test.wallClockStartedAt = wallClockStartedAt
|
||||
}
|
||||
|
||||
// if this isnt a hook, then the name is 'test'
|
||||
const hookName = runnable.type === 'hook' ? getHookName(runnable) : 'test'
|
||||
const isHook = runnable.type === 'hook'
|
||||
|
||||
// if this isn't a hook, then the name is 'test'
|
||||
const hookName = isHook ? getHookName(runnable) : 'test'
|
||||
|
||||
// set hook id to hook id or test id
|
||||
const hookId = runnable.type === 'hook' ? runnable.hookId : runnable.id
|
||||
|
||||
// if we haven't yet fired this event for this test
|
||||
// that means that we need to reset the previous state
|
||||
// of cy - since we now have a new 'test' and all of the
|
||||
// associated _runnables will share this state
|
||||
if (!fired(TEST_BEFORE_RUN_EVENT, test)) {
|
||||
fire(TEST_BEFORE_RUN_EVENT, test, Cypress)
|
||||
|
||||
// this is the earliest we can set test._retries since test:before:run
|
||||
// will load in testConfigOverrides (per test configuration)
|
||||
const retries = Cypress.getTestRetries() ?? -1
|
||||
|
||||
test._retries = retries
|
||||
}
|
||||
|
||||
const isHook = runnable.type === 'hook'
|
||||
const hookId = isHook ? runnable.hookId : runnable.id
|
||||
|
||||
const isAfterEachHook = isHook && hookName.match(/after each/)
|
||||
const isBeforeEachHook = isHook && hookName.match(/before each/)
|
||||
@@ -1522,22 +1512,32 @@ export default {
|
||||
})
|
||||
}
|
||||
|
||||
cy.state('duringUserTestExecution', false)
|
||||
|
||||
// our runnable is about to run, so let cy know. this enables
|
||||
// us to always have a correct runnable set even when we are
|
||||
// running lifecycle events
|
||||
// and also get back a function result handler that we use as
|
||||
// an async seam
|
||||
cy.setRunnable(runnable, hookId)
|
||||
|
||||
// TODO: handle promise timeouts here!
|
||||
// whenever any runnable is about to run
|
||||
// we figure out what test its associated to
|
||||
// if its a hook, and then we fire the
|
||||
// test:before:run:async action if its not
|
||||
// been fired before for this test
|
||||
return testBeforeRunAsync(test, Cypress)
|
||||
return Promise.try(() => {
|
||||
if (!fired(TEST_BEFORE_RUN_EVENT, test)) {
|
||||
cy.reset(test)
|
||||
test.slow(Cypress.config('slowTestThreshold'))
|
||||
test._retries = Cypress.getTestRetries() ?? -1
|
||||
fire(TEST_BEFORE_RUN_EVENT, test, Cypress)
|
||||
}
|
||||
|
||||
cy.state('duringUserTestExecution', false)
|
||||
|
||||
// our runnable is about to run, so let cy know. this enables
|
||||
// us to always have a correct runnable set even when we are
|
||||
// running lifecycle events
|
||||
// and also get back a function result handler that we use as
|
||||
// an async seam
|
||||
cy.setRunnable(runnable, hookId)
|
||||
})
|
||||
.then(() => {
|
||||
return testBeforeRunAsync(test, Cypress)
|
||||
})
|
||||
.catch((err) => {
|
||||
// TODO: if our async tasks fail
|
||||
// then allow us to cause the test
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import _ from 'lodash'
|
||||
import { extend, isObject, isString } from 'lodash'
|
||||
|
||||
const reset = (state = {}) => {
|
||||
// perf loop
|
||||
@@ -11,7 +11,7 @@ const reset = (state = {}) => {
|
||||
|
||||
// a basic object setter / getter class
|
||||
export default {
|
||||
create: (state = {}) => {
|
||||
create: (state = {}, validate?) => {
|
||||
const get = (key?) => {
|
||||
if (key) {
|
||||
return state[key]
|
||||
@@ -24,7 +24,7 @@ export default {
|
||||
let obj
|
||||
let ret
|
||||
|
||||
if (_.isObject(key)) {
|
||||
if (isObject(key)) {
|
||||
obj = key
|
||||
ret = obj
|
||||
} else {
|
||||
@@ -33,7 +33,9 @@ export default {
|
||||
ret = value
|
||||
}
|
||||
|
||||
_.extend(state, obj)
|
||||
validate && validate(obj)
|
||||
|
||||
extend(state, obj)
|
||||
|
||||
return ret
|
||||
}
|
||||
@@ -44,7 +46,7 @@ export default {
|
||||
case 0:
|
||||
return get()
|
||||
case 1:
|
||||
if (_.isString(key)) {
|
||||
if (isString(key)) {
|
||||
return get(key)
|
||||
}
|
||||
|
||||
|
||||
@@ -100,17 +100,16 @@ const getInvocationDetails = (specWindow, config) => {
|
||||
|
||||
// firefox throws a different stack than chromium
|
||||
// which includes stackframes from cypress_runner.js.
|
||||
// So we drop the lines until we get to the spec stackframe (incldues __cypress/tests)
|
||||
// So we drop the lines until we get to the spec stackframe (includes __cypress/tests)
|
||||
if (specWindow.Cypress && specWindow.Cypress.isBrowser('firefox')) {
|
||||
stack = stackWithLinesDroppedFromMarker(stack, '__cypress/tests', true)
|
||||
}
|
||||
|
||||
const details = getSourceDetailsForFirstLine(stack, config('projectRoot'))
|
||||
const details = getSourceDetailsForFirstLine(stack, config('projectRoot')) || {}
|
||||
|
||||
return {
|
||||
details,
|
||||
stack,
|
||||
}
|
||||
details.stack = stack
|
||||
|
||||
return details
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,6 +382,7 @@ const normalizedUserInvocationStack = (userInvocationStack) => {
|
||||
export default {
|
||||
replacedStack,
|
||||
getCodeFrame,
|
||||
getCodeFrameFromSource,
|
||||
getSourceStack,
|
||||
getStackLines,
|
||||
getSourceDetailsForFirstLine,
|
||||
|
||||
@@ -203,7 +203,7 @@ export function loadClientCertificateConfig (config) {
|
||||
clientCertificateStore.clear()
|
||||
|
||||
// The basic validation of the certificate configuration has already been done by this point
|
||||
// within the 'isValidClientCertificatesSet' function within packages/server/lib/util/validation.js
|
||||
// within the 'isValidClientCertificatesSet' function within packages/config/src/validation.js
|
||||
if (clientCertificates) {
|
||||
clientCertificates.forEach((item) => {
|
||||
debug(`loading client cert at index ${index}`)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,89 +0,0 @@
|
||||
exports['not one of the strings error message'] = `
|
||||
Expected \`test\` to be one of these values: "foo", "bar". Instead the value was: \`"nope"\`
|
||||
`
|
||||
|
||||
exports['number instead of string'] = `
|
||||
Expected \`test\` to be one of these values: "foo", "bar". Instead the value was: \`42\`
|
||||
`
|
||||
|
||||
exports['null instead of string'] = `
|
||||
Expected \`test\` to be one of these values: "foo", "bar". Instead the value was: \`null\`
|
||||
`
|
||||
|
||||
exports['not one of the numbers error message'] = `
|
||||
Expected \`test\` to be one of these values: 1, 2, 3. Instead the value was: \`4\`
|
||||
`
|
||||
|
||||
exports['string instead of a number'] = `
|
||||
Expected \`test\` to be one of these values: 1, 2, 3. Instead the value was: \`"foo"\`
|
||||
`
|
||||
|
||||
exports['null instead of a number'] = `
|
||||
Expected \`test\` to be one of these values: 1, 2, 3. Instead the value was: \`null\`
|
||||
`
|
||||
|
||||
exports['lib/util/validation #isValidBrowser passes valid browsers and forms error messages for invalid ones isValidBrowser 1'] = {
|
||||
"name": "isValidBrowser",
|
||||
"behavior": [
|
||||
{
|
||||
"given": {
|
||||
"name": "Chrome",
|
||||
"displayName": "Chrome Browser",
|
||||
"family": "chromium",
|
||||
"path": "/path/to/chrome",
|
||||
"version": "1.2.3",
|
||||
"majorVersion": 1
|
||||
},
|
||||
"expect": true
|
||||
},
|
||||
{
|
||||
"given": {
|
||||
"name": "FF",
|
||||
"displayName": "Firefox",
|
||||
"family": "firefox",
|
||||
"path": "/path/to/firefox",
|
||||
"version": "1.2.3",
|
||||
"majorVersion": "1"
|
||||
},
|
||||
"expect": true
|
||||
},
|
||||
{
|
||||
"given": {
|
||||
"name": "Electron",
|
||||
"displayName": "Electron",
|
||||
"family": "chromium",
|
||||
"path": "",
|
||||
"version": "99.101.3",
|
||||
"majorVersion": 99
|
||||
},
|
||||
"expect": true
|
||||
},
|
||||
{
|
||||
"given": {
|
||||
"name": "No display name",
|
||||
"family": "chromium"
|
||||
},
|
||||
"expect": "Expected `displayName` to be a non-empty string. Instead the value was: `{\"name\":\"No display name\",\"family\":\"chromium\"}`"
|
||||
},
|
||||
{
|
||||
"given": {
|
||||
"name": "bad family",
|
||||
"displayName": "Bad family browser",
|
||||
"family": "unknown family"
|
||||
},
|
||||
"expect": "Expected `family` to be either chromium or firefox. Instead the value was: `{\"name\":\"bad family\",\"displayName\":\"Bad family browser\",\"family\":\"unknown family\"}`"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
exports['undefined browsers'] = `
|
||||
Missing browsers list
|
||||
`
|
||||
|
||||
exports['empty list of browsers'] = `
|
||||
Expected at least one browser
|
||||
`
|
||||
|
||||
exports['browsers list with a string'] = `
|
||||
Found an error while validating the \`browsers\` list. Expected \`name\` to be a non-empty string. Instead the value was: \`"foo"\`
|
||||
`
|
||||
+36
-130
@@ -2,6 +2,7 @@ import _ from 'lodash'
|
||||
import path from 'path'
|
||||
import Promise from 'bluebird'
|
||||
import deepDiff from 'return-deep-diff'
|
||||
import configUtils from '@packages/config'
|
||||
|
||||
import errors from './errors'
|
||||
import scaffold from './scaffold'
|
||||
@@ -14,7 +15,6 @@ import pathHelpers from './util/path_helpers'
|
||||
|
||||
const debug = Debug('cypress:server:config')
|
||||
|
||||
import { options, breakingOptions } from './config_options'
|
||||
import { getProcessEnvVars, CYPRESS_SPECIAL_ENV_VARS } from './util/config'
|
||||
|
||||
export const RESOLVED_FROM = ['plugin', 'env', 'default', 'runtime', 'config'] as const
|
||||
@@ -30,26 +30,9 @@ export type ResolvedConfigurationOptions = Partial<{
|
||||
[x in keyof Cypress.ResolvedConfigOptions]: ResolvedFromConfig
|
||||
}>
|
||||
|
||||
const dashesOrUnderscoresRe = /^(_-)+/
|
||||
const folders = _(configUtils.options).filter({ isFolder: true }).map('name').value()
|
||||
|
||||
// takes an array and creates an index object of [keyKey]: [valueKey]
|
||||
const createIndex = (arr, keyKey, valueKey) => {
|
||||
return _.reduce(arr, (memo, item) => {
|
||||
if (item[valueKey] !== undefined) {
|
||||
memo[item[keyKey]] = item[valueKey]
|
||||
}
|
||||
|
||||
return memo
|
||||
}, {})
|
||||
}
|
||||
|
||||
const publicConfigKeys = _(options).reject({ isInternal: true }).map('name').value()
|
||||
const breakingKeys = _.map(breakingOptions, 'name')
|
||||
const folders = _(options).filter({ isFolder: true }).map('name').value()
|
||||
const validationRules = createIndex(options, 'name', 'validation')
|
||||
const defaultValues: Record<string, any> = createIndex(options, 'name', 'defaultValue')
|
||||
|
||||
const convertRelativeToAbsolutePaths = (projectRoot, obj, defaults = {}) => {
|
||||
const convertRelativeToAbsolutePaths = (projectRoot, obj) => {
|
||||
return _.reduce(folders, (memo, folder) => {
|
||||
const val = obj[folder]
|
||||
|
||||
@@ -62,54 +45,9 @@ const convertRelativeToAbsolutePaths = (projectRoot, obj, defaults = {}) => {
|
||||
, {})
|
||||
}
|
||||
|
||||
const validateNoBreakingConfig = (config) => {
|
||||
breakingOptions.forEach(({ name, errorKey, newName, isWarning, value }) => {
|
||||
if (config.hasOwnProperty(name)) {
|
||||
if (value && config[name] !== value) {
|
||||
// Bail if a value is specified but the config does not have that value.
|
||||
return
|
||||
}
|
||||
|
||||
if (isWarning) {
|
||||
return errors.warning(errorKey, {
|
||||
name,
|
||||
newName,
|
||||
value,
|
||||
configFile: config.configFile,
|
||||
})
|
||||
}
|
||||
|
||||
return errors.throw(errorKey, {
|
||||
name,
|
||||
newName,
|
||||
value,
|
||||
configFile: config.configFile,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const validate = (cfg, onErr) => {
|
||||
return _.each(cfg, (value, key) => {
|
||||
const validationFn = validationRules[key]
|
||||
|
||||
// does this key have a validation rule?
|
||||
if (validationFn) {
|
||||
// and is the value different from the default?
|
||||
if (value !== defaultValues[key]) {
|
||||
const result = validationFn(key, value)
|
||||
|
||||
if (result !== true) {
|
||||
return onErr(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const validateFile = (file) => {
|
||||
return (settings) => {
|
||||
return validate(settings, (errMsg) => {
|
||||
return configUtils.validate(settings, (errMsg) => {
|
||||
return errors.throw('SETTINGS_VALIDATION_ERROR', file, errMsg)
|
||||
})
|
||||
}
|
||||
@@ -123,8 +61,7 @@ const hideSpecialVals = function (val, key) {
|
||||
return val
|
||||
}
|
||||
|
||||
// an object with a few utility methods
|
||||
// for easy stubbing from unit tests
|
||||
// an object with a few utility methods for easy stubbing from unit tests
|
||||
export const utils = {
|
||||
resolveModule (name) {
|
||||
return require.resolve(name)
|
||||
@@ -187,10 +124,6 @@ export const utils = {
|
||||
},
|
||||
}
|
||||
|
||||
export function getConfigKeys () {
|
||||
return publicConfigKeys
|
||||
}
|
||||
|
||||
export function isValidCypressInternalEnvValue (value) {
|
||||
// names of config environments, see "config/app.yml"
|
||||
const names = ['development', 'test', 'staging', 'production']
|
||||
@@ -198,12 +131,6 @@ export function isValidCypressInternalEnvValue (value) {
|
||||
return _.includes(names, value)
|
||||
}
|
||||
|
||||
export function allowed (obj = {}) {
|
||||
const propertyNames = publicConfigKeys.concat(breakingKeys)
|
||||
|
||||
return _.pick(obj, propertyNames)
|
||||
}
|
||||
|
||||
export function get (projectRoot, options = {}) {
|
||||
return Promise.all([
|
||||
settings.read(projectRoot, options).then(validateFile('cypress.json')),
|
||||
@@ -224,8 +151,7 @@ export function set (obj: Record<string, any> = {}) {
|
||||
debug('setting config object')
|
||||
let { projectRoot, projectName, config, envFile, options } = obj
|
||||
|
||||
// just force config to be an object
|
||||
// so we dont have to do as much
|
||||
// just force config to be an object so we dont have to do as much
|
||||
// work in our tests
|
||||
if (config == null) {
|
||||
config = {}
|
||||
@@ -233,8 +159,7 @@ export function set (obj: Record<string, any> = {}) {
|
||||
|
||||
debug('config is %o', config)
|
||||
|
||||
// flatten the object's properties
|
||||
// into the master config object
|
||||
// flatten the object's properties into the master config object
|
||||
config.envFile = envFile
|
||||
config.projectRoot = projectRoot
|
||||
config.projectName = projectName
|
||||
@@ -251,7 +176,7 @@ export function mergeDefaults (config: Record<string, any> = {}, options: Record
|
||||
debug('merged config with options, got %o', config)
|
||||
|
||||
_
|
||||
.chain(allowed(options))
|
||||
.chain(configUtils.allowed(options))
|
||||
.omit('env')
|
||||
.omit('browsers')
|
||||
.each((val, key) => {
|
||||
@@ -268,18 +193,15 @@ export function mergeDefaults (config: Record<string, any> = {}, options: Record
|
||||
config.baseUrl = url.replace(/\/\/+$/, '/')
|
||||
}
|
||||
|
||||
_.defaults(config, defaultValues)
|
||||
const defaultsForRuntime = configUtils.getDefaultValues(options)
|
||||
|
||||
// Default values can be functions, in which case they are evaluated
|
||||
// at runtime - for example, slowTestThreshold where the default value
|
||||
// varies between e2e and component testing.
|
||||
config = _.mapValues(config, (value) => (typeof value === 'function' ? value(options) : value))
|
||||
_.defaultsDeep(config, defaultsForRuntime)
|
||||
|
||||
// split out our own app wide env from user env variables
|
||||
// and delete envFile
|
||||
config.env = parseEnv(config, options.env, resolved)
|
||||
|
||||
config.cypressEnv = process.env['CYPRESS_INTERNAL_ENV']
|
||||
config.cypressEnv = process.env.CYPRESS_INTERNAL_ENV
|
||||
debug('using CYPRESS_INTERNAL_ENV %s', config.cypressEnv)
|
||||
if (!isValidCypressInternalEnvValue(config.cypressEnv)) {
|
||||
errors.throw('INVALID_CYPRESS_INTERNAL_ENV', config.cypressEnv)
|
||||
@@ -297,36 +219,35 @@ export function mergeDefaults (config: Record<string, any> = {}, options: Record
|
||||
config.numTestsKeptInMemory = 0
|
||||
}
|
||||
|
||||
config = setResolvedConfigValues(config, defaultValues, resolved, options)
|
||||
config = setResolvedConfigValues(config, defaultsForRuntime, resolved)
|
||||
|
||||
if (config.port) {
|
||||
config = setUrls(config)
|
||||
}
|
||||
|
||||
config = setAbsolutePaths(config, defaultValues)
|
||||
config = setAbsolutePaths(config)
|
||||
|
||||
config = setParentTestsPaths(config)
|
||||
|
||||
config = setNodeBinary(config, options.args?.userNodePath, options.args?.userNodeVersion)
|
||||
|
||||
// validate config again here so that we catch
|
||||
// configuration errors coming from the CLI overrides
|
||||
// or env var overrides
|
||||
validate(config, (errMsg) => {
|
||||
// validate config again here so that we catch configuration errors coming
|
||||
// from the CLI overrides or env var overrides
|
||||
configUtils.validate(_.omit(config, 'browsers'), (errMsg) => {
|
||||
return errors.throw('CONFIG_VALIDATION_ERROR', errMsg)
|
||||
})
|
||||
|
||||
validateNoBreakingConfig(config)
|
||||
configUtils.validateNoBreakingConfig(config, errors.warning, errors.throw)
|
||||
|
||||
return setSupportFileAndFolder(config)
|
||||
.then(setPluginsFile)
|
||||
return setSupportFileAndFolder(config, defaultsForRuntime)
|
||||
.then((obj) => setPluginsFile(obj, defaultsForRuntime))
|
||||
.then(setScaffoldPaths)
|
||||
}
|
||||
|
||||
export function setResolvedConfigValues (config, defaults, resolved, options) {
|
||||
export function setResolvedConfigValues (config, defaults, resolved) {
|
||||
const obj = _.clone(config)
|
||||
|
||||
obj.resolved = resolveConfigValues(config, defaults, resolved, options)
|
||||
obj.resolved = resolveConfigValues(config, defaults, resolved)
|
||||
debug('resolved config is %o', obj.resolved.browsers)
|
||||
|
||||
return obj
|
||||
@@ -361,7 +282,7 @@ export function updateWithPluginValues (cfg, overrides) {
|
||||
|
||||
// make sure every option returned from the plugins file
|
||||
// passes our validation functions
|
||||
validate(overrides, (errMsg) => {
|
||||
configUtils.validate(overrides, (errMsg) => {
|
||||
if (cfg.pluginsFile && cfg.projectRoot) {
|
||||
const relativePluginsPath = path.relative(cfg.projectRoot, cfg.pluginsFile)
|
||||
|
||||
@@ -431,11 +352,11 @@ export function updateWithPluginValues (cfg, overrides) {
|
||||
// combines the default configuration object with values specified in the
|
||||
// configuration file like "cypress.json". Values in configuration file
|
||||
// overwrite the defaults.
|
||||
export function resolveConfigValues (config, defaults, resolved = {}, options = {}) {
|
||||
export function resolveConfigValues (config, defaults, resolved = {}) {
|
||||
// pick out only known configuration keys
|
||||
return _
|
||||
.chain(config)
|
||||
.pick(publicConfigKeys)
|
||||
.pick(configUtils.getPublicConfigKeys())
|
||||
.mapValues((val, key) => {
|
||||
let r
|
||||
const source = (s: ResolvedConfigurationOptionSource): ResolvedFromConfig => {
|
||||
@@ -455,11 +376,9 @@ export function resolveConfigValues (config, defaults, resolved = {}, options =
|
||||
return source(r)
|
||||
}
|
||||
|
||||
const defaultValue = typeof defaults[key] === 'function' ? defaults[key](options) : defaults[key]
|
||||
|
||||
if (!(!_.isEqual(config[key], defaultValue) && key !== 'browsers')) {
|
||||
if (!(!_.isEqual(config[key], defaults[key]) && key !== 'browsers')) {
|
||||
// "browsers" list is special, since it is dynamic by default
|
||||
// and can only be ovewritten via plugins file
|
||||
// and can only be overwritten via plugins file
|
||||
return source('default')
|
||||
}
|
||||
|
||||
@@ -497,7 +416,7 @@ export function setScaffoldPaths (obj) {
|
||||
}
|
||||
|
||||
// async function
|
||||
export function setSupportFileAndFolder (obj) {
|
||||
export function setSupportFileAndFolder (obj, defaults) {
|
||||
if (!obj.supportFile) {
|
||||
return Promise.resolve(obj)
|
||||
}
|
||||
@@ -531,7 +450,7 @@ export function setSupportFileAndFolder (obj) {
|
||||
return fs.pathExists(obj.supportFile)
|
||||
.then((found) => {
|
||||
if (!found) {
|
||||
errors.throw('SUPPORT_FILE_NOT_FOUND', obj.supportFile, obj.configFile || defaultValues.configFile)
|
||||
errors.throw('SUPPORT_FILE_NOT_FOUND', obj.supportFile, obj.configFile || defaults.configFile)
|
||||
}
|
||||
|
||||
return debug('switching to found file %s', obj.supportFile)
|
||||
@@ -539,7 +458,7 @@ export function setSupportFileAndFolder (obj) {
|
||||
}).catch({ code: 'MODULE_NOT_FOUND' }, () => {
|
||||
debug('support JS module %s does not load', sf)
|
||||
|
||||
const loadingDefaultSupportFile = sf === path.resolve(obj.projectRoot, defaultValues.supportFile)
|
||||
const loadingDefaultSupportFile = sf === path.resolve(obj.projectRoot, defaults.supportFile)
|
||||
|
||||
return utils.discoverModuleFile({
|
||||
filename: sf,
|
||||
@@ -548,7 +467,7 @@ export function setSupportFileAndFolder (obj) {
|
||||
})
|
||||
.then((result) => {
|
||||
if (result === null) {
|
||||
const configFile = obj.configFile || defaultValues.configFile
|
||||
const configFile = obj.configFile || defaults.configFile
|
||||
|
||||
return errors.throw('SUPPORT_FILE_NOT_FOUND', path.resolve(obj.projectRoot, sf), configFile)
|
||||
}
|
||||
@@ -584,7 +503,7 @@ export function setSupportFileAndFolder (obj) {
|
||||
// * and the pluginsFile is NOT set to the default
|
||||
// - throw an error, because it should be there if the user
|
||||
// explicitly set it
|
||||
export const setPluginsFile = Promise.method((obj) => {
|
||||
export const setPluginsFile = Promise.method((obj, defaults) => {
|
||||
if (!obj.pluginsFile) {
|
||||
return obj
|
||||
}
|
||||
@@ -607,7 +526,7 @@ export const setPluginsFile = Promise.method((obj) => {
|
||||
}).catch({ code: 'MODULE_NOT_FOUND' }, () => {
|
||||
debug('plugins module does not exist %o', { pluginsFile })
|
||||
|
||||
const isLoadingDefaultPluginsFile = pluginsFile === path.resolve(obj.projectRoot, defaultValues.pluginsFile)
|
||||
const isLoadingDefaultPluginsFile = pluginsFile === path.resolve(obj.projectRoot, defaults.pluginsFile)
|
||||
|
||||
return utils.discoverModuleFile({
|
||||
filename: pluginsFile,
|
||||
@@ -645,7 +564,7 @@ export function setParentTestsPaths (obj) {
|
||||
return obj
|
||||
}
|
||||
|
||||
export function setAbsolutePaths (obj, defaults) {
|
||||
export function setAbsolutePaths (obj) {
|
||||
let pr
|
||||
|
||||
obj = _.clone(obj)
|
||||
@@ -658,7 +577,7 @@ export function setAbsolutePaths (obj, defaults) {
|
||||
// obj.fileServerFolder = path.resolve(pr, obj.fileServerFolder)
|
||||
|
||||
// and do the same for all the rest
|
||||
_.extend(obj, convertRelativeToAbsolutePaths(pr, obj, defaults))
|
||||
_.extend(obj, convertRelativeToAbsolutePaths(pr, obj))
|
||||
}
|
||||
|
||||
return obj
|
||||
@@ -703,26 +622,13 @@ export function parseEnv (cfg: Record<string, any>, envCLI: Record<string, any>,
|
||||
|
||||
envCLI = envCLI != null ? envCLI : {}
|
||||
|
||||
const matchesConfigKey = function (key) {
|
||||
if (_.has(defaultValues, key)) {
|
||||
return key
|
||||
}
|
||||
|
||||
key = key.toLowerCase().replace(dashesOrUnderscoresRe, '')
|
||||
key = _.camelCase(key)
|
||||
|
||||
if (_.has(defaultValues, key)) {
|
||||
return key
|
||||
}
|
||||
}
|
||||
|
||||
const configFromEnv = _.reduce(envProc, (memo: string[], val, key) => {
|
||||
let cfgKey: string
|
||||
|
||||
cfgKey = matchesConfigKey(key)
|
||||
cfgKey = configUtils.matchesConfigKey(key)
|
||||
|
||||
if (cfgKey) {
|
||||
// only change the value if it hasnt been
|
||||
// only change the value if it hasn't been
|
||||
// set by the CLI. override default + config
|
||||
if (resolved[cfgKey] !== 'cli') {
|
||||
cfg[cfgKey] = val
|
||||
|
||||
@@ -11,6 +11,7 @@ require('./environment')
|
||||
|
||||
const Promise = require('bluebird')
|
||||
const debug = require('debug')('cypress:server:cypress')
|
||||
const { getPublicConfigKeys } = require('@packages/config')
|
||||
const argsUtils = require('./util/args')
|
||||
const chalk = require('chalk')
|
||||
const { openProject } = require('../lib/open_project')
|
||||
@@ -29,8 +30,9 @@ const exit = (code = 0) => {
|
||||
}
|
||||
|
||||
const showWarningForInvalidConfig = (options) => {
|
||||
const publicConfigKeys = getPublicConfigKeys()
|
||||
const invalidConfigOptions = require('lodash').keys(options.config).reduce((invalid, option) => {
|
||||
if (!require('./config').getConfigKeys().find((configKey) => configKey === option)) {
|
||||
if (!publicConfigKeys.find((configKey) => configKey === option)) {
|
||||
invalid.push(option)
|
||||
}
|
||||
|
||||
|
||||
@@ -633,7 +633,7 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) {
|
||||
We found an invalid value in the file: ${chalk.blue(filePath)}
|
||||
|
||||
${chalk.yellow(arg2)}`
|
||||
// happens when there is an invalid config value returned from the
|
||||
// happens when there is an invalid config value is returned from the
|
||||
// project's plugins file like "cypress/plugins.index.js"
|
||||
case 'PLUGINS_CONFIG_VALIDATION_ERROR':
|
||||
filePath = `\`${arg1}\``
|
||||
|
||||
@@ -7,6 +7,7 @@ const debugCiInfo = require('debug')('cypress:server:record:ci-info')
|
||||
const Promise = require('bluebird')
|
||||
const isForkPr = require('is-fork-pr')
|
||||
const commitInfo = require('@cypress/commit-info')
|
||||
|
||||
const api = require('../api')
|
||||
const logger = require('../logger')
|
||||
const errors = require('../errors')
|
||||
@@ -749,7 +750,7 @@ const createRunAndRecordSpecs = (options = {}) => {
|
||||
return _.pick({
|
||||
...v,
|
||||
clientId: v.id,
|
||||
config: v._testConfig || null,
|
||||
config: v._testConfig?.unverifiedTestConfig || null,
|
||||
title: v._titlePath,
|
||||
hookIds: v.hooks.map((hook) => hook.hookId),
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@ import path from 'path'
|
||||
|
||||
import browsers from './browsers'
|
||||
import pkg from '@packages/root'
|
||||
import { allowed } from '@packages/config'
|
||||
import { ServerCt } from './server-ct'
|
||||
import { SocketCt } from './socket-ct'
|
||||
import { SocketE2E } from './socket-e2e'
|
||||
@@ -410,7 +411,7 @@ export class ProjectBase<TServer extends ServerE2E | ServerCt> extends EE {
|
||||
// allowed config values to
|
||||
// prevent tampering with the
|
||||
// internals and breaking cypress
|
||||
const allowedCfg = config.allowed(cfg)
|
||||
const allowedCfg = allowed(cfg)
|
||||
|
||||
const modifiedCfg = await plugins.init(allowedCfg, {
|
||||
projectRoot: this.projectRoot,
|
||||
|
||||
@@ -4,8 +4,9 @@ const is = require('check-more-types')
|
||||
const path = require('path')
|
||||
const debug = require('debug')('cypress:server:args')
|
||||
const minimist = require('minimist')
|
||||
const { getPublicConfigKeys } = require('@packages/config')
|
||||
|
||||
const coerceUtil = require('./coerce')
|
||||
const configUtil = require('../config')
|
||||
const proxyUtil = require('./proxy')
|
||||
const errors = require('../errors')
|
||||
|
||||
@@ -344,7 +345,7 @@ module.exports = {
|
||||
}
|
||||
|
||||
// get a list of the available config keys
|
||||
const configKeys = configUtil.getConfigKeys()
|
||||
const configKeys = getPublicConfigKeys()
|
||||
|
||||
// and if any of our options match this
|
||||
const configValues = _.pick(options, configKeys)
|
||||
|
||||
@@ -108,9 +108,6 @@ module.exports = {
|
||||
// // strip off the unit part
|
||||
// spec = path.relative("unit", spec)
|
||||
|
||||
// // now simply resolve this with our unitFolder
|
||||
// // which makes it an absolute path
|
||||
// path.join(config.unitFolder, spec)
|
||||
debug('returning default path %s', spec)
|
||||
|
||||
return spec
|
||||
|
||||
@@ -188,7 +188,7 @@ export function read (projectRoot, options: SettingsOptions = {}) {
|
||||
return config
|
||||
})
|
||||
}).catch((err) => {
|
||||
debug('an error occured when reading config', err)
|
||||
debug('an error occurred when reading config', err)
|
||||
if (errors.isCypressErr(err)) {
|
||||
throw err
|
||||
}
|
||||
|
||||
@@ -128,6 +128,7 @@
|
||||
"@cypress/json-schemas": "5.39.0",
|
||||
"@cypress/sinon-chai": "2.9.1",
|
||||
"@ffprobe-installer/ffprobe": "1.1.0",
|
||||
"@packages/config": "0.0.0-development",
|
||||
"@packages/desktop-gui": "0.0.0-development",
|
||||
"@packages/electron": "0.0.0-development",
|
||||
"@packages/example": "0.0.0-development",
|
||||
|
||||
@@ -16,6 +16,8 @@ const pkg = require('@packages/root')
|
||||
const detect = require('@packages/launcher/lib/detect')
|
||||
const launch = require('@packages/launcher/lib/browsers')
|
||||
const extension = require('@packages/extension')
|
||||
const v = require('@packages/config/lib/validation')
|
||||
|
||||
const argsUtil = require(`${root}lib/util/args`)
|
||||
const { fs } = require(`${root}lib/util/fs`)
|
||||
const ciProvider = require(`${root}lib/util/ci_provider`)
|
||||
@@ -43,7 +45,6 @@ const browserUtils = require(`${root}lib/browsers/utils`)
|
||||
const chromeBrowser = require(`${root}lib/browsers/chrome`)
|
||||
const { openProject } = require(`${root}lib/open_project`)
|
||||
const env = require(`${root}lib/util/env`)
|
||||
const v = require(`${root}lib/util/validation`)
|
||||
const system = require(`${root}lib/util/system`)
|
||||
const appData = require(`${root}lib/util/app_data`)
|
||||
const electronApp = require('../../lib/util/electron-app')
|
||||
|
||||
@@ -2,15 +2,16 @@ require('../spec_helper')
|
||||
|
||||
const _ = require('lodash')
|
||||
const debug = require('debug')('test')
|
||||
const Fixtures = require('@tooling/system-tests/lib/fixtures')
|
||||
|
||||
const config = require(`${root}lib/config`)
|
||||
const errors = require(`${root}lib/errors`)
|
||||
const configUtil = require(`${root}lib/util/config`)
|
||||
const scaffold = require(`${root}lib/scaffold`)
|
||||
const Fixtures = require('@tooling/system-tests/lib/fixtures')
|
||||
let settings = require(`${root}lib/util/settings`)
|
||||
|
||||
describe('lib/config', () => {
|
||||
beforeEach(function () {
|
||||
before(function () {
|
||||
this.env = process.env
|
||||
|
||||
process.env = _.omit(process.env, 'CYPRESS_DEBUG')
|
||||
@@ -18,7 +19,7 @@ describe('lib/config', () => {
|
||||
Fixtures.scaffold()
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
after(function () {
|
||||
process.env = this.env
|
||||
})
|
||||
|
||||
@@ -986,18 +987,6 @@ describe('lib/config', () => {
|
||||
})
|
||||
})
|
||||
|
||||
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) {
|
||||
@@ -1070,9 +1059,8 @@ describe('lib/config', () => {
|
||||
cfg.projectRoot = '/foo/bar/'
|
||||
|
||||
return config.mergeDefaults(cfg, options)
|
||||
.then((val) => val[prop])
|
||||
.then((result) => {
|
||||
expect(result).to.deep.eq(value)
|
||||
.then((mergedConfig) => {
|
||||
expect(mergedConfig[prop]).to.deep.eq(value)
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -1302,9 +1290,9 @@ describe('lib/config', () => {
|
||||
})
|
||||
|
||||
it('can override socketId in options', () => {
|
||||
return config.mergeDefaults({ projectRoot: '/foo/bar/' }, { socketId: 1234 })
|
||||
return config.mergeDefaults({ projectRoot: '/foo/bar/' }, { socketId: '1234' })
|
||||
.then((cfg) => {
|
||||
expect(cfg.socketId).to.eq(1234)
|
||||
expect(cfg.socketId).to.eq('1234')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1461,6 +1449,8 @@ describe('lib/config', () => {
|
||||
projectId: { value: null, from: 'default' },
|
||||
redirectionLimit: { value: 20, from: 'default' },
|
||||
reporter: { value: 'json', from: 'cli' },
|
||||
resolvedNodePath: { value: null, from: 'default' },
|
||||
resolvedNodeVersion: { value: null, from: 'default' },
|
||||
reporterOptions: { value: null, from: 'default' },
|
||||
requestTimeout: { value: 5000, from: 'default' },
|
||||
responseTimeout: { value: 30000, from: 'default' },
|
||||
@@ -1469,6 +1459,7 @@ describe('lib/config', () => {
|
||||
screenshotsFolder: { value: 'cypress/screenshots', from: 'default' },
|
||||
slowTestThreshold: { value: 10000, from: 'default' },
|
||||
supportFile: { value: 'cypress/support', from: 'default' },
|
||||
supportFolder: { value: false, from: 'default' },
|
||||
taskTimeout: { value: 60000, from: 'default' },
|
||||
testFiles: { value: '**/*.*', from: 'default' },
|
||||
trashAssetsBeforeRuns: { value: true, from: 'default' },
|
||||
@@ -1567,6 +1558,8 @@ describe('lib/config', () => {
|
||||
projectId: { value: 'projectId123', from: 'env' },
|
||||
redirectionLimit: { value: 20, from: 'default' },
|
||||
reporter: { value: 'spec', from: 'default' },
|
||||
resolvedNodePath: { value: null, from: 'default' },
|
||||
resolvedNodeVersion: { value: null, from: 'default' },
|
||||
reporterOptions: { value: null, from: 'default' },
|
||||
requestTimeout: { value: 5000, from: 'default' },
|
||||
responseTimeout: { value: 30000, from: 'default' },
|
||||
@@ -1575,6 +1568,7 @@ describe('lib/config', () => {
|
||||
screenshotsFolder: { value: 'cypress/screenshots', from: 'default' },
|
||||
slowTestThreshold: { value: 10000, from: 'default' },
|
||||
supportFile: { value: 'cypress/support', from: 'default' },
|
||||
supportFolder: { value: false, from: 'default' },
|
||||
taskTimeout: { value: 60000, from: 'default' },
|
||||
testFiles: { value: '**/*.*', from: 'default' },
|
||||
trashAssetsBeforeRuns: { value: true, from: 'default' },
|
||||
@@ -2015,6 +2009,12 @@ describe('lib/config', () => {
|
||||
})
|
||||
|
||||
context('.setSupportFileAndFolder', () => {
|
||||
const mockSupportDefaults = {
|
||||
supportFile: 'cypress/support',
|
||||
supportFolder: false,
|
||||
configFile: 'cypress.json',
|
||||
}
|
||||
|
||||
it('does nothing if supportFile is falsey', () => {
|
||||
const obj = {
|
||||
projectRoot: '/_test-output/path/to/project',
|
||||
@@ -2034,7 +2034,7 @@ describe('lib/config', () => {
|
||||
supportFile: 'test/unit/config_spec.js',
|
||||
})
|
||||
|
||||
return config.setSupportFileAndFolder(obj)
|
||||
return config.setSupportFileAndFolder(obj, mockSupportDefaults)
|
||||
.then((result) => {
|
||||
expect(result).to.eql({
|
||||
projectRoot,
|
||||
@@ -2052,7 +2052,7 @@ describe('lib/config', () => {
|
||||
supportFile: 'cypress/support',
|
||||
})
|
||||
|
||||
return config.setSupportFileAndFolder(obj)
|
||||
return config.setSupportFileAndFolder(obj, mockSupportDefaults)
|
||||
.then((result) => {
|
||||
expect(result).to.eql({
|
||||
projectRoot,
|
||||
@@ -2070,7 +2070,7 @@ describe('lib/config', () => {
|
||||
supportFile: 'cypress/support',
|
||||
})
|
||||
|
||||
return config.setSupportFileAndFolder(obj)
|
||||
return config.setSupportFileAndFolder(obj, mockSupportDefaults)
|
||||
.then((result) => {
|
||||
expect(result).to.eql({
|
||||
projectRoot,
|
||||
@@ -2087,7 +2087,7 @@ describe('lib/config', () => {
|
||||
supportFile: 'does/not/exist',
|
||||
})
|
||||
|
||||
return config.setSupportFileAndFolder(obj)
|
||||
return config.setSupportFileAndFolder(obj, mockSupportDefaults)
|
||||
.catch((err) => {
|
||||
expect(err.message).to.include('The support file is missing or invalid.')
|
||||
})
|
||||
@@ -2108,7 +2108,7 @@ describe('lib/config', () => {
|
||||
supportFile: 'cypress/support',
|
||||
})
|
||||
|
||||
return config.setSupportFileAndFolder(obj)
|
||||
return config.setSupportFileAndFolder(obj, mockSupportDefaults)
|
||||
.then((result) => {
|
||||
debug('result is', result)
|
||||
|
||||
@@ -2135,7 +2135,7 @@ describe('lib/config', () => {
|
||||
supportFile: 'cypress/support.ts',
|
||||
})
|
||||
|
||||
return config.setSupportFileAndFolder(obj)
|
||||
return config.setSupportFileAndFolder(obj, mockSupportDefaults)
|
||||
.then((result) => {
|
||||
debug('result is', result)
|
||||
|
||||
@@ -2149,6 +2149,11 @@ describe('lib/config', () => {
|
||||
})
|
||||
|
||||
context('.setPluginsFile', () => {
|
||||
const mockPluginDefaults = {
|
||||
pluginsFile: 'cypress/plugins',
|
||||
configFile: 'cypress.json',
|
||||
}
|
||||
|
||||
it('does nothing if pluginsFile is falsey', () => {
|
||||
const obj = {
|
||||
projectRoot: '/_test-output/path/to/project',
|
||||
@@ -2168,7 +2173,7 @@ describe('lib/config', () => {
|
||||
pluginsFile: `${projectRoot}/cypress/plugins`,
|
||||
}
|
||||
|
||||
return config.setPluginsFile(obj)
|
||||
return config.setPluginsFile(obj, mockPluginDefaults)
|
||||
.then((result) => {
|
||||
expect(result).to.eql({
|
||||
projectRoot,
|
||||
@@ -2185,7 +2190,7 @@ describe('lib/config', () => {
|
||||
pluginsFile: `${projectRoot}/cypress/plugins`,
|
||||
}
|
||||
|
||||
return config.setPluginsFile(obj)
|
||||
return config.setPluginsFile(obj, mockPluginDefaults)
|
||||
.then((result) => {
|
||||
expect(result).to.eql({
|
||||
projectRoot,
|
||||
@@ -2209,7 +2214,7 @@ describe('lib/config', () => {
|
||||
pluginsFile: pluginsFolder,
|
||||
}
|
||||
|
||||
return config.setPluginsFile(obj)
|
||||
return config.setPluginsFile(obj, mockPluginDefaults)
|
||||
.then((result) => {
|
||||
expect(result).to.eql({
|
||||
projectRoot,
|
||||
@@ -2226,7 +2231,7 @@ describe('lib/config', () => {
|
||||
pluginsFile: `${projectRoot}/cypress/plugins`,
|
||||
})
|
||||
|
||||
return config.setPluginsFile(obj)
|
||||
return config.setPluginsFile(obj, mockPluginDefaults)
|
||||
.then((result) => {
|
||||
expect(result).to.eql({
|
||||
projectRoot,
|
||||
@@ -2243,7 +2248,7 @@ describe('lib/config', () => {
|
||||
pluginsFile: 'does/not/exist',
|
||||
}
|
||||
|
||||
return config.setPluginsFile(obj)
|
||||
return config.setPluginsFile(obj, mockPluginDefaults)
|
||||
.catch((err) => {
|
||||
expect(err.message).to.include('The plugins file is missing or invalid.')
|
||||
})
|
||||
@@ -2264,7 +2269,7 @@ describe('lib/config', () => {
|
||||
pluginsFile,
|
||||
}
|
||||
|
||||
return config.setPluginsFile(obj)
|
||||
return config.setPluginsFile(obj, mockPluginDefaults)
|
||||
.then((result) => {
|
||||
expect(result).to.eql({
|
||||
projectRoot,
|
||||
@@ -2309,17 +2314,6 @@ describe('lib/config', () => {
|
||||
expect(config.setAbsolutePaths({})).to.deep.eq({})
|
||||
})
|
||||
|
||||
// it "resolves fileServerFolder with projectRoot", ->
|
||||
// obj = {
|
||||
// projectRoot: "/_test-output/path/to/project"
|
||||
// fileServerFolder: "foo"
|
||||
// }
|
||||
|
||||
// expect(config.setAbsolutePaths(obj)).to.deep.eq({
|
||||
// projectRoot: "/_test-output/path/to/project"
|
||||
// fileServerFolder: "/_test-output/path/to/project/foo"
|
||||
// })
|
||||
|
||||
it('does not mutate existing obj', () => {
|
||||
const obj = {}
|
||||
|
||||
@@ -2337,7 +2331,7 @@ describe('lib/config', () => {
|
||||
expect(config.setAbsolutePaths(obj)).to.deep.eq(obj)
|
||||
})
|
||||
|
||||
return ['fileServerFolder', 'fixturesFolder', 'integrationFolder', 'unitFolder', 'supportFile', 'pluginsFile'].forEach((folder) => {
|
||||
return ['fileServerFolder', 'fixturesFolder', 'integrationFolder', 'supportFile', 'pluginsFile'].forEach((folder) => {
|
||||
it(`converts relative ${folder} to absolute path`, () => {
|
||||
const obj = {
|
||||
projectRoot: '/_test-output/path/to/project',
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
require('../spec_helper')
|
||||
const snapshot = require('snap-shot-it')
|
||||
const v = require(`${root}lib/util/validation`)
|
||||
|
||||
describe('lib/util/validation', () => {
|
||||
context('#isValidBrowserList', () => {
|
||||
it('does not allow empty or not browsers', () => {
|
||||
snapshot('undefined browsers', v.isValidBrowserList('browsers'))
|
||||
snapshot('empty list of browsers', v.isValidBrowserList('browsers', []))
|
||||
|
||||
return snapshot('browsers list with a string', v.isValidBrowserList('browsers', ['foo']))
|
||||
})
|
||||
})
|
||||
|
||||
context('#isValidBrowser', () => {
|
||||
it('passes valid browsers and forms error messages for invalid ones', () => {
|
||||
const browsers = [
|
||||
// valid browser
|
||||
{
|
||||
name: 'Chrome',
|
||||
displayName: 'Chrome Browser',
|
||||
family: 'chromium',
|
||||
path: '/path/to/chrome',
|
||||
version: '1.2.3',
|
||||
majorVersion: 1,
|
||||
},
|
||||
// another valid browser
|
||||
{
|
||||
name: 'FF',
|
||||
displayName: 'Firefox',
|
||||
family: 'firefox',
|
||||
path: '/path/to/firefox',
|
||||
version: '1.2.3',
|
||||
majorVersion: '1',
|
||||
},
|
||||
// Electron is a valid browser
|
||||
{
|
||||
name: 'Electron',
|
||||
displayName: 'Electron',
|
||||
family: 'chromium',
|
||||
path: '',
|
||||
version: '99.101.3',
|
||||
majorVersion: 99,
|
||||
},
|
||||
// invalid browser, missing displayName
|
||||
{
|
||||
name: 'No display name',
|
||||
family: 'chromium',
|
||||
},
|
||||
{
|
||||
name: 'bad family',
|
||||
displayName: 'Bad family browser',
|
||||
family: 'unknown family',
|
||||
},
|
||||
]
|
||||
|
||||
// data-driven testing - computers snapshot value for each item in the list passed through the function
|
||||
// https://github.com/bahmutov/snap-shot-it#data-driven-testing
|
||||
return snapshot.apply(null, [v.isValidBrowser].concat(browsers))
|
||||
})
|
||||
})
|
||||
|
||||
context('#isOneOf', () => {
|
||||
it('validates a string', () => {
|
||||
const validate = v.isOneOf('foo', 'bar')
|
||||
|
||||
expect(validate).to.be.a('function')
|
||||
expect(validate('test', 'foo')).to.be.true
|
||||
expect(validate('test', 'bar')).to.be.true
|
||||
|
||||
// different value
|
||||
let msg = validate('test', 'nope')
|
||||
|
||||
expect(msg).to.not.be.true
|
||||
snapshot('not one of the strings error message', msg)
|
||||
|
||||
msg = validate('test', 42)
|
||||
expect(msg).to.not.be.true
|
||||
snapshot('number instead of string', msg)
|
||||
|
||||
msg = validate('test', null)
|
||||
expect(msg).to.not.be.true
|
||||
|
||||
return snapshot('null instead of string', msg)
|
||||
})
|
||||
|
||||
it('validates a number', () => {
|
||||
const validate = v.isOneOf(1, 2, 3)
|
||||
|
||||
expect(validate).to.be.a('function')
|
||||
expect(validate('test', 1)).to.be.true
|
||||
expect(validate('test', 3)).to.be.true
|
||||
|
||||
// different value
|
||||
let msg = validate('test', 4)
|
||||
|
||||
expect(msg).to.not.be.true
|
||||
snapshot('not one of the numbers error message', msg)
|
||||
|
||||
msg = validate('test', 'foo')
|
||||
expect(msg).to.not.be.true
|
||||
snapshot('string instead of a number', msg)
|
||||
|
||||
msg = validate('test', null)
|
||||
expect(msg).to.not.be.true
|
||||
|
||||
return snapshot('null instead of a number', msg)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -46,7 +46,7 @@ We dynamically generated a new test to display this failure.
|
||||
│ Pending: 0 │
|
||||
│ Skipped: 0 │
|
||||
│ Screenshots: 1 │
|
||||
│ Video: true │
|
||||
│ Video: false │
|
||||
│ Duration: X seconds │
|
||||
│ Spec Ran: testConfigOverrides-invalid-browser.js │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
@@ -58,13 +58,6 @@ We dynamically generated a new test to display this failure.
|
||||
ght error was detected outside of a test (failed).png
|
||||
|
||||
|
||||
(Video)
|
||||
|
||||
- Started processing: Compressing to 32 CRF
|
||||
- Finished processing: /XXX/XXX/XXX/cypress/videos/testConfigOverrides-invalid-bro (X second)
|
||||
wser.js.mp4
|
||||
|
||||
|
||||
====================================================================================================
|
||||
|
||||
(Run Finished)
|
||||
@@ -142,3 +135,760 @@ exports['testConfigOverrides / has originalTitle when skip due to browser config
|
||||
|
||||
|
||||
`
|
||||
|
||||
exports['testConfigOverrides / fails when passing invalid config values - [chrome,electron]'] = `
|
||||
|
||||
====================================================================================================
|
||||
|
||||
(Run Starting)
|
||||
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Cypress: 1.2.3 │
|
||||
│ Browser: FooBrowser 88 │
|
||||
│ Specs: 1 found (testConfigOverrides-invalid.js) │
|
||||
│ Searched: cypress/integration/testConfigOverrides-invalid.js │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
Running: testConfigOverrides-invalid.js (1 of 1)
|
||||
|
||||
|
||||
1) inline test config override throws error
|
||||
2) inline test config override throws error when executed within cy cmd
|
||||
context config overrides throws error
|
||||
3) runs
|
||||
|
||||
nested contexts overrides throws error at the correct line number
|
||||
4) 1st test fails on overrides
|
||||
5) 2nd test fails on overrides
|
||||
|
||||
nested contexts
|
||||
test override
|
||||
6) throws error at the correct line number
|
||||
✓ does execute 2nd test
|
||||
|
||||
throws error correctly when before hook
|
||||
7) "before all" hook for "test config override throws error"
|
||||
|
||||
throws error correctly when beforeEach hook
|
||||
8) test config override throws error
|
||||
|
||||
|
||||
1 passing
|
||||
8 failing
|
||||
|
||||
1) inline test config override throws error:
|
||||
Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`""\`
|
||||
[stack trace lines]
|
||||
|
||||
2) inline test config override throws error when executed within cy cmd:
|
||||
Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`"null"\`
|
||||
[stack trace lines]
|
||||
|
||||
3) context config overrides throws error
|
||||
runs:
|
||||
CypressError: The config override passed to your test has the following validation error:
|
||||
|
||||
Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\`
|
||||
|
||||
https://on.cypress.io/config
|
||||
Error
|
||||
[stack trace lines]
|
||||
|
||||
4) nested contexts overrides throws error at the correct line number
|
||||
1st test fails on overrides:
|
||||
CypressError: The config override passed to your test has the following validation error:
|
||||
|
||||
Expected \`defaultCommandTimeout\` to be a number. Instead the value was: \`"500"\`
|
||||
|
||||
https://on.cypress.io/config
|
||||
Error
|
||||
[stack trace lines]
|
||||
|
||||
5) nested contexts overrides throws error at the correct line number
|
||||
2nd test fails on overrides:
|
||||
CypressError: The config override passed to your test has the following validation error:
|
||||
|
||||
Expected \`defaultCommandTimeout\` to be a number. Instead the value was: \`"500"\`
|
||||
|
||||
https://on.cypress.io/config
|
||||
Error
|
||||
[stack trace lines]
|
||||
|
||||
6) nested contexts
|
||||
test override
|
||||
throws error at the correct line number:
|
||||
CypressError: The config override passed to your test has the following validation error:
|
||||
|
||||
Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`"not_an_http_url"\`
|
||||
|
||||
https://on.cypress.io/config
|
||||
Error
|
||||
[stack trace lines]
|
||||
|
||||
7) throws error correctly when before hook
|
||||
"before all" hook for "test config override throws error":
|
||||
CypressError: The config override passed to your test has the following validation error:
|
||||
|
||||
Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\`
|
||||
|
||||
https://on.cypress.io/config
|
||||
|
||||
Because this error occurred during a \`before all\` hook we are skipping the remaining tests in the current suite: \`throws error correctly when...\`
|
||||
Error
|
||||
[stack trace lines]
|
||||
|
||||
8) throws error correctly when beforeEach hook
|
||||
test config override throws error:
|
||||
CypressError: The config override passed to your test has the following validation error:
|
||||
|
||||
Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\`
|
||||
|
||||
https://on.cypress.io/config
|
||||
Error
|
||||
[stack trace lines]
|
||||
|
||||
|
||||
|
||||
|
||||
(Results)
|
||||
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Tests: 9 │
|
||||
│ Passing: 1 │
|
||||
│ Failing: 8 │
|
||||
│ Pending: 0 │
|
||||
│ Skipped: 0 │
|
||||
│ Screenshots: 2 │
|
||||
│ Video: false │
|
||||
│ Duration: X seconds │
|
||||
│ Spec Ran: testConfigOverrides-invalid.js │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
||||
(Screenshots)
|
||||
|
||||
- /XXX/XXX/XXX/cypress/screenshots/testConfigOverrides-invalid.js/inline test conf (1280x720)
|
||||
ig override throws error (failed).png
|
||||
- /XXX/XXX/XXX/cypress/screenshots/testConfigOverrides-invalid.js/inline test conf (1280x720)
|
||||
ig override throws error when executed within cy cmd (failed).png
|
||||
|
||||
|
||||
====================================================================================================
|
||||
|
||||
(Run Finished)
|
||||
|
||||
|
||||
Spec Tests Passing Failing Pending Skipped
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ ✖ testConfigOverrides-invalid.js XX:XX 9 1 8 - - │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
✖ 1 of 1 failed (100%) XX:XX 9 1 8 - -
|
||||
|
||||
|
||||
`
|
||||
|
||||
exports['testConfigOverrides / fails when passing invalid config values with beforeEach - [chrome,electron]'] = `
|
||||
|
||||
====================================================================================================
|
||||
|
||||
(Run Starting)
|
||||
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Cypress: 1.2.3 │
|
||||
│ Browser: FooBrowser 88 │
|
||||
│ Specs: 1 found (testConfigOverrides-before-invalid.js) │
|
||||
│ Searched: cypress/integration/testConfigOverrides-before-invalid.js │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
Running: testConfigOverrides-before-invalid.js (1 of 1)
|
||||
|
||||
|
||||
runs all tests
|
||||
1) inline test config override throws error
|
||||
2) inline test config override throws error when executed within cy cmd
|
||||
context config overrides throws error
|
||||
3) runs
|
||||
nested contexts overrides throws error at the correct line number
|
||||
4) 1st test fails on overrides
|
||||
5) 2nd test fails on overrides
|
||||
nested contexts
|
||||
test override
|
||||
6) throws error at the correct line number
|
||||
✓ does execute 2nd test
|
||||
throws error correctly when before hook
|
||||
7) "before all" hook for "test config override throws error"
|
||||
throws error correctly when beforeEach hook
|
||||
8) test config override throws error
|
||||
|
||||
|
||||
1 passing
|
||||
8 failing
|
||||
|
||||
1) runs all tests
|
||||
inline test config override throws error:
|
||||
Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`""\`
|
||||
[stack trace lines]
|
||||
|
||||
2) runs all tests
|
||||
inline test config override throws error when executed within cy cmd:
|
||||
Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`"null"\`
|
||||
[stack trace lines]
|
||||
|
||||
3) runs all tests
|
||||
context config overrides throws error
|
||||
runs:
|
||||
CypressError: The config override passed to your test has the following validation error:
|
||||
|
||||
Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\`
|
||||
|
||||
https://on.cypress.io/config
|
||||
Error
|
||||
[stack trace lines]
|
||||
|
||||
4) runs all tests
|
||||
nested contexts overrides throws error at the correct line number
|
||||
1st test fails on overrides:
|
||||
CypressError: The config override passed to your test has the following validation error:
|
||||
|
||||
Expected \`defaultCommandTimeout\` to be a number. Instead the value was: \`"500"\`
|
||||
|
||||
https://on.cypress.io/config
|
||||
Error
|
||||
[stack trace lines]
|
||||
|
||||
5) runs all tests
|
||||
nested contexts overrides throws error at the correct line number
|
||||
2nd test fails on overrides:
|
||||
CypressError: The config override passed to your test has the following validation error:
|
||||
|
||||
Expected \`defaultCommandTimeout\` to be a number. Instead the value was: \`"500"\`
|
||||
|
||||
https://on.cypress.io/config
|
||||
Error
|
||||
[stack trace lines]
|
||||
|
||||
6) runs all tests
|
||||
nested contexts
|
||||
test override
|
||||
throws error at the correct line number:
|
||||
CypressError: The config override passed to your test has the following validation error:
|
||||
|
||||
Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`"not_an_http_url"\`
|
||||
|
||||
https://on.cypress.io/config
|
||||
Error
|
||||
[stack trace lines]
|
||||
|
||||
7) runs all tests
|
||||
throws error correctly when before hook
|
||||
"before all" hook for "test config override throws error":
|
||||
CypressError: The config override passed to your test has the following validation error:
|
||||
|
||||
Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\`
|
||||
|
||||
https://on.cypress.io/config
|
||||
|
||||
Because this error occurred during a \`before all\` hook we are skipping the remaining tests in the current suite: \`throws error correctly when...\`
|
||||
Error
|
||||
[stack trace lines]
|
||||
|
||||
8) runs all tests
|
||||
throws error correctly when beforeEach hook
|
||||
test config override throws error:
|
||||
CypressError: The config override passed to your test has the following validation error:
|
||||
|
||||
Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\`
|
||||
|
||||
https://on.cypress.io/config
|
||||
Error
|
||||
[stack trace lines]
|
||||
|
||||
|
||||
|
||||
|
||||
(Results)
|
||||
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Tests: 9 │
|
||||
│ Passing: 1 │
|
||||
│ Failing: 8 │
|
||||
│ Pending: 0 │
|
||||
│ Skipped: 0 │
|
||||
│ Screenshots: 2 │
|
||||
│ Video: false │
|
||||
│ Duration: X seconds │
|
||||
│ Spec Ran: testConfigOverrides-before-invalid.js │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
||||
(Screenshots)
|
||||
|
||||
- /XXX/XXX/XXX/cypress/screenshots/testConfigOverrides-before-invalid.js/runs all (1280x720)
|
||||
tests -- inline test config override throws error (failed).png
|
||||
- /XXX/XXX/XXX/cypress/screenshots/testConfigOverrides-before-invalid.js/runs all (1280x720)
|
||||
tests -- inline test config override throws error when executed within cy cmd (f
|
||||
ailed).png
|
||||
|
||||
|
||||
====================================================================================================
|
||||
|
||||
(Run Finished)
|
||||
|
||||
|
||||
Spec Tests Passing Failing Pending Skipped
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ ✖ testConfigOverrides-before-invalid. XX:XX 9 1 8 - - │
|
||||
│ js │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
✖ 1 of 1 failed (100%) XX:XX 9 1 8 - -
|
||||
|
||||
|
||||
`
|
||||
|
||||
exports['testConfigOverrides / correctly fails when invalid config values for it.only [chrome,electron]'] = `
|
||||
|
||||
====================================================================================================
|
||||
|
||||
(Run Starting)
|
||||
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Cypress: 1.2.3 │
|
||||
│ Browser: FooBrowser 88 │
|
||||
│ Specs: 1 found (testConfigOverrides-only-invalid.js) │
|
||||
│ Searched: cypress/integration/testConfigOverrides-only-invalid.js │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
Running: testConfigOverrides-only-invalid.js (1 of 1)
|
||||
|
||||
|
||||
nested contexts
|
||||
test override
|
||||
1) throws error at the correct line number
|
||||
|
||||
|
||||
0 passing
|
||||
1 failing
|
||||
|
||||
1) nested contexts
|
||||
test override
|
||||
throws error at the correct line number:
|
||||
CypressError: The config override passed to your test has the following validation error:
|
||||
|
||||
Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\`
|
||||
|
||||
https://on.cypress.io/config
|
||||
Error
|
||||
[stack trace lines]
|
||||
|
||||
|
||||
|
||||
|
||||
(Results)
|
||||
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Tests: 1 │
|
||||
│ Passing: 0 │
|
||||
│ Failing: 1 │
|
||||
│ Pending: 0 │
|
||||
│ Skipped: 0 │
|
||||
│ Screenshots: 0 │
|
||||
│ Video: false │
|
||||
│ Duration: X seconds │
|
||||
│ Spec Ran: testConfigOverrides-only-invalid.js │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
||||
====================================================================================================
|
||||
|
||||
(Run Finished)
|
||||
|
||||
|
||||
Spec Tests Passing Failing Pending Skipped
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ ✖ testConfigOverrides-only-invalid.js XX:XX 1 - 1 - - │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
✖ 1 of 1 failed (100%) XX:XX 1 - 1 - -
|
||||
|
||||
|
||||
`
|
||||
|
||||
exports['testConfigOverrides / fails when passing invalid config values - [firefox]'] = `
|
||||
|
||||
====================================================================================================
|
||||
|
||||
(Run Starting)
|
||||
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Cypress: 1.2.3 │
|
||||
│ Browser: FooBrowser 88 │
|
||||
│ Specs: 1 found (testConfigOverrides-invalid.js) │
|
||||
│ Searched: cypress/integration/testConfigOverrides-invalid.js │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
Running: testConfigOverrides-invalid.js (1 of 1)
|
||||
|
||||
|
||||
1) inline test config override throws error
|
||||
2) inline test config override throws error when executed within cy cmd
|
||||
context config overrides throws error
|
||||
3) runs
|
||||
|
||||
nested contexts overrides throws error at the correct line number
|
||||
4) 1st test fails on overrides
|
||||
5) 2nd test fails on overrides
|
||||
|
||||
nested contexts
|
||||
test override
|
||||
6) throws error at the correct line number
|
||||
✓ does execute 2nd test
|
||||
|
||||
throws error correctly when before hook
|
||||
7) "before all" hook for "test config override throws error"
|
||||
|
||||
throws error correctly when beforeEach hook
|
||||
8) test config override throws error
|
||||
|
||||
|
||||
1 passing
|
||||
8 failing
|
||||
|
||||
1) inline test config override throws error:
|
||||
Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`""\`
|
||||
[stack trace lines]
|
||||
|
||||
2) inline test config override throws error when executed within cy cmd:
|
||||
Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`"null"\`
|
||||
[stack trace lines]
|
||||
|
||||
3) context config overrides throws error
|
||||
runs:
|
||||
CypressError: The config override passed to your test has the following validation error:
|
||||
|
||||
Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\`
|
||||
|
||||
https://on.cypress.io/config
|
||||
[stack trace lines]
|
||||
|
||||
4) nested contexts overrides throws error at the correct line number
|
||||
1st test fails on overrides:
|
||||
CypressError: The config override passed to your test has the following validation error:
|
||||
|
||||
Expected \`defaultCommandTimeout\` to be a number. Instead the value was: \`"500"\`
|
||||
|
||||
https://on.cypress.io/config
|
||||
[stack trace lines]
|
||||
|
||||
5) nested contexts overrides throws error at the correct line number
|
||||
2nd test fails on overrides:
|
||||
CypressError: The config override passed to your test has the following validation error:
|
||||
|
||||
Expected \`defaultCommandTimeout\` to be a number. Instead the value was: \`"500"\`
|
||||
|
||||
https://on.cypress.io/config
|
||||
[stack trace lines]
|
||||
|
||||
6) nested contexts
|
||||
test override
|
||||
throws error at the correct line number:
|
||||
CypressError: The config override passed to your test has the following validation error:
|
||||
|
||||
Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`"not_an_http_url"\`
|
||||
|
||||
https://on.cypress.io/config
|
||||
[stack trace lines]
|
||||
|
||||
7) throws error correctly when before hook
|
||||
"before all" hook for "test config override throws error":
|
||||
CypressError: The config override passed to your test has the following validation error:
|
||||
|
||||
Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\`
|
||||
|
||||
https://on.cypress.io/config
|
||||
|
||||
Because this error occurred during a \`before all\` hook we are skipping the remaining tests in the current suite: \`throws error correctly when...\`
|
||||
[stack trace lines]
|
||||
|
||||
8) throws error correctly when beforeEach hook
|
||||
test config override throws error:
|
||||
CypressError: The config override passed to your test has the following validation error:
|
||||
|
||||
Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\`
|
||||
|
||||
https://on.cypress.io/config
|
||||
[stack trace lines]
|
||||
|
||||
|
||||
|
||||
|
||||
(Results)
|
||||
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Tests: 9 │
|
||||
│ Passing: 1 │
|
||||
│ Failing: 8 │
|
||||
│ Pending: 0 │
|
||||
│ Skipped: 0 │
|
||||
│ Screenshots: 2 │
|
||||
│ Video: false │
|
||||
│ Duration: X seconds │
|
||||
│ Spec Ran: testConfigOverrides-invalid.js │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
||||
(Screenshots)
|
||||
|
||||
- /XXX/XXX/XXX/cypress/screenshots/testConfigOverrides-invalid.js/inline test conf (1280x720)
|
||||
ig override throws error (failed).png
|
||||
- /XXX/XXX/XXX/cypress/screenshots/testConfigOverrides-invalid.js/inline test conf (1280x720)
|
||||
ig override throws error when executed within cy cmd (failed).png
|
||||
|
||||
|
||||
====================================================================================================
|
||||
|
||||
(Run Finished)
|
||||
|
||||
|
||||
Spec Tests Passing Failing Pending Skipped
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ ✖ testConfigOverrides-invalid.js XX:XX 9 1 8 - - │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
✖ 1 of 1 failed (100%) XX:XX 9 1 8 - -
|
||||
|
||||
|
||||
`
|
||||
|
||||
exports['testConfigOverrides / fails when passing invalid config values with beforeEach - [firefox]'] = `
|
||||
|
||||
====================================================================================================
|
||||
|
||||
(Run Starting)
|
||||
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Cypress: 1.2.3 │
|
||||
│ Browser: FooBrowser 88 │
|
||||
│ Specs: 1 found (testConfigOverrides-before-invalid.js) │
|
||||
│ Searched: cypress/integration/testConfigOverrides-before-invalid.js │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
Running: testConfigOverrides-before-invalid.js (1 of 1)
|
||||
|
||||
|
||||
runs all tests
|
||||
1) inline test config override throws error
|
||||
2) inline test config override throws error when executed within cy cmd
|
||||
context config overrides throws error
|
||||
3) runs
|
||||
nested contexts overrides throws error at the correct line number
|
||||
4) 1st test fails on overrides
|
||||
5) 2nd test fails on overrides
|
||||
nested contexts
|
||||
test override
|
||||
6) throws error at the correct line number
|
||||
✓ does execute 2nd test
|
||||
throws error correctly when before hook
|
||||
7) "before all" hook for "test config override throws error"
|
||||
throws error correctly when beforeEach hook
|
||||
8) test config override throws error
|
||||
|
||||
|
||||
1 passing
|
||||
8 failing
|
||||
|
||||
1) runs all tests
|
||||
inline test config override throws error:
|
||||
Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`""\`
|
||||
[stack trace lines]
|
||||
|
||||
2) runs all tests
|
||||
inline test config override throws error when executed within cy cmd:
|
||||
Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`"null"\`
|
||||
[stack trace lines]
|
||||
|
||||
3) runs all tests
|
||||
context config overrides throws error
|
||||
runs:
|
||||
CypressError: The config override passed to your test has the following validation error:
|
||||
|
||||
Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\`
|
||||
|
||||
https://on.cypress.io/config
|
||||
[stack trace lines]
|
||||
|
||||
4) runs all tests
|
||||
nested contexts overrides throws error at the correct line number
|
||||
1st test fails on overrides:
|
||||
CypressError: The config override passed to your test has the following validation error:
|
||||
|
||||
Expected \`defaultCommandTimeout\` to be a number. Instead the value was: \`"500"\`
|
||||
|
||||
https://on.cypress.io/config
|
||||
[stack trace lines]
|
||||
|
||||
5) runs all tests
|
||||
nested contexts overrides throws error at the correct line number
|
||||
2nd test fails on overrides:
|
||||
CypressError: The config override passed to your test has the following validation error:
|
||||
|
||||
Expected \`defaultCommandTimeout\` to be a number. Instead the value was: \`"500"\`
|
||||
|
||||
https://on.cypress.io/config
|
||||
[stack trace lines]
|
||||
|
||||
6) runs all tests
|
||||
nested contexts
|
||||
test override
|
||||
throws error at the correct line number:
|
||||
CypressError: The config override passed to your test has the following validation error:
|
||||
|
||||
Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`"not_an_http_url"\`
|
||||
|
||||
https://on.cypress.io/config
|
||||
[stack trace lines]
|
||||
|
||||
7) runs all tests
|
||||
throws error correctly when before hook
|
||||
"before all" hook for "test config override throws error":
|
||||
CypressError: The config override passed to your test has the following validation error:
|
||||
|
||||
Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\`
|
||||
|
||||
https://on.cypress.io/config
|
||||
|
||||
Because this error occurred during a \`before all\` hook we are skipping the remaining tests in the current suite: \`throws error correctly when...\`
|
||||
[stack trace lines]
|
||||
|
||||
8) runs all tests
|
||||
throws error correctly when beforeEach hook
|
||||
test config override throws error:
|
||||
CypressError: The config override passed to your test has the following validation error:
|
||||
|
||||
Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\`
|
||||
|
||||
https://on.cypress.io/config
|
||||
[stack trace lines]
|
||||
|
||||
|
||||
|
||||
|
||||
(Results)
|
||||
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Tests: 9 │
|
||||
│ Passing: 1 │
|
||||
│ Failing: 8 │
|
||||
│ Pending: 0 │
|
||||
│ Skipped: 0 │
|
||||
│ Screenshots: 2 │
|
||||
│ Video: false │
|
||||
│ Duration: X seconds │
|
||||
│ Spec Ran: testConfigOverrides-before-invalid.js │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
||||
(Screenshots)
|
||||
|
||||
- /XXX/XXX/XXX/cypress/screenshots/testConfigOverrides-before-invalid.js/runs all (1280x720)
|
||||
tests -- inline test config override throws error (failed).png
|
||||
- /XXX/XXX/XXX/cypress/screenshots/testConfigOverrides-before-invalid.js/runs all (1280x720)
|
||||
tests -- inline test config override throws error when executed within cy cmd (f
|
||||
ailed).png
|
||||
|
||||
|
||||
====================================================================================================
|
||||
|
||||
(Run Finished)
|
||||
|
||||
|
||||
Spec Tests Passing Failing Pending Skipped
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ ✖ testConfigOverrides-before-invalid. XX:XX 9 1 8 - - │
|
||||
│ js │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
✖ 1 of 1 failed (100%) XX:XX 9 1 8 - -
|
||||
|
||||
|
||||
`
|
||||
|
||||
exports['testConfigOverrides / correctly fails when invalid config values for it.only [firefox]'] = `
|
||||
|
||||
====================================================================================================
|
||||
|
||||
(Run Starting)
|
||||
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Cypress: 1.2.3 │
|
||||
│ Browser: FooBrowser 88 │
|
||||
│ Specs: 1 found (testConfigOverrides-only-invalid.js) │
|
||||
│ Searched: cypress/integration/testConfigOverrides-only-invalid.js │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
Running: testConfigOverrides-only-invalid.js (1 of 1)
|
||||
|
||||
|
||||
nested contexts
|
||||
test override
|
||||
1) throws error at the correct line number
|
||||
|
||||
|
||||
0 passing
|
||||
1 failing
|
||||
|
||||
1) nested contexts
|
||||
test override
|
||||
throws error at the correct line number:
|
||||
CypressError: The config override passed to your test has the following validation error:
|
||||
|
||||
Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\`
|
||||
|
||||
https://on.cypress.io/config
|
||||
[stack trace lines]
|
||||
|
||||
|
||||
|
||||
|
||||
(Results)
|
||||
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Tests: 1 │
|
||||
│ Passing: 0 │
|
||||
│ Failing: 1 │
|
||||
│ Pending: 0 │
|
||||
│ Skipped: 0 │
|
||||
│ Screenshots: 0 │
|
||||
│ Video: false │
|
||||
│ Duration: X seconds │
|
||||
│ Spec Ran: testConfigOverrides-only-invalid.js │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
|
||||
====================================================================================================
|
||||
|
||||
(Run Finished)
|
||||
|
||||
|
||||
Spec Tests Passing Failing Pending Skipped
|
||||
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│ ✖ testConfigOverrides-only-invalid.js XX:XX 1 - 1 - - │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
✖ 1 of 1 failed (100%) XX:XX 1 - 1 - -
|
||||
|
||||
|
||||
`
|
||||
|
||||
@@ -72,7 +72,6 @@ const routeHandlers = {
|
||||
req: 'postRunInstanceRequest@2.1.0',
|
||||
resSchema: 'postRunInstanceResponse@2.1.0',
|
||||
res: (req, res) => {
|
||||
console.log(mockServerState.allSpecs.length, mockServerState.specs.length)
|
||||
const response = {
|
||||
...postRunInstanceResponse,
|
||||
spec: mockServerState.specs.shift() || null,
|
||||
@@ -80,8 +79,6 @@ const routeHandlers = {
|
||||
totalInstances: mockServerState.allSpecs.length,
|
||||
}
|
||||
|
||||
console.log('response', response)
|
||||
|
||||
return res.json(response)
|
||||
},
|
||||
},
|
||||
|
||||
@@ -17,6 +17,7 @@ const Bluebird = require('bluebird')
|
||||
const debug = require('debug')('cypress:system-tests')
|
||||
const httpsProxy = require('@packages/https-proxy')
|
||||
const Fixtures = require('./fixtures')
|
||||
|
||||
const { allowDestroy } = require(`@packages/server/lib/util/server_destroy`)
|
||||
const cypress = require(`@packages/server/lib/cypress`)
|
||||
const screenshots = require(`@packages/server/lib/screenshots`)
|
||||
@@ -588,7 +589,7 @@ const localItFn = function (title: string, opts: ItOptions) {
|
||||
throw new Error('systemTests.it(...) must be passed a title as the first argument')
|
||||
}
|
||||
|
||||
// LOGIC FOR AUTOGENERATING DYNAMIC TESTS
|
||||
// LOGIC FOR AUTO-GENERATING DYNAMIC TESTS
|
||||
// - create multiple tests for each default browser
|
||||
// - if browser is specified in options:
|
||||
// ...skip the tests for each default browser if that browser
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
describe('runs all tests', () => {
|
||||
before(() => {
|
||||
})
|
||||
|
||||
const shouldNotExecute = () => {
|
||||
throw new Error('Test Override validation should have failed & it block should not have executed.')
|
||||
}
|
||||
|
||||
it('inline test config override throws error', () => {
|
||||
Cypress.config('baseUrl', '')
|
||||
})
|
||||
|
||||
it('inline test config override throws error when executed within cy cmd', () => {
|
||||
cy.then(() => {
|
||||
Cypress.config('baseUrl', 'null')
|
||||
})
|
||||
})
|
||||
|
||||
describe('context config overrides throws error', { retries: '1' }, () => {
|
||||
it('runs', () => {
|
||||
shouldNotExecute()
|
||||
})
|
||||
})
|
||||
|
||||
describe('nested contexts overrides throws error at the correct line number', { defaultCommandTimeout: '500' }, () => {
|
||||
it('1st test fails on overrides', () => {
|
||||
shouldNotExecute()
|
||||
})
|
||||
|
||||
it('2nd test fails on overrides', () => {
|
||||
shouldNotExecute()
|
||||
})
|
||||
})
|
||||
|
||||
describe('nested contexts ', () => {
|
||||
describe('test override', () => {
|
||||
it('throws error at the correct line number', { baseUrl: 'not_an_http_url' }, () => {
|
||||
shouldNotExecute()
|
||||
})
|
||||
|
||||
it('does execute 2nd test', () => {
|
||||
expect(1).to.eq(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('throws error correctly when before hook', () => {
|
||||
before(() => {})
|
||||
it('test config override throws error', { retries: '1' }, () => {
|
||||
shouldNotExecute()
|
||||
})
|
||||
})
|
||||
|
||||
describe('throws error correctly when beforeEach hook', () => {
|
||||
beforeEach(() => {})
|
||||
it('test config override throws error', { retries: '1' }, () => {
|
||||
shouldNotExecute()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,55 @@
|
||||
const shouldNotExecute = () => {
|
||||
throw new Error('Test Override validation should have failed & it block should not have executed.')
|
||||
}
|
||||
|
||||
it('inline test config override throws error', () => {
|
||||
Cypress.config('baseUrl', '')
|
||||
})
|
||||
|
||||
it('inline test config override throws error when executed within cy cmd', () => {
|
||||
cy.then(() => {
|
||||
Cypress.config('baseUrl', 'null')
|
||||
})
|
||||
})
|
||||
|
||||
describe('context config overrides throws error', { retries: '1' }, () => {
|
||||
it('runs', () => {
|
||||
shouldNotExecute()
|
||||
})
|
||||
})
|
||||
|
||||
describe('nested contexts overrides throws error at the correct line number', { defaultCommandTimeout: '500' }, () => {
|
||||
it('1st test fails on overrides', () => {
|
||||
shouldNotExecute()
|
||||
})
|
||||
|
||||
it('2nd test fails on overrides', () => {
|
||||
shouldNotExecute()
|
||||
})
|
||||
})
|
||||
|
||||
describe('nested contexts ', () => {
|
||||
describe('test override', () => {
|
||||
it('throws error at the correct line number', { baseUrl: 'not_an_http_url' }, () => {
|
||||
shouldNotExecute()
|
||||
})
|
||||
|
||||
it('does execute 2nd test', () => {
|
||||
expect(1).to.eq(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('throws error correctly when before hook', () => {
|
||||
before(() => {})
|
||||
it('test config override throws error', { retries: '1' }, () => {
|
||||
shouldNotExecute()
|
||||
})
|
||||
})
|
||||
|
||||
describe('throws error correctly when beforeEach hook', () => {
|
||||
beforeEach(() => {})
|
||||
it('test config override throws error', { retries: '1' }, () => {
|
||||
shouldNotExecute()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,22 @@
|
||||
const shouldNotExecute = () => {
|
||||
throw new Error('Test Override validation should have failed & it block should not have executed.')
|
||||
}
|
||||
|
||||
it('first should not run', () => {
|
||||
shouldNotExecute()
|
||||
})
|
||||
|
||||
describe('second should not run', () => {
|
||||
it('test', () => {
|
||||
shouldNotExecute()
|
||||
})
|
||||
})
|
||||
|
||||
describe('nested contexts ', { retries: '1' }, () => {
|
||||
describe('test override', () => {
|
||||
// eslint-disable-next-line mocha/no-exclusive-tests
|
||||
it.only('throws error at the correct line number', { baseUrl: 'not_an_http_url' }, () => {
|
||||
shouldNotExecute()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,7 +1,7 @@
|
||||
it('has expected resolvedNodePath and resolvedNodeVersion', () => {
|
||||
expect(Cypress.config('nodeVersion')).to.eq('bundled')
|
||||
|
||||
expect(Cypress.config('resolvedNodePath')).to.be.undefined
|
||||
expect(Cypress.config('resolvedNodePath')).to.be.null
|
||||
|
||||
expect(Cypress.config('resolvedNodeVersion')).to.eq(Cypress.env('expectedNodeVersion'))
|
||||
})
|
||||
|
||||
@@ -61,7 +61,7 @@ describe('e2e record', () => {
|
||||
|
||||
// spec 1
|
||||
`POST /runs/${runId}/instances`,
|
||||
// no instances/:id/tests becuase spec failed during eval
|
||||
// no instances/:id/tests because spec failed during eval
|
||||
`POST /instances/${instanceId}/results`,
|
||||
'PUT /videos/video.mp4',
|
||||
`PUT /instances/${instanceId}/stdout`,
|
||||
|
||||
@@ -155,11 +155,13 @@ describe('e2e reporters', () => {
|
||||
},
|
||||
processEnv: {
|
||||
MOCHA_COLORS: 1,
|
||||
CI: 1,
|
||||
CIRCLECI: true,
|
||||
},
|
||||
}).then((result) => {
|
||||
expect(result.stdout.match(/passes inherited(.*)/)[1]).to.contain('\u001b[33m')
|
||||
expect(result.stdout.match(/passes quickly(.*)/)[1]).not.to.contain('\u001b[33m')
|
||||
expect(result.stdout.match(/passes slowly(.*)/)[1]).to.contain('\u001b[33m')
|
||||
expect(result.stdout.match(/passes inherited(.*)/)[1], 'when verifying "passes inherited" test time colors').to.contain('\u001b[33m')
|
||||
expect(result.stdout.match(/passes quickly(.*)/)[1], 'when verifying "passes quickly" test time colors').not.to.contain('\u001b[33m')
|
||||
expect(result.stdout.match(/passes slowly(.*)/)[1], 'when verifying "passes slowly" test time color').to.contain('\u001b[33m')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -14,7 +14,9 @@ describe('testConfigOverrides', () => {
|
||||
spec: 'testConfigOverrides-invalid-browser.js',
|
||||
snapshot: true,
|
||||
expectedExitCode: 1,
|
||||
|
||||
config: {
|
||||
video: false,
|
||||
},
|
||||
})
|
||||
|
||||
systemTests.it('has originalTitle when skip due to browser config', {
|
||||
@@ -30,4 +32,43 @@ describe('testConfigOverrides', () => {
|
||||
expect(results.runs[0].tests[0].title).deep.eq(['suite', 'has invalid testConfigOverrides'])
|
||||
},
|
||||
})
|
||||
|
||||
// window.Error throws differently for firefox. break into
|
||||
// browser permutations for snapshot comparisons
|
||||
const permutations = [
|
||||
['chrome', 'electron'],
|
||||
['firefox'],
|
||||
]
|
||||
|
||||
permutations.forEach((browserList) => {
|
||||
systemTests.it(`fails when passing invalid config values - [${browserList}]`, {
|
||||
spec: 'testConfigOverrides-invalid.js',
|
||||
snapshot: true,
|
||||
browser: browserList,
|
||||
expectedExitCode: 8,
|
||||
config: {
|
||||
video: false,
|
||||
},
|
||||
})
|
||||
|
||||
systemTests.it(`fails when passing invalid config values with beforeEach - [${browserList}]`, {
|
||||
spec: 'testConfigOverrides-before-invalid.js',
|
||||
snapshot: true,
|
||||
browser: browserList,
|
||||
expectedExitCode: 8,
|
||||
config: {
|
||||
video: false,
|
||||
},
|
||||
})
|
||||
|
||||
systemTests.it(`correctly fails when invalid config values for it.only [${browserList}]`, {
|
||||
spec: 'testConfigOverrides-only-invalid.js',
|
||||
snapshot: true,
|
||||
browser: browserList,
|
||||
expectedExitCode: 1,
|
||||
config: {
|
||||
video: false,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user