mirror of
https://github.com/cypress-io/cypress.git
synced 2026-03-13 12:59:07 -05:00
feat: pass list of browsers to plugins file (#5068)
and allow project to customize the list of browsers
This commit is contained in:
2
cli/types/index.d.ts
vendored
2
cli/types/index.d.ts
vendored
@@ -60,7 +60,7 @@ declare namespace Cypress {
|
||||
name: "electron" | "chrome" | "canary" | "chromium" | "firefox"
|
||||
displayName: "Electron" | "Chrome" | "Canary" | "Chromium" | "FireFox"
|
||||
version: string
|
||||
majorVersion: string
|
||||
majorVersion: number
|
||||
path: string
|
||||
isHeaded: boolean
|
||||
isHeadless: boolean
|
||||
|
||||
@@ -34,10 +34,11 @@
|
||||
"commandTimeout": 4000,
|
||||
"cypressHostUrl": "http://localhost:2020",
|
||||
"cypressEnv": "development",
|
||||
"env": {
|
||||
|
||||
},
|
||||
"blacklistHosts": ["www.google-analytics.com", "hotjar.com"],
|
||||
"env": {},
|
||||
"blacklistHosts": [
|
||||
"www.google-analytics.com",
|
||||
"hotjar.com"
|
||||
],
|
||||
"execTimeout": 60000,
|
||||
"fileServerFolder": "/Users/jennifer/Dev/Projects/cypress-example-kitchensink",
|
||||
"fixturesFolder": "/Users/jennifer/Dev/Projects/cypress-example-kitchensink/cypress/fixtures",
|
||||
@@ -46,9 +47,7 @@
|
||||
"integrationFolder": "/Users/jennifer/Dev/Projects/cypress-example-kitchensink/cypress/integration",
|
||||
"isHeadless": false,
|
||||
"isNewProject": false,
|
||||
"javascripts": [
|
||||
|
||||
],
|
||||
"javascripts": [],
|
||||
"morgan": true,
|
||||
"namespace": "__cypress",
|
||||
"numTestsKeptInMemory": 50,
|
||||
@@ -180,6 +179,43 @@
|
||||
"from": "config",
|
||||
"value": "http://localhost:8080"
|
||||
},
|
||||
"browsers": {
|
||||
"from": "plugins",
|
||||
"value": [
|
||||
{
|
||||
"name": "chrome",
|
||||
"displayName": "Chrome",
|
||||
"family": "chrome",
|
||||
"version": "50.0.2661.86",
|
||||
"path": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
||||
"majorVersion": "50"
|
||||
},
|
||||
{
|
||||
"name": "chromium",
|
||||
"displayName": "Chromium",
|
||||
"family": "chrome",
|
||||
"version": "49.0.2609.0",
|
||||
"path": "/Users/bmann/Downloads/chrome-mac/Chromium.app/Contents/MacOS/Chromium",
|
||||
"majorVersion": "49"
|
||||
},
|
||||
{
|
||||
"name": "canary",
|
||||
"displayName": "Canary",
|
||||
"family": "chrome",
|
||||
"version": "48.0",
|
||||
"path": "/Users/bmann/Downloads/chrome-mac/Canary.app/Contents/MacOS/Canary",
|
||||
"majorVersion": "48"
|
||||
},
|
||||
{
|
||||
"name": "electron",
|
||||
"family": "electron",
|
||||
"displayName": "Electron",
|
||||
"path": "",
|
||||
"version": "99.101.1234",
|
||||
"majorVersion": "99"
|
||||
}
|
||||
]
|
||||
},
|
||||
"commandTimeout": {
|
||||
"from": "default",
|
||||
"value": 4000
|
||||
@@ -269,7 +305,8 @@
|
||||
"blacklistHosts": {
|
||||
"from": "config",
|
||||
"value": [
|
||||
"www.google-analytics.com", "hotjar.com"
|
||||
"www.google-analytics.com",
|
||||
"hotjar.com"
|
||||
]
|
||||
},
|
||||
"hosts": {
|
||||
|
||||
@@ -77,17 +77,85 @@ describe('Settings', () => {
|
||||
cy.contains('Your project\'s configuration is displayed')
|
||||
})
|
||||
|
||||
it('displays legend in table', () => {
|
||||
cy.get('table>tbody>tr').should('have.length', 6)
|
||||
it('displays browser information which is collapsed by default', () => {
|
||||
cy.contains('.config-vars', 'browsers')
|
||||
cy.get('.config-vars').invoke('text')
|
||||
.should('not.contain', '0:Chrome')
|
||||
|
||||
cy.contains('span', 'browsers').parents('div').first().find('span').first().click()
|
||||
cy.get('.config-vars').invoke('text')
|
||||
.should('contain', '0:Chrome')
|
||||
})
|
||||
|
||||
it('wraps config line in proper classes', () => {
|
||||
cy.get('.line').first().within(() => {
|
||||
cy.contains('animationDistanceThreshold').should('have.class', 'key')
|
||||
cy.contains(':').should('have.class', 'colon')
|
||||
cy.contains('5').should('have.class', 'default')
|
||||
cy.contains(',').should('have.class', 'comma')
|
||||
})
|
||||
it('removes the summary list of values once a key is expanded', () => {
|
||||
cy.contains('span', 'browsers').parents('div').first().find('span').first().click()
|
||||
cy.get('.config-vars').invoke('text')
|
||||
.should('not.contain', 'Chrome, Chromium')
|
||||
|
||||
cy.get('.config-vars').invoke('text')
|
||||
.should('contain', '0:Chrome')
|
||||
})
|
||||
|
||||
it('distinguishes between Arrays and Objects when expanded', () => {
|
||||
cy.get('.config-vars').invoke('text')
|
||||
.should('not.contain', 'browsers: Array (4)')
|
||||
|
||||
cy.contains('span', 'browsers').parents('div').first().find('span').first().click()
|
||||
cy.get('.config-vars').invoke('text')
|
||||
.should('contain', 'browsers: Array (4)')
|
||||
})
|
||||
|
||||
it('applies the same color treatment to expanded key values as the root key', () => {
|
||||
cy.contains('span', 'browsers').parents('div').first().find('span').first().click()
|
||||
cy.get('.config-vars').as('config-vars')
|
||||
.contains('span', 'Chrome').parent('span').should('have.class', 'plugins')
|
||||
|
||||
cy.get('@config-vars')
|
||||
.contains('span', 'Chromium').parent('span').should('have.class', 'plugins')
|
||||
|
||||
cy.get('@config-vars')
|
||||
.contains('span', 'Canary').parent('span').should('have.class', 'plugins')
|
||||
|
||||
cy.get('@config-vars')
|
||||
.contains('span', 'Electron').parent('span').should('have.class', 'plugins')
|
||||
|
||||
cy.contains('span', 'blacklistHosts').parents('div').first().find('span').first().click()
|
||||
cy.get('@config-vars')
|
||||
.contains('span', 'www.google-analytics.com').parent('span').should('have.class', 'config')
|
||||
|
||||
cy.get('@config-vars')
|
||||
.contains('span', 'hotjar.com').parent('span').should('have.class', 'config')
|
||||
|
||||
cy.contains('span', 'hosts').parents('div').first().find('span').first().click()
|
||||
cy.get('@config-vars')
|
||||
.contains('span', '127.0.0.1').parent('span').should('have.class', 'config')
|
||||
|
||||
cy.get('@config-vars')
|
||||
.contains('span', '127.0.0.2').parent('span').should('have.class', 'config')
|
||||
|
||||
cy.get('@config-vars')
|
||||
.contains('span', 'Electron').parents('div').first().find('span').first().click()
|
||||
|
||||
cy.get('@config-vars').contains('span', 'electron').parents('li').eq(1).find('.line .plugins').should('have.length', 6)
|
||||
})
|
||||
|
||||
it('displays string values as quoted strings', () => {
|
||||
cy.get('.config-vars').invoke('text')
|
||||
.should('contain', 'baseUrl:"http://localhost:8080"')
|
||||
})
|
||||
|
||||
it('displays undefined and null without quotations', () => {
|
||||
cy.get('.config-vars').invoke('text')
|
||||
.should('not.contain', '"undefined"')
|
||||
.should('not.contain', '"null"')
|
||||
})
|
||||
|
||||
it('does not show the root config label', () => {
|
||||
cy.get('.config-vars').find('> ol > li > div').should('have.css', 'display', 'none')
|
||||
})
|
||||
|
||||
it('displays legend in table', () => {
|
||||
cy.get('table>tbody>tr').should('have.length', 6)
|
||||
})
|
||||
|
||||
it('displays "true" values', () => {
|
||||
@@ -99,26 +167,13 @@ describe('Settings', () => {
|
||||
})
|
||||
|
||||
it('displays "object" values for env and hosts', () => {
|
||||
cy.get('.nested-obj').eq(0)
|
||||
.contains('fixturesFolder')
|
||||
cy.get('.line').contains('www.google-analytics.com, hotjar.com')
|
||||
|
||||
cy.get('.nested-obj').eq(1)
|
||||
.contains('*.foobar.com')
|
||||
cy.get('.line').contains('*.foobar.com, *.bazqux.com')
|
||||
})
|
||||
|
||||
it('displays "array" values for blacklistHosts', () => {
|
||||
cy.get('.nested-arr')
|
||||
.parent()
|
||||
.should('contain', '[')
|
||||
.and('contain', ']')
|
||||
.and('not.contain', '0')
|
||||
.and('not.contain', '1')
|
||||
.find('.line .config').should(($lines) => {
|
||||
expect($lines).to.have.length(2)
|
||||
expect($lines).to.contain('www.google-analytics.com')
|
||||
|
||||
expect($lines).to.contain('hotjar.com')
|
||||
})
|
||||
cy.contains('.line', 'blacklistHosts').contains('www.google-analytics.com, hotjar.com')
|
||||
})
|
||||
|
||||
it('opens help link on click', () => {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"watch": "npm run build -- --watch --progress"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/polyfill": "^7.7.0",
|
||||
"@cypress/icons": "0.7.0",
|
||||
"@cypress/json-schemas": "5.33.0",
|
||||
"@cypress/react-tooltip": "0.5.3",
|
||||
@@ -40,6 +41,7 @@
|
||||
"react": "16.8.6",
|
||||
"react-bootstrap-modal": "4.2.0",
|
||||
"react-dom": "16.8.6",
|
||||
"react-inspector": "^4.0.0",
|
||||
"react-loader": "2.4.5",
|
||||
"webpack": "4.35.3",
|
||||
"webpack-cli": "3.3.2"
|
||||
|
||||
@@ -1,115 +1,105 @@
|
||||
import _ from 'lodash'
|
||||
import cn from 'classnames'
|
||||
import { observer } from 'mobx-react'
|
||||
import React from 'react'
|
||||
import Tooltip from '@cypress/react-tooltip'
|
||||
import { ObjectInspector, ObjectName } from 'react-inspector'
|
||||
|
||||
import { configFileFormatted } from '../lib/config-file-formatted'
|
||||
import ipc from '../lib/ipc'
|
||||
|
||||
const display = (obj) => {
|
||||
const keys = _.keys(obj)
|
||||
const lastKey = _.last(keys)
|
||||
|
||||
return _.map(obj, (value, key) => {
|
||||
const hasComma = lastKey !== key
|
||||
|
||||
if (value.from == null) {
|
||||
return displayNestedObj(key, value, hasComma)
|
||||
}
|
||||
|
||||
if (value.isArray) {
|
||||
return getSpan(key, value, hasComma, true)
|
||||
}
|
||||
|
||||
if (_.isObject(value.value)) {
|
||||
const realValue = value.value.toJS ? value.value.toJS() : value.value
|
||||
|
||||
if (_.isArray(realValue)) {
|
||||
return displayArray(key, value, hasComma)
|
||||
const formatData = (data) => {
|
||||
if (Array.isArray(data)) {
|
||||
return _.map(data, (v) => {
|
||||
if (_.isObject(v) && (v.name || v.displayName)) {
|
||||
return _.defaultTo(v.displayName, v.name)
|
||||
}
|
||||
|
||||
return displayObject(key, value, hasComma)
|
||||
}
|
||||
return String(v)
|
||||
}).join(', ')
|
||||
}
|
||||
|
||||
return getSpan(key, value, hasComma)
|
||||
})
|
||||
if (_.isObject(data)) {
|
||||
return _.defaultTo(_.defaultTo(data.displayName, data.name), String(Object.keys(data).join(', ')))
|
||||
}
|
||||
|
||||
const excludedFromQuotations = ['null', 'undefined']
|
||||
|
||||
if (_.isString(data) && !excludedFromQuotations.includes(data)) {
|
||||
return `"${data}"`
|
||||
}
|
||||
|
||||
return String(data)
|
||||
}
|
||||
const ObjectLabel = ({ name, data, expanded, from, isNonenumerable }) => {
|
||||
const formattedData = formatData(data)
|
||||
|
||||
const displayNestedObj = (key, value, hasComma) => (
|
||||
<span key={key}>
|
||||
<span className='nested nested-obj'>
|
||||
<span className='key'>{key}</span>
|
||||
<span className='colon'>:</span>{' '}
|
||||
{'{'}
|
||||
{display(value)}
|
||||
</span>
|
||||
<span className='line'>{'}'}{getComma(hasComma)}</span>
|
||||
<br />
|
||||
</span>
|
||||
)
|
||||
|
||||
const displayNestedArr = (key, value, hasComma) => (
|
||||
<span key={key}>
|
||||
<span className='nested nested-arr'>
|
||||
<span className='key'>{key}</span>
|
||||
<span className='colon'>:</span>{' '}
|
||||
{'['}
|
||||
{display(value)}
|
||||
</span>
|
||||
<span className='line'>{']'}{getComma(hasComma)}</span>
|
||||
<br />
|
||||
</span>
|
||||
)
|
||||
|
||||
const displayArray = (key, nestedArr, hasComma) => {
|
||||
const arr = _.map(nestedArr.value, (value) => {
|
||||
return { value, from: nestedArr.from, isArray: true }
|
||||
})
|
||||
|
||||
return displayNestedArr(key, arr, hasComma)
|
||||
}
|
||||
|
||||
const displayObject = (key, nestedObj, hasComma) => {
|
||||
const obj = _.reduce(nestedObj.value, (obj, value, key) => {
|
||||
return _.extend(obj, {
|
||||
[key]: { value, from: nestedObj.from },
|
||||
})
|
||||
}, {})
|
||||
|
||||
return displayNestedObj(key, obj, hasComma)
|
||||
}
|
||||
|
||||
const getSpan = (key, obj, hasComma, isArray) => {
|
||||
return (
|
||||
<div key={key} className='line'>
|
||||
{getKey(key, isArray)}
|
||||
{getColon(isArray)}
|
||||
<Tooltip title={obj.from || ''} placement='right' className='cy-tooltip'>
|
||||
<span className={obj.from}>
|
||||
{getString(obj.value)}
|
||||
{`${obj.value}`}
|
||||
{getString(obj.value)}
|
||||
</span>
|
||||
</Tooltip>
|
||||
{getComma(hasComma)}
|
||||
</div>
|
||||
<span className="line" key={name}>
|
||||
<ObjectName name={name} dimmed={isNonenumerable} />
|
||||
<span>:</span>
|
||||
{!expanded && (
|
||||
<>
|
||||
<Tooltip title={from} placement='right' className='cy-tooltip'>
|
||||
<span className={cn(from, 'key-value-pair-value')}>
|
||||
<span>{formattedData}</span>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
{expanded && Array.isArray(data) && (
|
||||
<span> Array ({data.length})</span>
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const getKey = (key, isArray) => {
|
||||
return isArray ? '' : <span className='key'>{key}</span>
|
||||
ObjectLabel.defaultProps = {
|
||||
data: 'undefined',
|
||||
}
|
||||
|
||||
const getColon = (isArray) => {
|
||||
return isArray ? '' : <span className="colon">:{' '}</span>
|
||||
const createComputeFromValue = (obj) => {
|
||||
return (name, path) => {
|
||||
const pathParts = path.split('.')
|
||||
const pathDepth = pathParts.length
|
||||
|
||||
const rootKey = pathDepth <= 2 ? name : pathParts[1]
|
||||
|
||||
return obj[rootKey] ? obj[rootKey].from : undefined
|
||||
}
|
||||
}
|
||||
|
||||
const getString = (val) => {
|
||||
return _.isString(val) ? '\'' : ''
|
||||
}
|
||||
const ConfigDisplay = ({ data: obj }) => {
|
||||
const computeFromValue = createComputeFromValue(obj)
|
||||
const renderNode = ({ depth, name, data, isNonenumerable, expanded, path }) => {
|
||||
if (depth === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const getComma = (hasComma) => {
|
||||
return hasComma ? <span className='comma'>,</span> : ''
|
||||
const from = computeFromValue(name, path)
|
||||
|
||||
return (
|
||||
<ObjectLabel
|
||||
name={name}
|
||||
data={data}
|
||||
expanded={expanded}
|
||||
from={from}
|
||||
isNonenumerable={isNonenumerable}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const data = _.reduce(obj, (acc, value, key) => Object.assign(acc, {
|
||||
[key]: value.value,
|
||||
}), {})
|
||||
|
||||
return (
|
||||
<div className="config-vars">
|
||||
<span>{'{'}</span>
|
||||
<ObjectInspector data={data} expandLevel={1} nodeRenderer={renderNode} />
|
||||
<span>{'}'}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const openHelp = (e) => {
|
||||
@@ -146,16 +136,12 @@ const Configuration = observer(({ project }) => (
|
||||
<td>set from CLI arguments</td>
|
||||
</tr>
|
||||
<tr className='config-keys'>
|
||||
<td><span className='plugin'>plugin</span></td>
|
||||
<td><span className='plugins'>plugin</span></td>
|
||||
<td>set from plugin file</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<pre className='config-vars'>
|
||||
{'{'}
|
||||
{display(project.resolvedConfig)}
|
||||
{'}'}
|
||||
</pre>
|
||||
<ConfigDisplay data={project.resolvedConfig} />
|
||||
</div>
|
||||
))
|
||||
|
||||
|
||||
@@ -68,45 +68,40 @@
|
||||
font-size: 13px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 3px;
|
||||
background-color: #252831;
|
||||
background: #fff;
|
||||
color: #eee;
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
padding: 8px 12px;
|
||||
font-family: $font-mono;
|
||||
> span {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.envFile:hover, .env:hover, .config:hover, .cli:hover, .plugin:hover, .default:hover {
|
||||
ol[role="tree"] {
|
||||
> li > div{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.key-value-pair-value {
|
||||
margin-left: 2px;
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
padding: 2px;
|
||||
border-bottom: 1px solid transparent
|
||||
}
|
||||
|
||||
.key-value-pair-value:hover {
|
||||
border-bottom: 1px dotted #777;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.key {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.comma, .colon, {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.line {
|
||||
margin-left: 15px;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
.nested {
|
||||
margin-left: 15px;
|
||||
|
||||
.line {
|
||||
margin-left: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.envFile, .env, .config, .cli, .plugin, .default {
|
||||
.envFile, .env, .config, .cli, .plugins, .default {
|
||||
font-family: $font-mono;
|
||||
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
@@ -130,7 +125,7 @@
|
||||
color: #A21313;
|
||||
}
|
||||
|
||||
.plugin {
|
||||
.plugins {
|
||||
background-color: #f0e7fc;
|
||||
color: #134aa2;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import path from 'path'
|
||||
const config: typeof commonConfig = {
|
||||
...commonConfig,
|
||||
entry: {
|
||||
app: [path.resolve(__dirname, 'src/main')],
|
||||
app: [require.resolve('@babel/polyfill'), path.resolve(__dirname, 'src/main')],
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
|
||||
@@ -15,7 +15,14 @@ import {
|
||||
} from './types'
|
||||
import * as windowsHelper from './windows'
|
||||
|
||||
const setMajorVersion = (browser: FoundBrowser) => {
|
||||
type HasVersion = {
|
||||
version?: string
|
||||
majorVersion?: string | number
|
||||
name: string
|
||||
}
|
||||
|
||||
// TODO: make this function NOT change its argument
|
||||
export const setMajorVersion = <T extends HasVersion>(browser: T): T => {
|
||||
if (browser.version) {
|
||||
browser.majorVersion = browser.version.split('.')[0]
|
||||
log(
|
||||
@@ -24,6 +31,10 @@ const setMajorVersion = (browser: FoundBrowser) => {
|
||||
browser.version,
|
||||
browser.majorVersion
|
||||
)
|
||||
|
||||
if (browser.majorVersion) {
|
||||
browser.majorVersion = parseInt(browser.majorVersion)
|
||||
}
|
||||
}
|
||||
|
||||
return browser
|
||||
@@ -153,14 +164,18 @@ export const detectByPath = (
|
||||
|
||||
const regexExec = browser.versionRegex.exec(stdout) as Array<string>
|
||||
|
||||
return extend({}, browser, {
|
||||
const parsedBrowser = {
|
||||
name: browser.name,
|
||||
displayName: `Custom ${browser.displayName}`,
|
||||
info: `Loaded from ${path}`,
|
||||
custom: true,
|
||||
path,
|
||||
version: regexExec[1],
|
||||
majorVersion: regexExec[1].split('.', 2)[0],
|
||||
})
|
||||
}
|
||||
|
||||
setMajorVersion(parsedBrowser)
|
||||
|
||||
return extend({}, browser, parsedBrowser)
|
||||
}
|
||||
|
||||
return helper
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { detect } from '../../lib/detect'
|
||||
require('../spec_helper')
|
||||
import { detect, setMajorVersion } from '../../lib/detect'
|
||||
const os = require('os')
|
||||
import { log } from '../log'
|
||||
import { project } from 'ramda'
|
||||
@@ -32,4 +33,14 @@ describe('browser detection', () => {
|
||||
it('detects available browsers', () => {
|
||||
return detect().then(checkBrowsers)
|
||||
})
|
||||
|
||||
context('setMajorVersion', () => {
|
||||
const foundBrowser = {
|
||||
name: 'test browser',
|
||||
version: '11.22.33',
|
||||
}
|
||||
|
||||
setMajorVersion(foundBrowser)
|
||||
expect(foundBrowser.majorVersion, 'major version was converted to number').to.equal(11)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -74,14 +74,14 @@ describe('linux browser detection', () => {
|
||||
name: 'test-browser-name',
|
||||
version: '100.1.2.3',
|
||||
path: 'test-browser',
|
||||
majorVersion: '100',
|
||||
majorVersion: 100,
|
||||
},
|
||||
{
|
||||
displayName: 'Foo Browser',
|
||||
name: 'foo-browser',
|
||||
version: '100.1.2.3',
|
||||
path: 'foo-browser',
|
||||
majorVersion: '100',
|
||||
majorVersion: 100,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -107,7 +107,7 @@ describe('linux browser detection', () => {
|
||||
name: 'foo-browser',
|
||||
version: '100.1.2.3',
|
||||
path: 'foo-browser',
|
||||
majorVersion: '100',
|
||||
majorVersion: 100,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -130,7 +130,7 @@ describe('linux browser detection', () => {
|
||||
info: 'Loaded from /foo/bar/browser',
|
||||
custom: true,
|
||||
version: '9001.1.2.3',
|
||||
majorVersion: '9001',
|
||||
majorVersion: 9001,
|
||||
path: '/foo/bar/browser',
|
||||
})
|
||||
)
|
||||
@@ -168,7 +168,7 @@ describe('linux browser detection', () => {
|
||||
info: 'Loaded from /Applications/My Shiny New Browser.app',
|
||||
custom: true,
|
||||
version: '100.1.2.3',
|
||||
majorVersion: '100',
|
||||
majorVersion: 100,
|
||||
path: '/Applications/My Shiny New Browser.app',
|
||||
})
|
||||
)
|
||||
|
||||
@@ -73,4 +73,6 @@ To run an individual e2e test:
|
||||
npm run test-e2e -- --spec base_url
|
||||
```
|
||||
|
||||
To update snapshots, see `snap-shot-it` instructions: https://github.com/bahmutov/snap-shot-it#advanced-use
|
||||
When running e2e tests, some test projects output verbose logs. To see them run the test with `DEBUG=cypress:e2e` environment variable.
|
||||
|
||||
To update snapshots, see `snap-shot-it` instructions: https://github.com/bahmutov/snap-shot-it#advanced-use
|
||||
|
||||
@@ -138,3 +138,17 @@ exports['e2e config fails 1'] = `
|
||||
|
||||
|
||||
`
|
||||
|
||||
exports['e2e config catches invalid viewportWidth in the configuration file 1'] = `
|
||||
We found an invalid value in the file: \`cypress.json\`
|
||||
|
||||
Expected \`viewportWidth\` to be a number. Instead the value was: \`"foo"\`
|
||||
|
||||
`
|
||||
|
||||
exports['e2e config catches invalid browser in the configuration file 1'] = `
|
||||
We found an invalid value in the file: \`cypress.json\`
|
||||
|
||||
Found an error while validating the \`browsers\` list. Expected \`family\` to be either electron, chrome or firefox. Instead the value was: \`{"name":"bad browser","family":"unknown family","displayName":"Bad browser","version":"no version","path":"/path/to","majorVersion":123}\`
|
||||
|
||||
`
|
||||
|
||||
@@ -372,3 +372,33 @@ exports['e2e plugins calls after:screenshot for cy.screenshot() and failure scre
|
||||
|
||||
|
||||
`
|
||||
|
||||
exports['e2e plugins can filter browsers from config 1'] = `
|
||||
Can't run because you've entered an invalid browser name.
|
||||
|
||||
Browser: 'chrome' was not found on your system.
|
||||
|
||||
Available browsers found are: electron
|
||||
|
||||
`
|
||||
|
||||
exports['e2e plugins catches invalid viewportWidth returned from plugins 1'] = `
|
||||
An invalid configuration value returned from the plugins file: \`cypress/plugins/index.coffee\`
|
||||
|
||||
Expected \`viewportWidth\` to be a number. Instead the value was: \`"foo"\`
|
||||
|
||||
`
|
||||
|
||||
exports['e2e plugins catches invalid browsers list returned from plugins 1'] = `
|
||||
An invalid configuration value returned from the plugins file: \`cypress/plugins/index.coffee\`
|
||||
|
||||
Expected at list one browser
|
||||
|
||||
`
|
||||
|
||||
exports['e2e plugins catches invalid browser returned from plugins 1'] = `
|
||||
An invalid configuration value returned from the plugins file: \`cypress/plugins/index.coffee\`
|
||||
|
||||
Found an error while validating the \`browsers\` list. Expected \`displayName\` to be a non-empty string. Instead the value was: \`{"name":"browser name","family":"chrome"}\`
|
||||
|
||||
`
|
||||
|
||||
@@ -21,3 +21,69 @@ Expected \`test\` to be one of these values: 1, 2, 3. Instead the value was: \`"
|
||||
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": "chrome",
|
||||
"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": "electron",
|
||||
"path": "",
|
||||
"version": "99.101.3",
|
||||
"majorVersion": 99
|
||||
},
|
||||
"expect": true
|
||||
},
|
||||
{
|
||||
"given": {
|
||||
"name": "No display name",
|
||||
"family": "electron"
|
||||
},
|
||||
"expect": "Expected `displayName` to be a non-empty string. Instead the value was: `{\"name\":\"No display name\",\"family\":\"electron\"}`"
|
||||
},
|
||||
{
|
||||
"given": {
|
||||
"name": "bad family",
|
||||
"displayName": "Bad family browser",
|
||||
"family": "unknown family"
|
||||
},
|
||||
"expect": "Expected `family` to be either electron, chrome 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 list 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"\`
|
||||
`
|
||||
|
||||
@@ -2,6 +2,7 @@ _ = require("lodash")
|
||||
path = require("path")
|
||||
Promise = require("bluebird")
|
||||
debug = require("debug")("cypress:server:browsers")
|
||||
pluralize = require("pluralize")
|
||||
utils = require("./utils")
|
||||
errors = require("../errors")
|
||||
fs = require("../util/fs")
|
||||
@@ -36,6 +37,7 @@ cleanup = ->
|
||||
instance = null
|
||||
|
||||
getBrowserLauncherByFamily = (family) ->
|
||||
debug("getBrowserLauncherByFamily %o", { family })
|
||||
if not isBrowserFamily(family)
|
||||
debug("unknown browser family", family)
|
||||
|
||||
@@ -48,9 +50,13 @@ getBrowserLauncherByFamily = (family) ->
|
||||
isValidPathToBrowser = (str) ->
|
||||
path.basename(str) isnt str
|
||||
|
||||
ensureAndGetByNameOrPath = (nameOrPath, returnAll = false) ->
|
||||
utils.getBrowsers(nameOrPath)
|
||||
ensureAndGetByNameOrPath = (nameOrPath, returnAll = false, browsers = null) ->
|
||||
findBrowsers = if Array.isArray(browsers) then Promise.resolve(browsers) else utils.getBrowsers()
|
||||
|
||||
findBrowsers
|
||||
.then (browsers = []) ->
|
||||
debug("searching for browser %o", { nameOrPath, knownBrowsers: browsers })
|
||||
|
||||
## try to find the browser by name with the highest version property
|
||||
sortedBrowsers = _.sortBy(browsers, ['version'])
|
||||
if browser = _.findLast(sortedBrowsers, { name: nameOrPath })
|
||||
@@ -93,6 +99,7 @@ module.exports = {
|
||||
close: kill
|
||||
|
||||
getAllBrowsersWith: (nameOrPath) ->
|
||||
debug("getAllBrowsersWith %o", { nameOrPath })
|
||||
if nameOrPath
|
||||
return ensureAndGetByNameOrPath(nameOrPath, true)
|
||||
utils.getBrowsers()
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
path = require("path")
|
||||
debug = require("debug")("cypress:server:browsers:utils")
|
||||
Promise = require("bluebird")
|
||||
getPort = require("get-port")
|
||||
launcher = require("@packages/launcher")
|
||||
fs = require("../util/fs")
|
||||
appData = require("../util/app_data")
|
||||
pluralize = require("pluralize")
|
||||
profileCleaner = require("../util/profile_cleaner")
|
||||
|
||||
PATH_TO_BROWSERS = appData.path("browsers")
|
||||
@@ -83,20 +85,24 @@ module.exports = {
|
||||
launch: launcher.launch
|
||||
|
||||
getBrowsers: ->
|
||||
## TODO: accept an options object which
|
||||
## turns off getting electron browser?
|
||||
debug("getBrowsers")
|
||||
launcher.detect()
|
||||
.then (browsers = []) ->
|
||||
version = process.versions.chrome or ""
|
||||
debug("found browsers %o", { browsers })
|
||||
|
||||
## the internal version of Electron, which won't be detected by `launcher`
|
||||
browsers.concat({
|
||||
version = process.versions.chrome or ""
|
||||
majorVersion = parseInt(version.split(".")[0]) if version
|
||||
electronBrowser = {
|
||||
name: "electron"
|
||||
family: "electron"
|
||||
displayName: "Electron"
|
||||
version: version
|
||||
path: ""
|
||||
majorVersion: version.split(".")[0]
|
||||
majorVersion: majorVersion
|
||||
info: "Electron is the default browser that comes with Cypress. This is the browser that runs in headless mode. Selecting this browser is useful when debugging. The version number indicates the underlying Chromium version that Electron uses."
|
||||
})
|
||||
}
|
||||
|
||||
## the internal version of Electron, which won't be detected by `launcher`
|
||||
debug("adding Electron browser with version %s", version)
|
||||
browsers.concat(electronBrowser)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
_ = require("lodash")
|
||||
R = require("ramda")
|
||||
la = require("lazy-ass")
|
||||
path = require("path")
|
||||
check = require("check-more-types")
|
||||
Promise = require("bluebird")
|
||||
deepDiff = require("return-deep-diff")
|
||||
errors = require("./errors")
|
||||
scaffold = require("./scaffold")
|
||||
findSystemNode = require("./util/find_system_node")
|
||||
fs = require("./util/fs")
|
||||
keys = require("./util/keys")
|
||||
origin = require("./util/origin")
|
||||
@@ -13,6 +15,7 @@ settings = require("./util/settings")
|
||||
v = require("./util/validation")
|
||||
debug = require("debug")("cypress:server:config")
|
||||
pathHelpers = require("./util/path_helpers")
|
||||
findSystemNode = require("./util/find_system_node")
|
||||
|
||||
CYPRESS_ENV_PREFIX = "CYPRESS_"
|
||||
CYPRESS_ENV_PREFIX_LENGTH = "CYPRESS_".length
|
||||
@@ -47,6 +50,7 @@ folders = toWords """
|
||||
videosFolder
|
||||
"""
|
||||
|
||||
# Public configuration properties, like "cypress.json" fields
|
||||
configKeys = toWords """
|
||||
animationDistanceThreshold fileServerFolder
|
||||
baseUrl fixturesFolder
|
||||
@@ -74,17 +78,25 @@ configKeys = toWords """
|
||||
nodeVersion resolvedNodePath
|
||||
"""
|
||||
|
||||
# Deprecated and retired public configuration properties
|
||||
breakingConfigKeys = toWords """
|
||||
videoRecording
|
||||
screenshotOnHeadlessFailure
|
||||
trashAssetsBeforeHeadlessRuns
|
||||
"""
|
||||
|
||||
# Internal configuration properties the user should be able to overwrite
|
||||
systemConfigKeys = toWords """
|
||||
browsers
|
||||
"""
|
||||
|
||||
CONFIG_DEFAULTS = {
|
||||
port: null
|
||||
hosts: null
|
||||
morgan: true
|
||||
baseUrl: null
|
||||
# will be replaced by detected list of browsers
|
||||
browsers: []
|
||||
socketId: null
|
||||
projectId: null
|
||||
userAgent: null
|
||||
@@ -137,7 +149,7 @@ validationRules = {
|
||||
animationDistanceThreshold: v.isNumber
|
||||
baseUrl: v.isFullyQualifiedUrl
|
||||
blacklistHosts: v.isStringOrArrayOfStrings
|
||||
modifyObstructiveCode: v.isBoolean
|
||||
browsers: v.isValidBrowserList
|
||||
chromeWebSecurity: v.isBoolean
|
||||
configFile: v.isStringOrFalse
|
||||
defaultCommandTimeout: v.isNumber
|
||||
@@ -147,6 +159,8 @@ validationRules = {
|
||||
fixturesFolder: v.isStringOrFalse
|
||||
ignoreTestFiles: v.isStringOrArrayOfStrings
|
||||
integrationFolder: v.isString
|
||||
modifyObstructiveCode: v.isBoolean
|
||||
nodeVersion: v.isOneOf("default", "bundled", "system")
|
||||
numTestsKeptInMemory: v.isNumber
|
||||
pageLoadTimeout: v.isNumber
|
||||
pluginsFile: v.isStringOrFalse
|
||||
@@ -154,20 +168,19 @@ validationRules = {
|
||||
reporter: v.isString
|
||||
requestTimeout: v.isNumber
|
||||
responseTimeout: v.isNumber
|
||||
testFiles: v.isStringOrArrayOfStrings
|
||||
supportFile: v.isStringOrFalse
|
||||
taskTimeout: v.isNumber
|
||||
testFiles: v.isStringOrArrayOfStrings
|
||||
trashAssetsBeforeRuns: v.isBoolean
|
||||
userAgent: v.isString
|
||||
videoCompression: v.isNumberOrFalse
|
||||
video: v.isBoolean
|
||||
videoUploadOnPasses: v.isBoolean
|
||||
videoCompression: v.isNumberOrFalse
|
||||
videosFolder: v.isString
|
||||
videoUploadOnPasses: v.isBoolean
|
||||
viewportHeight: v.isNumber
|
||||
viewportWidth: v.isNumber
|
||||
waitForAnimations: v.isBoolean
|
||||
watchForFileChanges: v.isBoolean
|
||||
nodeVersion: v.isOneOf("default", "bundled", "system")
|
||||
}
|
||||
|
||||
convertRelativeToAbsolutePaths = (projectRoot, obj, defaults = {}) ->
|
||||
@@ -219,7 +232,8 @@ module.exports = {
|
||||
_.includes(names, value)
|
||||
|
||||
whitelist: (obj = {}) ->
|
||||
_.pick(obj, configKeys.concat(breakingConfigKeys))
|
||||
propertyNames = configKeys.concat(breakingConfigKeys).concat(systemConfigKeys)
|
||||
_.pick(obj, propertyNames)
|
||||
|
||||
get: (projectRoot, options = {}) ->
|
||||
Promise.all([
|
||||
@@ -236,12 +250,14 @@ module.exports = {
|
||||
})
|
||||
|
||||
set: (obj = {}) ->
|
||||
debug("setting config object")
|
||||
{projectRoot, projectName, config, envFile, options} = obj
|
||||
|
||||
## just force config to be an object
|
||||
## so we dont have to do as much
|
||||
## work in our tests
|
||||
config ?= {}
|
||||
debug("config is %o", config)
|
||||
|
||||
## flatten the object's properties
|
||||
## into the master config object
|
||||
@@ -255,10 +271,12 @@ module.exports = {
|
||||
resolved = {}
|
||||
|
||||
_.extend config, _.pick(options, "configFile", "morgan", "isTextTerminal", "socketId", "report", "browsers")
|
||||
debug("merged config with options, got %o", config)
|
||||
|
||||
_
|
||||
.chain(@whitelist(options))
|
||||
.omit("env")
|
||||
.omit("browsers")
|
||||
.each (val, key) ->
|
||||
resolved[key] = "cli"
|
||||
config[key] = val
|
||||
@@ -319,40 +337,93 @@ module.exports = {
|
||||
obj = _.clone(config)
|
||||
|
||||
obj.resolved = @resolveConfigValues(config, defaults, resolved)
|
||||
debug("resolved config is %o", obj.resolved.browsers)
|
||||
|
||||
return obj
|
||||
|
||||
# Given an object "resolvedObj" and a list of overrides in "obj"
|
||||
# marks all properties from "obj" inside "resolvedObj" using
|
||||
# {value: obj.val, from: "plugin"}
|
||||
setPluginResolvedOn: (resolvedObj, obj) ->
|
||||
_.each obj, (val, key) =>
|
||||
if _.isObject(val) && !_.isArray(val)
|
||||
## recurse setting overrides
|
||||
## inside of this nested objected
|
||||
@setPluginResolvedOn(resolvedObj[key], val)
|
||||
else
|
||||
## override the resolved value
|
||||
resolvedObj[key] = {
|
||||
value: val
|
||||
from: "plugin"
|
||||
}
|
||||
|
||||
updateWithPluginValues: (cfg, overrides = {}) ->
|
||||
## diff the overrides with cfg
|
||||
## including nested objects (env)
|
||||
diffs = deepDiff(cfg, overrides, true)
|
||||
debug("starting config %o", cfg)
|
||||
debug("overrides %o", overrides)
|
||||
|
||||
setResolvedOn = (resolvedObj, obj) ->
|
||||
_.each obj, (val, key) ->
|
||||
if _.isObject(val) && !_.isArray(val)
|
||||
## recurse setting overrides
|
||||
## inside of this nested objected
|
||||
setResolvedOn(resolvedObj[key], val)
|
||||
else
|
||||
## override the resolved value
|
||||
resolvedObj[key] = {
|
||||
value: val
|
||||
from: "plugin"
|
||||
}
|
||||
# make sure every option returned from the plugins file
|
||||
# passes our validation functions
|
||||
validate overrides, (errMsg) ->
|
||||
if cfg.pluginsFile and cfg.projectRoot
|
||||
relativePluginsPath = path.relative(cfg.projectRoot, cfg.pluginsFile)
|
||||
errors.throw("PLUGINS_CONFIG_VALIDATION_ERROR", relativePluginsPath, errMsg)
|
||||
else
|
||||
errors.throw("CONFIG_VALIDATION_ERROR", errMsg)
|
||||
|
||||
originalResolvedBrowsers = cfg && cfg.resolved && cfg.resolved.browsers && R.clone(cfg.resolved.browsers)
|
||||
if not originalResolvedBrowsers
|
||||
# have something to resolve with if plugins return nothing
|
||||
originalResolvedBrowsers = {
|
||||
value: cfg.browsers
|
||||
from: "default"
|
||||
}
|
||||
|
||||
diffs = deepDiff(cfg, overrides, true)
|
||||
debug("config diffs %o", diffs)
|
||||
|
||||
userBrowserList = diffs && diffs.browsers && R.clone(diffs.browsers)
|
||||
if userBrowserList
|
||||
debug("user browser list %o", userBrowserList)
|
||||
|
||||
## for each override go through
|
||||
## and change the resolved values of cfg
|
||||
## to point to the plugin
|
||||
setResolvedOn(cfg.resolved, diffs)
|
||||
if diffs
|
||||
@setPluginResolvedOn(cfg.resolved, diffs)
|
||||
debug("resolved config object %o", cfg.resolved)
|
||||
|
||||
## merge cfg into overrides
|
||||
_.defaultsDeep(diffs, cfg)
|
||||
merged = _.defaultsDeep(diffs, cfg)
|
||||
debug("merged config object %o", merged)
|
||||
|
||||
# the above _.defaultsDeep combines arrays,
|
||||
# if diffs.browsers = [1] and cfg.browsers = [1, 2]
|
||||
# then the merged result merged.browsers = [1, 2]
|
||||
# which is NOT what we want
|
||||
if Array.isArray(userBrowserList) and userBrowserList.length
|
||||
merged.browsers = userBrowserList
|
||||
merged.resolved.browsers.value = userBrowserList
|
||||
|
||||
if overrides.browsers == null
|
||||
# null breaks everything when merging lists
|
||||
debug("replacing null browsers with original list %o", originalResolvedBrowsers)
|
||||
merged.browsers = cfg.browsers
|
||||
if originalResolvedBrowsers
|
||||
merged.resolved.browsers = originalResolvedBrowsers
|
||||
|
||||
debug("merged plugins config %o", merged)
|
||||
return merged
|
||||
|
||||
# combines the default configuration object with values specified in the
|
||||
# configuration file like "cypress.json". Values in configuration file
|
||||
# overwrite the defaults.
|
||||
resolveConfigValues: (config, defaults, resolved = {}) ->
|
||||
## pick out only the keys found in configKeys
|
||||
## pick out only known configuration keys
|
||||
_
|
||||
.chain(config)
|
||||
.pick(configKeys)
|
||||
.pick(configKeys.concat(systemConfigKeys))
|
||||
.mapValues (val, key) ->
|
||||
source = (s) ->
|
||||
{
|
||||
@@ -366,10 +437,12 @@ module.exports = {
|
||||
r
|
||||
else
|
||||
source(r)
|
||||
when not _.isEqual(config[key], defaults[key])
|
||||
source("config")
|
||||
else
|
||||
# "browsers" list is special, since it is dynamic by default
|
||||
# and can only be ovewritten via plugins file
|
||||
when _.isEqual(config[key], defaults[key]) or key == "browsers"
|
||||
source("default")
|
||||
else
|
||||
source("config")
|
||||
.value()
|
||||
|
||||
# instead of the built-in Node process, specify a path to 3rd party Node
|
||||
|
||||
@@ -605,6 +605,7 @@ getMsgByType = (type, arg1 = {}, arg2) ->
|
||||
|
||||
Fix the error in your code and re-run your tests.
|
||||
"""
|
||||
# happens when there is an error in configuration file like "cypress.json"
|
||||
when "SETTINGS_VALIDATION_ERROR"
|
||||
filePath = "`#{arg1}`"
|
||||
"""
|
||||
@@ -612,6 +613,16 @@ getMsgByType = (type, arg1 = {}, arg2) ->
|
||||
|
||||
#{chalk.yellow(arg2)}
|
||||
"""
|
||||
# happens when there is an invalid config value returnes from the
|
||||
# project's plugins file like "cypress/plugins.index.js"
|
||||
when "PLUGINS_CONFIG_VALIDATION_ERROR"
|
||||
filePath = "`#{arg1}`"
|
||||
"""
|
||||
An invalid configuration value returned from the plugins file: #{chalk.blue(filePath)}
|
||||
|
||||
#{chalk.yellow(arg2)}
|
||||
"""
|
||||
# general configuration error not-specific to configuration or plugins files
|
||||
when "CONFIG_VALIDATION_ERROR"
|
||||
"""
|
||||
We found an invalid configuration value:
|
||||
|
||||
@@ -2,6 +2,7 @@ _ = require("lodash")
|
||||
ipc = require("electron").ipcMain
|
||||
shell = require("electron").shell
|
||||
debug = require('debug')('cypress:server:events')
|
||||
pluralize = require("pluralize")
|
||||
dialog = require("./dialog")
|
||||
pkg = require("./package")
|
||||
logs = require("./logs")
|
||||
@@ -189,6 +190,8 @@ handleEvent = (options, bus, event, id, type, arg) ->
|
||||
.catch(sendErr)
|
||||
|
||||
when "open:project"
|
||||
debug("open:project")
|
||||
|
||||
onSettingsChanged = ->
|
||||
bus.emit("config:changed")
|
||||
|
||||
@@ -208,6 +211,7 @@ handleEvent = (options, bus, event, id, type, arg) ->
|
||||
|
||||
browsers.getAllBrowsersWith(options.browser)
|
||||
.then (browsers = []) ->
|
||||
debug("setting found %s on the config", pluralize("browser", browsers.length, true))
|
||||
options.config = _.assign(options.config, { browsers })
|
||||
.then ->
|
||||
chromePolicyCheck.run (err) ->
|
||||
|
||||
@@ -13,7 +13,7 @@ const recordMode = require('./record')
|
||||
const errors = require('../errors')
|
||||
const Project = require('../project')
|
||||
const Reporter = require('../reporter')
|
||||
const browsers = require('../browsers')
|
||||
const browserUtils = require('../browsers')
|
||||
const openProject = require('../open_project')
|
||||
const videoCapture = require('../video_capture')
|
||||
const fs = require('../util/fs')
|
||||
@@ -447,7 +447,7 @@ const getProjectId = Promise.method((project, id) => {
|
||||
})
|
||||
|
||||
const getDefaultBrowserOptsByFamily = (browser, project, writeVideoFrame) => {
|
||||
la(browsers.isBrowserFamily(browser.family), 'invalid browser family in', browser)
|
||||
la(browserUtils.isBrowserFamily(browser.family), 'invalid browser family in', browser)
|
||||
|
||||
if (browser.family === 'electron') {
|
||||
return getElectronProps(browser.isHeaded, project, writeVideoFrame)
|
||||
@@ -534,17 +534,19 @@ const onWarning = (err) => {
|
||||
console.log(chalk.yellow(err.message))
|
||||
}
|
||||
|
||||
const openProjectCreate = (projectRoot, socketId, options) => {
|
||||
const openProjectCreate = (projectRoot, socketId, args) => {
|
||||
// now open the project to boot the server
|
||||
// putting our web client app in headless mode
|
||||
// - NO display server logs (via morgan)
|
||||
// - YES display reporter results (via mocha reporter)
|
||||
return openProject
|
||||
.create(projectRoot, options, {
|
||||
const options = {
|
||||
socketId,
|
||||
morgan: false,
|
||||
report: true,
|
||||
isTextTerminal: options.isTextTerminal,
|
||||
isTextTerminal: args.isTextTerminal,
|
||||
// pass the list of browsers we have detected when opening a project
|
||||
// to give user's plugins file a chance to change it
|
||||
browsers: args.browsers,
|
||||
onWarning,
|
||||
onError (err) {
|
||||
console.log('')
|
||||
@@ -558,7 +560,10 @@ const openProjectCreate = (projectRoot, socketId, options) => {
|
||||
|
||||
return openProject.emit('exitEarlyWithErr', err.message)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return openProject
|
||||
.create(projectRoot, args, options)
|
||||
.catch({ portInUse: true }, (err) => {
|
||||
// TODO: this needs to move to emit exitEarly
|
||||
// so we record the failure in CI
|
||||
@@ -587,7 +592,7 @@ const createAndOpenProject = function (socketId, options) {
|
||||
}
|
||||
|
||||
const removeOldProfiles = () => {
|
||||
return browsers.removeOldProfiles()
|
||||
return browserUtils.removeOldProfiles()
|
||||
.catch((err) => {
|
||||
// dont make removing old browsers profiles break the build
|
||||
return errors.warning('CANNOT_REMOVE_OLD_BROWSER_PROFILES', err.stack)
|
||||
@@ -1275,89 +1280,101 @@ module.exports = {
|
||||
|
||||
// ensure the project exists
|
||||
// and open up the project
|
||||
return createAndOpenProject(socketId, options)
|
||||
.then(({ project, projectId, config }) => {
|
||||
debug('project created and opened with config %o', config)
|
||||
return browserUtils.getAllBrowsersWith()
|
||||
.then((browsers) => {
|
||||
debug('found all system browsers %o', browsers)
|
||||
options.browsers = browsers
|
||||
|
||||
// if we have a project id and a key but record hasnt been given
|
||||
recordMode.warnIfProjectIdButNoRecordOption(projectId, options)
|
||||
recordMode.throwIfRecordParamsWithoutRecording(record, ciBuildId, parallel, group)
|
||||
return createAndOpenProject(socketId, options)
|
||||
.then(({ project, projectId, config }) => {
|
||||
debug('project created and opened with config %o', config)
|
||||
|
||||
if (record) {
|
||||
recordMode.throwIfNoProjectId(projectId, settings.configFile(options))
|
||||
recordMode.throwIfIncorrectCiBuildIdUsage(ciBuildId, parallel, group)
|
||||
recordMode.throwIfIndeterminateCiBuildId(ciBuildId, parallel, group)
|
||||
}
|
||||
|
||||
return Promise.all([
|
||||
system.info(),
|
||||
browsers.ensureAndGetByNameOrPath(browserName),
|
||||
this.findSpecs(config, specPattern),
|
||||
trashAssets(config),
|
||||
removeOldProfiles(),
|
||||
])
|
||||
.spread((sys = {}, browser = {}, specs = []) => {
|
||||
// return only what is return to the specPattern
|
||||
if (specPattern) {
|
||||
specPattern = specsUtil.getPatternRelativeToProjectRoot(specPattern, projectRoot)
|
||||
}
|
||||
|
||||
if (!specs.length) {
|
||||
errors.throw('NO_SPECS_FOUND', config.integrationFolder, specPattern)
|
||||
}
|
||||
|
||||
if (browser.family === 'chrome') {
|
||||
chromePolicyCheck.run(onWarning)
|
||||
}
|
||||
|
||||
const runAllSpecs = ({ beforeSpecRun, afterSpecRun, runUrl, parallel }) => {
|
||||
return this.runSpecs({
|
||||
beforeSpecRun,
|
||||
afterSpecRun,
|
||||
projectRoot,
|
||||
specPattern,
|
||||
socketId,
|
||||
parallel,
|
||||
browser,
|
||||
project,
|
||||
runUrl,
|
||||
group,
|
||||
config,
|
||||
specs,
|
||||
sys,
|
||||
videosFolder: config.videosFolder,
|
||||
video: config.video,
|
||||
videoCompression: config.videoCompression,
|
||||
videoUploadOnPasses: config.videoUploadOnPasses,
|
||||
exit: options.exit,
|
||||
headed: options.headed,
|
||||
outputPath: options.outputPath,
|
||||
})
|
||||
.tap(renderSummaryTable(runUrl))
|
||||
}
|
||||
// if we have a project id and a key but record hasnt been given
|
||||
recordMode.warnIfProjectIdButNoRecordOption(projectId, options)
|
||||
recordMode.throwIfRecordParamsWithoutRecording(record, ciBuildId, parallel, group)
|
||||
|
||||
if (record) {
|
||||
const { projectName } = config
|
||||
|
||||
return recordMode.createRunAndRecordSpecs({
|
||||
key,
|
||||
sys,
|
||||
specs,
|
||||
group,
|
||||
browser,
|
||||
parallel,
|
||||
ciBuildId,
|
||||
projectId,
|
||||
projectRoot,
|
||||
projectName,
|
||||
specPattern,
|
||||
runAllSpecs,
|
||||
})
|
||||
recordMode.throwIfNoProjectId(projectId, settings.configFile(options))
|
||||
recordMode.throwIfIncorrectCiBuildIdUsage(ciBuildId, parallel, group)
|
||||
recordMode.throwIfIndeterminateCiBuildId(ciBuildId, parallel, group)
|
||||
}
|
||||
|
||||
// not recording, can't be parallel
|
||||
return runAllSpecs({
|
||||
parallel: false,
|
||||
// user code might have modified list of allowed browsers
|
||||
// but be defensive about it
|
||||
const userBrowsers = _.get(config, 'resolved.browsers.value', browsers)
|
||||
|
||||
// all these operations are independent and should be run in parallel to
|
||||
// speed the initial booting time
|
||||
return Promise.all([
|
||||
system.info(),
|
||||
browserUtils.ensureAndGetByNameOrPath(browserName, false, userBrowsers),
|
||||
this.findSpecs(config, specPattern),
|
||||
trashAssets(config),
|
||||
removeOldProfiles(),
|
||||
])
|
||||
.spread((sys = {}, browser = {}, specs = []) => {
|
||||
// return only what is return to the specPattern
|
||||
if (specPattern) {
|
||||
specPattern = specsUtil.getPatternRelativeToProjectRoot(specPattern, projectRoot)
|
||||
}
|
||||
|
||||
if (!specs.length) {
|
||||
errors.throw('NO_SPECS_FOUND', config.integrationFolder, specPattern)
|
||||
}
|
||||
|
||||
if (browser.family === 'chrome') {
|
||||
chromePolicyCheck.run(onWarning)
|
||||
}
|
||||
|
||||
const runAllSpecs = ({ beforeSpecRun, afterSpecRun, runUrl, parallel }) => {
|
||||
return this.runSpecs({
|
||||
beforeSpecRun,
|
||||
afterSpecRun,
|
||||
projectRoot,
|
||||
specPattern,
|
||||
socketId,
|
||||
parallel,
|
||||
browser,
|
||||
project,
|
||||
runUrl,
|
||||
group,
|
||||
config,
|
||||
specs,
|
||||
sys,
|
||||
videosFolder: config.videosFolder,
|
||||
video: config.video,
|
||||
videoCompression: config.videoCompression,
|
||||
videoUploadOnPasses: config.videoUploadOnPasses,
|
||||
exit: options.exit,
|
||||
headed: options.headed,
|
||||
outputPath: options.outputPath,
|
||||
})
|
||||
.tap(renderSummaryTable(runUrl))
|
||||
}
|
||||
|
||||
if (record) {
|
||||
const { projectName } = config
|
||||
|
||||
return recordMode.createRunAndRecordSpecs({
|
||||
key,
|
||||
sys,
|
||||
specs,
|
||||
group,
|
||||
browser,
|
||||
parallel,
|
||||
ciBuildId,
|
||||
projectId,
|
||||
projectRoot,
|
||||
projectName,
|
||||
specPattern,
|
||||
runAllSpecs,
|
||||
})
|
||||
}
|
||||
|
||||
// not recording, can't be parallel
|
||||
return runAllSpecs({
|
||||
parallel: false,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -11,7 +11,7 @@ browsers = require("./browsers")
|
||||
specsUtil = require("./util/specs")
|
||||
preprocessor = require("./plugins/preprocessor")
|
||||
|
||||
create = ->
|
||||
moduleFactory = ->
|
||||
openProject = null
|
||||
relaunchBrowser = null
|
||||
specsWatcher = null
|
||||
@@ -192,6 +192,9 @@ create = ->
|
||||
@closeOpenProjectAndBrowsers()
|
||||
|
||||
create: (path, args = {}, options = {}) ->
|
||||
debug("open_project create %s", path)
|
||||
debug("and options %o", options)
|
||||
|
||||
## store the currently open project
|
||||
openProject = Project(path)
|
||||
|
||||
@@ -209,10 +212,11 @@ create = ->
|
||||
## open the project and return
|
||||
## the config for the project instance
|
||||
debug("opening project %s", path)
|
||||
debug("and options %o", options)
|
||||
|
||||
openProject.open(options)
|
||||
.return(@)
|
||||
}
|
||||
|
||||
module.exports = create()
|
||||
module.exports.Factory = create
|
||||
module.exports = moduleFactory()
|
||||
module.exports.Factory = moduleFactory
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// this module is responsible for loading the plugins file
|
||||
// and running the exported function to register event handlers
|
||||
// and executing any tasks that the plugin registers
|
||||
const _ = require('lodash')
|
||||
const debug = require('debug')('cypress:server:plugins:child')
|
||||
const Promise = require('bluebird')
|
||||
@@ -146,6 +149,8 @@ module.exports = (ipc, pluginsFile) => {
|
||||
}
|
||||
|
||||
ipc.on('load', (config) => {
|
||||
debug('plugins load file "%s"', pluginsFile)
|
||||
debug('passing config %o', config)
|
||||
load(ipc, config, pluginsFile)
|
||||
})
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ class Project extends EE
|
||||
|
||||
open: (options = {}) ->
|
||||
debug("opening project instance %s", @projectRoot)
|
||||
debug("project open options %o", options)
|
||||
@server = Server()
|
||||
|
||||
_.defaults options, {
|
||||
@@ -87,13 +88,17 @@ class Project extends EE
|
||||
## we try to load it and it's not there. We must do this here
|
||||
## else initialing the plugins will instantly fail.
|
||||
if cfg.pluginsFile
|
||||
debug("scaffolding with plugins file %s", cfg.pluginsFile)
|
||||
scaffold.plugins(path.dirname(cfg.pluginsFile), cfg)
|
||||
.then (cfg) =>
|
||||
@_initPlugins(cfg, options)
|
||||
.then (modifiedCfg) ->
|
||||
debug("plugin config yielded:", modifiedCfg)
|
||||
debug("plugin config yielded: %o", modifiedCfg)
|
||||
|
||||
return config.updateWithPluginValues(cfg, modifiedCfg)
|
||||
updatedConfig = config.updateWithPluginValues(cfg, modifiedCfg)
|
||||
debug("updated config: %o", updatedConfig)
|
||||
|
||||
return updatedConfig
|
||||
.then (cfg) =>
|
||||
@server.open(cfg, @, options.onWarning)
|
||||
.spread (port, warning) =>
|
||||
@@ -303,8 +308,10 @@ class Project extends EE
|
||||
_.pick(@, "spec", "browser")
|
||||
|
||||
setBrowsers: (browsers = []) ->
|
||||
debug("getting config before setting browsers %o", browsers)
|
||||
@getConfig()
|
||||
.then (cfg) ->
|
||||
debug("setting config browsers to %o", browsers)
|
||||
cfg.browsers = browsers
|
||||
|
||||
getAutomation: ->
|
||||
@@ -552,7 +559,7 @@ class Project extends EE
|
||||
)
|
||||
|
||||
@getProjectStatus = (clientProject) ->
|
||||
debug("get project status for", clientProject.id, clientProject.path)
|
||||
debug("get project status for client id %s at path %s", clientProject.id, clientProject.path)
|
||||
|
||||
if not clientProject.id
|
||||
debug("no project id")
|
||||
|
||||
@@ -126,6 +126,7 @@ class Server
|
||||
e
|
||||
|
||||
open: (config = {}, project, onWarning) ->
|
||||
debug("server open")
|
||||
la(_.isPlainObject(config), "expected plain config object", config)
|
||||
|
||||
Promise.try =>
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
*/
|
||||
const _ = require('lodash')
|
||||
const errors = require('../errors')
|
||||
const debug = require('debug')('cypress:server:validation')
|
||||
const is = require('check-more-types')
|
||||
const { commaListsOr } = require('common-tags')
|
||||
|
||||
// # validation functions take a key and a value and should:
|
||||
// # - return true if it passes validation
|
||||
@@ -18,6 +21,13 @@ const errors = require('../errors')
|
||||
|
||||
const str = JSON.stringify
|
||||
|
||||
/**
|
||||
* Forms good Markdown-like string message.
|
||||
* @param {string} key - The key that caused the error
|
||||
* @param {string} type - The expected type name
|
||||
* @param {any} value - The actual value
|
||||
* @returns {string} Formatted error message
|
||||
*/
|
||||
const errMsg = (key, value, type) => {
|
||||
return `Expected \`${key}\` to be ${type}. Instead the value was: \`${str(
|
||||
value
|
||||
@@ -40,7 +50,75 @@ 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.
|
||||
*/
|
||||
const isValidBrowser = (browser) => {
|
||||
if (!is.unemptyString(browser.name)) {
|
||||
return errMsg('name', browser, 'a non-empty string')
|
||||
}
|
||||
|
||||
const knownBrowserFamilies = ['electron', 'chrome', 'firefox']
|
||||
|
||||
if (!is.oneOf(knownBrowserFamilies)(browser.family)) {
|
||||
return errMsg('family', browser, commaListsOr`either ${knownBrowserFamilies}`)
|
||||
}
|
||||
|
||||
if (!is.unemptyString(browser.displayName)) {
|
||||
return errMsg('displayName', browser, 'a non-empty string')
|
||||
}
|
||||
|
||||
if (!is.unemptyString(browser.version)) {
|
||||
return errMsg('version', browser, 'a non-empty string')
|
||||
}
|
||||
|
||||
if (!is.string(browser.path)) {
|
||||
return errMsg('path', browser, 'a string')
|
||||
}
|
||||
|
||||
if (!is.string(browser.majorVersion) && !is.positive(browser.majorVersion)) {
|
||||
return errMsg('majorVersion', browser, 'a string or a positive number')
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the list of browsers.
|
||||
*/
|
||||
const isValidBrowserList = (key, browsers) => {
|
||||
debug('browsers %o', browsers)
|
||||
if (!browsers) {
|
||||
return 'Missing browsers list'
|
||||
}
|
||||
|
||||
if (!Array.isArray(browsers)) {
|
||||
debug('browsers is not an array', typeof browsers)
|
||||
|
||||
return 'Browsers should be an array'
|
||||
}
|
||||
|
||||
if (!browsers.length) {
|
||||
return 'Expected at list one browser'
|
||||
}
|
||||
|
||||
for (let k = 0; k < browsers.length; k += 1) {
|
||||
const err = isValidBrowser(browsers[k])
|
||||
|
||||
if (err !== true) {
|
||||
return `Found an error while validating the \`browsers\` list. ${err}`
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isValidBrowser,
|
||||
|
||||
isValidBrowserList,
|
||||
|
||||
isNumber (key, value) {
|
||||
if (value == null || isNumber(value)) {
|
||||
return true
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
e2e = require("../support/helpers/e2e")
|
||||
Fixtures = require("../support/helpers/fixtures")
|
||||
|
||||
configWithInvalidViewport = Fixtures.projectPath("config-with-invalid-viewport")
|
||||
configWithInvalidBrowser = Fixtures.projectPath("config-with-invalid-browser")
|
||||
|
||||
describe "e2e config", ->
|
||||
e2e.setup({
|
||||
@@ -33,3 +37,21 @@ describe "e2e config", ->
|
||||
snapshot: true
|
||||
expectedExitCode: 1
|
||||
})
|
||||
|
||||
it "catches invalid viewportWidth in the configuration file", ->
|
||||
# the test project has cypress.json with a bad setting
|
||||
# which should show an error and exit
|
||||
e2e.exec(@, {
|
||||
project: configWithInvalidViewport
|
||||
expectedExitCode: 1
|
||||
snapshot: true
|
||||
})
|
||||
|
||||
it "catches invalid browser in the configuration file", ->
|
||||
# the test project has cypress.json with a bad browser
|
||||
# which should show an error and exit
|
||||
e2e.exec(@, {
|
||||
project: configWithInvalidBrowser
|
||||
expectedExitCode: 1
|
||||
snapshot: true
|
||||
})
|
||||
|
||||
@@ -5,10 +5,14 @@ Fixtures = require("../support/helpers/fixtures")
|
||||
|
||||
pluginExtension = Fixtures.projectPath("plugin-extension")
|
||||
pluginConfig = Fixtures.projectPath("plugin-config")
|
||||
pluginFilterBrowsers = Fixtures.projectPath("plugin-filter-browsers")
|
||||
workingPreprocessor = Fixtures.projectPath("working-preprocessor")
|
||||
pluginsAsyncError = Fixtures.projectPath("plugins-async-error")
|
||||
pluginsAbsolutePath = Fixtures.projectPath("plugins-absolute-path")
|
||||
pluginAfterScreenshot = Fixtures.projectPath("plugin-after-screenshot")
|
||||
pluginReturnsBadConfig = Fixtures.projectPath("plugin-returns-bad-config")
|
||||
pluginReturnsEmptyBrowsersList = Fixtures.projectPath("plugin-returns-empty-browsers-list")
|
||||
pluginReturnsInvalidBrowser = Fixtures.projectPath("plugin-returns-invalid-browser")
|
||||
|
||||
describe "e2e plugins", ->
|
||||
e2e.setup()
|
||||
@@ -42,6 +46,43 @@ describe "e2e plugins", ->
|
||||
expectedExitCode: 0
|
||||
})
|
||||
|
||||
it "catches invalid viewportWidth returned from plugins", ->
|
||||
# the test project returns config object with a bad value
|
||||
e2e.exec(@, {
|
||||
project: pluginReturnsBadConfig
|
||||
expectedExitCode: 1
|
||||
snapshot: true
|
||||
})
|
||||
|
||||
it "catches invalid browsers list returned from plugins", ->
|
||||
e2e.exec(@, {
|
||||
project: pluginReturnsEmptyBrowsersList
|
||||
expectedExitCode: 1
|
||||
snapshot: true
|
||||
})
|
||||
|
||||
it "catches invalid browser returned from plugins", ->
|
||||
e2e.exec(@, {
|
||||
project: pluginReturnsInvalidBrowser
|
||||
expectedExitCode: 1
|
||||
snapshot: true
|
||||
})
|
||||
|
||||
it "can filter browsers from config", ->
|
||||
e2e.exec(@, {
|
||||
project: pluginFilterBrowsers
|
||||
# the test project filters available browsers
|
||||
# and returns a list with JUST Electron browser
|
||||
# and we ask to run in Chrome
|
||||
# thus the test should fail
|
||||
browser: "chrome"
|
||||
expectedExitCode: 1
|
||||
snapshot: true
|
||||
# we are interested in the actual filtered available browser name
|
||||
# which should be "electron"
|
||||
normalizeAvailableBrowsers: false
|
||||
})
|
||||
|
||||
e2e.it "works with user extensions", {
|
||||
browser: "chrome"
|
||||
spec: "app_spec.coffee"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
require("../spec_helper")
|
||||
|
||||
R = require("ramda")
|
||||
_ = require("lodash")
|
||||
os = require("os")
|
||||
cp = require("child_process")
|
||||
@@ -12,6 +13,7 @@ commitInfo = require("@cypress/commit-info")
|
||||
Fixtures = require("../support/helpers/fixtures")
|
||||
snapshot = require("snap-shot-it")
|
||||
stripAnsi = require("strip-ansi")
|
||||
debug = require("debug")("test")
|
||||
pkg = require("@packages/root")
|
||||
launcher = require("@packages/launcher")
|
||||
extension = require("@packages/extension")
|
||||
@@ -42,6 +44,7 @@ browserUtils = require("#{root}lib/browsers/utils")
|
||||
chromeBrowser = require("#{root}lib/browsers/chrome")
|
||||
openProject = require("#{root}lib/open_project")
|
||||
env = require("#{root}lib/util/env")
|
||||
v = require("#{root}lib/util/validation")
|
||||
system = require("#{root}lib/util/system")
|
||||
appData = require("#{root}lib/util/app_data")
|
||||
formStatePath = require("#{root}lib/util/saved_state").formStatePath
|
||||
@@ -75,7 +78,9 @@ ELECTRON_BROWSER = {
|
||||
name: "electron"
|
||||
family: "electron"
|
||||
displayName: "Electron"
|
||||
path: ""
|
||||
path: "",
|
||||
version: "99.101.1234",
|
||||
majorVersion: 99
|
||||
}
|
||||
|
||||
previousCwd = process.cwd()
|
||||
@@ -129,6 +134,9 @@ describe "lib/cypress", ->
|
||||
sinon.spy(errors, "logException")
|
||||
sinon.spy(console, "log")
|
||||
|
||||
# to make sure our Electron browser mock object passes validation during tests
|
||||
process.versions.chrome = ELECTRON_BROWSER.version
|
||||
|
||||
@expectExitWith = (code) =>
|
||||
expect(process.exit).to.be.calledWith(code)
|
||||
|
||||
@@ -146,6 +154,30 @@ describe "lib/cypress", ->
|
||||
## we spawn is closed down
|
||||
openProject.close()
|
||||
|
||||
context "test browsers", ->
|
||||
# sanity checks to make sure the browser objects we pass during tests
|
||||
# all pass the internal validation function
|
||||
it "has valid browsers", ->
|
||||
expect(v.isValidBrowserList("browsers", TYPICAL_BROWSERS)).to.be.true
|
||||
|
||||
it "has valid electron browser", ->
|
||||
expect(v.isValidBrowserList("browsers", [ELECTRON_BROWSER])).to.be.true
|
||||
|
||||
it "allows browser major to be a number", ->
|
||||
browser = {
|
||||
name: 'Edge Beta',
|
||||
family: 'chrome',
|
||||
displayName: 'Edge Beta',
|
||||
version: '80.0.328.2',
|
||||
path: '/some/path',
|
||||
majorVersion: 80
|
||||
}
|
||||
expect(v.isValidBrowserList("browsers", [browser])).to.be.true
|
||||
|
||||
it "validates returned list", ->
|
||||
browserUtils.getBrowsers().then (list) ->
|
||||
expect(v.isValidBrowserList("browsers", list)).to.be.true
|
||||
|
||||
context "--get-key", ->
|
||||
it "writes out key and exits on success", ->
|
||||
Promise.all([
|
||||
@@ -829,13 +861,20 @@ describe "lib/cypress", ->
|
||||
.then =>
|
||||
args = browserUtils.launch.firstCall.args
|
||||
|
||||
expect(args[0]).to.eq(_.find(TYPICAL_BROWSERS, { name: "chrome" }))
|
||||
# when we work with the browsers we set a few extra flags
|
||||
chrome = _.find(TYPICAL_BROWSERS, { name: "chrome" })
|
||||
launchedChrome = R.merge(chrome, {
|
||||
isHeadless: false,
|
||||
isHeaded: true
|
||||
})
|
||||
|
||||
expect(args[0], "found and used Chrome").to.deep.eq(launchedChrome)
|
||||
|
||||
browserArgs = args[2]
|
||||
|
||||
expect(browserArgs).to.have.length(7)
|
||||
expect(browserArgs, "two arguments to Chrome").to.have.length(7)
|
||||
|
||||
expect(browserArgs.slice(0, 4)).to.deep.eq([
|
||||
expect(browserArgs.slice(0, 4), "arguments to Chrome").to.deep.eq([
|
||||
"chrome", "foo", "bar", "baz"
|
||||
])
|
||||
|
||||
@@ -1494,14 +1533,16 @@ describe "lib/cypress", ->
|
||||
"--config-file=#{@filename}"
|
||||
])
|
||||
.then =>
|
||||
debug("cypress started with config %s", @filename)
|
||||
options = Events.start.firstCall.args[0]
|
||||
debug("first call arguments %o", Events.start.firstCall.args)
|
||||
Events.handleEvent(options, {}, {}, 123, "open:project", @pristinePath)
|
||||
.then =>
|
||||
expect(@open).to.be.called
|
||||
expect(@open, "open was called").to.be.called
|
||||
|
||||
fs.readJsonAsync(path.join(@pristinePath, @filename))
|
||||
.then (json) =>
|
||||
expect(json).to.deep.equal({})
|
||||
expect(json, "json file is empty").to.deep.equal({})
|
||||
|
||||
context "--cwd", ->
|
||||
beforeEach ->
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"browsers": [
|
||||
{
|
||||
"name": "bad browser",
|
||||
"family": "unknown family",
|
||||
"displayName": "Bad browser",
|
||||
"version": "no version",
|
||||
"path": "/path/to",
|
||||
"majorVersion": 123
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
it "is true", ->
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = (onFn, config) ->
|
||||
@@ -0,0 +1,25 @@
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add("login", (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||
@@ -0,0 +1,20 @@
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"viewportWidth": "foo"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
it "is true", ->
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = (onFn, config) ->
|
||||
@@ -16,7 +16,8 @@ describe "Cypress static methods + props", ->
|
||||
expect(browser.name).to.be.oneOf(["electron", "chrome", "canary", "chromium"])
|
||||
expect(browser.displayName).to.be.oneOf(["Electron", "Chrome", "Canary", "Chromium"])
|
||||
expect(browser.version).to.be.a("string")
|
||||
expect(browser.majorVersion).to.be.a("string")
|
||||
# we are parsing major version, so it should be a number
|
||||
expect(browser.majorVersion).to.be.a("number")
|
||||
expect(browser.path).to.be.a("string")
|
||||
|
||||
switch browser.isHeadless
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1 @@
|
||||
it "is true", ->
|
||||
@@ -0,0 +1,18 @@
|
||||
debug = require("debug")("cypress:e2e")
|
||||
module.exports = (onFn, config) ->
|
||||
debug("plugin file %s", __filename)
|
||||
debug("received config with browsers %o", config.browsers)
|
||||
|
||||
if not Array.isArray(config.browsers)
|
||||
throw new Error("Expected list of browsers in the config")
|
||||
if config.browsers.length == 0
|
||||
throw new Error("Expected at least 1 browser in the config")
|
||||
electronBrowser = config.browsers.find (browser) -> browser.name == "electron"
|
||||
if not electronBrowser
|
||||
throw new Error("List of browsers passed into plugins does not include Electron browser")
|
||||
|
||||
changedConfig = {
|
||||
browsers: [electronBrowser]
|
||||
}
|
||||
debug("returning only Electron browser from plugins %o", changedConfig)
|
||||
return changedConfig
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
it "is true", ->
|
||||
@@ -0,0 +1,5 @@
|
||||
# returns object with invalid properties
|
||||
module.exports = (onFn, config) ->
|
||||
{
|
||||
viewportWidth: "foo"
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add("login", (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||
@@ -0,0 +1,20 @@
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
it "is true", ->
|
||||
@@ -0,0 +1,5 @@
|
||||
# returns invalid config - browsers list cannot be empty
|
||||
module.exports = (onFn, config) ->
|
||||
{
|
||||
browsers: []
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add("login", (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||
@@ -0,0 +1,20 @@
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
it "is true", ->
|
||||
@@ -0,0 +1,9 @@
|
||||
# returns invalid config with a browser that is invalid
|
||||
# (missing multiple properties)
|
||||
module.exports = (onFn, config) ->
|
||||
{
|
||||
browsers: [{
|
||||
name: "browser name",
|
||||
family: "chrome"
|
||||
}]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add("login", (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||
@@ -0,0 +1,20 @@
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
@@ -90,17 +90,26 @@ replaceUploadingResults = (orig, match..., offset, string) ->
|
||||
return ret
|
||||
|
||||
normalizeStdout = (str, options = {}) ->
|
||||
normalizeOptions = _.defaults({}, options, {normalizeAvailableBrowsers: true})
|
||||
|
||||
## remove all of the dynamic parts of stdout
|
||||
## to normalize against what we expected
|
||||
str = str
|
||||
## /Users/jane/........../ -> //foo/bar/.projects/
|
||||
## (Required when paths are printed outside of our own formatting)
|
||||
.split(pathUpToProjectName).join("/foo/bar/.projects")
|
||||
.replace(availableBrowsersRe, "$1browser1, browser2, browser3")
|
||||
|
||||
if normalizeOptions.normalizeAvailableBrowsers
|
||||
# usually we are not interested in the browsers detected on this particular system
|
||||
# but some tests might filter / change the list of browsers
|
||||
# in that case the test should pass "normalizeAvailableBrowsers:false" as options
|
||||
str = str.replace(availableBrowsersRe, "$1browser1, browser2, browser3")
|
||||
|
||||
str = str
|
||||
.replace(browserNameVersionRe, replaceBrowserName)
|
||||
## numbers in parenths
|
||||
.replace(/\s\(\d+([ms]|ms)\)/g, "")
|
||||
## 12:35 -> XX:XX
|
||||
## 12:35 -> XX:XX
|
||||
.replace(/(\s+?)(\d+ms|\d+:\d+:?\d+)/g, replaceDurationInTables)
|
||||
.replace(/(coffee|js)-\d{3}/g, "$1-456")
|
||||
## Cypress: 2.1.0 -> Cypress: 1.2.3
|
||||
|
||||
@@ -765,7 +765,8 @@ describe "lib/config", ->
|
||||
projectId: { value: null, from: "default" },
|
||||
port: { value: 1234, from: "cli" },
|
||||
hosts: { value: null, from: "default" }
|
||||
blacklistHosts: { value: null, from: "default" }
|
||||
blacklistHosts: { value: null, from: "default" },
|
||||
browsers: { value: [], from: "default" },
|
||||
userAgent: { value: null, from: "default" }
|
||||
reporter: { value: "json", from: "cli" },
|
||||
reporterOptions: { value: null, from: "default" },
|
||||
@@ -833,6 +834,7 @@ describe "lib/config", ->
|
||||
port: { value: 2020, from: "config" },
|
||||
hosts: { value: null, from: "default" }
|
||||
blacklistHosts: { value: null, from: "default" }
|
||||
browsers: { value: [], from: "default" }
|
||||
userAgent: { value: null, from: "default" }
|
||||
reporter: { value: "spec", from: "default" },
|
||||
reporterOptions: { value: null, from: "default" },
|
||||
@@ -893,12 +895,85 @@ describe "lib/config", ->
|
||||
}
|
||||
})
|
||||
|
||||
context ".setPluginResolvedOn", ->
|
||||
it "resolves an object with single property", ->
|
||||
cfg = {}
|
||||
obj = {
|
||||
foo: "bar"
|
||||
}
|
||||
config.setPluginResolvedOn(cfg, obj)
|
||||
expect(cfg).to.deep.eq({
|
||||
foo: {
|
||||
value: "bar",
|
||||
from: "plugin"
|
||||
}
|
||||
})
|
||||
|
||||
it "resolves an object with multiple properties", ->
|
||||
cfg = {}
|
||||
obj = {
|
||||
foo: "bar",
|
||||
baz: [1, 2, 3]
|
||||
}
|
||||
config.setPluginResolvedOn(cfg, obj)
|
||||
expect(cfg).to.deep.eq({
|
||||
foo: {
|
||||
value: "bar",
|
||||
from: "plugin"
|
||||
},
|
||||
baz: {
|
||||
value: [1, 2, 3],
|
||||
from: "plugin"
|
||||
}
|
||||
})
|
||||
|
||||
it "resolves a nested object", ->
|
||||
# we need at least the structure
|
||||
cfg = {
|
||||
foo: {
|
||||
bar: 1
|
||||
}
|
||||
}
|
||||
obj = {
|
||||
foo: {
|
||||
bar: 42
|
||||
}
|
||||
}
|
||||
config.setPluginResolvedOn(cfg, obj)
|
||||
expect(cfg, "foo.bar gets value").to.deep.eq({
|
||||
foo: {
|
||||
bar: {
|
||||
value: 42,
|
||||
from: "plugin"
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
context "_.defaultsDeep", ->
|
||||
it "merges arrays", ->
|
||||
# sanity checks to confirm how Lodash merges arrays in defaultsDeep
|
||||
diffs = {
|
||||
list: [1]
|
||||
}
|
||||
cfg = {
|
||||
list: [1, 2]
|
||||
}
|
||||
merged = _.defaultsDeep({}, diffs, cfg)
|
||||
expect(merged, "arrays are combined").to.deep.eq({
|
||||
list: [1, 2]
|
||||
})
|
||||
|
||||
context ".updateWithPluginValues", ->
|
||||
it "is noop when no overrides", ->
|
||||
expect(config.updateWithPluginValues({foo: 'bar'}, null)).to.deep.eq({
|
||||
foo: 'bar'
|
||||
})
|
||||
|
||||
it "is noop with empty overrides", ->
|
||||
expect(config.updateWithPluginValues({foo: 'bar'}, {})).to.deep.eq({
|
||||
foo: 'bar'
|
||||
})
|
||||
|
||||
it "updates resolved config values and returns config with overrides", ->
|
||||
cfg = {
|
||||
foo: "bar"
|
||||
@@ -909,6 +984,7 @@ describe "lib/config", ->
|
||||
a: "a"
|
||||
b: "b"
|
||||
}
|
||||
# previously resolved values
|
||||
resolved: {
|
||||
foo: { value: "bar", from: "default" }
|
||||
baz: { value: "quux", from: "cli" }
|
||||
@@ -953,6 +1029,117 @@ describe "lib/config", ->
|
||||
}
|
||||
})
|
||||
|
||||
it "keeps the list of browsers if the plugins returns empty object", ->
|
||||
browser = {
|
||||
name: "fake browser name",
|
||||
family: "chrome",
|
||||
displayName: "My browser",
|
||||
version: "x.y.z",
|
||||
path: "/path/to/browser",
|
||||
majorVersion: "x"
|
||||
}
|
||||
|
||||
cfg = {
|
||||
browsers: [browser],
|
||||
resolved: {
|
||||
browsers: {
|
||||
value: [browser],
|
||||
from: "default"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
overrides = {}
|
||||
|
||||
expect(config.updateWithPluginValues(cfg, overrides)).to.deep.eq({
|
||||
browsers: [browser],
|
||||
resolved: {
|
||||
browsers: {
|
||||
value: [browser],
|
||||
from: "default"
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it "catches browsers=null returned from plugins", ->
|
||||
browser = {
|
||||
name: "fake browser name",
|
||||
family: "chrome",
|
||||
displayName: "My browser",
|
||||
version: "x.y.z",
|
||||
path: "/path/to/browser",
|
||||
majorVersion: "x"
|
||||
}
|
||||
|
||||
cfg = {
|
||||
browsers: [browser],
|
||||
resolved: {
|
||||
browsers: {
|
||||
value: [browser],
|
||||
from: "default"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
overrides = {
|
||||
browsers: null
|
||||
}
|
||||
|
||||
sinon.stub(errors, "throw")
|
||||
config.updateWithPluginValues(cfg, overrides)
|
||||
expect(errors.throw).to.have.been.calledWith("CONFIG_VALIDATION_ERROR")
|
||||
|
||||
it "allows user to filter browsers", ->
|
||||
browserOne = {
|
||||
name: "fake browser name",
|
||||
family: "chrome",
|
||||
displayName: "My browser",
|
||||
version: "x.y.z",
|
||||
path: "/path/to/browser",
|
||||
majorVersion: "x"
|
||||
}
|
||||
browserTwo = {
|
||||
name: "fake electron",
|
||||
family: "electron",
|
||||
displayName: "Electron",
|
||||
version: "x.y.z",
|
||||
# Electron browser is built-in, no external path
|
||||
path: "",
|
||||
majorVersion: "x"
|
||||
}
|
||||
|
||||
cfg = {
|
||||
browsers: [browserOne, browserTwo],
|
||||
resolved: {
|
||||
browsers: {
|
||||
value: [browserOne, browserTwo],
|
||||
from: "default"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
overrides = {
|
||||
browsers: [browserTwo]
|
||||
}
|
||||
|
||||
updated = config.updateWithPluginValues(cfg, overrides)
|
||||
expect(updated.resolved, "resolved values").to.deep.eq({
|
||||
browsers: {
|
||||
value: [browserTwo],
|
||||
from: 'plugin'
|
||||
}
|
||||
})
|
||||
|
||||
expect(updated, "all values").to.deep.eq({
|
||||
browsers: [browserTwo],
|
||||
resolved: {
|
||||
browsers: {
|
||||
value: [browserTwo],
|
||||
from: 'plugin'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
context ".parseEnv", ->
|
||||
it "merges together env from config, env from file, env from process, and env from CLI", ->
|
||||
sinon.stub(config, "getProcessEnvVars").returns({
|
||||
|
||||
@@ -215,7 +215,7 @@ describe "lib/project", ->
|
||||
it.skip "watches cypress.json", ->
|
||||
@server.open().bind(@).then ->
|
||||
expect(Watchers::watch).to.be.calledWith("/Users/brian/app/cypress.json")
|
||||
|
||||
|
||||
# TODO: skip this for now
|
||||
it.skip "passes watchers to Socket.startListening", ->
|
||||
options = {}
|
||||
|
||||
@@ -3,6 +3,57 @@ snapshot = require("snap-shot-it")
|
||||
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", []))
|
||||
snapshot("browsers list with a string", v.isValidBrowserList("browsers", ["foo"]))
|
||||
|
||||
context "#isValidBrowser", ->
|
||||
it "passes valid browsers and forms error messages for invalid ones", ->
|
||||
browsers = [
|
||||
# valid browser
|
||||
{
|
||||
name: "Chrome",
|
||||
displayName: "Chrome Browser",
|
||||
family: "chrome",
|
||||
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: "electron",
|
||||
path: "",
|
||||
version: "99.101.3",
|
||||
majorVersion: 99
|
||||
},
|
||||
# invalid browser, missing displayName
|
||||
{
|
||||
name: "No display name",
|
||||
family: "electron"
|
||||
},
|
||||
{
|
||||
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
|
||||
snapshot.apply(null, [v.isValidBrowser].concat(browsers))
|
||||
|
||||
context "#isOneOf", ->
|
||||
it "validates a string", ->
|
||||
validate = v.isOneOf("foo", "bar")
|
||||
|
||||
Reference in New Issue
Block a user