The viewport determines the width and height of your application. By default the viewport will be {state.defaults.width}px by {state.defaults.height}px unless specified by a cy.viewport command.
-
Additionally you can override the default viewport dimensions by specifying these values in your cypress.json.
+
Additionally you can override the default viewport dimensions by specifying these values in your {configFileFormatted(config.configFile)}.
{/* eslint-disable indent */}
-{`{
+ {`{
"viewportWidth": ${state.defaults.width},
"viewportHeight": ${state.defaults.height}
}`}
diff --git a/packages/runner/src/header/header.spec.jsx b/packages/runner/src/header/header.spec.jsx
index 7a0457bce94..1ac7abba133 100644
--- a/packages/runner/src/header/header.spec.jsx
+++ b/packages/runner/src/header/header.spec.jsx
@@ -14,6 +14,12 @@ const getState = (props) => _.extend({
updateWindowDimensions: sinon.spy(),
}, props)
+const propsWithState = (props) =>
+ ({
+ state: getState(props),
+ config: {},
+ })
+
describe('', () => {
beforeEach(() => {
driver.$.returns({ outerHeight: () => 42 })
@@ -21,30 +27,30 @@ describe('', () => {
it('has showing-selector-playground class if selector playground is open', () => {
selectorPlaygroundModel.isOpen = true
- expect(shallow()).to.have.className('showing-selector-playground')
+ expect(shallow()).to.have.className('showing-selector-playground')
})
it('does not showing-selector-playground class if selector playground is disabled', () => {
selectorPlaygroundModel.isOpen = false
- expect(shallow()).not.to.have.className('showing-selector-playground')
+ expect(shallow()).not.to.have.className('showing-selector-playground')
})
describe('selector playground button', () => {
it('is disabled if tests are loading', () => {
- const component = shallow()
+ const component = shallow()
expect(component.find('.selector-playground-toggle')).to.be.disabled
})
it('is disabled if tests are running', () => {
- const component = shallow()
+ const component = shallow()
expect(component.find('.selector-playground-toggle')).to.be.disabled
})
it('toggles the selector playground on click', () => {
selectorPlaygroundModel.toggleOpen = sinon.spy()
- const component = shallow()
+ const component = shallow()
component.find('.selector-playground-toggle').simulate('click')
expect(selectorPlaygroundModel.toggleOpen).to.be.called
@@ -52,23 +58,23 @@ describe('', () => {
it('updates window dimensions after selector playground is toggled', () => {
selectorPlaygroundModel.isOpen = false
- const state = getState()
+ const props = propsWithState()
- mount()
+ mount()
selectorPlaygroundModel.isOpen = true
- expect(state.updateWindowDimensions).to.be.calledWith({ headerHeight: 42 })
+ expect(props.state.updateWindowDimensions).to.be.calledWith({ headerHeight: 42 })
})
it('does not show tooltip if selector playground is open', () => {
selectorPlaygroundModel.isOpen = true
- const component = shallow()
+ const component = shallow()
expect(component.find(Tooltip)).to.have.prop('visible', false)
})
it('uses default tooltip visibility if selector playground is closed', () => {
selectorPlaygroundModel.isOpen = false
- const component = shallow()
+ const component = shallow()
expect(component.find(Tooltip)).to.have.prop('visible', null)
})
@@ -76,26 +82,26 @@ describe('', () => {
describe('url', () => {
it('has loading class when loading url', () => {
- const component = shallow()
+ const component = shallow()
expect(component.find('.url-container')).to.have.className('loading')
})
it('has highlighted class when url is highlighted', () => {
- const component = shallow()
+ const component = shallow()
expect(component.find('.url-container')).to.have.className('highlighted')
})
it('displays url', () => {
- const component = shallow()
+ const component = shallow()
expect(component.find('.url')).to.have.value('the://url')
})
it('opens url when clicked', () => {
sinon.stub(window, 'open')
- const component = shallow()
+ const component = shallow()
component.find('.url').simulate('click')
expect(window.open).to.be.calledWith('the://url')
@@ -104,7 +110,7 @@ describe('', () => {
describe('viewport info', () => {
it('has open class on button click', () => {
- const component = shallow()
+ const component = shallow()
component.find('.viewport-info button').simulate('click')
expect(component.find('.viewport-info')).to.have.className('open')
@@ -112,14 +118,14 @@ describe('', () => {
it('displays width, height, and display scale', () => {
const state = { width: 1, height: 2, displayScale: 3 }
- const component = shallow()
+ const component = shallow()
expect(component.find('.viewport-info button').text()).to.contain('1 x 2 (3%)')
})
it('displays default width and height in menu', () => {
const state = { defaults: { width: 4, height: 5 } }
- const component = shallow()
+ const component = shallow()
expect(component.find('.viewport-menu pre').text()).to.contain('"viewportWidth": 4')
expect(component.find('.viewport-menu pre').text()).to.contain('"viewportHeight": 5')
diff --git a/packages/runner/src/iframe/aut-iframe.js b/packages/runner/src/iframe/aut-iframe.js
index 1f384b08b1a..978e378aec8 100644
--- a/packages/runner/src/iframe/aut-iframe.js
+++ b/packages/runner/src/iframe/aut-iframe.js
@@ -1,11 +1,11 @@
import _ from 'lodash'
import { $ } from '@packages/driver'
-import blankContents from './blank-contents'
import dom from '../lib/dom'
-import eventManager from '../lib/event-manager'
import logger from '../lib/logger'
+import eventManager from '../lib/event-manager'
import visitFailure from './visit-failure'
+import blankContents from './blank-contents'
import selectorPlaygroundModel from '../selector-playground/selector-playground-model'
export default class AutIframe {
@@ -172,9 +172,15 @@ export default class AutIframe {
body = this._body()
}
+ // normalize
+ const el = $el.get(0)
+ const $body = body
+
+ body = $body.get(0)
+
// scroll the top of the element into view
- if ($el.get(0)) {
- $el.get(0).scrollIntoView()
+ if (el) {
+ el.scrollIntoView()
// if we have a scrollBy on our command
// then we need to additional scroll the window
// by these offsets
@@ -183,25 +189,28 @@ export default class AutIframe {
}
}
- $el.each((__, el) => {
- el = $(el)
+ $el.each((__, element) => {
+ const $_el = $(element)
+
// bail if our el no longer exists in the parent body
- if (!$.contains(body[0], el[0])) return
+ if (!$.contains(body, element)) return
// switch to using outerWidth + outerHeight
// because we want to highlight our element even
// if it only has margin and zero content height / width
- const dimensions = dom.getOuterSize(el)
+ const dimensions = dom.getOuterSize($_el)
// dont show anything if our element displaces nothing
- if (dimensions.width === 0 || dimensions.height === 0 || el.css('display') === 'none') return
+ if (dimensions.width === 0 || dimensions.height === 0 || $_el.css('display') === 'none') {
+ return
+ }
- dom.addElementBoxModelLayers(el, body).attr('data-highlight-el', true)
+ dom.addElementBoxModelLayers($_el, $body).attr('data-highlight-el', true)
})
if (coords) {
requestAnimationFrame(() => {
- dom.addHitBoxLayer(coords, body).attr('data-highlight-hitbox', true)
+ dom.addHitBoxLayer(coords, $body).attr('data-highlight-hitbox', true)
})
}
}
@@ -318,16 +327,16 @@ export default class AutIframe {
}
getElements (cypressDom) {
- const contents = this._contents()
- const selector = selectorPlaygroundModel.selector
+ const { selector, method } = selectorPlaygroundModel
+ const $contents = this._contents()
- if (!contents || !selector) return
+ if (!$contents || !selector) return
return dom.getElementsForSelector({
+ method,
selector,
- method: selectorPlaygroundModel.method,
- root: contents,
cypressDom,
+ $root: $contents,
})
}
diff --git a/packages/runner/src/iframe/visit-failure.js b/packages/runner/src/iframe/visit-failure.js
index 951033a223d..896c105e61b 100644
--- a/packages/runner/src/iframe/visit-failure.js
+++ b/packages/runner/src/iframe/visit-failure.js
@@ -10,7 +10,6 @@ export default (props) => {
}
const getStatus = () => {
-
if (!status) {
return ''
}
diff --git a/packages/runner/src/lib/config-file-formatted.jsx b/packages/runner/src/lib/config-file-formatted.jsx
new file mode 100644
index 00000000000..4f6bccb6d98
--- /dev/null
+++ b/packages/runner/src/lib/config-file-formatted.jsx
@@ -0,0 +1,18 @@
+import React from 'react'
+import { isUndefined } from 'lodash'
+
+const configFileFormatted = (configFile) => {
+ if (configFile === false) {
+ return <>cypress.json file (currently disabled by --config-file false)>
+ }
+
+ if (isUndefined(configFile) || configFile === 'cypress.json') {
+ return <>cypress.json file>
+ }
+
+ return <>custom config file {configFile}>
+}
+
+export {
+ configFileFormatted,
+}
diff --git a/packages/runner/src/lib/dom.js b/packages/runner/src/lib/dom.js
index f697b3cd1bb..a5474931423 100644
--- a/packages/runner/src/lib/dom.js
+++ b/packages/runner/src/lib/dom.js
@@ -16,10 +16,8 @@ const resetStyles = `
padding: 0 !important;
`
-function addHitBoxLayer (coords, body) {
- if (body == null) {
- body = $('body')
- }
+function addHitBoxLayer (coords, $body) {
+ $body = $body || $('body')
const height = 10
const width = 10
@@ -45,8 +43,11 @@ function addHitBoxLayer (coords, body) {
box-shadow: 0 0 5px #333;
z-index: 2147483647;
`)
- const box = $(``)
- const wrapper = $(``).appendTo(box)
+
+ const $box = $(``)
+
+ const wrapper = $(``).appendTo($box)
+
const dotStyles = styles(`
${resetStyles}
position: absolute;
@@ -61,17 +62,18 @@ function addHitBoxLayer (coords, body) {
$(``).appendTo(wrapper)
- return box.appendTo(body)
+ return $box.appendTo($body)
}
-function addElementBoxModelLayers ($el, body) {
- if (body == null) {
- body = $('body')
- }
+function addElementBoxModelLayers ($el, $body) {
+ $body = $body || $('body')
const dimensions = getElementDimensions($el)
- const container = $('
')
+ const $container = $('
')
+ .css({
+ opacity: 0.7,
+ })
const layers = {
Content: '#9FC4E7',
@@ -113,16 +115,21 @@ function addElementBoxModelLayers ($el, body) {
obj.left -= dimensions.marginLeft
}
+ if (attr === 'Padding') {
+ obj.top += dimensions.borderTop
+ obj.left += dimensions.borderLeft
+ }
+
// bail if the dimensions of this layer match the previous one
// so we dont create unnecessary layers
- if (dimensionsMatchPreviousLayer(obj, container)) return
+ if (dimensionsMatchPreviousLayer(obj, $container)) return
- return createLayer($el, attr, color, container, obj)
+ return createLayer($el, attr, color, $container, obj)
})
- container.appendTo(body)
+ $container.appendTo($body)
- container.children().each((index, el) => {
+ $container.children().each((index, el) => {
const $el = $(el)
const top = $el.data('top')
const left = $el.data('left')
@@ -134,7 +141,7 @@ function addElementBoxModelLayers ($el, body) {
})
})
- return container
+ return $container
}
function getOrCreateSelectorHelperDom ($body) {
@@ -220,7 +227,6 @@ function createLayer ($el, attr, color, container, dimensions) {
position: 'absolute',
zIndex: getZIndex($el),
backgroundColor: color,
- opacity: 0.6,
}
return $('
')
@@ -234,15 +240,15 @@ function createLayer ($el, attr, color, container, dimensions) {
function dimensionsMatchPreviousLayer (obj, container) {
// since we're prepending to the container that
// means the previous layer is actually the first child element
- const previousLayer = container.children().first()
+ const previousLayer = container.children().first().get(0)
// bail if there is no previous layer
- if (!previousLayer.length) {
+ if (!previousLayer) {
return
}
- return obj.width === previousLayer.width() &&
- obj.height === previousLayer.height()
+ return obj.width === previousLayer.offsetWidth &&
+ obj.height === previousLayer.offsetHeight
}
function getDimensionsFor (dimensions, attr, dimension) {
@@ -255,14 +261,17 @@ function getZIndex (el) {
}
return _.toNumber(el.css('zIndex'))
-
}
function getElementDimensions ($el) {
- const dimensions = {
+ const el = $el.get(0)
+
+ const { offsetHeight, offsetWidth } = el
+
+ const box = {
offset: $el.offset(), // offset disregards margin but takes into account border + padding
- height: $el.height(), // we want to use height here (because that always returns just the content hight) instead of .css() because .css('height') is altered based on whether box-sizing: border-box is set
- width: $el.width(),
+ // dont use jquery here for width/height because it uses getBoundingClientRect() which returns scaled values.
+ // TODO: switch back to using jquery when upgrading to jquery 3.4+
paddingTop: getPadding($el, 'top'),
paddingRight: getPadding($el, 'right'),
paddingBottom: getPadding($el, 'bottom'),
@@ -277,6 +286,14 @@ function getElementDimensions ($el) {
marginLeft: getMargin($el, 'left'),
}
+ // NOTE: offsetWidth/height always give us content + padding + border, so subtract them
+ // to get the true "clientHeight" and "clientWidth".
+ // we CANNOT just use "clientHeight" and "clientWidth" because those always return 0
+ // for inline elements >_<
+ //
+ box.width = offsetWidth - (box.paddingLeft + box.paddingRight + box.borderLeft + box.borderRight)
+ box.height = offsetHeight - (box.paddingTop + box.paddingBottom + box.borderTop + box.borderBottom)
+
// innerHeight: Get the current computed height for the first
// element in the set of matched elements, including padding but not border.
@@ -284,21 +301,24 @@ function getElementDimensions ($el) {
// element in the set of matched elements, including padding, border,
// and optionally margin. Returns a number (without 'px') representation
// of the value or null if called on an empty set of elements.
+ box.heightWithPadding = box.height + box.paddingTop + box.paddingBottom
+
+ box.heightWithBorder = box.heightWithPadding + box.borderTop + box.borderBottom
- dimensions.heightWithPadding = $el.innerHeight()
- dimensions.heightWithBorder = $el.innerHeight() + getTotalFor(['borderTop', 'borderBottom'], dimensions)
- dimensions.heightWithMargin = $el.outerHeight(true)
+ box.heightWithMargin = box.heightWithBorder + box.marginTop + box.marginBottom
- dimensions.widthWithPadding = $el.innerWidth()
- dimensions.widthWithBorder = $el.innerWidth() + getTotalFor(['borderRight', 'borderLeft'], dimensions)
- dimensions.widthWithMargin = $el.outerWidth(true)
+ box.widthWithPadding = box.width + box.paddingLeft + box.paddingRight
- return dimensions
+ box.widthWithBorder = box.widthWithPadding + box.borderLeft + box.borderRight
+
+ box.widthWithMargin = box.widthWithBorder + box.marginLeft + box.marginRight
+
+ return box
}
-function getNumAttrValue (el, attr) {
+function getNumAttrValue ($el, attr) {
// nuke anything thats not a number or a negative symbol
- const num = _.toNumber(el.css(attr).replace(/[^0-9\.-]+/, ''))
+ const num = _.toNumber($el.css(attr).replace(/[^0-9\.-]+/, ''))
if (!_.isFinite(num)) {
throw new Error('Element attr did not return a valid number')
@@ -307,28 +327,22 @@ function getNumAttrValue (el, attr) {
return num
}
-function getPadding (el, dir) {
- return getNumAttrValue(el, `padding-${dir}`)
-}
-
-function getBorder (el, dir) {
- return getNumAttrValue(el, `border-${dir}-width`)
+function getPadding ($el, dir) {
+ return getNumAttrValue($el, `padding-${dir}`)
}
-function getMargin (el, dir) {
- return getNumAttrValue(el, `margin-${dir}`)
+function getBorder ($el, dir) {
+ return getNumAttrValue($el, `border-${dir}-width`)
}
-function getTotalFor (directions, dimensions) {
- return _.reduce(directions, (memo, direction) => {
- return memo + dimensions[direction]
- }, 0)
+function getMargin ($el, dir) {
+ return getNumAttrValue($el, `margin-${dir}`)
}
-function getOuterSize (el) {
+function getOuterSize ($el) {
return {
- width: el.outerWidth(true),
- height: el.outerHeight(true),
+ width: $el.outerWidth(true),
+ height: $el.outerHeight(true),
}
}
@@ -338,8 +352,8 @@ function isInViewport (win, el) {
return (
rect.top >= 0 &&
rect.left >= 0 &&
- rect.bottom <= $(win).height() &&
- rect.right <= $(win).width()
+ rect.bottom <= win.innerHeight &&
+ rect.right <= win.innerWidth
)
}
@@ -351,17 +365,17 @@ function scrollIntoView (win, el) {
const sizzleRe = /sizzle/i
-function getElementsForSelector ({ root, selector, method, cypressDom }) {
+function getElementsForSelector ({ $root, selector, method, cypressDom }) {
let $el = null
try {
if (method === 'contains') {
- $el = root.find(cypressDom.getContainsSelector(selector))
+ $el = $root.find(cypressDom.getContainsSelector(selector))
if ($el.length) {
$el = cypressDom.getFirstDeepestElement($el)
}
} else {
- $el = root.find(selector)
+ $el = $root.find(selector)
}
} catch (err) {
// if not a sizzle error, ignore it and let $el be null
diff --git a/packages/runner/src/lib/logger.js b/packages/runner/src/lib/logger.js
index d39f0c711cc..850fb8f9f6d 100644
--- a/packages/runner/src/lib/logger.js
+++ b/packages/runner/src/lib/logger.js
@@ -76,8 +76,6 @@ export default {
if (!groups) return
- delete consoleProps.groups
-
return _.map(groups, (group) => {
group.items = this._formatted(group.items)
@@ -86,6 +84,17 @@ export default {
},
_logTable (consoleProps) {
+ if (isMultiEntryTable(consoleProps.table)) {
+ _.each(
+ _.sortBy(consoleProps.table, (val, key) => key),
+ (table) => {
+ return this._logTable({ table })
+ }
+ )
+
+ return
+ }
+
const table = this._getTable(consoleProps)
if (!table) return
@@ -104,8 +113,8 @@ export default {
if (!table) return
- delete consoleProps.table
-
return table
},
}
+
+const isMultiEntryTable = (table) => !_.isFunction(table) && !_.some(_.keys(table).map(isNaN).filter(Boolean), true)
diff --git a/packages/runner/src/lib/state.js b/packages/runner/src/lib/state.js
index 404eb322828..86141b7a1f1 100644
--- a/packages/runner/src/lib/state.js
+++ b/packages/runner/src/lib/state.js
@@ -97,7 +97,6 @@ export default class State {
top: (actualHeight + this.headerHeight + nudge),
},
}
-
}
@action setIsLoading (isLoading) {
diff --git a/packages/runner/src/lib/util.js b/packages/runner/src/lib/util.js
index 6245011b685..d4eade2474a 100644
--- a/packages/runner/src/lib/util.js
+++ b/packages/runner/src/lib/util.js
@@ -1,6 +1,29 @@
import path from 'path'
export default {
+ /**
+ * Correctly decodes Unicode string in encoded in base64
+ * @see https://github.com/cypress-io/cypress/issues/5435
+ * @see https://stackoverflow.com/questions/30106476/using-javascripts-atob-to-decode-base64-doesnt-properly-decode-utf-8-strings
+ *
+ * @example
+ ```
+ Buffer.from(JSON.stringify({state: '🙂'})).toString('base64')
+ // 'eyJzdGF0ZSI6IvCfmYIifQ=='
+ // "window.atob" does NOT work
+ // atob('eyJzdGF0ZSI6IvCfmYIifQ==')
+ // "{"state":"ð"}"
+ // but this function works
+ b64DecodeUnicode('eyJzdGF0ZSI6IvCfmYIifQ==')
+ '{"state":"🙂"}'
+ ```
+ */
+ b64DecodeUnicode (str) {
+ return decodeURIComponent(atob(str).split('').map(function (c) {
+ return `%${(`00${c.charCodeAt(0).toString(16)}`).slice(-2)}`
+ }).join(''))
+ },
+
hasSpecFile () {
return !!location.hash
},
@@ -17,7 +40,6 @@ export default {
}
return ''
-
},
absoluteSpecPath (config) {
diff --git a/packages/runner/src/lib/util.spec.js b/packages/runner/src/lib/util.spec.js
new file mode 100644
index 00000000000..6a04f844414
--- /dev/null
+++ b/packages/runner/src/lib/util.spec.js
@@ -0,0 +1,14 @@
+import util from './util'
+
+describe('util', () => {
+ context('b64DecodeUnicode', () => {
+ it('decodes unicode string correctly', () => {
+ // https://github.com/cypress-io/cypress/issues/5435
+ const s = '🙂 привет 🌎'
+ const encoded = Buffer.from(s).toString('base64')
+ const decoded = util.b64DecodeUnicode(encoded)
+
+ expect(decoded).to.equal(s)
+ })
+ })
+})
diff --git a/packages/runner/src/main.jsx b/packages/runner/src/main.jsx
index 169ac89f7e6..74afb5ef81c 100644
--- a/packages/runner/src/main.jsx
+++ b/packages/runner/src/main.jsx
@@ -4,12 +4,15 @@ import { render } from 'react-dom'
import State from './lib/state'
import Container from './app/container'
+import util from './lib/util'
configure({ enforceActions: 'strict' })
const Runner = {
- start (el, config) {
+ start (el, base64Config) {
action('started', () => {
+ const config = JSON.parse(util.b64DecodeUnicode(base64Config))
+
const state = new State((config.state || {}).reporterWidth)
Runner.state = state
diff --git a/packages/runner/static/index.html b/packages/runner/static/index.html
index df0fcdc5efd..8243cf6e261 100644
--- a/packages/runner/static/index.html
+++ b/packages/runner/static/index.html
@@ -17,7 +17,7 @@
window.__Cypress__ = true
setTimeout(function(){
- Runner.start(document.getElementById('app'), {{{config}}})
+ Runner.start(document.getElementById('app'), "{{base64Config | safe}}")
}, 0)
diff --git a/packages/runner/test/.eslintrc b/packages/runner/test/.eslintrc.json
similarity index 100%
rename from packages/runner/test/.eslintrc
rename to packages/runner/test/.eslintrc.json
diff --git a/packages/runner/test/helper.js b/packages/runner/test/helper.js
index 50a61981415..dd2df244cfa 100644
--- a/packages/runner/test/helper.js
+++ b/packages/runner/test/helper.js
@@ -17,7 +17,6 @@ const _useFakeTimers = sinon.useFakeTimers
let timers = []
sinon.useFakeTimers = function (...args) {
-
const ret = _useFakeTimers.apply(this, args)
timers.push(ret)
diff --git a/packages/runner/ts/import-coffee.d.ts b/packages/runner/ts/import-coffee.d.ts
new file mode 100644
index 00000000000..afd1157db41
--- /dev/null
+++ b/packages/runner/ts/import-coffee.d.ts
@@ -0,0 +1,5 @@
+// allows Typescript to import .coffee files
+declare module '*.coffee' {
+ const content: any
+ export default content
+}
diff --git a/packages/server/.gitignore b/packages/server/.gitignore
index 47fb47fd690..5e1717fe3e4 100644
--- a/packages/server/.gitignore
+++ b/packages/server/.gitignore
@@ -1,3 +1,5 @@
-lib/util/ensure-url.js
-lib/util/proxy.js
+# we do not explicitly ignore JavaScript files in "lib/browsers" folder
+# because when we add TS files we do not transpile them as a build step
+# instead always use require hooks to transpile TS files on the fly
+
.http-mitm-proxy
diff --git a/packages/server/README.md b/packages/server/README.md
index 7c0c79dc283..e40d36c06e7 100644
--- a/packages/server/README.md
+++ b/packages/server/README.md
@@ -40,6 +40,7 @@ Since this is slow, it's better to drive your development with tests.
* `npm run test-unit` executes unit tests in [`test/unit`](./test/unit)
* `npm run test-integration` executes integration tests in [`test/integration`](./test/integration)
+* `npm run test-performance` executes performance tests in [`test/performance`](./test/performance)
* `npm run test-e2e` executes the large (slow) end to end tests in [`test/e2e`](./test/e2e)
Each of these tasks can run in "watch" mode by appending `-watch` to the task:
@@ -54,7 +55,7 @@ Because of the large number of dependencies of the server, it's much more perfor
## runs only this one test file
npm run test ./test/unit/api_spec.coffee
-## works for integration tests too
+## works for integration tests
npm run test ./test/integration/server_spec.coffee
```
diff --git a/packages/server/__snapshots__/1_async_timeouts_spec.coffee.js b/packages/server/__snapshots__/1_async_timeouts_spec.coffee.js
index 4b312c50ed0..a1268bf028d 100644
--- a/packages/server/__snapshots__/1_async_timeouts_spec.coffee.js
+++ b/packages/server/__snapshots__/1_async_timeouts_spec.coffee.js
@@ -14,47 +14,55 @@ exports['e2e async timeouts failing1 1'] = `
────────────────────────────────────────────────────────────────────────────────────────────────────
- Running: async_timeouts_spec.coffee... (1 of 1)
+ Running: async_timeouts_spec.coffee (1 of 1)
async
1) bar fails
+ 2) fails async after cypress command
0 passing
- 1 failing
+ 2 failing
1) async bar fails:
Error: Timed out after '100ms'. The done() callback was never invoked!
at stack trace line
+ 2) async fails async after cypress command:
+ Error: Timed out after '100ms'. The done() callback was never invoked!
+ at stack trace line
+
(Results)
- ┌──────────────────────────────────────────┐
- │ Tests: 1 │
- │ Passing: 0 │
- │ Failing: 1 │
- │ Pending: 0 │
- │ Skipped: 0 │
- │ Screenshots: 1 │
- │ Video: true │
- │ Duration: X seconds │
- │ Spec Ran: async_timeouts_spec.coffee │
- └──────────────────────────────────────────┘
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ Tests: 2 │
+ │ Passing: 0 │
+ │ Failing: 2 │
+ │ Pending: 0 │
+ │ Skipped: 0 │
+ │ Screenshots: 2 │
+ │ Video: true │
+ │ Duration: X seconds │
+ │ Spec Ran: async_timeouts_spec.coffee │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
(Screenshots)
- - /foo/bar/.projects/e2e/cypress/screenshots/async_timeouts_spec.coffee/async -- bar fails (failed).png (1280x720)
+ - /XXX/XXX/XXX/cypress/screenshots/async_timeouts_spec.coffee/async -- bar fails ( (1280x720)
+ failed).png
+ - /XXX/XXX/XXX/cypress/screenshots/async_timeouts_spec.coffee/async -- fails async (1280x720)
+ after cypress command (failed).png
(Video)
- - Started processing: Compressing to 32 CRF
- - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds)
+ - Started processing: Compressing to 32 CRF
+ - Finished processing: /XXX/XXX/XXX/cypress/videos/async_timeouts_spec.coffee.mp4 (X second)
====================================================================================================
@@ -62,11 +70,11 @@ exports['e2e async timeouts failing1 1'] = `
(Run Finished)
- Spec Tests Passing Failing Pending Skipped
+ Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ ✖ async_timeouts_spec.coffee XX:XX 1 - 1 - - │
+ │ ✖ async_timeouts_spec.coffee XX:XX 2 - 2 - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
- 1 of 1 failed (100%) XX:XX 1 - 1 - -
+ ✖ 1 of 1 failed (100%) XX:XX 2 - 2 - -
`
diff --git a/packages/server/__snapshots__/1_base_url_spec.coffee.js b/packages/server/__snapshots__/1_base_url_spec.coffee.js
index 4a6ffdac121..1c6b5dd298a 100644
--- a/packages/server/__snapshots__/1_base_url_spec.coffee.js
+++ b/packages/server/__snapshots__/1_base_url_spec.coffee.js
@@ -1,4 +1,4 @@
-exports['e2e baseUrl https passes 1'] = `
+exports['e2e baseUrl / https / passes'] = `
====================================================================================================
@@ -14,7 +14,7 @@ exports['e2e baseUrl https passes 1'] = `
────────────────────────────────────────────────────────────────────────────────────────────────────
- Running: base_url_spec.coffee... (1 of 1)
+ Running: base_url_spec.coffee (1 of 1)
base url
@@ -26,23 +26,23 @@ exports['e2e baseUrl https passes 1'] = `
(Results)
- ┌────────────────────────────────────┐
- │ Tests: 1 │
- │ Passing: 1 │
- │ Failing: 0 │
- │ Pending: 0 │
- │ Skipped: 0 │
- │ Screenshots: 0 │
- │ Video: true │
- │ Duration: X seconds │
- │ Spec Ran: base_url_spec.coffee │
- └────────────────────────────────────┘
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ Tests: 1 │
+ │ Passing: 1 │
+ │ Failing: 0 │
+ │ Pending: 0 │
+ │ Skipped: 0 │
+ │ Screenshots: 0 │
+ │ Video: true │
+ │ Duration: X seconds │
+ │ Spec Ran: base_url_spec.coffee │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
(Video)
- - Started processing: Compressing to 32 CRF
- - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds)
+ - Started processing: Compressing to 32 CRF
+ - Finished processing: /XXX/XXX/XXX/cypress/videos/base_url_spec.coffee.mp4 (X second)
====================================================================================================
@@ -50,16 +50,16 @@ exports['e2e baseUrl https passes 1'] = `
(Run Finished)
- Spec Tests Passing Failing Pending Skipped
+ Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ ✔ base_url_spec.coffee XX:XX 1 1 - - - │
+ │ ✔ base_url_spec.coffee XX:XX 1 1 - - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
- All specs passed! XX:XX 1 1 - - -
+ ✔ All specs passed! XX:XX 1 1 - - -
`
-exports['e2e baseUrl http passes 1'] = `
+exports['e2e baseUrl / http / passes'] = `
====================================================================================================
@@ -75,7 +75,7 @@ exports['e2e baseUrl http passes 1'] = `
────────────────────────────────────────────────────────────────────────────────────────────────────
- Running: base_url_spec.coffee... (1 of 1)
+ Running: base_url_spec.coffee (1 of 1)
base url
@@ -87,23 +87,23 @@ exports['e2e baseUrl http passes 1'] = `
(Results)
- ┌────────────────────────────────────┐
- │ Tests: 1 │
- │ Passing: 1 │
- │ Failing: 0 │
- │ Pending: 0 │
- │ Skipped: 0 │
- │ Screenshots: 0 │
- │ Video: true │
- │ Duration: X seconds │
- │ Spec Ran: base_url_spec.coffee │
- └────────────────────────────────────┘
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ Tests: 1 │
+ │ Passing: 1 │
+ │ Failing: 0 │
+ │ Pending: 0 │
+ │ Skipped: 0 │
+ │ Screenshots: 0 │
+ │ Video: true │
+ │ Duration: X seconds │
+ │ Spec Ran: base_url_spec.coffee │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
(Video)
- - Started processing: Compressing to 32 CRF
- - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds)
+ - Started processing: Compressing to 32 CRF
+ - Finished processing: /XXX/XXX/XXX/cypress/videos/base_url_spec.coffee.mp4 (X second)
====================================================================================================
@@ -111,11 +111,11 @@ exports['e2e baseUrl http passes 1'] = `
(Run Finished)
- Spec Tests Passing Failing Pending Skipped
+ Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ ✔ base_url_spec.coffee XX:XX 1 1 - - - │
+ │ ✔ base_url_spec.coffee XX:XX 1 1 - - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
- All specs passed! XX:XX 1 1 - - -
+ ✔ All specs passed! XX:XX 1 1 - - -
`
diff --git a/packages/server/__snapshots__/1_blacklist_hosts_spec.coffee.js b/packages/server/__snapshots__/1_blacklist_hosts_spec.coffee.js
index 7233a132f44..ddc788d3972 100644
--- a/packages/server/__snapshots__/1_blacklist_hosts_spec.coffee.js
+++ b/packages/server/__snapshots__/1_blacklist_hosts_spec.coffee.js
@@ -14,7 +14,7 @@ exports['e2e blacklist passes 1'] = `
────────────────────────────────────────────────────────────────────────────────────────────────────
- Running: blacklist_hosts_spec.coffee... (1 of 1)
+ Running: blacklist_hosts_spec.coffee (1 of 1)
blacklist
@@ -26,23 +26,23 @@ exports['e2e blacklist passes 1'] = `
(Results)
- ┌───────────────────────────────────────────┐
- │ Tests: 1 │
- │ Passing: 1 │
- │ Failing: 0 │
- │ Pending: 0 │
- │ Skipped: 0 │
- │ Screenshots: 0 │
- │ Video: true │
- │ Duration: X seconds │
- │ Spec Ran: blacklist_hosts_spec.coffee │
- └───────────────────────────────────────────┘
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ Tests: 1 │
+ │ Passing: 1 │
+ │ Failing: 0 │
+ │ Pending: 0 │
+ │ Skipped: 0 │
+ │ Screenshots: 0 │
+ │ Video: true │
+ │ Duration: X seconds │
+ │ Spec Ran: blacklist_hosts_spec.coffee │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
(Video)
- - Started processing: Compressing to 32 CRF
- - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds)
+ - Started processing: Compressing to 32 CRF
+ - Finished processing: /XXX/XXX/XXX/cypress/videos/blacklist_hosts_spec.coffee.mp4 (X second)
====================================================================================================
@@ -50,11 +50,11 @@ exports['e2e blacklist passes 1'] = `
(Run Finished)
- Spec Tests Passing Failing Pending Skipped
+ Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ ✔ blacklist_hosts_spec.coffee XX:XX 1 1 - - - │
+ │ ✔ blacklist_hosts_spec.coffee XX:XX 1 1 - - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
- All specs passed! XX:XX 1 1 - - -
+ ✔ All specs passed! XX:XX 1 1 - - -
`
diff --git a/packages/server/__snapshots__/1_browserify_babel_es201spec.coffee.js b/packages/server/__snapshots__/1_browserify_babel_es201spec.coffee.js
index 935e693db51..aa760b12e4a 100644
--- a/packages/server/__snapshots__/1_browserify_babel_es201spec.coffee.js
+++ b/packages/server/__snapshots__/1_browserify_babel_es201spec.coffee.js
@@ -14,7 +14,7 @@ exports['e2e browserify, babel, es2015 passes 1'] = `
────────────────────────────────────────────────────────────────────────────────────────────────────
- Running: browserify_babel_es2015_passing_spec.coffee... (1 of 1)
+ Running: browserify_babel_es2015_passing_spec.coffee (1 of 1)
imports work
@@ -28,23 +28,24 @@ exports['e2e browserify, babel, es2015 passes 1'] = `
(Results)
- ┌───────────────────────────────────────────────────────────┐
- │ Tests: 3 │
- │ Passing: 3 │
- │ Failing: 0 │
- │ Pending: 0 │
- │ Skipped: 0 │
- │ Screenshots: 0 │
- │ Video: true │
- │ Duration: X seconds │
- │ Spec Ran: browserify_babel_es2015_passing_spec.coffee │
- └───────────────────────────────────────────────────────────┘
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ Tests: 3 │
+ │ Passing: 3 │
+ │ Failing: 0 │
+ │ Pending: 0 │
+ │ Skipped: 0 │
+ │ Screenshots: 0 │
+ │ Video: true │
+ │ Duration: X seconds │
+ │ Spec Ran: browserify_babel_es2015_passing_spec.coffee │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
(Video)
- - Started processing: Compressing to 32 CRF
- - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds)
+ - Started processing: Compressing to 32 CRF
+ - Finished processing: /XXX/XXX/XXX/cypress/videos/browserify_babel_es2015_passing (X second)
+ _spec.coffee.mp4
====================================================================================================
@@ -52,11 +53,12 @@ exports['e2e browserify, babel, es2015 passes 1'] = `
(Run Finished)
- Spec Tests Passing Failing Pending Skipped
+ Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ ✔ browserify_babel_es2015_passing_spec… XX:XX 3 3 - - - │
+ │ ✔ browserify_babel_es2015_passing_spe XX:XX 3 3 - - - │
+ │ c.coffee │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
- All specs passed! XX:XX 3 3 - - -
+ ✔ All specs passed! XX:XX 3 3 - - -
`
@@ -77,7 +79,7 @@ exports['e2e browserify, babel, es2015 fails 1'] = `
────────────────────────────────────────────────────────────────────────────────────────────────────
- Running: browserify_babel_es2015_failing_spec.js... (1 of 1)
+ Running: browserify_babel_es2015_failing_spec.js (1 of 1)
Oops...we found an error preparing this test file:
@@ -100,23 +102,24 @@ Fix the error in your code and re-run your tests.
(Results)
- ┌───────────────────────────────────────────────────────┐
- │ Tests: 0 │
- │ Passing: 0 │
- │ Failing: 1 │
- │ Pending: 0 │
- │ Skipped: 0 │
- │ Screenshots: 0 │
- │ Video: true │
- │ Duration: X seconds │
- │ Spec Ran: browserify_babel_es2015_failing_spec.js │
- └───────────────────────────────────────────────────────┘
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ Tests: 0 │
+ │ Passing: 0 │
+ │ Failing: 1 │
+ │ Pending: 0 │
+ │ Skipped: 0 │
+ │ Screenshots: 0 │
+ │ Video: true │
+ │ Duration: X seconds │
+ │ Spec Ran: browserify_babel_es2015_failing_spec.js │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
(Video)
- - Started processing: Compressing to 32 CRF
- - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds)
+ - Started processing: Compressing to 32 CRF
+ - Finished processing: /XXX/XXX/XXX/cypress/videos/browserify_babel_es2015_failing (X second)
+ _spec.js.mp4
====================================================================================================
@@ -124,11 +127,12 @@ Fix the error in your code and re-run your tests.
(Run Finished)
- Spec Tests Passing Failing Pending Skipped
+ Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ ✖ browserify_babel_es2015_failing_spec… XX:XX - - 1 - - │
+ │ ✖ browserify_babel_es2015_failing_spe XX:XX - - 1 - - │
+ │ c.js │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
- 1 of 1 failed (100%) XX:XX - - 1 - -
+ ✖ 1 of 1 failed (100%) XX:XX - - 1 - -
`
diff --git a/packages/server/__snapshots__/1_busted_support_file_spec.coffee.js b/packages/server/__snapshots__/1_busted_support_file_spec.coffee.js
index 86b5a9e76eb..5e07b496553 100644
--- a/packages/server/__snapshots__/1_busted_support_file_spec.coffee.js
+++ b/packages/server/__snapshots__/1_busted_support_file_spec.coffee.js
@@ -13,7 +13,7 @@ exports['e2e busted support file passes 1'] = `
────────────────────────────────────────────────────────────────────────────────────────────────────
- Running: app_spec.coffee... (1 of 1)
+ Running: app_spec.coffee (1 of 1)
Oops...we found an error preparing this test file:
@@ -32,23 +32,23 @@ Fix the error in your code and re-run your tests.
(Results)
- ┌───────────────────────────────┐
- │ Tests: 0 │
- │ Passing: 0 │
- │ Failing: 1 │
- │ Pending: 0 │
- │ Skipped: 0 │
- │ Screenshots: 0 │
- │ Video: true │
- │ Duration: X seconds │
- │ Spec Ran: app_spec.coffee │
- └───────────────────────────────┘
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ Tests: 0 │
+ │ Passing: 0 │
+ │ Failing: 1 │
+ │ Pending: 0 │
+ │ Skipped: 0 │
+ │ Screenshots: 0 │
+ │ Video: true │
+ │ Duration: X seconds │
+ │ Spec Ran: app_spec.coffee │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
(Video)
- - Started processing: Compressing to 32 CRF
- - Finished processing: /foo/bar/.projects/busted-support-file/cypress/videos/abc123.mp4 (X seconds)
+ - Started processing: Compressing to 32 CRF
+ - Finished processing: /XXX/XXX/XXX/cypress/videos/app_spec.coffee.mp4 (X second)
====================================================================================================
@@ -56,11 +56,11 @@ Fix the error in your code and re-run your tests.
(Run Finished)
- Spec Tests Passing Failing Pending Skipped
+ Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ ✖ app_spec.coffee XX:XX - - 1 - - │
+ │ ✖ app_spec.coffee XX:XX - - 1 - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
- 1 of 1 failed (100%) XX:XX - - 1 - -
+ ✖ 1 of 1 failed (100%) XX:XX - - 1 - -
`
diff --git a/packages/server/__snapshots__/1_cache_spec.coffee.js b/packages/server/__snapshots__/1_cache_spec.coffee.js
index cd339a9de68..e3cf20c3caf 100644
--- a/packages/server/__snapshots__/1_cache_spec.coffee.js
+++ b/packages/server/__snapshots__/1_cache_spec.coffee.js
@@ -14,7 +14,7 @@ exports['e2e cache passes 1'] = `
────────────────────────────────────────────────────────────────────────────────────────────────────
- Running: cache_spec.coffee... (1 of 1)
+ Running: cache_spec.coffee (1 of 1)
caching
@@ -29,23 +29,23 @@ exports['e2e cache passes 1'] = `
(Results)
- ┌─────────────────────────────────┐
- │ Tests: 4 │
- │ Passing: 4 │
- │ Failing: 0 │
- │ Pending: 0 │
- │ Skipped: 0 │
- │ Screenshots: 0 │
- │ Video: true │
- │ Duration: X seconds │
- │ Spec Ran: cache_spec.coffee │
- └─────────────────────────────────┘
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ Tests: 4 │
+ │ Passing: 4 │
+ │ Failing: 0 │
+ │ Pending: 0 │
+ │ Skipped: 0 │
+ │ Screenshots: 0 │
+ │ Video: true │
+ │ Duration: X seconds │
+ │ Spec Ran: cache_spec.coffee │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
(Video)
- - Started processing: Compressing to 32 CRF
- - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds)
+ - Started processing: Compressing to 32 CRF
+ - Finished processing: /XXX/XXX/XXX/cypress/videos/cache_spec.coffee.mp4 (X second)
====================================================================================================
@@ -53,11 +53,11 @@ exports['e2e cache passes 1'] = `
(Run Finished)
- Spec Tests Passing Failing Pending Skipped
+ Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ ✔ cache_spec.coffee XX:XX 4 4 - - - │
+ │ ✔ cache_spec.coffee XX:XX 4 4 - - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
- All specs passed! XX:XX 4 4 - - -
+ ✔ All specs passed! XX:XX 4 4 - - -
`
diff --git a/packages/server/__snapshots__/1_caught_uncaught_hook_errors_spec.coffee.js b/packages/server/__snapshots__/1_caught_uncaught_hook_errors_spec.coffee.js
index ae3b6add211..7c7ac5929ae 100644
--- a/packages/server/__snapshots__/1_caught_uncaught_hook_errors_spec.coffee.js
+++ b/packages/server/__snapshots__/1_caught_uncaught_hook_errors_spec.coffee.js
@@ -14,7 +14,7 @@ exports['e2e caught and uncaught hooks errors failing1 1'] = `
────────────────────────────────────────────────────────────────────────────────────────────────────
- Running: hook_caught_error_failing_spec.coffee... (1 of 1)
+ Running: hook_caught_error_failing_spec.coffee (1 of 1)
✓ t1a
@@ -56,7 +56,6 @@ Because this error occurred during a 'before each' hook we are skipping the rema
at stack trace line
at stack trace line
at stack trace line
- at stack trace line
2) s3a "before all" hook for "t8a":
Error: s3a before hook failed
@@ -75,30 +74,34 @@ Because this error occurred during a 'before all' hook we are skipping the remai
(Results)
- ┌─────────────────────────────────────────────────────┐
- │ Tests: 11 │
- │ Passing: 5 │
- │ Failing: 3 │
- │ Pending: 0 │
- │ Skipped: 3 │
- │ Screenshots: 3 │
- │ Video: true │
- │ Duration: X seconds │
- │ Spec Ran: hook_caught_error_failing_spec.coffee │
- └─────────────────────────────────────────────────────┘
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ Tests: 11 │
+ │ Passing: 5 │
+ │ Failing: 3 │
+ │ Pending: 0 │
+ │ Skipped: 3 │
+ │ Screenshots: 3 │
+ │ Video: true │
+ │ Duration: X seconds │
+ │ Spec Ran: hook_caught_error_failing_spec.coffee │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
(Screenshots)
- - /foo/bar/.projects/e2e/cypress/screenshots/hook_caught_error_failing_spec.coffee/s1a -- t2a -- before each hook (failed).png (1280x720)
- - /foo/bar/.projects/e2e/cypress/screenshots/hook_caught_error_failing_spec.coffee/s3a -- t8a -- before all hook (failed).png (1280x720)
- - /foo/bar/.projects/e2e/cypress/screenshots/hook_caught_error_failing_spec.coffee/s4a -- t10a -- before all hook (failed).png (1280x720)
+ - /XXX/XXX/XXX/cypress/screenshots/hook_caught_error_failing_spec.coffee/s1a -- t2 (1280x720)
+ a -- before each hook (failed).png
+ - /XXX/XXX/XXX/cypress/screenshots/hook_caught_error_failing_spec.coffee/s3a -- t8 (1280x720)
+ a -- before all hook (failed).png
+ - /XXX/XXX/XXX/cypress/screenshots/hook_caught_error_failing_spec.coffee/s4a -- t1 (1280x720)
+ 0a -- before all hook (failed).png
(Video)
- - Started processing: Compressing to 32 CRF
- - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds)
+ - Started processing: Compressing to 32 CRF
+ - Finished processing: /XXX/XXX/XXX/cypress/videos/hook_caught_error_failing_spec. (X second)
+ coffee.mp4
====================================================================================================
@@ -106,11 +109,12 @@ Because this error occurred during a 'before all' hook we are skipping the remai
(Run Finished)
- Spec Tests Passing Failing Pending Skipped
+ Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ ✖ hook_caught_error_failing_spec.coffee XX:XX 11 5 3 - 3 │
+ │ ✖ hook_caught_error_failing_spec.coff XX:XX 11 5 3 - 3 │
+ │ ee │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
- 1 of 1 failed (100%) XX:XX 11 5 3 - 3
+ ✖ 1 of 1 failed (100%) XX:XX 11 5 3 - 3
`
@@ -131,7 +135,7 @@ exports['e2e caught and uncaught hooks errors failing2 1'] = `
────────────────────────────────────────────────────────────────────────────────────────────────────
- Running: hook_uncaught_error_failing_spec.coffee... (1 of 1)
+ Running: hook_uncaught_error_failing_spec.coffee (1 of 1)
✓ t1b
@@ -166,28 +170,30 @@ Because this error occurred during a 'before each' hook we are skipping the rema
(Results)
- ┌───────────────────────────────────────────────────────┐
- │ Tests: 7 │
- │ Passing: 4 │
- │ Failing: 1 │
- │ Pending: 0 │
- │ Skipped: 2 │
- │ Screenshots: 1 │
- │ Video: true │
- │ Duration: X seconds │
- │ Spec Ran: hook_uncaught_error_failing_spec.coffee │
- └───────────────────────────────────────────────────────┘
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ Tests: 7 │
+ │ Passing: 4 │
+ │ Failing: 1 │
+ │ Pending: 0 │
+ │ Skipped: 2 │
+ │ Screenshots: 1 │
+ │ Video: true │
+ │ Duration: X seconds │
+ │ Spec Ran: hook_uncaught_error_failing_spec.coffee │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
(Screenshots)
- - /foo/bar/.projects/e2e/cypress/screenshots/hook_uncaught_error_failing_spec.coffee/s1b -- t2b -- before each hook (failed).png (1280x720)
+ - /XXX/XXX/XXX/cypress/screenshots/hook_uncaught_error_failing_spec.coffee/s1b -- (1280x720)
+ t2b -- before each hook (failed).png
(Video)
- - Started processing: Compressing to 32 CRF
- - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds)
+ - Started processing: Compressing to 32 CRF
+ - Finished processing: /XXX/XXX/XXX/cypress/videos/hook_uncaught_error_failing_spe (X second)
+ c.coffee.mp4
====================================================================================================
@@ -195,11 +201,12 @@ Because this error occurred during a 'before each' hook we are skipping the rema
(Run Finished)
- Spec Tests Passing Failing Pending Skipped
+ Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ ✖ hook_uncaught_error_failing_spec.cof… XX:XX 7 4 1 - 2 │
+ │ ✖ hook_uncaught_error_failing_spec.co XX:XX 7 4 1 - 2 │
+ │ ffee │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
- 1 of 1 failed (100%) XX:XX 7 4 1 - 2
+ ✖ 1 of 1 failed (100%) XX:XX 7 4 1 - 2
`
@@ -220,7 +227,7 @@ exports['e2e caught and uncaught hooks errors failing3 1'] = `
────────────────────────────────────────────────────────────────────────────────────────────────────
- Running: hook_uncaught_root_error_failing_spec.coffee... (1 of 1)
+ Running: hook_uncaught_root_error_failing_spec.coffee (1 of 1)
1) "before each" hook for "t1c"
@@ -247,28 +254,30 @@ Because this error occurred during a 'before each' hook we are skipping all of t
(Results)
- ┌────────────────────────────────────────────────────────────┐
- │ Tests: 4 │
- │ Passing: 0 │
- │ Failing: 1 │
- │ Pending: 0 │
- │ Skipped: 3 │
- │ Screenshots: 1 │
- │ Video: true │
- │ Duration: X seconds │
- │ Spec Ran: hook_uncaught_root_error_failing_spec.coffee │
- └────────────────────────────────────────────────────────────┘
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ Tests: 4 │
+ │ Passing: 0 │
+ │ Failing: 1 │
+ │ Pending: 0 │
+ │ Skipped: 3 │
+ │ Screenshots: 1 │
+ │ Video: true │
+ │ Duration: X seconds │
+ │ Spec Ran: hook_uncaught_root_error_failing_spec.coffee │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
(Screenshots)
- - /foo/bar/.projects/e2e/cypress/screenshots/hook_uncaught_root_error_failing_spec.coffee/t1c -- before each hook (failed).png (1280x720)
+ - /XXX/XXX/XXX/cypress/screenshots/hook_uncaught_root_error_failing_spec.coffee/t1 (1280x720)
+ c -- before each hook (failed).png
(Video)
- - Started processing: Compressing to 32 CRF
- - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds)
+ - Started processing: Compressing to 32 CRF
+ - Finished processing: /XXX/XXX/XXX/cypress/videos/hook_uncaught_root_error_failin (X second)
+ g_spec.coffee.mp4
====================================================================================================
@@ -276,11 +285,12 @@ Because this error occurred during a 'before each' hook we are skipping all of t
(Run Finished)
- Spec Tests Passing Failing Pending Skipped
+ Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ ✖ hook_uncaught_root_error_failing_spe… XX:XX 4 - 1 - 3 │
+ │ ✖ hook_uncaught_root_error_failing_sp XX:XX 4 - 1 - 3 │
+ │ ec.coffee │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
- 1 of 1 failed (100%) XX:XX 4 - 1 - 3
+ ✖ 1 of 1 failed (100%) XX:XX 4 - 1 - 3
`
@@ -301,7 +311,7 @@ exports['e2e caught and uncaught hooks errors failing4 1'] = `
────────────────────────────────────────────────────────────────────────────────────────────────────
- Running: hook_uncaught_error_events_failing_spec.coffee... (1 of 1)
+ Running: hook_uncaught_error_events_failing_spec.coffee (1 of 1)
uncaught hook error should continue to fire all mocha events
@@ -334,28 +344,31 @@ Because this error occurred during a 'before each' hook we are skipping the rema
(Results)
- ┌──────────────────────────────────────────────────────────────┐
- │ Tests: 3 │
- │ Passing: 2 │
- │ Failing: 1 │
- │ Pending: 0 │
- │ Skipped: 0 │
- │ Screenshots: 1 │
- │ Video: true │
- │ Duration: X seconds │
- │ Spec Ran: hook_uncaught_error_events_failing_spec.coffee │
- └──────────────────────────────────────────────────────────────┘
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ Tests: 3 │
+ │ Passing: 2 │
+ │ Failing: 1 │
+ │ Pending: 0 │
+ │ Skipped: 0 │
+ │ Screenshots: 1 │
+ │ Video: true │
+ │ Duration: X seconds │
+ │ Spec Ran: hook_uncaught_error_events_failing_spec.coffee │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
(Screenshots)
- - /foo/bar/.projects/e2e/cypress/screenshots/hook_uncaught_error_events_failing_spec.coffee/uncaught hook error should continue to fire all mocha events -- s1 -- does not run -- before each hook (failed).png (1280x720)
+ - /XXX/XXX/XXX/cypress/screenshots/hook_uncaught_error_events_failing_spec.coffee/ (1280x720)
+ uncaught hook error should continue to fire all mocha events -- s1 -- does not r
+ un -- before each hook (failed).png
(Video)
- - Started processing: Compressing to 32 CRF
- - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds)
+ - Started processing: Compressing to 32 CRF
+ - Finished processing: /XXX/XXX/XXX/cypress/videos/hook_uncaught_error_events_fail (X second)
+ ing_spec.coffee.mp4
====================================================================================================
@@ -363,11 +376,12 @@ Because this error occurred during a 'before each' hook we are skipping the rema
(Run Finished)
- Spec Tests Passing Failing Pending Skipped
+ Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ ✖ hook_uncaught_error_events_failing_s… XX:XX 3 2 1 - - │
+ │ ✖ hook_uncaught_error_events_failing_ XX:XX 3 2 1 - - │
+ │ spec.coffee │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
- 1 of 1 failed (100%) XX:XX 3 2 1 - -
+ ✖ 1 of 1 failed (100%) XX:XX 3 2 1 - -
`
diff --git a/packages/server/__snapshots__/1_commands_outside_of_test_spec.coffee.js b/packages/server/__snapshots__/1_commands_outside_of_test_spec.coffee.js
index b236eeef1de..51f64d5e4de 100644
--- a/packages/server/__snapshots__/1_commands_outside_of_test_spec.coffee.js
+++ b/packages/server/__snapshots__/1_commands_outside_of_test_spec.coffee.js
@@ -1,4 +1,4 @@
-exports['e2e commands outside of test passes on passing assertions 1'] = `
+exports['e2e commands outside of test / passes on passing assertions'] = `
====================================================================================================
@@ -14,7 +14,7 @@ exports['e2e commands outside of test passes on passing assertions 1'] = `
────────────────────────────────────────────────────────────────────────────────────────────────────
- Running: assertions_passing_outside_of_test_spec.coffee... (1 of 1)
+ Running: assertions_passing_outside_of_test_spec.coffee (1 of 1)
0 passing
@@ -22,23 +22,24 @@ exports['e2e commands outside of test passes on passing assertions 1'] = `
(Results)
- ┌──────────────────────────────────────────────────────────────┐
- │ Tests: 0 │
- │ Passing: 0 │
- │ Failing: 0 │
- │ Pending: 0 │
- │ Skipped: 0 │
- │ Screenshots: 0 │
- │ Video: true │
- │ Duration: X seconds │
- │ Spec Ran: assertions_passing_outside_of_test_spec.coffee │
- └──────────────────────────────────────────────────────────────┘
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ Tests: 0 │
+ │ Passing: 0 │
+ │ Failing: 0 │
+ │ Pending: 0 │
+ │ Skipped: 0 │
+ │ Screenshots: 0 │
+ │ Video: true │
+ │ Duration: X seconds │
+ │ Spec Ran: assertions_passing_outside_of_test_spec.coffee │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
(Video)
- - Started processing: Compressing to 32 CRF
- - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds)
+ - Started processing: Compressing to 32 CRF
+ - Finished processing: /XXX/XXX/XXX/cypress/videos/assertions_passing_outside_of_t (X second)
+ est_spec.coffee.mp4
====================================================================================================
@@ -46,16 +47,17 @@ exports['e2e commands outside of test passes on passing assertions 1'] = `
(Run Finished)
- Spec Tests Passing Failing Pending Skipped
+ Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ ✔ assertions_passing_outside_of_test_s… XX:XX - - - - - │
+ │ ✔ assertions_passing_outside_of_test_ XX:XX - - - - - │
+ │ spec.coffee │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
- All specs passed! XX:XX - - - - -
+ ✔ All specs passed! XX:XX - - - - -
`
-exports['e2e commands outside of test [chrome] fails on cy commands 1'] = `
+exports['e2e commands outside of test / fails on failing assertions'] = `
====================================================================================================
@@ -64,20 +66,14 @@ exports['e2e commands outside of test [chrome] fails on cy commands 1'] = `
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Cypress: 1.2.3 │
│ Browser: FooBrowser 88 │
- │ Specs: 1 found (commands_outside_of_test_spec.coffee) │
- │ Searched: cypress/integration/commands_outside_of_test_spec.coffee │
+ │ Specs: 1 found (assertions_failing_outside_of_test_spec.coffee) │
+ │ Searched: cypress/integration/assertions_failing_outside_of_test_spec.coffee │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
────────────────────────────────────────────────────────────────────────────────────────────────────
- Running: commands_outside_of_test_spec.coffee... (1 of 1)
-
-Warning: Cypress can only record videos when using the built in 'electron' browser.
-
-You have set the browser to: 'chrome'
-
-A video will not be recorded when using this browser.
+ Running: assertions_failing_outside_of_test_spec.coffee (1 of 1)
1) An uncaught error was detected outside of a test
@@ -86,15 +82,7 @@ A video will not be recorded when using this browser.
1 failing
1) An uncaught error was detected outside of a test:
- Uncaught CypressError: Cannot call "cy.viewport()" outside a running test.
-
-This usually happens when you accidentally write commands outside an it(...) test.
-
-If that is the case, just move these commands inside an it(...) test.
-
-Check your test file for errors.
-
-https://on.cypress.io/cannot-execute-commands-outside-test
+ expected true to be false
This error originated from your test code, not from Cypress.
@@ -103,35 +91,37 @@ When Cypress detects uncaught errors originating from your test code it will aut
Cypress could not associate this error to any specific test.
We dynamically generated a new test to display this failure.
- at stack trace line
- at stack trace line
- at stack trace line
- at stack trace line
- at stack trace line
- at stack trace line
- at stack trace line
+ AssertionError: expected true to be false
(Results)
- ┌────────────────────────────────────────────────────┐
- │ Tests: 1 │
- │ Passing: 0 │
- │ Failing: 1 │
- │ Pending: 0 │
- │ Skipped: 0 │
- │ Screenshots: 1 │
- │ Video: false │
- │ Duration: X seconds │
- │ Spec Ran: commands_outside_of_test_spec.coffee │
- └────────────────────────────────────────────────────┘
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ Tests: 1 │
+ │ Passing: 0 │
+ │ Failing: 1 │
+ │ Pending: 0 │
+ │ Skipped: 0 │
+ │ Screenshots: 1 │
+ │ Video: true │
+ │ Duration: X seconds │
+ │ Spec Ran: assertions_failing_outside_of_test_spec.coffee │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
(Screenshots)
- - /foo/bar/.projects/e2e/cypress/screenshots/commands_outside_of_test_spec.coffee/An uncaught error was detected outside of a test (failed).png (YYYYxZZZZ)
+ - /XXX/XXX/XXX/cypress/screenshots/assertions_failing_outside_of_test_spec.coffee/ (1280x720)
+ An uncaught error was detected outside of a test (failed).png
+
+
+ (Video)
+
+ - Started processing: Compressing to 32 CRF
+ - Finished processing: /XXX/XXX/XXX/cypress/videos/assertions_failing_outside_of_t (X second)
+ est_spec.coffee.mp4
====================================================================================================
@@ -139,16 +129,17 @@ We dynamically generated a new test to display this failure.
(Run Finished)
- Spec Tests Passing Failing Pending Skipped
+ Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ ✖ commands_outside_of_test_spec.coffee XX:XX 1 - 1 - - │
+ │ ✖ assertions_failing_outside_of_test_ XX:XX 1 - 1 - - │
+ │ spec.coffee │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
- 1 of 1 failed (100%) XX:XX 1 - 1 - -
+ ✖ 1 of 1 failed (100%) XX:XX 1 - 1 - -
`
-exports['e2e commands outside of test [chrome] fails on failing assertions 1'] = `
+exports['e2e commands outside of test / fails on cy commands'] = `
====================================================================================================
@@ -157,20 +148,14 @@ exports['e2e commands outside of test [chrome] fails on failing assertions 1'] =
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Cypress: 1.2.3 │
│ Browser: FooBrowser 88 │
- │ Specs: 1 found (assertions_failing_outside_of_test_spec.coffee) │
- │ Searched: cypress/integration/assertions_failing_outside_of_test_spec.coffee │
+ │ Specs: 1 found (commands_outside_of_test_spec.coffee) │
+ │ Searched: cypress/integration/commands_outside_of_test_spec.coffee │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
────────────────────────────────────────────────────────────────────────────────────────────────────
- Running: assertions_failing_outside_of_test_spec.coffee... (1 of 1)
-
-Warning: Cypress can only record videos when using the built in 'electron' browser.
-
-You have set the browser to: 'chrome'
-
-A video will not be recorded when using this browser.
+ Running: commands_outside_of_test_spec.coffee (1 of 1)
1) An uncaught error was detected outside of a test
@@ -179,7 +164,15 @@ A video will not be recorded when using this browser.
1 failing
1) An uncaught error was detected outside of a test:
- expected true to be false
+ Uncaught CypressError: Cannot call "cy.viewport()" outside a running test.
+
+This usually happens when you accidentally write commands outside an it(...) test.
+
+If that is the case, just move these commands inside an it(...) test.
+
+Check your test file for errors.
+
+https://on.cypress.io/cannot-execute-commands-outside-test
This error originated from your test code, not from Cypress.
@@ -188,29 +181,43 @@ When Cypress detects uncaught errors originating from your test code it will aut
Cypress could not associate this error to any specific test.
We dynamically generated a new test to display this failure.
- AssertionError: expected true to be false
+ at stack trace line
+ at stack trace line
+ at stack trace line
+ at stack trace line
+ at stack trace line
+ at stack trace line
+ at stack trace line
(Results)
- ┌──────────────────────────────────────────────────────────────┐
- │ Tests: 1 │
- │ Passing: 0 │
- │ Failing: 1 │
- │ Pending: 0 │
- │ Skipped: 0 │
- │ Screenshots: 1 │
- │ Video: false │
- │ Duration: X seconds │
- │ Spec Ran: assertions_failing_outside_of_test_spec.coffee │
- └──────────────────────────────────────────────────────────────┘
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ Tests: 1 │
+ │ Passing: 0 │
+ │ Failing: 1 │
+ │ Pending: 0 │
+ │ Skipped: 0 │
+ │ Screenshots: 1 │
+ │ Video: true │
+ │ Duration: X seconds │
+ │ Spec Ran: commands_outside_of_test_spec.coffee │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
(Screenshots)
- - /foo/bar/.projects/e2e/cypress/screenshots/assertions_failing_outside_of_test_spec.coffee/An uncaught error was detected outside of a test (failed).png (YYYYxZZZZ)
+ - /XXX/XXX/XXX/cypress/screenshots/commands_outside_of_test_spec.coffee/An uncaugh (1280x720)
+ t error was detected outside of a test (failed).png
+
+
+ (Video)
+
+ - Started processing: Compressing to 32 CRF
+ - Finished processing: /XXX/XXX/XXX/cypress/videos/commands_outside_of_test_spec.c (X second)
+ offee.mp4
====================================================================================================
@@ -218,11 +225,12 @@ We dynamically generated a new test to display this failure.
(Run Finished)
- Spec Tests Passing Failing Pending Skipped
+ Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ ✖ assertions_failing_outside_of_test_s… XX:XX 1 - 1 - - │
+ │ ✖ commands_outside_of_test_spec.coffe XX:XX 1 - 1 - - │
+ │ e │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
- 1 of 1 failed (100%) XX:XX 1 - 1 - -
+ ✖ 1 of 1 failed (100%) XX:XX 1 - 1 - -
`
diff --git a/packages/server/__snapshots__/1_interception_spec.coffee.js b/packages/server/__snapshots__/1_interception_spec.coffee.js
index 210034a534d..6c0a0ee3ae3 100644
--- a/packages/server/__snapshots__/1_interception_spec.coffee.js
+++ b/packages/server/__snapshots__/1_interception_spec.coffee.js
@@ -14,7 +14,7 @@ exports['e2e interception spec character encodings does not mangle non-UTF-8 tex
────────────────────────────────────────────────────────────────────────────────────────────────────
- Running: character_encoding_spec.js... (1 of 1)
+ Running: character_encoding_spec.js (1 of 1)
character encoding tests
@@ -45,23 +45,23 @@ exports['e2e interception spec character encodings does not mangle non-UTF-8 tex
(Results)
- ┌──────────────────────────────────────────┐
- │ Tests: 16 │
- │ Passing: 16 │
- │ Failing: 0 │
- │ Pending: 0 │
- │ Skipped: 0 │
- │ Screenshots: 0 │
- │ Video: true │
- │ Duration: X seconds │
- │ Spec Ran: character_encoding_spec.js │
- └──────────────────────────────────────────┘
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ Tests: 16 │
+ │ Passing: 16 │
+ │ Failing: 0 │
+ │ Pending: 0 │
+ │ Skipped: 0 │
+ │ Screenshots: 0 │
+ │ Video: true │
+ │ Duration: X seconds │
+ │ Spec Ran: character_encoding_spec.js │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
(Video)
- - Started processing: Compressing to 32 CRF
- - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds)
+ - Started processing: Compressing to 32 CRF
+ - Finished processing: /XXX/XXX/XXX/cypress/videos/character_encoding_spec.js.mp4 (X second)
====================================================================================================
@@ -69,11 +69,11 @@ exports['e2e interception spec character encodings does not mangle non-UTF-8 tex
(Run Finished)
- Spec Tests Passing Failing Pending Skipped
+ Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ ✔ character_encoding_spec.js XX:XX 16 16 - - - │
+ │ ✔ character_encoding_spec.js XX:XX 16 16 - - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
- All specs passed! XX:XX 16 16 - - -
+ ✔ All specs passed! XX:XX 16 16 - - -
`
diff --git a/packages/server/__snapshots__/1_system_node_spec.coffee.js b/packages/server/__snapshots__/1_system_node_spec.coffee.js
new file mode 100644
index 00000000000..f7cc6e82f5c
--- /dev/null
+++ b/packages/server/__snapshots__/1_system_node_spec.coffee.js
@@ -0,0 +1,59 @@
+exports['e2e system node uses system node when launching plugins file 1'] = `
+
+====================================================================================================
+
+ (Run Starting)
+
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ Cypress: 1.2.3 │
+ │ Browser: FooBrowser 88 │
+ │ Node Version: vX (/foo/bar/node) │
+ │ Specs: 1 found (spec.js) │
+ │ Searched: cypress/integration/spec.js │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
+
+
+────────────────────────────────────────────────────────────────────────────────────────────────────
+
+ Running: spec.js (1 of 1)
+
+
+ ✓ has expected resolvedNodePath and resolvedNodeVersion
+
+ 1 passing
+
+
+ (Results)
+
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ Tests: 1 │
+ │ Passing: 1 │
+ │ Failing: 0 │
+ │ Pending: 0 │
+ │ Skipped: 0 │
+ │ Screenshots: 0 │
+ │ Video: true │
+ │ Duration: X seconds │
+ │ Spec Ran: spec.js │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
+
+
+ (Video)
+
+ - Started processing: Compressing to 32 CRF
+ - Finished processing: /XXX/XXX/XXX/cypress/videos/spec.js.mp4 (X second)
+
+
+====================================================================================================
+
+ (Run Finished)
+
+
+ Spec Tests Passing Failing Pending Skipped
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ ✔ spec.js XX:XX 1 1 - - - │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
+ ✔ All specs passed! XX:XX 1 1 - - -
+
+
+`
diff --git a/packages/server/__snapshots__/2_cdp_spec.ts.js b/packages/server/__snapshots__/2_cdp_spec.ts.js
new file mode 100644
index 00000000000..29bcc77870c
--- /dev/null
+++ b/packages/server/__snapshots__/2_cdp_spec.ts.js
@@ -0,0 +1,45 @@
+exports['e2e cdp / fails when remote debugging port cannot be connected to'] = `
+
+====================================================================================================
+
+ (Run Starting)
+
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ Cypress: 1.2.3 │
+ │ Browser: FooBrowser 88 │
+ │ Specs: 1 found (spec.ts) │
+ │ Searched: cypress/integration/spec.ts │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
+
+
+────────────────────────────────────────────────────────────────────────────────────────────────────
+
+ Running: spec.ts (1 of 1)
+Failed to connect to Chrome, retrying in 1 second (attempt 18/32)
+Failed to connect to Chrome, retrying in 1 second (attempt 19/32)
+Failed to connect to Chrome, retrying in 1 second (attempt 20/32)
+Failed to connect to Chrome, retrying in 1 second (attempt 21/32)
+Failed to connect to Chrome, retrying in 1 second (attempt 22/32)
+Failed to connect to Chrome, retrying in 1 second (attempt 23/32)
+Failed to connect to Chrome, retrying in 1 second (attempt 24/32)
+Failed to connect to Chrome, retrying in 1 second (attempt 25/32)
+Failed to connect to Chrome, retrying in 1 second (attempt 26/32)
+Failed to connect to Chrome, retrying in 1 second (attempt 27/32)
+Failed to connect to Chrome, retrying in 1 second (attempt 28/32)
+Failed to connect to Chrome, retrying in 1 second (attempt 29/32)
+Failed to connect to Chrome, retrying in 1 second (attempt 30/32)
+Failed to connect to Chrome, retrying in 1 second (attempt 31/32)
+Failed to connect to Chrome, retrying in 1 second (attempt 32/32)
+Cypress failed to make a connection to the Chrome DevTools Protocol after retrying for 20 seconds.
+
+This usually indicates there was a problem opening the Chrome browser.
+
+The CDP port requested was 7777.
+
+Error details:
+
+Error: connect ECONNREFUSED 127.0.0.1:7777
+ at stack trace line
+
+
+`
diff --git a/packages/server/__snapshots__/2_controllers_spec.coffee.js b/packages/server/__snapshots__/2_controllers_spec.coffee.js
index 2fa9234c53b..a333b179195 100644
--- a/packages/server/__snapshots__/2_controllers_spec.coffee.js
+++ b/packages/server/__snapshots__/2_controllers_spec.coffee.js
@@ -14,7 +14,7 @@ exports['e2e plugins fails when spec does not exist 1'] = `
────────────────────────────────────────────────────────────────────────────────────────────────────
- Running: spec.js... (1 of 1)
+ Running: spec.js (1 of 1)
Oops...we found an error preparing this test file:
@@ -33,23 +33,23 @@ Fix the error in your code and re-run your tests.
(Results)
- ┌─────────────────────────┐
- │ Tests: 0 │
- │ Passing: 0 │
- │ Failing: 1 │
- │ Pending: 0 │
- │ Skipped: 0 │
- │ Screenshots: 0 │
- │ Video: true │
- │ Duration: X seconds │
- │ Spec Ran: spec.js │
- └─────────────────────────┘
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ Tests: 0 │
+ │ Passing: 0 │
+ │ Failing: 1 │
+ │ Pending: 0 │
+ │ Skipped: 0 │
+ │ Screenshots: 0 │
+ │ Video: true │
+ │ Duration: X seconds │
+ │ Spec Ran: spec.js │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
(Video)
- - Started processing: Compressing to 32 CRF
- - Finished processing: /foo/bar/.projects/non-existent-spec/cypress/videos/abc123.mp4 (X seconds)
+ - Started processing: Compressing to 32 CRF
+ - Finished processing: /XXX/XXX/XXX/cypress/videos/spec.js.mp4 (X second)
====================================================================================================
@@ -57,11 +57,11 @@ Fix the error in your code and re-run your tests.
(Run Finished)
- Spec Tests Passing Failing Pending Skipped
+ Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ ✖ spec.js XX:XX - - 1 - - │
+ │ ✖ spec.js XX:XX - - 1 - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
- 1 of 1 failed (100%) XX:XX - - 1 - -
+ ✖ 1 of 1 failed (100%) XX:XX - - 1 - -
`
diff --git a/packages/server/__snapshots__/2_cookies_spec.coffee.js b/packages/server/__snapshots__/2_cookies_spec.coffee.js
index a83a425d4de..b73ad3b5df9 100644
--- a/packages/server/__snapshots__/2_cookies_spec.coffee.js
+++ b/packages/server/__snapshots__/2_cookies_spec.coffee.js
@@ -1,4 +1,4 @@
-exports['e2e cookies passes 1'] = `
+exports['e2e cookies with baseurl'] = `
====================================================================================================
@@ -7,14 +7,92 @@ exports['e2e cookies passes 1'] = `
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Cypress: 1.2.3 │
│ Browser: FooBrowser 88 │
- │ Specs: 1 found (cookies_spec.coffee) │
- │ Searched: cypress/integration/cookies_spec.coffee │
+ │ Specs: 1 found (cookies_spec_baseurl.coffee) │
+ │ Searched: cypress/integration/cookies_spec_baseurl.coffee │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
────────────────────────────────────────────────────────────────────────────────────────────────────
- Running: cookies_spec.coffee... (1 of 1)
+ Running: cookies_spec_baseurl.coffee (1 of 1)
+
+
+ cookies
+ with whitelist
+ ✓ can get all cookies
+ ✓ resets cookies between tests correctly
+ ✓ should be only two left now
+ ✓ handles undefined cookies
+ without whitelist
+ ✓ sends set cookies to path
+ ✓ handles expired cookies secure
+ ✓ issue: #224 sets expired cookies between redirects
+ ✓ issue: #1321 failing to set or parse cookie
+ ✓ issue: #2724 does not fail on invalid cookies
+ ✓ can set and clear cookie
+ in a cy.visit
+ ✓ can set cookies on way too many redirects with HTTP intermediary
+ ✓ can set cookies on way too many redirects with HTTPS intermediary
+ in a cy.request
+ ✓ can set cookies on way too many redirects with HTTP intermediary
+ ✓ can set cookies on way too many redirects with HTTPS intermediary
+
+
+ 14 passing
+
+
+ (Results)
+
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ Tests: 14 │
+ │ Passing: 14 │
+ │ Failing: 0 │
+ │ Pending: 0 │
+ │ Skipped: 0 │
+ │ Screenshots: 0 │
+ │ Video: true │
+ │ Duration: X seconds │
+ │ Spec Ran: cookies_spec_baseurl.coffee │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
+
+
+ (Video)
+
+ - Started processing: Compressing to 32 CRF
+ - Finished processing: /XXX/XXX/XXX/cypress/videos/cookies_spec_baseurl.coffee.mp4 (X second)
+
+
+====================================================================================================
+
+ (Run Finished)
+
+
+ Spec Tests Passing Failing Pending Skipped
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ ✔ cookies_spec_baseurl.coffee XX:XX 14 14 - - - │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
+ ✔ All specs passed! XX:XX 14 14 - - -
+
+
+`
+
+exports['e2e cookies with no baseurl'] = `
+
+====================================================================================================
+
+ (Run Starting)
+
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ Cypress: 1.2.3 │
+ │ Browser: FooBrowser 88 │
+ │ Specs: 1 found (cookies_spec_no_baseurl.coffee) │
+ │ Searched: cypress/integration/cookies_spec_no_baseurl.coffee │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
+
+
+────────────────────────────────────────────────────────────────────────────────────────────────────
+
+ Running: cookies_spec_no_baseurl.coffee (1 of 1)
cookies
@@ -36,23 +114,24 @@ exports['e2e cookies passes 1'] = `
(Results)
- ┌───────────────────────────────────┐
- │ Tests: 9 │
- │ Passing: 9 │
- │ Failing: 0 │
- │ Pending: 0 │
- │ Skipped: 0 │
- │ Screenshots: 0 │
- │ Video: true │
- │ Duration: X seconds │
- │ Spec Ran: cookies_spec.coffee │
- └───────────────────────────────────┘
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ Tests: 9 │
+ │ Passing: 9 │
+ │ Failing: 0 │
+ │ Pending: 0 │
+ │ Skipped: 0 │
+ │ Screenshots: 0 │
+ │ Video: true │
+ │ Duration: X seconds │
+ │ Spec Ran: cookies_spec_no_baseurl.coffee │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
(Video)
- - Started processing: Compressing to 32 CRF
- - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds)
+ - Started processing: Compressing to 32 CRF
+ - Finished processing: /XXX/XXX/XXX/cypress/videos/cookies_spec_no_baseurl.coffee. (X second)
+ mp4
====================================================================================================
@@ -60,11 +139,11 @@ exports['e2e cookies passes 1'] = `
(Run Finished)
- Spec Tests Passing Failing Pending Skipped
+ Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ ✔ cookies_spec.coffee XX:XX 9 9 - - - │
+ │ ✔ cookies_spec_no_baseurl.coffee XX:XX 9 9 - - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
- All specs passed! XX:XX 9 9 - - -
+ ✔ All specs passed! XX:XX 9 9 - - -
`
diff --git a/packages/server/__snapshots__/2_domain_spec.coffee.js b/packages/server/__snapshots__/2_domain_spec.coffee.js
index 788b96e306b..a2aa4915eeb 100644
--- a/packages/server/__snapshots__/2_domain_spec.coffee.js
+++ b/packages/server/__snapshots__/2_domain_spec.coffee.js
@@ -1,4 +1,4 @@
-exports['e2e domain passing 1'] = `
+exports['e2e domain / passes'] = `
====================================================================================================
@@ -14,7 +14,7 @@ exports['e2e domain passing 1'] = `
────────────────────────────────────────────────────────────────────────────────────────────────────
- Running: domain_2_spec.coffee... (1 of 2)
+ Running: domain_2_spec.coffee (1 of 2)
localhost
@@ -32,28 +32,28 @@ exports['e2e domain passing 1'] = `
(Results)
- ┌────────────────────────────────────┐
- │ Tests: 3 │
- │ Passing: 3 │
- │ Failing: 0 │
- │ Pending: 0 │
- │ Skipped: 0 │
- │ Screenshots: 0 │
- │ Video: true │
- │ Duration: X seconds │
- │ Spec Ran: domain_2_spec.coffee │
- └────────────────────────────────────┘
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ Tests: 3 │
+ │ Passing: 3 │
+ │ Failing: 0 │
+ │ Pending: 0 │
+ │ Skipped: 0 │
+ │ Screenshots: 0 │
+ │ Video: true │
+ │ Duration: X seconds │
+ │ Spec Ran: domain_2_spec.coffee │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
(Video)
- - Started processing: Compressing to 32 CRF
- - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds)
+ - Started processing: Compressing to 32 CRF
+ - Finished processing: /XXX/XXX/XXX/cypress/videos/domain_2_spec.coffee.mp4 (X second)
────────────────────────────────────────────────────────────────────────────────────────────────────
- Running: domain_spec.coffee... (2 of 2)
+ Running: domain_spec.coffee (2 of 2)
localhost
@@ -71,23 +71,23 @@ exports['e2e domain passing 1'] = `
(Results)
- ┌──────────────────────────────────┐
- │ Tests: 3 │
- │ Passing: 3 │
- │ Failing: 0 │
- │ Pending: 0 │
- │ Skipped: 0 │
- │ Screenshots: 0 │
- │ Video: true │
- │ Duration: X seconds │
- │ Spec Ran: domain_spec.coffee │
- └──────────────────────────────────┘
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ Tests: 3 │
+ │ Passing: 3 │
+ │ Failing: 0 │
+ │ Pending: 0 │
+ │ Skipped: 0 │
+ │ Screenshots: 0 │
+ │ Video: true │
+ │ Duration: X seconds │
+ │ Spec Ran: domain_spec.coffee │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
(Video)
- - Started processing: Compressing to 32 CRF
- - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds)
+ - Started processing: Compressing to 32 CRF
+ - Finished processing: /XXX/XXX/XXX/cypress/videos/domain_spec.coffee.mp4 (X second)
====================================================================================================
@@ -95,13 +95,13 @@ exports['e2e domain passing 1'] = `
(Run Finished)
- Spec Tests Passing Failing Pending Skipped
+ Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ ✔ domain_2_spec.coffee XX:XX 3 3 - - - │
+ │ ✔ domain_2_spec.coffee XX:XX 3 3 - - - │
├────────────────────────────────────────────────────────────────────────────────────────────────┤
- │ ✔ domain_spec.coffee XX:XX 3 3 - - - │
+ │ ✔ domain_spec.coffee XX:XX 3 3 - - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
- All specs passed! XX:XX 6 6 - - -
+ ✔ All specs passed! XX:XX 6 6 - - -
`
diff --git a/packages/server/__snapshots__/2_go_spec.coffee.js b/packages/server/__snapshots__/2_go_spec.coffee.js
index 595011135e3..e714f0f9e2e 100644
--- a/packages/server/__snapshots__/2_go_spec.coffee.js
+++ b/packages/server/__snapshots__/2_go_spec.coffee.js
@@ -1,4 +1,4 @@
-exports['e2e go passes 1'] = `
+exports['e2e go / passes'] = `
====================================================================================================
@@ -14,7 +14,7 @@ exports['e2e go passes 1'] = `
────────────────────────────────────────────────────────────────────────────────────────────────────
- Running: go_spec.coffee... (1 of 1)
+ Running: go_spec.coffee (1 of 1)
cy.go
@@ -27,23 +27,23 @@ exports['e2e go passes 1'] = `
(Results)
- ┌──────────────────────────────┐
- │ Tests: 2 │
- │ Passing: 2 │
- │ Failing: 0 │
- │ Pending: 0 │
- │ Skipped: 0 │
- │ Screenshots: 0 │
- │ Video: true │
- │ Duration: X seconds │
- │ Spec Ran: go_spec.coffee │
- └──────────────────────────────┘
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ Tests: 2 │
+ │ Passing: 2 │
+ │ Failing: 0 │
+ │ Pending: 0 │
+ │ Skipped: 0 │
+ │ Screenshots: 0 │
+ │ Video: true │
+ │ Duration: X seconds │
+ │ Spec Ran: go_spec.coffee │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
(Video)
- - Started processing: Compressing to 32 CRF
- - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds)
+ - Started processing: Compressing to 32 CRF
+ - Finished processing: /XXX/XXX/XXX/cypress/videos/go_spec.coffee.mp4 (X second)
====================================================================================================
@@ -51,11 +51,11 @@ exports['e2e go passes 1'] = `
(Run Finished)
- Spec Tests Passing Failing Pending Skipped
+ Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ ✔ go_spec.coffee XX:XX 2 2 - - - │
+ │ ✔ go_spec.coffee XX:XX 2 2 - - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
- All specs passed! XX:XX 2 2 - - -
+ ✔ All specs passed! XX:XX 2 2 - - -
`
diff --git a/packages/server/__snapshots__/2_iframe_spec.coffee.js b/packages/server/__snapshots__/2_iframe_spec.coffee.js
index 16af86e6f99..9e71154d5c6 100644
--- a/packages/server/__snapshots__/2_iframe_spec.coffee.js
+++ b/packages/server/__snapshots__/2_iframe_spec.coffee.js
@@ -1,4 +1,4 @@
-exports['e2e iframes passes 1'] = `
+exports['e2e iframes / passes'] = `
====================================================================================================
@@ -14,7 +14,7 @@ exports['e2e iframes passes 1'] = `
────────────────────────────────────────────────────────────────────────────────────────────────────
- Running: iframe_spec.coffee... (1 of 1)
+ Running: iframe_spec.coffee (1 of 1)
iframes
@@ -32,23 +32,23 @@ exports['e2e iframes passes 1'] = `
(Results)
- ┌──────────────────────────────────┐
- │ Tests: 7 │
- │ Passing: 7 │
- │ Failing: 0 │
- │ Pending: 0 │
- │ Skipped: 0 │
- │ Screenshots: 0 │
- │ Video: true │
- │ Duration: X seconds │
- │ Spec Ran: iframe_spec.coffee │
- └──────────────────────────────────┘
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ Tests: 7 │
+ │ Passing: 7 │
+ │ Failing: 0 │
+ │ Pending: 0 │
+ │ Skipped: 0 │
+ │ Screenshots: 0 │
+ │ Video: true │
+ │ Duration: X seconds │
+ │ Spec Ran: iframe_spec.coffee │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
(Video)
- - Started processing: Compressing to 32 CRF
- - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds)
+ - Started processing: Compressing to 32 CRF
+ - Finished processing: /XXX/XXX/XXX/cypress/videos/iframe_spec.coffee.mp4 (X second)
====================================================================================================
@@ -56,11 +56,11 @@ exports['e2e iframes passes 1'] = `
(Run Finished)
- Spec Tests Passing Failing Pending Skipped
+ Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ ✔ iframe_spec.coffee XX:XX 7 7 - - - │
+ │ ✔ iframe_spec.coffee XX:XX 7 7 - - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
- All specs passed! XX:XX 7 7 - - -
+ ✔ All specs passed! XX:XX 7 7 - - -
`
diff --git a/packages/server/__snapshots__/2_images_spec.coffee.js b/packages/server/__snapshots__/2_images_spec.coffee.js
index 459db989336..a188d8e1bec 100644
--- a/packages/server/__snapshots__/2_images_spec.coffee.js
+++ b/packages/server/__snapshots__/2_images_spec.coffee.js
@@ -1,4 +1,4 @@
-exports['e2e images passes 1'] = `
+exports['e2e images / passes'] = `
====================================================================================================
@@ -14,7 +14,7 @@ exports['e2e images passes 1'] = `
────────────────────────────────────────────────────────────────────────────────────────────────────
- Running: images_spec.coffee... (1 of 1)
+ Running: images_spec.coffee (1 of 1)
images
@@ -27,23 +27,23 @@ exports['e2e images passes 1'] = `
(Results)
- ┌──────────────────────────────────┐
- │ Tests: 2 │
- │ Passing: 2 │
- │ Failing: 0 │
- │ Pending: 0 │
- │ Skipped: 0 │
- │ Screenshots: 0 │
- │ Video: true │
- │ Duration: X seconds │
- │ Spec Ran: images_spec.coffee │
- └──────────────────────────────────┘
+ ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
+ │ Tests: 2 │
+ │ Passing: 2 │
+ │ Failing: 0 │
+ │ Pending: 0 │
+ │ Skipped: 0 │
+ │ Screenshots: 0 │
+ │ Video: true │
+ │ Duration: X seconds │
+ │ Spec Ran: images_spec.coffee │
+ └────────────────────────────────────────────────────────────────────────────────────────────────┘
(Video)
- - Started processing: Compressing to 32 CRF
- - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds)
+ - Started processing: Compressing to 32 CRF
+ - Finished processing: /XXX/XXX/XXX/cypress/videos/images_spec.coffee.mp4 (X second)
====================================================================================================
@@ -51,11 +51,11 @@ exports['e2e images passes 1'] = `
(Run Finished)
- Spec Tests Passing Failing Pending Skipped
+ Spec Tests Passing Failing Pending Skipped
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
- │ ✔ images_spec.coffee XX:XX 2 2 - - - │
+ │ ✔ images_spec.coffee XX:XX 2 2 - - - │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
- All specs passed! XX:XX 2 2 - - -
+ ✔ All specs passed! XX:XX 2 2 - - -
`
diff --git a/packages/server/__snapshots__/2_config_spec.coffee.js b/packages/server/__snapshots__/3_config_spec.coffee.js
similarity index 59%
rename from packages/server/__snapshots__/2_config_spec.coffee.js
rename to packages/server/__snapshots__/3_config_spec.coffee.js
index 4fa3114c562..05ef5538ef3 100644
--- a/packages/server/__snapshots__/2_config_spec.coffee.js
+++ b/packages/server/__snapshots__/3_config_spec.coffee.js
@@ -14,7 +14,7 @@ exports['e2e config passes 1'] = `
────────────────────────────────────────────────────────────────────────────────────────────────────
- Running: config_passing_spec.coffee... (1 of 1)
+ Running: config_passing_spec.coffee (1 of 1)
Cypress static methods + props
@@ -23,30 +23,32 @@ exports['e2e config passes 1'] = `
✓ .arch
✓ .browser
✓ .spec
+ .env
+ ✓ doesn't die on
- {{#each javascripts}}
-
+ {{each(options.javascripts)}}
+
{{/each}}
- {{#each specs}}
-
+ {{each(options.specs)}}
+
{{/each}}
diff --git a/packages/server/lib/modes/record.coffee b/packages/server/lib/modes/record.coffee
index f832b24f290..03d5ccfb02e 100644
--- a/packages/server/lib/modes/record.coffee
+++ b/packages/server/lib/modes/record.coffee
@@ -17,6 +17,7 @@ keys = require("../util/keys")
terminal = require("../util/terminal")
humanTime = require("../util/human_time")
ciProvider = require("../util/ci_provider")
+settings = require("../util/settings")
onBeforeRetry = (details) ->
errors.warning(
@@ -92,9 +93,9 @@ throwIfIncorrectCiBuildIdUsage = (ciBuildId, parallel, group) ->
if ciBuildId and (not parallel and not group)
errors.throw("INCORRECT_CI_BUILD_ID_USAGE", { ciBuildId })
-throwIfNoProjectId = (projectId) ->
+throwIfNoProjectId = (projectId, configFile) ->
if not projectId
- errors.throw("CANNOT_RECORD_NO_PROJECT_ID")
+ errors.throw("CANNOT_RECORD_NO_PROJECT_ID", configFile)
getSpecRelativePath = (spec) ->
_.get(spec, "relative", null)
@@ -388,7 +389,7 @@ createRun = Promise.method (options = {}) ->
},
})
when 404
- errors.throw("DASHBOARD_PROJECT_NOT_FOUND", projectId)
+ errors.throw("DASHBOARD_PROJECT_NOT_FOUND", projectId, settings.configFile(options))
when 412
errors.throw("DASHBOARD_INVALID_RUN_REQUEST", err.error)
when 422
@@ -543,7 +544,9 @@ createRunAndRecordSpecs = (options = {}) ->
if not resp
## if a forked run, can't record and can't be parallel
## because the necessary env variables aren't present
- runAllSpecs({}, false)
+ runAllSpecs({
+ parallel: false
+ })
else
{ runUrl, runId, machineId, groupId } = resp
@@ -624,7 +627,12 @@ createRunAndRecordSpecs = (options = {}) ->
instanceId
})
- runAllSpecs({ beforeSpecRun, afterSpecRun, runUrl })
+ runAllSpecs({
+ runUrl,
+ parallel,
+ beforeSpecRun,
+ afterSpecRun,
+ })
module.exports = {
createRun
diff --git a/packages/server/lib/modes/run.coffee b/packages/server/lib/modes/run.coffee
deleted file mode 100644
index 6572989a879..00000000000
--- a/packages/server/lib/modes/run.coffee
+++ /dev/null
@@ -1,1057 +0,0 @@
-_ = require("lodash")
-pkg = require("@packages/root")
-path = require("path")
-chalk = require("chalk")
-human = require("human-interval")
-debug = require("debug")("cypress:server:run")
-Promise = require("bluebird")
-logSymbols = require("log-symbols")
-
-recordMode = require("./record")
-errors = require("../errors")
-Project = require("../project")
-Reporter = require("../reporter")
-browsers = require("../browsers")
-openProject = require("../open_project")
-videoCapture = require("../video_capture")
-Windows = require("../gui/windows")
-fs = require("../util/fs")
-env = require("../util/env")
-trash = require("../util/trash")
-random = require("../util/random")
-system = require("../util/system")
-duration = require("../util/duration")
-terminal = require("../util/terminal")
-specsUtil = require("../util/specs")
-humanTime = require("../util/human_time")
-electronApp = require("../util/electron_app")
-chromePolicyCheck = require("../util/chrome_policy_check")
-
-DELAY_TO_LET_VIDEO_FINISH_MS = 1000
-
-color = (val, c) ->
- chalk[c](val)
-
-gray = (val) ->
- color(val, "gray")
-
-colorIf = (val, c) ->
- if val is 0
- val = "-"
- c = "gray"
-
- color(val, c)
-
-getSymbol = (num) ->
- if num then logSymbols.error else logSymbols.success
-
-formatBrowser = (browser) ->
- ## todo finish browser
- _.compact([
- browser.displayName,
- browser.majorVersion,
- browser.isHeadless and gray("(headless)")
- ]).join(" ")
-
-formatFooterSummary = (results) ->
- { totalFailed, runs } = results
-
- ## pass or fail color
- c = if totalFailed then "red" else "green"
-
- phrase = do ->
- ## if we have any specs failing...
- if not totalFailed
- return "All specs passed!"
-
- ## number of specs
- total = runs.length
- failingRuns = _.filter(runs, "stats.failures").length
- percent = Math.round(failingRuns / total * 100)
-
- "#{failingRuns} of #{total} failed (#{percent}%)"
-
- return [
- color(phrase, c),
- gray(duration.format(results.totalDuration)),
- colorIf(results.totalTests, "reset"),
- colorIf(results.totalPassed, "green"),
- colorIf(totalFailed, "red"),
- colorIf(results.totalPending, "cyan"),
- colorIf(results.totalSkipped, "blue"),
- ]
-
-formatSpecSummary = (name, failures) ->
- [
- getSymbol(failures),
- color(name, "reset")
- ]
- .join(" ")
-
-formatRecordParams = (runUrl, parallel, group) ->
- if runUrl
- group or= false
- "Group: #{group}, Parallel: #{Boolean(parallel)}"
-
-formatSpecPattern = (specPattern) ->
- if specPattern
- specPattern.join(", ")
-
-formatSpecs = (specs) ->
- names = _.map(specs, "name")
-
- ## 25 found: (foo.spec.js, bar.spec.js, baz.spec.js)
- [
- "#{names.length} found "
- gray("("),
- gray(names.join(', ')),
- gray(")")
- ]
- .join("")
-
-displayRunStarting = (options = {}) ->
- { specs, specPattern, browser, runUrl, parallel, group } = options
-
- console.log("")
-
- terminal.divider("=")
-
- console.log("")
-
- terminal.header("Run Starting", {
- color: ["reset"]
- })
-
- console.log("")
-
- table = terminal.table({
- colWidths: [12, 88]
- type: "outsideBorder"
- })
-
- data = _
- .chain([
- [gray("Cypress:"), pkg.version]
- [gray("Browser:"), formatBrowser(browser)]
- [gray("Specs:"), formatSpecs(specs)]
- [gray("Searched:"), formatSpecPattern(specPattern)]
- [gray("Params:"), formatRecordParams(runUrl, parallel, group)]
- [gray("Run URL:"), runUrl]
- ])
- .filter(_.property(1))
- .value()
-
- table.push(data...)
-
- console.log(table.toString())
-
- console.log("")
-
-displaySpecHeader = (name, curr, total, estimated) ->
- console.log("")
-
- PADDING = 2
-
- table = terminal.table({
- colWidths: [80, 20]
- colAligns: ["left", "right"]
- type: "pageDivider"
- style: {
- "padding-left": PADDING
- }
- })
-
- table.push(["", ""])
- table.push([
- "Running: " + gray(name + "..."),
- gray("(#{curr} of #{total})")
- ])
-
- console.log(table.toString())
-
- if estimated
- estimatedLabel = " ".repeat(PADDING) + "Estimated:"
- console.log(estimatedLabel, gray(humanTime.long(estimated)))
-
-collectTestResults = (obj = {}, estimated) ->
- {
- name: _.get(obj, 'spec.name')
- tests: _.get(obj, 'stats.tests')
- passes: _.get(obj, 'stats.passes')
- pending: _.get(obj, 'stats.pending')
- failures: _.get(obj, 'stats.failures')
- skipped: _.get(obj, 'stats.skipped' )
- duration: humanTime.long(_.get(obj, 'stats.wallClockDuration'))
- estimated: estimated and humanTime.long(estimated)
- screenshots: obj.screenshots and obj.screenshots.length
- video: Boolean(obj.video)
- }
-
-renderSummaryTable = (runUrl) -> (results) ->
- { runs } = results
-
- console.log("")
-
- terminal.divider("=")
-
- console.log("")
-
- terminal.header("Run Finished", {
- color: ["reset"]
- })
-
- if runs and runs.length
- head = [" Spec", "", "Tests", "Passing", "Failing", "Pending", "Skipped"]
- colAligns = ["left", "right", "right", "right", "right", "right", "right"]
- colWidths = [39, 11, 10, 10, 10, 10, 10]
-
- table1 = terminal.table({
- colAligns
- colWidths
- type: "noBorder"
- head: _.map(head, gray)
- })
-
- table2 = terminal.table({
- colAligns
- colWidths
- type: "border"
- })
-
- table3 = terminal.table({
- colAligns
- colWidths
- type: "noBorder"
- head: formatFooterSummary(results)
- style: {
- "padding-right": 2
- }
- })
-
- _.each runs, (run) ->
- { spec, stats } = run
-
- ms = duration.format(stats.wallClockDuration)
-
- table2.push([
- formatSpecSummary(spec.name, stats.failures)
- color(ms, "gray")
- colorIf(stats.tests, "reset")
- colorIf(stats.passes, "green"),
- colorIf(stats.failures, "red"),
- colorIf(stats.pending, "cyan"),
- colorIf(stats.skipped, "blue")
- ])
-
- console.log("")
- console.log("")
- console.log(terminal.renderTables(table1, table2, table3))
- console.log("")
-
- if runUrl
- console.log("")
-
- table4 = terminal.table({
- colWidths: [100]
- type: "pageDivider"
- style: {
- "padding-left": 2
- }
- })
-
- table4.push(["", ""])
- table4.push(["Recorded Run: " + gray(runUrl)])
-
- console.log(terminal.renderTables(table4))
- console.log("")
-
-iterateThroughSpecs = (options = {}) ->
- { specs, runEachSpec, parallel, beforeSpecRun, afterSpecRun, config } = options
-
- serial = ->
- Promise.mapSeries(specs, runEachSpec)
-
- serialWithRecord = ->
- Promise
- .mapSeries specs, (spec, index, length) ->
- beforeSpecRun(spec)
- .then ({ estimated }) ->
- runEachSpec(spec, index, length, estimated)
- .tap (results) ->
- afterSpecRun(spec, results, config)
-
- parallelWithRecord = (runs) ->
- beforeSpecRun()
- .then ({ spec, claimedInstances, totalInstances, estimated }) ->
- ## no more specs to run?
- if not spec
- ## then we're done!
- return runs
-
- ## find the actual spec object amongst
- ## our specs array since the API sends us
- ## the relative name
- spec = _.find(specs, { relative: spec })
-
- runEachSpec(spec, claimedInstances - 1, totalInstances, estimated)
- .tap (results) ->
- runs.push(results)
-
- afterSpecRun(spec, results, config)
- .then ->
- ## recurse
- parallelWithRecord(runs)
-
- switch
- when parallel
- ## if we are running in parallel
- ## then ask the server for the next spec
- parallelWithRecord([])
- when beforeSpecRun
- ## else iterate serialially and record
- ## the results of each spec
- serialWithRecord()
- else
- ## else iterate in serial
- serial()
-
-getProjectId = Promise.method (project, id) ->
- id ?= env.get("CYPRESS_PROJECT_ID")
-
- ## if we have an ID just use it
- if id
- return id
-
- project
- .getProjectId()
- .catch ->
- ## no id no problem
- return null
-
-reduceRuns = (runs, prop) ->
- _.reduce runs, (memo, run) ->
- memo += _.get(run, prop)
- , 0
-
-getRun = (run, prop) ->
- _.get(run, prop)
-
-writeOutput = (outputPath, results) ->
- Promise.try ->
- return if not outputPath
-
- debug("saving output results as %s", outputPath)
-
- fs.outputJsonAsync(outputPath, results)
-
-onWarning = (err) ->
- console.log(chalk.yellow(err.message))
-
-openProjectCreate = (projectRoot, socketId, options) ->
- ## 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)
- openProject.create(projectRoot, options, {
- socketId
- morgan: false
- report: true
- isTextTerminal: options.isTextTerminal
- onWarning
- onError: (err) ->
- console.log("")
- if err.details
- console.log(err.message)
- console.log("")
- console.log(chalk.yellow(err.details))
- else
- console.log(err.stack)
- openProject.emit("exitEarlyWithErr", err.message)
- })
- .catch {portInUse: true}, (err) ->
- ## TODO: this needs to move to emit exitEarly
- ## so we record the failure in CI
- errors.throw("PORT_IN_USE_LONG", err.port)
-
-createAndOpenProject = (socketId, options) ->
- { projectRoot, projectId } = options
-
- Project
- .ensureExists(projectRoot)
- .then ->
- ## open this project without
- ## adding it to the global cache
- openProjectCreate(projectRoot, socketId, options)
- .call("getProject")
- .then (project) ->
- Promise.props({
- project
- config: project.getConfig()
- projectId: getProjectId(project, projectId)
- })
-
-removeOldProfiles = ->
- browsers.removeOldProfiles()
- .catch (err) ->
- ## dont make removing old browsers profiles break the build
- errors.warning("CANNOT_REMOVE_OLD_BROWSER_PROFILES", err.stack)
-
-trashAssets = (config = {}) ->
- if config.trashAssetsBeforeRuns isnt true
- return Promise.resolve()
-
- Promise.join(
- trash.folder(config.videosFolder)
- trash.folder(config.screenshotsFolder)
- )
- .catch (err) ->
- ## dont make trashing assets fail the build
- errors.warning("CANNOT_TRASH_ASSETS", err.stack)
-
-## if we've been told to record and we're not spawning a headed browser
-browserCanBeRecorded = (browser) ->
- browser.name is "electron" and browser.isHeadless
-
-createVideoRecording = (videoName) ->
- outputDir = path.dirname(videoName)
-
- fs
- .ensureDirAsync(outputDir)
- .then ->
- videoCapture
- .start(videoName, {
- onError: (err) ->
- ## catch video recording failures and log them out
- ## but don't let this affect the run at all
- errors.warning("VIDEO_RECORDING_FAILED", err.stack)
- })
-
-getVideoRecordingDelay = (startedVideoCapture) ->
- if startedVideoCapture
- return DELAY_TO_LET_VIDEO_FINISH_MS
-
- return 0
-
-maybeStartVideoRecording = Promise.method (options = {}) ->
- { spec, browser, video, videosFolder } = options
-
- ## bail if we've been told not to capture
- ## a video recording
- if not video
- return
-
- ## handle if this browser cannot actually
- ## be recorded
- if not browserCanBeRecorded(browser)
- console.log("")
-
- if browser.name is "electron" and browser.isHeaded
- errors.warning("CANNOT_RECORD_VIDEO_HEADED")
- else
- errors.warning("CANNOT_RECORD_VIDEO_FOR_THIS_BROWSER", browser.name)
-
- return
-
- ## make sure we have a videsFolder
- if not videosFolder
- throw new Error("Missing videoFolder for recording")
-
- videoPath = (suffix) ->
- path.join(videosFolder, spec.name + suffix)
-
- videoName = videoPath(".mp4")
- compressedVideoName = videoPath("-compressed.mp4")
-
- @createVideoRecording(videoName)
- .then (props = {}) ->
- return {
- videoName,
- compressedVideoName,
- endVideoCapture: props.endVideoCapture,
- writeVideoFrame: props.writeVideoFrame,
- startedVideoCapture: props.startedVideoCapture,
- }
-
-module.exports = {
- collectTestResults
-
- getProjectId
-
- writeOutput
-
- openProjectCreate
-
- createVideoRecording
-
- getVideoRecordingDelay
-
- maybeStartVideoRecording
-
- getElectronProps: (isHeaded, project, writeVideoFrame) ->
- electronProps = {
- width: 1280
- height: 720
- show: isHeaded
- onCrashed: ->
- err = errors.get("RENDERER_CRASHED")
- errors.log(err)
-
- project.emit("exitEarlyWithErr", err.message)
- onNewWindow: (e, url, frameName, disposition, options) ->
- ## force new windows to automatically open with show: false
- ## this prevents window.open inside of javascript client code
- ## to cause a new BrowserWindow instance to open
- ## https://github.com/cypress-io/cypress/issues/123
- options.show = false
- }
-
- if writeVideoFrame
- electronProps.recordFrameRate = 20
- electronProps.onPaint = (event, dirty, image) ->
- writeVideoFrame(image.toJPEG(100))
-
- electronProps
-
- displayResults: (obj = {}, estimated) ->
- results = collectTestResults(obj, estimated)
-
- c = if results.failures then "red" else "green"
-
- console.log("")
-
- terminal.header("Results", {
- color: [c]
- })
-
- table = terminal.table({
- type: "outsideBorder"
- })
-
- data = _.chain([
- ["Tests:", results.tests]
- ["Passing:", results.passes]
- ["Failing:", results.failures]
- ["Pending:", results.pending]
- ["Skipped:", results.skipped]
- ["Screenshots:", results.screenshots]
- ["Video:", results.video]
- ["Duration:", results.duration]
- ["Estimated:", results.estimated] if estimated
- ["Spec Ran:", results.name]
- ])
- .compact()
- .map (arr) ->
- [key, val] = arr
-
- [color(key, "gray"), color(val, c)]
- .value()
-
- table.push(data...)
-
- console.log("")
- console.log(table.toString())
- console.log("")
-
- displayScreenshots: (screenshots = []) ->
- console.log("")
-
- terminal.header("Screenshots", {color: ["yellow"]})
-
- console.log("")
-
- format = (s) ->
- dimensions = gray("(#{s.width}x#{s.height})")
-
- " - #{s.path} #{dimensions}"
-
- screenshots.forEach (screenshot) ->
- console.log(format(screenshot))
-
- console.log("")
-
- postProcessRecording: (end, name, cname, videoCompression, shouldUploadVideo) ->
- debug("ending the video recording %o", { name, videoCompression, shouldUploadVideo })
-
- ## once this ended promises resolves
- ## then begin processing the file
- end()
- .then ->
- ## dont process anything if videoCompress is off
- ## or we've been told not to upload the video
- return if videoCompression is false or shouldUploadVideo is false
-
- console.log("")
-
- terminal.header("Video", {
- color: ["cyan"]
- })
-
- console.log("")
-
- console.log(
- gray(" - Started processing: "),
- chalk.cyan("Compressing to #{videoCompression} CRF")
- )
-
- started = new Date
- progress = Date.now()
- throttle = env.get("VIDEO_COMPRESSION_THROTTLE") or human("10 seconds")
-
- onProgress = (float) ->
- switch
- when float is 1
- finished = new Date - started
- dur = "(#{humanTime.long(finished)})"
- console.log(
- gray(" - Finished processing: "),
- chalk.cyan(name),
- gray(dur)
- )
- console.log("")
-
- when (new Date - progress) > throttle
- ## bump up the progress so we dont
- ## continuously get notifications
- progress += throttle
- percentage = Math.ceil(float * 100) + "%"
- console.log(" - Compression progress: ", chalk.cyan(percentage))
-
- # bar.tickTotal(float)
-
- videoCapture.process(name, cname, videoCompression, onProgress)
- .catch {recordingVideoFailed: true}, (err) ->
- ## dont do anything if this error occured because
- ## recording the video had already failed
- return
- .catch (err) ->
- ## log that post processing was attempted
- ## but failed and dont let this change the run exit code
- errors.warning("VIDEO_POST_PROCESSING_FAILED", err.stack)
-
- launchBrowser: (options = {}) ->
- { browser, spec, writeVideoFrame, project, screenshots, projectRoot } = options
-
- browserOpts = switch browser.name
- when "electron"
- @getElectronProps(browser.isHeaded, project, writeVideoFrame)
- else
- {}
-
- browserOpts.automationMiddleware = {
- onAfterResponse: (message, data, resp) =>
- if message is "take:screenshot" and resp
- screenshots.push @screenshotMetadata(data, resp)
-
- resp
- }
-
- browserOpts.projectRoot = projectRoot
-
- openProject.launch(browser, spec, browserOpts)
-
- listenForProjectEnd: (project, exit) ->
- new Promise (resolve) ->
- if exit is false
- resolve = (arg) ->
- console.log("not exiting due to options.exit being false")
-
- onEarlyExit = (errMsg) ->
- ## probably should say we ended
- ## early too: (Ended Early: true)
- ## in the stats
- obj = {
- error: errors.stripAnsi(errMsg)
- stats: {
- failures: 1
- tests: 0
- passes: 0
- pending: 0
- suites: 0
- skipped: 0
- wallClockDuration: 0
- wallClockStartedAt: (new Date()).toJSON()
- wallClockEndedAt: (new Date()).toJSON()
- }
- }
-
- resolve(obj)
-
- onEnd = (obj) ->
- resolve(obj)
-
- ## when our project fires its end event
- ## resolve the promise
- project.once("end", onEnd)
- project.once("exitEarlyWithErr", onEarlyExit)
-
- waitForBrowserToConnect: (options = {}) ->
- { project, socketId, timeout } = options
-
- attempts = 0
-
- do waitForBrowserToConnect = =>
- Promise.join(
- @waitForSocketConnection(project, socketId)
- @launchBrowser(options)
- )
- .timeout(timeout ? 30000)
- .catch Promise.TimeoutError, (err) =>
- attempts += 1
-
- console.log("")
-
- ## always first close the open browsers
- ## before retrying or dieing
- openProject.closeBrowser()
- .then ->
- switch attempts
- ## try again up to 3 attempts
- when 1, 2
- word = if attempts is 1 then "Retrying..." else "Retrying again..."
- errors.warning("TESTS_DID_NOT_START_RETRYING", word)
-
- waitForBrowserToConnect()
-
- else
- err = errors.get("TESTS_DID_NOT_START_FAILED")
- errors.log(err)
-
- project.emit("exitEarlyWithErr", err.message)
-
- waitForSocketConnection: (project, id) ->
- debug("waiting for socket connection... %o", { id })
-
- new Promise (resolve, reject) ->
- fn = (socketId) ->
- debug("got socket connection %o", { id: socketId })
-
- if socketId is id
- ## remove the event listener if we've connected
- project.removeListener("socket:connected", fn)
-
- ## resolve the promise
- resolve()
-
- ## when a socket connects verify this
- ## is the one that matches our id!
- project.on("socket:connected", fn)
-
- waitForTestsToFinishRunning: (options = {}) ->
- { project, screenshots, startedVideoCapture, endVideoCapture, videoName, compressedVideoName, videoCompression, videoUploadOnPasses, exit, spec, estimated } = options
-
- ## https://github.com/cypress-io/cypress/issues/2370
- ## delay 1 second if we're recording a video to give
- ## the browser padding to render the final frames
- ## to avoid chopping off the end of the video
- delay = @getVideoRecordingDelay(startedVideoCapture)
-
- @listenForProjectEnd(project, exit)
- .delay(delay)
- .then (obj) =>
- _.defaults(obj, {
- error: null
- hooks: null
- tests: null
- video: null
- screenshots: null
- reporterStats: null
- })
-
- if startedVideoCapture
- obj.video = videoName
-
- if screenshots
- obj.screenshots = screenshots
-
- obj.spec = spec
-
- finish = ->
- return obj
-
- @displayResults(obj, estimated)
-
- if screenshots and screenshots.length
- @displayScreenshots(screenshots)
-
- { tests, stats } = obj
-
- failingTests = _.filter(tests, { state: "failed" })
-
- hasFailingTests = _.get(stats, 'failures') > 0
-
- ## if we have a video recording
- if startedVideoCapture and tests and tests.length
- ## always set the video timestamp on tests
- obj.tests = Reporter.setVideoTimestamp(startedVideoCapture, tests)
-
- ## we should upload the video if we upload on passes (by default)
- ## or if we have any failures and have started the video
- suv = Boolean(videoUploadOnPasses is true or (startedVideoCapture and hasFailingTests))
-
- obj.shouldUploadVideo = suv
-
- debug("attempting to close the browser")
-
- ## always close the browser now as opposed to letting
- ## it exit naturally with the parent process due to
- ## electron bug in windows
- openProject.closeBrowser()
- .then =>
- if endVideoCapture
- @postProcessRecording(endVideoCapture, videoName, compressedVideoName, videoCompression, suv)
- .then(finish)
- ## TODO: add a catch here
- else
- finish()
-
- screenshotMetadata: (data, resp) ->
- {
- screenshotId: random.id()
- name: data.name ? null
- testId: data.testId
- takenAt: resp.takenAt
- path: resp.path
- height: resp.dimensions.height
- width: resp.dimensions.width
- }
-
- runSpecs: (options = {}) ->
- { config, browser, sys, headed, outputPath, specs, specPattern, beforeSpecRun, afterSpecRun, runUrl, parallel, group } = options
-
- isHeadless = browser.name is "electron" and not headed
-
- browser.isHeadless = isHeadless
- browser.isHeaded = not isHeadless
-
- results = {
- startedTestsAt: null
- endedTestsAt: null
- totalDuration: null
- totalSuites: null,
- totalTests: null,
- totalFailed: null,
- totalPassed: null,
- totalPending: null,
- totalSkipped: null,
- runs: null,
- browserPath: browser.path,
- browserName: browser.name,
- browserVersion: browser.version,
- osName: sys.osName,
- osVersion: sys.osVersion,
- cypressVersion: pkg.version,
- runUrl: runUrl,
- config,
- }
-
- displayRunStarting({
- specs
- group
- runUrl
- browser
- parallel
- specPattern
- })
-
- runEachSpec = (spec, index, length, estimated) =>
- displaySpecHeader(spec.name, index + 1, length, estimated)
-
- @runSpec(spec, options, estimated)
- .get("results")
- .tap (results) ->
- debug("spec results %o", results)
-
- iterateThroughSpecs({
- specs
- config
- parallel
- runEachSpec
- afterSpecRun
- beforeSpecRun
- })
- .then (runs = []) ->
- results.startedTestsAt = start = getRun(_.first(runs), "stats.wallClockStartedAt")
- results.endedTestsAt = end = getRun(_.last(runs), "stats.wallClockEndedAt")
- results.totalDuration = reduceRuns(runs, "stats.wallClockDuration")
- results.totalSuites = reduceRuns(runs, "stats.suites")
- results.totalTests = reduceRuns(runs, "stats.tests")
- results.totalPassed = reduceRuns(runs, "stats.passes")
- results.totalPending = reduceRuns(runs, "stats.pending")
- results.totalFailed = reduceRuns(runs, "stats.failures")
- results.totalSkipped = reduceRuns(runs, "stats.skipped")
- results.runs = runs
-
- debug("final results of all runs: %o", results)
-
- writeOutput(outputPath, results)
- .return(results)
-
- runSpec: (spec = {}, options = {}, estimated) ->
- { project, browser } = options
-
- { isHeadless } = browser
-
- debug("about to run spec %o", {
- spec
- isHeadless
- browser
- })
-
- screenshots = []
-
- ## we know we're done running headlessly
- ## when the renderer has connected and
- ## finishes running all of the tests.
- ## we're using an event emitter interface
- ## to gracefully handle this in promise land
-
- @maybeStartVideoRecording({
- spec,
- browser,
- video: options.video,
- videosFolder: options.videosFolder,
- })
- .then (videoRecordProps = {}) =>
- Promise.props({
- results: @waitForTestsToFinishRunning({
- spec
- project
- estimated
- screenshots
- videoName: videoRecordProps.videoName
- compressedVideoName: videoRecordProps.compressedVideoName
- endVideoCapture: videoRecordProps.endVideoCapture
- startedVideoCapture: videoRecordProps.startedVideoCapture
- exit: options.exit
- videoCompression: options.videoCompression
- videoUploadOnPasses: options.videoUploadOnPasses
- }),
-
- connection: @waitForBrowserToConnect({
- spec
- project
- browser
- screenshots
- writeVideoFrame: videoRecordProps.writeVideoFrame
- socketId: options.socketId
- webSecurity: options.webSecurity
- projectRoot: options.projectRoot
- })
- })
-
- findSpecs: (config, specPattern) ->
- specsUtil.find(config, specPattern)
- .tap (specs = []) =>
- if debug.enabled
- names = _.map(specs, "name")
- debug(
- "found '%d' specs using spec pattern '%s': %o",
- names.length,
- specPattern,
- names
- )
-
- ready: (options = {}) ->
- debug("run mode ready with options %o", options)
-
- _.defaults(options, {
- isTextTerminal: true
- browser: "electron"
- })
-
- socketId = random.id()
-
- { projectRoot, record, key, ciBuildId, parallel, group } = options
-
- browserName = options.browser
-
- ## alias and coerce to null
- specPattern = options.spec ? null
-
- ## warn if we're using deprecated --ci flag
- recordMode.warnIfCiFlag(options.ci)
-
- ## ensure the project exists
- ## and open up the project
- createAndOpenProject(socketId, options)
- .then ({ project, projectId, config }) =>
- ## 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
- recordMode.throwIfNoProjectId(projectId)
- recordMode.throwIfIncorrectCiBuildIdUsage(ciBuildId, parallel, group)
- recordMode.throwIfIndeterminateCiBuildId(ciBuildId, parallel, group)
-
- Promise.all([
- system.info(),
- browsers.ensureAndGetByNameOrPath(browserName),
- @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 not specs.length
- errors.throw('NO_SPECS_FOUND', config.integrationFolder, specPattern)
-
- if browser.family == 'chrome'
- chromePolicyCheck.run(onWarning)
-
- runAllSpecs = ({ beforeSpecRun, afterSpecRun, runUrl }, parallelOverride = parallel) =>
- @runSpecs({
- beforeSpecRun
- afterSpecRun
- projectRoot
- specPattern
- socketId
- parallel: parallelOverride
- 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
- { projectName } = config
-
- recordMode.createRunAndRecordSpecs({
- key
- sys
- specs
- group
- browser
- parallel
- ciBuildId
- projectId
- projectRoot
- projectName
- specPattern
- runAllSpecs
- })
- else
- ## not recording, can't be parallel
- runAllSpecs({}, false)
-
- run: (options) ->
- electronApp
- .ready()
- .then =>
- @ready(options)
-
-}
diff --git a/packages/server/lib/modes/run.js b/packages/server/lib/modes/run.js
new file mode 100644
index 00000000000..4173fc3abbd
--- /dev/null
+++ b/packages/server/lib/modes/run.js
@@ -0,0 +1,1373 @@
+/* eslint-disable no-console */
+const _ = require('lodash')
+const la = require('lazy-ass')
+const pkg = require('@packages/root')
+const path = require('path')
+const chalk = require('chalk')
+const human = require('human-interval')
+const debug = require('debug')('cypress:server:run')
+const Promise = require('bluebird')
+const logSymbols = require('log-symbols')
+
+const recordMode = require('./record')
+const errors = require('../errors')
+const Project = require('../project')
+const Reporter = require('../reporter')
+const browsers = require('../browsers')
+const openProject = require('../open_project')
+const videoCapture = require('../video_capture')
+const fs = require('../util/fs')
+const env = require('../util/env')
+const trash = require('../util/trash')
+const random = require('../util/random')
+const system = require('../util/system')
+const duration = require('../util/duration')
+const newlines = require('../util/newlines')
+const terminal = require('../util/terminal')
+const specsUtil = require('../util/specs')
+const humanTime = require('../util/human_time')
+const electronApp = require('../util/electron_app')
+const settings = require('../util/settings')
+const chromePolicyCheck = require('../util/chrome_policy_check')
+
+const DELAY_TO_LET_VIDEO_FINISH_MS = 1000
+
+const color = (val, c) => {
+ return chalk[c](val)
+}
+
+const gray = (val) => {
+ return color(val, 'gray')
+}
+
+const colorIf = function (val, c) {
+ if (val === 0) {
+ val = '-'
+ c = 'gray'
+ }
+
+ return color(val, c)
+}
+
+const getSymbol = function (num) {
+ if (num) {
+ return logSymbols.error
+ }
+
+ return logSymbols.success
+}
+
+const getWidth = (table, index) => {
+ // get the true width of a table's column,
+ // based off of calculated table options for that column
+ const columnWidth = table.options.colWidths[index]
+
+ if (columnWidth) {
+ return columnWidth - (table.options.style['padding-left'] + table.options.style['padding-right'])
+ }
+}
+
+const formatBrowser = (browser) => {
+ // TODO: finish browser
+ return _.compact([
+ browser.displayName,
+ browser.majorVersion,
+ browser.isHeadless && gray('(headless)'),
+ ]).join(' ')
+}
+
+const formatFooterSummary = (results) => {
+ const { totalFailed, runs } = results
+
+ // pass or fail color
+ const c = totalFailed ? 'red' : 'green'
+
+ const phrase = (() => {
+ // if we have any specs failing...
+ if (!totalFailed) {
+ return 'All specs passed!'
+ }
+
+ // number of specs
+ const total = runs.length
+ const failingRuns = _.filter(runs, 'stats.failures').length
+ const percent = Math.round((failingRuns / total) * 100)
+
+ return `${failingRuns} of ${total} failed (${percent}%)`
+ })()
+
+ return [
+ formatSymbolSummary(totalFailed),
+ color(phrase, c),
+ gray(duration.format(results.totalDuration)),
+ colorIf(results.totalTests, 'reset'),
+ colorIf(results.totalPassed, 'green'),
+ colorIf(totalFailed, 'red'),
+ colorIf(results.totalPending, 'cyan'),
+ colorIf(results.totalSkipped, 'blue'),
+ ]
+}
+
+const formatSymbolSummary = (failures) => {
+ return getSymbol(failures)
+}
+
+const formatPath = (name, n, colour = 'reset') => {
+ if (!name) return ''
+
+ const fakeCwdPath = env.get('FAKE_CWD_PATH')
+
+ if (fakeCwdPath && env.get('CYPRESS_ENV') === 'test') {
+ // if we're testing within Cypress, we want to strip out
+ // the current working directory before calculating the stdout tables
+ // this will keep our snapshots consistent everytime we run
+ const cwdPath = process.cwd()
+
+ name = name
+ .split(cwdPath)
+ .join(fakeCwdPath)
+ }
+
+ // add newLines at each n char and colorize the path
+ if (n) {
+ let nameWithNewLines = newlines.addNewlineAtEveryNChar(name, n)
+
+ return `${color(nameWithNewLines, colour)}`
+ }
+
+ return `${color(name, colour)}`
+}
+
+const formatNodeVersion = ({ resolvedNodeVersion, resolvedNodePath }, width) => {
+ debug('formatting Node version. %o', { version: resolvedNodeVersion, path: resolvedNodePath })
+
+ if (resolvedNodePath) {
+ return formatPath(`v${resolvedNodeVersion} (${resolvedNodePath})`, width)
+ }
+}
+
+const formatRecordParams = function (runUrl, parallel, group) {
+ if (runUrl) {
+ if (!group) {
+ group = false
+ }
+
+ return `Group: ${group}, Parallel: ${Boolean(parallel)}`
+ }
+}
+
+const displayRunStarting = function (options = {}) {
+ const { config, specs, specPattern, browser, runUrl, parallel, group } = options
+
+ console.log('')
+
+ terminal.divider('=')
+
+ console.log('')
+
+ terminal.header('Run Starting', {
+ color: ['reset'],
+ })
+
+ console.log('')
+
+ // if we show Node Version, then increase 1st column width
+ // to include wider 'Node Version:'
+ const colWidths = config.resolvedNodePath ? [16, 84] : [12, 88]
+
+ const table = terminal.table({
+ colWidths,
+ type: 'outsideBorder',
+ })
+
+ const formatSpecPattern = () => {
+ // foo.spec.js, bar.spec.js, baz.spec.js
+ // also inserts newlines at col width
+ if (specPattern) {
+ return formatPath(specPattern.join(', '), getWidth(table, 1))
+ }
+ }
+
+ const formatSpecs = (specs) => {
+ // 25 found: (foo.spec.js, bar.spec.js, baz.spec.js)
+ const names = _.map(specs, 'name')
+ const specsTruncated = _.truncate(names.join(', '), { length: 250 })
+
+ const stringifiedSpecs = [
+ `${names.length} found `,
+ '(',
+ specsTruncated,
+ ')',
+ ]
+ .join('')
+
+ return formatPath(stringifiedSpecs, getWidth(table, 1))
+ }
+
+ const data = _
+ .chain([
+ [gray('Cypress:'), pkg.version],
+ [gray('Browser:'), formatBrowser(browser)],
+ [gray('Node Version:'), formatNodeVersion(config, getWidth(table, 1))],
+ [gray('Specs:'), formatSpecs(specs)],
+ [gray('Searched:'), formatSpecPattern(specPattern)],
+ [gray('Params:'), formatRecordParams(runUrl, parallel, group)],
+ [gray('Run URL:'), runUrl ? formatPath(runUrl, getWidth(table, 1)) : ''],
+ ])
+ .filter(_.property(1))
+ .value()
+
+ table.push(...data)
+
+ console.log(table.toString())
+
+ return console.log('')
+}
+
+const displaySpecHeader = function (name, curr, total, estimated) {
+ console.log('')
+
+ const PADDING = 2
+
+ const table = terminal.table({
+ colWidths: [10, 70, 20],
+ colAligns: ['left', 'left', 'right'],
+ type: 'pageDivider',
+ style: {
+ 'padding-left': PADDING,
+ 'padding-right': 0,
+ },
+ })
+
+ table.push(['', ''])
+ table.push([
+ 'Running:',
+ `${formatPath(name, getWidth(table, 1), 'gray')}`,
+ gray(`(${curr} of ${total})`),
+ ])
+
+ console.log(table.toString())
+
+ if (estimated) {
+ const estimatedLabel = `${' '.repeat(PADDING)}Estimated:`
+
+ return console.log(estimatedLabel, gray(humanTime.long(estimated)))
+ }
+}
+
+const collectTestResults = (obj = {}, estimated) => {
+ return {
+ name: _.get(obj, 'spec.name'),
+ tests: _.get(obj, 'stats.tests'),
+ passes: _.get(obj, 'stats.passes'),
+ pending: _.get(obj, 'stats.pending'),
+ failures: _.get(obj, 'stats.failures'),
+ skipped: _.get(obj, 'stats.skipped'),
+ duration: humanTime.long(_.get(obj, 'stats.wallClockDuration')),
+ estimated: estimated && humanTime.long(estimated),
+ screenshots: obj.screenshots && obj.screenshots.length,
+ video: Boolean(obj.video),
+ }
+}
+
+const renderSummaryTable = (runUrl) => {
+ return function (results) {
+ const { runs } = results
+
+ console.log('')
+
+ terminal.divider('=')
+
+ console.log('')
+
+ terminal.header('Run Finished', {
+ color: ['reset'],
+ })
+
+ if (runs && runs.length) {
+ const colAligns = ['left', 'left', 'right', 'right', 'right', 'right', 'right', 'right']
+ const colWidths = [3, 41, 11, 9, 9, 9, 9, 9]
+
+ const table1 = terminal.table({
+ colAligns,
+ colWidths,
+ type: 'noBorder',
+ head: [
+ '',
+ gray('Spec'),
+ '',
+ gray('Tests'),
+ gray('Passing'),
+ gray('Failing'),
+ gray('Pending'),
+ gray('Skipped'),
+ ],
+ })
+
+ const table2 = terminal.table({
+ colAligns,
+ colWidths,
+ type: 'border',
+ })
+
+ const table3 = terminal.table({
+ colAligns,
+ colWidths,
+ type: 'noBorder',
+ head: formatFooterSummary(results),
+ })
+
+ _.each(runs, (run) => {
+ const { spec, stats } = run
+
+ const ms = duration.format(stats.wallClockDuration)
+
+ return table2.push([
+ formatSymbolSummary(stats.failures),
+ formatPath(spec.name, getWidth(table2, 1)),
+ color(ms, 'gray'),
+ colorIf(stats.tests, 'reset'),
+ colorIf(stats.passes, 'green'),
+ colorIf(stats.failures, 'red'),
+ colorIf(stats.pending, 'cyan'),
+ colorIf(stats.skipped, 'blue'),
+ ])
+ })
+
+ console.log('')
+ console.log('')
+ console.log(terminal.renderTables(table1, table2, table3))
+ console.log('')
+
+ if (runUrl) {
+ console.log('')
+
+ const table4 = terminal.table({
+ colWidths: [100],
+ type: 'pageDivider',
+ style: {
+ 'padding-left': 2,
+ },
+ })
+
+ table4.push(['', ''])
+ table4.push([`Recorded Run: ${formatPath(runUrl, getWidth(table4, 0), 'gray')}`])
+
+ console.log(terminal.renderTables(table4))
+
+ console.log('')
+ }
+ }
+ }
+}
+
+const iterateThroughSpecs = function (options = {}) {
+ const { specs, runEachSpec, parallel, beforeSpecRun, afterSpecRun, config } = options
+
+ const serial = () => {
+ return Promise.mapSeries(specs, runEachSpec)
+ }
+
+ const serialWithRecord = () => {
+ return Promise
+ .mapSeries(specs, (spec, index, length) => {
+ return beforeSpecRun(spec)
+ .then(({ estimated }) => {
+ return runEachSpec(spec, index, length, estimated)
+ })
+ .tap((results) => {
+ return afterSpecRun(spec, results, config)
+ })
+ })
+ }
+
+ const parallelWithRecord = (runs) => {
+ return beforeSpecRun()
+ .then(({ spec, claimedInstances, totalInstances, estimated }) => {
+ // no more specs to run?
+ if (!spec) {
+ // then we're done!
+ return runs
+ }
+
+ // find the actual spec object amongst
+ // our specs array since the API sends us
+ // the relative name
+ spec = _.find(specs, { relative: spec })
+
+ return runEachSpec(
+ spec,
+ claimedInstances - 1,
+ totalInstances,
+ estimated
+ )
+ .tap((results) => {
+ runs.push(results)
+
+ return afterSpecRun(spec, results, config)
+ })
+ .then(() => {
+ // recurse
+ return parallelWithRecord(runs)
+ })
+ })
+ }
+
+ if (parallel) {
+ // if we are running in parallel
+ // then ask the server for the next spec
+ return parallelWithRecord([])
+ }
+
+ if (beforeSpecRun) {
+ // else iterate serialially and record
+ // the results of each spec
+ return serialWithRecord()
+ }
+
+ // else iterate in serial
+ return serial()
+}
+
+const getProjectId = Promise.method((project, id) => {
+ if (id == null) {
+ id = env.get('CYPRESS_PROJECT_ID')
+ }
+
+ // if we have an ID just use it
+ if (id) {
+ return id
+ }
+
+ return project.getProjectId()
+ .catch(() => {
+ // no id no problem
+ return null
+ })
+})
+
+const getDefaultBrowserOptsByFamily = (browser, project, writeVideoFrame) => {
+ la(browsers.isBrowserFamily(browser.family), 'invalid browser family in', browser)
+
+ if (browser.family === 'electron') {
+ return getElectronProps(browser.isHeaded, project, writeVideoFrame)
+ }
+
+ if (browser.family === 'chrome') {
+ return getChromeProps(browser.isHeaded, project, writeVideoFrame)
+ }
+
+ return {}
+}
+
+const getChromeProps = (isHeaded, project, writeVideoFrame) => {
+ const shouldWriteVideo = Boolean(writeVideoFrame)
+
+ debug('setting Chrome properties %o', { isHeaded, shouldWriteVideo })
+
+ return _
+ .chain({})
+ .tap((props) => {
+ if (isHeaded && writeVideoFrame) {
+ props.screencastFrame = (e) => {
+ // https://chromedevtools.github.io/devtools-protocol/tot/Page#event-screencastFrame
+ writeVideoFrame(Buffer.from(e.data, 'base64'))
+ }
+ }
+ })
+ .value()
+}
+
+const getElectronProps = (isHeaded, project, writeVideoFrame) => {
+ return _
+ .chain({
+ width: 1280,
+ height: 720,
+ show: isHeaded,
+ onCrashed () {
+ const err = errors.get('RENDERER_CRASHED')
+
+ errors.log(err)
+
+ return project.emit('exitEarlyWithErr', err.message)
+ },
+ onNewWindow (e, url, frameName, disposition, options) {
+ // force new windows to automatically open with show: false
+ // this prevents window.open inside of javascript client code
+ // to cause a new BrowserWindow instance to open
+ // https://github.com/cypress-io/cypress/issues/123
+ options.show = false
+ },
+ })
+ .tap((props) => {
+ if (writeVideoFrame) {
+ props.recordFrameRate = 20
+ props.onPaint = (event, dirty, image) => {
+ return writeVideoFrame(image.toJPEG(100))
+ }
+ }
+ })
+ .value()
+}
+
+const sumByProp = (runs, prop) => {
+ return _.sumBy(runs, prop) || 0
+}
+
+const getRun = (run, prop) => {
+ return _.get(run, prop)
+}
+
+const writeOutput = (outputPath, results) => {
+ return Promise.try(() => {
+ if (!outputPath) {
+ return
+ }
+
+ debug('saving output results %o', { outputPath })
+
+ return fs.outputJsonAsync(outputPath, results)
+ })
+}
+
+const onWarning = (err) => {
+ console.log(chalk.yellow(err.message))
+}
+
+const openProjectCreate = (projectRoot, socketId, options) => {
+ // 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, {
+ socketId,
+ morgan: false,
+ report: true,
+ isTextTerminal: options.isTextTerminal,
+ onWarning,
+ onError (err) {
+ console.log('')
+ if (err.details) {
+ console.log(err.message)
+ console.log('')
+ console.log(chalk.yellow(err.details))
+ } else {
+ console.log(err.stack)
+ }
+
+ return openProject.emit('exitEarlyWithErr', err.message)
+ },
+ })
+ .catch({ portInUse: true }, (err) => {
+ // TODO: this needs to move to emit exitEarly
+ // so we record the failure in CI
+ return errors.throw('PORT_IN_USE_LONG', err.port)
+ })
+}
+
+const createAndOpenProject = function (socketId, options) {
+ const { projectRoot, projectId } = options
+
+ return Project
+ .ensureExists(projectRoot, options)
+ .then(() => {
+ // open this project without
+ // adding it to the global cache
+ return openProjectCreate(projectRoot, socketId, options)
+ })
+ .call('getProject')
+ .then((project) => {
+ return Promise.props({
+ project,
+ config: project.getConfig(),
+ projectId: getProjectId(project, projectId),
+ })
+ })
+}
+
+const removeOldProfiles = () => {
+ return browsers.removeOldProfiles()
+ .catch((err) => {
+ // dont make removing old browsers profiles break the build
+ return errors.warning('CANNOT_REMOVE_OLD_BROWSER_PROFILES', err.stack)
+ })
+}
+
+const trashAssets = Promise.method((config = {}) => {
+ if (config.trashAssetsBeforeRuns !== true) {
+ return
+ }
+
+ return Promise.join(
+ trash.folder(config.videosFolder),
+ trash.folder(config.screenshotsFolder)
+ )
+ .catch((err) => {
+ // dont make trashing assets fail the build
+ return errors.warning('CANNOT_TRASH_ASSETS', err.stack)
+ })
+})
+
+// if we've been told to record and we're not spawning a headed browser
+const browserCanBeRecorded = (browser) => {
+ if (browser.family === 'electron' && browser.isHeadless) {
+ return true
+ }
+
+ if (browser.family === 'chrome' && browser.isHeaded) {
+ return true
+ }
+
+ return false
+}
+
+const createVideoRecording = function (videoName) {
+ const outputDir = path.dirname(videoName)
+
+ return fs
+ .ensureDirAsync(outputDir)
+ .then(() => {
+ return videoCapture
+ .start(videoName, {
+ onError (err) {
+ // catch video recording failures and log them out
+ // but don't let this affect the run at all
+ return errors.warning('VIDEO_RECORDING_FAILED', err.stack)
+ },
+ })
+ })
+}
+
+const getVideoRecordingDelay = function (startedVideoCapture) {
+ if (startedVideoCapture) {
+ return DELAY_TO_LET_VIDEO_FINISH_MS
+ }
+
+ return 0
+}
+
+const maybeStartVideoRecording = Promise.method(function (options = {}) {
+ const { spec, browser, video, videosFolder } = options
+
+ // bail if we've been told not to capture
+ // a video recording
+ if (!video) {
+ return
+ }
+
+ // handle if this browser cannot actually
+ // be recorded
+ if (!browserCanBeRecorded(browser)) {
+ console.log('')
+
+ // TODO update error messages and included browser name and headed mode
+ if (browser.family === 'electron' && browser.isHeaded) {
+ errors.warning('CANNOT_RECORD_VIDEO_HEADED')
+ } else {
+ errors.warning('CANNOT_RECORD_VIDEO_FOR_THIS_BROWSER', browser.name)
+ }
+
+ return
+ }
+
+ // make sure we have a videosFolder
+ if (!videosFolder) {
+ throw new Error('Missing videoFolder for recording')
+ }
+
+ const videoPath = (suffix) => {
+ return path.join(videosFolder, spec.name + suffix)
+ }
+
+ const videoName = videoPath('.mp4')
+ const compressedVideoName = videoPath('-compressed.mp4')
+
+ return this.createVideoRecording(videoName)
+ .then((props = {}) => {
+ return {
+ videoName,
+ compressedVideoName,
+ endVideoCapture: props.endVideoCapture,
+ writeVideoFrame: props.writeVideoFrame,
+ startedVideoCapture: props.startedVideoCapture,
+ }
+ })
+})
+
+module.exports = {
+ collectTestResults,
+
+ getProjectId,
+
+ writeOutput,
+
+ openProjectCreate,
+
+ createVideoRecording,
+
+ getVideoRecordingDelay,
+
+ maybeStartVideoRecording,
+
+ getChromeProps,
+
+ getElectronProps,
+
+ displayResults (obj = {}, estimated) {
+ const results = collectTestResults(obj, estimated)
+
+ const c = results.failures ? 'red' : 'green'
+
+ console.log('')
+
+ terminal.header('Results', {
+ color: [c],
+ })
+
+ const table = terminal.table({
+ colWidths: [14, 86],
+ type: 'outsideBorder',
+ })
+
+ const data = _.chain([
+ ['Tests:', results.tests],
+ ['Passing:', results.passes],
+ ['Failing:', results.failures],
+ ['Pending:', results.pending],
+ ['Skipped:', results.skipped],
+ ['Screenshots:', results.screenshots],
+ ['Video:', results.video],
+ ['Duration:', results.duration],
+ estimated ? ['Estimated:', results.estimated] : undefined,
+ ['Spec Ran:', formatPath(results.name, getWidth(table, 1), c)],
+ ])
+ .compact()
+ .map((arr) => {
+ const [key, val] = arr
+
+ return [color(key, 'gray'), color(val, c)]
+ })
+ .value()
+
+ table.push(...data)
+
+ console.log('')
+ console.log(table.toString())
+ console.log('')
+ },
+
+ displayScreenshots (screenshots = []) {
+ console.log('')
+
+ terminal.header('Screenshots', { color: ['yellow'] })
+
+ console.log('')
+
+ const table = terminal.table({
+ colWidths: [3, 82, 15],
+ colAligns: ['left', 'left', 'right'],
+ type: 'noBorder',
+ style: {
+ 'padding-right': 0,
+ },
+ chars: {
+ 'left': ' ',
+ 'right': '',
+ },
+ })
+
+ screenshots.forEach((screenshot) => {
+ const dimensions = gray(`(${screenshot.width}x${screenshot.height})`)
+
+ table.push([
+ '-',
+ formatPath(`${screenshot.path}`, getWidth(table, 1)),
+ gray(dimensions),
+ ])
+ })
+
+ console.log(table.toString())
+
+ console.log('')
+ },
+
+ postProcessRecording (end, name, cname, videoCompression, shouldUploadVideo) {
+ debug('ending the video recording %o', { name, videoCompression, shouldUploadVideo })
+
+ // once this ended promises resolves
+ // then begin processing the file
+ return end()
+ .then(() => {
+ // dont process anything if videoCompress is off
+ // or we've been told not to upload the video
+ if (videoCompression === false || shouldUploadVideo === false) {
+ return
+ }
+
+ console.log('')
+
+ terminal.header('Video', {
+ color: ['cyan'],
+ })
+
+ console.log('')
+
+ const table = terminal.table({
+ colWidths: [3, 21, 76],
+ colAligns: ['left', 'left', 'left'],
+ type: 'noBorder',
+ style: {
+ 'padding-right': 0,
+ },
+ chars: {
+ 'left': ' ',
+ 'right': '',
+ },
+ })
+
+ table.push([
+ gray('-'),
+ gray('Started processing:'),
+ chalk.cyan(`Compressing to ${videoCompression} CRF`),
+ ])
+
+ console.log(table.toString())
+
+ const started = Date.now()
+ let progress = Date.now()
+ const throttle = env.get('VIDEO_COMPRESSION_THROTTLE') || human('10 seconds')
+
+ const onProgress = function (float) {
+ if (float === 1) {
+ const finished = Date.now() - started
+ const dur = `(${humanTime.long(finished)})`
+
+ const table = terminal.table({
+ colWidths: [3, 21, 61, 15],
+ colAligns: ['left', 'left', 'left', 'right'],
+ type: 'noBorder',
+ style: {
+ 'padding-right': 0,
+ },
+ chars: {
+ 'left': ' ',
+ 'right': '',
+ },
+ })
+
+ table.push([
+ gray('-'),
+ gray('Finished processing:'),
+ `${formatPath(name, getWidth(table, 2), 'cyan')}`,
+ gray(dur),
+ ])
+
+ console.log(table.toString())
+
+ console.log('')
+ }
+
+ if (Date.now() - progress > throttle) {
+ // bump up the progress so we dont
+ // continuously get notifications
+ progress += throttle
+ const percentage = `${Math.ceil(float * 100)}%`
+
+ console.log(' Compression progress: ', chalk.cyan(percentage))
+ }
+ }
+
+ // bar.tickTotal(float)
+
+ return videoCapture.process(name, cname, videoCompression, onProgress)
+ })
+ .catch((err) => {
+ // log that post processing was attempted
+ // but failed and dont let this change the run exit code
+ errors.warning('VIDEO_POST_PROCESSING_FAILED', err.stack)
+ })
+ },
+
+ launchBrowser (options = {}) {
+ const { browser, spec, writeVideoFrame, project, screenshots, projectRoot } = options
+
+ const browserOpts = getDefaultBrowserOptsByFamily(browser, project, writeVideoFrame)
+
+ browserOpts.automationMiddleware = {
+ onAfterResponse: (message, data, resp) => {
+ if (message === 'take:screenshot' && resp) {
+ screenshots.push(this.screenshotMetadata(data, resp))
+ }
+
+ return resp
+ },
+ }
+
+ browserOpts.projectRoot = projectRoot
+
+ return openProject.launch(browser, spec, browserOpts)
+ },
+
+ listenForProjectEnd (project, exit) {
+ return new Promise((resolve) => {
+ if (exit === false) {
+ resolve = () => {
+ console.log('not exiting due to options.exit being false')
+ }
+ }
+
+ const onEarlyExit = function (errMsg) {
+ // probably should say we ended
+ // early too: (Ended Early: true)
+ // in the stats
+ const obj = {
+ error: errors.stripAnsi(errMsg),
+ stats: {
+ failures: 1,
+ tests: 0,
+ passes: 0,
+ pending: 0,
+ suites: 0,
+ skipped: 0,
+ wallClockDuration: 0,
+ wallClockStartedAt: new Date().toJSON(),
+ wallClockEndedAt: new Date().toJSON(),
+ },
+ }
+
+ return resolve(obj)
+ }
+
+ const onEnd = (obj) => {
+ return resolve(obj)
+ }
+
+ // when our project fires its end event
+ // resolve the promise
+ project.once('end', onEnd)
+
+ return project.once('exitEarlyWithErr', onEarlyExit)
+ })
+ },
+
+ waitForBrowserToConnect (options = {}) {
+ const { project, socketId, timeout } = options
+
+ let attempts = 0
+
+ const wait = () => {
+ return Promise.join(
+ this.waitForSocketConnection(project, socketId),
+ this.launchBrowser(options)
+ )
+ .timeout(timeout || 30000)
+ .catch(Promise.TimeoutError, (err) => {
+ attempts += 1
+
+ console.log('')
+
+ // always first close the open browsers
+ // before retrying or dieing
+ return openProject.closeBrowser()
+ .then(() => {
+ if (attempts === 1 || attempts === 2) {
+ // try again up to 3 attempts
+ const word = attempts === 1 ? 'Retrying...' : 'Retrying again...'
+
+ errors.warning('TESTS_DID_NOT_START_RETRYING', word)
+
+ return wait()
+ }
+
+ err = errors.get('TESTS_DID_NOT_START_FAILED')
+ errors.log(err)
+
+ return project.emit('exitEarlyWithErr', err.message)
+ })
+ })
+ }
+
+ return wait()
+ },
+
+ waitForSocketConnection (project, id) {
+ debug('waiting for socket connection... %o', { id })
+
+ return new Promise((resolve, reject) => {
+ const fn = function (socketId) {
+ debug('got socket connection %o', { id: socketId })
+
+ if (socketId === id) {
+ // remove the event listener if we've connected
+ project.removeListener('socket:connected', fn)
+
+ // resolve the promise
+ return resolve()
+ }
+ }
+
+ // when a socket connects verify this
+ // is the one that matches our id!
+ return project.on('socket:connected', fn)
+ })
+ },
+
+ waitForTestsToFinishRunning (options = {}) {
+ const { project, screenshots, startedVideoCapture, endVideoCapture, videoName, compressedVideoName, videoCompression, videoUploadOnPasses, exit, spec, estimated } = options
+
+ // https://github.com/cypress-io/cypress/issues/2370
+ // delay 1 second if we're recording a video to give
+ // the browser padding to render the final frames
+ // to avoid chopping off the end of the video
+ const delay = this.getVideoRecordingDelay(startedVideoCapture)
+
+ return this.listenForProjectEnd(project, exit)
+ .delay(delay)
+ .then((obj) => {
+ _.defaults(obj, {
+ error: null,
+ hooks: null,
+ tests: null,
+ video: null,
+ screenshots: null,
+ reporterStats: null,
+ })
+
+ if (startedVideoCapture) {
+ obj.video = videoName
+ }
+
+ if (screenshots) {
+ obj.screenshots = screenshots
+ }
+
+ obj.spec = spec
+
+ const finish = () => {
+ return obj
+ }
+
+ this.displayResults(obj, estimated)
+
+ if (screenshots && screenshots.length) {
+ this.displayScreenshots(screenshots)
+ }
+
+ const { tests, stats } = obj
+
+ const hasFailingTests = _.get(stats, 'failures') > 0
+
+ // if we have a video recording
+ if (startedVideoCapture && tests && tests.length) {
+ // always set the video timestamp on tests
+ obj.tests = Reporter.setVideoTimestamp(startedVideoCapture, tests)
+ }
+
+ // we should upload the video if we upload on passes (by default)
+ // or if we have any failures and have started the video
+ const suv = Boolean(videoUploadOnPasses === true || (startedVideoCapture && hasFailingTests))
+
+ obj.shouldUploadVideo = suv
+
+ debug('attempting to close the browser')
+
+ // always close the browser now as opposed to letting
+ // it exit naturally with the parent process due to
+ // electron bug in windows
+ return openProject
+ .closeBrowser()
+ .then(() => {
+ if (endVideoCapture) {
+ return this.postProcessRecording(
+ endVideoCapture,
+ videoName,
+ compressedVideoName,
+ videoCompression,
+ suv
+ ).then(finish)
+ // TODO: add a catch here
+ }
+
+ return finish()
+ })
+ })
+ },
+
+ screenshotMetadata (data, resp) {
+ return {
+ screenshotId: random.id(),
+ name: data.name || null,
+ testId: data.testId,
+ takenAt: resp.takenAt,
+ path: resp.path,
+ height: resp.dimensions.height,
+ width: resp.dimensions.width,
+ }
+ },
+
+ runSpecs (options = {}) {
+ const { config, browser, sys, headed, outputPath, specs, specPattern, beforeSpecRun, afterSpecRun, runUrl, parallel, group } = options
+
+ const isHeadless = browser.family === 'electron' && !headed
+
+ browser.isHeadless = isHeadless
+ browser.isHeaded = !isHeadless
+
+ const results = {
+ startedTestsAt: null,
+ endedTestsAt: null,
+ totalDuration: null,
+ totalSuites: null,
+ totalTests: null,
+ totalFailed: null,
+ totalPassed: null,
+ totalPending: null,
+ totalSkipped: null,
+ runs: null,
+ browserPath: browser.path,
+ browserName: browser.name,
+ browserVersion: browser.version,
+ osName: sys.osName,
+ osVersion: sys.osVersion,
+ cypressVersion: pkg.version,
+ runUrl,
+ config,
+ }
+
+ displayRunStarting({
+ config,
+ specs,
+ group,
+ runUrl,
+ browser,
+ parallel,
+ specPattern,
+ })
+
+ const runEachSpec = (spec, index, length, estimated) => {
+ displaySpecHeader(spec.name, index + 1, length, estimated)
+
+ return this.runSpec(spec, options, estimated)
+ .get('results')
+ .tap((results) => {
+ return debug('spec results %o', results)
+ })
+ }
+
+ return iterateThroughSpecs({
+ specs,
+ config,
+ parallel,
+ runEachSpec,
+ afterSpecRun,
+ beforeSpecRun,
+ })
+ .then((runs = []) => {
+ results.startedTestsAt = getRun(_.first(runs), 'stats.wallClockStartedAt')
+ results.endedTestsAt = getRun(_.last(runs), 'stats.wallClockEndedAt')
+ results.totalDuration = sumByProp(runs, 'stats.wallClockDuration')
+ results.totalSuites = sumByProp(runs, 'stats.suites')
+ results.totalTests = sumByProp(runs, 'stats.tests')
+ results.totalPassed = sumByProp(runs, 'stats.passes')
+ results.totalPending = sumByProp(runs, 'stats.pending')
+ results.totalFailed = sumByProp(runs, 'stats.failures')
+ results.totalSkipped = sumByProp(runs, 'stats.skipped')
+ results.runs = runs
+
+ debug('final results of all runs: %o', results)
+
+ return writeOutput(outputPath, results).return(results)
+ })
+ },
+
+ runSpec (spec = {}, options = {}, estimated) {
+ const { project, browser } = options
+
+ const { isHeadless } = browser
+
+ debug('about to run spec %o', {
+ spec,
+ isHeadless,
+ browser,
+ })
+
+ const screenshots = []
+
+ // we know we're done running headlessly
+ // when the renderer has connected and
+ // finishes running all of the tests.
+ // we're using an event emitter interface
+ // to gracefully handle this in promise land
+
+ return this.maybeStartVideoRecording({
+ spec,
+ browser,
+ video: options.video,
+ videosFolder: options.videosFolder,
+ })
+ .then((videoRecordProps = {}) => {
+ return Promise.props({
+ results: this.waitForTestsToFinishRunning({
+ spec,
+ project,
+ estimated,
+ screenshots,
+ videoName: videoRecordProps.videoName,
+ compressedVideoName: videoRecordProps.compressedVideoName,
+ endVideoCapture: videoRecordProps.endVideoCapture,
+ startedVideoCapture: videoRecordProps.startedVideoCapture,
+ exit: options.exit,
+ videoCompression: options.videoCompression,
+ videoUploadOnPasses: options.videoUploadOnPasses,
+ }),
+
+ connection: this.waitForBrowserToConnect({
+ spec,
+ project,
+ browser,
+ screenshots,
+ writeVideoFrame: videoRecordProps.writeVideoFrame,
+ socketId: options.socketId,
+ webSecurity: options.webSecurity,
+ projectRoot: options.projectRoot,
+ }),
+ })
+ })
+ },
+
+ findSpecs (config, specPattern) {
+ return specsUtil
+ .find(config, specPattern)
+ .tap((specs = []) => {
+ if (debug.enabled) {
+ const names = _.map(specs, 'name')
+
+ return debug(
+ 'found \'%d\' specs using spec pattern \'%s\': %o',
+ names.length,
+ specPattern,
+ names
+ )
+ }
+ })
+ },
+
+ ready (options = {}) {
+ debug('run mode ready with options %o', options)
+
+ _.defaults(options, {
+ isTextTerminal: true,
+ browser: 'electron',
+ })
+
+ const socketId = random.id()
+
+ const { projectRoot, record, key, ciBuildId, parallel, group } = options
+
+ const browserName = options.browser
+
+ // alias and coerce to null
+ let specPattern = options.spec || null
+
+ // warn if we're using deprecated --ci flag
+ recordMode.warnIfCiFlag(options.ci)
+
+ // 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)
+
+ // 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) {
+ 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 (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,
+ })
+ })
+ })
+ },
+
+ run (options) {
+ return electronApp
+ .ready()
+ .then(() => {
+ return this.ready(options)
+ })
+ },
+}
diff --git a/packages/server/lib/open_project.coffee b/packages/server/lib/open_project.coffee
index 12f4ad210a8..26173aef40d 100644
--- a/packages/server/lib/open_project.coffee
+++ b/packages/server/lib/open_project.coffee
@@ -201,6 +201,9 @@ create = ->
relaunchBrowser()
})
+ if !_.isUndefined(args.configFile)
+ options.configFile = args.configFile
+
options = _.extend {}, args.config, options
## open the project and return
diff --git a/packages/server/lib/plugins/child/task.js b/packages/server/lib/plugins/child/task.js
index 79cacf8f749..8cda37ad623 100644
--- a/packages/server/lib/plugins/child/task.js
+++ b/packages/server/lib/plugins/child/task.js
@@ -42,7 +42,6 @@ const wrap = (ipc, events, ids, args) => {
}
return '__cypress_unhandled__'
-
}
util.wrapChildPromise(ipc, invoke, ids, [arg])
diff --git a/packages/server/lib/plugins/index.coffee b/packages/server/lib/plugins/index.coffee
index 78e77b6a33c..ab5c725f13b 100644
--- a/packages/server/lib/plugins/index.coffee
+++ b/packages/server/lib/plugins/index.coffee
@@ -37,7 +37,18 @@ module.exports = {
registeredEvents = {}
- pluginsProcess = cp.fork(path.join(__dirname, "child", "index.js"), ["--file", config.pluginsFile], { stdio: "inherit" })
+ childIndexFilename = path.join(__dirname, "child", "index.js")
+ childArguments = ["--file", config.pluginsFile]
+ childOptions = {
+ stdio: "inherit"
+ }
+
+ if config.resolvedNodePath
+ debug("launching using custom node version %o", _.pick(config, ['resolvedNodePath', 'resolvedNodeVersion']))
+ childOptions.execPath = config.resolvedNodePath
+
+ debug("forking to run %s", childIndexFilename)
+ pluginsProcess = cp.fork(childIndexFilename, childArguments, childOptions)
ipc = util.wrapIpc(pluginsProcess)
handler(ipc) for handler in handlers
@@ -57,9 +68,11 @@ module.exports = {
}
ipc.send("execute", registration.event, ids, args)
+ debug("resolving with new config %o", newCfg)
resolve(newCfg)
ipc.on "load:error", (type, args...) ->
+ debug("load:error %s, rejecting", type)
reject(errors.get(type, args...))
killPluginsProcess = ->
@@ -68,6 +81,7 @@ module.exports = {
handleError = (err) ->
debug("plugins process error:", err.stack)
+ return if not pluginsProcess ## prevent repeating this in case of multiple errors
killPluginsProcess()
err = errors.get("PLUGINS_ERROR", err.annotated or err.stack or err.message)
err.title = "Error running plugin"
@@ -92,7 +106,7 @@ module.exports = {
isRegistered
execute: (event, args...) ->
- debug("execute plugin event '#{event}' with args: %o %o %o", args...)
+ debug("execute plugin event '#{event}' Node '#{process.version}' with args: %o %o %o", args...)
registeredEvents[event](args...)
## for testing purposes
diff --git a/packages/server/lib/plugins/util.coffee b/packages/server/lib/plugins/util.coffee
index f72ecf5d44c..e628dbc07a2 100644
--- a/packages/server/lib/plugins/util.coffee
+++ b/packages/server/lib/plugins/util.coffee
@@ -58,7 +58,7 @@ module.exports = {
value = undefined
debug("promise resolved for id '#{invocationId}' with value", value)
-
+
resolve(value)
ipc.on("promise:fulfilled:#{invocationId}", handler)
diff --git a/packages/server/lib/project.coffee b/packages/server/lib/project.coffee
index 33e9344d59d..93d174ba376 100644
--- a/packages/server/lib/project.coffee
+++ b/packages/server/lib/project.coffee
@@ -67,6 +67,9 @@ class Project extends EE
onSettingsChanged: false
}
+ debug("project options %o", options)
+ @options = options
+
if process.env.CYPRESS_MEMORY
logMemory = ->
console.log("memory info", process.memoryUsage())
@@ -181,7 +184,7 @@ class Project extends EE
fs.pathExists(supportFile)
.then (found) =>
if not found
- errors.throw("SUPPORT_FILE_NOT_FOUND", supportFile)
+ errors.throw("SUPPORT_FILE_NOT_FOUND", supportFile, settings.configFile(cfg))
watchPluginsFile: (cfg, options) ->
debug("attempt watch plugins file: #{cfg.pluginsFile}")
@@ -205,7 +208,7 @@ class Project extends EE
options.onError(err)
})
- watchSettings: (onSettingsChanged) ->
+ watchSettings: (onSettingsChanged, options) ->
## bail if we havent been told to
## watch anything (like in run mode)
return if not onSettingsChanged
@@ -224,11 +227,13 @@ class Project extends EE
onSettingsChanged.call(@)
}
- @watchers.watch(settings.pathToCypressJson(@projectRoot), obj)
+ if options.configFile != false
+ @watchers.watch(settings.pathToConfigFile(@projectRoot, options), obj)
+
@watchers.watch(settings.pathToCypressEnvJson(@projectRoot), obj)
watchSettingsAndStartWebsockets: (options = {}, cfg = {}) ->
- @watchSettings(options.onSettingsChanged)
+ @watchSettings(options.onSettingsChanged, options)
{ reporter, projectRoot } = cfg
@@ -313,7 +318,9 @@ class Project extends EE
## returns project config (user settings + defaults + cypress.json)
## with additional object "state" which are transient things like
## window width and height, DevTools open or not, etc.
- getConfig: (options = {}) =>
+ getConfig: (options={}) =>
+ options ?= @options
+
if @cfg
return Promise.resolve(@cfg)
@@ -438,12 +445,12 @@ class Project extends EE
getProjectId: ->
@verifyExistence()
.then =>
- settings.read(@projectRoot)
- .then (settings) =>
- if settings and id = settings.projectId
+ settings.read(@projectRoot, @options)
+ .then (readSettings) =>
+ if readSettings and id = readSettings.projectId
return id
- errors.throw("NO_PROJECT_ID", @projectRoot)
+ errors.throw("NO_PROJECT_ID", settings.configFile(@options), @projectRoot)
verifyExistence: ->
fs
@@ -486,6 +493,8 @@ class Project extends EE
@getPathsAndIds = ->
cache.getProjectRoots()
.map (projectRoot) ->
+ ## this assumes that the configFile for a cached project is 'cypress.json'
+ ## https://git.io/JeGyF
Promise.props({
path: projectRoot
id: settings.id(projectRoot)
@@ -557,7 +566,12 @@ class Project extends EE
@remove = (path) ->
cache.removeProject(path)
- @add = (path) ->
+ @add = (path, options) ->
+ ## don't cache a project if a non-default configFile is set
+ ## https://git.io/JeGyF
+ if settings.configFile(options) isnt 'cypress.json'
+ return Promise.resolve({ path })
+
cache.insertProject(path)
.then =>
@id(path)
@@ -569,9 +583,9 @@ class Project extends EE
@id = (path) ->
Project(path).getProjectId()
- @ensureExists = (path) ->
- ## do we have a cypress.json for this project?
- settings.exists(path)
+ @ensureExists = (path, options) ->
+ ## is there a configFile? is the root writable?
+ settings.exists(path, options)
@config = (path) ->
Project(path).getConfig()
diff --git a/packages/server/lib/request.coffee b/packages/server/lib/request.coffee
index 543a02c2b2c..024d44b9ebf 100644
--- a/packages/server/lib/request.coffee
+++ b/packages/server/lib/request.coffee
@@ -4,23 +4,16 @@ rp = require("request-promise")
url = require("url")
tough = require("tough-cookie")
debug = require("debug")("cypress:server:request")
-moment = require("moment")
Promise = require("bluebird")
stream = require("stream")
duplexify = require("duplexify")
agent = require("@packages/network").agent
statusCode = require("./util/status_code")
streamBuffer = require("./util/stream_buffer").streamBuffer
-Cookies = require("./automation/cookies")
-
-Cookie = tough.Cookie
-CookieJar = tough.CookieJar
-
-## shallow clone the original
-serializableProperties = Cookie.serializableProperties.slice(0)
+SERIALIZABLE_COOKIE_PROPS = ['name', 'value', 'domain', 'expiry', 'path', 'secure', 'hostOnly', 'httpOnly']
NETWORK_ERRORS = "ECONNREFUSED ECONNRESET EPIPE EHOSTUNREACH EAI_AGAIN ENOTFOUND".split(" ")
-VERBOSE_REQUEST_OPTS = "followRedirect jar strictSSL".split(" ")
+VERBOSE_REQUEST_OPTS = "followRedirect strictSSL".split(" ")
HTTP_CLIENT_REQUEST_EVENTS = "abort connect continue information socket timeout upgrade".split(" ")
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"
@@ -153,97 +146,6 @@ pick = (resp = {}) ->
"Response Status": resp.statusCode
}
-setCookies = (cookies, jar, headers, url) =>
- return if _.isEmpty(cookies)
-
- if jar
- cookies.forEach (c) ->
- jar.setCookie(c, url, {ignoreError: true})
-
- else
- headers.Cookie = createCookieString(cookies)
-
-newCookieJar = ->
- j = new CookieJar(undefined, {looseMode: true})
-
- ## match the same api signature as @request
- {
- _jar: j
-
- toJSON: ->
- ## temporarily include the URL property
- ## and restore afterwards. this is used to fix
- ## https://github.com/cypress-io/cypress/issues/1321
- Cookie.serializableProperties = serializableProperties.concat("url")
- cookies = j.toJSON()
- Cookie.serializableProperties = serializableProperties
- return cookies
-
- setCookie: (cookieOrStr, uri, options) ->
- ## store the original URL this cookie was set on
- if cookie = j.setCookieSync(cookieOrStr, uri, options)
- ## only set cookie URL if it was created correctly
- ## since servers may send invalid cookies that fail
- ## to parse - we may get undefined here
- cookie.url = uri
-
- return cookie
-
- getCookieString: (uri) ->
- j.getCookieStringSync(uri, {expire: false})
-
- getCookies: (uri) ->
- j.getCookiesSync(uri, {expire: false})
- }
-
-convertToJarCookie = (cookies = []) ->
- _.map cookies, (cookie) ->
- props = {
- key: cookie.name
- path: cookie.path
- value: cookie.value
- secure: cookie.secure
- httpOnly: cookie.httpOnly
- hostOnly: cookie.hostOnly
- }
-
- ## hostOnly is the default when
- ## NO DOMAIN= attribute was set
- ##
- ## so if we are not hostOnly then
- ## this cookie WAS created with
- ## a Domain= attribute and therefore
- ## which lessens whichs domains this
- ## cookie may be sent, and therefore
- ## we need to set props.domain else
- ## the domain would be implied by URL
- if not cookie.hostOnly
- ## https://github.com/salesforce/tough-cookie/issues/26
- ## we need to strip the leading dot
- ## on domains else tough cookie will not
- ## properly send these cookies.
- ## we get dot leading domains from the
- ## chrome cookie API's
- props.domain = _.trimStart(cookie.domain, ".")
-
- ## if we have an expiry then this
- ## is the number of seconds since the epoch
- ## that this cookie expires. we need to convert
- ## this to a JS date object
- if cookie.expiry?
- props.expires = moment.unix(cookie.expiry).toDate()
-
- return new Cookie(props)
-
-reduceCookieToArray = (c) ->
- _.reduce c, (memo, val, key) ->
- memo.push [key.trim(), val.trim()].join("=")
- memo
- , []
-
-createCookieString = (c) ->
- reduceCookieToArray(c).join("; ")
-
createRetryingRequestPromise = (opts) ->
{
requestId,
@@ -459,10 +361,6 @@ module.exports = (options = {}) ->
getDelayForRetry
- reduceCookieToArray
-
- createCookieString
-
setDefaults
create: (strOrOpts, promise) ->
@@ -483,7 +381,8 @@ module.exports = (options = {}) ->
contentTypeIsJson: (response) ->
## TODO: use https://github.com/jshttp/type-is for this
- response?.headers?["content-type"]?.includes("application/json")
+ ## https://github.com/cypress-io/cypress/pull/5166
+ response?.headers?["content-type"]?.split(';', 2)[0].endsWith("json")
parseJsonBody: (body) ->
try
@@ -517,131 +416,95 @@ module.exports = (options = {}) ->
return response
- setJarCookies: (jar, automationFn) ->
- setCookie = (cookie) ->
+ setRequestCookieHeader: (req, reqUrl, automationFn) ->
+ automationFn('get:cookies', { url: reqUrl })
+ .then (cookies) ->
+ debug('got cookies from browser %o', { reqUrl, cookies })
+ header = cookies.map (cookie) ->
+ "#{cookie.name}=#{cookie.value}"
+ .join("; ") || undefined
+ req.headers.Cookie = header
+ header
+
+ setCookiesOnBrowser: (res, resUrl, automationFn) ->
+ cookies = res.headers['set-cookie']
+ if !cookies
+ return Promise.resolve()
+
+ if !(cookies instanceof Array)
+ cookies = [cookies]
+
+ parsedUrl = url.parse(resUrl)
+ debug('setting cookies on browser %o', { url: parsedUrl.href, cookies })
+
+ Promise.map cookies, (cookie) ->
+ cookie = tough.Cookie.parse(cookie, { loose: true })
cookie.name = cookie.key
- ## TODO: fix this
- return if cookie.name and cookie.name.startsWith("__cypress")
-
- ## tough-cookie will return us a cookie that looks like this....
- # { key: 'secret-session',
- # value: 's%3AxMYoMAXnnMN2pzjYKJx21Id9zjQOaPsT.aKJv1mlfNlCEtrPUjgt48KX0c7xNiB%2Bb0fLijmi48dY',
- # domain: 'session.foobar.com',
- # path: '/',
- # httpOnly: true,
- # extensions: [ 'SameSite=Strict' ],
- # hostOnly: true,
- # creation: '2016-09-04T18:48:06.882Z',
- # lastAccessed: '2016-09-04T18:48:06.882Z',
- # name: 'secret-session' }
- #
- # { key: '2293-session',
- # value: 'true',
- # domain: 'localhost',
- # path: '/',
- # hostOnly: true,
- # creation: '2016-09-05T03:03:20.780Z',
- # lastAccessed: '2016-09-05T03:03:20.780Z',
- # name: '2293-session' }
-
- switch
- when cookie.maxAge?
- ## when we have maxAge
- ## prefer that
- ## unix returns us time in seconds
- ## from the epoc + we add that
- ## to maxAge since thats relative seconds
- ## from now
- cookie.expiry = moment().unix() + cookie.maxAge
- when ex = cookie.expires
- ## tough cookie provides javascript date
- ## formatted expires
- cookie.expiry = moment(ex).unix()
-
- automationFn("set:cookie", cookie)
- .then ->
- ## the automation may return us null in
- ## the case an expired cookie is removed
- Cookies.normalizeCookieProps(cookie)
-
- Promise.try ->
- store = jar.toJSON()
-
- debug("setting request jar cookies %o", store.cookies)
-
- ## this likely needs
- ## to be an 'each' not a map
- ## since we need to set cookies
- ## in sequence and not all at once
- ## because cookies could have colliding
- ## values which need to be set in order
- Promise.each(store.cookies, setCookie)
+ if not cookie.domain
+ ## take the domain from the URL
+ cookie.domain = parsedUrl.hostname
+ cookie.hostOnly = true
+
+ return if not tough.domainMatch(cookie.domain, parsedUrl.hostname)
+
+ expiry = cookie.expiryTime()
+ if isFinite(expiry)
+ cookie.expiry = expiry / 1000
+
+ cookie = _.pick(cookie, SERIALIZABLE_COOKIE_PROPS)
+
+ if expiry <= 0
+ return automationFn('clear:cookie', cookie)
+ automationFn('set:cookie', cookie)
sendStream: (headers, automationFn, options = {}) ->
_.defaults options, {
headers: {}
- jar: true
onBeforeReqInit: (fn) -> fn()
}
if not caseInsensitiveGet(options.headers, "user-agent") and (ua = headers["user-agent"])
options.headers["user-agent"] = ua
- ## create a new jar instance
- ## unless its falsy or already set
- if options.jar is true
- options.jar = newCookieJar()
-
_.extend options, {
strictSSL: false
}
self = @
- if jar = options.jar
- followRedirect = options.followRedirect
-
- options.followRedirect = (incomingRes) ->
- ## if we have a cookie jar
- req = @
-
- newUrl = url.resolve(options.url, incomingRes.headers.location)
-
- ## and when we know we should follow the redirect
- ## we need to override the init method and
- ## first set the existing jar cookies on the browser
- ## and then grab the cookies for the new url
- req.init = _.wrap req.init, (orig, opts) =>
- options.onBeforeReqInit ->
- self.setJarCookies(jar, automationFn)
- .then ->
- automationFn("get:cookies", {url: newUrl, includeHostOnly: true})
- .then(convertToJarCookie)
- .then (cookies) ->
- setCookies(cookies, jar, null, newUrl)
- .then ->
- orig.call(req, opts)
-
- followRedirect.call(req, incomingRes)
-
- automationFn("get:cookies", {url: options.url, includeHostOnly: true})
- .then(convertToJarCookie)
- .then (cookies) ->
- setCookies(cookies, options.jar, options.headers, options.url)
+ followRedirect = options.followRedirect
+
+ currentUrl = options.url
+
+ options.followRedirect = (incomingRes) ->
+ if followRedirect and not followRedirect(incomingRes)
+ return false
+
+ newUrl = url.resolve(currentUrl, incomingRes.headers.location)
+
+ ## and when we know we should follow the redirect
+ ## we need to override the init method and
+ ## first set the received cookies on the browser
+ ## and then grab the cookies for the new url
+ self.setCookiesOnBrowser(incomingRes, currentUrl, automationFn)
+ .then (cookies) =>
+ self.setRequestCookieHeader(@, newUrl, automationFn)
+ .then =>
+ currentUrl = newUrl
+ true
+
+ @setRequestCookieHeader(options, options.url, automationFn)
.then =>
return =>
debug("sending request as stream %o", merge(options))
- str = @create(options)
- str.getJar = -> options.jar
- str
+ @create(options)
sendPromise: (headers, automationFn, options = {}) ->
_.defaults options, {
headers: {}
gzip: true
- jar: true
cookies: true
followRedirect: true
}
@@ -660,11 +523,6 @@ module.exports = (options = {}) ->
accept: "*/*"
})
- ## create a new jar instance
- ## unless its falsy or already set
- if options.jar is true
- options.jar = newCookieJar()
-
_.extend(options, {
strictSSL: false
simple: false
@@ -682,10 +540,11 @@ module.exports = (options = {}) ->
delete options.json
delete options.body
+ self = @
+
send = =>
ms = Date.now()
- self = @
redirects = []
requestResponses = []
@@ -693,36 +552,26 @@ module.exports = (options = {}) ->
requestResponses.push(pick(response))
if options.followRedirect
+ currentUrl = options.url
+
options.followRedirect = (incomingRes) ->
- newUrl = url.resolve(options.url, incomingRes.headers.location)
+ newUrl = url.resolve(currentUrl, incomingRes.headers.location)
## normalize the url
redirects.push([incomingRes.statusCode, newUrl].join(": "))
push(incomingRes)
- ## if we have a cookie jar
- if jar = options.jar
- req = @
-
- ## and when we know we should follow the redirect
- ## we need to override the init method and
- ## first set the existing jar cookies on the browser
- ## and then grab the cookies for the new url
- req.init = _.wrap req.init, (orig, opts) =>
- self.setJarCookies(options.jar, automationFn)
- .then ->
- automationFn("get:cookies", {url: newUrl, includeHostOnly: true})
- .then(convertToJarCookie)
- .then (cookies) ->
- setCookies(cookies, jar, null, newUrl)
- .then ->
- orig.call(req, opts)
-
- ## cause the redirect to happen
- ## but swallow up the incomingRes
- ## so we can build an array of responses
- return true
+ ## and when we know we should follow the redirect
+ ## we need to override the init method and
+ ## first set the new cookies on the browser
+ ## and then grab the cookies for the new url
+ self.setCookiesOnBrowser(incomingRes, currentUrl, automationFn)
+ .then =>
+ self.setRequestCookieHeader(@, newUrl, automationFn)
+ .then =>
+ currentUrl = newUrl
+ true
@create(options, true)
.then(@normalizeResponse.bind(@, push))
@@ -742,29 +591,23 @@ module.exports = (options = {}) ->
## the current url
resp.redirectedToUrl = url.resolve(options.url, loc)
- if options.jar
- @setJarCookies(options.jar, automationFn)
- .return(resp)
- else
- resp
+ @setCookiesOnBrowser(resp, currentUrl, automationFn)
+ .return(resp)
if c = options.cookies
## if we have a cookie object then just
## send the request up!
if _.isObject(c)
- setCookies(c, null, options.headers)
+ cookieHeader = _.keys(c).map (k) ->
+ "#{k}=#{c[k]}"
+ .join('; ')
+ if cookieHeader
+ options.headers.Cookie = cookieHeader
send()
else
## else go get the cookies first
## then make the request
-
- ## TODO: we can simply use the 'url' property on the cookies API
- ## which automatically pulls all of the cookies that would be
- ## set for that url!
- automationFn("get:cookies", {url: options.url, includeHostOnly: true})
- .then(convertToJarCookie)
- .then (cookies) ->
- setCookies(cookies, options.jar, options.headers, options.url)
+ self.setRequestCookieHeader(options, options.url, automationFn)
.then(send)
else
send()
diff --git a/packages/server/lib/routes.coffee b/packages/server/lib/routes.coffee
index 449fc75ce41..7f240375450 100644
--- a/packages/server/lib/routes.coffee
+++ b/packages/server/lib/routes.coffee
@@ -15,7 +15,7 @@ files = require("./controllers/files")
proxy = require("./controllers/proxy")
staticCtrl = require("./controllers/static")
-module.exports = (app, config, request, getRemoteState, project, nodeProxy) ->
+module.exports = (app, config, request, getRemoteState, getDeferredResponse, project, nodeProxy) ->
## routing for the actual specs which are processed automatically
## this could be just a regular .js file or a .coffee file
app.get "/__cypress/tests", (req, res, next) ->
@@ -45,7 +45,7 @@ module.exports = (app, config, request, getRemoteState, project, nodeProxy) ->
files.handleIframe(req, res, config, getRemoteState)
app.all "/__cypress/xhrs/*", (req, res, next) ->
- xhrs.handle(req, res, config, next)
+ xhrs.handle(req, res, getDeferredResponse, config, next)
app.get "/__root/*", (req, res, next) ->
file = path.join(config.projectRoot, req.params[0])
diff --git a/packages/server/lib/scaffold/support/commands.js b/packages/server/lib/scaffold/support/commands.js
index c1f5a772e2b..ca4d256f3eb 100644
--- a/packages/server/lib/scaffold/support/commands.js
+++ b/packages/server/lib/scaffold/support/commands.js
@@ -21,5 +21,5 @@
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
-// -- This is will overwrite an existing command --
+// -- This will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
diff --git a/packages/server/lib/screenshots.coffee b/packages/server/lib/screenshots.coffee
index 4b800a6d655..c19be62d627 100644
--- a/packages/server/lib/screenshots.coffee
+++ b/packages/server/lib/screenshots.coffee
@@ -375,7 +375,8 @@ module.exports = {
## caused by jimp reading the image buffer
if data.simple
takenAt = new Date().toJSON()
- return automate(data).then (dataUrl) ->
+ return automate(data)
+ .then (dataUrl) ->
{
takenAt
multipart: false
diff --git a/packages/server/lib/server.coffee b/packages/server/lib/server.coffee
index a06491932df..a442dd3c429 100644
--- a/packages/server/lib/server.coffee
+++ b/packages/server/lib/server.coffee
@@ -1,5 +1,4 @@
_ = require("lodash")
-exphbs = require("express-handlebars")
url = require("url")
http = require("http")
concatStream = require("concat-stream")
@@ -31,6 +30,8 @@ logger = require("./logger")
Socket = require("./socket")
Request = require("./request")
fileServer = require("./file_server")
+XhrServer = require("./xhr_ws_server")
+templateEngine = require("./template_engine")
DEFAULT_DOMAIN_NAME = "localhost"
fullyQualifiedRe = /^https?:\/\//
@@ -85,11 +86,7 @@ class Server
## since we use absolute paths, configure express-handlebars to not automatically find layouts
## https://github.com/cypress-io/cypress/issues/2891
- app.engine("html", exphbs({
- defaultLayout: false
- layoutsDir: []
- partialsDir: []
- }))
+ app.engine("html", templateEngine.render)
## handle the proxied url in case
## we have not yet started our websocket server
@@ -145,12 +142,13 @@ class Server
## and set the responseTimeout
@_request = Request({timeout: config.responseTimeout})
@_nodeProxy = httpProxy.createProxyServer()
+ @_xhrServer = XhrServer.create()
getRemoteState = => @_getRemoteState()
@createHosts(config.hosts)
- @createRoutes(app, config, @_request, getRemoteState, project, @_nodeProxy)
+ @createRoutes(app, config, @_request, getRemoteState, @_xhrServer.getDeferredResponse, project, @_nodeProxy)
@createServer(app, config, project, @_request, onWarning)
@@ -369,12 +367,17 @@ class Server
if obj = buffers.getByOriginalUrl(urlStr)
debug("got previous request buffer for url:", urlStr)
- ## reset the cookies from the existing stream's jar
+ ## reset the cookies from the buffer on the browser
return runPhase ->
resolve(
- request.setJarCookies(obj.jar, automationRequest)
- .then (c) ->
- return obj.details
+ Promise.map obj.details.cookies, (cookie) ->
+ ## prevent prepending a . to the cookie domain if top-level
+ ## navigation occurs as a result of a cy.visit
+ if _.isUndefined(cookie.hostOnly) && !cookie.domain?.startsWith('.')
+ cookie.hostOnly = true
+
+ automationRequest('set:cookie', cookie)
+ .return(obj.details)
)
redirects = []
@@ -412,15 +415,16 @@ class Server
_.pick(incomingRes, "headers", "statusCode")
)
- jar = str.getJar()
+ newUrl ?= urlStr
runPhase =>
- request.setJarCookies(jar, automationRequest)
- .then (c) =>
+ ## get the cookies that would be sent with this request so they can be rehydrated
+ automationRequest("get:cookies", {
+ domain: cors.getSuperDomain(newUrl)
+ })
+ .then (cookies) =>
@_remoteVisitingUrl = false
- newUrl ?= urlStr
-
statusIs2xxOrAllowedFailure = ->
## is our status code in the 2xx range, or have we disabled failing
## on status code?
@@ -434,7 +438,7 @@ class Server
contentType
url: newUrl
status: incomingRes.statusCode
- cookies: c
+ cookies
statusText: statusCode.getText(incomingRes.statusCode)
redirects
originalUrl
@@ -484,9 +488,8 @@ class Server
buffers.set({
url: newUrl
- jar: jar
stream: responseBufferStream
- details: details
+ details
originalUrl: originalUrl
response: incomingRes
})
@@ -635,30 +638,7 @@ class Server
{hostname} = url.parse("http://#{host}")
onProxyErr = (err, req, res) ->
- ## by default http-proxy will call socket.end
- ## with no data, so we need to override the end
- ## function and write our own response
- ## https://github.com/nodejitsu/node-http-proxy/blob/master/lib/http-proxy/passes/ws-incoming.js#L159
- end = socket.end
- socket.end = ->
- socket.end = end
-
- response = [
- "HTTP/#{req.httpVersion} 502 #{statusCode.getText(502)}"
- "X-Cypress-Proxy-Error-Message: #{err.message}"
- "X-Cypress-Proxy-Error-Code: #{err.code}"
- ].join("\r\n") + "\r\n\r\n"
-
- proxiedUrl = "#{protocol}//#{hostname}:#{port}"
-
- debug(
- "Got ERROR proxying websocket connection to url: '%s' received error: '%s' with code '%s'",
- proxiedUrl,
- err.toString()
- err.code
- )
-
- socket.end(response)
+ debug("Got ERROR proxying websocket connection", { err, port, protocol, hostname, req })
proxy.ws(req, socket, head, {
secure: false
@@ -726,10 +706,11 @@ class Server
startWebsockets: (automation, config, options = {}) ->
options.onResolveUrl = @_onResolveUrl.bind(@)
options.onRequest = @_onRequest.bind(@)
+ options.onIncomingXhr = @_xhrServer.onIncomingXhr
+ options.onResetXhrServer = @_xhrServer.reset
@_socket = Socket(config)
@_socket.startListening(@_server, automation, config, options)
@_normalizeReqUrl(@_server)
- # handleListeners(@_server)
module.exports = Server
diff --git a/packages/server/lib/socket.coffee b/packages/server/lib/socket.coffee
index af47a7df876..8fe57f62939 100644
--- a/packages/server/lib/socket.coffee
+++ b/packages/server/lib/socket.coffee
@@ -132,6 +132,8 @@ class Socket
_.defaults options,
socketId: null
+ onIncomingXhr: ->
+ onResetXhrServer: ->
onSetRunnables: ->
onMocha: ->
onConnect: ->
@@ -298,6 +300,10 @@ class Socket
options.onResolveUrl(url, headers, automationRequest, resolveOpts)
when "http:request"
options.onRequest(headers, automationRequest, args[0])
+ when "reset:xhr:server"
+ options.onResetXhrServer()
+ when "incoming:xhr"
+ options.onIncomingXhr(args[0], args[1])
when "get:fixture"
fixture.get(config.fixturesFolder, args[0], args[1])
when "read:file"
@@ -343,6 +349,7 @@ class Socket
socket.on event, (data) =>
@toReporter(event, data)
+
end: ->
@ended = true
diff --git a/packages/server/lib/template_engine.js b/packages/server/lib/template_engine.js
new file mode 100644
index 00000000000..44ca41e0bcc
--- /dev/null
+++ b/packages/server/lib/template_engine.js
@@ -0,0 +1,29 @@
+const Sqrl = require('squirrelly')
+const fs = require('./util/fs')
+
+const cache = {}
+
+module.exports = {
+ cache,
+
+ render (filePath, options, cb) {
+ const cachedFn = cache[filePath]
+
+ // if we already have a cachedFn function
+ if (cachedFn) {
+ // just return it and move in
+ return cb(null, cachedFn(options, Sqrl))
+ }
+
+ // else go read it off the filesystem
+ return fs
+ .readFileAsync(filePath, 'utf8')
+ .then((str) => {
+ // and cache the Sqrl compiled template fn
+ const compiledFn = cache[filePath] = Sqrl.Compile(str)
+
+ return compiledFn(options, Sqrl)
+ })
+ .asCallback(cb)
+ },
+}
diff --git a/packages/server/lib/util/app_data.js b/packages/server/lib/util/app_data.js
index 77d87d871e6..aa147615ff0 100644
--- a/packages/server/lib/util/app_data.js
+++ b/packages/server/lib/util/app_data.js
@@ -31,7 +31,6 @@ const getSymlinkType = function () {
}
return 'dir'
-
}
const isProduction = () => {
diff --git a/packages/server/lib/util/args.js b/packages/server/lib/util/args.js
index d343b5ecaa6..065002c1a5c 100644
--- a/packages/server/lib/util/args.js
+++ b/packages/server/lib/util/args.js
@@ -10,7 +10,7 @@ const nestedObjectsInCurlyBracesRe = /\{(.+?)\}/g
const nestedArraysInSquareBracketsRe = /\[(.+?)\]/g
const everythingAfterFirstEqualRe = /=(.*)/
-const whitelist = 'cwd appPath execPath apiKey smokeTest getKey generateKey runProject project spec reporter reporterOptions port env ci record updating ping key logs clearLogs returnPkg version mode headed config exit exitWithCode browser runMode outputPath parallel ciBuildId group inspectBrk proxySource'.split(' ')
+const whitelist = 'cwd appPath execPath apiKey smokeTest getKey generateKey runProject project spec reporter reporterOptions port env ci record updating ping key logs clearLogs returnPkg version mode headed config exit exitWithCode browser runMode outputPath parallel ciBuildId group inspectBrk configFile proxySource'.split(' ')
// returns true if the given string has double quote character "
// only at the last position.
@@ -30,7 +30,6 @@ const normalizeBackslash = function (s) {
}
return s
-
}
const normalizeBackslashes = function (options) {
@@ -39,7 +38,7 @@ const normalizeBackslashes = function (options) {
// backslash at the end
// https://github.com/cypress-io/cypress/issues/535
// these properties are paths and likely to have backslash on Windows
- const pathProperties = ['runProject', 'project', 'appPath', 'execPath']
+ const pathProperties = ['runProject', 'project', 'appPath', 'execPath', 'configFile']
pathProperties.forEach((property) => {
if (options[property]) {
@@ -146,6 +145,7 @@ module.exports = {
'app-path': 'appPath',
'ci-build-id': 'ciBuildId',
'clear-logs': 'clearLogs',
+ 'config-file': 'configFile',
'exec-path': 'execPath',
'exit-with-code': 'exitWithCode',
'inspect-brk': 'inspectBrk',
diff --git a/packages/server/lib/util/buffers.js b/packages/server/lib/util/buffers.js
index 3bec3c8e23e..4524a43aae3 100644
--- a/packages/server/lib/util/buffers.js
+++ b/packages/server/lib/util/buffers.js
@@ -4,6 +4,14 @@ const uri = require('./uri')
let buffers = []
+const stripPort = (url) => {
+ try {
+ return uri.removeDefaultPort(url).format()
+ } catch (e) {
+ return url
+ }
+}
+
module.exports = {
all () {
return buffers
@@ -20,35 +28,26 @@ module.exports = {
},
set (obj = {}) {
+ obj.url = stripPort(obj.url)
+ obj.originalUrl = stripPort(obj.originalUrl)
+
+ debug('setting buffer %o', _.pick(obj, 'url'))
+
return buffers.push(_.pick(obj, 'url', 'originalUrl', 'jar', 'stream', 'response', 'details'))
},
getByOriginalUrl (str) {
- return _.find(buffers, { originalUrl: str })
- },
-
- get (str) {
- const find = (str) => {
- return _.find(buffers, { url: str })
- }
-
- const b = find(str)
+ const b = _.find(buffers, { originalUrl: stripPort(str) })
if (b) {
- return b
+ debug('found request buffer by original url %o', { str, buffer: _.pick(b, 'url', 'originalUrl', 'details'), bufferCount: buffers.length })
}
- let parsed = uri.parse(str)
-
- //# if we're on https and we have a port
- //# then attempt to find the buffer by
- //# slicing off the port since our buffer
- //# was likely stored without a port
- if ((parsed.protocol === 'https:') && parsed.port) {
- parsed = uri.removePort(parsed)
+ return b
+ },
- return find(parsed.format())
- }
+ get (str) {
+ return _.find(buffers, { url: stripPort(str) })
},
take (str) {
diff --git a/packages/server/lib/util/chrome_policy_check.js b/packages/server/lib/util/chrome_policy_check.js
index 8e550f7aec6..a54978adabe 100644
--- a/packages/server/lib/util/chrome_policy_check.js
+++ b/packages/server/lib/util/chrome_policy_check.js
@@ -99,7 +99,7 @@ module.exports = {
*/
if (os.platform() === 'win32') {
try {
- const registryJs = require('@cypress/registry-js')
+ const registryJs = require('registry-js')
module.exports = {
run: getRunner(registryJs),
diff --git a/packages/server/lib/util/cors.js b/packages/server/lib/util/cors.js
index e0608f76681..69e8f76d80a 100644
--- a/packages/server/lib/util/cors.js
+++ b/packages/server/lib/util/cors.js
@@ -6,63 +6,83 @@ const parseDomain = require('parse-domain')
const ipAddressRe = /^[\d\.]+$/
-module.exports = {
- parseUrlIntoDomainTldPort (str) {
- let { hostname, port, protocol } = url.parse(str)
+function getSuperDomain (url) {
+ const parsed = parseUrlIntoDomainTldPort(url)
- if (port == null) {
- port = protocol === 'https:' ? '443' : '80'
- }
+ return _.compact([parsed.domain, parsed.tld]).join('.')
+}
- let parsed = parseDomain(hostname, {
- privateTlds: true, // use the public suffix
- customTlds: ipAddressRe,
- })
-
- // if we couldn't get a parsed domain
- if (!parsed) {
- // then just fall back to a dumb check
- // based on assumptions that the tld
- // is the last segment after the final
- // '.' and that the domain is the segment
- // before that
- const segments = hostname.split('.')
-
- parsed = {
- tld: segments[segments.length - 1] || '',
- domain: segments[segments.length - 2] || '',
- }
- }
+function _parseDomain (domain, options = {}) {
+ return parseDomain(domain, _.defaults(options, {
+ privateTlds: true,
+ customTlds: ipAddressRe,
+ }))
+}
- const obj = {}
+function parseUrlIntoDomainTldPort (str) {
+ let { hostname, port, protocol } = url.parse(str)
- obj.port = port
- obj.tld = parsed.tld
- obj.domain = parsed.domain
- // obj.protocol = protocol
+ if (port == null) {
+ port = protocol === 'https:' ? '443' : '80'
+ }
- debug('Parsed URL %o', obj)
+ let parsed = _parseDomain(hostname)
- return obj
- },
+ // if we couldn't get a parsed domain
+ if (!parsed) {
+ // then just fall back to a dumb check
+ // based on assumptions that the tld
+ // is the last segment after the final
+ // '.' and that the domain is the segment
+ // before that
+ const segments = hostname.split('.')
- urlMatchesOriginPolicyProps (urlStr, props) {
- // take a shortcut here in the case
- // where remoteHostAndPort is null
- if (!props) {
- return false
+ parsed = {
+ tld: segments[segments.length - 1] || '',
+ domain: segments[segments.length - 2] || '',
}
+ }
+
+ const obj = {}
+
+ obj.port = port
+ obj.tld = parsed.tld
+ obj.domain = parsed.domain
+ // obj.protocol = protocol
+
+ debug('Parsed URL %o', obj)
+
+ return obj
+}
+
+function urlMatchesOriginPolicyProps (urlStr, props) {
+ // take a shortcut here in the case
+ // where remoteHostAndPort is null
+ if (!props) {
+ return false
+ }
+
+ const parsedUrl = this.parseUrlIntoDomainTldPort(urlStr)
+
+ // does the parsedUrl match the parsedHost?
+ return _.isEqual(parsedUrl, props)
+}
+
+function urlMatchesOriginProtectionSpace (urlStr, origin) {
+ const normalizedUrl = uri.addDefaultPort(urlStr).format()
+ const normalizedOrigin = uri.addDefaultPort(origin).format()
+
+ return _.startsWith(normalizedUrl, normalizedOrigin)
+}
+
+module.exports = {
+ parseUrlIntoDomainTldPort,
- const parsedUrl = this.parseUrlIntoDomainTldPort(urlStr)
+ parseDomain: _parseDomain,
- // does the parsedUrl match the parsedHost?
- return _.isEqual(parsedUrl, props)
- },
+ getSuperDomain,
- urlMatchesOriginProtectionSpace (urlStr, origin) {
- const normalizedUrl = uri.addDefaultPort(urlStr).format()
- const normalizedOrigin = uri.addDefaultPort(origin).format()
+ urlMatchesOriginPolicyProps,
- return _.startsWith(normalizedUrl, normalizedOrigin)
- },
+ urlMatchesOriginProtectionSpace,
}
diff --git a/packages/server/lib/util/electron_app.js b/packages/server/lib/util/electron_app.js
index 0af8548b5db..9180b559e28 100644
--- a/packages/server/lib/util/electron_app.js
+++ b/packages/server/lib/util/electron_app.js
@@ -1,3 +1,5 @@
+const debug = require('debug')('cypress:server:electron_app')
+
const scale = () => {
try {
const { app } = require('electron')
@@ -12,6 +14,13 @@ const ready = () => {
const Promise = require('bluebird')
const { app } = require('electron')
+ // electron >= 5.0.0 will exit the app if all browserwindows are closed,
+ // this is obviously undesirable in run mode
+ // https://github.com/cypress-io/cypress/pull/4720#issuecomment-514316695
+ app.on('window-all-closed', () => {
+ debug('all BrowserWindows closed, not exiting')
+ })
+
const waitForReady = () => {
return new Promise((resolve) => {
app.on('ready', resolve)
diff --git a/packages/server/lib/util/ensure-url.js b/packages/server/lib/util/ensure-url.js
new file mode 100644
index 00000000000..2abb1126866
--- /dev/null
+++ b/packages/server/lib/util/ensure-url.js
@@ -0,0 +1,68 @@
+"use strict";
+var __importDefault = (this && this.__importDefault) || function (mod) {
+ return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+var __importStar = (this && this.__importStar) || function (mod) {
+ if (mod && mod.__esModule) return mod;
+ var result = {};
+ if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
+ result["default"] = mod;
+ return result;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var lodash_1 = __importDefault(require("lodash"));
+var bluebird_1 = __importDefault(require("bluebird"));
+var debug_1 = __importDefault(require("debug"));
+var request_promise_1 = __importDefault(require("request-promise"));
+var url = __importStar(require("url"));
+var network_1 = require("@packages/network");
+var debug = debug_1.default('cypress:server:ensure-url');
+exports.retryIsListening = function (urlStr, options) {
+ var retryIntervals = options.retryIntervals, onRetry = options.onRetry;
+ var delaysRemaining = lodash_1.default.clone(retryIntervals);
+ var run = function () {
+ debug('checking that baseUrl is available', {
+ baseUrl: urlStr,
+ delaysRemaining: delaysRemaining,
+ retryIntervals: retryIntervals,
+ });
+ return exports.isListening(urlStr)
+ .catch(function (err) {
+ var delay = delaysRemaining.shift();
+ if (!delay) {
+ throw err;
+ }
+ onRetry({
+ delay: delay,
+ attempt: retryIntervals.length - delaysRemaining.length,
+ remaining: delaysRemaining.length + 1,
+ });
+ return bluebird_1.default.delay(delay)
+ .then(function () {
+ return run();
+ });
+ });
+ };
+ return run();
+};
+exports.isListening = function (urlStr) {
+ // takes a urlStr and verifies the hostname + port is listening
+ var _a = url.parse(urlStr), hostname = _a.hostname, protocol = _a.protocol, port = _a.port;
+ if (port == null) {
+ port = protocol === 'https:' ? '443' : '80';
+ }
+ if (process.env.HTTP_PROXY) {
+ // cannot make arbitrary connections behind a proxy, attempt HTTP/HTTPS
+ // For some reason, TypeScript gets confused by the "agent" parameter
+ // and required double ts-ignore to allow it on local machines and on CI
+ // @ts-ignore
+ return request_promise_1.default({
+ url: urlStr,
+ // @ts-ignore
+ agent: network_1.agent,
+ proxy: null,
+ })
+ .catch({ name: 'StatusCodeError' }, function () { }); // we just care if it can connect, not if it's a valid resource
+ }
+ return network_1.connect.getAddress(Number(port), String(hostname));
+};
diff --git a/packages/server/lib/util/ensure-url.ts b/packages/server/lib/util/ensure-url.ts
index 604ecbcbaf6..58da35d64bc 100644
--- a/packages/server/lib/util/ensure-url.ts
+++ b/packages/server/lib/util/ensure-url.ts
@@ -58,6 +58,9 @@ export const isListening = (urlStr: string) => {
if (process.env.HTTP_PROXY) {
// cannot make arbitrary connections behind a proxy, attempt HTTP/HTTPS
+ // For some reason, TypeScript gets confused by the "agent" parameter
+ // and required double ts-ignore to allow it on local machines and on CI
+ // @ts-ignore
return rp({
url: urlStr,
// @ts-ignore
diff --git a/packages/server/lib/util/file.js b/packages/server/lib/util/file.js
index dfa8cf05f5a..7270f04c530 100644
--- a/packages/server/lib/util/file.js
+++ b/packages/server/lib/util/file.js
@@ -127,7 +127,6 @@ class File {
}
throw err
-
})
.finally(() => {
debug('read succeeded or failed for %s', this.path)
diff --git a/packages/server/lib/util/find_system_node.js b/packages/server/lib/util/find_system_node.js
new file mode 100644
index 00000000000..cec8d87d338
--- /dev/null
+++ b/packages/server/lib/util/find_system_node.js
@@ -0,0 +1,83 @@
+const debug = require('debug')('cypress:server:find_system_node')
+const execa = require('execa')
+const fixPath = require('fix-path')
+const Promise = require('bluebird')
+const which = require('which')
+
+const NODE_VERSION_RE = /^v(\d+\.\d+\.\d+)/m
+
+/*
+ * Find the full path to a `node` binary on the current PATH.
+ * Note about fix-path:
+ * while fix-path is good, it can cause unexpected behavior when running Cypress locally
+ * for example, using NVM we set local Node to 8
+ * then fix-path adds all user paths, and the found Node is whatever we have
+ * installed globally, like 6 or 10 (NVM path comes later)
+ * So this function only fixes the path, if the Node cannot be found on first attempt
+ */
+function findNodeInFullPath () {
+ debug('finding Node with $PATH %s', process.env.PATH)
+
+ return Promise.fromCallback((cb) => {
+ return which('node', cb)
+ })
+ .catch(() => {
+ debug('could not find Node, trying to fix path')
+ // Fix the $PATH on macOS when run from a GUI app
+ fixPath()
+ debug('searching again with fixed $PATH %s', process.env.PATH)
+
+ return Promise.fromCallback((cb) => {
+ return which('node', cb)
+ })
+ })
+ .tap((path) => {
+ debug('found Node %o', { path })
+ })
+ .catch((err) => {
+ debug('could not find Node %o', { err })
+
+ throw err
+ })
+}
+
+function findNodeVersionFromPath (path) {
+ if (!path) {
+ return Promise.resolve(null)
+ }
+
+ return execa.stdout(path, ['-v'])
+ .then((stdout) => {
+ debug('node -v returned %o', { stdout })
+ const matches = NODE_VERSION_RE.exec(stdout)
+
+ if (matches && matches.length === 2) {
+ const version = matches[1]
+
+ debug('found Node version', { version })
+
+ return version
+ }
+ })
+ .catch((err) => {
+ debug('could not resolve Node version %o', { err })
+
+ throw err
+ })
+}
+
+function findNodePathAndVersion () {
+ return findNodeInFullPath()
+ .then((path) => {
+ return findNodeVersionFromPath(path)
+ .then((version) => {
+ return {
+ path, version,
+ }
+ })
+ })
+}
+
+module.exports = {
+ findNodePathAndVersion,
+}
diff --git a/packages/server/lib/util/newlines.js b/packages/server/lib/util/newlines.js
new file mode 100644
index 00000000000..ef4155558e6
--- /dev/null
+++ b/packages/server/lib/util/newlines.js
@@ -0,0 +1,12 @@
+const R = require('ramda')
+
+const addNewlineAtEveryNChar = (str, n) => {
+ // Add a newline char after every 'n' char
+ if (str) {
+ return R.splitEvery(n, str).join('\n')
+ }
+}
+
+module.exports = {
+ addNewlineAtEveryNChar,
+}
diff --git a/packages/server/lib/util/proxy.js b/packages/server/lib/util/proxy.js
new file mode 100644
index 00000000000..10d935ec18d
--- /dev/null
+++ b/packages/server/lib/util/proxy.js
@@ -0,0 +1,86 @@
+"use strict";
+var __importDefault = (this && this.__importDefault) || function (mod) {
+ return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var lodash_1 = __importDefault(require("lodash"));
+var debug_1 = __importDefault(require("debug"));
+var os_1 = __importDefault(require("os"));
+var get_windows_proxy_1 = require("./get-windows-proxy");
+var debug = debug_1.default('cypress:server:util:proxy');
+var falsyEnv = function (v) {
+ return v === 'false' || v === '0' || !v;
+};
+var copyLowercaseEnvToUppercase = function (name) {
+ // uppercase environment variables are used throughout Cypress and dependencies
+ // but users sometimes supply these vars as lowercase
+ var lowerEnv = process.env[name.toLowerCase()];
+ if (lowerEnv) {
+ debug('overriding uppercase env var with lowercase %o', { name: name });
+ process.env[name.toUpperCase()] = lowerEnv;
+ }
+};
+var normalizeEnvironmentProxy = function () {
+ if (falsyEnv(process.env.HTTP_PROXY)) {
+ debug('HTTP_PROXY is falsy, disabling HTTP_PROXY');
+ delete process.env.HTTP_PROXY;
+ }
+ if (!process.env.HTTPS_PROXY && process.env.HTTP_PROXY) {
+ // request library will use HTTP_PROXY as a fallback for HTTPS urls, but
+ // proxy-from-env will not, so let's just force it to fall back like this
+ debug('setting HTTPS_PROXY to HTTP_PROXY since it does not exist');
+ process.env.HTTPS_PROXY = process.env.HTTP_PROXY;
+ }
+ if (!process.env.hasOwnProperty('NO_PROXY')) {
+ // don't proxy localhost, to match Chrome's default behavior and user expectation
+ debug('setting default NO_PROXY of ``');
+ process.env.NO_PROXY = '';
+ }
+ var noProxyParts = lodash_1.default.compact((process.env.NO_PROXY || '').split(','));
+ if (!noProxyParts.includes('<-loopback>')) {
+ debug('<-loopback> not found, adding localhost to NO_PROXY');
+ process.env.NO_PROXY = noProxyParts.concat([
+ '127.0.0.1', '::1', 'localhost',
+ ]).join(',');
+ }
+ debug('normalized proxy environment variables %o', lodash_1.default.pick(process.env, [
+ 'NO_PROXY', 'HTTP_PROXY', 'HTTPS_PROXY',
+ ]));
+};
+var mergeNpmProxyVars = function () {
+ // copy npm's `proxy` and `https-proxy` config if they are set
+ // https://github.com/cypress-io/cypress/pull/4705
+ [
+ ['npm_config_proxy', 'HTTP_PROXY'],
+ ['npm_config_https_proxy', 'HTTPS_PROXY'],
+ ].forEach(function (_a) {
+ var from = _a[0], to = _a[1];
+ if (!falsyEnv(process.env[from]) && lodash_1.default.isUndefined(process.env[to])) {
+ debug('using npm\'s %s as %s', from, to);
+ process.env[to] = process.env[from];
+ }
+ });
+};
+exports.loadSystemProxySettings = function () {
+ debug('found proxy environment variables %o', lodash_1.default.pick(process.env, [
+ 'NO_PROXY', 'HTTP_PROXY', 'HTTPS_PROXY',
+ 'no_proxy', 'http_proxy', 'https_proxy',
+ 'npm_config_proxy', 'npm_config_https_proxy',
+ ]));
+ ['NO_PROXY', 'HTTP_PROXY', 'HTTPS_PROXY'].forEach(copyLowercaseEnvToUppercase);
+ mergeNpmProxyVars();
+ if (!lodash_1.default.isUndefined(process.env.HTTP_PROXY)) {
+ normalizeEnvironmentProxy();
+ return;
+ }
+ if (os_1.default.platform() !== 'win32') {
+ return;
+ }
+ var windowsProxy = get_windows_proxy_1.getWindowsProxy();
+ if (windowsProxy) {
+ process.env.HTTP_PROXY = process.env.HTTPS_PROXY = windowsProxy.httpProxy;
+ process.env.NO_PROXY = process.env.NO_PROXY || windowsProxy.noProxy;
+ }
+ normalizeEnvironmentProxy();
+ return 'win32';
+};
diff --git a/packages/server/lib/util/replace_stream.js b/packages/server/lib/util/replace_stream.js
new file mode 100644
index 00000000000..a20a4b7a600
--- /dev/null
+++ b/packages/server/lib/util/replace_stream.js
@@ -0,0 +1,67 @@
+"use strict";
+var __importDefault = (this && this.__importDefault) || function (mod) {
+ return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var through_1 = __importDefault(require("through"));
+var GraphemeSplitter = require('grapheme-splitter');
+var splitter = new GraphemeSplitter();
+/**
+ * UTF-8 grapheme aware stream replacer
+ * https://github.com/cypress-io/cypress/pull/4984
+ */
+function replaceStream(patterns, replacements, options) {
+ if (options === void 0) { options = { maxTailLength: 100 }; }
+ if (!Array.isArray(patterns)) {
+ patterns = [patterns];
+ }
+ if (!Array.isArray(replacements)) {
+ replacements = [replacements];
+ }
+ var tail = '';
+ return through_1.default(function write(chunk) {
+ var _this = this;
+ var emitted = false;
+ var emitTailUpTo = function (index) {
+ emitted = true;
+ _this.queue(tail.slice(0, index));
+ tail = tail.slice(index);
+ };
+ chunk = chunk.toString('utf8');
+ tail = tail + chunk;
+ var replacementEndIndex = 0;
+ patterns.forEach(function (pattern, i) {
+ var replacement = replacements[i];
+ tail = tail.replace(pattern, function replacer(match) {
+ // ugly, but necessary due to bizarre function signature of String#replace
+ var offset = arguments[arguments.length - 2]; // eslint-disable-line prefer-rest-params
+ if (offset + replacement.length > replacementEndIndex) {
+ replacementEndIndex = offset + replacement.length;
+ }
+ return match.replace(pattern, replacement);
+ });
+ });
+ // if a replacement did occur, we should emit up to the end of what was replaced
+ if (replacementEndIndex) {
+ emitTailUpTo(replacementEndIndex);
+ }
+ // if we're overflowing max chars, emit the overflow at the beginning
+ if (tail.length > options.maxTailLength) {
+ // the maximum width of a unicode char is 4
+ // use grapheme-splitter to find a good breaking point
+ var breakableAt = splitter.nextBreak(tail, Math.max(0, tail.length - options.maxTailLength - 4));
+ emitTailUpTo(breakableAt);
+ }
+ if (!emitted) {
+ // this.queue('')
+ }
+ }, function end() {
+ if (tail.length) {
+ this.queue(tail);
+ }
+ this.queue(null);
+ });
+}
+module.exports = {
+ replaceStream: replaceStream,
+};
diff --git a/packages/server/lib/util/replace_stream.ts b/packages/server/lib/util/replace_stream.ts
new file mode 100644
index 00000000000..ee423ff6c8f
--- /dev/null
+++ b/packages/server/lib/util/replace_stream.ts
@@ -0,0 +1,84 @@
+import through from 'through'
+
+const GraphemeSplitter = require('grapheme-splitter')
+
+interface IGraphemeSplitter {
+ nextBreak: (string: string, at: number) => number
+}
+
+const splitter: IGraphemeSplitter = new GraphemeSplitter()
+
+/**
+ * UTF-8 grapheme aware stream replacer
+ * https://github.com/cypress-io/cypress/pull/4984
+ */
+function replaceStream (patterns: RegExp | RegExp[], replacements: string | string[], options = { maxTailLength: 100 }) {
+ if (!Array.isArray(patterns)) {
+ patterns = [patterns]
+ }
+
+ if (!Array.isArray(replacements)) {
+ replacements = [replacements]
+ }
+
+ let tail = ''
+
+ return through(function write (this: InternalStream, chunk) {
+ let emitted = false
+
+ const emitTailUpTo = (index) => {
+ emitted = true
+ this.queue(tail.slice(0, index))
+ tail = tail.slice(index)
+ }
+
+ chunk = chunk.toString('utf8')
+
+ tail = tail + chunk
+
+ let replacementEndIndex = 0
+
+ ;(patterns as RegExp[]).forEach((pattern, i) => {
+ const replacement = replacements[i]
+
+ tail = tail.replace(pattern, function replacer (match) {
+ // ugly, but necessary due to bizarre function signature of String#replace
+ const offset = arguments[arguments.length - 2] // eslint-disable-line prefer-rest-params
+
+ if (offset + replacement.length > replacementEndIndex) {
+ replacementEndIndex = offset + replacement.length
+ }
+
+ return match.replace(pattern, replacement)
+ })
+ })
+
+ // if a replacement did occur, we should emit up to the end of what was replaced
+ if (replacementEndIndex) {
+ emitTailUpTo(replacementEndIndex)
+ }
+
+ // if we're overflowing max chars, emit the overflow at the beginning
+ if (tail.length > options.maxTailLength) {
+ // the maximum width of a unicode char is 4
+ // use grapheme-splitter to find a good breaking point
+ const breakableAt = splitter.nextBreak(tail, Math.max(0, tail.length - options.maxTailLength - 4))
+
+ emitTailUpTo(breakableAt)
+ }
+
+ if (!emitted) {
+ // this.queue('')
+ }
+ }, function end (this: InternalStream) {
+ if (tail.length) {
+ this.queue(tail)
+ }
+
+ this.queue(null)
+ })
+}
+
+module.exports = {
+ replaceStream,
+}
diff --git a/packages/server/lib/util/security.js b/packages/server/lib/util/security.js
index 10566960209..6e37c11bf51 100644
--- a/packages/server/lib/util/security.js
+++ b/packages/server/lib/util/security.js
@@ -1,7 +1,8 @@
// Tests located in packages/server/test/unit/security_spec
const pumpify = require('pumpify')
-const replacestream = require('replacestream')
+const { replaceStream } = require('./replace_stream')
+const utf8Stream = require('utf8-stream')
const topOrParentEqualityBeforeRe = /((?:window|self)(?:\.|\[['"](?:top|self)['"]\])?\s*[!=]==?\s*(?:(?:window|self)(?:\.|\[['"]))?)(top|parent)(?![\w])/g
const topOrParentEqualityAfterRe = /(top|parent)((?:["']\])?\s*[!=]==?\s*(?:window|self))/g
@@ -18,10 +19,21 @@ const strip = (html) => {
const stripStream = () => {
return pumpify(
- replacestream(topOrParentEqualityBeforeRe, '$1self'),
- replacestream(topOrParentEqualityAfterRe, 'self$2'),
- replacestream(topOrParentLocationOrFramesRe, '$1self$3$4'),
- replacestream(jiraTopWindowGetterRe, '$1 || $2.parent.__Cypress__$3')
+ utf8Stream(),
+ replaceStream(
+ [
+ topOrParentEqualityBeforeRe,
+ topOrParentEqualityAfterRe,
+ topOrParentLocationOrFramesRe,
+ jiraTopWindowGetterRe,
+ ],
+ [
+ '$1self',
+ 'self$2',
+ '$1self$3$4',
+ '$1 || $2.parent.__Cypress__$3',
+ ]
+ )
)
}
diff --git a/packages/server/lib/util/server_destroy.js b/packages/server/lib/util/server_destroy.js
index 802539e376b..92d45668f89 100644
--- a/packages/server/lib/util/server_destroy.js
+++ b/packages/server/lib/util/server_destroy.js
@@ -1,12 +1,5 @@
-// TODO: This file was created by bulk-decaffeinate.
-// Sanity-check the conversion and remove this comment.
-/*
- * decaffeinate suggestions:
- * DS102: Remove unnecessary code created because of implicit returns
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
const Promise = require('bluebird')
-const allowDestroy = require('server-destroy')
+const { allowDestroy } = require('@packages/network')
module.exports = function (server) {
allowDestroy(server)
@@ -16,4 +9,4 @@ module.exports = function (server) {
.catch(() => {})
}
}
-//# dont catch any errors
+// dont catch any errors
diff --git a/packages/server/lib/util/settings.js b/packages/server/lib/util/settings.js
index c2ed642e35e..def3b56fb9c 100644
--- a/packages/server/lib/util/settings.js
+++ b/packages/server/lib/util/settings.js
@@ -31,6 +31,14 @@ const flattenCypress = function (obj) {
}
}
+const maybeVerifyConfigFile = Promise.method((configFile) => {
+ if (configFile === false) {
+ return
+ }
+
+ return fs.statAsync(configFile)
+})
+
const renameVisitToPageLoad = function (obj) {
let v
@@ -78,7 +86,7 @@ module.exports = {
},
_logReadErr (file, err) {
- return this._err('ERROR_READING_FILE', file, err)
+ errors.throw('ERROR_READING_FILE', file, err)
},
_logWriteErr (file, err) {
@@ -102,13 +110,16 @@ module.exports = {
}
return memo
-
}
, _.cloneDeep(obj))
},
- id (projectRoot) {
- const file = this._pathToFile(projectRoot, 'cypress.json')
+ configFile (options = {}) {
+ return options.configFile === false ? false : (options.configFile || 'cypress.json')
+ },
+
+ id (projectRoot, options = {}) {
+ const file = this.pathToConfigFile(projectRoot, options)
return fs.readJsonAsync(file)
.get('projectId')
@@ -117,33 +128,37 @@ module.exports = {
})
},
- exists (projectRoot) {
- const file = this._pathToFile(projectRoot, 'cypress.json')
+ exists (projectRoot, options = {}) {
+ const file = this.pathToConfigFile(projectRoot, options)
- //# first check if cypress.json exists
- return fs.statAsync(file)
+ // first check if cypress.json exists
+ return maybeVerifyConfigFile(file)
.then(() =>
- //# if it does also check that the projectRoot
- //# directory is writable
+ // if it does also check that the projectRoot
+ // directory is writable
{
return fs.accessAsync(projectRoot, fs.W_OK)
}).catch({ code: 'ENOENT' }, (err) => {
- //# cypress.json does not exist, we missing project
+ // cypress.json does not exist, we missing project
log('cannot find file %s', file)
- return this._err('PROJECT_DOES_NOT_EXIST', projectRoot, err)
+ return this._err('CONFIG_FILE_NOT_FOUND', this.configFile(options), projectRoot)
}).catch((err) => {
if (errors.isCypressErr(err)) {
throw err
}
- //# else we cannot read due to folder permissions
+ // else we cannot read due to folder permissions
return this._logReadErr(file, err)
})
},
- read (projectRoot) {
- const file = this._pathToFile(projectRoot, 'cypress.json')
+ read (projectRoot, options = {}) {
+ if (options.configFile === false) {
+ return Promise.resolve({})
+ }
+
+ const file = this.pathToConfigFile(projectRoot, options)
return fs.readJsonAsync(file)
.catch({ code: 'ENOENT' }, () => {
@@ -159,7 +174,6 @@ module.exports = {
//# else write the new reduced obj
return this._write(file, changed)
-
}).catch((err) => {
if (errors.isCypressErr(err)) {
throw err
@@ -170,7 +184,7 @@ module.exports = {
},
readEnv (projectRoot) {
- const file = this._pathToFile(projectRoot, 'cypress.env.json')
+ const file = this.pathToCypressEnvJson(projectRoot)
return fs.readJsonAsync(file)
.catch({ code: 'ENOENT' }, () => {
@@ -185,23 +199,29 @@ module.exports = {
})
},
- write (projectRoot, obj = {}) {
+ write (projectRoot, obj = {}, options = {}) {
+ if (options.configFile === false) {
+ return Promise.resolve({})
+ }
+
return this.read(projectRoot)
.then((settings) => {
_.extend(settings, obj)
- const file = this._pathToFile(projectRoot, 'cypress.json')
+ const file = this.pathToConfigFile(projectRoot, options)
return this._write(file, settings)
})
},
- remove (projectRoot) {
- return fs.unlinkSync(this._pathToFile(projectRoot, 'cypress.json'))
+ remove (projectRoot, options = {}) {
+ return fs.unlinkSync(this.pathToConfigFile(projectRoot, options))
},
- pathToCypressJson (projectRoot) {
- return this._pathToFile(projectRoot, 'cypress.json')
+ pathToConfigFile (projectRoot, options = {}) {
+ const configFile = this.configFile(options)
+
+ return configFile && this._pathToFile(projectRoot, configFile)
},
pathToCypressEnvJson (projectRoot) {
diff --git a/packages/server/lib/util/shell.js b/packages/server/lib/util/shell.js
index 2021873dfe3..36c31f639f2 100644
--- a/packages/server/lib/util/shell.js
+++ b/packages/server/lib/util/shell.js
@@ -77,7 +77,6 @@ const sourceShellCommand = function (cmd, shell) {
//# so suppress it by sending it to /dev/null and ignore
//# any failures with this
return `source ${profilePath} > /dev/null 2>&1; ${cmd}`
-
}
const findBash = () => {
diff --git a/packages/server/lib/util/specs.js b/packages/server/lib/util/specs.js
index b14ffa5dfa4..bee7d99f0e1 100644
--- a/packages/server/lib/util/specs.js
+++ b/packages/server/lib/util/specs.js
@@ -14,6 +14,7 @@ const path = require('path')
const check = require('check-more-types')
const debug = require('debug')('cypress:server:specs')
const minimatch = require('minimatch')
+const Promise = require('bluebird')
const glob = require('./glob')
const MINIMATCH_OPTIONS = { dot: true, matchBase: true }
@@ -24,7 +25,10 @@ const getPatternRelativeToProjectRoot = (specPattern, projectRoot) => {
})
}
-const find = function (config, specPattern) {
+/**
+ * Finds all spec files that pass the config.
+ */
+const find = function findSpecs (config, specPattern) {
let fixturesFolderPath
la(check.maybe.strings(specPattern), 'invalid spec pattern', specPattern)
@@ -36,6 +40,12 @@ const find = function (config, specPattern) {
integrationFolderPath
)
+ if (specPattern) {
+ debug('spec pattern "%s"', specPattern)
+ } else {
+ debug('there is no spec pattern')
+ }
+
//# support files are not automatically
//# ignored because only _fixtures are hard
//# coded. the rest is simply whatever is in
@@ -132,17 +142,31 @@ const find = function (config, specPattern) {
.value()
}
- //# grab all the files
- return glob(config.testFiles, options)
+ // grab all the files
+ debug('globbing test files "%s"', config.testFiles)
+ debug('glob options %o', options)
+
+ // ensure we handle either a single string or a list of strings the same way
+ const testFilesPatterns = [].concat(config.testFiles)
+
+ /**
+ * Finds matching files for the given pattern, filters out specs to be ignored.
+ */
+ const findOnePattern = (pattern) => {
+ return glob(pattern, options)
+ .tap(debug)
+
+ // filter out anything that matches our
+ // ignored test files glob
+ .filter(doesNotMatchAllIgnoredPatterns)
+ .filter(matchesSpecPattern)
+ .map(setNameParts)
+ .tap((files) => {
+ return debug('found %d spec files: %o', files.length, files)
+ })
+ }
- //# filter out anything that matches our
- //# ignored test files glob
- .filter(doesNotMatchAllIgnoredPatterns)
- .filter(matchesSpecPattern)
- .map(setNameParts)
- .tap((files) => {
- return debug('found %d spec files: %o', files.length, files)
- })
+ return Promise.mapSeries(testFilesPatterns, findOnePattern).then(_.flatten)
}
module.exports = {
diff --git a/packages/server/lib/util/stream_buffer.js b/packages/server/lib/util/stream_buffer.js
index 632a92735a4..5133e26391b 100644
--- a/packages/server/lib/util/stream_buffer.js
+++ b/packages/server/lib/util/stream_buffer.js
@@ -1,129 +1,115 @@
-const _ = require('lodash')
-const debug = require('debug')('cypress:server:stream_buffer')
-const stream = require('stream')
-
-function streamBuffer (initialSize = 2048) {
- let buffer = Buffer.allocUnsafe(initialSize)
- let bytesWritten = 0
- let finished = false
-
- const readers = []
-
- const onWrite = (chunk, enc, cb) => {
- if (chunk.length + bytesWritten > buffer.length) {
- let newBufferLength = buffer.length
-
- while (newBufferLength < chunk.length + bytesWritten) {
- newBufferLength *= 2
- }
-
- debug('extending buffer to accomodate new chunk', {
- bufferLength: buffer.length,
- newBufferLength,
- })
-
- const newBuffer = Buffer.allocUnsafe(newBufferLength)
-
- buffer.copy(newBuffer)
- buffer = newBuffer
- }
-
- debug('appending chunk to buffer %o', { bytesWritten, chunkLength: chunk.length })
-
- bytesWritten += chunk.copy(buffer, bytesWritten)
-
- // emit in case there are readers waiting
- writeable.emit('chunk', chunk)
-
- cb()
- }
-
- const onFinal = (cb) => {
- debug('stream buffer writeable final called')
- finished = true
- cb()
- }
-
- const writeable = new stream.Writable({
- write: onWrite,
- final: onFinal,
- autoDestroy: true,
- })
-
- writeable.createReadStream = () => {
- let bytesRead = 0
- const readerId = _.uniqueId('reader')
-
- const onRead = function (size) {
- // if there are unread bytes in the buffer,
- // send up to bytesWritten back
- if (bytesRead < bytesWritten) {
- const chunkLength = bytesWritten - bytesRead
- const bytes = buffer.slice(bytesRead, bytesRead + chunkLength)
- const bytesLength = bytes.length
-
- debug('reading unread bytes from buffer %o', {
- readerId, bytesRead, bytesWritten, chunkLength, readChunkLength: bytesLength,
- })
-
- bytesRead += bytesLength
-
- // if we can still push more bytes into
- // the buffer then do it
- if (readable.push(bytes)) {
- return onRead(size)
+"use strict";
+var __importDefault = (this && this.__importDefault) || function (mod) {
+ return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var lodash_1 = __importDefault(require("lodash"));
+var debug_1 = __importDefault(require("debug"));
+var stream_1 = __importDefault(require("stream"));
+var debug = debug_1.default('cypress:server:stream_buffer');
+function streamBuffer(initialSize) {
+ if (initialSize === void 0) { initialSize = 2048; }
+ var buffer = Buffer.allocUnsafe(initialSize);
+ var bytesWritten = 0;
+ var finished = false;
+ var readers = [];
+ var onWrite = function (chunk, enc, cb) {
+ if (finished || !chunk || !buffer) {
+ debug('received write after deleting buffer, ignoring %o', { chunkLength: chunk && chunk.length, enc: enc });
+ return cb();
}
- }
-
- // if it's finished and there are no unread bytes, EOF
- if (finished) {
- // cleanup listeners that were added
- writeable.removeListener('chunk', onRead)
- writeable.removeListener('finish', onRead)
-
- debug('buffered stream EOF %o', { readerId })
-
- return readable.push(null)
- }
-
- // if we're not finished we may end up writing
- // more data - or we may end
- writeable.removeListener('chunk', onRead)
- writeable.once('chunk', onRead)
-
- // if the writeable stream buffer isn't finished
- // yet - then read() will not be called again,
- // so we restart reading when its finished
- writeable.removeListener('finish', onRead)
- writeable.once('finish', onRead)
- }
-
- const readable = new stream.Readable({
- read: onRead,
- autoDestroy: true,
- })
-
- readers.push(readable)
-
- return readable
- }
-
- writeable.unpipeAll = () => {
- buffer = null // aggressive GC
- _.invokeMap(readers, 'unpipe')
- }
-
- writeable._buffer = () => {
- return buffer
- }
-
- writeable._finished = () => {
- return finished
- }
-
- return writeable
+ if (chunk.length + bytesWritten > buffer.length) {
+ var newBufferLength = buffer.length;
+ while (newBufferLength < chunk.length + bytesWritten) {
+ newBufferLength *= 2;
+ }
+ debug('extending buffer to accomodate new chunk', {
+ bufferLength: buffer.length,
+ newBufferLength: newBufferLength,
+ });
+ var newBuffer = Buffer.allocUnsafe(newBufferLength);
+ buffer.copy(newBuffer);
+ buffer = newBuffer;
+ }
+ debug('appending chunk to buffer %o', { bytesWritten: bytesWritten, chunkLength: chunk.length });
+ bytesWritten += chunk.copy(buffer, bytesWritten);
+ // emit in case there are readers waiting
+ writeable.emit('chunk', chunk);
+ cb();
+ };
+ var onFinal = function (cb) {
+ debug('stream buffer writeable final called');
+ finished = true;
+ cb();
+ };
+ var writeable = new stream_1.default.Writable({
+ write: onWrite,
+ final: onFinal,
+ // @ts-ignore
+ autoDestroy: true,
+ });
+ writeable.createReadStream = function () {
+ var bytesRead = 0;
+ var readerId = lodash_1.default.uniqueId('reader');
+ var onRead = function (size) {
+ if (!buffer) {
+ debug('read requested after unpipeAll, ignoring %o', { size: size });
+ return;
+ }
+ // if there are unread bytes in the buffer,
+ // send up to bytesWritten back
+ if (bytesRead < bytesWritten) {
+ var chunkLength = bytesWritten - bytesRead;
+ var bytes = buffer.slice(bytesRead, bytesRead + chunkLength);
+ var bytesLength = bytes.length;
+ debug('reading unread bytes from buffer %o', {
+ readerId: readerId, bytesRead: bytesRead, bytesWritten: bytesWritten, chunkLength: chunkLength, readChunkLength: bytesLength,
+ });
+ bytesRead += bytesLength;
+ // if we can still push more bytes into
+ // the buffer then do it
+ if (readable.push(bytes)) {
+ return onRead(size);
+ }
+ }
+ // if it's finished and there are no unread bytes, EOF
+ if (finished) {
+ // cleanup listeners that were added
+ writeable.removeListener('chunk', onRead);
+ writeable.removeListener('finish', onRead);
+ debug('buffered stream EOF %o', { readerId: readerId });
+ return readable.push(null);
+ }
+ // if we're not finished we may end up writing
+ // more data - or we may end
+ writeable.removeListener('chunk', onRead);
+ writeable.once('chunk', onRead);
+ // if the writeable stream buffer isn't finished
+ // yet - then read() will not be called again,
+ // so we restart reading when its finished
+ writeable.removeListener('finish', onRead);
+ writeable.once('finish', onRead);
+ };
+ var readable = new stream_1.default.Readable({
+ read: onRead,
+ // @ts-ignore
+ autoDestroy: true,
+ });
+ readers.push(readable);
+ return readable;
+ };
+ writeable.unpipeAll = function () {
+ buffer = null; // aggressive GC
+ lodash_1.default.invokeMap(readers, 'unpipe');
+ };
+ writeable._buffer = function () {
+ return buffer;
+ };
+ writeable._finished = function () {
+ return finished;
+ };
+ return writeable;
}
-
module.exports = {
- streamBuffer,
-}
+ streamBuffer: streamBuffer,
+};
diff --git a/packages/server/lib/util/stream_buffer.ts b/packages/server/lib/util/stream_buffer.ts
new file mode 100644
index 00000000000..528bb54427a
--- /dev/null
+++ b/packages/server/lib/util/stream_buffer.ts
@@ -0,0 +1,145 @@
+import _ from 'lodash'
+import debugModule from 'debug'
+import stream from 'stream'
+
+const debug = debugModule('cypress:server:stream_buffer')
+
+function streamBuffer (initialSize = 2048) {
+ let buffer: Buffer | null = Buffer.allocUnsafe(initialSize)
+ let bytesWritten = 0
+ let finished = false
+
+ const readers: stream.Readable[] = []
+
+ const onWrite = (chunk, enc, cb) => {
+ if (finished || !chunk || !buffer) {
+ debug('received write after deleting buffer, ignoring %o', { chunkLength: chunk && chunk.length, enc })
+
+ return cb()
+ }
+
+ if (chunk.length + bytesWritten > buffer.length) {
+ let newBufferLength = buffer.length
+
+ while (newBufferLength < chunk.length + bytesWritten) {
+ newBufferLength *= 2
+ }
+
+ debug('extending buffer to accomodate new chunk', {
+ bufferLength: buffer.length,
+ newBufferLength,
+ })
+
+ const newBuffer = Buffer.allocUnsafe(newBufferLength)
+
+ buffer.copy(newBuffer)
+ buffer = newBuffer
+ }
+
+ debug('appending chunk to buffer %o', { bytesWritten, chunkLength: chunk.length })
+
+ bytesWritten += chunk.copy(buffer, bytesWritten)
+
+ // emit in case there are readers waiting
+ writeable.emit('chunk', chunk)
+
+ cb()
+ }
+
+ const onFinal = (cb) => {
+ debug('stream buffer writeable final called')
+ finished = true
+ cb()
+ }
+
+ const writeable = new stream.Writable({
+ write: onWrite,
+ final: onFinal,
+ // @ts-ignore
+ autoDestroy: true,
+ })
+
+ writeable.createReadStream = () => {
+ let bytesRead = 0
+ const readerId = _.uniqueId('reader')
+
+ const onRead = function (size) {
+ if (!buffer) {
+ debug('read requested after unpipeAll, ignoring %o', { size })
+
+ return
+ }
+
+ // if there are unread bytes in the buffer,
+ // send up to bytesWritten back
+ if (bytesRead < bytesWritten) {
+ const chunkLength = bytesWritten - bytesRead
+ const bytes = buffer.slice(bytesRead, bytesRead + chunkLength)
+ const bytesLength = bytes.length
+
+ debug('reading unread bytes from buffer %o', {
+ readerId, bytesRead, bytesWritten, chunkLength, readChunkLength: bytesLength,
+ })
+
+ bytesRead += bytesLength
+
+ // if we can still push more bytes into
+ // the buffer then do it
+ if (readable.push(bytes)) {
+ return onRead(size)
+ }
+ }
+
+ // if it's finished and there are no unread bytes, EOF
+ if (finished) {
+ // cleanup listeners that were added
+ writeable.removeListener('chunk', onRead)
+ writeable.removeListener('finish', onRead)
+
+ debug('buffered stream EOF %o', { readerId })
+
+ return readable.push(null)
+ }
+
+ // if we're not finished we may end up writing
+ // more data - or we may end
+ writeable.removeListener('chunk', onRead)
+ writeable.once('chunk', onRead)
+
+ // if the writeable stream buffer isn't finished
+ // yet - then read() will not be called again,
+ // so we restart reading when its finished
+ writeable.removeListener('finish', onRead)
+ writeable.once('finish', onRead)
+ }
+
+ const readable = new stream.Readable({
+ read: onRead,
+ // @ts-ignore
+ autoDestroy: true,
+ })
+
+ readers.push(readable)
+
+ return readable
+ }
+
+ writeable.unpipeAll = () => {
+ buffer = null // aggressive GC
+ _.invokeMap(readers, 'unpipe')
+ }
+
+ writeable._buffer = () => {
+ return buffer
+ }
+
+ writeable._finished = () => {
+ return finished
+ }
+
+ return writeable
+}
+
+module.exports = {
+ streamBuffer,
+}
diff --git a/packages/server/lib/util/suppress_unauthorized_warning.js b/packages/server/lib/util/suppress_unauthorized_warning.js
new file mode 100644
index 00000000000..1b6c3e78c03
--- /dev/null
+++ b/packages/server/lib/util/suppress_unauthorized_warning.js
@@ -0,0 +1,33 @@
+"use strict";
+var __importDefault = (this && this.__importDefault) || function (mod) {
+ return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var lodash_1 = __importDefault(require("lodash"));
+var originalEmitWarning = process.emitWarning;
+var suppressed = false;
+/**
+ * Don't emit the NODE_TLS_REJECT_UNAUTHORIZED warning while
+ * we work on proper SSL verification.
+ * https://github.com/cypress-io/cypress/issues/5248
+ */
+function suppress() {
+ if (suppressed) {
+ return;
+ }
+ suppressed = true;
+ process.emitWarning = function (warning) {
+ var args = [];
+ for (var _i = 1; _i < arguments.length; _i++) {
+ args[_i - 1] = arguments[_i];
+ }
+ if (lodash_1.default.isString(warning) && lodash_1.default.includes(warning, 'NODE_TLS_REJECT_UNAUTHORIZED')) {
+ // node will only emit the warning once
+ // https://github.com/nodejs/node/blob/82f89ec8c1554964f5029fab1cf0f4fad1fa55a8/lib/_tls_wrap.js#L1378-L1384
+ process.emitWarning = originalEmitWarning;
+ return;
+ }
+ return originalEmitWarning.call.apply(originalEmitWarning, [process, warning].concat(args));
+ };
+}
+exports.suppress = suppress;
diff --git a/packages/server/lib/util/suppress_unauthorized_warning.ts b/packages/server/lib/util/suppress_unauthorized_warning.ts
new file mode 100644
index 00000000000..29e2bfb39ee
--- /dev/null
+++ b/packages/server/lib/util/suppress_unauthorized_warning.ts
@@ -0,0 +1,30 @@
+import _ from 'lodash'
+
+const originalEmitWarning = process.emitWarning
+
+let suppressed = false
+
+/**
+ * Don't emit the NODE_TLS_REJECT_UNAUTHORIZED warning while
+ * we work on proper SSL verification.
+ * https://github.com/cypress-io/cypress/issues/5248
+ */
+export function suppress () {
+ if (suppressed) {
+ return
+ }
+
+ suppressed = true
+
+ process.emitWarning = (warning, ...args) => {
+ if (_.isString(warning) && _.includes(warning, 'NODE_TLS_REJECT_UNAUTHORIZED')) {
+ // node will only emit the warning once
+ // https://github.com/nodejs/node/blob/82f89ec8c1554964f5029fab1cf0f4fad1fa55a8/lib/_tls_wrap.js#L1378-L1384
+ process.emitWarning = originalEmitWarning
+
+ return
+ }
+
+ return originalEmitWarning.call(process, warning, ...args)
+ }
+}
diff --git a/packages/server/lib/util/system.js b/packages/server/lib/util/system.js
index 023a0195902..4e6f0bcce35 100644
--- a/packages/server/lib/util/system.js
+++ b/packages/server/lib/util/system.js
@@ -24,7 +24,6 @@ const getOsVersion = () => {
}
return os.release()
-
})
}
diff --git a/packages/server/lib/util/terminal.js b/packages/server/lib/util/terminal.js
index a105fbbf201..0d97c243230 100644
--- a/packages/server/lib/util/terminal.js
+++ b/packages/server/lib/util/terminal.js
@@ -1,31 +1,17 @@
-/* eslint-disable
- brace-style,
- default-case,
- no-cond-assign,
- no-console,
- no-unused-vars,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS102: Remove unnecessary code created because of implicit returns
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
+/* eslint-disable no-console */
const _ = require('lodash')
const chalk = require('chalk')
-const Table = require('cli-table2')
-const utils = require('cli-table2/src/utils')
+const Table = require('cli-table3')
+const utils = require('cli-table3/src/utils')
const widestLine = require('widest-line')
const terminalSize = require('./terminal-size')
const MAXIMUM_SIZE = 100
const EXPECTED_SUM = 100
-const getMaximumColumns = () =>
-//# get the maximum amount of columns
-//# that can fit in the terminal
-{
+const getMaximumColumns = () => {
+ // get the maximum amount of columns
+ // that can fit in the terminal
return Math.min(MAXIMUM_SIZE, terminalSize.get().columns)
}
@@ -38,37 +24,6 @@ const getBordersLength = (left, right) => {
.value()
}
-const convertDecimalsToNumber = function (colWidths, cols) {
- let diff
- const sum = _.sum(colWidths)
-
- if (sum !== EXPECTED_SUM) {
- throw new Error(`Expected colWidths array to sum to: ${EXPECTED_SUM}, instead got: ${sum}`)
- }
-
- [50, 10, 25]
-
- const widths = _.map(colWidths, (width) => {
- //# easier to deal with numbers than floats...
- const num = (cols * width) / EXPECTED_SUM
-
- return Math.floor(num)
- })
-
- const total = _.sum(widths)
-
- //# if we got a sum less than the total
- //# columns, then add the difference to
- //# the first element in the array of widths
- if ((diff = cols - total) > 0) {
- const first = widths[0]
-
- widths[0] += diff
- }
-
- return widths
-}
-
const renderTables = (...tables) => {
return _
.chain([])
@@ -88,6 +43,7 @@ const getChars = function (type) {
'left-mid': ' ├',
'middle': '',
'mid-mid': '',
+ 'right': '│',
'bottom-mid': '',
'bottom-left': ' └',
}
@@ -102,7 +58,7 @@ const getChars = function (type) {
'middle': '',
'mid': '',
'mid-mid': '',
- 'right': '',
+ 'right': ' ',
'right-mid': '',
'bottom': '',
'bottom-left': '',
@@ -141,6 +97,27 @@ const getChars = function (type) {
'right-mid': '',
'middle': '',
}
+ case 'allBorders':
+ return {
+ // this is default from cli-table mostly just for debugging,
+ // if you want to see where borders would be drawn
+ 'top': '─',
+ 'top-mid': '┬',
+ 'top-left': '┌',
+ 'top-right': '┐',
+ 'bottom': '─',
+ 'bottom-mid': '┴',
+ 'bottom-left': '└',
+ 'bottom-right': '┘',
+ 'left': '│',
+ 'left-mid': '├',
+ 'mid': '─',
+ 'mid-mid': '┼',
+ 'right': '│',
+ 'right-mid': '┤',
+ 'middle': '│',
+ }
+ default: throw new Error(`Table chars type: "${type}" is not supported`)
}
}
@@ -151,15 +128,14 @@ const wrapBordersInGray = (chars) => {
}
return char
-
})
}
const table = function (options = {}) {
- const { colWidths, type } = options
-
+ const { type } = options
const defaults = utils.mergeOptions({})
+ let { colWidths } = options
let chars = _.defaults(getChars(type), defaults.chars)
_.defaultsDeep(options, {
@@ -174,32 +150,43 @@ const table = function (options = {}) {
({ chars } = options)
- const borders = getBordersLength(chars.left, chars.right)
+ if (colWidths) {
+ const sum = _.sum(colWidths)
- options.chars = wrapBordersInGray(chars)
+ if (sum !== EXPECTED_SUM) {
+ throw new Error(`Expected colWidths array to sum to: ${EXPECTED_SUM}, instead got: ${sum}`)
+ }
- if (colWidths) {
- //# subtract borders to get the actual size
- //# so it displaces a maximum number of columns
- const cols = getMaximumColumns() - borders
+ const bordersLength = getBordersLength(chars.left, chars.right)
+
+ if (bordersLength > 0) {
+ // redistribute the columns to account for borders on each side...
+ // and subtract borders size from the largest width cell
+ const largestCellWidth = _.max(colWidths)
+
+ const index = _.indexOf(colWidths, largestCellWidth)
- options.colWidths = convertDecimalsToNumber(colWidths, cols)
+ colWidths = _.clone(colWidths)
+
+ colWidths[index] = largestCellWidth - bordersLength
+ options.colWidths = colWidths
+ }
}
+ options.chars = wrapBordersInGray(chars)
+
return new Table(options)
}
const header = function (message, options = {}) {
- let c
-
_.defaults(options, {
color: null,
})
message = ` (${chalk.underline.bold(message)})`
- if (c = options.color) {
- const colors = [].concat(c)
+ if (options.color) {
+ const colors = [].concat(options.color)
message = _.reduce(colors, (memo, color) => {
return chalk[color](memo)
@@ -207,15 +194,14 @@ const header = function (message, options = {}) {
, message)
}
- return console.log(message)
+ console.log(message)
}
const divider = function (symbol, color = 'gray') {
const cols = getMaximumColumns()
-
const str = symbol.repeat(cols)
- return console.log(chalk[color](str))
+ console.log(chalk[color](str))
}
module.exports = {
@@ -228,7 +214,4 @@ module.exports = {
renderTables,
getMaximumColumns,
-
- convertDecimalsToNumber,
-
}
diff --git a/packages/server/lib/util/validation.js b/packages/server/lib/util/validation.js
index f4f0aa45f00..2651858ef34 100644
--- a/packages/server/lib/util/validation.js
+++ b/packages/server/lib/util/validation.js
@@ -12,12 +12,16 @@
const _ = require('lodash')
const errors = require('../errors')
-//# validation functions take a key and a value and should:
-//# - return true if it passes validation
-//# - return a error message if it fails validation
+// # validation functions take a key and a value and should:
+// # - return true if it passes validation
+// # - return a error message if it fails validation
+
+const str = JSON.stringify
const errMsg = (key, value, type) => {
- return `Expected \`${key}\` to be ${type}. Instead the value was: \`${JSON.stringify(value)}\``
+ return `Expected \`${key}\` to be ${type}. Instead the value was: \`${str(
+ value
+ )}\``
}
const isFullyQualifiedUrl = (value) => {
@@ -38,12 +42,11 @@ const { isString } = _
module.exports = {
isNumber (key, value) {
- if ((value == null) || isNumber(value)) {
+ if (value == null || isNumber(value)) {
return true
}
return errMsg(key, value, 'a number')
-
},
isNumberOrFalse (key, value) {
@@ -52,52 +55,50 @@ module.exports = {
}
return errMsg(key, value, 'a number or false')
-
},
isFullyQualifiedUrl (key, value) {
- if ((value == null) || isFullyQualifiedUrl(value)) {
+ if (value == null || isFullyQualifiedUrl(value)) {
return true
}
- return errMsg(key, value, 'a fully qualified URL (starting with `http://` or `https://`)')
-
+ return errMsg(
+ key,
+ value,
+ 'a fully qualified URL (starting with `http://` or `https://`)'
+ )
},
isBoolean (key, value) {
- if ((value == null) || _.isBoolean(value)) {
+ if (value == null || _.isBoolean(value)) {
return true
}
return errMsg(key, value, 'a boolean')
-
},
isPlainObject (key, value) {
- if ((value == null) || _.isPlainObject(value)) {
+ if (value == null || _.isPlainObject(value)) {
return true
}
return errMsg(key, value, 'a plain object')
-
},
isString (key, value) {
- if ((value == null) || isString(value)) {
+ if (value == null || isString(value)) {
return true
}
return errMsg(key, value, 'a string')
-
},
isArray (key, value) {
- if ((value == null) || isArray(value)) {
+ if (value == null || isArray(value)) {
return true
}
return errMsg(key, value, 'an array')
-
},
isStringOrFalse (key, value) {
@@ -106,7 +107,6 @@ module.exports = {
}
return errMsg(key, value, 'a string or false')
-
},
isStringOrArrayOfStrings (key, value) {
@@ -115,6 +115,28 @@ module.exports = {
}
return errMsg(key, value, 'a string or an array of strings')
+ },
+ /**
+ * Checks if given value for a key is equal to one of the provided values.
+ * @example
+ ```
+ validate = v.isOneOf("foo", "bar")
+ validate("example", "foo") // true
+ validate("example", "else") // error message string
+ ```
+ */
+ isOneOf (...values) {
+ return (key, value) => {
+ if (values.some((v) => {
+ return v === value
+ })) {
+ return true
+ }
+
+ const strings = values.map(str).join(', ')
+
+ return errMsg(key, value, `one of these values: ${strings}`)
+ }
},
}
diff --git a/packages/server/lib/video_capture.coffee b/packages/server/lib/video_capture.coffee
index 3d3a9f7dfbe..0a1953b10ed 100644
--- a/packages/server/lib/video_capture.coffee
+++ b/packages/server/lib/video_capture.coffee
@@ -1,18 +1,52 @@
_ = require("lodash")
+la = require("lazy-ass")
+os = require("os")
+path = require("path")
utils = require("fluent-ffmpeg/lib/utils")
debug = require("debug")("cypress:server:video")
-# extra verbose logs for logging individual frames
-debugFrames = require("debug")("cypress:server:video:frames")
ffmpeg = require("fluent-ffmpeg")
stream = require("stream")
Promise = require("bluebird")
ffmpegPath = require("@ffmpeg-installer/ffmpeg").path
+BlackHoleStream = require("black-hole-stream")
fs = require("./util/fs")
+## extra verbose logs for logging individual frames
+debugFrames = require("debug")("cypress-verbose:server:video:frames")
+
debug("using ffmpeg from %s", ffmpegPath)
+
ffmpeg.setFfmpegPath(ffmpegPath)
+deferredPromise = ->
+ resolve = reject = null
+ promise = new Promise (_resolve, _reject) ->
+ resolve = _resolve
+ reject = _reject
+ { promise, resolve, reject }
+
module.exports = {
+ getMsFromDuration: (duration) ->
+ utils.timemarkToSeconds(duration) * 1000
+
+ getCodecData: (src) ->
+ new Promise (resolve, reject) ->
+ ffmpeg()
+ .on "stderr", (stderr) ->
+ debug("get codecData stderr log %o", { message: stderr })
+ .on("codecData", resolve)
+ .input(src)
+ .format("null")
+ .output(new BlackHoleStream())
+ .run()
+ .tap (data) ->
+ debug('codecData %o', {
+ src,
+ data,
+ })
+ .tapCatch (err) ->
+ debug("getting codecData failed", { err })
+
copy: (src, dest) ->
debug("copying from %s to %s", src, dest)
fs
@@ -22,7 +56,7 @@ module.exports = {
start: (name, options = {}) ->
pt = stream.PassThrough()
- ended = Promise.pending()
+ ended = deferredPromise()
done = false
errored = false
written = false
@@ -66,7 +100,7 @@ module.exports = {
if not wantsWrite = pt.write(data)
pt.once "drain", ->
debugFrames("video stream drained")
-
+
wantsWrite = true
else
skipped += 1
@@ -105,8 +139,6 @@ module.exports = {
if logErrors
options.onError(err, stdout, stderr)
- err.recordingVideoFailed = true
-
## reject the ended promise
ended.reject(err)
@@ -115,14 +147,14 @@ module.exports = {
ended.resolve()
.save(name)
-
+
startCapturing()
.then ({ cmd, startedVideoCapture }) ->
return {
- cmd,
+ cmd,
endVideoCapture,
writeVideoFrame,
- startedVideoCapture,
+ startedVideoCapture,
}
process: (name, cname, videoCompression, onProgress = ->) ->
diff --git a/packages/server/lib/xhr_ws_server.js b/packages/server/lib/xhr_ws_server.js
new file mode 100644
index 00000000000..a041d54d326
--- /dev/null
+++ b/packages/server/lib/xhr_ws_server.js
@@ -0,0 +1,70 @@
+"use strict";
+var __importDefault = (this && this.__importDefault) || function (mod) {
+ return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+var lodash_1 = __importDefault(require("lodash"));
+var bluebird_1 = __importDefault(require("bluebird"));
+var debug_1 = __importDefault(require("debug"));
+var debug = debug_1.default('cypress:server:xhr_ws_server');
+function trunc(str) {
+ return lodash_1.default.truncate(str, {
+ length: 100,
+ omission: '... [truncated to 100 chars]',
+ });
+}
+function create() {
+ var incomingXhrResponses = {};
+ function onIncomingXhr(id, data) {
+ debug('onIncomingXhr %o', { id: id, res: trunc(data) });
+ var deferred = incomingXhrResponses[id];
+ if (deferred && typeof deferred !== 'string') {
+ // request came before response, resolve with it
+ return deferred.resolve(data);
+ }
+ // response came before request, cache the data
+ incomingXhrResponses[id] = data;
+ }
+ function getDeferredResponse(id) {
+ debug('getDeferredResponse %o', { id: id });
+ // if we already have it, send it
+ var res = incomingXhrResponses[id];
+ if (res) {
+ if (typeof res === 'object') {
+ debug('returning existing deferred promise for %o', { id: id, res: res });
+ return res.promise;
+ }
+ debug('already have deferred response %o', { id: id, res: trunc(res) });
+ delete incomingXhrResponses[id];
+ return res;
+ }
+ var deferred = {};
+ deferred.promise = new bluebird_1.default(function (resolve, reject) {
+ debug('do not have response, waiting %o', { id: id });
+ deferred.resolve = resolve;
+ deferred.reject = reject;
+ })
+ .tap(function (res) {
+ debug('deferred response found %o', { id: id, res: trunc(res) });
+ });
+ incomingXhrResponses[id] = deferred;
+ return deferred.promise;
+ }
+ function reset() {
+ debug('resetting incomingXhrs %o', { incomingXhrResponses: incomingXhrResponses });
+ lodash_1.default.forEach(incomingXhrResponses, function (res) {
+ if (typeof res !== 'string') {
+ var err = new Error('This stubbed XHR was pending on a stub response object from the driver, but the test ended before that happened.');
+ err.testEndedBeforeResponseReceived = true;
+ res.reject(err);
+ }
+ });
+ incomingXhrResponses = {};
+ }
+ return {
+ onIncomingXhr: onIncomingXhr,
+ getDeferredResponse: getDeferredResponse,
+ reset: reset,
+ };
+}
+exports.create = create;
diff --git a/packages/server/lib/xhr_ws_server.ts b/packages/server/lib/xhr_ws_server.ts
new file mode 100644
index 00000000000..1001eef0c33
--- /dev/null
+++ b/packages/server/lib/xhr_ws_server.ts
@@ -0,0 +1,93 @@
+import _ from 'lodash'
+import Bluebird from 'bluebird'
+import debugModule from 'debug'
+
+const debug = debugModule('cypress:server:xhr_ws_server')
+
+function trunc (str) {
+ return _.truncate(str, {
+ length: 100,
+ omission: '... [truncated to 100 chars]',
+ })
+}
+
+type DeferredPromise
= {
+ resolve: Function
+ reject: Function
+ promise: Bluebird
+}
+
+export function create () {
+ let incomingXhrResponses: {
+ [key: string]: string | DeferredPromise
+ } = {}
+
+ function onIncomingXhr (id: string, data: string) {
+ debug('onIncomingXhr %o', { id, res: trunc(data) })
+ const deferred = incomingXhrResponses[id]
+
+ if (deferred && typeof deferred !== 'string') {
+ // request came before response, resolve with it
+ return deferred.resolve(data)
+ }
+
+ // response came before request, cache the data
+ incomingXhrResponses[id] = data
+ }
+
+ function getDeferredResponse (id) {
+ debug('getDeferredResponse %o', { id })
+ // if we already have it, send it
+ const res = incomingXhrResponses[id]
+
+ if (res) {
+ if (typeof res === 'object') {
+ debug('returning existing deferred promise for %o', { id, res })
+
+ return res.promise
+ }
+
+ debug('already have deferred response %o', { id, res: trunc(res) })
+ delete incomingXhrResponses[id]
+
+ return res
+ }
+
+ let deferred: Partial> = {}
+
+ deferred.promise = new Bluebird((resolve, reject) => {
+ debug('do not have response, waiting %o', { id })
+ deferred.resolve = resolve
+ deferred.reject = reject
+ })
+ .tap((res) => {
+ debug('deferred response found %o', { id, res: trunc(res) })
+ }) as Bluebird
+
+ incomingXhrResponses[id] = deferred as DeferredPromise
+
+ return deferred.promise
+ }
+
+ function reset () {
+ debug('resetting incomingXhrs %o', { incomingXhrResponses })
+
+ _.forEach(incomingXhrResponses, (res) => {
+ if (typeof res !== 'string') {
+ const err: any = new Error('This stubbed XHR was pending on a stub response object from the driver, but the test ended before that happened.')
+
+ err.testEndedBeforeResponseReceived = true
+
+ res.reject(err)
+ }
+ })
+
+ incomingXhrResponses = {}
+ }
+
+ return {
+ onIncomingXhr,
+ getDeferredResponse,
+ reset,
+ }
+}
diff --git a/packages/server/package.json b/packages/server/package.json
index bcf7a7045b5..b6db52e0e5d 100644
--- a/packages/server/package.json
+++ b/packages/server/package.json
@@ -42,19 +42,21 @@
"dependencies": {
"@cypress/browserify-preprocessor": "1.1.2",
"@cypress/commit-info": "2.2.0",
- "@cypress/get-windows-proxy": "1.5.4",
+ "@cypress/get-windows-proxy": "1.6.0",
"@cypress/icons": "0.7.0",
"@ffmpeg-installer/ffmpeg": "1.0.19",
"ansi_up": "1.3.0",
- "bluebird": "3.4.7",
+ "black-hole-stream": "0.0.1",
+ "bluebird": "3.7.0",
"browserify": "16.3.0",
"chai": "1.10.0",
"chalk": "2.4.2",
"charset": "1.0.1",
"check-more-types": "2.24.0",
- "chokidar": "3.0.1",
+ "chokidar": "3.2.2",
+ "chrome-remote-interface": "0.28.0",
"cjsxify": "0.3.0",
- "cli-table2": "0.2.0",
+ "cli-table3": "0.5.1",
"color-string": "1.5.3",
"common-tags": "1.8.0",
"compression": "1.7.4",
@@ -70,20 +72,22 @@
"evil-dns": "0.2.0",
"execa": "1.0.0",
"express": "4.16.4",
- "express-handlebars": "3.0.2",
"find-process": "1.4.1",
+ "fix-path": "2.1.0",
"fluent-ffmpeg": "2.1.2",
"fs-extra": "8.1.0",
+ "get-port": "5.0.0",
"getos": "3.1.1",
"glob": "7.1.3",
- "graceful-fs": "4.2.0",
+ "graceful-fs": "4.2.3",
+ "grapheme-splitter": "1.0.4",
"http-accept": "0.1.6",
"http-proxy": "1.17.0",
"http-status-codes": "1.3.2",
"human-interval": "0.1.6",
"iconv-lite": "0.5.0",
"image-size": "0.7.4",
- "is-fork-pr": "2.3.0",
+ "is-fork-pr": "2.5.0",
"is-html": "2.0.0",
"jimp": "0.6.4",
"jsonlint": "1.6.3",
@@ -102,36 +106,37 @@
"moment": "2.24.0",
"morgan": "1.9.1",
"node-machine-id": "1.1.10",
- "node-offline-localhost": "0.9.0",
"node-webkit-updater": "cypress-io/node-webkit-updater#e74623726f381487f543e373e71515177a32daeb",
"opn": "cypress-io/opn#2f4e9a216ca7bdb95dfae9d46d99ddf004b3cbb5",
"ospath": "1.2.2",
"p-queue": "6.1.0",
"parse-domain": "2.0.0",
+ "pluralize": "8.0.0",
"pumpify": "1.5.1",
"ramda": "0.24.1",
"randomstring": "1.1.5",
- "replacestream": "4.0.3",
- "request": "2.88.0",
+ "request": "cypress-io/request#47cdc67085c9fddc8d39d3172538f3f86c96bb8b",
"request-promise": "4.2.4",
"return-deep-diff": "0.3.0",
- "sanitize-filename": "1.6.1",
+ "sanitize-filename": "1.6.3",
"semver": "6.3.0",
"send": "0.17.0",
- "server-destroy": "1.0.1",
"shell-env": "3.0.0",
"signal-exit": "3.0.2",
"sinon": "5.1.1",
+ "squirrelly": "7.7.0",
"strip-ansi": "3.0.1",
"syntax-error": "1.4.0",
- "term-size": "2.0.0",
+ "term-size": "2.1.0",
"through": "2.3.8",
"tough-cookie": "3.0.1",
"trash": "5.2.0",
"underscore": "1.9.1",
"underscore.string": "3.3.5",
"url-parse": "1.4.7",
+ "utf8-stream": "0.0.0",
"uuid": "3.3.2",
+ "which": "1.3.1",
"widest-line": "3.1.0",
"winston": "2.4.4"
},
@@ -146,22 +151,24 @@
"@cypress/debugging-proxy": "2.0.1",
"@cypress/json-schemas": "5.32.2",
"@cypress/sinon-chai": "1.1.0",
+ "@types/chai-as-promised": "7.1.2",
"babel-plugin-add-module-exports": "1.0.2",
"babelify": "10.0.0",
- "bin-up": "1.2.0",
+ "bin-up": "1.2.2",
"body-parser": "1.19.0",
+ "chai-as-promised": "7.1.1",
"chai-uuid": "1.0.6",
"chokidar-cli": "1.2.2",
"chrome-har-capturer": "0.13.4",
"coffee-coverage": "3.0.1",
- "console-table-printer": "1.0.0-beta12",
"cors": "2.8.5",
+ "devtools-protocol": "0.0.702485",
"eol": "0.9.1",
"eventsource": "1.0.7",
"express-session": "1.16.1",
"express-useragent": "1.0.12",
"http-mitm-proxy": "0.7.0",
- "https-proxy-agent": "2.2.0",
+ "https-proxy-agent": "3.0.0",
"istanbul": "0.4.5",
"mocked-env": "1.2.4",
"mockery": "2.1.0",
@@ -171,7 +178,7 @@
"proxyquire": "2.1.0",
"react": "15.6.2",
"repl.history": "0.1.4",
- "snap-shot-it": "7.8.0",
+ "snap-shot-it": "7.9.0",
"ssestream": "1.0.1",
"stream-to-promise": "1.1.1",
"supertest": "4.0.2",
@@ -184,5 +191,8 @@
"config",
"lib"
],
- "productName": "Cypress"
+ "productName": "Cypress",
+ "optionalDependencies": {
+ "registry-js": "1.8.0"
+ }
}
diff --git a/packages/server/test/.eslintrc b/packages/server/test/.eslintrc.json
similarity index 100%
rename from packages/server/test/.eslintrc
rename to packages/server/test/.eslintrc.json
diff --git a/packages/server/test/e2e/1_async_timeouts_spec.coffee b/packages/server/test/e2e/1_async_timeouts_spec.coffee
index c34e0e4f882..ba3c2875965 100644
--- a/packages/server/test/e2e/1_async_timeouts_spec.coffee
+++ b/packages/server/test/e2e/1_async_timeouts_spec.coffee
@@ -7,5 +7,5 @@ describe "e2e async timeouts", ->
e2e.exec(@, {
spec: "async_timeouts_spec.coffee"
snapshot: true
- expectedExitCode: 1
+ expectedExitCode: 2
})
diff --git a/packages/server/test/e2e/1_base_url_spec.coffee b/packages/server/test/e2e/1_base_url_spec.coffee
index d99c3e17244..9fb0fb68a1f 100644
--- a/packages/server/test/e2e/1_base_url_spec.coffee
+++ b/packages/server/test/e2e/1_base_url_spec.coffee
@@ -12,12 +12,11 @@ describe "e2e baseUrl", ->
}
})
- it "passes", ->
- e2e.exec(@, {
- spec: "base_url_spec.coffee"
- snapshot: true
- expectedExitCode: 0
- })
+ e2e.it "passes", {
+ spec: "base_url_spec.coffee"
+ snapshot: true
+ expectedExitCode: 0
+ }
context "http", ->
e2e.setup({
@@ -30,9 +29,8 @@ describe "e2e baseUrl", ->
}
})
- it "passes", ->
- e2e.exec(@, {
- spec: "base_url_spec.coffee"
- snapshot: true
- expectedExitCode: 0
- })
+ e2e.it "passes", {
+ spec: "base_url_spec.coffee"
+ snapshot: true
+ expectedExitCode: 0
+ }
diff --git a/packages/server/test/e2e/1_busted_support_file_spec.coffee b/packages/server/test/e2e/1_busted_support_file_spec.coffee
index c86006bbf3c..68b76dc690c 100644
--- a/packages/server/test/e2e/1_busted_support_file_spec.coffee
+++ b/packages/server/test/e2e/1_busted_support_file_spec.coffee
@@ -9,6 +9,7 @@ describe "e2e busted support file", ->
it "passes", ->
e2e.exec(@, {
project: bustedSupportFile
+ sanitizeScreenshotDimensions: true
snapshot: true
expectedExitCode: 1
})
diff --git a/packages/server/test/e2e/1_commands_outside_of_test_spec.coffee b/packages/server/test/e2e/1_commands_outside_of_test_spec.coffee
index d5ad5c514d2..7c9ef2b054c 100644
--- a/packages/server/test/e2e/1_commands_outside_of_test_spec.coffee
+++ b/packages/server/test/e2e/1_commands_outside_of_test_spec.coffee
@@ -1,45 +1,22 @@
e2e = require("../support/helpers/e2e")
-_ = require('lodash')
describe "e2e commands outside of test", ->
e2e.setup()
- _.each [
- 'chrome',
- 'electron'
- ], (browser) ->
+ e2e.it "fails on cy commands", {
+ spec: "commands_outside_of_test_spec.coffee"
+ snapshot: true
+ expectedExitCode: 1
+ }
- it "[#{browser}] fails on cy commands", ->
- ## TODO: remove this after electron upgrade
- if browser is 'electron'
- console.log('⚠️ skipping test in electron due to chromium 63 bug with sourceMaps')
- ## this.skip() will not work here since it skips the afterEach
- return
+ e2e.it "fails on failing assertions", {
+ spec: "assertions_failing_outside_of_test_spec.coffee"
+ snapshot: true
+ expectedExitCode: 1
+ }
- e2e.exec(@, {
- spec: "commands_outside_of_test_spec.coffee"
- snapshot: true
- expectedExitCode: 1
- browser
- })
-
- it "[#{browser}] fails on failing assertions", ->
- ## TODO: remove this after electron upgrade
- if browser is 'electron'
- console.log('⚠️ skipping test in electron due to chromium 63 bug with sourceMaps')
- ## this.skip() will not work here since it skips the afterEach
- return
-
- e2e.exec(@, {
- spec: "assertions_failing_outside_of_test_spec.coffee"
- snapshot: true
- expectedExitCode: 1
- browser
- })
-
- it "passes on passing assertions", ->
- e2e.exec(@, {
- spec: "assertions_passing_outside_of_test_spec.coffee"
- snapshot: true
- expectedExitCode: 0
- })
+ e2e.it "passes on passing assertions", {
+ spec: "assertions_passing_outside_of_test_spec.coffee"
+ snapshot: true
+ expectedExitCode: 0
+ }
diff --git a/packages/server/test/e2e/1_system_node_spec.coffee b/packages/server/test/e2e/1_system_node_spec.coffee
new file mode 100644
index 00000000000..7c8f42f6444
--- /dev/null
+++ b/packages/server/test/e2e/1_system_node_spec.coffee
@@ -0,0 +1,33 @@
+e2e = require("../support/helpers/e2e")
+execa = require("execa")
+Fixtures = require("../support/helpers/fixtures")
+Promise = require("bluebird")
+
+systemNode = Fixtures.projectPath("system-node")
+
+describe "e2e system node", ->
+ e2e.setup()
+
+ it "uses system node when launching plugins file", ->
+ Promise.join(
+ execa.stdout("node", ["-v"])
+ execa.stdout("node", ["-e", "console.log(process.execPath)"])
+ (expectedNodeVersion, expectedNodePath) =>
+ expectedNodeVersion = expectedNodeVersion.slice(1) ## v1.2.3 -> 1.2.3
+ e2e.exec(@, {
+ project: systemNode
+ config: {
+ env: {
+ expectedNodeVersion
+ expectedNodePath
+ }
+ }
+ spec: "spec.js"
+ sanitizeScreenshotDimensions: true
+ snapshot: true
+ expectedExitCode: 0
+ })
+ .then ({ stderr }) ->
+ expect(stderr).to.contain("Plugin Node version: #{expectedNodeVersion}")
+ expect(stderr).to.contain("Plugin Electron version: undefined")
+ )
diff --git a/packages/server/test/e2e/2_cdp_spec.ts b/packages/server/test/e2e/2_cdp_spec.ts
new file mode 100644
index 00000000000..f32ad1c180d
--- /dev/null
+++ b/packages/server/test/e2e/2_cdp_spec.ts
@@ -0,0 +1,27 @@
+import mockedEnv from 'mocked-env'
+
+const e2e = require('../support/helpers/e2e')
+const Fixtures = require('../support/helpers/fixtures')
+
+describe('e2e cdp', function () {
+ e2e.setup()
+ let restoreEnv: Function
+
+ beforeEach(() => {
+ restoreEnv = mockedEnv({
+ CYPRESS_REMOTE_DEBUGGING_PORT: '7777',
+ })
+ })
+
+ afterEach(() => {
+ restoreEnv()
+ })
+
+ e2e.it('fails when remote debugging port cannot be connected to', {
+ project: Fixtures.projectPath('remote-debugging-port-removed'),
+ spec: 'spec.ts',
+ browser: 'chrome',
+ expectedExitCode: 1,
+ snapshot: true,
+ })
+})
diff --git a/packages/server/test/e2e/2_controllers_spec.coffee b/packages/server/test/e2e/2_controllers_spec.coffee
index a1c89b7e5c5..01769a639f8 100644
--- a/packages/server/test/e2e/2_controllers_spec.coffee
+++ b/packages/server/test/e2e/2_controllers_spec.coffee
@@ -10,6 +10,7 @@ describe "e2e plugins", ->
e2e.exec(@, {
spec: "spec.js"
project: nonExistentSpec
+ sanitizeScreenshotDimensions: true
snapshot: true
expectedExitCode: 1
})
diff --git a/packages/server/test/e2e/2_cookies_spec.coffee b/packages/server/test/e2e/2_cookies_spec.coffee
index 6a5f8ff7b80..bb547736421 100644
--- a/packages/server/test/e2e/2_cookies_spec.coffee
+++ b/packages/server/test/e2e/2_cookies_spec.coffee
@@ -65,24 +65,115 @@ onServer = (app) ->
res.send("")
+ app.get "/setCascadingCookies", (req, res) ->
+ n = Number(req.query.n)
+
+ ## alternates between base URLs
+ a = req.query.a
+ b = req.query.b
+
+ res.header("Set-Cookie", [
+ "namefoo#{n}=valfoo#{n}"
+ ])
+
+ console.log('to', a, 'from', b)
+
+ if n > 0
+ res.redirect("#{a}/setCascadingCookies?n=#{n-1}&a=#{b}&b=#{a}")
+
+ res.send("finished setting cookies")
+
+haveRoot = !process.env.USE_HIGH_PORTS && process.geteuid() == 0
+
+if not haveRoot
+ console.warn('(e2e tests warning) You are not running as root; therefore, 2_cookies_spec cannot cover the case where the default (80/443) HTTP(s) port is used. Alternate ports (2121/2323) will be used instead.')
+
+httpPort = 2121
+httpsPort = 2323
+
+if haveRoot
+ httpPort = 80
+ httpsPort = 443
+
+otherDomain = "quux.bar.net"
+otherUrl = "http://#{otherDomain}#{if haveRoot then '' else ":#{httpPort}"}"
+otherHttpsUrl = "https://#{otherDomain}#{if haveRoot then '' else ":#{httpsPort}"}"
+
describe "e2e cookies", ->
e2e.setup({
servers: [{
onServer
- port: 2121
+ port: httpPort
}, {
onServer
- port: 2323
+ port: httpsPort
https: true
}]
settings: {
- baseUrl: "https://localhost:2323/",
+ hosts: {
+ "*.foo.com": "127.0.0.1"
+ "*.bar.net": "127.0.0.1"
+ }
}
})
- it "passes", ->
- e2e.exec(@, {
- spec: "cookies_spec.coffee"
- snapshot: true
- expectedExitCode: 0
- })
+ ## this is a big chunky test that runs cookies_spec with all combinations of these:
+ ## - cookies on `localhost`, fully-qualified-domain-name, and IP address domains
+ ## - `https` baseurls, `http` baseurls, and no baseurls set
+ ## - both default port 80/443 and custom ports (if you are running as root)
+ ## - all browsers
+ ## snapshots are combined to ensure that there is no difference in any of these situations
+ [
+ ["localhost", "localhost"],
+ ["FQDN", "www.bar.foo.com"],
+ ["IP", "127.0.0.1"],
+ ]
+ .forEach ([
+ format,
+ baseDomain,
+ ]) =>
+ context "with #{format} urls", ->
+ httpUrl = "http://#{baseDomain}#{if haveRoot then '' else ":#{httpPort}"}"
+ httpsUrl = "https://#{baseDomain}#{if haveRoot then '' else ":#{httpsPort}"}"
+
+ [
+ [httpUrl, false],
+ [httpsUrl, true]
+ ].forEach ([
+ baseUrl,
+ https
+ ]) ->
+ e2e.it "passes with baseurl: #{baseUrl}", {
+ config: {
+ baseUrl
+ env: {
+ baseUrl
+ expectedDomain: baseDomain
+ https
+ httpUrl
+ httpsUrl
+ otherUrl
+ otherHttpsUrl
+ }
+ }
+ spec: "cookies_spec_baseurl.coffee"
+ snapshot: true
+ expectedExitCode: 0
+ onRun: (exec) =>
+ exec({
+ originalTitle: "e2e cookies with baseurl"
+ })
+ }
+
+ e2e.it "passes with no baseurl", {
+ config: {
+ env: {
+ httpUrl
+ httpsUrl
+ }
+ }
+ originalTitle: "e2e cookies with no baseurl"
+ spec: "cookies_spec_no_baseurl.coffee"
+ snapshot: true
+ expectedExitCode: 0
+ }
diff --git a/packages/server/test/e2e/2_domain_spec.coffee b/packages/server/test/e2e/2_domain_spec.coffee
index 677e91f5801..d40c565764e 100644
--- a/packages/server/test/e2e/2_domain_spec.coffee
+++ b/packages/server/test/e2e/2_domain_spec.coffee
@@ -13,15 +13,11 @@ describe "e2e domain", ->
}
})
- it "passing", ->
- ## run both domain specs back to back to ensure
- ## that the internal server + project state is
- ## reset each time we spawn the browser
- e2e.exec(@, {
- spec: "domain*"
- snapshot: true
- expectedExitCode: 0
- config: {
- hosts
- }
- })
+ e2e.it "passes", {
+ spec: "domain*"
+ snapshot: true
+ expectedExitCode: 0
+ config: {
+ hosts
+ }
+ }
diff --git a/packages/server/test/e2e/2_go_spec.coffee b/packages/server/test/e2e/2_go_spec.coffee
index bb1fb1ac096..ce41d44d83e 100644
--- a/packages/server/test/e2e/2_go_spec.coffee
+++ b/packages/server/test/e2e/2_go_spec.coffee
@@ -15,13 +15,11 @@ describe "e2e go", ->
}
})
- it "passes", ->
- ## this tests that history changes work as intended
- ## there have been regressions in electron which would
- ## otherwise cause these tests to fail
-
- e2e.exec(@, {
- spec: "go_spec.coffee"
- snapshot: true
- expectedExitCode: 0
- })
+ ## this tests that history changes work as intended
+ ## there have been regressions in electron which would
+ ## otherwise cause these tests to fail
+ e2e.it "passes", {
+ spec: "go_spec.coffee"
+ snapshot: true
+ expectedExitCode: 0
+ }
diff --git a/packages/server/test/e2e/2_iframe_spec.coffee b/packages/server/test/e2e/2_iframe_spec.coffee
index c45009d7873..d697b5c35b9 100644
--- a/packages/server/test/e2e/2_iframe_spec.coffee
+++ b/packages/server/test/e2e/2_iframe_spec.coffee
@@ -66,15 +66,14 @@ describe "e2e iframes", ->
}
})
- it "passes", ->
- e2e.exec(@, {
- spec: "iframe_spec.coffee"
- snapshot: true
- expectedExitCode: 0
- config: {
- hosts: {
- "*.foo.com": "127.0.0.1"
- "*.bar.com": "127.0.0.1"
- }
+ e2e.it "passes", {
+ spec: "iframe_spec.coffee"
+ snapshot: true
+ expectedExitCode: 0
+ config: {
+ hosts: {
+ "*.foo.com": "127.0.0.1"
+ "*.bar.com": "127.0.0.1"
}
- })
+ }
+ }
diff --git a/packages/server/test/e2e/2_images_spec.coffee b/packages/server/test/e2e/2_images_spec.coffee
index e27dbc712ff..a2e0c30d4b6 100644
--- a/packages/server/test/e2e/2_images_spec.coffee
+++ b/packages/server/test/e2e/2_images_spec.coffee
@@ -8,12 +8,11 @@ describe "e2e images", ->
}
})
- it "passes", ->
- ## this tests that images are correctly proxied and that we are not
- ## accidentally modifying their bytes in the stream
+ ## this tests that images are correctly proxied and that we are not
+ ## accidentally modifying their bytes in the stream
- e2e.exec(@, {
- spec: "images_spec.coffee"
- snapshot: true
- expectedExitCode: 0
- })
+ e2e.it "passes", {
+ spec: "images_spec.coffee"
+ snapshot: true
+ expectedExitCode: 0
+ }
diff --git a/packages/server/test/e2e/2_config_spec.coffee b/packages/server/test/e2e/3_config_spec.coffee
similarity index 84%
rename from packages/server/test/e2e/2_config_spec.coffee
rename to packages/server/test/e2e/3_config_spec.coffee
index e67f7363bc6..5b6ad6d6d07 100644
--- a/packages/server/test/e2e/2_config_spec.coffee
+++ b/packages/server/test/e2e/3_config_spec.coffee
@@ -14,6 +14,11 @@ describe "e2e config", ->
spec: "config_passing_spec.coffee"
snapshot: true
expectedExitCode: 0
+ config: {
+ env: {
+ scriptlet: ""
+ }
+ }
})
it "fails", ->
diff --git a/packages/server/test/e2e/3_issue_1669_spec.coffee b/packages/server/test/e2e/3_issue_1669_spec.coffee
new file mode 100644
index 00000000000..0666dc0ef71
--- /dev/null
+++ b/packages/server/test/e2e/3_issue_1669_spec.coffee
@@ -0,0 +1,15 @@
+e2e = require("../support/helpers/e2e")
+Fixtures = require("../support/helpers/fixtures")
+
+describe "e2e issue 2891", ->
+ e2e.setup()
+
+ ## https://github.com/cypress-io/cypress/issues/2891
+
+ it "passes", ->
+ e2e.exec(@, {
+ spec: "issue_1669_spec.js"
+ snapshot: true
+ browser: 'chrome'
+ expectedExitCode: 1
+ })
diff --git a/packages/server/test/e2e/3_issue_2891_spec.coffee b/packages/server/test/e2e/3_issue_2891_spec.coffee
index 8697c3fea64..126e57f3dd8 100644
--- a/packages/server/test/e2e/3_issue_2891_spec.coffee
+++ b/packages/server/test/e2e/3_issue_2891_spec.coffee
@@ -10,6 +10,7 @@ describe "e2e issue 2891", ->
e2e.exec(@, {
project: Fixtures.projectPath("default-layout")
spec: "default_layout_spec.js"
+ sanitizeScreenshotDimensions: true
snapshot: true
expectedExitCode: 0
})
diff --git a/packages/server/test/e2e/3_js_error_handling_spec.coffee b/packages/server/test/e2e/3_js_error_handling_spec.coffee
index 9b2e2a13c75..c08814e5d25 100644
--- a/packages/server/test/e2e/3_js_error_handling_spec.coffee
+++ b/packages/server/test/e2e/3_js_error_handling_spec.coffee
@@ -41,9 +41,8 @@ describe "e2e js error handling", ->
}]
})
- it "fails", ->
- e2e.exec(@, {
- spec: "js_error_handling_failing_spec.coffee"
- snapshot: true
- expectedExitCode: 5
- })
+ e2e.it "fails", {
+ spec: "js_error_handling_failing_spec.coffee"
+ snapshot: true
+ expectedExitCode: 5
+ }
diff --git a/packages/server/test/e2e/3_new_project_spec.coffee b/packages/server/test/e2e/3_new_project_spec.coffee
index 433fdb51afa..100993b7d78 100644
--- a/packages/server/test/e2e/3_new_project_spec.coffee
+++ b/packages/server/test/e2e/3_new_project_spec.coffee
@@ -20,6 +20,7 @@ describe "e2e new project", ->
.catch =>
e2e.exec(@, {
project: noScaffoldingPath
+ sanitizeScreenshotDimensions: true
snapshot: true
expectedExitCode: 0
})
diff --git a/packages/server/test/e2e/3_page_loading_spec.coffee b/packages/server/test/e2e/3_page_loading_spec.coffee
index d50a595ce58..36fb4a652a5 100644
--- a/packages/server/test/e2e/3_page_loading_spec.coffee
+++ b/packages/server/test/e2e/3_page_loading_spec.coffee
@@ -61,15 +61,13 @@ describe "e2e page_loading", ->
}]
})
- it "passes", ->
- ## this tests that __cypress.initial is set correctly whilst navigating
- ## between pages, or during cy.reload
- ## additionally this creates an edge case where after __cypress.initial is
- ## set we send an XHR which should not inject because its requested for JSON
- ## but that another XHR which is requested for html should inject
-
- e2e.exec(@, {
- spec: "page_loading_spec.coffee"
- snapshot: true
- expectedExitCode: 0
- })
+ ## this tests that __cypress.initial is set correctly whilst navigating
+ ## between pages, or during cy.reload
+ ## additionally this creates an edge case where after __cypress.initial is
+ ## set we send an XHR which should not inject because its requested for JSON
+ ## but that another XHR which is requested for html should inject
+ e2e.it "passes", {
+ spec: "page_loading_spec.coffee"
+ snapshot: true
+ expectedExitCode: 0
+ }
diff --git a/packages/server/test/e2e/3_plugins_spec.coffee b/packages/server/test/e2e/3_plugins_spec.coffee
index 3c820a6df50..cab9d1cfe08 100644
--- a/packages/server/test/e2e/3_plugins_spec.coffee
+++ b/packages/server/test/e2e/3_plugins_spec.coffee
@@ -17,6 +17,7 @@ describe "e2e plugins", ->
e2e.exec(@, {
spec: "app_spec.coffee"
project: workingPreprocessor
+ sanitizeScreenshotDimensions: true
snapshot: true
expectedExitCode: 0
})
@@ -25,6 +26,7 @@ describe "e2e plugins", ->
e2e.exec(@, {
spec: "app_spec.coffee"
project: pluginsAsyncError
+ sanitizeScreenshotDimensions: true
snapshot: true
expectedExitCode: 1
})
@@ -35,18 +37,19 @@ describe "e2e plugins", ->
env: "foo=foo,bar=bar"
config: { pageLoadTimeout: 10000 }
project: pluginConfig
+ sanitizeScreenshotDimensions: true
snapshot: true
expectedExitCode: 0
})
- it "works with user extensions", ->
- e2e.exec(@, {
- browser: "chrome"
- spec: "app_spec.coffee"
- project: pluginExtension
- snapshot: true
- expectedExitCode: 0
- })
+ e2e.it "works with user extensions", {
+ browser: "chrome"
+ spec: "app_spec.coffee"
+ project: pluginExtension
+ sanitizeScreenshotDimensions: true
+ snapshot: true
+ expectedExitCode: 0
+ }
it "handles absolute path to pluginsFile", ->
e2e.exec(@, {
@@ -58,6 +61,7 @@ describe "e2e plugins", ->
)
}
project: pluginsAbsolutePath
+ sanitizeScreenshotDimensions: true
snapshot: true
expectedExitCode: 0
})
@@ -66,6 +70,7 @@ describe "e2e plugins", ->
e2e.exec(@, {
spec: "after_screenshot_spec.coffee"
project: pluginAfterScreenshot
+ sanitizeScreenshotDimensions: true
snapshot: true
expectedExitCode: 1
})
diff --git a/packages/server/test/e2e/3_user_agent_spec.coffee b/packages/server/test/e2e/3_user_agent_spec.coffee
index f92804e4bcb..d173c25fec9 100644
--- a/packages/server/test/e2e/3_user_agent_spec.coffee
+++ b/packages/server/test/e2e/3_user_agent_spec.coffee
@@ -23,18 +23,8 @@ describe "e2e user agent", ->
}
})
- it "passes on chrome", ->
- e2e.exec(@, {
- browser: "chrome"
- spec: "user_agent_spec.coffee"
- snapshot: true
- expectedExitCode: 0
- })
-
- it "passes on electron", ->
- e2e.exec(@, {
- browser: "electron"
- spec: "user_agent_spec.coffee"
- snapshot: true
- expectedExitCode: 0
- })
+ e2e.it "passes", {
+ spec: "user_agent_spec.coffee"
+ snapshot: true
+ expectedExitCode: 0
+ }
diff --git a/packages/server/test/e2e/2_browser_path_spec.coffee b/packages/server/test/e2e/4_browser_path_spec.coffee
similarity index 100%
rename from packages/server/test/e2e/2_browser_path_spec.coffee
rename to packages/server/test/e2e/4_browser_path_spec.coffee
diff --git a/packages/server/test/e2e/2_form_submissions_spec.coffee b/packages/server/test/e2e/4_form_submissions_spec.coffee
similarity index 86%
rename from packages/server/test/e2e/2_form_submissions_spec.coffee
rename to packages/server/test/e2e/4_form_submissions_spec.coffee
index 6510000c9e4..19a8e687f1c 100644
--- a/packages/server/test/e2e/2_form_submissions_spec.coffee
+++ b/packages/server/test/e2e/4_form_submissions_spec.coffee
@@ -126,22 +126,20 @@ describe "e2e forms", ->
.then (resp) ->
fs.outputFileAsync(pathToLargeImage, resp)
- it "passes with https on localhost", ->
- e2e.exec(@, {
- config: {
- baseUrl: "https://localhost:#{HTTPS_PORT}"
- }
- spec: "form_submission_multipart_spec.coffee"
- snapshot: true
- expectedExitCode: 0
- })
-
- it "passes with http on localhost", ->
- e2e.exec(@, {
- config: {
- baseUrl: "http://localhost:#{HTTP_PORT}"
- }
- spec: "form_submission_multipart_spec.coffee"
- snapshot: true
- expectedExitCode: 0
- })
+ e2e.it "passes with https on localhost", {
+ config: {
+ baseUrl: "https://localhost:#{HTTPS_PORT}"
+ }
+ spec: "form_submission_multipart_spec.coffee"
+ snapshot: true
+ expectedExitCode: 0
+ }
+
+ e2e.it "passes with http on localhost", {
+ config: {
+ baseUrl: "http://localhost:#{HTTP_PORT}"
+ }
+ spec: "form_submission_multipart_spec.coffee"
+ snapshot: true
+ expectedExitCode: 0
+ }
diff --git a/packages/server/test/e2e/4_request_spec.coffee b/packages/server/test/e2e/4_request_spec.coffee
index 3277f955fda..44abadc5f95 100644
--- a/packages/server/test/e2e/4_request_spec.coffee
+++ b/packages/server/test/e2e/4_request_spec.coffee
@@ -2,12 +2,7 @@ bodyParser = require("body-parser")
cookieParser = require("cookie-parser")
e2e = require("../support/helpers/e2e")
-counts = {
- "localhost:2290": 0
- "localhost:2291": 0
- "localhost:2292": 0
- "localhost:2293": 0
-}
+counts = null
urlencodedParser = bodyParser.urlencoded({ extended: false })
jsonParser = bodyParser.json()
@@ -131,12 +126,19 @@ describe "e2e requests", ->
}]
})
- it "passes", ->
- e2e.exec(@, {
- spec: "request_spec.coffee"
- snapshot: true
- expectedExitCode: 0
- })
+ beforeEach ->
+ counts = {
+ "localhost:2290": 0
+ "localhost:2291": 0
+ "localhost:2292": 0
+ "localhost:2293": 0
+ }
+
+ e2e.it "passes", {
+ spec: "request_spec.coffee"
+ snapshot: true
+ expectedExitCode: 0
+ }
it "fails when network immediately fails", ->
e2e.exec(@, {
@@ -156,3 +158,15 @@ describe "e2e requests", ->
.replace(/"etag": "(.+),/, '"etag": "W/13-52060a5f",')
.replace(/"date": "(.+),/, '"date": "Fri, 18 Aug 2017 15:01:13 GMT",')
})
+
+ it "prints long http props on fail", ->
+ e2e.exec(@, {
+ spec: "request_long_http_props_failing_spec.coffee"
+ snapshot: true
+ expectedExitCode: 1
+ onStdout: (stdout) ->
+ stdout
+ .replace(/"user-agent": ".+",/, '"user-agent": "foo",')
+ .replace(/"etag": "(.+),/, '"etag": "W/13-52060a5f",')
+ .replace(/"date": "(.+),/, '"date": "Fri, 18 Aug 2017 15:01:13 GMT",')
+ })
diff --git a/packages/server/test/e2e/4_screenshot_element_capture_spec.coffee b/packages/server/test/e2e/4_screenshot_element_capture_spec.coffee
index 0619f0d034a..2095d2b1ee3 100644
--- a/packages/server/test/e2e/4_screenshot_element_capture_spec.coffee
+++ b/packages/server/test/e2e/4_screenshot_element_capture_spec.coffee
@@ -16,12 +16,10 @@ describe "e2e screenshot element capture", ->
}
})
- it "passes", ->
- ## this tests that consistent screenshots are taken for element captures,
- ## that the runner UI is hidden and that the page is scrolled properly
-
- e2e.exec(@, {
- spec: "screenshot_element_capture_spec.coffee"
- expectedExitCode: 0
- snapshot: true
- })
+ ## this tests that consistent screenshots are taken for element captures,
+ ## that the runner UI is hidden and that the page is scrolled properly
+ e2e.it "passes", {
+ spec: "screenshot_element_capture_spec.coffee"
+ expectedExitCode: 0
+ snapshot: true
+ }
diff --git a/packages/server/test/e2e/4_screenshot_fullpage_capture_spec.coffee b/packages/server/test/e2e/4_screenshot_fullpage_capture_spec.coffee
index 65bfbef74cc..9e5fa22983a 100644
--- a/packages/server/test/e2e/4_screenshot_fullpage_capture_spec.coffee
+++ b/packages/server/test/e2e/4_screenshot_fullpage_capture_spec.coffee
@@ -18,12 +18,10 @@ describe "e2e screenshot fullPage capture", ->
}
})
- it "passes", ->
- ## this tests that consistent screenshots are taken for fullPage captures,
- ## that the runner UI is hidden and that the page is scrolled properly
-
- e2e.exec(@, {
- spec: "screenshot_fullpage_capture_spec.coffee"
- expectedExitCode: 0
- snapshot: true
- })
+ ## this tests that consistent screenshots are taken for fullPage captures,
+ ## that the runner UI is hidden and that the page is scrolled properly
+ e2e.it "passes", {
+ spec: "screenshot_fullpage_capture_spec.coffee"
+ expectedExitCode: 0
+ snapshot: true
+ }
diff --git a/packages/server/test/e2e/4_screenshot_nested_file_spec.coffee b/packages/server/test/e2e/4_screenshot_nested_file_spec.coffee
index 0f1b010dfe0..79990aec46b 100644
--- a/packages/server/test/e2e/4_screenshot_nested_file_spec.coffee
+++ b/packages/server/test/e2e/4_screenshot_nested_file_spec.coffee
@@ -3,9 +3,8 @@ e2e = require("../support/helpers/e2e")
describe "e2e screenshot in nested spec", ->
e2e.setup()
- it "passes", ->
- e2e.exec(@, {
- spec: "nested-1/nested-2/screenshot_nested_file_spec.coffee"
- expectedExitCode: 0
- snapshot: true
- })
+ e2e.it "passes", {
+ spec: "nested-1/nested-2/screenshot_nested_file_spec.coffee"
+ expectedExitCode: 0
+ snapshot: true
+ }
diff --git a/packages/server/test/e2e/4_websockets_spec.coffee b/packages/server/test/e2e/4_websockets_spec.coffee
index 0e2f5fd4c67..386b10ccc85 100644
--- a/packages/server/test/e2e/4_websockets_spec.coffee
+++ b/packages/server/test/e2e/4_websockets_spec.coffee
@@ -30,9 +30,8 @@ describe "e2e websockets", ->
})
## https://github.com/cypress-io/cypress/issues/556
- it "passes", ->
- e2e.exec(@, {
- spec: "websockets_spec.coffee"
- snapshot: true
- expectedExitCode: 0
- })
+ e2e.it "passes", {
+ spec: "websockets_spec.coffee"
+ snapshot: true
+ expectedExitCode: 0
+ }
diff --git a/packages/server/test/e2e/4_xhr_spec.coffee b/packages/server/test/e2e/4_xhr_spec.coffee
index d013a23dd7e..5d2a67b4330 100644
--- a/packages/server/test/e2e/4_xhr_spec.coffee
+++ b/packages/server/test/e2e/4_xhr_spec.coffee
@@ -26,9 +26,8 @@ describe "e2e xhr", ->
}
})
- it "passes", ->
- e2e.exec(@, {
- spec: "xhr_spec.coffee"
- snapshot: true
- expectedExitCode: 0
- })
+ e2e.it "passes", {
+ spec: "xhr_spec.coffee"
+ snapshot: true
+ expectedExitCode: 0
+ }
diff --git a/packages/server/test/e2e/5_screenshot_viewport_capture_spec.coffee b/packages/server/test/e2e/5_screenshot_viewport_capture_spec.coffee
index 74e06497ecf..9969452a429 100644
--- a/packages/server/test/e2e/5_screenshot_viewport_capture_spec.coffee
+++ b/packages/server/test/e2e/5_screenshot_viewport_capture_spec.coffee
@@ -14,12 +14,10 @@ describe "e2e screenshot viewport capture", ->
}
})
- it "passes", ->
- ## this tests that consistent screenshots are taken for app
- ## captures (namely that the runner UI is hidden)
-
- e2e.exec(@, {
- spec: "screenshot_viewport_capture_spec.coffee"
- expectedExitCode: 0
- snapshot: true
- })
+ ## this tests that consistent screenshots are taken for app
+ ## captures (namely that the runner UI is hidden)
+ e2e.it "passes", {
+ spec: "screenshot_viewport_capture_spec.coffee"
+ expectedExitCode: 0
+ snapshot: true
+ }
diff --git a/packages/server/test/e2e/5_screenshots_spec.coffee b/packages/server/test/e2e/5_screenshots_spec.coffee
index 0869e906666..21dc1794667 100644
--- a/packages/server/test/e2e/5_screenshots_spec.coffee
+++ b/packages/server/test/e2e/5_screenshots_spec.coffee
@@ -56,67 +56,68 @@ describe "e2e screenshots", ->
}
})
- it "passes", ->
- ## this tests that screenshots can be manually generated
- ## and are also generated automatically on failure with
- ## the test title as the file name
-
- e2e.exec(@, {
- spec: "screenshots_spec.coffee"
- expectedExitCode: 4
- snapshot: true
- timeout: 180000
- })
- .then ->
- screenshot = (paths...) ->
- path.join(e2ePath, "cypress", "screenshots", "screenshots_spec.coffee", paths...)
-
- screenshot1 = screenshot("black.png")
- screenshot2 = screenshot("red.png")
- screenshot3 = screenshot("foo", "bar", "baz.png")
- screenshot4 = screenshot("taking screenshots -- generates pngs on failure (failed).png")
- screenshot5 = screenshot("taking screenshots -- before hooks -- empty test 1 -- before all hook (failed).png")
- screenshot6 = screenshot("taking screenshots -- each hooks -- empty test 2 -- before each hook (failed).png")
- screenshot7 = screenshot("taking screenshots -- each hooks -- empty test 2 -- after each hook (failed).png")
- screenshot8 = screenshot("taking screenshots -- ensures unique paths when there's a non-named screenshot and a failure.png")
- screenshot9 = screenshot("taking screenshots -- ensures unique paths when there's a non-named screenshot and a failure (failed).png")
-
- Promise.all([
- fs.statAsync(screenshot1).get("size")
- fs.statAsync(screenshot2).get("size")
- fs.statAsync(screenshot3).get("size")
- fs.statAsync(screenshot4).get("size")
- fs.statAsync(screenshot5).get("size")
- fs.statAsync(screenshot6).get("size")
- fs.statAsync(screenshot7).get("size")
- fs.statAsync(screenshot8).get("size")
- fs.statAsync(screenshot9).get("size")
- ])
- .then (sizes) ->
- ## make sure all of the values are unique
- expect(sizes).to.deep.eq(_.uniq(sizes))
+ ## this tests that screenshots can be manually generated
+ ## and are also generated automatically on failure with
+ ## the test title as the file name
+ e2e.it "passes", {
+ spec: "screenshots_spec.js"
+ expectedExitCode: 4
+ snapshot: true
+ timeout: 180000
+ onRun: (exec, browser) ->
+ exec()
+ .then ->
+ screenshot = (paths...) ->
+ path.join(e2ePath, "cypress", "screenshots", "screenshots_spec.js", paths...)
- ## png1 should not be within 5k of png2
- expect(sizes[0]).not.to.be.closeTo(sizes[1], 5000)
+ screenshot1 = screenshot("black.png")
+ screenshot2 = screenshot("red.png")
+ screenshot3 = screenshot("foo", "bar", "baz.png")
+ screenshot4 = screenshot("taking screenshots -- generates pngs on failure (failed).png")
+ screenshot5 = screenshot("taking screenshots -- before hooks -- empty test 1 -- before all hook (failed).png")
+ screenshot6 = screenshot("taking screenshots -- each hooks -- empty test 2 -- before each hook (failed).png")
+ screenshot7 = screenshot("taking screenshots -- each hooks -- empty test 2 -- after each hook (failed).png")
+ screenshot8 = screenshot("taking screenshots -- ensures unique paths when there's a non-named screenshot and a failure.png")
+ screenshot9 = screenshot("taking screenshots -- ensures unique paths when there's a non-named screenshot and a failure (failed).png")
- ## TODO: this assertion is flaky, sometimes the sizes
- ## are as close as 200 bytes, commenting out for now
- ## until it can be further investigated
- ##
- ## png3 should not be within 1.5k of png4
- # expect(sizes[2]).not.to.be.closeTo(sizes[3], 1500)
- .then ->
Promise.all([
- sizeOf(screenshot1)
- sizeOf(screenshot2)
- sizeOf(screenshot3)
- sizeOf(screenshot4)
- sizeOf(screenshot5)
- sizeOf(screenshot6)
- sizeOf(screenshot7)
+ fs.statAsync(screenshot1).get("size")
+ fs.statAsync(screenshot2).get("size")
+ fs.statAsync(screenshot3).get("size")
+ fs.statAsync(screenshot4).get("size")
+ fs.statAsync(screenshot5).get("size")
+ fs.statAsync(screenshot6).get("size")
+ fs.statAsync(screenshot7).get("size")
+ fs.statAsync(screenshot8).get("size")
+ fs.statAsync(screenshot9).get("size")
])
- .then (dimensions = []) ->
- ## all of the images should be 1280x720
- ## since thats what we run headlessly
- _.each dimensions, (dimension) ->
- expect(dimension).to.deep.eq({width: 1280, height: 720, type: "png"})
+ .then (sizes) ->
+ ## make sure all of the values are unique
+ expect(sizes).to.deep.eq(_.uniq(sizes))
+
+ ## png1 should not be within 5k of png2
+ expect(sizes[0]).not.to.be.closeTo(sizes[1], 5000)
+
+ ## TODO: this assertion is flaky, sometimes the sizes
+ ## are as close as 200 bytes, commenting out for now
+ ## until it can be further investigated
+ ##
+ ## png3 should not be within 1.5k of png4
+ # expect(sizes[2]).not.to.be.closeTo(sizes[3], 1500)
+ .then ->
+ Promise.all([
+ sizeOf(screenshot1)
+ sizeOf(screenshot2)
+ sizeOf(screenshot3)
+ sizeOf(screenshot4)
+ sizeOf(screenshot5)
+ sizeOf(screenshot6)
+ sizeOf(screenshot7)
+ ])
+ .then (dimensions = []) ->
+ if browser is "electron"
+ ## all of the images should be 1280x720
+ ## since thats what we run headlessly
+ _.each dimensions, (dimension) ->
+ expect(dimension).to.deep.eq({width: 1280, height: 720, type: "png"})
+ }
diff --git a/packages/server/test/e2e/5_server_sent_events_spec.coffee b/packages/server/test/e2e/5_server_sent_events_spec.coffee
index da1fee9d483..30d797bf5f9 100644
--- a/packages/server/test/e2e/5_server_sent_events_spec.coffee
+++ b/packages/server/test/e2e/5_server_sent_events_spec.coffee
@@ -56,9 +56,8 @@ describe "e2e server sent events", ->
})
## https://github.com/cypress-io/cypress/issues/1440
- it "passes", ->
- e2e.exec(@, {
- spec: "server_sent_events_spec.coffee"
- snapshot: true
- expectedExitCode: 0
- })
+ e2e.it "passes", {
+ spec: "server_sent_events_spec.coffee"
+ snapshot: true
+ expectedExitCode: 0
+ }
diff --git a/packages/server/test/e2e/5_spec_isolation_spec.coffee b/packages/server/test/e2e/5_spec_isolation_spec.coffee
index e429c39ce35..dc1f5cd17af 100644
--- a/packages/server/test/e2e/5_spec_isolation_spec.coffee
+++ b/packages/server/test/e2e/5_spec_isolation_spec.coffee
@@ -158,71 +158,73 @@ expectRunsToHaveCorrectStats = (runs = []) ->
screenshot.screenshotId = "some-random-id"
screenshot.path = e2e.normalizeStdout(screenshot.path)
+
screenshot
describe "e2e spec_isolation", ->
e2e.setup()
- it "failing", ->
- e2e.exec(@, {
- spec: specs
- outputPath: outputPath
- snapshot: false
- expectedExitCode: 5
- })
- .then ->
- ## now what we want to do is read in the outputPath
- ## and snapshot it so its what we expect after normalizing it
- fs.readJsonAsync(outputPath)
- .then (json) ->
- ## ensure that config has been set
- expect(json.config).to.be.an('object')
- expect(json.config.projectName).to.eq("e2e")
- expect(json.config.projectRoot).to.eq(e2ePath)
-
- ## but zero out config because it's too volatile
- json.config = {}
-
- expect(json.browserPath).to.be.a('string')
- expect(json.browserName).to.be.a('string')
- expect(json.browserVersion).to.be.a('string')
- expect(json.osName).to.be.a('string')
- expect(json.osVersion).to.be.a('string')
- expect(json.cypressVersion).to.be.a('string')
-
- _.extend(json, {
- browserPath: 'path/to/browser'
- browserName: 'FooBrowser'
- browserVersion: '88'
- osName: 'FooOS'
- osVersion: '1234'
- cypressVersion: '9.9.9'
- })
-
- ## ensure the totals are accurate
- expect(json.totalTests).to.eq(
- _.sum([
- json.totalFailed,
- json.totalPassed,
- json.totalPending,
- json.totalSkipped
- ])
- )
-
- expectStartToBeBeforeEnd(json, "startedTestsAt", "endedTestsAt")
-
- ## ensure totalDuration matches all of the stats durations
- expectDurationWithin(
- json,
- "totalDuration",
- _.sumBy(json.runs, "stats.wallClockDuration"),
- _.sumBy(json.runs, "stats.wallClockDuration"),
- 5555
- )
-
- ## should be 4 total runs
- expect(json.runs).to.have.length(4)
-
- expectRunsToHaveCorrectStats(json.runs)
-
- snapshot(json)
+ e2e.it "fails", {
+ spec: specs
+ outputPath: outputPath
+ snapshot: false
+ expectedExitCode: 5
+ onRun: (exec) ->
+ exec()
+ .then ->
+ ## now what we want to do is read in the outputPath
+ ## and snapshot it so its what we expect after normalizing it
+ fs.readJsonAsync(outputPath)
+ .then (json) ->
+ ## ensure that config has been set
+ expect(json.config).to.be.an('object')
+ expect(json.config.projectName).to.eq("e2e")
+ expect(json.config.projectRoot).to.eq(e2ePath)
+
+ ## but zero out config because it's too volatile
+ json.config = {}
+
+ expect(json.browserPath).to.be.a('string')
+ expect(json.browserName).to.be.a('string')
+ expect(json.browserVersion).to.be.a('string')
+ expect(json.osName).to.be.a('string')
+ expect(json.osVersion).to.be.a('string')
+ expect(json.cypressVersion).to.be.a('string')
+
+ _.extend(json, {
+ browserPath: 'path/to/browser'
+ browserName: 'FooBrowser'
+ browserVersion: '88'
+ osName: 'FooOS'
+ osVersion: '1234'
+ cypressVersion: '9.9.9'
+ })
+
+ ## ensure the totals are accurate
+ expect(json.totalTests).to.eq(
+ _.sum([
+ json.totalFailed,
+ json.totalPassed,
+ json.totalPending,
+ json.totalSkipped
+ ])
+ )
+
+ expectStartToBeBeforeEnd(json, "startedTestsAt", "endedTestsAt")
+
+ ## ensure totalDuration matches all of the stats durations
+ expectDurationWithin(
+ json,
+ "totalDuration",
+ _.sumBy(json.runs, "stats.wallClockDuration"),
+ _.sumBy(json.runs, "stats.wallClockDuration"),
+ 5555
+ )
+
+ ## should be 4 total runs
+ expect(json.runs).to.have.length(4)
+
+ expectRunsToHaveCorrectStats(json.runs)
+
+ snapshot('e2e spec isolation fails', json, { allowSharedSnapshot: true })
+ }
diff --git a/packages/server/test/e2e/5_stdout_spec.coffee b/packages/server/test/e2e/5_stdout_spec.coffee
index ea967a294cf..4ff8f937aee 100644
--- a/packages/server/test/e2e/5_stdout_spec.coffee
+++ b/packages/server/test/e2e/5_stdout_spec.coffee
@@ -35,10 +35,17 @@ describe "e2e stdout", ->
expectedExitCode: 0
})
- it "logs that chrome cannot be recorded", ->
+ e2e.it "logs that chrome cannot be recorded", {
+ spec: "simple_spec.coffee"
+ browser: "chrome"
+ snapshot: true
+ expectedExitCode: 0
+ }
+
+ it "displays fullname of nested specfile", ->
e2e.exec(@, {
- spec: "simple_spec.coffee"
- browser: "chrome"
+ port: 2020
snapshot: true
+ spec: "nested-1/nested-2/nested-3/*"
expectedExitCode: 0
})
diff --git a/packages/server/test/e2e/5_subdomain_spec.coffee b/packages/server/test/e2e/5_subdomain_spec.coffee
index a13ef487298..7ae1a241532 100644
--- a/packages/server/test/e2e/5_subdomain_spec.coffee
+++ b/packages/server/test/e2e/5_subdomain_spec.coffee
@@ -102,14 +102,13 @@ describe "e2e subdomain", ->
}
})
- it "passes", ->
- e2e.exec(@, {
- spec: "subdomain_spec.coffee"
- snapshot: true
- expectedExitCode: 0
- config: {
- hosts: {
- "*.foobar.com": "127.0.0.1"
- }
+ e2e.it "passes", {
+ spec: "subdomain_spec.coffee"
+ snapshot: true
+ expectedExitCode: 0
+ config: {
+ hosts: {
+ "*.foobar.com": "127.0.0.1"
}
- })
+ }
+ }
diff --git a/packages/server/test/e2e/5_task_not_registered_spec.coffee b/packages/server/test/e2e/5_task_not_registered_spec.coffee
index b90d058aadd..776652e74f2 100644
--- a/packages/server/test/e2e/5_task_not_registered_spec.coffee
+++ b/packages/server/test/e2e/5_task_not_registered_spec.coffee
@@ -8,6 +8,7 @@ describe "e2e task", ->
e2e.exec(@, {
project: Fixtures.projectPath("task-not-registered")
spec: "task_not_registered_spec.coffee"
+ sanitizeScreenshotDimensions: true
snapshot: true
expectedExitCode: 1
})
diff --git a/packages/server/test/e2e/6_task_spec.coffee b/packages/server/test/e2e/6_task_spec.coffee
index 52061f4e9f4..916750f369d 100644
--- a/packages/server/test/e2e/6_task_spec.coffee
+++ b/packages/server/test/e2e/6_task_spec.coffee
@@ -21,6 +21,7 @@ describe "e2e task", ->
e2e.exec(@, {
project: Fixtures.projectPath("multiple-task-registrations")
spec: "multiple_task_registrations_spec.coffee"
+ sanitizeScreenshotDimensions: true
snapshot: true
expectedExitCode: 0
})
diff --git a/packages/server/test/e2e/6_uncaught_spec_errors_spec.coffee b/packages/server/test/e2e/6_uncaught_spec_errors_spec.coffee
index 0cf98aa2499..c2677c84723 100644
--- a/packages/server/test/e2e/6_uncaught_spec_errors_spec.coffee
+++ b/packages/server/test/e2e/6_uncaught_spec_errors_spec.coffee
@@ -3,37 +3,32 @@ e2e = require("../support/helpers/e2e")
describe "e2e uncaught errors", ->
e2e.setup()
- it "failing1", ->
- e2e.exec(@, {
- spec: "uncaught_synchronous_before_tests_parsed.coffee"
- snapshot: true
- expectedExitCode: 1
- })
+ e2e.it "failing1", {
+ spec: "uncaught_synchronous_before_tests_parsed.coffee"
+ snapshot: true
+ expectedExitCode: 1
+ }
- it "failing2", ->
- e2e.exec(@, {
- spec: "uncaught_synchronous_during_hook_spec.coffee"
- snapshot: true
- expectedExitCode: 1
- })
+ e2e.it "failing2", {
+ spec: "uncaught_synchronous_during_hook_spec.coffee"
+ snapshot: true
+ expectedExitCode: 1
+ }
- it "failing3", ->
- e2e.exec(@, {
- spec: "uncaught_during_test_spec.coffee"
- snapshot: true
- expectedExitCode: 1
- })
+ e2e.it "failing3", {
+ spec: "uncaught_during_test_spec.coffee"
+ snapshot: true
+ expectedExitCode: 1
+ }
- it "failing4", ->
- e2e.exec(@, {
- spec: "uncaught_during_hook_spec.coffee"
- snapshot: true
- expectedExitCode: 1
- })
+ e2e.it "failing4", {
+ spec: "uncaught_during_hook_spec.coffee"
+ snapshot: true
+ expectedExitCode: 1
+ }
- it "failing5", ->
- e2e.exec(@, {
- spec: "caught_async_sync_test_spec.coffee"
- snapshot: true
- expectedExitCode: 4
- })
+ e2e.it "failing5", {
+ spec: "caught_async_sync_test_spec.coffee"
+ snapshot: true
+ expectedExitCode: 4
+ }
diff --git a/packages/server/test/e2e/6_uncaught_support_file_spec.coffee b/packages/server/test/e2e/6_uncaught_support_file_spec.coffee
index 7a39170cb17..c773758150c 100644
--- a/packages/server/test/e2e/6_uncaught_support_file_spec.coffee
+++ b/packages/server/test/e2e/6_uncaught_support_file_spec.coffee
@@ -9,6 +9,7 @@ describe "e2e uncaught support file errors", ->
it "failing", ->
e2e.exec(@, {
project: uncaughtSupportFile
+ sanitizeScreenshotDimensions: true
snapshot: true
expectedExitCode: 1
})
diff --git a/packages/server/test/e2e/6_video_compression_spec.coffee b/packages/server/test/e2e/6_video_compression_spec.coffee
index 8955fda34d4..7bd5cceb242 100644
--- a/packages/server/test/e2e/6_video_compression_spec.coffee
+++ b/packages/server/test/e2e/6_video_compression_spec.coffee
@@ -1,16 +1,46 @@
+path = require("path")
+humanInterval = require("human-interval")
e2e = require("../support/helpers/e2e")
+glob = require("../../lib/util/glob")
+videoCapture = require("../../lib/video_capture")
+Fixtures = require("../support/helpers/fixtures")
+
+NUM_TESTS = 40
+MS_PER_TEST = 500
+EXPECTED_DURATION_MS = NUM_TESTS * MS_PER_TEST
describe "e2e video compression", ->
e2e.setup()
- it "passes", ->
- process.env.VIDEO_COMPRESSION_THROTTLE = 10
-
- e2e.exec(@, {
- spec: "video_compression_spec.coffee"
- snapshot: false
- expectedExitCode: 0
- })
- .get("stdout")
- .then (stdout) ->
- expect(stdout).to.match(/Compression progress:\s+\d{1,3}%/)
+ e2e.it "passes", {
+ spec: "video_compression_spec.coffee"
+ snapshot: false
+ config: {
+ env: {
+ NUM_TESTS
+ MS_PER_TEST
+ }
+ }
+ expectedExitCode: 0
+ onRun: (exec) ->
+ process.env.VIDEO_COMPRESSION_THROTTLE = 10
+
+ exec()
+ .tap ->
+ videosPath = Fixtures.projectPath("e2e/cypress/videos/*")
+
+ glob(videosPath)
+ .then (files) ->
+ expect(files).to.have.length(1, "globbed for videos and found: #{files.length}. Expected to find 1 video. Search in videosPath: #{videosPath}.")
+
+ videoCapture.getCodecData(files[0])
+ .then ({ duration }) ->
+ durationMs = videoCapture.getMsFromDuration(duration)
+ expect(durationMs).to.be.ok
+ expect(durationMs).to.be.closeTo(EXPECTED_DURATION_MS, humanInterval('10 seconds'))
+
+ .get("stdout")
+ .then (stdout) ->
+ expect(stdout).to.match(/Compression progress:\s+\d{1,3}%/)
+
+ },
diff --git a/packages/server/test/e2e/6_viewport_spec.coffee b/packages/server/test/e2e/6_viewport_spec.coffee
index 363ad1dca28..6f4ec612b0a 100644
--- a/packages/server/test/e2e/6_viewport_spec.coffee
+++ b/packages/server/test/e2e/6_viewport_spec.coffee
@@ -8,9 +8,8 @@ describe "e2e viewport", ->
}
})
- it "passes", ->
- e2e.exec(@, {
- spec: "viewport_spec.coffee"
- snapshot: true
- expectedExitCode: 0
- })
+ e2e.it "passes", {
+ spec: "viewport_spec.coffee"
+ snapshot: true
+ expectedExitCode: 0
+ }
diff --git a/packages/server/test/e2e/6_visit_spec.coffee b/packages/server/test/e2e/6_visit_spec.coffee
index 33830c10584..5e94f6e214d 100644
--- a/packages/server/test/e2e/6_visit_spec.coffee
+++ b/packages/server/test/e2e/6_visit_spec.coffee
@@ -62,55 +62,48 @@ describe "e2e visit", ->
}
})
- it "passes", ->
- ## this tests that hashes are applied during a visit
- ## which forces the browser to scroll to the div
- ## additionally this tests that jquery.js is not truncated
- ## due to __cypress.initial cookies not being cleared by
- ## the hash.html response
-
- ## additionally this tests that xhr request headers + body
- ## can reach the backend without being modified or changed
- ## by the cypress proxy in any way
-
- e2e.exec(@, {
- spec: "visit_spec.coffee"
- snapshot: true
- expectedExitCode: 0
- })
-
- it "fails when network connection immediately fails", ->
- e2e.exec(@, {
- spec: "visit_http_network_error_failing_spec.coffee"
- snapshot: true
- expectedExitCode: 1
- })
-
- it "fails when server responds with 500", ->
- e2e.exec(@, {
- spec: "visit_http_500_response_failing_spec.coffee"
- snapshot: true
- expectedExitCode: 1
- })
-
- it "fails when file server responds with 404", ->
- e2e.exec(@, {
- spec: "visit_file_404_response_failing_spec.coffee"
- snapshot: true
- expectedExitCode: 1
- })
-
- it "fails when content type isnt html", ->
- e2e.exec(@, {
- spec: "visit_non_html_content_type_failing_spec.coffee"
- snapshot: true
- expectedExitCode: 1
- })
-
- it "calls onBeforeLoad when overwriting cy.visit", ->
- e2e.exec(@, {
- spec: "issue_2196_spec.coffee"
- })
+ ## this tests that hashes are applied during a visit
+ ## which forces the browser to scroll to the div
+ ## additionally this tests that jquery.js is not truncated
+ ## due to __cypress.initial cookies not being cleared by
+ ## the hash.html response
+
+ ## additionally this tests that xhr request headers + body
+ ## can reach the backend without being modified or changed
+ ## by the cypress proxy in any way
+ e2e.it "passes", {
+ spec: "visit_spec.coffee"
+ snapshot: true
+ expectedExitCode: 0
+ }
+
+ e2e.it "fails when network connection immediately fails", {
+ spec: "visit_http_network_error_failing_spec.coffee"
+ snapshot: true
+ expectedExitCode: 1
+ }
+
+ e2e.it "fails when server responds with 500", {
+ spec: "visit_http_500_response_failing_spec.coffee"
+ snapshot: true
+ expectedExitCode: 1
+ }
+
+ e2e.it "fails when file server responds with 404", {
+ spec: "visit_file_404_response_failing_spec.coffee"
+ snapshot: true
+ expectedExitCode: 1
+ }
+
+ e2e.it "fails when content type isnt html", {
+ spec: "visit_non_html_content_type_failing_spec.coffee"
+ snapshot: true
+ expectedExitCode: 1
+ }
+
+ e2e.it "calls onBeforeLoad when overwriting cy.visit", {
+ spec: "issue_2196_spec.coffee"
+ }
context "low responseTimeout, normal pageLoadTimeout", ->
e2e.setup({
@@ -124,12 +117,11 @@ describe "e2e visit", ->
}
})
- it "fails when response never ends", ->
- e2e.exec(@, {
- spec: "visit_response_never_ends_failing_spec.js",
- snapshot: true,
- expectedExitCode: 3
- })
+ e2e.it "fails when response never ends", {
+ spec: "visit_response_never_ends_failing_spec.js",
+ snapshot: true,
+ expectedExitCode: 3
+ }
context "normal response timeouts", ->
e2e.setup({
@@ -143,9 +135,8 @@ describe "e2e visit", ->
}
})
- it "fails when visit times out", ->
- e2e.exec(@, {
- spec: "visit_http_timeout_failing_spec.coffee"
- snapshot: true
- expectedExitCode: 2
- })
+ e2e.it "fails when visit times out", {
+ spec: "visit_http_timeout_failing_spec.coffee"
+ snapshot: true
+ expectedExitCode: 2
+ }
diff --git a/packages/server/test/e2e/6_web_security_spec.coffee b/packages/server/test/e2e/6_web_security_spec.coffee
index b8f79496237..acd72b9c554 100644
--- a/packages/server/test/e2e/6_web_security_spec.coffee
+++ b/packages/server/test/e2e/6_web_security_spec.coffee
@@ -3,7 +3,7 @@ e2e = require("../support/helpers/e2e")
onServer = (app) ->
app.get "/link", (req, res) ->
- res.send("link
second")
+ res.send("link
second")
app.get "/cross_origin", (req, res) ->
res.send("cross origin
")
@@ -12,14 +12,14 @@ onServer = (app) ->
res.send("""
form
-
""")
app.post "/submit", (req, res) ->
- res.redirect("https://www.foo.com:55665/cross_origin")
+ res.redirect("https://www.foo.com:44665/cross_origin")
app.get "/javascript", (req, res) ->
res.send("""
@@ -27,7 +27,7 @@ onServer = (app) ->
javascript
@@ -41,10 +41,10 @@ describe "e2e web security", ->
context "when enabled", ->
e2e.setup({
servers: [{
- port: 5566
+ port: 4466
onServer: onServer
}, {
- port: 55665
+ port: 44665
https: true
onServer: onServer
}]
@@ -55,20 +55,19 @@ describe "e2e web security", ->
}
})
- it "fails", ->
- e2e.exec(@, {
- spec: "web_security_spec.coffee"
- snapshot: true
- expectedExitCode: 3
- })
+ e2e.it "fails", {
+ spec: "web_security_spec.coffee"
+ snapshot: true
+ expectedExitCode: 3
+ }
context "when disabled", ->
e2e.setup({
servers: [{
- port: 5566
+ port: 4466
onServer: onServer
}, {
- port: 55665
+ port: 44665
https: true
onServer: onServer
}]
@@ -80,10 +79,8 @@ describe "e2e web security", ->
}
})
- it "passes", ->
- e2e.exec(@, {
- spec: "web_security_spec.coffee"
- browser: "chrome"
- snapshot: true
- expectedExitCode: 0
- })
+ e2e.it "passes", {
+ spec: "web_security_spec.coffee"
+ snapshot: true
+ expectedExitCode: 0
+ }
diff --git a/packages/server/test/e2e/7_record_spec.coffee b/packages/server/test/e2e/7_record_spec.coffee
index 5319637e6de..5bd145c221c 100644
--- a/packages/server/test/e2e/7_record_spec.coffee
+++ b/packages/server/test/e2e/7_record_spec.coffee
@@ -297,7 +297,7 @@ describe "e2e record", ->
secondInstancePut = requests[6]
expect(secondInstancePut.body.error).to.be.null
expect(secondInstancePut.body.tests).to.have.length(2)
- expect(secondInstancePut.body.hooks).to.have.length(1)
+ expect(secondInstancePut.body.hooks).to.have.length(2)
expect(secondInstancePut.body.screenshots).to.have.length(1)
expect(secondInstancePut.body.stats.tests).to.eq(2)
expect(secondInstancePut.body.stats.failures).to.eq(1)
@@ -318,7 +318,7 @@ describe "e2e record", ->
thirdInstancePut = requests[11]
expect(thirdInstancePut.body.error).to.be.null
expect(thirdInstancePut.body.tests).to.have.length(2)
- expect(thirdInstancePut.body.hooks).to.have.length(0)
+ expect(thirdInstancePut.body.hooks).to.have.length(1)
expect(thirdInstancePut.body.screenshots).to.have.length(1)
expect(thirdInstancePut.body.stats.tests).to.eq(2)
expect(thirdInstancePut.body.stats.passes).to.eq(1)
@@ -340,7 +340,7 @@ describe "e2e record", ->
fourthInstancePut = requests[15]
expect(fourthInstancePut.body.error).to.be.null
expect(fourthInstancePut.body.tests).to.have.length(1)
- expect(fourthInstancePut.body.hooks).to.have.length(0)
+ expect(fourthInstancePut.body.hooks).to.have.length(1)
expect(fourthInstancePut.body.screenshots).to.have.length(1)
expect(fourthInstancePut.body.stats.tests).to.eq(1)
expect(fourthInstancePut.body.stats.failures).to.eq(1)
diff --git a/packages/server/test/integration/cypress_spec.coffee b/packages/server/test/integration/cypress_spec.coffee
index 2ebb03989a0..671c076173f 100644
--- a/packages/server/test/integration/cypress_spec.coffee
+++ b/packages/server/test/integration/cypress_spec.coffee
@@ -39,6 +39,7 @@ Watchers = require("#{root}lib/watchers")
browsers = require("#{root}lib/browsers")
videoCapture = require("#{root}lib/video_capture")
browserUtils = require("#{root}lib/browsers/utils")
+chromeBrowser = require("#{root}lib/browsers/chrome")
openProject = require("#{root}lib/open_project")
env = require("#{root}lib/util/env")
system = require("#{root}lib/util/system")
@@ -123,7 +124,7 @@ describe "lib/cypress", ->
.callThrough()
.withArgs("INVOKED_BINARY_OUTSIDE_NPM_MODULE")
.returns(null)
-
+
sinon.spy(errors, "log")
sinon.spy(errors, "logException")
sinon.spy(console, "log")
@@ -335,6 +336,20 @@ describe "lib/cypress", ->
expect(browsers.open).to.be.calledWithMatch(ELECTRON_BROWSER, {url: "http://localhost:8888/__/#/tests/integration/test2.coffee"})
@expectExitWith(0)
+ it "runs project by limiting spec files via config.testFiles string glob pattern", ->
+ cypress.start(["--run-project=#{@todosPath}", "--config=testFiles=#{@todosPath}/tests/test2.coffee"])
+ .then =>
+ expect(browsers.open).to.be.calledWithMatch(ELECTRON_BROWSER, {url: "http://localhost:8888/__/#/tests/integration/test2.coffee"})
+ @expectExitWith(0)
+
+ it "runs project by limiting spec files via config.testFiles as a JSON array of string glob patterns", ->
+ cypress.start(["--run-project=#{@todosPath}", "--config=testFiles=[\"**/test2.coffee\",\"**/test1.js\"]"])
+ .then =>
+ expect(browsers.open).to.be.calledWithMatch(ELECTRON_BROWSER, {url: "http://localhost:8888/__/#/tests/integration/test2.coffee"})
+ .then =>
+ expect(browsers.open).to.be.calledWithMatch(ELECTRON_BROWSER, {url: "http://localhost:8888/__/#/tests/integration/test1.js"})
+ @expectExitWith(0)
+
it "does not watch settings or plugins in run mode", ->
watch = sinon.spy(Watchers.prototype, "watch")
watchTree = sinon.spy(Watchers.prototype, "watchTree")
@@ -383,7 +398,7 @@ describe "lib/cypress", ->
])
.each(ensureDoesNotExist)
.then =>
- @expectExitWithErr("PROJECT_DOES_NOT_EXIST", @pristinePath)
+ @expectExitWithErr("CONFIG_FILE_NOT_FOUND", @pristinePath)
it "does not scaffold integration or example specs when runMode", ->
settings.write(@pristinePath, {})
@@ -646,9 +661,8 @@ describe "lib/cypress", ->
## also make sure we test the rest of the integration functionality
## for headed errors! <-- not unit tests, but integration tests!
it "logs error and exits when project folder has read permissions only and cannot write cypress.json", ->
- if process.env.CI
- ## Gleb: disabling this because Node 8 docker image runs as root
- ## which makes accessing everything possible.
+ ## test disabled if running as root - root can write all things at all times
+ if process.geteuid() == 0
return
permissionsPath = path.resolve("./permissions")
@@ -765,8 +779,12 @@ describe "lib/cypress", ->
ee = new EE()
ee.kill = ->
+ # ughh, would be nice to test logic inside the launcher
+ # that cleans up after the browser exit
+ # like calling client.close() if available to let the
+ # browser free any resources
ee.emit("exit")
- ee.close = ->
+ ee.destroy = ->
ee.emit("closed")
ee.isDestroyed = -> false
ee.loadURL = ->
@@ -775,7 +793,7 @@ describe "lib/cypress", ->
debugger: {
on: sinon.stub()
attach: sinon.stub()
- sendCommand: sinon.stub()
+ sendCommand: sinon.stub().resolves()
}
setUserAgent: sinon.stub()
session: {
@@ -787,10 +805,23 @@ describe "lib/cypress", ->
sinon.stub(browserUtils, "launch").resolves(ee)
sinon.stub(Windows, "create").returns(ee)
- sinon.stub(Windows, "automation")
context "before:browser:launch", ->
it "chrome", ->
+ # during testing, do not try to connect to the remote interface or
+ # use the Chrome remote interface client
+ criClient = {
+ ensureMinimumProtocolVersion: sinon.stub().resolves()
+ close: sinon.stub().resolves()
+ }
+ sinon.stub(chromeBrowser, "_connectToChromeRemoteInterface").resolves(criClient)
+ # the "returns(resolves)" stub is due to curried method
+ # it accepts URL to visit and then waits for actual CRI client reference
+ # and only then navigates to that URL
+ sinon.stub(chromeBrowser, "_navigateUsingCRI").resolves()
+
+ sinon.stub(chromeBrowser, "_setAutomation").returns()
+
cypress.start([
"--run-project=#{@pluginBrowser}"
"--browser=chrome"
@@ -810,6 +841,10 @@ describe "lib/cypress", ->
@expectExitWith(0)
+ expect(chromeBrowser._navigateUsingCRI).to.have.been.calledOnce
+ expect(chromeBrowser._setAutomation).to.have.been.calledOnce
+ expect(chromeBrowser._connectToChromeRemoteInterface).to.have.been.calledOnce
+
it "electron", ->
writeVideoFrame = sinon.stub()
videoCapture.start.returns({ writeVideoFrame })
@@ -892,6 +927,35 @@ describe "lib/cypress", ->
@expectExitWith(0)
+ describe "--config-file", ->
+ it "false does not require cypress.json to run", ->
+ fs.statAsync(path.join(@pristinePath, 'cypress.json'))
+ .then =>
+ throw new Error("cypress.json should not exist")
+ .catch {code: "ENOENT"}, =>
+ cypress.start([
+ "--run-project=#{@pristinePath}"
+ "--no-run-mode",
+ "--config-file",
+ "false"
+ ]).then =>
+ @expectExitWith(0)
+
+ it "with a custom config file fails when it doesn't exist", ->
+ @filename = "abcdefgh.test.json"
+
+ fs.statAsync(path.join(@todosPath, @filename))
+ .then =>
+ throw new Error("#{@filename} should not exist")
+ .catch {code: "ENOENT"}, =>
+ cypress.start([
+ "--run-project=#{@todosPath}"
+ "--no-run-mode",
+ "--config-file",
+ @filename
+ ]).then =>
+ @expectExitWithErr("CONFIG_FILE_NOT_FOUND", @filename, @todosPath)
+
## most record mode logic is covered in e2e tests.
## we only need to cover the edge cases / warnings
context "--record or --ci", ->
@@ -1396,6 +1460,49 @@ describe "lib/cypress", ->
.then ->
expect(event.sender.send.withArgs("response").firstCall.args[1].data).to.eql(warning)
+ describe "--config-file", ->
+ beforeEach ->
+ @filename = "foo.bar.baz.asdf.quux.json"
+ @open = sinon.stub(Server.prototype, "open").resolves([])
+
+ it "reads config from a custom config file", ->
+ sinon.stub(fs, "readJsonAsync")
+ fs.readJsonAsync.withArgs(path.join(@pristinePath, @filename)).resolves({
+ env: { foo: "bar" },
+ port: 2020
+ })
+ fs.readJsonAsync.callThrough()
+
+ cypress.start([
+ "--config-file=#{@filename}"
+ ])
+ .then =>
+ options = Events.start.firstCall.args[0]
+ Events.handleEvent(options, {}, {}, 123, "open:project", @pristinePath)
+ .then =>
+ expect(@open).to.be.called
+
+ cfg = @open.getCall(0).args[0]
+
+ expect(cfg.env.foo).to.equal("bar")
+ expect(cfg.port).to.equal(2020)
+
+ it "creates custom config file if it does not exist", ->
+ write = sinon.spy(settings, "_write")
+
+ cypress.start([
+ "--config-file=#{@filename}"
+ ])
+ .then =>
+ options = Events.start.firstCall.args[0]
+ Events.handleEvent(options, {}, {}, 123, "open:project", @pristinePath)
+ .then =>
+ expect(@open).to.be.called
+
+ fs.readJsonAsync(path.join(@pristinePath, @filename))
+ .then (json) =>
+ expect(json).to.deep.equal({})
+
context "--cwd", ->
beforeEach ->
errors.warning.restore()
diff --git a/packages/server/test/integration/http_requests_spec.coffee b/packages/server/test/integration/http_requests_spec.coffee
index cacd50aeb81..f5510b6a770 100644
--- a/packages/server/test/integration/http_requests_spec.coffee
+++ b/packages/server/test/integration/http_requests_spec.coffee
@@ -3,7 +3,9 @@ require("../spec_helper")
_ = require("lodash")
r = require("request")
rp = require("request-promise")
+compression = require("compression")
dns = require("dns")
+express = require("express")
http = require("http")
path = require("path")
url = require("url")
@@ -53,6 +55,8 @@ browserifyFile = (filePath) ->
)
describe "Routes", ->
+ require("mocha-banner").register()
+
beforeEach ->
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"
@@ -269,8 +273,12 @@ describe "Routes", ->
@rp("http://localhost:9999/__")
.then (res) ->
expect(res.statusCode).to.eq(200)
- expect(res.body).to.include("version")
- expect(res.body).to.include(pkg.version)
+
+ base64Config = /Runner\.start\(.*, "(.*)"\)/.exec(res.body)[1]
+ configStr = Buffer.from(base64Config, 'base64').toString()
+
+ expect(configStr).to.include("version")
+ expect(configStr).to.include(pkg.version)
context "GET /__cypress/runner/*", ->
beforeEach ->
@@ -659,6 +667,23 @@ describe "Routes", ->
expect(res.statusCode).to.eq(200)
expect(res.body).to.deep.eq({test: "We’ll"})
+ context "deferred", ->
+ it "closes connection if no stub is received before a reset", ->
+ p = @rp({
+ url: "http://localhost:2020/__cypress/xhrs/users/1"
+ json: true
+ headers: {
+ "x-cypress-id": "foo1"
+ "x-cypress-responsedeferred": true
+ }
+ })
+
+ setTimeout =>
+ @server._xhrServer.reset()
+ , 100
+
+ expect(p).to.be.rejectedWith('Error: socket hang up')
+
context "fixture", ->
beforeEach ->
Fixtures.scaffold("todos")
@@ -1003,6 +1028,44 @@ describe "Routes", ->
expect(res.body).not.to.include("document.domain = 'github.com'")
expect(res.body).to.include("")
+ ## https://github.com/cypress-io/cypress/issues/1746
+ it "can ungzip utf-8 javascript and inject without corrupting it", ->
+ js = ""
+
+ app = express()
+
+ app.use compression({ chunkSize: 64, threshold: 1 })
+
+ app.get "/", (req, res) =>
+ res.setHeader('content-type', 'application/javascript; charset=UTF-8')
+ res.setHeader('transfer-encoding', 'chunked')
+
+ write = (chunk) =>
+ js += chunk
+ res.write(chunk)
+
+ write("function ")
+ _.times 100, =>
+ write("😡😈".repeat(10))
+ write(" () { }")
+ res.end()
+
+ server = http.createServer(app)
+
+ Promise.fromCallback (cb) =>
+ server.listen(12345, cb)
+ .then =>
+ @rp({
+ url: "http://localhost:12345"
+ gzip: true
+ })
+ .then (res) ->
+ expect(res.statusCode).to.eq(200)
+ expect(res.body).to.deep.eq(js)
+ .finally =>
+ Promise.fromCallback (cb) =>
+ server.close(cb)
+
context "accept-encoding", ->
beforeEach ->
@setup("http://www.github.com")
@@ -2615,20 +2678,6 @@ describe "Routes", ->
## shouldn't be more than 500ms
expect(reqTime).to.be.lt(500)
- # b = res.body
- #
- # console.time("1")
- # b.replace(topOrParentEqualityBeforeRe, "$self")
- # console.timeEnd("1")
- #
- # console.time("2")
- # b.replace(topOrParentEqualityAfterRe, "self$2")
- # console.timeEnd("2")
- #
- # console.time("3")
- # b.replace(topOrParentLocationOrFramesRe, "$1self$3$4")
- # console.timeEnd("3")
-
describe "off with config", ->
beforeEach ->
@setup("http://www.google.com", {
@@ -3119,7 +3168,7 @@ describe "Routes", ->
afterEach ->
@httpSrv.close()
- [204, 304, 101, 102, 103].forEach (status) ->
+ [204, 304].forEach (status) ->
it "passes through a #{status} response immediately", ->
@rp({
url: "http://localhost:#{@port}/?status=#{status}"
diff --git a/packages/server/test/integration/server_spec.coffee b/packages/server/test/integration/server_spec.coffee
index 54c96396e10..277aa42cee1 100644
--- a/packages/server/test/integration/server_spec.coffee
+++ b/packages/server/test/integration/server_spec.coffee
@@ -19,6 +19,8 @@ expectToEqDetails = (actual, expected) ->
expect(actual).to.deep.eq(expected)
describe "Server", ->
+ require("mocha-banner").register()
+
beforeEach ->
sinon.stub(Server.prototype, "reset")
@@ -29,8 +31,8 @@ describe "Server", ->
nock.enableNetConnect()
@automationRequest = sinon.stub()
- .withArgs("get:cookies").resolves([])
- .withArgs("set:cookie").resolves({})
+ @automationRequest.withArgs("get:cookies").resolves([])
+ @automationRequest.withArgs("set:cookie").resolves({})
@setup = (initialUrl, obj = {}) =>
if _.isObject(initialUrl)
@@ -65,36 +67,26 @@ describe "Server", ->
}
rp(options)
- open = =>
- Promise.all([
- ## open our https server
- httpsServer.start(8443),
-
- ## and open our cypress server
- @server = Server()
+ return Promise.all([
+ ## open our https server
+ httpsServer.start(8443),
- @server.open(cfg)
- .spread (port) =>
- if initialUrl
- @server._onDomainSet(initialUrl)
+ ## and open our cypress server
+ @server = Server()
- @srv = @server.getHttpServer()
+ @server.open(cfg)
+ .spread (port) =>
+ if initialUrl
+ @server._onDomainSet(initialUrl)
- # @session = new (Session({app: @srv}))
+ @srv = @server.getHttpServer()
- @proxy = "http://localhost:" + port
+ # @session = new (Session({app: @srv}))
- @fileServer = @server._fileServer.address()
- ])
+ @proxy = "http://localhost:" + port
- if @server
- Promise.join(
- httpsServer.stop()
- @server.close()
- )
- .then(open)
- else
- open()
+ @fileServer = @server._fileServer.address()
+ ])
afterEach ->
nock.cleanAll()
@@ -649,7 +641,6 @@ describe "Server", ->
@server._onResolveUrl("http://localhost:64646", {}, @automationRequest)
.catch (err) ->
expect(err.message).to.eq("connect ECONNREFUSED 127.0.0.1:64646")
- expect(err.stack).to.include("._errnoException")
expect(err.port).to.eq(64646)
expect(err.code).to.eq("ECONNREFUSED")
diff --git a/packages/server/test/integration/websockets_spec.coffee b/packages/server/test/integration/websockets_spec.coffee
index aceac43d7a8..aef90d0174c 100644
--- a/packages/server/test/integration/websockets_spec.coffee
+++ b/packages/server/test/integration/websockets_spec.coffee
@@ -14,11 +14,13 @@ Automation = require("#{root}lib/automation")
Fixtures = require("#{root}/test/support/helpers/fixtures")
cyPort = 12345
-otherPort = 5555
+otherPort = 55551
wsPort = 20000
wssPort = 8443
describe "Web Sockets", ->
+ require("mocha-banner").register()
+
beforeEach ->
Fixtures.scaffold()
@@ -59,7 +61,7 @@ describe "Web Sockets", ->
expect(err.code).to.eq("ECONNRESET")
done()
- it "sends back 502 Bad Gateway when error upgrading", (done) ->
+ it "sends back ECONNRESET when error upgrading", (done) ->
agent = new httpsAgent("http://localhost:#{cyPort}")
@server._onDomainSet("http://localhost:#{otherPort}")
@@ -68,11 +70,9 @@ describe "Web Sockets", ->
agent: agent
})
- client.on "unexpected-response", (req, res) ->
- expect(res.statusCode).to.eq(502)
- expect(res.statusMessage).to.eq("Bad Gateway")
- expect(res.headers).to.have.property("x-cypress-proxy-error-message")
- expect(res.headers).to.have.property("x-cypress-proxy-error-code")
+ client.on "error", (err) ->
+ expect(err.code).to.eq('ECONNRESET')
+ expect(err.message).to.eq('socket hang up')
done()
diff --git a/packages/server/test/performance/cy_visit_performance_spec.js b/packages/server/test/performance/cy_visit_performance_spec.js
index 30e19fe6179..40049f676a2 100644
--- a/packages/server/test/performance/cy_visit_performance_spec.js
+++ b/packages/server/test/performance/cy_visit_performance_spec.js
@@ -19,6 +19,7 @@ context('cy.visit performance tests', function () {
},
settings: {
baseUrl: 'http://localhost:3434',
+ video: false,
},
})
@@ -37,6 +38,7 @@ context('cy.visit performance tests', function () {
snapshot: true,
expectedExitCode: 0,
config: {
+ video: false,
env: {
currentRetry: this.test._currentRetry,
},
diff --git a/packages/server/test/performance/proxy_performance_spec.js b/packages/server/test/performance/proxy_performance_spec.js
index 3452d6e218f..4e33d1219d8 100644
--- a/packages/server/test/performance/proxy_performance_spec.js
+++ b/packages/server/test/performance/proxy_performance_spec.js
@@ -12,7 +12,6 @@ const DebuggingProxy = require('@cypress/debugging-proxy')
const HarCapturer = require('chrome-har-capturer')
const performance = require('../support/helpers/performance')
const Promise = require('bluebird')
-const Table = require('console-table-printer').Table
const sanitizeFilename = require('sanitize-filename')
process.env.CYPRESS_ENV = 'development'
@@ -387,14 +386,10 @@ describe('Proxy Performance', function () {
after(() => {
debug(`Done in ${Math.round((new Date() / 1000) - start)}s`)
- // console.table not available until Node 10
- const t = new Table()
-
- t.addRows(testCases)
-
- // console.log is bad for eslint, but nobody never said nothing about process.stdout.write
process.stdout.write('Note: All times are in milliseconds.\n')
- t.printTable()
+
+ // eslint-disable-next-line no-console
+ console.table(testCases)
return Promise.map(testCases, (testCase) => {
testCase['URL'] = urlUnderTest
diff --git a/packages/server/test/scripts/.eslintrc b/packages/server/test/scripts/.eslintrc.json
similarity index 100%
rename from packages/server/test/scripts/.eslintrc
rename to packages/server/test/scripts/.eslintrc.json
diff --git a/packages/server/test/scripts/e2e.js b/packages/server/test/scripts/e2e.js
index 1276e7ab559..20e7737760b 100644
--- a/packages/server/test/scripts/e2e.js
+++ b/packages/server/test/scripts/e2e.js
@@ -37,7 +37,9 @@ glob('test/e2e/**/*')
.then((specs = []) => {
if (options.spec) {
return _.filter(specs, (spec) => {
- return spec.includes(options.spec)
+ return _.some(options.spec.split(','), (specPart) => {
+ return spec.includes(specPart)
+ })
})
}
diff --git a/packages/server/test/scripts/run.js b/packages/server/test/scripts/run.js
index c804473db0b..4fd9cd41e2e 100644
--- a/packages/server/test/scripts/run.js
+++ b/packages/server/test/scripts/run.js
@@ -24,6 +24,10 @@ const isWindows = () => {
return os.platform() === 'win32'
}
+const isGteNode12 = () => {
+ return Number(process.versions.node.split('.')[0]) >= 12
+}
+
if (!run) {
return exitErr(`
Error: A path to a spec file must be specified!
@@ -64,6 +68,14 @@ if (options['inspect-brk']) {
)
}
+if (isGteNode12()) {
+ // max HTTP header size 8kb -> 1mb
+ // https://github.com/cypress-io/cypress/issues/76
+ commandAndArguments.args.push(
+ `--max-http-header-size=${1024 * 1024}`
+ )
+}
+
commandAndArguments.args.push(
'node_modules/.bin/_mocha',
run
diff --git a/packages/server/test/spec_helper.coffee b/packages/server/test/spec_helper.coffee
index da4f4fa5d6c..817aa670e88 100644
--- a/packages/server/test/spec_helper.coffee
+++ b/packages/server/test/spec_helper.coffee
@@ -16,6 +16,7 @@ appData = require("../lib/util/app_data")
require("chai")
.use(require("@cypress/sinon-chai"))
.use(require("chai-uuid"))
+.use(require("chai-as-promised"))
if process.env.UPDATE
throw new Error("You're using UPDATE=1 which is the old way of updating snapshots.\n\nThe correct environment variable is SNAPSHOT_UPDATE=1")
@@ -95,3 +96,10 @@ afterEach ->
nock.enableNetConnect()
process.env = _.clone(env)
+
+module.exports = {
+ expect: global.expect
+ nock: global.nock
+ proxyquire: global.proxyquire
+ sinon: global.sinon
+}
diff --git a/packages/reporter/cypress/.eslintrc b/packages/server/test/support/fixtures/projects/.eslintrc.json
similarity index 100%
rename from packages/reporter/cypress/.eslintrc
rename to packages/server/test/support/fixtures/projects/.eslintrc.json
diff --git a/packages/server/test/support/fixtures/projects/busted-support-file/cypress/support/index.js b/packages/server/test/support/fixtures/projects/busted-support-file/cypress/support/index.js
index b634e938071..fbab6df357e 100644
--- a/packages/server/test/support/fixtures/projects/busted-support-file/cypress/support/index.js
+++ b/packages/server/test/support/fixtures/projects/busted-support-file/cypress/support/index.js
@@ -1 +1 @@
-import "./does/not/exist"
\ No newline at end of file
+import './does/not/exist'
diff --git a/packages/server/test/support/fixtures/projects/default-layout/cypress/integration/default_layout_spec.js b/packages/server/test/support/fixtures/projects/default-layout/cypress/integration/default_layout_spec.js
index e2fbbbe9ab7..7e5e612bae4 100644
--- a/packages/server/test/support/fixtures/projects/default-layout/cypress/integration/default_layout_spec.js
+++ b/packages/server/test/support/fixtures/projects/default-layout/cypress/integration/default_layout_spec.js
@@ -1 +1,2 @@
+/* eslint-disable mocha/no-global-tests */
it('works', () => {})
diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/async_timeouts_spec.coffee b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/async_timeouts_spec.coffee
index e54da4e1447..fa70218a922 100644
--- a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/async_timeouts_spec.coffee
+++ b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/async_timeouts_spec.coffee
@@ -1,8 +1,13 @@
describe "async", ->
- it.only "bar fails", (done) ->
+ it "bar fails", (done) ->
@timeout(100)
cy.on "fail", ->
## async caught fail
foo.bar()
+
+ it "fails async after cypress command", (done) ->
+ @timeout(100)
+
+ cy.wait(0)
diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/browserify_babel_es2015_failing_spec.js b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/browserify_babel_es2015_failing_spec.js
index 16a6f89e5e5..8bcc6576e58 100644
--- a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/browserify_babel_es2015_failing_spec.js
+++ b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/browserify_babel_es2015_failing_spec.js
@@ -1 +1 @@
-import "../../lib/fail"
\ No newline at end of file
+import '../../lib/fail'
diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/config_passing_spec.coffee b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/config_passing_spec.coffee
index 7974d5dab75..17e9e8e9ead 100644
--- a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/config_passing_spec.coffee
+++ b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/config_passing_spec.coffee
@@ -18,7 +18,7 @@ describe "Cypress static methods + props", ->
expect(browser.version).to.be.a("string")
expect(browser.majorVersion).to.be.a("string")
expect(browser.path).to.be.a("string")
-
+
switch browser.isHeadless
when true
expect(browser.isHeaded).to.be.false
@@ -34,3 +34,8 @@ describe "Cypress static methods + props", ->
expect(spec.name).to.eq("config_passing_spec.coffee")
expect(spec.relative).to.eq("cypress/integration/config_passing_spec.coffee")
expect(spec.absolute.indexOf("cypress/integration/config_passing_spec.coffee")).to.be.gt(0)
+
+ context ".env", ->
+ ## https://github.com/cypress-io/cypress/issues/4952
+ it "doesn't die on ")
diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/cookies_spec_baseurl.coffee b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/cookies_spec_baseurl.coffee
new file mode 100644
index 00000000000..c5d860fdf66
--- /dev/null
+++ b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/cookies_spec_baseurl.coffee
@@ -0,0 +1,265 @@
+{ _ } = Cypress
+
+expectedDomain = Cypress.env('expectedDomain')
+httpUrl = Cypress.env('httpUrl')
+httpsUrl = Cypress.env('httpsUrl')
+otherUrl = Cypress.env('otherUrl')
+otherHttpsUrl = Cypress.env('otherHttpsUrl')
+
+baseUrlLocation = new Cypress.Location(Cypress.config('baseUrl'))
+
+describe "cookies", ->
+ before ->
+ if Cypress.env('noBaseUrl')
+ return
+
+ ## assert we're running on expected baseurl
+ expect(Cypress.env('baseUrl')).to.be.a('string')
+ .and.have.length.gt(0)
+ .and.eq(Cypress.config('baseUrl'))
+
+ beforeEach ->
+ cy.wrap({foo: "bar"})
+
+ context "with whitelist", ->
+ before ->
+ Cypress.Cookies.defaults({
+ whitelist: "foo1"
+ })
+
+ it "can get all cookies", ->
+ ## setcookie sets on the superdomain by default
+ setCookieDomain = ".#{baseUrlLocation.getSuperDomain()}"
+
+ if ['localhost', '127.0.0.1'].includes(expectedDomain)
+ setCookieDomain = expectedDomain
+
+ cy
+ .clearCookie("foo1")
+ .setCookie("foo", "bar").then (c) ->
+ expect(c.domain).to.eq(setCookieDomain)
+ expect(c.httpOnly).to.eq(false)
+ expect(c.name).to.eq("foo")
+ expect(c.value).to.eq("bar")
+ expect(c.path).to.eq("/")
+ expect(c.secure).to.eq(false)
+ expect(c.expiry).to.be.a("number")
+
+ expect(c).to.have.keys(
+ "domain", "name", "value", "path", "secure", "httpOnly", "expiry"
+ )
+ .getCookies()
+ .should("have.length", 1)
+ .then (cookies) ->
+ c = cookies[0]
+
+ expect(c.domain).to.eq(setCookieDomain)
+ expect(c.httpOnly).to.eq(false)
+ expect(c.name).to.eq("foo")
+ expect(c.value).to.eq("bar")
+ expect(c.path).to.eq("/")
+ expect(c.secure).to.eq(false)
+ expect(c.expiry).to.be.a("number")
+
+ expect(c).to.have.keys(
+ "domain", "name", "value", "path", "secure", "httpOnly", "expiry"
+ )
+ .clearCookies()
+ .should("be.null")
+ .setCookie("wtf", "bob", {httpOnly: true, path: "/foo", secure: true})
+ .getCookie("wtf").then (c) ->
+ expect(c.domain).to.eq(setCookieDomain)
+ expect(c.httpOnly).to.eq(true)
+ expect(c.name).to.eq("wtf")
+ expect(c.value).to.eq("bob")
+ expect(c.path).to.eq("/foo")
+ expect(c.secure).to.eq(true)
+ expect(c.expiry).to.be.a("number")
+
+ expect(c).to.have.keys(
+ "domain", "name", "value", "path", "secure", "httpOnly", "expiry"
+ )
+ .clearCookie("wtf")
+ .should("be.null")
+ .getCookie("doesNotExist")
+ .should("be.null")
+ .document()
+ .its("cookie")
+ .should("be.empty")
+
+ it "resets cookies between tests correctly", ->
+ Cypress.Cookies.preserveOnce("foo2")
+
+ for i in [1..100]
+ do (i) ->
+ cy.setCookie("foo" + i, "#{i}")
+
+ cy.getCookies().should("have.length", 100)
+
+ it "should be only two left now", ->
+ cy.getCookies().should("have.length", 2)
+
+ it "handles undefined cookies", ->
+ cy.visit("/cookieWithNoName")
+
+ context "without whitelist", ->
+ before ->
+ Cypress.Cookies.defaults({
+ whitelist: []
+ })
+
+ it "sends set cookies to path", ->
+ cy
+ .clearCookies()
+ .setCookie("asdf", "jkl")
+ .request("/requestCookies")
+ .its("body").should("deep.eq", { asdf: "jkl" })
+
+ it "handles expired cookies secure", ->
+ cy
+ .visit("/set")
+ .getCookie("shouldExpire").should("exist")
+ .visit("/expirationMaxAge")
+ .getCookie("shouldExpire").should("not.exist")
+ .visit("/set")
+ .getCookie("shouldExpire").should("exist")
+ .visit("/expirationExpires")
+ .getCookie("shouldExpire").should("not.exist")
+
+ it "issue: #224 sets expired cookies between redirects", ->
+ cy
+ .visit("/set")
+ .getCookie("shouldExpire").should("exist")
+ .visit("/expirationRedirect")
+ .url().should("include", "/logout")
+ .getCookie("shouldExpire").should("not.exist")
+
+ .visit("/set")
+ .getCookie("shouldExpire").should("exist")
+ .request("/expirationRedirect")
+ .getCookie("shouldExpire").should("not.exist")
+
+ it "issue: #1321 failing to set or parse cookie", ->
+ ## this is happening because the original cookie was set
+ ## with a secure flag, and then expired without the secure
+ ## flag.
+ cy
+ .visit("#{httpsUrl}/setOneHourFromNowAndSecure")
+ .getCookies().should("have.length", 1)
+
+ ## secure cookies should have been attached
+ .request("#{httpsUrl}/requestCookies")
+ .its("body").should("deep.eq", { shouldExpire: "oneHour" })
+
+ ## secure cookies should not have been attached
+ .request("#{httpUrl}/requestCookies")
+ .its("body").should("deep.eq", {})
+
+ .visit("#{httpsUrl}/expirationMaxAge")
+ .getCookies().should("be.empty")
+
+ it "issue: #2724 does not fail on invalid cookies", ->
+ cy.request("#{httpsUrl}/invalidCookies")
+
+ ## https://github.com/cypress-io/cypress/issues/5453
+ it "can set and clear cookie", ->
+ cy.setCookie('foo', 'bar')
+ cy.clearCookie('foo')
+ cy.getCookie('foo').should('be.null')
+
+ [
+ 'visit',
+ 'request'
+ ].forEach (cmd) ->
+ context "in a cy.#{cmd}", ->
+ [
+ ['HTTP', otherUrl]
+ ['HTTPS', otherHttpsUrl],
+ ].forEach ([protocol, altUrl]) =>
+ it "can set cookies on way too many redirects with #{protocol} intermediary", ->
+ n = 8
+
+ altDomain = (new Cypress.Location(altUrl)).getHostName()
+
+ expectedGetCookiesArray = [
+ {
+ "name": "namefoo8",
+ "value": "valfoo8",
+ "path": "/",
+ "domain": expectedDomain,
+ "secure": false,
+ "httpOnly": false
+ },
+ {
+ "name": "namefoo7",
+ "value": "valfoo7",
+ "path": "/",
+ "domain": altDomain,
+ "secure": false,
+ "httpOnly": false
+ },
+ {
+ "name": "namefoo6",
+ "value": "valfoo6",
+ "path": "/",
+ "domain": expectedDomain,
+ "secure": false,
+ "httpOnly": false
+ },
+ {
+ "name": "namefoo5",
+ "value": "valfoo5",
+ "path": "/",
+ "domain": altDomain,
+ "secure": false,
+ "httpOnly": false
+ },
+ {
+ "name": "namefoo4",
+ "value": "valfoo4",
+ "path": "/",
+ "domain": expectedDomain,
+ "secure": false,
+ "httpOnly": false
+ },
+ {
+ "name": "namefoo3",
+ "value": "valfoo3",
+ "path": "/",
+ "domain": altDomain,
+ "secure": false,
+ "httpOnly": false
+ },
+ {
+ "name": "namefoo2",
+ "value": "valfoo2",
+ "path": "/",
+ "domain": expectedDomain,
+ "secure": false,
+ "httpOnly": false
+ },
+ {
+ "name": "namefoo1",
+ "value": "valfoo1",
+ "path": "/",
+ "domain": altDomain,
+ "secure": false,
+ "httpOnly": false
+ },
+ {
+ "name": "namefoo0",
+ "value": "valfoo0",
+ "path": "/",
+ "domain": expectedDomain,
+ "secure": false,
+ "httpOnly": false
+ }
+ ]
+
+ cy[cmd]("/setCascadingCookies?n=#{n}&a=#{altUrl}&b=#{Cypress.env('baseUrl')}")
+
+ cy.getCookies({ domain: null }).then (cookies) ->
+ ## reverse them so they'll be in the order they were set
+ cookies = _.reverse(_.sortBy(cookies, _.property('name')))
+
+ expect(cookies).to.deep.eq(expectedGetCookiesArray)
diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/cookies_spec.coffee b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/cookies_spec_no_baseurl.coffee
similarity index 82%
rename from packages/server/test/support/fixtures/projects/e2e/cypress/integration/cookies_spec.coffee
rename to packages/server/test/support/fixtures/projects/e2e/cypress/integration/cookies_spec_no_baseurl.coffee
index 332f49fcff7..ebe46a3cd9a 100644
--- a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/cookies_spec.coffee
+++ b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/cookies_spec_no_baseurl.coffee
@@ -1,3 +1,6 @@
+httpUrl = Cypress.env('httpUrl')
+httpsUrl = Cypress.env('httpsUrl')
+
describe "cookies", ->
beforeEach ->
cy.wrap({foo: "bar"})
@@ -75,7 +78,7 @@ describe "cookies", ->
cy.getCookies().should("have.length", 2)
it "handles undefined cookies", ->
- cy.visit("http://localhost:2121/cookieWithNoName")
+ cy.visit("#{httpUrl}/cookieWithNoName")
context "without whitelist", ->
before ->
@@ -87,31 +90,31 @@ describe "cookies", ->
cy
.clearCookies()
.setCookie("asdf", "jkl")
- .request("http://localhost:2121/requestCookies")
+ .request("#{httpUrl}/requestCookies")
.its("body").should("deep.eq", { asdf: "jkl" })
it "handles expired cookies secure", ->
cy
- .visit("http://localhost:2121/set")
+ .visit("#{httpUrl}/set")
.getCookie("shouldExpire").should("exist")
- .visit("http://localhost:2121/expirationMaxAge")
+ .visit("#{httpUrl}/expirationMaxAge")
.getCookie("shouldExpire").should("not.exist")
- .visit("http://localhost:2121/set")
+ .visit("#{httpUrl}/set")
.getCookie("shouldExpire").should("exist")
- .visit("http://localhost:2121/expirationExpires")
+ .visit("#{httpUrl}/expirationExpires")
.getCookie("shouldExpire").should("not.exist")
it "issue: #224 sets expired cookies between redirects", ->
cy
- .visit("http://localhost:2121/set")
+ .visit("#{httpUrl}/set")
.getCookie("shouldExpire").should("exist")
- .visit("http://localhost:2121/expirationRedirect")
+ .visit("#{httpUrl}/expirationRedirect")
.url().should("include", "/logout")
.getCookie("shouldExpire").should("not.exist")
- .visit("http://localhost:2121/set")
+ .visit("#{httpUrl}/set")
.getCookie("shouldExpire").should("exist")
- .request("http://localhost:2121/expirationRedirect")
+ .request("#{httpUrl}/expirationRedirect")
.getCookie("shouldExpire").should("not.exist")
it "issue: #1321 failing to set or parse cookie", ->
@@ -119,19 +122,19 @@ describe "cookies", ->
## with a secure flag, and then expired without the secure
## flag.
cy
- .visit("https://localhost:2323/setOneHourFromNowAndSecure")
+ .visit("#{httpsUrl}/setOneHourFromNowAndSecure")
.getCookies().should("have.length", 1)
## secure cookies should have been attached
- .request("https://localhost:2323/requestCookies")
+ .request("#{httpsUrl}/requestCookies")
.its("body").should("deep.eq", { shouldExpire: "oneHour" })
## secure cookies should not have been attached
- .request("http://localhost:2121/requestCookies")
+ .request("#{httpUrl}/requestCookies")
.its("body").should("deep.eq", {})
- .visit("https://localhost:2323/expirationMaxAge")
+ .visit("#{httpsUrl}/expirationMaxAge")
.getCookies().should("be.empty")
it "issue: #2724 does not fail on invalid cookies", ->
- cy.request('https://localhost:2323/invalidCookies')
+ cy.request("#{httpsUrl}/invalidCookies")
diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/https_passthru_spec.js b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/https_passthru_spec.js
index ae3a83c6480..b7315852265 100644
--- a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/https_passthru_spec.js
+++ b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/https_passthru_spec.js
@@ -23,6 +23,7 @@ describe('https passthru retries', () => {
img.onload = () => {
reject(new Error('onload event fired, but should not have. expected onerror to fire.'))
}
+
img.onerror = resolve
})
})
diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/issue_1669_spec.js b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/issue_1669_spec.js
new file mode 100644
index 00000000000..b037b2613f5
--- /dev/null
+++ b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/issue_1669_spec.js
@@ -0,0 +1,12 @@
+describe('issue-1669 undefined err.stack in beforeEach hook', () => {
+ beforeEach(() => {
+ const errorWithoutStack = new Error('some error, without stack')
+
+ delete errorWithoutStack.stack
+ throw errorWithoutStack
+ })
+
+ it('cy.setCookie should fail with correct error', () => {
+ expect(true).ok
+ })
+})
diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/nested-1/nested-2/nested-3/spec.coffee b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/nested-1/nested-2/nested-3/spec.coffee
new file mode 100644
index 00000000000..faec02dcf9c
--- /dev/null
+++ b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/nested-1/nested-2/nested-3/spec.coffee
@@ -0,0 +1,2 @@
+describe "stdout_specfile_display_spec", ->
+ it "passes", ->
\ No newline at end of file
diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/nested-1/nested-2/nested-3/stdout_specfile.coffee b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/nested-1/nested-2/nested-3/stdout_specfile.coffee
new file mode 100644
index 00000000000..faec02dcf9c
--- /dev/null
+++ b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/nested-1/nested-2/nested-3/stdout_specfile.coffee
@@ -0,0 +1,2 @@
+describe "stdout_specfile_display_spec", ->
+ it "passes", ->
\ No newline at end of file
diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/nested-1/nested-2/nested-3/stdout_specfile_display_spec_with_a_really_long_name_that_never_has_a_line_break_or_new_line.coffee b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/nested-1/nested-2/nested-3/stdout_specfile_display_spec_with_a_really_long_name_that_never_has_a_line_break_or_new_line.coffee
new file mode 100644
index 00000000000..773e906776f
--- /dev/null
+++ b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/nested-1/nested-2/nested-3/stdout_specfile_display_spec_with_a_really_long_name_that_never_has_a_line_break_or_new_line.coffee
@@ -0,0 +1,3 @@
+describe "stdout_specfile_display_spec", ->
+ it "passes", ->
+ cy.screenshot()
\ No newline at end of file
diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/network_error_handling_spec.js b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/network_error_handling_spec.js
index b62f31b30c8..318ddc13ae5 100644
--- a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/network_error_handling_spec.js
+++ b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/network_error_handling_spec.js
@@ -27,8 +27,10 @@ describe('network error handling', function () {
})
.get('input[type=text]')
.type('bar')
+
cy.get('input[type=submit]')
.click()
+
cy.contains('{"foo":"bar"}')
})
})
diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/request_long_http_props_failing_spec.coffee b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/request_long_http_props_failing_spec.coffee
new file mode 100644
index 00000000000..b0901699c05
--- /dev/null
+++ b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/request_long_http_props_failing_spec.coffee
@@ -0,0 +1,4 @@
+describe "when status code isnt 2xx or 3xx", ->
+ it "fails", ->
+ cy.request("http://localhost:2294/myreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallyreallylong
+")
\ No newline at end of file
diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/screenshots_spec.coffee b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/screenshots_spec.coffee
deleted file mode 100644
index 33cbbb16b95..00000000000
--- a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/screenshots_spec.coffee
+++ /dev/null
@@ -1,248 +0,0 @@
-{ devicePixelRatio } = window
-
-describe "taking screenshots", ->
- failureTestRan = false
- onAfterScreenshotResults = []
-
- Cypress.Screenshot.defaults({
- onAfterScreenshot: ($el, results) ->
- onAfterScreenshotResults.push(results)
- })
-
- it "manually generates pngs", ->
- cy
- .visit("http://localhost:3322/color/black")
- .screenshot("black", { capture: "runner" })
- .wait(1500)
- .visit("http://localhost:3322/color/red")
- .screenshot("red", { capture: "runner" })
-
- it "can nest screenshots in folders", ->
- cy
- .visit("http://localhost:3322/color/white")
- .screenshot("foo/bar/baz", { capture: "runner" })
-
- it "generates pngs on failure", ->
- failureTestRan = true
-
- cy
- .visit("http://localhost:3322/color/yellow")
- .wait(1500)
- .then ->
- ## failure 1
- throw new Error("fail whale")
-
- it "calls onAfterScreenshot with results of failed tests", ->
- ## this test will only pass if the previous test ran
- if not failureTestRan
- throw new Error("this test can only pass if the previous test ran")
-
- testFailure = Cypress._.find(onAfterScreenshotResults, {
- testFailure: true
- })
-
- expect(testFailure).to.exist
-
- expect(Cypress._.map(onAfterScreenshotResults, "name")).to.deep.eq([
- "black", "red", "foo/bar/baz", undefined
- ])
-
- it "handles devicePixelRatio correctly on headless electron", ->
- ## this checks to see if the topLeftRight pixel (1, 0) is
- ## currently white. when electron runs offscreen it upscales
- ## images incorrectly on retina screens and the algorithm
- ## blurs this pixel into gray.
- cy
- .screenshot("color-check", { capture: "runner" })
- .task("ensure:pixel:color", {
- devicePixelRatio
- coords: [1, 0]
- color: [255, 255, 255] ## white
- name: "screenshots_spec.coffee/color-check"
- })
- .task("ensure:pixel:color", {
- devicePixelRatio
- coords: [0, 1]
- color: [255, 255, 255] ## white
- name: "screenshots_spec.coffee/color-check"
- })
-
- it "crops app captures to just app size", ->
- cy
- .viewport(600, 400)
- .visit("http://localhost:3322/color/yellow")
- .screenshot("crop-check", { capture: "viewport" })
- .task("check:screenshot:size", {
- name: "screenshots_spec.coffee/crop-check.png",
- width: 600,
- height: 400,
- devicePixelRatio
- })
-
- it "can capture fullPage screenshots", ->
- cy
- .viewport(600, 200)
- .visit("http://localhost:3322/fullPage")
- .screenshot("fullPage", { capture: "fullPage" })
- .task("check:screenshot:size", {
- name: "screenshots_spec.coffee/fullPage.png",
- width: 600,
- height: 500,
- devicePixelRatio
- })
-
- it "accepts subsequent same captures after multiple tries", ->
- cy
- .viewport(600, 200)
- .visit("http://localhost:3322/fullPage-same")
- .screenshot("fullPage-same", { capture: "fullPage" })
- .task("check:screenshot:size", {
- name: "screenshots_spec.coffee/fullPage-same.png",
- width: 600,
- height: 500,
- devicePixelRatio
- })
-
- it "accepts screenshot after multiple tries if somehow app has pixels that match helper pixels", ->
- cy
- .viewport(1280, 720)
- .visit("http://localhost:3322/pathological")
- .screenshot("pathological", { capture: "viewport" })
-
- it "can capture element screenshots", ->
- cy
- .viewport(600, 200)
- .visit("http://localhost:3322/element")
- .get(".element")
- .screenshot("element")
- .task("check:screenshot:size", {
- name: "screenshots_spec.coffee/element.png",
- width: 400,
- height: 300,
- devicePixelRatio
- })
-
- it "retries each screenshot for up to 1500ms", ->
- cy
- .viewport(400, 400)
- .visit("http://localhost:3322/identical")
- .get("div:first").should("have.css", "height", "1300px")
- .screenshot({
- onAfterScreenshot: ($el, results) ->
- expect($el).to.match("div")
-
- { duration } = results
-
- ## there should be 4 screenshots taken
- ## because the height is 1700px.
- ## the 1st will resolve super fast since it
- ## won't match any other screenshots.
- ## the 2nd/3rd will take up to their 1500ms
- ## because they will be identical to the first.
- ## the 4th will also go quickly because it will not
- ## match the 3rd
- first = fourth = 250
- second = third = 1500
- total = first + second + third + fourth
- padding = 2000 ## account for slower machines
-
- expect(duration).to.be.within(total, total + padding)
- })
-
- it "ensures unique paths for non-named screenshots", ->
- cy.screenshot({ capture: "runner" })
- cy.screenshot({ capture: "runner" })
- cy.screenshot({ capture: "runner" })
- cy.readFile("cypress/screenshots/screenshots_spec.coffee/taking screenshots -- ensures unique paths for non-named screenshots.png", "base64")
- cy.readFile("cypress/screenshots/screenshots_spec.coffee/taking screenshots -- ensures unique paths for non-named screenshots (1).png", "base64")
- cy.readFile("cypress/screenshots/screenshots_spec.coffee/taking screenshots -- ensures unique paths for non-named screenshots (2).png", "base64")
-
- it "ensures unique paths when there's a non-named screenshot and a failure", ->
- cy.screenshot({ capture: "viewport" }).then ->
- throw new Error("failing on purpose")
-
- describe "clipping", ->
- it "can clip app screenshots", ->
- cy
- .viewport(600, 200)
- .visit("http://localhost:3322/color/yellow")
- .screenshot("app-clip", {
- capture: "viewport", clip: { x: 10, y: 10, width: 100, height: 50 }
- })
- .task("check:screenshot:size", {
- name: "screenshots_spec.coffee/app-clip.png",
- width: 100,
- height: 50,
- devicePixelRatio
- })
-
- it "can clip runner screenshots", ->
- cy
- .viewport(600, 200)
- .visit("http://localhost:3322/color/yellow")
- .screenshot("runner-clip", {
- capture: "runner", clip: { x: 15, y: 15, width: 120, height: 60 }
- })
- .task("check:screenshot:size", {
- name: "screenshots_spec.coffee/runner-clip.png",
- width: 120,
- height: 60,
- devicePixelRatio
- })
-
- it "can clip fullPage screenshots", ->
- cy
- .viewport(600, 200)
- .visit("http://localhost:3322/fullPage")
- .screenshot("fullPage-clip", {
- capture: "fullPage", clip: { x: 20, y: 20, width: 140, height: 70 }
- })
- .task("check:screenshot:size", {
- name: "screenshots_spec.coffee/fullPage-clip.png",
- width: 140,
- height: 70,
- devicePixelRatio
- })
-
- it "can clip element screenshots", ->
- cy
- .viewport(600, 200)
- .visit("http://localhost:3322/element")
- .get(".element")
- .screenshot("element-clip", {
- clip: { x: 25, y: 25, width: 160, height: 80 }
- })
- .task("check:screenshot:size", {
- name: "screenshots_spec.coffee/element-clip.png",
- width: 160,
- height: 80,
- devicePixelRatio
- })
-
- it "doesn't take a screenshot for a pending test", ->
- @skip()
-
- context "before hooks", ->
- before ->
- ## failure 2
- throw new Error("before hook failing")
-
- it "empty test 1", ->
-
- context "each hooks", ->
- beforeEach ->
- ## failure 3
- throw new Error("before each hook failed")
-
- afterEach ->
- ## failure 3 still (since associated only to a single test)
- throw new Error("after each hook failed")
-
- it "empty test 2", ->
-
- context "really long test title #{Cypress._.repeat('a', 255)}", ->
- it "takes a screenshot", ->
- cy.screenshot()
-
- it "takes another screenshot", ->
- cy.screenshot()
diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/screenshots_spec.js b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/screenshots_spec.js
new file mode 100644
index 00000000000..5a3f34a2948
--- /dev/null
+++ b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/screenshots_spec.js
@@ -0,0 +1,330 @@
+const { devicePixelRatio } = window
+const path = require('path')
+
+describe('taking screenshots', () => {
+ let failureTestRan = false
+ const onAfterScreenshotResults = []
+
+ Cypress.Screenshot.defaults({
+ onAfterScreenshot ($el, results) {
+ onAfterScreenshotResults.push(results)
+ },
+ })
+
+ it('manually generates pngs', () => {
+ cy.visit('http://localhost:3322/color/black')
+ cy.screenshot('black', { capture: 'runner' })
+ cy.wait(1500)
+ cy.visit('http://localhost:3322/color/red')
+ cy.screenshot('red', { capture: 'runner' })
+ })
+
+ it('can nest screenshots in folders', () => {
+ cy.visit('http://localhost:3322/color/white')
+ cy.screenshot('foo/bar/baz', { capture: 'runner' })
+ })
+
+ it('generates pngs on failure', () => {
+ failureTestRan = true
+
+ cy.visit('http://localhost:3322/color/yellow')
+ cy.wait(1500)
+ .then(() => {
+ // failure 1
+ throw new Error('fail whale')
+ })
+ })
+
+ it('calls onAfterScreenshot with results of failed tests', () => {
+ // this test will only pass if the previous test ran
+ if (!failureTestRan) {
+ throw new Error('this test can only pass if the previous test ran')
+ }
+
+ const testFailure = Cypress._.find(onAfterScreenshotResults, {
+ testFailure: true,
+ })
+
+ expect(testFailure).to.exist
+
+ expect(Cypress._.map(onAfterScreenshotResults, 'name')).to.deep.eq([
+ 'black', 'red', 'foo/bar/baz', undefined,
+ ])
+ })
+
+ it('handles devicePixelRatio correctly on headless electron', () => {
+ // this checks to see if the topLeftRight pixel (1, 0) is
+ // currently white. when electron runs offscreen it upscales
+ // images incorrectly on retina screens and the algorithm
+ // blurs this pixel into gray.
+ cy.screenshot('color-check', { capture: 'runner' })
+ cy.task('ensure:pixel:color', {
+ devicePixelRatio,
+ colors: [{
+ coords: [1, 0],
+ color: [255, 255, 255], // white
+ }],
+ name: `${path.basename(__filename)}/color-check`,
+ })
+
+ cy.task('ensure:pixel:color', {
+ devicePixelRatio,
+ colors: [{
+ coords: [1, 0],
+ color: [255, 255, 255], // white
+ }],
+ name: `${path.basename(__filename)}/color-check`,
+ })
+ })
+
+ it('crops app captures to just app size', () => {
+ cy.viewport(600, 400)
+ cy.visit('http://localhost:3322/color/yellow')
+ cy.screenshot('crop-check', { capture: 'viewport' })
+ cy.task('check:screenshot:size', {
+ name: `${path.basename(__filename)}/crop-check.png`,
+ width: 600,
+ height: 400,
+ devicePixelRatio,
+ })
+ })
+
+ it('can capture fullPage screenshots', () => {
+ cy.viewport(600, 200)
+ cy.visit('http://localhost:3322/fullPage')
+ cy.screenshot('fullPage', { capture: 'fullPage' })
+ cy.task('check:screenshot:size', {
+ name: `${path.basename(__filename)}/fullPage.png`,
+ width: 600,
+ height: 500,
+ devicePixelRatio,
+ })
+ })
+
+ it('accepts subsequent same captures after multiple tries', () => {
+ cy.viewport(600, 200)
+ cy.visit('http://localhost:3322/fullPage-same')
+ cy.screenshot('fullPage-same', { capture: 'fullPage' })
+ cy.task('check:screenshot:size', {
+ name: `${path.basename(__filename)}/fullPage-same.png`,
+ width: 600,
+ height: 500,
+ devicePixelRatio,
+ })
+ })
+
+ it('accepts screenshot after multiple tries if somehow app has pixels that match helper pixels', () => {
+ cy.viewport(1280, 720)
+ cy.visit('http://localhost:3322/pathological')
+ cy.screenshot('pathological', { capture: 'viewport' })
+ })
+
+ it('can capture element screenshots', () => {
+ cy.viewport(600, 200)
+ cy.visit('http://localhost:3322/element')
+ cy.get('.element')
+ .screenshot('element')
+
+ cy.task('check:screenshot:size', {
+ name: `${path.basename(__filename)}/element.png`,
+ width: 400,
+ height: 300,
+ devicePixelRatio,
+ })
+ })
+
+ it('retries each screenshot for up to 1500ms', () => {
+ cy.viewport(400, 400)
+ cy.visit('http://localhost:3322/identical')
+ cy.get('div:first').should('have.css', 'height', '1300px')
+ .screenshot({
+ onAfterScreenshot ($el, results) {
+ let fourth; let third
+
+ expect($el).to.match('div')
+
+ const { duration } = results
+
+ // there should be 4 screenshots taken
+ // because the height is 1700px.
+ // the 1st will resolve super fast since it
+ // won't match any other screenshots.
+ // the 2nd/3rd will take up to their 1500ms
+ // because they will be identical to the first.
+ // the 4th will also go quickly because it will not
+ // match the 3rd
+ const first = (fourth = 250)
+ const second = (third = 1500)
+ const total = first + second + third + fourth
+ const padding = 2000 // account for slower machines
+
+ expect(duration).to.be.within(total, total + padding)
+ },
+ })
+ })
+
+ it('ensures unique paths for non-named screenshots', () => {
+ cy.screenshot({ capture: 'runner' })
+ cy.screenshot({ capture: 'runner' })
+ cy.screenshot({ capture: 'runner' })
+ cy.readFile(`cypress/screenshots/${path.basename(__filename)}/taking screenshots -- ensures unique paths for non-named screenshots.png`, 'base64')
+ cy.readFile(`cypress/screenshots/${path.basename(__filename)}/taking screenshots -- ensures unique paths for non-named screenshots (1).png`, 'base64')
+
+ cy.readFile(`cypress/screenshots/${path.basename(__filename)}/taking screenshots -- ensures unique paths for non-named screenshots (2).png`, 'base64')
+ })
+
+ it('ensures unique paths when there\'s a non-named screenshot and a failure', () => {
+ cy.screenshot({ capture: 'viewport' }).then(() => {
+ throw new Error('failing on purpose')
+ })
+ })
+
+ it('properly resizes the AUT iframe', () => {
+ // ensures that the aut iframe is not undersized by making sure the screenshot
+ // is completely white and doesn't have the black background showing
+ cy
+ .visit('http://localhost:3322/color/white')
+ .screenshot('aut-resize')
+ .task('ensure:pixel:color', {
+ devicePixelRatio,
+ colors: [
+ { coords: [5, 5], color: [255, 255, 255] },
+ { coords: [1275, 5], color: [255, 255, 255] },
+ { coords: [5, 715], color: [255, 255, 255] },
+ { coords: [1275, 715], color: [255, 255, 255] },
+ ],
+ name: `${path.basename(__filename)}/aut-resize`,
+ })
+ })
+
+ describe('clipping', () => {
+ it('can clip app screenshots', () => {
+ cy.viewport(600, 200)
+ cy.visit('http://localhost:3322/color/yellow')
+ cy.screenshot('app-clip', {
+ capture: 'viewport', clip: { x: 10, y: 10, width: 100, height: 50 },
+ })
+
+ cy.task('check:screenshot:size', {
+ name: `${path.basename(__filename)}/app-clip.png`,
+ width: 100,
+ height: 50,
+ devicePixelRatio,
+ })
+ })
+
+ it('can clip runner screenshots', () => {
+ cy.viewport(600, 200)
+ cy.visit('http://localhost:3322/color/yellow')
+ cy.screenshot('runner-clip', {
+ capture: 'runner', clip: { x: 15, y: 15, width: 120, height: 60 },
+ })
+
+ cy.task('check:screenshot:size', {
+ name: `${path.basename(__filename)}/runner-clip.png`,
+ width: 120,
+ height: 60,
+ devicePixelRatio,
+ })
+ })
+
+ it('can clip fullPage screenshots', () => {
+ cy.viewport(600, 200)
+ cy.visit('http://localhost:3322/fullPage')
+ cy.screenshot('fullPage-clip', {
+ capture: 'fullPage', clip: { x: 20, y: 20, width: 140, height: 70 },
+ })
+
+ cy.task('check:screenshot:size', {
+ name: `${path.basename(__filename)}/fullPage-clip.png`,
+ width: 140,
+ height: 70,
+ devicePixelRatio,
+ })
+ })
+
+ it('can clip element screenshots', () => {
+ cy.viewport(600, 200)
+ cy.visit('http://localhost:3322/element')
+ cy.get('.element')
+ .screenshot('element-clip', {
+ clip: { x: 25, y: 25, width: 160, height: 80 },
+ })
+
+ cy.task('check:screenshot:size', {
+ name: `${path.basename(__filename)}/element-clip.png`,
+ width: 160,
+ height: 80,
+ devicePixelRatio,
+ })
+ })
+ })
+
+ it('does not take a screenshot for a pending test', function () {
+ this.skip()
+ })
+
+ it('adds padding to element screenshot when specified', () => {
+ cy.visit('http://localhost:3322/element')
+ cy.get('.element')
+ .screenshot('element-padding', {
+ padding: 10,
+ })
+
+ cy.task('check:screenshot:size', {
+ name: `${path.basename(__filename)}/element-padding.png`,
+ width: 420,
+ height: 320,
+ devicePixelRatio,
+ })
+ })
+
+ it('does not add padding to non-element screenshot', () => {
+ cy.viewport(600, 200)
+ cy.visit('http://localhost:3322/color/yellow')
+ cy.screenshot('non-element-padding', {
+ capture: 'viewport',
+ padding: 10,
+ })
+
+ cy.task('check:screenshot:size', {
+ name: `${path.basename(__filename)}/non-element-padding.png`,
+ width: 600,
+ height: 200,
+ devicePixelRatio,
+ })
+ })
+
+ context('before hooks', () => {
+ before(() => {
+ // failure 2
+ throw new Error('before hook failing')
+ })
+
+ it('empty test 1', () => {})
+ })
+
+ context('each hooks', () => {
+ beforeEach(() => {
+ // failure 3
+ throw new Error('before each hook failed')
+ })
+
+ afterEach(() => {
+ // failure 3 still (since associated only to a single test)
+ throw new Error('after each hook failed')
+ })
+
+ it('empty test 2', () => {})
+ })
+
+ context(`really long test title ${Cypress._.repeat('a', 255)}`, () => {
+ it('takes a screenshot', () => {
+ cy.screenshot()
+ })
+
+ it('takes another screenshot', () => {
+ cy.screenshot()
+ })
+ })
+})
diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/subdomain_spec.coffee b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/subdomain_spec.coffee
index 10abee2177a..08d712e80cd 100644
--- a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/subdomain_spec.coffee
+++ b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/subdomain_spec.coffee
@@ -38,7 +38,7 @@ describe "subdomains", ->
occurences = Cypress._.compact(cookie.split("secret-session"))
expect(occurences).to.have.length(1)
- it "corrects sets domain based cookies", ->
+ it "correctly sets domain based cookies", ->
cy
.visit("http://domain.foobar.com:2292")
.getCookies().should("have.length", 1)
diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/video_compression_spec.coffee b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/video_compression_spec.coffee
index ec36529dc8b..37261de6944 100644
--- a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/video_compression_spec.coffee
+++ b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/video_compression_spec.coffee
@@ -1,3 +1,3 @@
-Cypress._.times 40, (i) ->
+Cypress._.times Cypress.env('NUM_TESTS'), (i) ->
it "num: #{i+1} makes some long tests", ->
- cy.wait(500)
+ cy.wait(Cypress.env('MS_PER_TEST'))
diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/visit_response_never_ends_failing_spec.js b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/visit_response_never_ends_failing_spec.js
index 520842f07da..43059d8f4d2 100644
--- a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/visit_response_never_ends_failing_spec.js
+++ b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/visit_response_never_ends_failing_spec.js
@@ -1,5 +1,4 @@
context('response timeouts result in an error', () => {
-
// ESOCKETTIMEDOUT after ~2 seconds
it('handles no response errors on the initial visit', () => {
cy
diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/web_security_spec.coffee b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/web_security_spec.coffee
index 2a154a95104..150a3709eb8 100644
--- a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/web_security_spec.coffee
+++ b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/web_security_spec.coffee
@@ -2,24 +2,24 @@ describe "web security", ->
it "fails when clicking to another origin", ->
cy
- .visit("http://localhost:5566/link")
+ .visit("http://localhost:4466/link")
.get("a").click()
- .url().should("eq", "https://www.foo.com:55665/cross_origin")
+ .url().should("eq", "https://www.foo.com:44665/cross_origin")
cy.contains("h1", "cross origin")
it "fails when submitted a form and being redirected to another origin", ->
cy
- .visit("http://localhost:5566/form")
+ .visit("http://localhost:4466/form")
.get("input").click()
- .url().should("eq", "https://www.foo.com:55665/cross_origin")
+ .url().should("eq", "https://www.foo.com:44665/cross_origin")
cy.contains("h1", "cross origin")
it "fails when using a javascript redirect to another origin", ->
cy
- .visit("http://localhost:5566/javascript")
+ .visit("http://localhost:4466/javascript")
.get("button").click()
- .url().should("eq", "https://www.foo.com:55665/cross_origin")
+ .url().should("eq", "https://www.foo.com:44665/cross_origin")
cy.contains("h1", "cross origin")
diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/xhr_spec.coffee b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/xhr_spec.coffee
index ee5d0923251..de26aa8c320 100644
--- a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/xhr_spec.coffee
+++ b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/xhr_spec.coffee
@@ -76,6 +76,33 @@ describe "xhrs", ->
it "works prior to visit", ->
cy.server()
+ ## https://github.com/cypress-io/cypress/issues/5431
+ it "can stub a 100kb response", (done) ->
+ body = 'X'.repeat(100 * 1024)
+
+ cy.server()
+ cy.route({
+ method: 'POST'
+ url: '/foo'
+ response: {
+ 'bar': body
+ }
+ })
+
+ cy.visit("/index.html")
+ .then (win) ->
+ xhr = new win.XMLHttpRequest
+ xhr.open("POST", "/foo")
+ xhr.send()
+
+ finish = ->
+ expect(xhr.status).to.eq(200)
+ expect(xhr.responseText).to.include(body)
+ done()
+
+ xhr.onload = finish
+ xhr.onerror = finish
+
describe "server with 1 visit", ->
before ->
cy.visit("/xhr.html")
diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/plugins/index.js b/packages/server/test/support/fixtures/projects/e2e/cypress/plugins/index.js
index ed471c9b69e..ea2e7409985 100644
--- a/packages/server/test/support/fixtures/projects/e2e/cypress/plugins/index.js
+++ b/packages/server/test/support/fixtures/projects/e2e/cypress/plugins/index.js
@@ -1,8 +1,9 @@
+require('@packages/ts/register')
+
const _ = require('lodash')
const Jimp = require('jimp')
const path = require('path')
const Promise = require('bluebird')
-
const performance = require('../../../../test/support/helpers/performance')
module.exports = (on) => {
@@ -30,23 +31,25 @@ module.exports = (on) => {
throw new Error(message)
},
- 'ensure:pixel:color' ({ name, coords, color, devicePixelRatio }) {
+ 'ensure:pixel:color' ({ name, colors, devicePixelRatio }) {
const imagePath = path.join(__dirname, '..', 'screenshots', `${name}.png`)
return Jimp.read(imagePath)
.then((image) => {
- let [x, y] = coords
+ _.each(colors, ({ coords, color }) => {
+ let [x, y] = coords
- x = x * devicePixelRatio
- y = y * devicePixelRatio
+ x = x * devicePixelRatio
+ y = y * devicePixelRatio
- const pixels = Jimp.intToRGBA(image.getPixelColor(x, y))
+ const pixels = Jimp.intToRGBA(image.getPixelColor(x, y))
- const { r, g, b } = pixels
+ const { r, g, b } = pixels
- if (!_.isEqual(color, [r, g, b])) {
- throw new Error(`The pixel color at coords: [${x}, ${y}] does not match the expected pixel color. The color was [${r}, ${g}, ${b}] and was expected to be [${color.join(', ')}].`)
- }
+ if (!_.isEqual(color, [r, g, b])) {
+ throw new Error(`The pixel color at coords: [${x}, ${y}] does not match the expected pixel color. The color was [${r}, ${g}, ${b}] and was expected to be [${color.join(', ')}].`)
+ }
+ })
return null
})
@@ -101,6 +104,7 @@ module.exports = (on) => {
'record:fast_visit_spec' ({ percentiles, url, browser, currentRetry }) {
percentiles.forEach(([percent, percentile]) => {
+ // eslint-disable-next-line no-console
console.log(`${percent}%\t of visits to ${url} finished in less than ${percentile}ms`)
})
@@ -110,8 +114,9 @@ module.exports = (on) => {
currentRetry,
...percentiles.reduce((acc, pair) => {
acc[pair[0]] = pair[1]
+
return acc
- }, {})
+ }, {}),
}
return performance.track('fast_visit_spec percentiles', data)
diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/support/foo/bar.js b/packages/server/test/support/fixtures/projects/e2e/cypress/support/foo/bar.js
index be19a41deb6..80446ed767d 100644
--- a/packages/server/test/support/fixtures/projects/e2e/cypress/support/foo/bar.js
+++ b/packages/server/test/support/fixtures/projects/e2e/cypress/support/foo/bar.js
@@ -1 +1,2 @@
-console.log("bar")
\ No newline at end of file
+/* eslint-disable no-console */
+console.log('bar')
diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/support/index.js b/packages/server/test/support/fixtures/projects/e2e/cypress/support/index.js
index e69de29bb2d..69f619dc143 100644
--- a/packages/server/test/support/fixtures/projects/e2e/cypress/support/index.js
+++ b/packages/server/test/support/fixtures/projects/e2e/cypress/support/index.js
@@ -0,0 +1,21 @@
+before(function () {
+ if (Cypress.browser.family === 'chrome') {
+ return Cypress.automation('remote:debugger:protocol', {
+ command: 'Emulation.setDeviceMetricsOverride',
+ params: {
+ width: 1280,
+ height: 720,
+ deviceScaleFactor: 1,
+ mobile: false,
+ screenWidth: 1280,
+ screenHeight: 720,
+ },
+ })
+ .then(() => {
+ // can't tell expect() not to log, so manually throwing here
+ if (window.devicePixelRatio !== 1) {
+ throw new Error('Setting devicePixelRatio to 1 failed')
+ }
+ })
+ }
+})
diff --git a/packages/server/test/support/fixtures/projects/e2e/lib/bar.js b/packages/server/test/support/fixtures/projects/e2e/lib/bar.js
index b6a1e32eb01..fec0747a32d 100644
--- a/packages/server/test/support/fixtures/projects/e2e/lib/bar.js
+++ b/packages/server/test/support/fixtures/projects/e2e/lib/bar.js
@@ -1,3 +1,3 @@
-import baz from "./baz"
+import baz from './baz'
-export default baz
\ No newline at end of file
+export default baz
diff --git a/packages/server/test/support/fixtures/projects/e2e/lib/baz.js b/packages/server/test/support/fixtures/projects/e2e/lib/baz.js
index 1ddc6884657..72796455a56 100644
--- a/packages/server/test/support/fixtures/projects/e2e/lib/baz.js
+++ b/packages/server/test/support/fixtures/projects/e2e/lib/baz.js
@@ -1,3 +1,3 @@
export default () => {
- return "baz"
-}
\ No newline at end of file
+ return 'baz'
+}
diff --git a/packages/server/test/support/fixtures/projects/e2e/reporters/custom.js b/packages/server/test/support/fixtures/projects/e2e/reporters/custom.js
index b8577e357f5..af6bdba91c3 100644
--- a/packages/server/test/support/fixtures/projects/e2e/reporters/custom.js
+++ b/packages/server/test/support/fixtures/projects/e2e/reporters/custom.js
@@ -1,3 +1,4 @@
+/* eslint-disable no-console */
module.exports = function Reporter (runner) {
runner.on('test end', function (test) {
console.log(test.title)
diff --git a/packages/server/test/support/fixtures/projects/ids/cypress/integration/bar.js b/packages/server/test/support/fixtures/projects/ids/cypress/integration/bar.js
index 62dbdba0395..1b64ff8bcc2 100644
--- a/packages/server/test/support/fixtures/projects/ids/cypress/integration/bar.js
+++ b/packages/server/test/support/fixtures/projects/ids/cypress/integration/bar.js
@@ -1,3 +1,3 @@
-context("some context[i9w]", function(){
+context('some context[i9w]', function () {
it('tests[abc]')
-})
\ No newline at end of file
+})
diff --git a/packages/server/test/support/fixtures/projects/ids/cypress/integration/baz.js b/packages/server/test/support/fixtures/projects/ids/cypress/integration/baz.js
index fb5b944d2aa..fd32a7c442d 100644
--- a/packages/server/test/support/fixtures/projects/ids/cypress/integration/baz.js
+++ b/packages/server/test/support/fixtures/projects/ids/cypress/integration/baz.js
@@ -1 +1 @@
-import "./dom.jsx"
\ No newline at end of file
+import './dom.jsx'
diff --git a/packages/server/test/support/fixtures/projects/multiple-task-registrations/cypress/plugins/index.js b/packages/server/test/support/fixtures/projects/multiple-task-registrations/cypress/plugins/index.js
index 2979d06e965..8c66f332da0 100644
--- a/packages/server/test/support/fixtures/projects/multiple-task-registrations/cypress/plugins/index.js
+++ b/packages/server/test/support/fixtures/projects/multiple-task-registrations/cypress/plugins/index.js
@@ -1,11 +1,19 @@
module.exports = (on) => {
on('task', {
- 'one' () { return 'one' },
- 'two' () { return 'two' },
+ 'one' () {
+ return 'one'
+ },
+ 'two' () {
+ return 'two'
+ },
})
on('task', {
- 'two' () { return 'two again' },
- 'three' () { return 'three' },
+ 'two' () {
+ return 'two again'
+ },
+ 'three' () {
+ return 'three'
+ },
})
}
diff --git a/packages/server/test/support/fixtures/projects/no-server/helpers/includes.js b/packages/server/test/support/fixtures/projects/no-server/helpers/includes.js
index 03f300c4fa9..81969706f70 100644
--- a/packages/server/test/support/fixtures/projects/no-server/helpers/includes.js
+++ b/packages/server/test/support/fixtures/projects/no-server/helpers/includes.js
@@ -1,3 +1,3 @@
-beforeEach(function(){
+beforeEach(function () {
-});
\ No newline at end of file
+})
diff --git a/packages/server/test/support/fixtures/projects/no-server/my-tests/test1.js b/packages/server/test/support/fixtures/projects/no-server/my-tests/test1.js
index d995e2b550f..e3a5395eee3 100644
--- a/packages/server/test/support/fixtures/projects/no-server/my-tests/test1.js
+++ b/packages/server/test/support/fixtures/projects/no-server/my-tests/test1.js
@@ -1,3 +1,4 @@
-it("tests without a server", function(){
+/* eslint-disable mocha/no-global-tests */
+it('tests without a server', function () {
-});
\ No newline at end of file
+})
diff --git a/packages/server/test/support/fixtures/projects/plugin-extension/ext/background.js b/packages/server/test/support/fixtures/projects/plugin-extension/ext/background.js
index aa1a7bbebb2..c8bd7e7d930 100644
--- a/packages/server/test/support/fixtures/projects/plugin-extension/ext/background.js
+++ b/packages/server/test/support/fixtures/projects/plugin-extension/ext/background.js
@@ -1,5 +1,3 @@
-/* global document */
-
const el = document.getElementById('extension')
if (el) {
diff --git a/packages/server/test/support/fixtures/projects/plugin-extension/ext/manifest.json b/packages/server/test/support/fixtures/projects/plugin-extension/ext/manifest.json
index f99b8dac309..68a6c0163a9 100644
--- a/packages/server/test/support/fixtures/projects/plugin-extension/ext/manifest.json
+++ b/packages/server/test/support/fixtures/projects/plugin-extension/ext/manifest.json
@@ -3,16 +3,22 @@
"version": "0",
"description": "tests adding user extension into Cypress",
"permissions": [
- "tabs", "webNavigation", ""
+ "tabs",
+ "webNavigation",
+ ""
],
"content_scripts": [
{
- "matches": [""],
+ "matches": [
+ ""
+ ],
"exclude_matches": [
"*://*/__cypress/*",
"*://*/__/*"
],
- "js": ["background.js"],
+ "js": [
+ "background.js"
+ ],
"run_at": "document_end",
"all_frames": true
}
diff --git a/packages/server/test/support/fixtures/projects/plugins-async-error/cypress/plugins/index.js b/packages/server/test/support/fixtures/projects/plugins-async-error/cypress/plugins/index.js
index 30ae3627088..9689fd8b504 100644
--- a/packages/server/test/support/fixtures/projects/plugins-async-error/cypress/plugins/index.js
+++ b/packages/server/test/support/fixtures/projects/plugins-async-error/cypress/plugins/index.js
@@ -1,5 +1,3 @@
-/* global Promise */
-
module.exports = (on) => {
on('file:preprocessor', () => {
return new Promise(() => {
diff --git a/packages/server/test/support/fixtures/projects/remote-debugging-port-removed/cypress.json b/packages/server/test/support/fixtures/projects/remote-debugging-port-removed/cypress.json
new file mode 100644
index 00000000000..0967ef424bc
--- /dev/null
+++ b/packages/server/test/support/fixtures/projects/remote-debugging-port-removed/cypress.json
@@ -0,0 +1 @@
+{}
diff --git a/packages/server/test/support/fixtures/projects/remote-debugging-port-removed/cypress/integration/spec.ts b/packages/server/test/support/fixtures/projects/remote-debugging-port-removed/cypress/integration/spec.ts
new file mode 100644
index 00000000000..60bc2f96021
--- /dev/null
+++ b/packages/server/test/support/fixtures/projects/remote-debugging-port-removed/cypress/integration/spec.ts
@@ -0,0 +1,3 @@
+describe('passes', () => {
+ it('passes', () => {})
+})
diff --git a/packages/server/test/support/fixtures/projects/remote-debugging-port-removed/cypress/plugins.js b/packages/server/test/support/fixtures/projects/remote-debugging-port-removed/cypress/plugins.js
new file mode 100644
index 00000000000..60dd08b421a
--- /dev/null
+++ b/packages/server/test/support/fixtures/projects/remote-debugging-port-removed/cypress/plugins.js
@@ -0,0 +1,14 @@
+const la = require('lazy-ass')
+
+module.exports = (on) => {
+ on('before:browser:launch', (browser = {}, args) => {
+ la(browser.family === 'chrome', 'this test can only be run with a chrome-family browser')
+
+ // remove debugging port so that the browser connection fails
+ const newArgs = args.filter((arg) => !arg.startsWith('--remote-debugging-port='))
+
+ la(newArgs.length === args.length - 1, 'exactly one argument should have been removed')
+
+ return newArgs
+ })
+}
diff --git a/packages/server/test/support/fixtures/projects/system-node/cypress.json b/packages/server/test/support/fixtures/projects/system-node/cypress.json
new file mode 100644
index 00000000000..ceb03f06191
--- /dev/null
+++ b/packages/server/test/support/fixtures/projects/system-node/cypress.json
@@ -0,0 +1,3 @@
+{
+ "nodeVersion": "system"
+}
diff --git a/packages/server/test/support/fixtures/projects/system-node/cypress/integration/spec.js b/packages/server/test/support/fixtures/projects/system-node/cypress/integration/spec.js
new file mode 100644
index 00000000000..e4ee2cdd091
--- /dev/null
+++ b/packages/server/test/support/fixtures/projects/system-node/cypress/integration/spec.js
@@ -0,0 +1,6 @@
+/* eslint-disable mocha/no-global-tests */
+it('has expected resolvedNodePath and resolvedNodeVersion', () => {
+ expect(Cypress.config('nodeVersion')).to.eq('system')
+ expect(Cypress.config('resolvedNodePath')).to.eq(Cypress.env('expectedNodePath'))
+ expect(Cypress.config('resolvedNodeVersion')).to.eq(Cypress.env('expectedNodeVersion'))
+})
diff --git a/packages/server/test/support/fixtures/projects/system-node/cypress/plugins/index.js b/packages/server/test/support/fixtures/projects/system-node/cypress/plugins/index.js
new file mode 100644
index 00000000000..7445d0c7456
--- /dev/null
+++ b/packages/server/test/support/fixtures/projects/system-node/cypress/plugins/index.js
@@ -0,0 +1,5 @@
+module.exports = (onFn, config) => {
+ process.stderr.write('Plugin Loaded\n')
+ process.stderr.write(`Plugin Node version: ${process.versions.node}\n`)
+ process.stderr.write(`Plugin Electron version: ${process.versions.electron}\n`)
+}
diff --git a/packages/server/test/support/fixtures/projects/todos/tests/test1.js b/packages/server/test/support/fixtures/projects/todos/tests/test1.js
index 06281e2ddb9..e5b9547cd65 100644
--- a/packages/server/test/support/fixtures/projects/todos/tests/test1.js
+++ b/packages/server/test/support/fixtures/projects/todos/tests/test1.js
@@ -1,3 +1,4 @@
-it("is truthy", function(){
+/* eslint-disable mocha/no-global-tests */
+it('is truthy', function () {
expect(true).to.be.true
-})
\ No newline at end of file
+})
diff --git a/packages/server/test/support/fixtures/projects/working-preprocessor/cypress/integration/another_spec.js b/packages/server/test/support/fixtures/projects/working-preprocessor/cypress/integration/another_spec.js
index e6764289305..7bf33f67833 100644
--- a/packages/server/test/support/fixtures/projects/working-preprocessor/cypress/integration/another_spec.js
+++ b/packages/server/test/support/fixtures/projects/working-preprocessor/cypress/integration/another_spec.js
@@ -1,5 +1,4 @@
-/* global it, expect */
-
+/* eslint-disable mocha/no-global-tests */
it('is another spec', () => {
expect(false).to.be.false
})
diff --git a/packages/server/test/support/helpers/e2e.coffee b/packages/server/test/support/helpers/e2e.coffee
index a380fd97281..38bf5f82e78 100644
--- a/packages/server/test/support/helpers/e2e.coffee
+++ b/packages/server/test/support/helpers/e2e.coffee
@@ -33,6 +33,8 @@ Promise.config({
e2ePath = Fixtures.projectPath("e2e")
pathUpToProjectName = Fixtures.projectPath("")
+DEFAULT_BROWSERS = ['electron', 'chrome']
+
stackTraceLinesRe = /^(\s+)at\s(.+)/gm
browserNameVersionRe = /(Browser\:\s+)(Custom |)(Electron|Chrome|Canary|Chromium|Firefox)(\s\d+)(\s\(\w+\))?(\s+)/
availableBrowsersRe = /(Available browsers found are: )(.+)/g
@@ -55,14 +57,24 @@ replaceDurationSeconds = (str, p1, p2, p3, p4) ->
replaceDurationFromReporter = (str, p1, p2, p3) ->
## duration='1589'
-
p1 + _.padEnd("X", p2.length, "X") + p3
+replaceNodeVersion = (str, p1, p2, p3) ->
+ return _.padEnd("#{p1}X (/foo/bar/node)", (p1.length + p2.length + p3.length))
+
replaceDurationInTables = (str, p1, p2) ->
## when swapping out the duration, ensure we pad the
## full length of the duration so it doesn't shift content
_.padStart("XX:XX", p1.length + p2.length)
+replaceParenTime = (str, p1) ->
+ ## could be (1 second) or (10 seconds)
+ ## need to account for shortest and longest
+ return _.padStart("(X second)", p1.length)
+
+replaceScreenshotDims = (str, p1) ->
+ return _.padStart("(YxX)", p1.length)
+
replaceUploadingResults = (orig, match..., offset, string) ->
results = match[1].split('\n').map((res) ->
res.replace(/\(\d+\/(\d+)\)/g, '(*/$1)')
@@ -77,31 +89,49 @@ normalizeStdout = (str, options = {}) ->
## remove all of the dynamic parts of stdout
## to normalize against what we expected
str = str
- .split(pathUpToProjectName)
- .join("/foo/bar/.projects")
+ ## /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")
.replace(browserNameVersionRe, replaceBrowserName)
- .replace(/\s\(\d+([ms]|ms)\)/g, "") ## numbers in parenths
- .replace(/(\s+?)(\d+ms|\d+:\d+:?\d+)/g, replaceDurationInTables) ## durations in tables
+ ## numbers in parenths
+ .replace(/\s\(\d+([ms]|ms)\)/g, "")
+ ## 12:35 -> XX:XX
+ .replace(/(\s+?)(\d+ms|\d+:\d+:?\d+)/g, replaceDurationInTables)
.replace(/(coffee|js)-\d{3}/g, "$1-456")
- .replace(/(.+)(\/.+\.mp4)/g, "$1/abc123.mp4") ## replace dynamic video names
- .replace(/(Cypress\:\s+)(\d\.\d\.\d)/g, "$1" + "1.2.3") ## replace Cypress: 2.1.0
+ ## Cypress: 2.1.0 -> Cypress: 1.2.3
+ .replace(/(Cypress\:\s+)(\d\.\d\.\d)/g, "$1" + "1.2.3")
+ ## Node Version: 10.2.3 (Users/jane/node) -> Node Version: X (foo/bar/node)
+ .replace(/(Node Version\:\s+v)(\d+\.\d+\.\d+)( \(.*\)\s+)/g, replaceNodeVersion)
+ ## 15 seconds -> X second
.replace(/(Duration\:\s+)(\d+\sminutes?,\s+)?(\d+\sseconds?)(\s+)/g, replaceDurationSeconds)
- .replace(/(duration\=\')(\d+)(\')/g, replaceDurationFromReporter) ## replace duration='1589'
- .replace(/\((\d+ minutes?,\s+)?\d+ seconds?\)/g, "(X seconds)")
+ ## duration='1589' -> duration='XXXX'
+ .replace(/(duration\=\')(\d+)(\')/g, replaceDurationFromReporter)
+ ## (15 seconds) -> (XX seconds)
+ .replace(/(\((\d+ minutes?,\s+)?\d+ seconds?\))/g, replaceParenTime)
.replace(/\r/g, "")
- .replace(/(Uploading Results.*?\n\n)((.*-.*[\s\S\r]){2,}?)(\n\n)/g, replaceUploadingResults) ## replaces multiple lines of uploading results (since order not guaranteed)
+ ## replaces multiple lines of uploading results (since order not guaranteed)
+ .replace(/(Uploading Results.*?\n\n)((.*-.*[\s\S\r]){2,}?)(\n\n)/g, replaceUploadingResults)
+ ## fix "Require stacks" for CI
+ .replace(/^(\- )(\/.*\/packages\/server\/)(.*)$/gm, "$1$3")
- if options.browser isnt undefined and options.browser isnt 'electron'
- str = str.replace(/\(\d{2,4}x\d{2,4}\)/g, "(YYYYxZZZZ)") ## screenshot dimensions
+ if options.sanitizeScreenshotDimensions
+ ## screenshot dimensions
+ str = str.replace(/(\(\d+x\d+\))/g, replaceScreenshotDims)
return str.split("\n")
.map(replaceStackTraceLines)
.join("\n")
+ensurePort = (port) ->
+ if port is 5566
+ throw new Error('Specified port cannot be on 5566 because it conflicts with --inspect-brk=5566')
+
startServer = (obj) ->
{ onServer, port, https } = obj
+ ensurePort(port)
+
app = express()
if https
@@ -151,8 +181,94 @@ copy = ->
)
)
-module.exports = {
- normalizeStdout
+getMochaItFn = (only, skip, browser, specifiedBrowser) ->
+ ## if we've been told to skip this test
+ ## or if we specified a particular browser and this
+ ## doesn't match the one we're currently trying to run...
+ if skip or (specifiedBrowser and specifiedBrowser isnt browser)
+ ## then skip this test
+ return it.skip
+
+ if only
+ return it.only
+
+ return it
+
+getBrowsers = (generateTestsForDefaultBrowsers, browser, defaultBrowsers) ->
+ ## if we're generating tests for default browsers
+ if generateTestsForDefaultBrowsers
+ ## then return an array of default browsers
+ return defaultBrowsers
+
+ ## but if we haven't been told to generate tests for default browsers
+ ## and weren't provided a specified browser then throw
+ if not browser
+ throw new Error('A browser must be specified when { generateTestsForDefaultBrowsers: false }.')
+
+ ## otherwise return the specified browser
+ return [browser]
+
+localItFn = (title, options = {}) ->
+ options = _
+ .chain(options)
+ .clone()
+ .defaults({
+ only: false,
+ skip: false,
+ browser: process.env.BROWSER
+ generateTestsForDefaultBrowsers: true
+ onRun: (execFn, browser, ctx) ->
+ execFn()
+ })
+ .value()
+
+ { only, skip, browser, generateTestsForDefaultBrowsers, onRun, spec, expectedExitCode } = options
+
+ if not title
+ throw new Error('e2e.it(...) must be passed a title as the first argument')
+
+ ## LOGIC FOR AUTOGENERATING DYNAMIC TESTS
+ ## - if generateTestsForDefaultBrowsers
+ ## - create multiple tests for each default browser
+ ## - if browser is specified in options:
+ ## ...skip the tests for each default browser if that browser
+ ## ...does not match the specified one (used in CI)
+ ## - else only generate a single test with the specified browser
+
+ ## run the tests for all the default browsers, or if a browser
+ ## has been specified, only run it for that
+ specifiedBrowser = browser
+ browsersToTest = getBrowsers(generateTestsForDefaultBrowsers, browser, DEFAULT_BROWSERS)
+
+ browserToTest = (browser) ->
+ mochaItFn = getMochaItFn(only, skip, browser, specifiedBrowser)
+
+ testTitle = "#{title} [#{browser}]"
+
+ mochaItFn testTitle, ->
+ originalTitle = @test.parent.titlePath().concat(title).join(" / ")
+
+ ctx = @
+
+ execFn = (overrides = {}) ->
+ e2e.exec(ctx, _.extend({ originalTitle }, options, overrides, { browser }))
+
+ onRun(execFn, browser, ctx)
+
+ return _.each(browsersToTest, browserToTest)
+
+localItFn.only = (title, options) ->
+ options.only = true
+ localItFn(title, options)
+
+localItFn.skip = (title, options) ->
+ options.skip = true
+ localItFn(title, options)
+
+module.exports = e2e = {
+ normalizeStdout,
+
+ it: localItFn
snapshot: (args...) ->
args = _.compact(args)
@@ -232,9 +348,11 @@ module.exports = {
options: (ctx, options = {}) ->
_.defaults(options, {
- browser: process.env.BROWSER
+ browser: 'electron'
project: e2ePath
timeout: if options.exit is false then 3000000 else 120000
+ originalTitle: null
+ sanitizeScreenshotDimensions: false
})
ctx.timeout(options.timeout)
@@ -262,6 +380,7 @@ module.exports = {
args.push("--spec=#{options.spec}")
if options.port
+ ensurePort(options.port)
args.push("--port=#{options.port}")
if options.headed
@@ -356,7 +475,11 @@ module.exports = {
expect(headless).to.include("(headless)")
str = normalizeStdout(stdout, options)
- snapshot(str)
+
+ if options.originalTitle
+ snapshot(options.originalTitle, str, { allowSharedSnapshot: true })
+ else
+ snapshot(str)
return {
code: code
@@ -369,14 +492,14 @@ module.exports = {
env: _.chain(process.env)
.omit("CYPRESS_DEBUG")
.extend({
- ## FYI: color will already be disabled
+ ## FYI: color will be disabled
## because we are piping the child process
COLUMNS: 100
LINES: 24
})
.defaults({
+ FAKE_CWD_PATH: "/XXX/XXX/XXX"
DEBUG_COLORS: "1"
-
## prevent any Compression progress
## messages from showing up
VIDEO_COMPRESSION_THROTTLE: 120000
diff --git a/packages/server/test/unit/blacklist_spec.coffee b/packages/server/test/unit/blacklist_spec.coffee
deleted file mode 100644
index f0796cea355..00000000000
--- a/packages/server/test/unit/blacklist_spec.coffee
+++ /dev/null
@@ -1,45 +0,0 @@
-require("../spec_helper")
-
-blacklist = require("#{root}lib/util/blacklist")
-
-hosts = [
- "*.google.com"
- "shop.apple.com"
- "localhost:6666"
- "adwords.com"
- "*yahoo.com"
-]
-
-matchesStr = (url, host, val) ->
- m = blacklist.matches(url, host)
- expect(!!m).to.eq(val, "url: '#{url}' did not pass")
-
-matchesArray = (url, val) ->
- m = blacklist.matches(url, hosts)
- expect(!!m).to.eq(val, "url: '#{url}' did not pass")
-
-matchesHost = (url, host) ->
- expect(blacklist.matches(url, hosts)).to.eq(host)
-
-describe "lib/util/blacklist", ->
- it "handles hosts, ports, wildcards", ->
- matchesArray("https://mail.google.com/foo", true)
- matchesArray("https://shop.apple.com/bar", true)
- matchesArray("http://localhost:6666/", true)
- matchesArray("https://localhost:6666/", true)
- matchesArray("https://adwords.com:443/", true)
- matchesArray("http://adwords.com:80/quux", true)
- matchesArray("https://yahoo.com:443/asdf", true)
- matchesArray("http://mail.yahoo.com:443/asdf", true)
-
- matchesArray("https://buy.adwords.com:443/", false)
- matchesArray("http://localhost:66667", false)
- matchesArray("http://mac.apple.com/", false)
-
- matchesStr("https://localhost:6666/foo", "localhost:6666", true)
- matchesStr("https://localhost:6666/foo", "localhost:5555", false)
-
- it "returns the matched host", ->
- matchesHost("https://shop.apple.com:443/foo", "shop.apple.com")
- matchesHost("http://mail.yahoo.com:80/bar", "*yahoo.com")
- matchesHost("https://localhost:6666/bar", "localhost:6666")
diff --git a/packages/server/test/unit/blacklist_spec.js b/packages/server/test/unit/blacklist_spec.js
new file mode 100644
index 00000000000..5c40b2dddd9
--- /dev/null
+++ b/packages/server/test/unit/blacklist_spec.js
@@ -0,0 +1,53 @@
+require('../spec_helper')
+
+const blacklist = require(`${root}lib/util/blacklist`)
+
+const hosts = [
+ '*.google.com',
+ 'shop.apple.com',
+ 'localhost:6666',
+ 'adwords.com',
+ '*yahoo.com',
+]
+
+const matchesStr = function (url, host, val) {
+ const m = blacklist.matches(url, host)
+
+ expect(!!m).to.eq(val, `url: '${url}' did not pass`)
+}
+
+const matchesArray = function (url, val) {
+ const m = blacklist.matches(url, hosts)
+
+ expect(!!m).to.eq(val, `url: '${url}' did not pass`)
+}
+
+const matchesHost = (url, host) => {
+ expect(blacklist.matches(url, hosts)).to.eq(host)
+}
+
+describe('lib/util/blacklist', () => {
+ it('handles hosts, ports, wildcards', () => {
+ matchesArray('https://mail.google.com/foo', true)
+ matchesArray('https://shop.apple.com/bar', true)
+ matchesArray('http://localhost:6666/', true)
+ matchesArray('https://localhost:6666/', true)
+ matchesArray('https://adwords.com:443/', true)
+ matchesArray('http://adwords.com:80/quux', true)
+ matchesArray('https://yahoo.com:443/asdf', true)
+ matchesArray('http://mail.yahoo.com:443/asdf', true)
+
+ matchesArray('https://buy.adwords.com:443/', false)
+ matchesArray('http://localhost:66667', false)
+ matchesArray('http://mac.apple.com/', false)
+
+ matchesStr('https://localhost:6666/foo', 'localhost:6666', true)
+ matchesStr('https://localhost:6666/foo', 'localhost:5555', false)
+ })
+
+ it('returns the matched host', () => {
+ matchesHost('https://shop.apple.com:443/foo', 'shop.apple.com')
+ matchesHost('http://mail.yahoo.com:80/bar', '*yahoo.com')
+ matchesHost('https://localhost:6666/bar', 'localhost:6666')
+ })
+})
diff --git a/packages/server/test/unit/browsers/browsers_spec.coffee b/packages/server/test/unit/browsers/browsers_spec.coffee
index 308bf12bbc8..295a2e07e84 100644
--- a/packages/server/test/unit/browsers/browsers_spec.coffee
+++ b/packages/server/test/unit/browsers/browsers_spec.coffee
@@ -6,6 +6,12 @@ browsers = require("#{root}../lib/browsers")
utils = require("#{root}../lib/browsers/utils")
describe "lib/browsers/index", ->
+ context ".isBrowserFamily", ->
+ it "allows only known browsers", ->
+ expect(browsers.isBrowserFamily("chrome")).to.be.true
+ expect(browsers.isBrowserFamily("electron")).to.be.true
+ expect(browsers.isBrowserFamily("my-favorite-browser")).to.be.false
+
context ".ensureAndGetByNameOrPath", ->
it "returns browser by name", ->
sinon.stub(utils, "getBrowsers").resolves([
@@ -32,11 +38,17 @@ describe "lib/browsers/index", ->
family: 'foo-bad'
}, {
browsers: []
- }).then ->
+ })
+ .then (e) ->
+ console.error(e)
throw new Error("should've failed")
- .catch (err) ->
- expect(err.type).to.eq("BROWSER_NOT_FOUND_BY_NAME")
- expect(err.message).to.contain("'foo-bad-bang' was not found on your system")
+ , (err) ->
+ # by being explicit with assertions, if something is unexpected
+ # we will get good error message that includes the "err" object
+ expect(err).to.have.property("type").to.eq("BROWSER_NOT_FOUND_BY_NAME")
+ expect(err).to.have.property("message").to.contain("'foo-bad-bang' was not found on your system")
+
+ # Ooo, browser clean up tests are disabled?!!
# it "calls onBrowserClose callback on close", ->
# onBrowserClose = sinon.stub()
diff --git a/packages/server/test/unit/browsers/cdp_automation_spec.coffee b/packages/server/test/unit/browsers/cdp_automation_spec.coffee
new file mode 100644
index 00000000000..afd87a23520
--- /dev/null
+++ b/packages/server/test/unit/browsers/cdp_automation_spec.coffee
@@ -0,0 +1,127 @@
+require("../../spec_helper")
+{ CdpAutomation } = require("#{root}../lib/browsers/cdp_automation")
+
+context "lib/browsers/cdp_automation", ->
+ context ".CdpAutomation", ->
+ beforeEach ->
+ @sendDebuggerCommand = sinon.stub()
+
+ @automation = CdpAutomation(@sendDebuggerCommand)
+
+ @sendDebuggerCommand
+ .throws()
+ .withArgs('Browser.getVersion')
+ .resolves()
+
+ @onRequest = @automation.onRequest
+
+ describe "get:cookies", ->
+ beforeEach ->
+ @sendDebuggerCommand.withArgs('Network.getAllCookies')
+ .resolves({
+ cookies: [
+ {name: "foo", value: "f", path: "/", domain: "localhost", secure: true, httpOnly: true, expires: 123}
+ {name: "bar", value: "b", path: "/", domain: "localhost", secure: false, httpOnly: false, expires: 456}
+ ]
+ })
+
+ it "returns all cookies", ->
+ @onRequest('get:cookies', { domain: "localhost" })
+ .then (resp) ->
+ expect(resp).to.deep.eq([
+ {name: "foo", value: "f", path: "/", domain: "localhost", secure: true, httpOnly: true, expirationDate: 123}
+ {name: "bar", value: "b", path: "/", domain: "localhost", secure: false, httpOnly: false, expirationDate: 456}
+ ])
+
+ describe "get:cookie", ->
+ beforeEach ->
+ @sendDebuggerCommand.withArgs('Network.getAllCookies')
+ .resolves({
+ cookies: [
+ {name: "session", value: "key", path: "/login", domain: "google.com", secure: true, httpOnly: true, expires: 123}
+ ]
+ })
+
+ it "returns a specific cookie by name", ->
+ @onRequest('get:cookie', {domain: "google.com", name: "session"})
+ .then (resp) ->
+ expect(resp).to.deep.eq({name: "session", value: "key", path: "/login", domain: "google.com", secure: true, httpOnly: true, expirationDate: 123})
+
+ it "returns null when no cookie by name is found", ->
+ @onRequest('get:cookie', {domain: "google.com", name: "doesNotExist"})
+ .then (resp) ->
+ expect(resp).to.be.null
+
+ describe "set:cookie", ->
+ beforeEach ->
+ @sendDebuggerCommand.withArgs('Network.setCookie', {domain: ".google.com", name: "session", value: "key", path: "/", expires: undefined})
+ .resolves({ success: true })
+ .withArgs('Network.setCookie', {domain: "foo", path: "/bar", name: "", value: "", expires: undefined})
+ .rejects(new Error("some error"))
+ .withArgs('Network.getAllCookies')
+ .resolves({
+ cookies: [
+ {name: "session", value: "key", path: "/", domain: ".google.com", secure: false, httpOnly: false}
+ ]
+ })
+
+ it "resolves with the cookie props", ->
+ @onRequest('set:cookie', {domain: "google.com", name: "session", value: "key", path: "/"})
+ .then (resp) ->
+ expect(resp).to.deep.eq({domain: ".google.com", expirationDate: undefined, httpOnly: false, name: "session", value: "key", path: "/", secure: false})
+
+ it "rejects with error", ->
+ @onRequest('set:cookie', {domain: "foo", path: "/bar"})
+ .then ->
+ throw new Error("should have failed")
+ .catch (err) ->
+ expect(err.message).to.eq("some error")
+
+ describe "clear:cookie", ->
+ beforeEach ->
+ @sendDebuggerCommand.withArgs('Network.getAllCookies')
+ .resolves({
+ cookies: [
+ {name: "session", value: "key", path: "/", domain: "google.com", secure: true, httpOnly: true, expires: 123}
+ {name: "shouldThrow", value: "key", path: "/assets", domain: "cdn.github.com", secure: false, httpOnly: true, expires: 123}
+ ]
+ })
+
+ @sendDebuggerCommand.withArgs('Network.deleteCookies', { domain: "cdn.github.com", name: "shouldThrow" })
+ .rejects(new Error("some error"))
+ .withArgs('Network.deleteCookies')
+ .resolves()
+
+ it "resolves single removed cookie", ->
+ @onRequest('clear:cookie', {domain: "google.com", name: "session"})
+ .then (resp) ->
+ expect(resp).to.deep.eq(
+ {name: "session", value: "key", path: "/", domain: "google.com", secure: true, httpOnly: true, expirationDate: 123}
+ )
+
+ it "returns null when no cookie by name is found", ->
+ @onRequest('clear:cookie', {domain: "google.com", name: "doesNotExist"})
+ .then (resp) ->
+ expect(resp).to.be.null
+
+ it "rejects with error", ->
+ @onRequest('clear:cookie', {domain: "cdn.github.com", name: "shouldThrow"})
+ .then ->
+ throw new Error("should have failed")
+ .catch (err) ->
+ expect(err.message).to.eq("some error")
+
+ describe "take:screenshot", ->
+ it "resolves with base64 data URL", ->
+ @sendDebuggerCommand.withArgs('Browser.getVersion').resolves({ protocolVersion: '1.3' })
+ @sendDebuggerCommand.withArgs('Page.captureScreenshot').resolves({ data: 'foo' })
+
+ expect(@onRequest('take:screenshot'))
+ .to.eventually.equal('data:image/png;base64,foo')
+
+ it "rejects nicely if Page.captureScreenshot fails", ->
+ @sendDebuggerCommand.withArgs('Browser.getVersion').resolves({ protocolVersion: '1.3' })
+ @sendDebuggerCommand.withArgs('Page.captureScreenshot').rejects()
+
+ expect(@onRequest('take:screenshot'))
+ .to.be.rejectedWith('The browser responded with an error when Cypress attempted to take a screenshot.')
diff --git a/packages/server/test/unit/browsers/chrome_spec.coffee b/packages/server/test/unit/browsers/chrome_spec.coffee
index 233a95eb6f6..92258cb44e7 100644
--- a/packages/server/test/unit/browsers/chrome_spec.coffee
+++ b/packages/server/test/unit/browsers/chrome_spec.coffee
@@ -12,19 +12,49 @@ describe "lib/browsers/chrome", ->
context "#open", ->
beforeEach ->
@args = []
+ # mock CRI client during testing
+ @criClient = {
+ ensureMinimumProtocolVersion: sinon.stub().resolves()
+ send: sinon.stub().resolves()
+ Page: {
+ screencastFrame: sinon.stub().returns()
+ },
+ close: sinon.stub().resolves()
+ }
+ @automation = {
+ use: sinon.stub().returns()
+ }
+ # mock launched browser child process object
+ @launchedBrowser = {
+ kill: sinon.stub().returns()
+ }
sinon.stub(chrome, "_getArgs").returns(@args)
sinon.stub(chrome, "_writeExtension").resolves("/path/to/ext")
+ sinon.stub(chrome, "_connectToChromeRemoteInterface").resolves(@criClient)
sinon.stub(plugins, "has")
sinon.stub(plugins, "execute")
- sinon.stub(utils, "launch")
+ sinon.stub(utils, "launch").resolves(@launchedBrowser)
sinon.stub(utils, "getProfileDir").returns("/profile/dir")
sinon.stub(utils, "ensureCleanCache").resolves("/profile/dir/CypressCache")
+ # port for Chrome remote interface communication
+ sinon.stub(utils, "getPort").resolves(50505)
+
+ afterEach ->
+ expect(@criClient.ensureMinimumProtocolVersion).to.be.calledOnce
+
+ it "focuses on the page and calls CRI Page.visit", ->
+ chrome.open("chrome", "http://", {}, @automation)
+ .then =>
+ expect(utils.getPort).to.have.been.calledOnce # to get remote interface port
+ expect(@criClient.send).to.have.been.calledTwice
+ expect(@criClient.send).to.have.been.calledWith("Page.bringToFront")
+ expect(@criClient.send).to.have.been.calledWith("Page.navigate")
it "is noop without before:browser:launch", ->
plugins.has.returns(false)
- chrome.open("chrome", "http://", {}, {})
+ chrome.open("chrome", "http://", {}, @automation)
.then ->
expect(plugins.execute).not.to.be.called
@@ -32,9 +62,11 @@ describe "lib/browsers/chrome", ->
plugins.has.returns(true)
plugins.execute.resolves(null)
- chrome.open("chrome", "http://", {}, {})
+ chrome.open("chrome", "http://", {}, @automation)
.then =>
- expect(utils.launch).to.be.calledWith("chrome", "http://", @args)
+ # to initialize remote interface client and prepare for true tests
+ # we load the browser with blank page first
+ expect(utils.launch).to.be.calledWith("chrome", "about:blank", @args)
it "normalizes --load-extension if provided in plugin", ->
plugins.has.returns(true)
@@ -47,7 +79,7 @@ describe "lib/browsers/chrome", ->
## this should get obliterated
@args.push("--something=else")
- chrome.open("chrome", "http://", {}, {})
+ chrome.open("chrome", "http://", {}, @automation)
.then =>
args = utils.launch.firstCall.args[2]
@@ -69,7 +101,7 @@ describe "lib/browsers/chrome", ->
## this should get obliterated
@args.push("--something=else")
- chrome.open("chrome", "http://", {}, {})
+ chrome.open("chrome", "http://", {}, @automation)
.then =>
args = utils.launch.firstCall.args[2]
@@ -89,7 +121,7 @@ describe "lib/browsers/chrome", ->
})
sinon.stub(fs, "writeJson")
- chrome.open("chrome", "http://", {}, {})
+ chrome.open("chrome", "http://", {}, @automation)
.then ->
expect(fs.writeJson).to.be.calledWith("/profile/dir/Default/Preferences", {
profile: {
@@ -98,6 +130,23 @@ describe "lib/browsers/chrome", ->
}
})
+ it "calls cri client close on kill", ->
+ ## need a reference here since the stub will be monkey-patched
+ kill = @launchedBrowser.kill
+
+ chrome.open("chrome", "http://", {}, @automation)
+ .then =>
+ expect(@launchedBrowser.kill).to.be.a("function")
+ @launchedBrowser.kill()
+ .then =>
+ expect(@criClient.close).to.be.calledOnce
+ expect(kill).to.be.calledOnce
+
+ it "rejects if CDP version check fails", ->
+ @criClient.ensureMinimumProtocolVersion.rejects()
+
+ expect(chrome.open("chrome", "http://", {}, @automation)).to.be.rejectedWith('Cypress requires at least Chrome 64.')
+
context "#_getArgs", ->
it "disables gpu when linux", ->
sinon.stub(os, "platform").returns("linux")
@@ -178,4 +227,3 @@ describe "lib/browsers/chrome", ->
chromeVersionHasLoopback("71", false)
chromeVersionHasLoopback("72", true)
chromeVersionHasLoopback("73", true)
-
diff --git a/packages/server/test/unit/browsers/cri-client_spec.ts b/packages/server/test/unit/browsers/cri-client_spec.ts
new file mode 100644
index 00000000000..44ddabbfe29
--- /dev/null
+++ b/packages/server/test/unit/browsers/cri-client_spec.ts
@@ -0,0 +1,88 @@
+import Bluebird from 'bluebird'
+import { create } from '../../../lib/browsers/cri-client'
+
+const { expect, proxyquire, sinon } = require('../../spec_helper')
+
+const DEBUGGER_URL = 'http://foo'
+
+describe('lib/browsers/cri-client', function () {
+ let criClient: {
+ create: typeof create
+ }
+ let send: sinon.SinonStub
+ let criImport: sinon.SinonStub
+
+ function getClient () {
+ return criClient.create(DEBUGGER_URL)
+ }
+
+ beforeEach(function () {
+ sinon.stub(Bluebird, 'promisify').returnsArg(0)
+
+ send = sinon.stub()
+
+ criImport = sinon.stub()
+ .withArgs({
+ target: DEBUGGER_URL,
+ local: true,
+ })
+ .resolves({ send })
+
+ criClient = proxyquire('../lib/browsers/cri-client', {
+ 'chrome-remote-interface': criImport,
+ })
+ })
+
+ context('.create', function () {
+ it('returns an instance of the CRI client', async function () {
+ const client = await getClient()
+
+ expect(client.send).to.be.instanceOf(Function)
+ })
+
+ context('#send', function () {
+ it('calls cri.send with command and data', async function () {
+ send.resolves()
+ const client = await getClient()
+
+ client.send('Browser.getVersion', { baz: 'quux' })
+ expect(send).to.be.calledWith('Browser.getVersion', { baz: 'quux' })
+ })
+ })
+
+ context('#ensureMinimumProtocolVersion', function () {
+ function withProtocolVersion (actual, test) {
+ if (actual) {
+ send.withArgs('Browser.getVersion')
+ .resolves({ protocolVersion: actual })
+ }
+
+ return getClient()
+ .then((client) => {
+ return client.ensureMinimumProtocolVersion(test)
+ })
+ }
+
+ it('resolves if protocolVersion = current', function () {
+ return expect(withProtocolVersion('1.3', '1.3')).to.be.fulfilled
+ })
+
+ it('resolves if protocolVersion > current', function () {
+ return expect(withProtocolVersion('1.4', '1.3')).to.be.fulfilled
+ })
+
+ it('rejects if Browser.getVersion not supported yet', function () {
+ send.withArgs('Browser.getVersion')
+ .rejects()
+
+ return expect(withProtocolVersion(null, '1.3')).to.be
+ .rejectedWith('A minimum CDP version of v1.3 is required, but the current browser has an older version.')
+ })
+
+ it('rejects if protocolVersion < current', function () {
+ return expect(withProtocolVersion('1.2', '1.3')).to.be
+ .rejectedWith('A minimum CDP version of v1.3 is required, but the current browser has v1.2.')
+ })
+ })
+ })
+})
diff --git a/packages/server/test/unit/browsers/electron_spec.coffee b/packages/server/test/unit/browsers/electron_spec.coffee
index 5fcd43cffd4..29745109db0 100644
--- a/packages/server/test/unit/browsers/electron_spec.coffee
+++ b/packages/server/test/unit/browsers/electron_spec.coffee
@@ -34,6 +34,11 @@ describe "lib/browsers/electron", ->
remove: sinon.stub()
}
}
+ "debugger": {
+ attach: sinon.stub().returns()
+ sendCommand: sinon.stub().resolves()
+ on: sinon.stub().returns()
+ }
}
})
@@ -343,7 +348,7 @@ describe "lib/browsers/electron", ->
it "sets proxy rules for webContents", ->
webContents = {
session: {
- setProxy: sinon.stub().yieldsAsync()
+ setProxy: sinon.stub().callsArg(1)
}
}
diff --git a/packages/server/test/unit/browsers/protocol_spec.ts b/packages/server/test/unit/browsers/protocol_spec.ts
new file mode 100644
index 00000000000..03e21b370eb
--- /dev/null
+++ b/packages/server/test/unit/browsers/protocol_spec.ts
@@ -0,0 +1,88 @@
+import '../../spec_helper'
+import _ from 'lodash'
+import 'chai-as-promised' // for the types!
+import chalk from 'chalk'
+import { connect } from '@packages/network'
+import CRI from 'chrome-remote-interface'
+import { expect } from 'chai'
+import humanInterval from 'human-interval'
+import protocol from '../../../lib/browsers/protocol'
+import sinon from 'sinon'
+import snapshot from 'snap-shot-it'
+import stripAnsi from 'strip-ansi'
+import { stripIndents } from 'common-tags'
+
+describe('lib/browsers/protocol', function () {
+ context('._getDelayMsForRetry', function () {
+ it('retries as expected for up to 20 seconds', function () {
+ const log = sinon.spy(console, 'log')
+
+ let delays = []
+ let delay: number
+ let i = 0
+
+ while ((delay = protocol._getDelayMsForRetry(i))) {
+ delays.push(delay)
+ i++
+ }
+
+ expect(_.sum(delays)).to.eq(humanInterval('20 seconds'))
+
+ log.getCalls().forEach((log, i) => {
+ const line = stripAnsi(log.args[0])
+
+ expect(line).to.include(`Failed to connect to Chrome, retrying in 1 second (attempt ${i + 18}/32)`)
+ })
+
+ snapshot(delays)
+ })
+ })
+
+ context('.getWsTargetFor', function () {
+ it('rejects if CDP connection fails', function () {
+ const innerErr = new Error('cdp connection failure')
+
+ sinon.stub(connect, 'createRetryingSocket').callsArgWith(1, innerErr)
+ const p = protocol.getWsTargetFor(12345)
+
+ const expectedError = stripIndents`
+ Cypress failed to make a connection to the Chrome DevTools Protocol after retrying for 20 seconds.
+
+ This usually indicates there was a problem opening the Chrome browser.
+
+ The CDP port requested was ${chalk.yellow('12345')}.
+
+ Error details:
+ `
+
+ return expect(p).to.eventually.be.rejected
+ .and.property('message').include(expectedError)
+ .and.include(innerErr.message)
+ })
+
+ it('returns the debugger URL of the first about:blank tab', async function () {
+ const targets = [
+ {
+ type: 'page',
+ url: 'chrome://newtab',
+ webSocketDebuggerUrl: 'foo',
+ },
+ {
+ type: 'page',
+ url: 'about:blank',
+ webSocketDebuggerUrl: 'bar',
+ },
+ ]
+
+ const end = sinon.stub()
+
+ sinon.stub(CRI, 'List').withArgs({ port: 12345 }).resolves(targets)
+ sinon.stub(connect, 'createRetryingSocket').callsArgWith(1, null, { end })
+
+ const p = protocol.getWsTargetFor(12345)
+
+ await expect(p).to.eventually.equal('bar')
+ expect(end).to.be.calledOnce
+ })
+ })
+})
diff --git a/packages/server/test/unit/buffers_spec.coffee b/packages/server/test/unit/buffers_spec.coffee
deleted file mode 100644
index dad7b1c44c4..00000000000
--- a/packages/server/test/unit/buffers_spec.coffee
+++ /dev/null
@@ -1,66 +0,0 @@
-require("../spec_helper")
-
-buffers = require("#{root}lib/util/buffers")
-
-describe "lib/util/buffers", ->
- beforeEach ->
- buffers.reset()
-
- afterEach ->
- buffers.reset()
-
- context "#get", ->
- it "returns buffer by url", ->
- obj = {url: "foo"}
-
- buffers.set(obj)
-
- buffer = buffers.get("foo")
-
- expect(buffer).to.deep.eq(obj)
-
- it "falls back to setting the port when buffer could not be found", ->
- obj = {url: "https://www.google.com/"}
-
- buffers.set(obj)
-
- buffer = buffers.get("https://www.google.com:443/")
-
- expect(buffer).to.deep.eq(obj)
-
- context "#getByOriginalUrl", ->
- it "returns buffer by originalUrl", ->
- obj = {originalUrl: "foo"}
-
- buffers.set(obj)
-
- buffer = buffers.getByOriginalUrl("foo")
-
- expect(buffer).to.deep.eq(obj)
-
- context "#take", ->
- it "removes the found buffer", ->
- obj = {url: "https://www.google.com/"}
-
- buffers.set(obj)
-
- expect(buffers.all()).to.have.length(1)
-
- buffer = buffers.take("https://www.google.com:443/")
-
- expect(buffer).to.deep.eq(obj)
-
- expect(buffers.all()).to.have.length(0)
-
- it "does not remove anything when not found", ->
- obj = {url: "https://www.google.com/"}
-
- buffers.set(obj)
-
- expect(buffers.all()).to.have.length(1)
-
- buffer = buffers.take("asdf")
-
- expect(buffer).to.be.undefined
-
- expect(buffers.all()).to.have.length(1)
diff --git a/packages/server/test/unit/buffers_spec.js b/packages/server/test/unit/buffers_spec.js
new file mode 100644
index 00000000000..29885f7c153
--- /dev/null
+++ b/packages/server/test/unit/buffers_spec.js
@@ -0,0 +1,77 @@
+require('../spec_helper')
+
+const buffers = require(`${root}lib/util/buffers`)
+
+describe('lib/util/buffers', () => {
+ beforeEach(() => {
+ buffers.reset()
+ })
+
+ afterEach(() => {
+ buffers.reset()
+ })
+
+ context('#get', () => {
+ it('returns buffer by url', () => {
+ const obj = { url: 'foo' }
+
+ buffers.set(obj)
+
+ const buffer = buffers.get('foo')
+
+ expect(buffer).to.deep.eq(obj)
+ })
+
+ it('falls back to setting the port when buffer could not be found', () => {
+ const obj = { url: 'https://www.google.com/' }
+
+ buffers.set(obj)
+
+ const buffer = buffers.get('https://www.google.com:443/')
+
+ expect(buffer).to.deep.eq(obj)
+ })
+ })
+
+ context('#getByOriginalUrl', () => {
+ it('returns buffer by originalUrl', () => {
+ const obj = { originalUrl: 'foo' }
+
+ buffers.set(obj)
+
+ const buffer = buffers.getByOriginalUrl('foo')
+
+ expect(buffer).to.deep.eq(obj)
+ })
+ })
+
+ context('#take', () => {
+ it('removes the found buffer', () => {
+ const obj = { url: 'https://www.google.com/' }
+
+ buffers.set(obj)
+
+ expect(buffers.all()).to.have.length(1)
+
+ const buffer = buffers.take('https://www.google.com:443/')
+
+ expect(buffer).to.deep.eq(obj)
+
+ expect(buffers.all()).to.have.length(0)
+ })
+
+ it('does not remove anything when not found', () => {
+ const obj = { url: 'https://www.google.com/' }
+
+ buffers.set(obj)
+
+ expect(buffers.all()).to.have.length(1)
+
+ const buffer = buffers.take('asdf')
+
+ expect(buffer).to.be.undefined
+
+ expect(buffers.all()).to.have.length(1)
+ })
+ })
+})
diff --git a/packages/server/test/unit/config_spec.coffee b/packages/server/test/unit/config_spec.coffee
index 8880304be78..384a88ef370 100644
--- a/packages/server/test/unit/config_spec.coffee
+++ b/packages/server/test/unit/config_spec.coffee
@@ -4,7 +4,9 @@ _ = require("lodash")
path = require("path")
R = require("ramda")
config = require("#{root}lib/config")
+errors = require("#{root}lib/errors")
configUtil = require("#{root}lib/util/config")
+findSystemNode = require("#{root}lib/util/find_system_node")
scaffold = require("#{root}lib/scaffold")
settings = require("#{root}lib/util/settings")
@@ -17,6 +19,27 @@ describe "lib/config", ->
afterEach ->
process.env = @env
+ context "environment name check", ->
+ it "throws an error for unknown CYPRESS_ENV", ->
+ sinon.stub(errors, "throw").withArgs("INVALID_CYPRESS_ENV", "foo-bar")
+ process.env.CYPRESS_ENV = "foo-bar"
+ cfg = {
+ projectRoot: "/foo/bar/"
+ }
+ options = {}
+ config.mergeDefaults(cfg, options)
+ expect(errors.throw).have.been.calledOnce
+
+ it "allows known CYPRESS_ENV", ->
+ sinon.stub(errors, "throw")
+ process.env.CYPRESS_ENV = "test"
+ cfg = {
+ projectRoot: "/foo/bar/"
+ }
+ options = {}
+ config.mergeDefaults(cfg, options)
+ expect(errors.throw).not.to.be.called
+
context ".get", ->
beforeEach ->
@projectRoot = "/_test-output/path/to/project"
@@ -202,11 +225,11 @@ describe "lib/config", ->
it "fails if not a string or array", ->
@setup({ignoreTestFiles: 5})
- @expectValidationFails("be a string or an array of string")
+ @expectValidationFails("be a string or an array of strings")
it "fails if not an array of strings", ->
@setup({ignoreTestFiles: [5]})
- @expectValidationFails("be a string or an array of string")
+ @expectValidationFails("be a string or an array of strings")
@expectValidationFails("the value was: `[5]`")
context "integrationFolder", ->
@@ -299,9 +322,18 @@ describe "lib/config", ->
@setup({testFiles: "**/*.coffee"})
@expectValidationPasses()
- it "fails if not a string", ->
+ it "passes if an array of strings", ->
+ @setup({testFiles: ["**/*.coffee", "**/*.jsx"]})
+ @expectValidationPasses()
+
+ it "fails if not a string or array", ->
@setup({testFiles: 42})
- @expectValidationFails("be a string")
+ @expectValidationFails("be a string or an array of strings")
+
+ it "fails if not an array of strings", ->
+ @setup({testFiles: [5]})
+ @expectValidationFails("be a string or an array of strings")
+ @expectValidationFails("the value was: `[5]`")
context "supportFile", ->
it "passes if a string", ->
@@ -412,11 +444,11 @@ describe "lib/config", ->
it "fails if not a string or array", ->
@setup({blacklistHosts: 5})
- @expectValidationFails("be a string or an array of string")
+ @expectValidationFails("be a string or an array of strings")
it "fails if not an array of strings", ->
@setup({blacklistHosts: [5]})
- @expectValidationFails("be a string or an array of string")
+ @expectValidationFails("be a string or an array of strings")
@expectValidationFails("the value was: `[5]`")
context ".getConfigKeys", ->
@@ -764,7 +796,8 @@ describe "lib/config", ->
ignoreTestFiles: { value: "*.hot-update.js", from: "default" },
integrationFolder: { value: "cypress/integration", from: "default" },
screenshotsFolder: { value: "cypress/screenshots", from: "default" },
- testFiles: { value: "**/*.*", from: "default" }
+ testFiles: { value: "**/*.*", from: "default" },
+ nodeVersion: { value: "default", from: "default" },
})
it "sets config, envFile and env", ->
@@ -830,7 +863,8 @@ describe "lib/config", ->
ignoreTestFiles: { value: "*.hot-update.js", from: "default" },
integrationFolder: { value: "cypress/integration", from: "default" },
screenshotsFolder: { value: "cypress/screenshots", from: "default" },
- testFiles: { value: "**/*.*", from: "default" }
+ testFiles: { value: "**/*.*", from: "default" },
+ nodeVersion: { value: "default", from: "default" },
env: {
foo: {
value: "foo"
@@ -1219,6 +1253,57 @@ describe "lib/config", ->
expect(config.setAbsolutePaths(obj)).to.deep.eq(expected)
+ context ".setNodeBinary", ->
+ beforeEach ->
+ @findSystemNode = sinon.stub(findSystemNode, "findNodePathAndVersion")
+ @nodeVersion = process.versions.node
+
+ it "sets current Node ver if nodeVersion != system", ->
+ config.setNodeBinary({
+ nodeVersion: undefined
+ })
+ .then (obj) =>
+ expect(@findSystemNode).to.not.be.called
+ expect(obj).to.deep.eq({
+ nodeVersion: undefined,
+ resolvedNodeVersion: @nodeVersion
+ })
+
+ it "sets found Node ver if nodeVersion = system and findNodePathAndVersion resolves", ->
+ @findSystemNode.resolves({
+ path: '/foo/bar/node',
+ version: '1.2.3'
+ })
+
+ config.setNodeBinary({
+ nodeVersion: "system"
+ })
+ .then (obj) =>
+ expect(@findSystemNode).to.be.calledOnce
+ expect(obj).to.deep.eq({
+ nodeVersion: "system",
+ resolvedNodeVersion: "1.2.3",
+ resolvedNodePath: "/foo/bar/node"
+ })
+
+ it "sets current Node ver and warns if nodeVersion = system and findNodePathAndVersion rejects", ->
+ err = new Error()
+ onWarning = sinon.stub()
+
+ @findSystemNode.rejects(err)
+
+ config.setNodeBinary({
+ nodeVersion: "system"
+ }, onWarning)
+ .then (obj) =>
+ expect(@findSystemNode).to.be.calledOnce
+ expect(onWarning).to.be.calledOnce
+ expect(obj).to.deep.eq({
+ nodeVersion: "system",
+ resolvedNodeVersion: @nodeVersion,
+ })
+ expect(obj.resolvedNodePath).to.be.undefined
+
describe "lib/util/config", ->
context ".isDefault", ->
diff --git a/packages/server/test/unit/cors_spec.coffee b/packages/server/test/unit/cors_spec.coffee
deleted file mode 100644
index 96ef40eaee7..00000000000
--- a/packages/server/test/unit/cors_spec.coffee
+++ /dev/null
@@ -1,214 +0,0 @@
-require("../spec_helper")
-
-cors = require("#{root}lib/util/cors")
-
-describe "lib/util/cors", ->
- context ".parseUrlIntoDomainTldPort", ->
- beforeEach ->
- @isEq = (url, obj) ->
- expect(cors.parseUrlIntoDomainTldPort(url)).to.deep.eq(obj)
-
- it "parses https://www.google.com", ->
- @isEq("https://www.google.com", {
- port: "443"
- domain: "google"
- tld: "com"
- })
-
- it "parses http://localhost:8080", ->
- @isEq("http://localhost:8080", {
- port: "8080"
- domain: ""
- tld: "localhost"
- })
-
- it "parses http://app.localhost:8080", ->
- @isEq("http://app.localhost:8080", {
- port: "8080"
- domain: "app"
- tld: "localhost"
- })
-
- it "parses http://app.localhost.dev:8080", ->
- @isEq("http://app.localhost.dev:8080", {
- port: "8080"
- domain: "localhost"
- tld: "dev"
- })
-
- it "parses http://app.local:8080", ->
- @isEq("http://app.local:8080", {
- port: "8080"
- domain: "app"
- tld: "local"
- })
-
- ## public suffix example of a private tld
- it "parses https://example.herokuapp.com", ->
- @isEq("https://example.herokuapp.com", {
- port: "443"
- domain: "example"
- tld: "herokuapp.com"
- })
-
- it "parses http://www.local.nl", ->
- @isEq("http://www.local.nl", {
- port: "80"
- domain: "local"
- tld: "nl"
- })
-
- ## https://github.com/cypress-io/cypress/issues/3717
- it "parses http://dev.classea12.beta.gouv.fr", ->
- @isEq("http://dev.classea12.beta.gouv.fr", {
- port: "80"
- domain: "beta"
- tld: "gouv.fr"
- })
-
- it "parses http://www.local.nl:8080", ->
- @isEq("http://www.local.nl:8080", {
- port: "8080"
- domain: "local"
- tld: "nl"
- })
-
- it "parses 192.168.1.1:8080", ->
- @isEq("http://192.168.1.1:8080", {
- port: "8080"
- domain: ""
- tld: "192.168.1.1"
- })
-
- context ".urlMatchesOriginPolicyProps", ->
- beforeEach ->
- @isFalse = (url, props) =>
- expect(cors.urlMatchesOriginPolicyProps(url, props)).to.be.false
-
- @isTrue = (url, props) =>
- expect(cors.urlMatchesOriginPolicyProps(url, props)).to.be.true
-
- describe "domain + subdomain", ->
- beforeEach ->
- @props = cors.parseUrlIntoDomainTldPort("https://staging.google.com")
-
- it "does not match", ->
- @isFalse("https://foo.bar:443", @props)
- @isFalse("http://foo.bar:80", @props)
- @isFalse("http://foo.bar", @props)
- @isFalse("http://staging.google.com", @props)
- @isFalse("http://staging.google.com:80", @props)
- @isFalse("https://staging.google2.com:443", @props)
- @isFalse("https://staging.google.net:443", @props)
- @isFalse("https://google.net:443", @props)
- @isFalse("http://google.com", @props)
-
- it "matches", ->
- @isTrue("https://staging.google.com:443", @props)
- @isTrue("https://google.com:443", @props)
- @isTrue("https://foo.google.com:443", @props)
- @isTrue("https://foo.bar.google.com:443", @props)
-
- describe "public suffix", ->
- beforeEach ->
- @props = cors.parseUrlIntoDomainTldPort("https://example.gitlab.io")
-
- it "does not match", ->
- @isFalse("http://example.gitlab.io", @props)
- @isFalse("https://foo.gitlab.io:443", @props)
-
- it "matches", ->
- @isTrue("https://example.gitlab.io:443", @props)
- @isTrue("https://foo.example.gitlab.io:443", @props)
-
- describe "localhost", ->
- beforeEach ->
- @props = cors.parseUrlIntoDomainTldPort("http://localhost:4200")
-
- it "does not match", ->
- @isFalse("http://localhost:4201", @props)
- @isFalse("http://localhoss:4200", @props)
-
- it "matches", ->
- @isTrue("http://localhost:4200", @props)
-
- describe "app.localhost", ->
- beforeEach ->
- @props = cors.parseUrlIntoDomainTldPort("http://app.localhost:4200")
-
- it "does not match", ->
- @isFalse("http://app.localhost:4201", @props)
- @isFalse("http://app.localhoss:4200", @props)
-
- it "matches", ->
- @isTrue("http://app.localhost:4200", @props)
- @isTrue("http://name.app.localhost:4200", @props)
-
- describe "local", ->
- beforeEach ->
- @props = cors.parseUrlIntoDomainTldPort("http://brian.dev.local")
-
- it "does not match", ->
- @isFalse("https://brian.dev.local:443", @props)
- @isFalse("https://brian.dev.local", @props)
- @isFalse("http://brian.dev2.local:81", @props)
-
- it "matches", ->
- @isTrue("http://jennifer.dev.local:80", @props)
- @isTrue("http://jennifer.dev.local", @props)
-
- describe "ip address", ->
- beforeEach ->
- @props = cors.parseUrlIntoDomainTldPort("http://192.168.5.10")
-
- it "does not match", ->
- @isFalse("http://192.168.5.10:443", @props)
- @isFalse("https://192.168.5.10", @props)
- @isFalse("http://193.168.5.10", @props)
- @isFalse("http://193.168.5.10:80", @props)
-
- it "matches", ->
- @isTrue("http://192.168.5.10", @props)
- @isTrue("http://192.168.5.10:80", @props)
-
- context ".urlMatchesOriginProtectionSpace", ->
- isMatch = (urlStr, origin) ->
- expect(urlStr, "the url: '#{urlStr}' did not match origin protection space: '#{origin}'").to.satisfy ->
- cors.urlMatchesOriginProtectionSpace(urlStr, origin)
-
- isNotMatch = (urlStr, origin) ->
- expect(urlStr, "the url: '#{urlStr}' matched origin protection space: '#{origin}'")
- .not.to.satisfy ->
- cors.urlMatchesOriginProtectionSpace(urlStr, origin)
-
- it "ports", ->
- isMatch("http://example.com/", "http://example.com:80")
- isMatch("http://example.com:80/", "http://example.com")
- isMatch("http://example.com:80/", "http://example.com:80")
- isMatch("https://example.com:443/", "https://example.com:443")
- isMatch("https://example.com:443/", "https://example.com")
- isMatch("https://example.com/", "https://example.com:443")
-
- isNotMatch("https://example.com:1234/", "https://example.com")
- isNotMatch("https://example.com:1234/", "https://example.com:443")
-
- it "schemes", ->
- isNotMatch("http://example.com/", "https://example.com")
- isNotMatch("https://example.com/", "http://example.com")
- isNotMatch("http://example.com/", "ftp://example.com")
- isNotMatch("http://example.com/", "file://example.com")
-
- it "does not factor in path or search", ->
- isMatch("http://example.com/foo", "http://example.com")
- isMatch("http://example.com/foo/bar", "http://example.com")
- isMatch("http://example.com/?foo=bar", "http://example.com")
- isMatch("http://example.com/foo?bar=baz", "http://example.com")
-
- it "subdomains", ->
- isMatch("http://example.com/", "http://example.com")
- isMatch("http://www.example.com/", "http://www.example.com")
- isMatch("http://foo.bar.example.com/", "http://foo.bar.example.com")
-
- isNotMatch("http://www.example.com/", "http://example.com")
- isNotMatch("http://foo.example.com/", "http://bar.example.com")
- isNotMatch("http://foo.example.com/", "http://foo.bar.example.com")
diff --git a/packages/server/test/unit/cors_spec.js b/packages/server/test/unit/cors_spec.js
new file mode 100644
index 00000000000..56b21925102
--- /dev/null
+++ b/packages/server/test/unit/cors_spec.js
@@ -0,0 +1,265 @@
+require('../spec_helper')
+
+const cors = require(`${root}lib/util/cors`)
+
+describe('lib/util/cors', () => {
+ context('.parseUrlIntoDomainTldPort', () => {
+ beforeEach(function () {
+ this.isEq = (url, obj) => {
+ expect(cors.parseUrlIntoDomainTldPort(url)).to.deep.eq(obj)
+ }
+ })
+
+ it('parses https://www.google.com', function () {
+ this.isEq('https://www.google.com', {
+ port: '443',
+ domain: 'google',
+ tld: 'com',
+ })
+ })
+
+ it('parses http://localhost:8080', function () {
+ this.isEq('http://localhost:8080', {
+ port: '8080',
+ domain: '',
+ tld: 'localhost',
+ })
+ })
+
+ it('parses http://app.localhost:8080', function () {
+ this.isEq('http://app.localhost:8080', {
+ port: '8080',
+ domain: 'app',
+ tld: 'localhost',
+ })
+ })
+
+ it('parses http://app.localhost.dev:8080', function () {
+ this.isEq('http://app.localhost.dev:8080', {
+ port: '8080',
+ domain: 'localhost',
+ tld: 'dev',
+ })
+ })
+
+ it('parses http://app.local:8080', function () {
+ this.isEq('http://app.local:8080', {
+ port: '8080',
+ domain: 'app',
+ tld: 'local',
+ })
+ })
+
+ // public suffix example of a private tld
+ it('parses https://example.herokuapp.com', function () {
+ this.isEq('https://example.herokuapp.com', {
+ port: '443',
+ domain: 'example',
+ tld: 'herokuapp.com',
+ })
+ })
+
+ it('parses http://www.local.nl', function () {
+ this.isEq('http://www.local.nl', {
+ port: '80',
+ domain: 'local',
+ tld: 'nl',
+ })
+ })
+
+ // https://github.com/cypress-io/cypress/issues/3717
+ it('parses http://dev.classea12.beta.gouv.fr', function () {
+ this.isEq('http://dev.classea12.beta.gouv.fr', {
+ port: '80',
+ domain: 'beta',
+ tld: 'gouv.fr',
+ })
+ })
+
+ it('parses http://www.local.nl:8080', function () {
+ this.isEq('http://www.local.nl:8080', {
+ port: '8080',
+ domain: 'local',
+ tld: 'nl',
+ })
+ })
+
+ it('parses 192.168.1.1:8080', function () {
+ this.isEq('http://192.168.1.1:8080', {
+ port: '8080',
+ domain: '',
+ tld: '192.168.1.1',
+ })
+ })
+ })
+
+ context('.urlMatchesOriginPolicyProps', () => {
+ beforeEach(function () {
+ this.isFalse = (url, props) => {
+ expect(cors.urlMatchesOriginPolicyProps(url, props)).to.be.false
+ }
+
+ this.isTrue = (url, props) => {
+ expect(cors.urlMatchesOriginPolicyProps(url, props)).to.be.true
+ }
+ })
+
+ describe('domain + subdomain', () => {
+ beforeEach(function () {
+ this.props = cors.parseUrlIntoDomainTldPort('https://staging.google.com')
+ })
+
+ it('does not match', function () {
+ this.isFalse('https://foo.bar:443', this.props)
+ this.isFalse('http://foo.bar:80', this.props)
+ this.isFalse('http://foo.bar', this.props)
+ this.isFalse('http://staging.google.com', this.props)
+ this.isFalse('http://staging.google.com:80', this.props)
+ this.isFalse('https://staging.google2.com:443', this.props)
+ this.isFalse('https://staging.google.net:443', this.props)
+ this.isFalse('https://google.net:443', this.props)
+ this.isFalse('http://google.com', this.props)
+ })
+
+ it('matches', function () {
+ this.isTrue('https://staging.google.com:443', this.props)
+ this.isTrue('https://google.com:443', this.props)
+ this.isTrue('https://foo.google.com:443', this.props)
+ this.isTrue('https://foo.bar.google.com:443', this.props)
+ })
+ })
+
+ describe('public suffix', () => {
+ beforeEach(function () {
+ this.props = cors.parseUrlIntoDomainTldPort('https://example.gitlab.io')
+ })
+
+ it('does not match', function () {
+ this.isFalse('http://example.gitlab.io', this.props)
+ this.isFalse('https://foo.gitlab.io:443', this.props)
+ })
+
+ it('matches', function () {
+ this.isTrue('https://example.gitlab.io:443', this.props)
+ this.isTrue('https://foo.example.gitlab.io:443', this.props)
+ })
+ })
+
+ describe('localhost', () => {
+ beforeEach(function () {
+ this.props = cors.parseUrlIntoDomainTldPort('http://localhost:4200')
+ })
+
+ it('does not match', function () {
+ this.isFalse('http://localhost:4201', this.props)
+ this.isFalse('http://localhoss:4200', this.props)
+ })
+
+ it('matches', function () {
+ this.isTrue('http://localhost:4200', this.props)
+ })
+ })
+
+ describe('app.localhost', () => {
+ beforeEach(function () {
+ this.props = cors.parseUrlIntoDomainTldPort('http://app.localhost:4200')
+ })
+
+ it('does not match', function () {
+ this.isFalse('http://app.localhost:4201', this.props)
+ this.isFalse('http://app.localhoss:4200', this.props)
+ })
+
+ it('matches', function () {
+ this.isTrue('http://app.localhost:4200', this.props)
+ this.isTrue('http://name.app.localhost:4200', this.props)
+ })
+ })
+
+ describe('local', () => {
+ beforeEach(function () {
+ this.props = cors.parseUrlIntoDomainTldPort('http://brian.dev.local')
+ })
+
+ it('does not match', function () {
+ this.isFalse('https://brian.dev.local:443', this.props)
+ this.isFalse('https://brian.dev.local', this.props)
+ this.isFalse('http://brian.dev2.local:81', this.props)
+ })
+
+ it('matches', function () {
+ this.isTrue('http://jennifer.dev.local:80', this.props)
+ this.isTrue('http://jennifer.dev.local', this.props)
+ })
+ })
+
+ describe('ip address', () => {
+ beforeEach(function () {
+ this.props = cors.parseUrlIntoDomainTldPort('http://192.168.5.10')
+ })
+
+ it('does not match', function () {
+ this.isFalse('http://192.168.5.10:443', this.props)
+ this.isFalse('https://192.168.5.10', this.props)
+ this.isFalse('http://193.168.5.10', this.props)
+ this.isFalse('http://193.168.5.10:80', this.props)
+ })
+
+ it('matches', function () {
+ this.isTrue('http://192.168.5.10', this.props)
+ this.isTrue('http://192.168.5.10:80', this.props)
+ })
+ })
+ })
+
+ context('.urlMatchesOriginProtectionSpace', () => {
+ const isMatch = (urlStr, origin) => {
+ expect(urlStr, `the url: '${urlStr}' did not match origin protection space: '${origin}'`).to.satisfy(() => {
+ return cors.urlMatchesOriginProtectionSpace(urlStr, origin)
+ })
+ }
+
+ const isNotMatch = (urlStr, origin) => {
+ expect(urlStr, `the url: '${urlStr}' matched origin protection space: '${origin}'`)
+ .not.to.satisfy(() => {
+ return cors.urlMatchesOriginProtectionSpace(urlStr, origin)
+ })
+ }
+
+ it('ports', () => {
+ isMatch('http://example.com/', 'http://example.com:80')
+ isMatch('http://example.com:80/', 'http://example.com')
+ isMatch('http://example.com:80/', 'http://example.com:80')
+ isMatch('https://example.com:443/', 'https://example.com:443')
+ isMatch('https://example.com:443/', 'https://example.com')
+ isMatch('https://example.com/', 'https://example.com:443')
+
+ isNotMatch('https://example.com:1234/', 'https://example.com')
+ isNotMatch('https://example.com:1234/', 'https://example.com:443')
+ })
+
+ it('schemes', () => {
+ isNotMatch('http://example.com/', 'https://example.com')
+ isNotMatch('https://example.com/', 'http://example.com')
+ isNotMatch('http://example.com/', 'ftp://example.com')
+ isNotMatch('http://example.com/', 'file://example.com')
+ })
+
+ it('does not factor in path or search', () => {
+ isMatch('http://example.com/foo', 'http://example.com')
+ isMatch('http://example.com/foo/bar', 'http://example.com')
+ isMatch('http://example.com/?foo=bar', 'http://example.com')
+ isMatch('http://example.com/foo?bar=baz', 'http://example.com')
+ })
+
+ it('subdomains', () => {
+ isMatch('http://example.com/', 'http://example.com')
+ isMatch('http://www.example.com/', 'http://www.example.com')
+ isMatch('http://foo.bar.example.com/', 'http://foo.bar.example.com')
+
+ isNotMatch('http://www.example.com/', 'http://example.com')
+ isNotMatch('http://foo.example.com/', 'http://bar.example.com')
+ isNotMatch('http://foo.example.com/', 'http://foo.bar.example.com')
+ })
+ })
+})
diff --git a/packages/server/test/unit/find_system_node_spec.coffee b/packages/server/test/unit/find_system_node_spec.coffee
new file mode 100644
index 00000000000..218eaa2a3c0
--- /dev/null
+++ b/packages/server/test/unit/find_system_node_spec.coffee
@@ -0,0 +1,5 @@
+require("../spec_helper")
+
+findSystemNode = require("#{root}lib/util/find_system_node")
+
+describe "lib/util/find_system_node", ->
diff --git a/packages/server/test/unit/gui/auth_spec.js b/packages/server/test/unit/gui/auth_spec.js
index 98a329b5219..770a0975d1e 100644
--- a/packages/server/test/unit/gui/auth_spec.js
+++ b/packages/server/test/unit/gui/auth_spec.js
@@ -88,25 +88,24 @@ describe('lib/gui/auth', function () {
})
it('returns a promise that is fulfilled when openExternal succeeds', function () {
- sinon.stub(electron.shell, 'openExternal').callsArg(2)
+ sinon.stub(electron.shell, 'openExternal').resolves()
return auth._launchNativeAuth(REDIRECT_URL)
.then(() => {
- expect(electron.shell.openExternal).to.be.calledWithMatch(REDIRECT_URL, {}, sinon.match.func)
+ expect(electron.shell.openExternal).to.be.calledWithMatch(REDIRECT_URL)
})
})
it('is still fulfilled when openExternal fails, but sendWarning is called', function () {
- sinon.stub(electron.shell, 'openExternal').callsArgWith(2, new Error)
+ sinon.stub(electron.shell, 'openExternal').rejects()
const sendWarning = sinon.stub()
return auth._launchNativeAuth(REDIRECT_URL, sendWarning)
.then(() => {
- expect(electron.shell.openExternal).to.be.calledWithMatch(REDIRECT_URL, {}, sinon.match.func)
+ expect(electron.shell.openExternal).to.be.calledWithMatch(REDIRECT_URL)
expect(sendWarning).to.be.calledWithMatch('warning', 'AUTH_COULD_NOT_LAUNCH_BROWSER', REDIRECT_URL)
})
})
})
})
-
})
diff --git a/packages/server/test/unit/gui/events_spec.coffee b/packages/server/test/unit/gui/events_spec.coffee
index 92b54ef0f41..aa1f6060b1e 100644
--- a/packages/server/test/unit/gui/events_spec.coffee
+++ b/packages/server/test/unit/gui/events_spec.coffee
@@ -134,30 +134,6 @@ describe "lib/gui/events", ->
@handleEvent("get:current:user").then (assert) =>
assert.sendErrCalledWith(err)
- context "cookies", ->
- describe "clear:github:cookies", ->
- it "clears cookies and returns null", ->
- sinon.stub(Windows, "getBrowserAutomation")
- .withArgs(@event.sender)
- .returns({
- clearCookies: sinon.stub().withArgs({domain: "github.com"}).resolves()
- })
-
- @handleEvent("clear:github:cookies").then (assert) =>
- assert.sendCalledWith(null)
-
- it "catches errors", ->
- err = new Error("foo")
-
- sinon.stub(Windows, "getBrowserAutomation")
- .withArgs(@event.sender)
- .returns({
- clearCookies: sinon.stub().withArgs({domain: "github.com"}).rejects(err)
- })
-
- @handleEvent("clear:github:cookies", {foo: "bar"}).then (assert) =>
- assert.sendErrCalledWith(err)
-
context "external shell", ->
describe "external:open", ->
it "shell.openExternal with arg", ->
@@ -377,14 +353,14 @@ describe "lib/gui/events", ->
describe "add:project", ->
it "adds project + returns result", ->
- sinon.stub(Project, "add").withArgs("/_test-output/path/to/project").resolves("result")
+ sinon.stub(Project, "add").withArgs("/_test-output/path/to/project", @options).resolves("result")
@handleEvent("add:project", "/_test-output/path/to/project").then (assert) =>
assert.sendCalledWith("result")
it "catches errors", ->
err = new Error("foo")
- sinon.stub(Project, "add").withArgs("/_test-output/path/to/project").rejects(err)
+ sinon.stub(Project, "add").withArgs("/_test-output/path/to/project", @options).rejects(err)
@handleEvent("add:project", "/_test-output/path/to/project").then (assert) =>
assert.sendErrCalledWith(err)
diff --git a/packages/server/test/unit/gui/menu_spec.js b/packages/server/test/unit/gui/menu_spec.js
index aedd5e1ec0c..96b8c0cef03 100644
--- a/packages/server/test/unit/gui/menu_spec.js
+++ b/packages/server/test/unit/gui/menu_spec.js
@@ -36,49 +36,17 @@ describe('gui/menu', function () {
})
context('Cypress', function () {
- describe('on macOS', function () {
- it('contains about, services, hide, hide others, show all, quit', () => {
- menu.set()
- const labels = getLabels(getMenuItem('Cypress').submenu)
-
- expect(labels).to.eql([
- 'About Cypress',
- 'Services',
- 'Hide Cypress',
- 'Hide Others',
- 'Show All',
- 'Quit',
- ])
- })
-
- it('sets roles and shortcuts', () => {
- menu.set()
- const cyMenu = getMenuItem('Cypress')
-
- expect(getSubMenuItem(cyMenu, 'About Cypress').role).to.equal('about')
- expect(getSubMenuItem(cyMenu, 'Services').role).to.equal('services')
- expect(getSubMenuItem(cyMenu, 'Hide Cypress').role).to.equal('hide')
- expect(getSubMenuItem(cyMenu, 'Hide Cypress').accelerator).to.equal('Command+H')
- expect(getSubMenuItem(cyMenu, 'Hide Others').role).to.equal('hideothers')
- expect(getSubMenuItem(cyMenu, 'Hide Others').accelerator).to.equal('Command+Shift+H')
- expect(getSubMenuItem(cyMenu, 'Show All').role).to.equal('unhide')
- expect(getSubMenuItem(cyMenu, 'Quit').accelerator).to.equal('Command+Q')
- })
+ it('on darwin has appMenu role', () => {
+ menu.set()
+ const cyMenu = getMenuItem('Cypress')
- it('exits process when Quit is clicked', () => {
- sinon.stub(process, 'exit')
- menu.set()
- getSubMenuItem(getMenuItem('Cypress'), 'Quit').click()
- expect(process.exit).to.be.calledWith(0)
- })
+ expect(cyMenu.role).to.eq('appMenu')
})
- describe('other OS', () => {
- it('does not exist', () => {
- os.platform.returns('linux')
- menu.set()
- expect(getMenuItem('Cypress')).to.be.undefined
- })
+ it('on other OS does not exist', () => {
+ os.platform.returns('linux')
+ menu.set()
+ expect(getMenuItem('Cypress')).to.be.undefined
})
})
diff --git a/packages/server/test/unit/gui/project_spec.coffee b/packages/server/test/unit/gui/project_spec.coffee
deleted file mode 100644
index ba5453cceb2..00000000000
--- a/packages/server/test/unit/gui/project_spec.coffee
+++ /dev/null
@@ -1,79 +0,0 @@
-# require("../../spec_helper")
-#
-# extension = require("@packages/extension")
-# Fixtures = require("../../support/helpers/fixtures")
-# project = require("#{root}../lib/open_project")
-# Project = require("#{root}../lib/project")
-# launcher = require("#{root}../lib/launcher")
-#
-# describe "lib/open_projects", ->
-# beforeEach ->
-# Fixtures.scaffold()
-#
-# process.versions.chrome = "2020.0.1776"
-#
-# @todosPath = Fixtures.projectPath("todos")
-#
-# afterEach ->
-# Fixtures.remove()
-#
-# project.close()
-#
-# context ".open", ->
-# beforeEach ->
-# @projectInstance = {
-# getConfig: sinon.stub().resolves({proxyUrl: "foo", socketIoRoute: "bar"})
-# }
-#
-# browsers = [{
-# name: "chrome"
-# verson: "2077.1.42"
-# path: "/path/to/Chrome.app"
-# majorVersion: "2077"
-# }]
-# sinon.stub(launcher, "getBrowsers").resolves(browsers)
-# sinon.stub(extension, "setHostAndPath").withArgs("foo", "bar").resolves()
-# @open = sinon.stub(Project.prototype, "open").resolves(@projectInstance)
-#
-# it "resolves with opened project instance", ->
-# project.open(@todosPath)
-# .then (p) =>
-# expect(p.projectRoot).to.eq(@todosPath)
-# expect(p).to.be.an.instanceOf(Project)
-#
-# it "merges options into whitelisted config args", ->
-# args = {port: 2222, baseUrl: "localhost", foo: "bar"}
-# options = {socketId: 123, port: 2020}
-# project.open(@todosPath, args, options)
-# .then =>
-# expect(@open).to.be.calledWithMatch({
-# port: 2020
-# socketId: 123
-# baseUrl: "localhost"
-# sync: true
-# })
-# expect(@open.getCall(0).args[0].onReloadBrowser).to.be.a("function")
-#
-# it "passes onReloadBrowser which calls relaunch with url + browser", ->
-# relaunch = sinon.stub(project, "relaunch")
-#
-# project.open(@todosPath)
-# .then =>
-# @open.getCall(0).args[0].onReloadBrowser("foo", "bar")
-# expect(relaunch).to.be.calledWith("foo", "bar")
-#
-# it "opens project with available browsers, appending electron browser", ->
-# project.open(@todosPath)
-# .then =>
-# browsers = @open.lastCall.args[0].browsers
-# expect(browsers.length).to.equal(2)
-# expect(browsers[0].name).to.equal("chrome")
-# expect(browsers[1].name).to.equal("electron")
-#
-# it "electron browser has info explaining what it is", ->
-# project.open(@todosPath)
-# .then =>
-# electron = @open.lastCall.args[0].browsers[1]
-# expect(electron.info).to.include("version of Chrome")
-#
-# context ".close", ->
diff --git a/packages/server/test/unit/gui/windows_spec.coffee b/packages/server/test/unit/gui/windows_spec.coffee
index 99edba77309..c54562d5bb9 100644
--- a/packages/server/test/unit/gui/windows_spec.coffee
+++ b/packages/server/test/unit/gui/windows_spec.coffee
@@ -32,17 +32,6 @@ describe "lib/gui/windows", ->
afterEach ->
Windows.reset()
- context ".getBrowserAutomation", ->
- beforeEach ->
- sinon.stub(Windows, "automation")
- sinon.stub(Windows, "getByWebContents")
-
- it "gets window and passes to electron.automation", ->
- Windows.getByWebContents.withArgs("foo").returns("bar")
- Windows.automation.withArgs("bar").returns("baz")
-
- expect(Windows.getBrowserAutomation("foo")).to.eq("baz")
-
context ".getByWebContents", ->
beforeEach ->
sinon.stub(BrowserWindow, "fromWebContents")
@@ -174,164 +163,3 @@ describe "lib/gui/windows", ->
.delay(100)
.then () =>
expect(@state.set).to.be.calledWith({whatsUpWithDevTools: false})
-
- context ".automation", ->
- beforeEach ->
- @cookies = {
- set: sinon.stub()
- get: sinon.stub()
- remove: sinon.stub()
- }
-
- @win = {
- webContents: {
- session: {
- cookies: @cookies
- }
- }
- }
-
- @automation = Windows.automation(@win)
-
- describe ".getCookies", ->
- beforeEach ->
- @cookies.get
- .withArgs({domain: "localhost"})
- .yieldsAsync(null, [
- {name: "foo", value: "f", path: "/", domain: "localhost", secure: true, httpOnly: true, expiry: 123}
- {name: "bar", value: "b", path: "/", domain: "localhost", secure: false, httpOnly: false, expiry: 456}
- ])
-
- it "returns all cookies", ->
- @automation.getCookies({domain: "localhost"})
- .then (resp) ->
- expect(resp).to.deep.eq([
- {name: "foo", value: "f", path: "/", domain: "localhost", secure: true, httpOnly: true, expiry: 123}
- {name: "bar", value: "b", path: "/", domain: "localhost", secure: false, httpOnly: false, expiry: 456}
- ])
-
- describe ".getCookie", ->
- beforeEach ->
- @cookies.get
- .withArgs({domain: "google.com", name: "session"})
- .yieldsAsync(null, [
- {name: "session", value: "key", path: "/login", domain: "google", secure: true, httpOnly: true, expiry: 123}
- ])
- .withArgs({domain: "google.com", name: "doesNotExist"})
- .yieldsAsync(null, [])
-
- it "returns a specific cookie by name", ->
- @automation.getCookie({domain: "google.com", name: "session"})
- .then (resp) ->
- expect(resp).to.deep.eq({name: "session", value: "key", path: "/login", domain: "google", secure: true, httpOnly: true, expiry: 123})
-
- it "returns null when no cookie by name is found", ->
- @automation.getCookie({domain: "google.com", name: "doesNotExist"})
- .then (resp) ->
- expect(resp).to.be.null
-
- describe ".setCookie", ->
- beforeEach ->
- @cookies.set
- .withArgs({domain: "google.com", name: "session", value: "key", path: "/", url: "http://google.com/"})
- .yieldsAsync(null,
- {name: "session", value: "key", path: "/", domain: "google", secure: false, httpOnly: false}
- )
- .withArgs({domain: "foo", path: "/bar", url: "http://foo/bar"})
- .yieldsAsync(new Error("some error"))
-
- it "resolves with the cookie props", ->
- @automation.setCookie({domain: "google.com", name: "session", value: "key", path: "/"})
- .then (resp) ->
- expect(resp).to.deep.eq({domain: "google.com", name: "session", value: "key", path: "/", url: "http://google.com/"})
-
- it "rejects with error", ->
- @automation.setCookie({domain: "foo", path: "/bar", url: "http://foo/bar"})
- .then ->
- throw new Error("should have failed")
- .catch (err) ->
- expect(err.message).to.eq("some error")
-
- describe ".clearCookies", ->
- beforeEach ->
- @cookies.get
- .withArgs({domain: "google.com"})
- .yieldsAsync(null, [
- {name: "session", value: "key", path: "/", domain: "google.com", secure: true, httpOnly: true, expiry: 123}
- {name: "foo", value: "bar", path: "/foo", domain: "google.com", secure: false, httpOnly: false, expiry: 456}
- ])
- .withArgs({domain: "cdn.github.com"})
- .yieldsAsync(null, [
- {name: "shouldThrow", value: "key", path: "/assets", domain: "cdn.github.com", secure: false, httpOnly: true, expiry: 123}
- ])
-
- @cookies.remove
- .withArgs("https://google.com/", "session")
- .yieldsAsync(null)
-
- .withArgs("http://google.com/foo", "foo")
- .yieldsAsync(null)
-
- .withArgs("http://cdn.github.com/assets", "shouldThrow")
- .yieldsAsync(new Error("some error"))
-
- it "resolves with array of removed cookies", ->
- @automation.clearCookies({domain: "google.com"})
- .then (resp) ->
- expect(resp).to.deep.eq([
- {name: "session", value: "key", path: "/", domain: "google.com", secure: true, httpOnly: true, expiry: 123}
- {name: "foo", value: "bar", path: "/foo", domain: "google.com", secure: false, httpOnly: false, expiry: 456}
- ])
-
- it "rejects with error", ->
- @automation.clearCookies({domain: "cdn.github.com"})
- .then ->
- throw new Error("should have failed")
- .catch (err) ->
- expect(err.message).to.eq("some error")
-
- describe ".clearCookie", ->
- beforeEach ->
- @cookies.get
- .withArgs({domain: "google.com", name: "session"})
- .yieldsAsync(null, [
- {name: "session", value: "key", path: "/", domain: "google.com", secure: true, httpOnly: true, expiry: 123}
- ])
-
- .withArgs({domain: "google.com", name: "doesNotExist"})
- .yieldsAsync(null, [])
-
- .withArgs({domain: "cdn.github.com", name: "shouldThrow"})
- .yieldsAsync(null, [
- {name: "shouldThrow", value: "key", path: "/assets", domain: "cdn.github.com", secure: false, httpOnly: true, expiry: 123}
- ])
-
- @cookies.remove
- .withArgs("https://google.com/", "session")
- .yieldsAsync(null)
-
- .withArgs("http://cdn.github.com/assets", "shouldThrow")
- .yieldsAsync(new Error("some error"))
-
- it "resolves single removed cookie", ->
- @automation.clearCookie({domain: "google.com", name: "session"})
- .then (resp) ->
- expect(resp).to.deep.eq(
- {name: "session", value: "key", path: "/", domain: "google.com", secure: true, httpOnly: true, expiry: 123}
- )
-
- it "returns null when no cookie by name is found", ->
- @automation.clearCookie({domain: "google.com", name: "doesNotExist"})
- .then (resp) ->
- expect(resp).to.be.null
-
- it "rejects with error", ->
- @automation.clearCookie({domain: "cdn.github.com", name: "shouldThrow"})
- .then ->
- throw new Error("should have failed")
- .catch (err) ->
- expect(err.message).to.eq("some error")
-
- describe "isAutomationConnected", ->
- it "returns true", ->
- expect(@automation.isAutomationConnected()).to.be.true
diff --git a/packages/server/test/unit/modes/record_spec.coffee b/packages/server/test/unit/modes/record_spec.coffee
index 20e0a710ba2..8e66ad5cf85 100644
--- a/packages/server/test/unit/modes/record_spec.coffee
+++ b/packages/server/test/unit/modes/record_spec.coffee
@@ -126,7 +126,58 @@ describe "lib/modes/record", ->
runAllSpecs
})
.then ->
- expect(runAllSpecs).to.have.been.calledWith({}, false)
+ expect(runAllSpecs).to.have.been.calledWith({ parallel: false })
+ expect(createRun).to.have.been.calledOnce
+ expect(createRun.firstCall.args).to.have.length(1)
+ { commit } = createRun.firstCall.args[0]
+ debug('git is %o', commit)
+ expect(commit).to.deep.equal({
+ sha: env.COMMIT_INFO_SHA,
+ branch: env.COMMIT_INFO_BRANCH,
+ authorName: env.COMMIT_INFO_AUTHOR,
+ authorEmail: env.COMMIT_INFO_EMAIL,
+ message: env.COMMIT_INFO_MESSAGE,
+ remoteOrigin: env.COMMIT_INFO_REMOTE,
+ defaultBranch: null
+ })
+
+ describe "override commit information", ->
+ resetEnv = null
+
+ env = {
+ COMMIT_INFO_BRANCH: "my-branch-221",
+ COMMIT_INFO_MESSAGE: "best commit ever",
+ COMMIT_INFO_EMAIL: "user@company.com",
+ COMMIT_INFO_AUTHOR: "Agent Smith",
+ COMMIT_INFO_SHA: "0123456",
+ COMMIT_INFO_REMOTE: "remote repo"
+ }
+
+ beforeEach ->
+ # stub git commands to return values
+ sinon.stub(commitInfo, "getBranch").resolves("my-ci-branch")
+ sinon.stub(commitInfo, "getMessage").resolves("my ci msg")
+ sinon.stub(commitInfo, "getEmail").resolves("my.ci@email.com")
+ sinon.stub(commitInfo, "getAuthor").resolves("My CI Name")
+ sinon.stub(commitInfo, "getSha").resolves("mycisha")
+ sinon.stub(commitInfo, "getRemoteOrigin").resolves("my ci remote")
+ # but set environment variables
+ resetEnv = mockedEnv(env, {clear: true})
+
+ afterEach ->
+ resetEnv()
+
+ it "calls api.createRun with the commit overrided from environment variables", ->
+ createRun = sinon.stub(api, "createRun").resolves()
+ runAllSpecs = sinon.stub()
+ recordMode.createRunAndRecordSpecs({
+ key: "foo",
+ sys: {},
+ browser: {},
+ runAllSpecs
+ })
+ .then ->
+ expect(runAllSpecs).to.have.been.calledWith({ parallel: false })
expect(createRun).to.have.been.calledOnce
expect(createRun.firstCall.args).to.have.length(1)
{ commit } = createRun.firstCall.args[0]
diff --git a/packages/server/test/unit/modes/run_spec.coffee b/packages/server/test/unit/modes/run_spec.coffee
index 584d8535a3a..aebc3df774a 100644
--- a/packages/server/test/unit/modes/run_spec.coffee
+++ b/packages/server/test/unit/modes/run_spec.coffee
@@ -133,7 +133,6 @@ describe "lib/modes/run", ->
context ".launchBrowser", ->
beforeEach ->
@launch = sinon.stub(openProject, "launch")
- sinon.stub(runMode, "getElectronProps").returns({foo: "bar"})
sinon.stub(runMode, "screenshotMetadata").returns({a: "a"})
it "can launch electron", ->
@@ -143,7 +142,11 @@ describe "lib/modes/run", ->
absolute: "/path/to/spec"
}
- browser = { name: "electron", isHeaded: false }
+ browser = {
+ name: "electron",
+ family: "electron",
+ isHeaded: false
+ }
runMode.launchBrowser({
spec
@@ -153,9 +156,7 @@ describe "lib/modes/run", ->
screenshots: screenshots
})
- expect(runMode.getElectronProps).to.be.calledWith(false, @projectInstance, "write")
-
- expect(@launch).to.be.calledWithMatch(browser, spec, { foo: "bar" })
+ expect(@launch).to.be.calledWithMatch(browser, spec)
browserOpts = @launch.firstCall.args[2]
@@ -173,15 +174,17 @@ describe "lib/modes/run", ->
absolute: "/path/to/spec"
}
- browser = { name: "chrome", isHeaded: true }
+ browser = {
+ name: "chrome",
+ family: "chrome",
+ isHeaded: true
+ }
runMode.launchBrowser({
spec
browser
})
- expect(runMode.getElectronProps).not.to.be.called
-
expect(@launch).to.be.calledWithMatch(browser, spec, {})
context ".postProcessRecording", ->
@@ -216,6 +219,15 @@ describe "lib/modes/run", ->
.then ->
expect(videoCapture.process).not.to.be.called
+ it "logs a warning on failure and resolves", ->
+ sinon.stub(errors, 'warning')
+ end = sinon.stub().rejects()
+
+ runMode.postProcessRecording(end)
+ .then ->
+ expect(end).to.be.calledOnce
+ expect(errors.warning).to.be.calledWith('VIDEO_POST_PROCESSING_FAILED')
+
context ".waitForBrowserToConnect", ->
it "throws TESTS_DID_NOT_START_FAILED after 3 connection attempts", ->
sinon.spy(errors, "warning")
@@ -527,10 +539,18 @@ describe "lib/modes/run", ->
.then ->
expect(errors.warning).to.be.calledWith("CANNOT_RECORD_VIDEO_HEADED")
- it "disables video recording for non-electron browser", ->
+ it "throws an error if invalid browser family supplied", ->
+ browser = { name: "opera", family: "opera - btw when is Opera support coming?" }
+
+ sinon.stub(browsers, "ensureAndGetByNameOrPath").resolves(browser)
+
+ expect(runMode.run({browser: "opera"}))
+ .to.be.rejectedWith(/invalid browser family in/)
+
+ it "shows no warnings for chrome browser", ->
runMode.run({browser: "chrome"})
.then ->
- expect(errors.warning).to.be.calledWith("CANNOT_RECORD_VIDEO_FOR_THIS_BROWSER")
+ expect(errors.warning).to.not.be.called
it "names video file with spec name", ->
runMode.run()
@@ -554,7 +574,8 @@ describe "lib/modes/run", ->
sinon.stub(browsers, "ensureAndGetByNameOrPath").resolves({
name: "fooBrowser",
path: "path/to/browser"
- version: "777"
+ version: "777",
+ family: "electron"
})
sinon.stub(runMode, "waitForSocketConnection").resolves()
sinon.stub(runMode, "waitForTestsToFinishRunning").resolves({
@@ -606,7 +627,7 @@ describe "lib/modes/run", ->
})
it "passes headed to openProject.launch", ->
- browser = { name: "electron" }
+ browser = { name: "electron", family: "electron" }
browsers.ensureAndGetByNameOrPath.resolves(browser)
diff --git a/packages/server/test/unit/newlines_spec.ts b/packages/server/test/unit/newlines_spec.ts
new file mode 100644
index 00000000000..a3050923d0b
--- /dev/null
+++ b/packages/server/test/unit/newlines_spec.ts
@@ -0,0 +1,15 @@
+import '../spec_helper'
+
+import newlines from '../../lib/util/newlines'
+
+describe('lib/util/newlines', function () {
+ it('inserts newline at each n char', function () {
+ expect(newlines.addNewlineAtEveryNChar('123456789', 3)).to.eq('123\n456\n789')
+ })
+})
+
+describe('lib/util/newlines', function () {
+ it('returns undefined if str not defined', function () {
+ expect(newlines.addNewlineAtEveryNChar(undefined, 3)).to.eq(undefined)
+ })
+})
diff --git a/packages/server/test/unit/plugins/child/run_plugins_spec.coffee b/packages/server/test/unit/plugins/child/run_plugins_spec.coffee
index 13500b50c3f..a2ffdea0bed 100644
--- a/packages/server/test/unit/plugins/child/run_plugins_spec.coffee
+++ b/packages/server/test/unit/plugins/child/run_plugins_spec.coffee
@@ -12,12 +12,10 @@ Fixtures = require("#{root}../../test/support/helpers/fixtures")
colorCodeRe = /\[[0-9;]+m/gm
pathRe = /\/?([a-z0-9_-]+\/)*[a-z0-9_-]+\/([a-z_]+\.\w+)[:0-9]+/gmi
-stackPathRe = /\(?\/?([a-z0-9_-]+\/)*([a-z0-9_-]+\.\w+)[:0-9]+\)?/gmi
withoutStack = (err) -> _.omit(err, "stack")
withoutColorCodes = (str) -> str.replace(colorCodeRe, "")
withoutPath = (str) -> str.replace(pathRe, '$2)')
-withoutStackPaths = (stack) -> stack.replace(stackPathRe, '$2')
describe "lib/plugins/child/run_plugins", ->
beforeEach ->
@@ -35,7 +33,7 @@ describe "lib/plugins/child/run_plugins", ->
mockery.registerSubstitute("plugins-file", "/does/not/exist.coffee")
runPlugins(@ipc, "plugins-file")
expect(@ipc.send).to.be.calledWith("load:error", "PLUGINS_FILE_ERROR", "plugins-file")
- snapshot(withoutStackPaths(@ipc.send.lastCall.args[3]))
+ snapshot(@ipc.send.lastCall.args[3].split('\n')[0])
it "sends error message if requiring pluginsFile errors", ->
## path for substitute is relative to lib/plugins/child/plugins_child.js
@@ -45,7 +43,7 @@ describe "lib/plugins/child/run_plugins", ->
)
runPlugins(@ipc, "plugins-file")
expect(@ipc.send).to.be.calledWith("load:error", "PLUGINS_FILE_ERROR", "plugins-file")
- snapshot(withoutStackPaths(@ipc.send.lastCall.args[3]))
+ snapshot(@ipc.send.lastCall.args[3].split('\n')[0])
it "sends error message if pluginsFile has syntax error", ->
## path for substitute is relative to lib/plugins/child/plugins_child.js
diff --git a/packages/server/test/unit/plugins/index_spec.coffee b/packages/server/test/unit/plugins/index_spec.coffee
index d1537f3c57e..c23c1ee2afc 100644
--- a/packages/server/test/unit/plugins/index_spec.coffee
+++ b/packages/server/test/unit/plugins/index_spec.coffee
@@ -27,18 +27,54 @@ describe "lib/plugins/index", ->
plugins.init({}) ## doesn't reject or time out
it "forks child process", ->
+ # have to fire "loaded" message, otherwise plugins.init promise never resolves
+ @ipc.on.withArgs("loaded").yields([])
plugins.init({ pluginsFile: "cypress-plugin" })
- expect(cp.fork).to.be.called
- expect(cp.fork.lastCall.args[0]).to.contain("plugins/child/index.js")
- expect(cp.fork.lastCall.args[1]).to.eql(["--file", "cypress-plugin"])
+ .then ->
+ expect(cp.fork).to.be.called
+ expect(cp.fork.lastCall.args[0]).to.contain("plugins/child/index.js")
+ expect(cp.fork.lastCall.args[1]).to.eql(["--file", "cypress-plugin"])
+
+ it "uses system Node when available", ->
+ @ipc.on.withArgs("loaded").yields([])
+ systemNode = "/my/path/to/system/node"
+ config = {
+ pluginsFile: "cypress-plugin",
+ nodeVersion: "system"
+ resolvedNodeVersion: "v1.2.3"
+ resolvedNodePath: systemNode
+ }
+ plugins.init(config)
+ .then ->
+ options = {
+ stdio: "inherit",
+ execPath: systemNode
+ }
+ expect(cp.fork.lastCall.args[2]).to.eql(options)
+
+ it "uses bundled Node when cannot find system Node", ->
+ @ipc.on.withArgs("loaded").yields([])
+ config = {
+ pluginsFile: "cypress-plugin",
+ nodeVersion: "system"
+ resolvedNodeVersion: "v1.2.3"
+ }
+ plugins.init(config)
+ .then ->
+ options = {
+ stdio: "inherit"
+ }
+ expect(cp.fork.lastCall.args[2]).to.eql(options)
it "calls any handlers registered with the wrapped ipc", ->
+ @ipc.on.withArgs("loaded").yields([])
handler = sinon.spy()
plugins.registerHandler(handler)
plugins.init({ pluginsFile: "cypress-plugin" })
- expect(handler).to.be.called
- expect(handler.lastCall.args[0].send).to.be.a("function")
- expect(handler.lastCall.args[0].on).to.be.a("function")
+ .then ->
+ expect(handler).to.be.called
+ expect(handler.lastCall.args[0].send).to.be.a("function")
+ expect(handler.lastCall.args[0].on).to.be.a("function")
it "sends config via ipc", ->
@ipc.on.withArgs("loaded").yields([])
diff --git a/packages/server/test/unit/project_spec.coffee b/packages/server/test/unit/project_spec.coffee
index 33a979ea485..92ce844d07c 100644
--- a/packages/server/test/unit/project_spec.coffee
+++ b/packages/server/test/unit/project_spec.coffee
@@ -211,10 +211,12 @@ describe "lib/project", ->
expect(@project.saveState).to.be.calledWith({ autoScrollingEnabled: false})
expect(config.state).to.eql({ autoScrollingEnabled: false })
+ # TODO: skip this for now
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 = {}
@@ -222,6 +224,7 @@ describe "lib/project", ->
startListening = Socket::startListening
expect(startListening.getCall(0).args[0]).to.be.instanceof(Watchers)
expect(startListening.getCall(0).args[1]).to.eq(options)
+ null
context "#close", ->
beforeEach ->
@@ -300,7 +303,7 @@ describe "lib/project", ->
beforeEach ->
@project = Project("/_test-output/path/to/project")
@project.server = {startWebsockets: ->}
- sinon.stub(settings, "pathToCypressJson").returns("/path/to/cypress.json")
+ sinon.stub(settings, "pathToConfigFile").returns("/path/to/cypress.json")
sinon.stub(settings, "pathToCypressEnvJson").returns("/path/to/cypress.env.json")
@watch = sinon.stub(@project.watchers, "watch")
@@ -530,7 +533,7 @@ describe "lib/project", ->
@pristinePath = Fixtures.projectPath("pristine")
it "inserts path into cache", ->
- Project.add(@pristinePath)
+ Project.add(@pristinePath, {})
.then =>
cache.read()
.then (json) =>
@@ -540,7 +543,7 @@ describe "lib/project", ->
it "returns object containing path and id", ->
sinon.stub(settings, "read").resolves({projectId: "id-123"})
- Project.add(@pristinePath)
+ Project.add(@pristinePath, {})
.then (project) =>
expect(project.id).to.equal("id-123")
expect(project.path).to.equal(@pristinePath)
@@ -549,10 +552,20 @@ describe "lib/project", ->
it "returns object containing just the path", ->
sinon.stub(settings, "read").rejects()
- Project.add(@pristinePath)
+ Project.add(@pristinePath, {})
+ .then (project) =>
+ expect(project.id).to.be.undefined
+ expect(project.path).to.equal(@pristinePath)
+
+ describe "if configFile is non-default", ->
+ it "doesn't cache anything and returns object containing just the path", ->
+ Project.add(@pristinePath, { configFile: false })
.then (project) =>
expect(project.id).to.be.undefined
expect(project.path).to.equal(@pristinePath)
+ cache.read()
+ .then (json) =>
+ expect(json.PROJECTS).to.deep.eq([])
context "#createCiProject", ->
beforeEach ->
diff --git a/packages/server/test/unit/replace_stream_spec.js b/packages/server/test/unit/replace_stream_spec.js
new file mode 100644
index 00000000000..b0f7e41887b
--- /dev/null
+++ b/packages/server/test/unit/replace_stream_spec.js
@@ -0,0 +1,617 @@
+/**
+ * This file contains test code from the `replacestream` library
+ * (https://github.com/eugeneware/replacestream), to which the following license applies:
+ *
+ * Copyright (c) 2014, Eugene Ware
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Eugene Ware nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY EUGENE WARE ''AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL EUGENE WARE BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+require('../spec_helper')
+
+const concatStream = require('concat-stream')
+const { passthruStream } = require(`${root}lib/util/passthru_stream`)
+const { replaceStream } = require(`${root}lib/util/replace_stream`)
+
+const script = [
+ '