From 946b8c442e389bef01948642aacba4eb65f8c827 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Thu, 18 Jun 2020 11:05:46 -0400 Subject: [PATCH 01/42] 5.0 release From ffcb036b40f8ba9555cacd9b9c6d84ad049dd05b Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Mon, 29 Jun 2020 14:29:51 -0400 Subject: [PATCH 02/42] feat(deps): electron@9.0.5 (#7791) * chore(deps): electron@9.0.5 BREAKING CHANGE: libgbm is a requirement * update node, xcode, docker images * lockfile * chore(types): tsify lib/gui/windows and spec * fix Electron extension loading global extension loading was deprecated in 9, now has to be per-session * make windows fns stubbable * update electron_spec * tsify issue_173_spec * use upstream foxdriver to fix FF >= 75 see https://github.com/benmalka/foxdriver/issues/7 * update test * for now, install libgbm-dev at ci time see https://github.com/cypress-io/cypress-docker-images/pull/332 * fix open mode * remove devtools-ext dir --- .node-version | 2 +- appveyor.yml | 2 +- circle.yml | 11 +- package.json | 2 +- packages/electron/package.json | 2 +- ...sue_173_spec.js => 3_issue_173_spec.ts.js} | 0 packages/server/lib/browsers/electron.js | 16 +- packages/server/lib/browsers/firefox-util.ts | 2 +- packages/server/lib/gui/events.js | 9 +- packages/server/lib/gui/windows.js | 327 ----------------- packages/server/lib/gui/windows.ts | 332 ++++++++++++++++++ packages/server/package.json | 2 +- ..._issue_173_spec.js => 3_issue_173_spec.ts} | 2 +- .../cypress/integration/spec.js | 6 - .../cypress/plugins/index.js | 11 +- .../devtools-ext/devtools.html | 15 - .../devtools-ext/manifest.json | 13 - .../test/unit/browsers/electron_spec.js | 6 +- .../server/test/unit/browsers/firefox_spec.ts | 2 +- packages/server/test/unit/gui/events_spec.js | 20 +- .../gui/{windows_spec.js => windows_spec.ts} | 55 ++- scripts/run-docker-local.sh | 2 +- yarn.lock | 32 +- 23 files changed, 419 insertions(+), 452 deletions(-) rename packages/server/__snapshots__/{3_issue_173_spec.js => 3_issue_173_spec.ts.js} (100%) delete mode 100644 packages/server/lib/gui/windows.js create mode 100644 packages/server/lib/gui/windows.ts rename packages/server/test/e2e/{3_issue_173_spec.js => 3_issue_173_spec.ts} (79%) delete mode 100644 packages/server/test/support/fixtures/projects/browser-extensions/devtools-ext/devtools.html delete mode 100644 packages/server/test/support/fixtures/projects/browser-extensions/devtools-ext/manifest.json rename packages/server/test/unit/gui/{windows_spec.js => windows_spec.ts} (80%) diff --git a/.node-version b/.node-version index e56b2006c570..5c088ddb94af 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -12.8.1 +12.14.1 diff --git a/appveyor.yml b/appveyor.yml index 462b9f83ee2a..14abce2131d2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,7 +7,7 @@ branches: # https://www.appveyor.com/docs/lang/nodejs-iojs/ environment: # use matching version of Node.js - nodejs_version: "12.8.1" + nodejs_version: "12.14.1" # encode secure variables which will NOT be used # in pull requests # https://www.appveyor.com/docs/build-configuration/#secure-variables diff --git a/circle.yml b/circle.yml index faed166f3da5..bd56932af9f2 100644 --- a/circle.yml +++ b/circle.yml @@ -43,14 +43,14 @@ executors: # the Docker image with Cypress dependencies and Chrome browser cy-doc: docker: - - image: cypress/browsers:node12.13.0-chrome80-ff74 + - image: cypress/browsers:node12.14.1-chrome83-ff77 environment: PLATFORM: linux # Docker image with non-root "node" user non-root-docker-user: docker: - - image: cypress/browsers:node12.13.0-chrome80-ff74 + - image: cypress/browsers:node12.14.1-chrome83-ff77 user: node environment: PLATFORM: linux @@ -60,8 +60,8 @@ executors: # https://circleci.com/docs/2.0/testing-ios/#supported-xcode-versions mac: macos: - ## Node 12.10.0 (yarn 1.17.3) - xcode: "11.0.0" + ## Node 12.12.0 (yarn 1.19.1) + xcode: "11.2.1" environment: PLATFORM: mac @@ -1144,6 +1144,9 @@ jobs: - run: mkdir test-binary - run: node --version - run: npm --version + # TODO: this can be removed as soon as the minimum Node version is bumped to 10 + # see https://github.com/cypress-io/cypress-docker-images/pull/332 + - run: apt-get update && apt-get install -y libgbm-dev - run: name: Create new NPM package working_directory: test-binary diff --git a/package.json b/package.json index 47526985a7ff..6cadd110bd02 100644 --- a/package.json +++ b/package.json @@ -187,7 +187,7 @@ "typescript": "3.7.4" }, "engines": { - "node": ">=12.8.1", + "node": ">=12.14.1", "yarn": ">=1.17.3" }, "productName": "Cypress", diff --git a/packages/electron/package.json b/packages/electron/package.json index 1f3d82074ae3..a359834160ea 100644 --- a/packages/electron/package.json +++ b/packages/electron/package.json @@ -24,7 +24,7 @@ "minimist": "1.2.5" }, "devDependencies": { - "electron": "8.3.1", + "electron": "9.0.5", "mocha": "3.5.3" }, "files": [ diff --git a/packages/server/__snapshots__/3_issue_173_spec.js b/packages/server/__snapshots__/3_issue_173_spec.ts.js similarity index 100% rename from packages/server/__snapshots__/3_issue_173_spec.js rename to packages/server/__snapshots__/3_issue_173_spec.ts.js diff --git a/packages/server/lib/browsers/electron.js b/packages/server/lib/browsers/electron.js index 51eb4f1283fd..84d83e4c5061 100644 --- a/packages/server/lib/browsers/electron.js +++ b/packages/server/lib/browsers/electron.js @@ -66,12 +66,12 @@ const _getAutomation = function (win, options) { return automation } -const _installExtensions = function (extensionPaths = [], options) { - Windows.removeAllExtensions() +const _installExtensions = function (win, extensionPaths = [], options) { + Windows.removeAllExtensions(win) - return extensionPaths.forEach((path) => { + return Bluebird.map(extensionPaths, (path) => { try { - return Windows.installExtension(path) + return Windows.installExtension(win, path) } catch (error) { return options.onWarning(errors.get('EXTENSION_NOT_LOADED', 'Electron', path)) } @@ -349,10 +349,10 @@ module.exports = { debug('launching browser window to url: %s', url) - _installExtensions(launchOptions.extensions, options) - return this._render(url, projectRoot, automation, preferences) - .then((win) => { + .then(async (win) => { + await _installExtensions(win, launchOptions.extensions, options) + // cause the webview to receive focus so that // native browser focus + blur events fire correctly // https://github.com/cypress-io/cypress/issues/1939 @@ -363,7 +363,7 @@ module.exports = { win.once('closed', () => { debug('closed event fired') - Windows.removeAllExtensions() + Windows.removeAllExtensions(win) return events.emit('exit') }) diff --git a/packages/server/lib/browsers/firefox-util.ts b/packages/server/lib/browsers/firefox-util.ts index 3c4078d264ac..84908987c5dd 100644 --- a/packages/server/lib/browsers/firefox-util.ts +++ b/packages/server/lib/browsers/firefox-util.ts @@ -4,7 +4,7 @@ import _ from 'lodash' import Marionette from 'marionette-client' import { Command } from 'marionette-client/lib/marionette/message.js' import util from 'util' -import Foxdriver from '@benmalka/foxdriver' +import Foxdriver from 'foxdriver' import * as protocol from './protocol' const errors = require('../errors') diff --git a/packages/server/lib/gui/events.js b/packages/server/lib/gui/events.js index f034cef9ac77..fdd30230c632 100644 --- a/packages/server/lib/gui/events.js +++ b/packages/server/lib/gui/events.js @@ -36,6 +36,11 @@ const nullifyUnserializableValues = (obj) => { const handleEvent = function (options, bus, event, id, type, arg) { debug('got request for event: %s, %o', type, arg) + _.defaults(options, { + windowOpenFn: Windows.open, + getWindowByWebContentsFn: Windows.getByWebContents, + }) + const sendResponse = function (originalData = {}) { try { const data = nullifyUnserializableValues(originalData) @@ -161,12 +166,12 @@ const handleEvent = function (options, bus, event, id, type, arg) { .catch(sendErr) case 'window:open': - return Windows.open(options.projectRoot, arg) + return options.windowOpenFn(options.projectRoot, arg) .then(send) .catch(sendErr) case 'window:close': - return Windows.getByWebContents(event.sender).destroy() + return options.getWindowByWebContentsFn(event.sender).destroy() case 'open:file': return fileOpener.openFile(arg) diff --git a/packages/server/lib/gui/windows.js b/packages/server/lib/gui/windows.js deleted file mode 100644 index db5aff55c18f..000000000000 --- a/packages/server/lib/gui/windows.js +++ /dev/null @@ -1,327 +0,0 @@ -const _ = require('lodash') -const Promise = require('bluebird') -const cyDesktop = require('@packages/desktop-gui') -const contextMenu = require('electron-context-menu') -const { BrowserWindow } = require('electron') -const debug = require('debug')('cypress:server:windows') -const cwd = require('../cwd') -const savedState = require('../saved_state') - -let windows = {} -let recentlyCreatedWindow = false - -const getUrl = function (type) { - switch (type) { - case 'INDEX': - return cyDesktop.getPathToIndex() - default: - throw new Error(`No acceptable window type found for: '${type}'`) - } -} - -const getByType = (type) => { - return windows[type] -} - -const setWindowProxy = function (win) { - if (!process.env.HTTP_PROXY) { - return - } - - return win.webContents.session.setProxy({ - proxyRules: process.env.HTTP_PROXY, - proxyBypassRules: process.env.NO_PROXY, - }) -} - -module.exports = { - installExtension (path) { - // extensions can only be installed for all BrowserWindows - const name = BrowserWindow.addExtension(path) - - debug('electron extension installed %o', { success: !!name, name, path }) - - if (!name) { - throw new Error('Extension could not be installed.') - } - }, - - removeAllExtensions () { - const extensions = _.keys(BrowserWindow.getExtensions()) - - debug('removing all electron extensions %o', extensions) - - return extensions.forEach(BrowserWindow.removeExtension) - }, - - reset () { - windows = {} - }, - - destroy (type) { - let win - - if (type && (win = getByType(type))) { - return win.destroy() - } - }, - - get (type) { - return getByType(type) || (() => { - throw new Error(`No window exists for: '${type}'`) - })() - }, - - showAll () { - return _.invoke(windows, 'showInactive') - }, - - hideAllUnlessAnotherWindowIsFocused () { - // bail if we have another focused window - // or we are in the middle of creating a new one - if (BrowserWindow.getFocusedWindow() || recentlyCreatedWindow) { - return - } - - // else hide all windows - return _.invoke(windows, 'hide') - }, - - focusMainWindow () { - return getByType('INDEX').show() - }, - - getByWebContents (webContents) { - return BrowserWindow.fromWebContents(webContents) - }, - - _newBrowserWindow (options) { - return new BrowserWindow(options) - }, - - defaults (options = {}) { - return _.defaultsDeep(options, { - x: null, - y: null, - show: true, - frame: true, - width: null, - height: null, - minWidth: null, - minHeight: null, - devTools: false, - trackState: false, - contextMenu: false, - recordFrameRate: null, - // extension: null ## TODO add these once we update electron - // devToolsExtension: null ## since these API's were added in 1.7.6 - onFocus () {}, - onBlur () {}, - onClose () {}, - onCrashed () {}, - onNewWindow () {}, - webPreferences: { - partition: null, - webSecurity: true, - nodeIntegration: false, - backgroundThrottling: false, - }, - }) - }, - - create (projectRoot, options = {}) { - let ts - - options = this.defaults(options) - - if (options.show === false) { - options.frame = false - options.webPreferences.offscreen = true - } - - options.webPreferences.webSecurity = !!options.chromeWebSecurity - - if (options.partition) { - options.webPreferences.partition = options.partition - } - - const win = this._newBrowserWindow(options) - - win.on('blur', function (...args) { - return options.onBlur.apply(win, args) - }) - - win.on('focus', function (...args) { - return options.onFocus.apply(win, args) - }) - - win.once('closed', function (...args) { - win.removeAllListeners() - - return options.onClose.apply(win, args) - }) - - // the webview loses focus on navigation, so we - // have to refocus it everytime top navigates in headless mode - // https://github.com/cypress-io/cypress/issues/2190 - if (options.show === false) { - win.webContents.on('did-start-loading', () => { - if (!win.isDestroyed()) { - return win.focusOnWebView() - } - }) - } - - win.webContents.on('crashed', function (...args) { - return options.onCrashed.apply(win, args) - }) - - win.webContents.on('new-window', function (...args) { - return options.onNewWindow.apply(win, args) - }) - - ts = options.trackState - - if (ts) { - this.trackState(projectRoot, options.isTextTerminal, win, ts) - } - - // open dev tools if they're true - if (options.devTools) { - // and possibly detach dev tools if true - win.webContents.openDevTools() - } - - if (options.contextMenu) { - // adds context menu with copy, paste, inspect element, etc - contextMenu({ - showInspectElement: true, - window: win, - }) - } - - return win - }, - - open (projectRoot, options = {}) { - // if we already have a window open based - // on that type then just show + focus it! - let win - - win = getByType(options.type) - - if (win) { - win.show() - - return Promise.resolve(win) - } - - recentlyCreatedWindow = true - - _.defaults(options, { - width: 600, - height: 500, - show: true, - webPreferences: { - preload: cwd('lib', 'ipc', 'ipc.js'), - }, - }) - - if (!options.url) { - options.url = getUrl(options.type) - } - - win = this.create(projectRoot, options) - - debug('creating electron window with options %o', options) - - windows[options.type] = win - - win.webContents.id = _.uniqueId('webContents') - - win.once('closed', () => { - delete windows[options.type] - }) - - // enable our url to be a promise - // and wait for this to be resolved - return Promise.join( - options.url, - setWindowProxy(win), - ) - .spread((url) => { - // navigate the window here! - win.loadURL(url) - - recentlyCreatedWindow = false - }).thenReturn(win) - }, - - trackState (projectRoot, isTextTerminal, win, keys) { - const isDestroyed = () => { - return win.isDestroyed() - } - - win.on('resize', _.debounce(() => { - if (isDestroyed()) { - return - } - - const [width, height] = win.getSize() - const [x, y] = win.getPosition() - const newState = {} - - newState[keys.width] = width - newState[keys.height] = height - newState[keys.x] = x - newState[keys.y] = y - - return savedState.create(projectRoot, isTextTerminal) - .then((state) => { - return state.set(newState) - }) - } - , 500)) - - win.on('moved', _.debounce(() => { - if (isDestroyed()) { - return - } - - const [x, y] = win.getPosition() - const newState = {} - - newState[keys.x] = x - newState[keys.y] = y - - return savedState.create(projectRoot, isTextTerminal) - .then((state) => { - return state.set(newState) - }) - } - , 500)) - - win.webContents.on('devtools-opened', () => { - const newState = {} - - newState[keys.devTools] = true - - return savedState.create(projectRoot, isTextTerminal) - .then((state) => { - return state.set(newState) - }) - }) - - return win.webContents.on('devtools-closed', () => { - const newState = {} - - newState[keys.devTools] = false - - return savedState.create(projectRoot, isTextTerminal) - .then((state) => { - return state.set(newState) - }) - }) - }, - -} diff --git a/packages/server/lib/gui/windows.ts b/packages/server/lib/gui/windows.ts new file mode 100644 index 000000000000..f44a7bb6ab24 --- /dev/null +++ b/packages/server/lib/gui/windows.ts @@ -0,0 +1,332 @@ +import _ from 'lodash' +import Bluebird from 'bluebird' +import contextMenu from 'electron-context-menu' +import { BrowserWindow } from 'electron' +import Debug from 'debug' +import cwd from '../cwd' +import savedState from '../saved_state' +const cyDesktop = require('@packages/desktop-gui') + +const debug = Debug('cypress:server:windows') + +export type WindowOptions = Electron.BrowserWindowConstructorOptions & { + type?: 'INDEX' + url?: string + devTools?: boolean +} + +let windows = {} +let recentlyCreatedWindow = false + +const getUrl = function (type) { + switch (type) { + case 'INDEX': + return cyDesktop.getPathToIndex() + default: + throw new Error(`No acceptable window type found for: '${type}'`) + } +} + +const getByType = (type) => { + return windows[type] +} + +const setWindowProxy = function (win) { + if (!process.env.HTTP_PROXY) { + return + } + + return win.webContents.session.setProxy({ + proxyRules: process.env.HTTP_PROXY, + proxyBypassRules: process.env.NO_PROXY, + }) +} + +export function installExtension (win: BrowserWindow, path) { + return win.webContents.session.loadExtension(path) + .then((data) => { + debug('electron extension installed %o', { data, path }) + }) + .catch((err) => { + debug('error installing electron extension %o', { err, path }) + throw err + }) +} + +export function removeAllExtensions (win: BrowserWindow) { + let extensions + + try { + extensions = win.webContents.session.getAllExtensions() + + extensions.forEach(({ id }) => { + win.webContents.session.removeExtension(id) + }) + } catch (err) { + debug('error removing all extensions %o', { err, extensions }) + } +} + +export function reset () { + windows = {} +} + +export function destroy (type) { + let win + + if (type && (win = getByType(type))) { + return win.destroy() + } +} + +export function get (type) { + return getByType(type) || (() => { + throw new Error(`No window exists for: '${type}'`) + })() +} + +export function showAll () { + return _.invoke(windows, 'showInactive') +} + +export function hideAllUnlessAnotherWindowIsFocused () { + // bail if we have another focused window + // or we are in the middle of creating a new one + if (BrowserWindow.getFocusedWindow() || recentlyCreatedWindow) { + return + } + + // else hide all windows + return _.invoke(windows, 'hide') +} + +export function focusMainWindow () { + return getByType('INDEX').show() +} + +export function getByWebContents (webContents) { + return BrowserWindow.fromWebContents(webContents) +} + +export function _newBrowserWindow (options) { + return new BrowserWindow(options) +} + +export function defaults (options = {}) { + return _.defaultsDeep(options, { + x: null, + y: null, + show: true, + frame: true, + width: null, + height: null, + minWidth: null, + minHeight: null, + devTools: false, + trackState: false, + contextMenu: false, + recordFrameRate: null, + onFocus () {}, + onBlur () {}, + onClose () {}, + onCrashed () {}, + onNewWindow () {}, + webPreferences: { + partition: null, + webSecurity: true, + nodeIntegration: false, + backgroundThrottling: false, + }, + }) +} + +export function create (projectRoot, _options: WindowOptions = {}, newBrowserWindow = _newBrowserWindow) { + const options = defaults(_options) + + if (options.show === false) { + options.frame = false + options.webPreferences.offscreen = true + } + + options.webPreferences.webSecurity = !!options.chromeWebSecurity + + if (options.partition) { + options.webPreferences.partition = options.partition + } + + const win = newBrowserWindow(options) + + win.on('blur', function (...args) { + return options.onBlur.apply(win, args) + }) + + win.on('focus', function (...args) { + return options.onFocus.apply(win, args) + }) + + win.once('closed', function (...args) { + win.removeAllListeners() + + return options.onClose.apply(win, args) + }) + + // the webview loses focus on navigation, so we + // have to refocus it everytime top navigates in headless mode + // https://github.com/cypress-io/cypress/issues/2190 + if (options.show === false) { + win.webContents.on('did-start-loading', () => { + if (!win.isDestroyed()) { + return win.focusOnWebView() + } + }) + } + + win.webContents.on('crashed', function (...args) { + return options.onCrashed.apply(win, args) + }) + + win.webContents.on('new-window', function (...args) { + return options.onNewWindow.apply(win, args) + }) + + if (options.trackState) { + trackState(projectRoot, options.isTextTerminal, win, options.trackState) + } + + // open dev tools if they're true + if (options.devTools) { + // and possibly detach dev tools if true + win.webContents.openDevTools() + } + + if (options.contextMenu) { + // adds context menu with copy, paste, inspect element, etc + contextMenu({ + showInspectElement: true, + window: win, + }) + } + + return win +} + +export function open (projectRoot, options: WindowOptions = {}, newBrowserWindow = _newBrowserWindow) { + // if we already have a window open based + // on that type then just show + focus it! + let win + + win = getByType(options.type) + + if (win) { + win.show() + + return Bluebird.resolve(win) + } + + recentlyCreatedWindow = true + + _.defaults(options, { + width: 600, + height: 500, + show: true, + webPreferences: { + preload: cwd('lib', 'ipc', 'ipc.js'), + }, + }) + + if (!options.url) { + options.url = getUrl(options.type) + } + + win = create(projectRoot, options, newBrowserWindow) + + debug('creating electron window with options %o', options) + + if (options.type) { + windows[options.type] = win + + win.once('closed', () => { + delete windows[options.type!] + }) + } + + // enable our url to be a promise + // and wait for this to be resolved + return Bluebird.join( + options.url, + setWindowProxy(win), + ) + .spread((url) => { + // navigate the window here! + win.loadURL(url) + + recentlyCreatedWindow = false + }).thenReturn(win) +} + +export function trackState (projectRoot, isTextTerminal, win, keys) { + const isDestroyed = () => { + return win.isDestroyed() + } + + win.on('resize', _.debounce(() => { + if (isDestroyed()) { + return + } + + const [width, height] = win.getSize() + const [x, y] = win.getPosition() + const newState = {} + + newState[keys.width] = width + newState[keys.height] = height + newState[keys.x] = x + newState[keys.y] = y + + return savedState.create(projectRoot, isTextTerminal) + .then((state) => { + return state.set(newState) + }) + } + , 500)) + + win.on('moved', _.debounce(() => { + if (isDestroyed()) { + return + } + + const [x, y] = win.getPosition() + const newState = {} + + newState[keys.x] = x + newState[keys.y] = y + + return savedState.create(projectRoot, isTextTerminal) + .then((state) => { + return state.set(newState) + }) + } + , 500)) + + win.webContents.on('devtools-opened', () => { + const newState = {} + + newState[keys.devTools] = true + + return savedState.create(projectRoot, isTextTerminal) + .then((state) => { + return state.set(newState) + }) + }) + + return win.webContents.on('devtools-closed', () => { + const newState = {} + + newState[keys.devTools] = false + + return savedState.create(projectRoot, isTextTerminal) + .then((state) => { + return state.set(newState) + }) + }) +} diff --git a/packages/server/package.json b/packages/server/package.json index 5c4ed446c525..1ea6710657b2 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -20,7 +20,6 @@ "test-watch": "./test/support/watch test" }, "dependencies": { - "@benmalka/foxdriver": "0.4.0", "@cypress/browserify-preprocessor": "2.2.4", "@cypress/commit-info": "2.2.0", "@cypress/get-windows-proxy": "1.6.1", @@ -59,6 +58,7 @@ "firefox-profile": "1.3.1", "fix-path": "2.1.0", "fluent-ffmpeg": "2.1.2", + "foxdriver": "0.1.1", "fs-extra": "8.1.0", "get-port": "5.1.1", "getos": "3.2.1", diff --git a/packages/server/test/e2e/3_issue_173_spec.js b/packages/server/test/e2e/3_issue_173_spec.ts similarity index 79% rename from packages/server/test/e2e/3_issue_173_spec.js rename to packages/server/test/e2e/3_issue_173_spec.ts index d73090d33af0..dc11a1c4cf25 100644 --- a/packages/server/test/e2e/3_issue_173_spec.js +++ b/packages/server/test/e2e/3_issue_173_spec.ts @@ -1,4 +1,4 @@ -const e2e = require('../support/helpers/e2e').default +import e2e from '../support/helpers/e2e' describe('e2e issue 173', () => { e2e.setup() diff --git a/packages/server/test/support/fixtures/projects/browser-extensions/cypress/integration/spec.js b/packages/server/test/support/fixtures/projects/browser-extensions/cypress/integration/spec.js index d9cafa56b83b..0804337984fb 100644 --- a/packages/server/test/support/fixtures/projects/browser-extensions/cypress/integration/spec.js +++ b/packages/server/test/support/fixtures/projects/browser-extensions/cypress/integration/spec.js @@ -1,11 +1,5 @@ context('before:browser:launch extension e2e', () => { it('has the expected extension', () => { - if (Cypress.browser.name === 'electron') { - cy.wrap(window.top).its('theExtensionLoaded').should('be.true') - - return - } - cy.visit('/index.html') .get('#extension') .should('contain', 'inserted from extension!') diff --git a/packages/server/test/support/fixtures/projects/browser-extensions/cypress/plugins/index.js b/packages/server/test/support/fixtures/projects/browser-extensions/cypress/plugins/index.js index e708af7d7f9d..1b9a005ab36c 100644 --- a/packages/server/test/support/fixtures/projects/browser-extensions/cypress/plugins/index.js +++ b/packages/server/test/support/fixtures/projects/browser-extensions/cypress/plugins/index.js @@ -2,16 +2,7 @@ const path = require('path') module.exports = (on) => { on('before:browser:launch', (browser, options) => { - const { extensions } = options - - if (browser.name === 'electron') { - // electron doesn't support background pages yet, so load a devtools extension - // instead which will work - extensions.push(path.join(__dirname, '../../devtools-ext')) - } else { - extensions.push(path.join(__dirname, '../../../plugin-extension/ext')) - } - + options.extensions.push(path.join(__dirname, '../../../plugin-extension/ext')) options.preferences.devTools = true return options diff --git a/packages/server/test/support/fixtures/projects/browser-extensions/devtools-ext/devtools.html b/packages/server/test/support/fixtures/projects/browser-extensions/devtools-ext/devtools.html deleted file mode 100644 index 46e7f0645178..000000000000 --- a/packages/server/test/support/fixtures/projects/browser-extensions/devtools-ext/devtools.html +++ /dev/null @@ -1,15 +0,0 @@ - diff --git a/packages/server/test/support/fixtures/projects/browser-extensions/devtools-ext/manifest.json b/packages/server/test/support/fixtures/projects/browser-extensions/devtools-ext/manifest.json deleted file mode 100644 index 0b9d8264c1c7..000000000000 --- a/packages/server/test/support/fixtures/projects/browser-extensions/devtools-ext/manifest.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "e2e devtools ext", - "version": "0", - "description": "tests adding devtools extension into Cypress", - "permissions": [ - "tabs", - "webNavigation", - "" - ], - "content_scripts": [], - "devtools_page": "devtools.html", - "manifest_version": 2 -} diff --git a/packages/server/test/unit/browsers/electron_spec.js b/packages/server/test/unit/browsers/electron_spec.js index 85701d28ac64..b6eea551eac5 100644 --- a/packages/server/test/unit/browsers/electron_spec.js +++ b/packages/server/test/unit/browsers/electron_spec.js @@ -135,15 +135,15 @@ describe('lib/browsers/electron', () => { plugins.has.returns(true) plugins.execute.resolves({ extensions: ['foo', 'bar'] }) - Windows.installExtension.withArgs('bar').throws() + Windows.installExtension.withArgs(sinon.match.any, 'bar').throws() return electron.open('electron', this.url, this.options, this.automation) .then(() => { expect(Windows.removeAllExtensions).to.be.calledOnce expect(Windows.installExtension).to.be.calledTwice - expect(Windows.installExtension).to.be.calledWith('foo') - expect(Windows.installExtension).to.be.calledWith('bar') + expect(Windows.installExtension).to.be.calledWith(sinon.match.any, 'foo') + expect(Windows.installExtension).to.be.calledWith(sinon.match.any, 'bar') expect(this.options.onWarning).to.be.calledOnce diff --git a/packages/server/test/unit/browsers/firefox_spec.ts b/packages/server/test/unit/browsers/firefox_spec.ts index 6ee69a500218..28b553686792 100644 --- a/packages/server/test/unit/browsers/firefox_spec.ts +++ b/packages/server/test/unit/browsers/firefox_spec.ts @@ -7,7 +7,7 @@ import firefoxUtil from '../../../lib/browsers/firefox-util' import * as firefox from '../../../lib/browsers/firefox' import { EventEmitter } from 'events' import Marionette from 'marionette-client' -import Foxdriver from '@benmalka/foxdriver' +import Foxdriver from 'foxdriver' const mockfs = require('mock-fs') const FirefoxProfile = require('firefox-profile') const utils = require('../../../lib/browsers/utils') diff --git a/packages/server/test/unit/gui/events_spec.js b/packages/server/test/unit/gui/events_spec.js index 8c21facd7ce5..8389551f38c0 100644 --- a/packages/server/test/unit/gui/events_spec.js +++ b/packages/server/test/unit/gui/events_spec.js @@ -17,9 +17,8 @@ const openProject = require(`${root}../lib/open_project`) const open = require(`${root}../lib/util/open`) const auth = require(`${root}../lib/gui/auth`) const logs = require(`${root}../lib/gui/logs`) -const events = require(`${root}../lib/gui/events`) +const events = require(`../../../lib/gui/events`) const dialog = require(`${root}../lib/gui/dialog`) -const Windows = require(`${root}../lib/gui/windows`) const ensureUrl = require(`${root}../lib/util/ensure-url`) const konfig = require(`${root}../lib/konfig`) @@ -206,11 +205,11 @@ describe('lib/gui/events', () => { loadURL () {}, webContents: {}, }) - - return sinon.stub(Windows, 'create').withArgs(this.options.projectRoot).returns(this.win) }) - it('calls Windows#open with args and resolves with return of Windows.open', function () { + it('calls windowOpenFn with args and resolves with return', function () { + this.options.windowOpenFn = sinon.stub().rejects().withArgs({ type: 'INDEX ' }).resolves(this.win) + return this.handleEvent('window:open', { type: 'INDEX' }) .then((assert) => { return assert.sendCalledWith(events.nullifyUnserializableValues(this.win)) @@ -220,7 +219,7 @@ describe('lib/gui/events', () => { it('catches errors', function () { const err = new Error('foo') - sinon.stub(Windows, 'open').withArgs(this.options.projectRoot, { foo: 'bar' }).rejects(err) + this.options.windowOpenFn = sinon.stub().withArgs(this.options.projectRoot, { foo: 'bar' }).rejects(err) return this.handleEvent('window:open', { foo: 'bar' }).then((assert) => { return assert.sendErrCalledWith(err) @@ -230,11 +229,14 @@ describe('lib/gui/events', () => { describe('window:close', () => { it('calls destroy on Windows#getByWebContents', function () { - this.destroy = sinon.stub() - sinon.stub(Windows, 'getByWebContents').withArgs(this.event.sender).returns({ destroy: this.destroy }) + const win = { + destroy: sinon.stub(), + } + + this.options.getWindowByWebContentsFn = sinon.stub().withArgs(this.event.sender).returns(win) this.handleEvent('window:close') - expect(this.destroy).to.be.calledOnce + expect(win.destroy).to.be.calledOnce }) }) }) diff --git a/packages/server/test/unit/gui/windows_spec.js b/packages/server/test/unit/gui/windows_spec.ts similarity index 80% rename from packages/server/test/unit/gui/windows_spec.js rename to packages/server/test/unit/gui/windows_spec.ts index abfac43b39cd..952f0e4f9895 100644 --- a/packages/server/test/unit/gui/windows_spec.js +++ b/packages/server/test/unit/gui/windows_spec.ts @@ -1,13 +1,17 @@ -require('../../spec_helper') +import '../../spec_helper' + +import { expect } from 'chai' +import 'sinon-chai' + +import _ from 'lodash' +import path from 'path' +import Promise from 'bluebird' +import { EventEmitter } from 'events' +import { BrowserWindow } from 'electron' +import * as Windows from '../../../lib/gui/windows' +import savedState from '../../../lib/saved_state' -const _ = require('lodash') -const path = require('path') -const Promise = require('bluebird') -const EE = require('events').EventEmitter -const { BrowserWindow } = require('electron') const cyDesktop = require('@packages/desktop-gui') -const Windows = require(`${root}../lib/gui/windows`) -const savedState = require(`${root}../lib/saved_state`) const DEFAULT_USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Cypress/0.0.0 Chrome/59.0.3071.115 Electron/1.8.2 Safari/537.36' @@ -15,17 +19,15 @@ describe('lib/gui/windows', () => { beforeEach(function () { Windows.reset() - this.win = new EE() + this.win = new EventEmitter() this.win.loadURL = sinon.stub() this.win.destroy = sinon.stub() this.win.getSize = sinon.stub().returns([1, 2]) this.win.getPosition = sinon.stub().returns([3, 4]) - this.win.webContents = new EE() + this.win.webContents = new EventEmitter() this.win.webContents.openDevTools = sinon.stub() this.win.webContents.userAgent = DEFAULT_USER_AGENT this.win.isDestroyed = sinon.stub().returns(false) - - return sinon.stub(Windows, '_newBrowserWindow').returns(this.win) }) afterEach(() => { @@ -33,38 +35,31 @@ describe('lib/gui/windows', () => { }) context('.getByWebContents', () => { - beforeEach(() => { - return sinon.stub(BrowserWindow, 'fromWebContents') - }) - it('calls BrowserWindow.fromWebContents', () => { - BrowserWindow.fromWebContents.withArgs('foo').returns('bar') + sinon.stub(BrowserWindow, 'fromWebContents').withArgs('foo' as any).returns('bar' as any) expect(Windows.getByWebContents('foo')).to.eq('bar') }) }) context('.open', () => { - beforeEach(function () { - return sinon.stub(Windows, 'create').returns(this.win) - }) - - it('sets default options', () => { - const options = { + it('sets default options', function () { + const options: Windows.WindowOptions = { type: 'INDEX', } - return Windows.open('/path/to/project', options) + return Windows.open('/path/to/project', options, () => this.win) .then((win) => { - expect(options).to.deep.eq({ + expect(options).to.include({ height: 500, width: 600, type: 'INDEX', show: true, url: cyDesktop.getPathToIndex(), - webPreferences: { - preload: path.resolve('lib', 'ipc', 'ipc.js'), - }, + }) + + expect(options.webPreferences).to.include({ + preload: path.resolve('lib', 'ipc', 'ipc.js'), }) expect(win.loadURL).to.be.calledWith(cyDesktop.getPathToIndex()) @@ -74,10 +69,10 @@ describe('lib/gui/windows', () => { context('.create', () => { it('opens dev tools if saved state is open', function () { - Windows.create('/foo/', { devTools: true }) + Windows.create('/foo/', { devTools: true }, () => this.win) expect(this.win.webContents.openDevTools).to.be.called - Windows.create('/foo/', {}) + Windows.create('/foo/', {}, () => this.win) expect(this.win.webContents.openDevTools).not.to.be.calledTwice }) diff --git a/scripts/run-docker-local.sh b/scripts/run-docker-local.sh index 495998aa2955..27fd5c49ccf6 100755 --- a/scripts/run-docker-local.sh +++ b/scripts/run-docker-local.sh @@ -3,7 +3,7 @@ set e+x echo "This script should be run from cypress's root" -name=cypress/browsers:node12.13.0-chrome80-ff74 +name=cypress/browsers:node12.14.1-chrome83-ff77 echo "Pulling CI container $name" docker pull $name diff --git a/yarn.lock b/yarn.lock index 903933a02df9..af886e8ca276 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1427,18 +1427,6 @@ jsonpointer "^4.0.0" xtend "^4.0.0" -"@benmalka/foxdriver@0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@benmalka/foxdriver/-/foxdriver-0.4.0.tgz#aecd158a785a09e104da036efe7611a13af4ed11" - integrity sha512-Uc9n5TTRV2AhfYaqkaDc5k72pfKxyd6LDVrQJyNLZ7uY4ThgEcQMjpVRkB8C7v16gKhubH4gmD0lad5pGvDUBg== - dependencies: - fs-extra "^4.0.1" - get-port "^3.2.0" - npmlog "^4.1.2" - safe-buffer "^5.1.1" - tcp-port-used "^1.0.1" - tmp "0.0.33" - "@chromaui/localtunnel@1.10.1": version "1.10.1" resolved "https://registry.yarnpkg.com/@chromaui/localtunnel/-/localtunnel-1.10.1.tgz#34da7dab7055a16b1b9034a9eb7e3054ebec4b98" @@ -10597,10 +10585,10 @@ electron-to-chromium@^1.3.413: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.441.tgz#094f71b992dca5bc96b798cfbaf37dc76302015a" integrity sha512-leBfJwLuyGs1jEei2QioI+PjVMavmUIvPYidE8dCCYWLAq0uefhN3NYgDNb8WxD3uiUNnJ3ScMXg0upSlwySzQ== -electron@8.3.1: - version "8.3.1" - resolved "https://registry.yarnpkg.com/electron/-/electron-8.3.1.tgz#79e98c4d5b8e7c09a8a811f1aa78903f0c692721" - integrity sha512-VZpgLVFyD2SwFDkO9rwUcNgrAMah+g38FEtALGxli8bRVTbcHl8bt21szfa0YUWpc6hWcaf6JdZjqDS5q73Bsg== +electron@9.0.5: + version "9.0.5" + resolved "https://registry.yarnpkg.com/electron/-/electron-9.0.5.tgz#189ee117cc2a2777cccf40fae0766acec5faae57" + integrity sha512-bnL9H48LuQ250DML8xUscsKiuSu+xv5umXbpBXYJ0BfvYVmFfNbG3jCfhrsH7aP6UcQKVxOG1R/oQExd0EFneQ== dependencies: "@electron/get" "^1.0.1" "@types/node" "^12.0.12" @@ -12426,6 +12414,18 @@ forwarded@~0.1.2: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= +foxdriver@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/foxdriver/-/foxdriver-0.1.1.tgz#b66e0fb1f495b0139c7a0a5a5f31541b2adf0b23" + integrity sha512-ZJ6u9kzCkmDo7OVIzqF+KlMRLc+W53GCzglaLhFG1ixMliEC7o6TeucYd1XpSacpmZnPRVQE+byyR7LXC84htA== + dependencies: + fs-extra "^4.0.1" + get-port "^3.2.0" + npmlog "^4.1.2" + safe-buffer "^5.1.1" + tcp-port-used "^1.0.1" + tmp "0.0.33" + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" From 0b4529bbdbe713b6e9d7640daab6de65a7a39714 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Mon, 29 Jun 2020 14:41:50 -0400 Subject: [PATCH 03/42] feat: make cookies have sameSite key by default (#7790) * feat: make cookies have sameSite key by default BREAKING CHANGE: modifies the shape of Cookie objects * update tests * add deprecation notice Co-authored-by: Brian Mann --- cli/schema/cypress.schema.json | 5 ----- cli/types/cypress.d.ts | 6 ------ .../integration/commands/cookies_spec.js | 4 +--- packages/driver/src/cy/commands/cookies.js | 14 -------------- .../{2_cookies_spec.js => 2_cookies_spec.ts.js} | 9 ++++----- .../{4_request_spec.js => 4_request_spec.ts.js} | 0 ...subdomain_spec.js => 5_subdomain_spec.ts.js} | 0 packages/server/lib/config.js | 9 +++++---- packages/server/lib/errors.js | 5 +++++ packages/server/lib/experiments.ts | 2 -- .../{2_cookies_spec.js => 2_cookies_spec.ts} | 10 ++++------ .../{4_request_spec.js => 4_request_spec.ts} | 6 +++--- ...{5_subdomain_spec.js => 5_subdomain_spec.ts} | 10 +++++----- .../integration/cookies_spec_no_baseurl.coffee | 17 ++++++++--------- .../e2e/cypress/integration/request_spec.coffee | 4 ++-- .../cypress/integration/subdomain_spec.coffee | 2 +- packages/server/test/unit/config_spec.js | 13 +++++++++++-- 17 files changed, 49 insertions(+), 67 deletions(-) rename packages/server/__snapshots__/{2_cookies_spec.js => 2_cookies_spec.ts.js} (96%) rename packages/server/__snapshots__/{4_request_spec.js => 4_request_spec.ts.js} (100%) rename packages/server/__snapshots__/{5_subdomain_spec.js => 5_subdomain_spec.ts.js} (100%) rename packages/server/test/e2e/{2_cookies_spec.js => 2_cookies_spec.ts} (96%) rename packages/server/test/e2e/{4_request_spec.js => 4_request_spec.ts} (96%) rename packages/server/test/e2e/{5_subdomain_spec.js => 5_subdomain_spec.ts} (93%) diff --git a/cli/schema/cypress.schema.json b/cli/schema/cypress.schema.json index f68a30ee6c18..c3d2ffb22876 100644 --- a/cli/schema/cypress.schema.json +++ b/cli/schema/cypress.schema.json @@ -224,11 +224,6 @@ "default": "bundled", "description": "If set to 'system', Cypress will try to find a Node.js executable on your path to use when executing your plugins. Otherwise, Cypress will use the Node version bundled with Cypress." }, - "experimentalGetCookiesSameSite": { - "type": "boolean", - "default": false, - "description": "If `true`, Cypress will add `sameSite` values to the objects yielded from `cy.setCookie()`, `cy.getCookie()`, and `cy.getCookies()`. This will become the default behavior in Cypress 5.0." - }, "experimentalSourceRewriting": { "type": "boolean", "default": false, diff --git a/cli/types/cypress.d.ts b/cli/types/cypress.d.ts index dde1a928b720..cb4adf2ca055 100644 --- a/cli/types/cypress.d.ts +++ b/cli/types/cypress.d.ts @@ -2442,12 +2442,6 @@ declare namespace Cypress { * @default { runMode: 1, openMode: null } */ firefoxGcInterval: Nullable, openMode: Nullable }> - /** - * If `true`, Cypress will add `sameSite` values to the objects yielded from `cy.setCookie()`, - * `cy.getCookie()`, and `cy.getCookies()`. This will become the default behavior in Cypress 5.0. - * @default false - */ - experimentalGetCookiesSameSite: boolean /** * Enables AST-based JS/HTML rewriting. This may fix issues caused by the existing regex-based JS/HTML replacement * algorithm. diff --git a/packages/driver/cypress/integration/commands/cookies_spec.js b/packages/driver/cypress/integration/commands/cookies_spec.js index 896f1fd5bb2e..6d2092626075 100644 --- a/packages/driver/cypress/integration/commands/cookies_spec.js +++ b/packages/driver/cypress/integration/commands/cookies_spec.js @@ -474,9 +474,7 @@ describe('src/cy/commands/cookies', () => { }) }) - it('can set cookies with sameSite', { - experimentalGetCookiesSameSite: true, - }, () => { + it('can set cookies with sameSite', () => { Cypress.automation.restore() Cypress.utils.addTwentyYears.restore() diff --git a/packages/driver/src/cy/commands/cookies.js b/packages/driver/src/cy/commands/cookies.js index b5fda3ad6ac2..51e6d1f9ee0a 100644 --- a/packages/driver/src/cy/commands/cookies.js +++ b/packages/driver/src/cy/commands/cookies.js @@ -58,12 +58,6 @@ const normalizeSameSite = (sameSite) => { } module.exports = function (Commands, Cypress, cy, state, config) { - const maybeStripSameSiteProp = (cookie) => { - if (cookie && !Cypress.config('experimentalGetCookiesSameSite')) { - delete cookie.sameSite - } - } - const automateCookies = function (event, obj = {}, log, timeout) { const automate = () => { return Cypress.automation(event, mergeDefaults(obj)) @@ -183,8 +177,6 @@ module.exports = function (Commands, Cypress, cy, state, config) { return automateCookies('get:cookie', { name }, options._log, options.timeout) .then((resp) => { - maybeStripSameSiteProp(resp) - options.cookie = resp return resp @@ -222,10 +214,6 @@ module.exports = function (Commands, Cypress, cy, state, config) { return automateCookies('get:cookies', _.pick(options, 'domain'), options._log, options.timeout) .then((resp) => { - if (Array.isArray(resp)) { - resp.forEach(maybeStripSameSiteProp) - } - options.cookies = resp return resp @@ -299,8 +287,6 @@ module.exports = function (Commands, Cypress, cy, state, config) { return automateCookies('set:cookie', cookie, options._log, options.timeout) .then((resp) => { - maybeStripSameSiteProp(resp) - options.cookie = resp return resp diff --git a/packages/server/__snapshots__/2_cookies_spec.js b/packages/server/__snapshots__/2_cookies_spec.ts.js similarity index 96% rename from packages/server/__snapshots__/2_cookies_spec.js rename to packages/server/__snapshots__/2_cookies_spec.ts.js index 39c46f5cdafa..7bc11a5d62cb 100644 --- a/packages/server/__snapshots__/2_cookies_spec.js +++ b/packages/server/__snapshots__/2_cookies_spec.ts.js @@ -5,11 +5,10 @@ exports['e2e cookies with baseurl'] = ` (Run Starting) ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ Cypress: 1.2.3 │ - │ Browser: FooBrowser 88 │ - │ Specs: 1 found (cookies_spec_baseurl.coffee) │ - │ Searched: cypress/integration/cookies_spec_baseurl.coffee │ - │ Experiments: experimentalGetCookiesSameSite=true │ + │ Cypress: 1.2.3 │ + │ Browser: FooBrowser 88 │ + │ Specs: 1 found (cookies_spec_baseurl.coffee) │ + │ Searched: cypress/integration/cookies_spec_baseurl.coffee │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ diff --git a/packages/server/__snapshots__/4_request_spec.js b/packages/server/__snapshots__/4_request_spec.ts.js similarity index 100% rename from packages/server/__snapshots__/4_request_spec.js rename to packages/server/__snapshots__/4_request_spec.ts.js diff --git a/packages/server/__snapshots__/5_subdomain_spec.js b/packages/server/__snapshots__/5_subdomain_spec.ts.js similarity index 100% rename from packages/server/__snapshots__/5_subdomain_spec.js rename to packages/server/__snapshots__/5_subdomain_spec.ts.js diff --git a/packages/server/lib/config.js b/packages/server/lib/config.js index 8c674634a796..7f9cbd7596b7 100644 --- a/packages/server/lib/config.js +++ b/packages/server/lib/config.js @@ -93,7 +93,8 @@ configKeys.push('componentFolder') const breakingConfigKeys = toWords(`\ videoRecording screenshotOnHeadlessFailure -trashAssetsBeforeHeadlessRuns\ +trashAssetsBeforeHeadlessRuns +experimentalGetCookiesSameSite\ `) // Internal configuration properties the user should be able to overwrite @@ -105,7 +106,6 @@ browsers\ // each should start with "experimental" and be camel cased // example: experimentalComponentTesting const experimentalConfigKeys = toWords(`\ -experimentalGetCookiesSameSite experimentalSourceRewriting experimentalComponentTesting experimentalShadowDomSupport @@ -174,7 +174,6 @@ const CONFIG_DEFAULTS = { componentFolder: 'cypress/component', // TODO: example for component testing with subkeys // experimentalComponentTesting: { componentFolder: 'cypress/component' } - experimentalGetCookiesSameSite: false, experimentalSourceRewriting: false, experimentalShadowDomSupport: false, experimentalFetchPolyfill: false, @@ -222,7 +221,6 @@ const validationRules = { // validation for component testing experiment componentFolder: v.isStringOrFalse, // experimental flag validation below - experimentalGetCookiesSameSite: v.isBoolean, experimentalSourceRewriting: v.isBoolean, experimentalShadowDomSupport: v.isBoolean, experimentalFetchPolyfill: v.isBoolean, @@ -251,7 +249,10 @@ const validateNoBreakingConfig = (cfg) => { return errors.throw('RENAMED_CONFIG_OPTION', key, 'trashAssetsBeforeRuns') case 'videoRecording': return errors.throw('RENAMED_CONFIG_OPTION', key, 'video') + case 'experimentalGetCookiesSameSite': + return errors.warning('EXPERIMENTAL_SAMESITE_REMOVED') default: + throw new Error(`unknown breaking config key ${key}`) } } }) diff --git a/packages/server/lib/errors.js b/packages/server/lib/errors.js index 8797c2a48d4f..914eae146b65 100644 --- a/packages/server/lib/errors.js +++ b/packages/server/lib/errors.js @@ -923,6 +923,11 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { Enable write permissions to this directory to ensure screenshots and videos are stored. If you don't require screenshots or videos to be stored you can safely ignore this warning.` + case 'EXPERIMENTAL_SAMESITE_REMOVED': + return stripIndent`\ + The \`experimentalGetCookiesSameSite\` configuration option was removed in Cypress 5. Yielding the \`sameSite\` property is now the default behavior of the \`cy.cookie\` commands. + + You can safely remove this option from your config.` default: } } diff --git a/packages/server/lib/experiments.ts b/packages/server/lib/experiments.ts index 610bf5d194e3..e365e019ac56 100644 --- a/packages/server/lib/experiments.ts +++ b/packages/server/lib/experiments.ts @@ -53,7 +53,6 @@ interface StringValues { const _summaries: StringValues = { experimentalComponentTesting: 'Framework-specific component testing, uses `componentFolder` to load component specs', experimentalSourceRewriting: 'Enables AST-based JS/HTML rewriting. This may fix issues caused by the existing regex-based JS/HTML replacement algorithm.', - experimentalGetCookiesSameSite: 'Adds `sameSite` values to the objects yielded from `cy.setCookie()`, `cy.getCookie()`, and `cy.getCookies()`. This will become the default behavior in Cypress 5.0.', experimentalFetchPolyfill: 'Polyfills `window.fetch` to enable Network spying and stubbing', experimentalShadowDomSupport: 'Enables support for shadow DOM traversal, introduces the `shadow()` command and the `includeShadowDom` option to traversal commands.', } @@ -71,7 +70,6 @@ const _summaries: StringValues = { const _names: StringValues = { experimentalComponentTesting: 'Component Testing', experimentalSourceRewriting: 'Improved source rewriting', - experimentalGetCookiesSameSite: 'Set `sameSite` property when retrieving cookies', experimentalShadowDomSupport: 'Shadow DOM Support', experimentalFetchPolyfill: 'Fetch polyfill', } diff --git a/packages/server/test/e2e/2_cookies_spec.js b/packages/server/test/e2e/2_cookies_spec.ts similarity index 96% rename from packages/server/test/e2e/2_cookies_spec.js rename to packages/server/test/e2e/2_cookies_spec.ts index 2b9d06263068..83a10d35886e 100644 --- a/packages/server/test/e2e/2_cookies_spec.js +++ b/packages/server/test/e2e/2_cookies_spec.ts @@ -1,7 +1,7 @@ -const moment = require('moment') -const parser = require('cookie-parser') -const e2e = require('../support/helpers/e2e').default -const humanInterval = require('human-interval') +import moment from 'moment' +import parser from 'cookie-parser' +import e2e from '../support/helpers/e2e' +import humanInterval from 'human-interval' const onServer = function (app) { app.use(parser()) @@ -194,7 +194,6 @@ describe('e2e cookies', () => { // we can remove this extra test case e2e.it('with forced SameSite strictness', { config: { - experimentalGetCookiesSameSite: true, baseUrl, env: { baseUrl, @@ -248,7 +247,6 @@ describe('e2e cookies', () => { ) => { e2e.it(`passes with baseurl: ${baseUrl}`, { config: { - experimentalGetCookiesSameSite: true, baseUrl, env: { baseUrl, diff --git a/packages/server/test/e2e/4_request_spec.js b/packages/server/test/e2e/4_request_spec.ts similarity index 96% rename from packages/server/test/e2e/4_request_spec.js rename to packages/server/test/e2e/4_request_spec.ts index fa33ab6e6105..e857488bcacf 100644 --- a/packages/server/test/e2e/4_request_spec.js +++ b/packages/server/test/e2e/4_request_spec.ts @@ -1,6 +1,6 @@ -const bodyParser = require('body-parser') -const cookieParser = require('cookie-parser') -const e2e = require('../support/helpers/e2e').default +import bodyParser from 'body-parser' +import cookieParser from 'cookie-parser' +import e2e from '../support/helpers/e2e' let counts = null diff --git a/packages/server/test/e2e/5_subdomain_spec.js b/packages/server/test/e2e/5_subdomain_spec.ts similarity index 93% rename from packages/server/test/e2e/5_subdomain_spec.js rename to packages/server/test/e2e/5_subdomain_spec.ts index ebcf82fb2109..568330b5a5b2 100644 --- a/packages/server/test/e2e/5_subdomain_spec.js +++ b/packages/server/test/e2e/5_subdomain_spec.ts @@ -1,7 +1,7 @@ -const cors = require('cors') -const parser = require('cookie-parser') -const session = require('express-session') -const e2e = require('../support/helpers/e2e').default +import cors from 'cors' +import parser from 'cookie-parser' +import session from 'express-session' +import e2e from '../support/helpers/e2e' const onServer = function (app) { app.use(parser()) @@ -48,7 +48,7 @@ const onServer = function (app) { cookie: { sameSite: true, }, - }) + }) as Function app.get('/htmlCookies', (req, res) => { const { diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/cookies_spec_no_baseurl.coffee b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/cookies_spec_no_baseurl.coffee index ebe46a3cd9a9..9fd762d558f9 100644 --- a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/cookies_spec_no_baseurl.coffee +++ b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/cookies_spec_no_baseurl.coffee @@ -12,6 +12,11 @@ describe "cookies", -> }) it "can get all cookies", -> + expectedKeys = ["domain", "name", "value", "path", "secure", "httpOnly", "expiry"] + + if Cypress.isBrowser('firefox') + expectedKeys.push('sameSite') + cy .clearCookie("foo1") .setCookie("foo", "bar").then (c) -> @@ -23,9 +28,7 @@ describe "cookies", -> 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" - ) + expect(c).to.have.keys(expectedKeys) .getCookies() .should("have.length", 1) .then (cookies) -> @@ -39,9 +42,7 @@ describe "cookies", -> 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" - ) + expect(c).to.have.keys(expectedKeys) .clearCookies() .should("be.null") .setCookie("wtf", "bob", {httpOnly: true, path: "/foo", secure: true}) @@ -54,9 +55,7 @@ describe "cookies", -> 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" - ) + expect(c).to.have.keys(expectedKeys) .clearCookie("wtf") .should("be.null") .getCookie("doesNotExist") diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/request_spec.coffee b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/request_spec.coffee index d59ea40bab53..5b96e92d988c 100644 --- a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/request_spec.coffee +++ b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/request_spec.coffee @@ -20,14 +20,14 @@ describe "redirects + requests", -> expect(cookies[0].secure).to.eq(false) expect(cookies[0].expiry).to.be.closeTo(oneMinuteFromNow, 5) - expect(cookies[1]).to.deep.eq({ + expect(cookies[1]).to.deep.eq(Cypress._.merge({ domain: "localhost" name: "2293-session" value: "true" httpOnly: false path: "/" secure: false - }) + }, (if Cypress.isBrowser('firefox') then { sameSite: 'no_restriction' } else {}))) it "visits to a different superdomain will be resolved twice", -> cy 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 08d712e80cd3..6af1d6a2b348 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 @@ -42,7 +42,7 @@ describe "subdomains", -> cy .visit("http://domain.foobar.com:2292") .getCookies().should("have.length", 1) - .getCookie("nomnom").should("deep.eq", { + .getCookie("nomnom").should("include", { domain: ".foobar.com" name: "nomnom" value: "good" diff --git a/packages/server/test/unit/config_spec.js b/packages/server/test/unit/config_spec.js index 9c821ccb04d5..e41d83f737ab 100644 --- a/packages/server/test/unit/config_spec.js +++ b/packages/server/test/unit/config_spec.js @@ -1079,6 +1079,17 @@ describe('lib/config', () => { }) }) + // @see https://github.com/cypress-io/cypress/issues/6892 + it('warns if experimentalGetCookiesSameSite is passed', async function () { + const warning = sinon.spy(errors, 'warning') + + await this.defaults('experimentalGetCookiesSameSite', true, { + experimentalGetCookiesSameSite: true, + }) + + expect(warning).to.be.calledWith('EXPERIMENTAL_SAMESITE_REMOVED') + }) + describe('.resolved', () => { it('sets reporter and port to cli', () => { const obj = { @@ -1108,7 +1119,6 @@ describe('lib/config', () => { requestTimeout: { value: 5000, from: 'default' }, responseTimeout: { value: 30000, from: 'default' }, execTimeout: { value: 60000, from: 'default' }, - experimentalGetCookiesSameSite: { value: false, from: 'default' }, experimentalSourceRewriting: { value: false, from: 'default' }, taskTimeout: { value: 60000, from: 'default' }, numTestsKeptInMemory: { value: 50, from: 'default' }, @@ -1185,7 +1195,6 @@ describe('lib/config', () => { requestTimeout: { value: 5000, from: 'default' }, responseTimeout: { value: 30000, from: 'default' }, execTimeout: { value: 60000, from: 'default' }, - experimentalGetCookiesSameSite: { value: false, from: 'default' }, experimentalSourceRewriting: { value: false, from: 'default' }, taskTimeout: { value: 60000, from: 'default' }, numTestsKeptInMemory: { value: 50, from: 'default' }, From f75cc4509fbf8014a19956c43b071891c7f081d7 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Mon, 29 Jun 2020 15:28:38 -0400 Subject: [PATCH 04/42] fix foxdriver patching --- packages/server/package.json | 2 +- .../patches/@benmalka+foxdriver+0.4.0.patch | 49 ------------------- packages/server/patches/foxdriver+0.1.1.patch | 46 +++++++++++++++++ 3 files changed, 47 insertions(+), 50 deletions(-) delete mode 100644 packages/server/patches/@benmalka+foxdriver+0.4.0.patch create mode 100644 packages/server/patches/foxdriver+0.1.1.patch diff --git a/packages/server/package.json b/packages/server/package.json index 1ea6710657b2..b866fc160486 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -198,7 +198,7 @@ "productName": "Cypress", "workspaces": { "nohoist": [ - "@benmalka/foxdriver" + "foxdriver" ] }, "optionalDependencies": { diff --git a/packages/server/patches/@benmalka+foxdriver+0.4.0.patch b/packages/server/patches/@benmalka+foxdriver+0.4.0.patch deleted file mode 100644 index 5bf691110afb..000000000000 --- a/packages/server/patches/@benmalka+foxdriver+0.4.0.patch +++ /dev/null @@ -1,49 +0,0 @@ -diff --git a/node_modules/@benmalka/foxdriver/build/logger.js b/node_modules/@benmalka/foxdriver/build/logger.js -index c28401c..bfb5da4 100644 ---- a/node_modules/@benmalka/foxdriver/build/logger.js -+++ b/node_modules/@benmalka/foxdriver/build/logger.js -@@ -5,9 +5,7 @@ Object.defineProperty(exports, "__esModule", { - }); - exports.default = Logger; - --var _npmlog = require("npmlog"); -- --var _npmlog2 = _interopRequireDefault(_npmlog); -+const debug = require('debug')('@benmalka/foxdriver') - - var _package = require("../package.json"); - -@@ -20,11 +18,6 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de - */ - const NPM_LEVELS = ['silly', 'verbose', 'debug', 'info', 'http', 'warn', 'error', 'chrome', 'firefox']; - --_npmlog2.default.addLevel('debug', 1000, { -- fg: 'blue', -- bg: 'black' --}, 'dbug'); -- - function Logger(component) { - const wrappedLogger = {}; - const prefix = _package2.default.name + (component ? `:${component}` : ''); -@@ -34,10 +27,8 @@ function Logger(component) { - - Object.defineProperty(wrappedLogger, 'level', { - get: () => { -- return _npmlog2.default.level; - }, - set: newValue => { -- _npmlog2.default.level = newValue; - }, - enumerable: true, - configurable: true -@@ -48,8 +39,8 @@ function Logger(component) { - - for (let level of NPM_LEVELS) { - wrappedLogger[level] = (...args) => { -- if (!process.env.DEBUG) return; -- return _npmlog2.default[level](prefix, ...args); -+ // @see https://github.com/cypress-io/cypress/issues/7723 -+ debug(prefix, ...args) - }; - } - diff --git a/packages/server/patches/foxdriver+0.1.1.patch b/packages/server/patches/foxdriver+0.1.1.patch new file mode 100644 index 000000000000..80eb15350c95 --- /dev/null +++ b/packages/server/patches/foxdriver+0.1.1.patch @@ -0,0 +1,46 @@ +diff --git a/node_modules/foxdriver/build/logger.js b/node_modules/foxdriver/build/logger.js +index 80e315d..c160f30 100644 +--- a/node_modules/foxdriver/build/logger.js ++++ b/node_modules/foxdriver/build/logger.js +@@ -7,7 +7,7 @@ Object.defineProperty(exports, "__esModule", { + }); + exports.default = Logger; + +-var _npmlog = _interopRequireDefault(require("npmlog")); ++const debug = require('debug')('foxdriver') + + var _package = _interopRequireDefault(require("../package.json")); + +@@ -16,10 +16,6 @@ var _package = _interopRequireDefault(require("../package.json")); + */ + const NPM_LEVELS = ['silly', 'verbose', 'debug', 'info', 'http', 'warn', 'error', 'chrome', 'firefox']; + +-_npmlog.default.addLevel('debug', 1000, { +- fg: 'blue', +- bg: 'black' +-}, 'dbug'); + + function Logger(component) { + const wrappedLogger = {}; +@@ -30,10 +26,8 @@ function Logger(component) { + + Object.defineProperty(wrappedLogger, 'level', { + get: () => { +- return _npmlog.default.level; + }, + set: newValue => { +- _npmlog.default.level = newValue; + }, + enumerable: true, + configurable: true +@@ -44,8 +38,8 @@ function Logger(component) { + + for (let level of NPM_LEVELS) { + wrappedLogger[level] = (...args) => { +- if (!process.env.DEBUG) return; +- return _npmlog.default[level](prefix, ...args); ++ // @see https://github.com/cypress-io/cypress/issues/7723 ++ debug(prefix, ...args) + }; + } + From e767bcfe4b54b0baf7f5e11d43908f4bed29b957 Mon Sep 17 00:00:00 2001 From: Jennifer Shehane Date: Tue, 30 Jun 2020 06:47:12 +0630 Subject: [PATCH 05/42] Drop support for Node 8; Require Node 10+ (#7650) * Require Node 10+ for cli engine * update chalk to latest (requires Node 10+) * Update execa to latest * Update cli-table3 to latest (required Node 10+) * Update log-symbols (requires Node 10+) * Update tmp dep (requires Node >=8.17.0) * Update fs-extra dep (requires Node 10) * Update mock-fs to 4.12.0 * increase circle base image to node 10 * fix flaky firefox test * make job names consistent * reconfigure percy parallelism, finalize percy after running visual jobs, remove separate desktop-gui-visual-tests job * rename job correctly Co-authored-by: Brian Mann --- circle.yml | 133 ++++++++---------- cli/package.json | 16 +-- .../integration/e2e/uncaught_errors_spec.js | 18 ++- packages/server/package.json | 2 +- yarn.lock | 50 ++++++- 5 files changed, 129 insertions(+), 90 deletions(-) diff --git a/circle.yml b/circle.yml index bd56932af9f2..02bf785edd1b 100644 --- a/circle.yml +++ b/circle.yml @@ -398,7 +398,8 @@ jobs: - run: name: Upload CLI snapshots for diffing command: | - PERCY_TOKEN=$PERCY_TOKEN_CLI \ + PERCY_PARALLEL_NONCE=$CIRCLE_WORKFLOW_ID \ + PERCY_PARALLEL_TOTAL=-1 \ yarn percy snapshot ./cli/visual-snapshots unit-tests: @@ -457,7 +458,7 @@ jobs: # test binary build code - run: yarn test-scripts - "server-unit-tests": + server-unit-tests: <<: *defaults parallelism: 2 steps: @@ -470,7 +471,7 @@ jobs: path: /tmp/cypress - store-npm-logs - "server-integration-tests": + server-integration-tests: <<: *defaults parallelism: 2 steps: @@ -483,7 +484,7 @@ jobs: path: /tmp/cypress - store-npm-logs - "server-performance-tests": + server-performance-tests: <<: *defaults steps: - attach_workspace: @@ -498,216 +499,216 @@ jobs: path: /tmp/artifacts - store-npm-logs - "server-e2e-tests-chrome-1": + server-e2e-tests-chrome-1: <<: *defaults steps: - run-e2e-tests: browser: chrome chunk: "1" - "server-e2e-tests-chrome-2": + server-e2e-tests-chrome-2: <<: *defaults steps: - run-e2e-tests: browser: chrome chunk: "2" - "server-e2e-tests-chrome-3": + server-e2e-tests-chrome-3: <<: *defaults steps: - run-e2e-tests: browser: chrome chunk: "3" - "server-e2e-tests-chrome-4": + server-e2e-tests-chrome-4: <<: *defaults steps: - run-e2e-tests: browser: chrome chunk: "4" - "server-e2e-tests-chrome-5": + server-e2e-tests-chrome-5: <<: *defaults steps: - run-e2e-tests: browser: chrome chunk: "5" - "server-e2e-tests-chrome-6": + server-e2e-tests-chrome-6: <<: *defaults steps: - run-e2e-tests: browser: chrome chunk: "6" - "server-e2e-tests-chrome-7": + server-e2e-tests-chrome-7: <<: *defaults steps: - run-e2e-tests: browser: chrome chunk: "7" - "server-e2e-tests-chrome-8": + server-e2e-tests-chrome-8: <<: *defaults steps: - run-e2e-tests: browser: chrome chunk: "8" - "server-e2e-tests-electron-1": + server-e2e-tests-electron-1: <<: *defaults steps: - run-e2e-tests: browser: electron chunk: "1" - "server-e2e-tests-electron-2": + server-e2e-tests-electron-2: <<: *defaults steps: - run-e2e-tests: browser: electron chunk: "2" - "server-e2e-tests-electron-3": + server-e2e-tests-electron-3: <<: *defaults steps: - run-e2e-tests: browser: electron chunk: "3" - "server-e2e-tests-electron-4": + server-e2e-tests-electron-4: <<: *defaults steps: - run-e2e-tests: browser: electron chunk: "4" - "server-e2e-tests-electron-5": + server-e2e-tests-electron-5: <<: *defaults steps: - run-e2e-tests: browser: electron chunk: "5" - "server-e2e-tests-electron-6": + server-e2e-tests-electron-6: <<: *defaults steps: - run-e2e-tests: browser: electron chunk: "6" - "server-e2e-tests-electron-7": + server-e2e-tests-electron-7: <<: *defaults steps: - run-e2e-tests: browser: electron chunk: "7" - "server-e2e-tests-electron-8": + server-e2e-tests-electron-8: <<: *defaults steps: - run-e2e-tests: browser: electron chunk: "8" - "server-e2e-tests-non-root": + server-e2e-tests-non-root: <<: *defaults steps: - run-e2e-tests: chunk: non_root - "server-e2e-tests-firefox-1": + server-e2e-tests-firefox-1: <<: *defaults steps: - run-e2e-tests: browser: firefox chunk: "1" - "server-e2e-tests-firefox-2": + server-e2e-tests-firefox-2: <<: *defaults steps: - run-e2e-tests: browser: firefox chunk: "2" - "server-e2e-tests-firefox-3": + server-e2e-tests-firefox-3: <<: *defaults steps: - run-e2e-tests: browser: firefox chunk: "3" - "server-e2e-tests-firefox-4": + server-e2e-tests-firefox-4: <<: *defaults steps: - run-e2e-tests: browser: firefox chunk: "4" - "server-e2e-tests-firefox-5": + server-e2e-tests-firefox-5: <<: *defaults steps: - run-e2e-tests: browser: firefox chunk: "5" - "server-e2e-tests-firefox-6": + server-e2e-tests-firefox-6: <<: *defaults steps: - run-e2e-tests: browser: firefox chunk: "6" - "server-e2e-tests-firefox-7": + server-e2e-tests-firefox-7: <<: *defaults steps: - run-e2e-tests: browser: firefox chunk: "7" - "server-e2e-tests-firefox-8": + server-e2e-tests-firefox-8: <<: *defaults steps: - run-e2e-tests: browser: firefox chunk: "8" - "runner-integration-tests-chrome": + runner-integration-tests-chrome: <<: *defaults parallelism: 2 steps: - run-runner-integration-tests: browser: chrome - "runner-integration-tests-firefox": + runner-integration-tests-firefox: <<: *defaults parallelism: 2 steps: - run-runner-integration-tests: browser: firefox - "driver-integration-tests-chrome": + driver-integration-tests-chrome: <<: *defaults parallelism: 5 steps: - run-driver-integration-tests: browser: chrome - # "driver-integration-tests-electron": + # driver-integration-tests-electron: # <<: *defaults # parallelism: 5 # steps: # - run-driver-integration-tests: # browser: electron - "driver-integration-tests-firefox": + driver-integration-tests-firefox: <<: *defaults parallelism: 5 steps: - run-driver-integration-tests: browser: firefox - "desktop-gui-integration-tests-2x": + desktop-gui-integration-tests-2x: <<: *defaults parallelism: 2 steps: @@ -725,6 +726,9 @@ jobs: command: | CYPRESS_KONFIG_ENV=production \ CYPRESS_RECORD_KEY=$PACKAGES_RECORD_KEY \ + PERCY_PARALLEL_NONCE=$CIRCLE_WORKFLOW_ID \ + PERCY_PARALLEL_TOTAL=-1 \ + yarn percy exec -- \ yarn cypress:run --record --parallel --group 2x-desktop-gui working_directory: packages/desktop-gui - verify-mocha-results @@ -734,38 +738,6 @@ jobs: path: /tmp/artifacts - store-npm-logs - desktop-gui-visual-tests: - <<: *defaults - parallelism: 1 - steps: - - attach_workspace: - at: ~/ - - run: - command: yarn build-prod - working_directory: packages/desktop-gui - - run: - name: Desktop GUI server - command: yarn start - working_directory: packages/desktop-gui - background: true - - run: - # will use PERCY_TOKEN environment variable if available - # to tie Percy builds together, use environment variables - # https://docs.percy.io/docs/parallel-test-suites - # we can use workflow id and list number of jobs with Percy screenshots - # if we allow Percy snapshots in more jobs, use the same - # PERCY_* variables in all of them - command: | - CYPRESS_KONFIG_ENV=production \ - PERCY_PARALLEL_NONCE=$CIRCLE_WORKFLOW_ID \ - PERCY_PARALLEL_TOTAL=1 \ - yarn percy exec -- \ - yarn cypress:run --spec cypress/integration/settings_spec.js,cypress/integration/specs_list_spec.js - working_directory: packages/desktop-gui - - verify-mocha-results - # we don't really need any artifacts - we are only interested in visual screenshots - - store-npm-logs - desktop-gui-component-tests: <<: *defaults parallelism: 1 @@ -792,7 +764,7 @@ jobs: command: | CYPRESS_KONFIG_ENV=production \ PERCY_PARALLEL_NONCE=$CIRCLE_WORKFLOW_ID \ - PERCY_PARALLEL_TOTAL=1 \ + PERCY_PARALLEL_TOTAL=-1 \ yarn percy exec -- \ yarn cypress:run --spec 'src/**/*_spec.jsx' working_directory: packages/desktop-gui @@ -800,7 +772,7 @@ jobs: # we don't really need any artifacts - we are only interested in visual screenshots - store-npm-logs - "reporter-integration-tests": + reporter-integration-tests: <<: *defaults steps: - attach_workspace: @@ -821,7 +793,7 @@ jobs: path: /tmp/artifacts - store-npm-logs - "ui-components-integration-tests": + ui-components-integration-tests: <<: *defaults steps: - attach_workspace: @@ -842,7 +814,7 @@ jobs: path: /tmp/artifacts - store-npm-logs - "run-launcher": + run-launcher: <<: *defaults steps: - attach_workspace: @@ -851,6 +823,13 @@ jobs: command: node index.js working_directory: packages/launcher + percy-finalize: + <<: *defaults + steps: + - run: + name: "finalizes percy builds" + command: npx percy finalize --all + build-binary: <<: *defaults shell: /bin/bash --login @@ -1134,7 +1113,7 @@ jobs: test-npm-module-on-minimum-node-version: <<: *defaults docker: - - image: cypress/base:8.0.0 + - image: cypress/base:10.0.0 steps: - attach_workspace: at: ~/ @@ -1577,9 +1556,6 @@ linux-workflow: &linux-workflow - desktop-gui-integration-tests-2x: requires: - build - - desktop-gui-visual-tests: - requires: - - build - desktop-gui-component-tests: requires: - build @@ -1592,6 +1568,13 @@ linux-workflow: &linux-workflow - run-launcher: requires: - build + + - percy-finalize: + requires: + - desktop-gui-integration-tests-2x + - desktop-gui-component-tests + - cli-visual-tests + # various testing scenarios, like building full binary # and testing it on a real project - test-against-staging: diff --git a/cli/package.json b/cli/package.json index bfff011e8b86..52fc50f53f3a 100644 --- a/cli/package.json +++ b/cli/package.json @@ -28,24 +28,24 @@ "arch": "2.1.2", "bluebird": "3.7.2", "cachedir": "2.3.0", - "chalk": "2.4.2", + "chalk": "4.1.0", "check-more-types": "2.24.0", - "cli-table3": "0.5.1", + "cli-table3": "0.6.0", "commander": "4.1.1", "common-tags": "1.8.0", "debug": "4.1.1", "eventemitter2": "6.4.2", - "execa": "1.0.0", + "execa": "4.0.2", "executable": "4.1.1", "extract-zip": "1.7.0", - "fs-extra": "8.1.0", + "fs-extra": "9.0.1", "getos": "3.2.1", "is-ci": "2.0.0", "is-installed-globally": "0.3.2", "lazy-ass": "1.6.0", "listr": "0.14.3", "lodash": "4.17.15", - "log-symbols": "3.0.0", + "log-symbols": "4.0.0", "minimist": "1.2.5", "moment": "2.26.0", "ospath": "1.2.2", @@ -53,7 +53,7 @@ "ramda": "0.26.1", "request-progress": "3.0.0", "supports-color": "7.1.0", - "tmp": "0.1.0", + "tmp": "0.2.1", "untildify": "4.0.0", "url": "0.11.0", "yauzl": "2.10.0" @@ -82,7 +82,7 @@ "execa-wrap": "1.4.0", "hasha": "5.0.0", "mocha": "6.2.2", - "mock-fs": "4.9.0", + "mock-fs": "4.12.0", "mocked-env": "1.2.4", "nock": "12.0.2", "postinstall-postinstall": "2.0.0", @@ -104,7 +104,7 @@ "cypress": "bin/cypress" }, "engines": { - "node": ">=8.0.0" + "node": ">=10.0.0" }, "types": "types" } diff --git a/packages/driver/cypress/integration/e2e/uncaught_errors_spec.js b/packages/driver/cypress/integration/e2e/uncaught_errors_spec.js index 6651e0aad5ca..bd3b3af01ddf 100644 --- a/packages/driver/cypress/integration/e2e/uncaught_errors_spec.js +++ b/packages/driver/cypress/integration/e2e/uncaught_errors_spec.js @@ -117,7 +117,18 @@ describe('uncaught errors', () => { expect(uncaught).to.be.true expect(err.message).to.include('foo is not defined') expect(click.get('name')).to.eq('click') - expect(click.get('error')).to.eq(err) + + // TODO: when there's an uncaught exception event + // we should log this to the command log so then + // we could update this test to always reference + // that command log + // + // FIXME: in firefox this test sometimes fails + // because the cy.click() command resolves before + // the page navigation event occurs and therefore + // the state('current') command is null'd out and + // firefox does not highlight the click command in read + // expect(click.get('error')).to.eq(err) done() }) @@ -127,7 +138,10 @@ describe('uncaught errors', () => { .window().then((win) => { return win.$('visit') .appendTo(win.document.body) - }).contains('visit').click() + }) + .contains('visit').click() + + cy.url().should('include', 'visit_error.html') }) // https://github.com/cypress-io/cypress/issues/987 diff --git a/packages/server/package.json b/packages/server/package.json index b866fc160486..2e0ff8c23c17 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -170,7 +170,7 @@ "mochawesome-1.5.2": "npm:mochawesome@1.5.2", "mochawesome-2.3.1": "npm:mochawesome@2.3.1", "mochawesome-3.0.1": "npm:mochawesome@3.0.1", - "mock-fs": "4.10.4", + "mock-fs": "4.12.0", "mocked-env": "1.2.4", "mockery": "2.1.0", "multiparty": "4.2.1", diff --git a/yarn.lock b/yarn.lock index af886e8ca276..de18f201eb44 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8023,6 +8023,14 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3. escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" @@ -8332,6 +8340,16 @@ cli-table3@0.5.1: optionalDependencies: colors "^1.1.2" +cli-table3@0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.0.tgz#b7b1bc65ca8e7b5cef9124e13dc2b21e2ce4faee" + integrity sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ== + dependencies: + object-assign "^4.1.0" + string-width "^4.2.0" + optionalDependencies: + colors "^1.1.2" + cli-truncate@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574" @@ -12489,6 +12507,16 @@ fs-extra@9.0.0, fs-extra@^9.0.0: jsonfile "^6.0.1" universalify "^1.0.0" +fs-extra@9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" + integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^1.0.0" + fs-extra@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-1.0.0.tgz#cd3ce5f7e7cb6145883fcae3191e9877f8587950" @@ -16895,6 +16923,13 @@ log-symbols@3.0.0, log-symbols@^3.0.0: dependencies: chalk "^2.4.2" +log-symbols@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920" + integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA== + dependencies: + chalk "^4.0.0" + log-symbols@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18" @@ -17954,10 +17989,10 @@ mochawesome-report-generator@^3.0.1: validator "^9.1.2" yargs "^10.0.3" -mock-fs@4.10.4: - version "4.10.4" - resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.10.4.tgz#4eaa3d6f7da2f44e1f3dd6b462cbbcb7b082e3d4" - integrity sha512-gDfZDLaPIvtOusbusLinfx6YSe2YpQsDT8qdP41P47dQ/NQggtkHukz7hwqgt8QvMBmAv+Z6DGmXPyb5BWX2nQ== +mock-fs@4.12.0: + version "4.12.0" + resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.12.0.tgz#a5d50b12d2d75e5bec9dac3b67ffe3c41d31ade4" + integrity sha512-/P/HtrlvBxY4o/PzXY9cCNBrdylDNxg7gnrv2sMNxj+UJ2m8jSpl0/A6fuJeNAWr99ZvGWH8XCbE0vmnM5KupQ== mock-fs@4.9.0: version "4.9.0" @@ -24294,6 +24329,13 @@ tmp@0.1.0, tmp@^0.1.0: dependencies: rimraf "^2.6.3" +tmp@0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" + integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== + dependencies: + rimraf "^3.0.0" + tmpl@1.0.x: version "1.0.4" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" From 639df99036c09dafb0007db078a2f5c402287e4d Mon Sep 17 00:00:00 2001 From: Jennifer Shehane Date: Tue, 30 Jun 2020 12:57:29 +0630 Subject: [PATCH 06/42] Rename uses of term 'whitelist' (#7782) * Rename non-user facing instances of whitelist * Rename server option 'whitelist' to 'ignore' * Update use of whitelist with server to throw instead of warn * Rename Cypress.Cookies.defaults 'whitelist' option to 'preserve' * fix circle yml parameter parsing consistent * compose cloning an external repo and switching to the NEXT_DEV_VERSION branch consistently * add cypress org to repo parameter * cd into the right dir before switching branches * one line git checkout * simplify passing repo * cd into the right dir * clone into the right dir * oh my cd 101 * replace kitchen sink strings for 5.0.0 Co-authored-by: Brian Mann --- circle.yml | 138 ++++++------- cli/types/cypress.d.ts | 6 +- cli/types/tests/kitchen-sink.ts | 2 +- .../integration/commands/cookies_spec.js | 14 ++ .../cypress/integration/commands/xhr_spec.js | 187 +++++------------- packages/driver/src/cy/commands/cookies.js | 2 +- packages/driver/src/cy/keyboard.ts | 2 +- packages/driver/src/cypress/cookies.js | 16 +- packages/driver/src/cypress/error_messages.js | 6 + packages/driver/src/cypress/server.js | 29 +-- packages/example/bin/convert.js | 5 + .../server/__snapshots__/2_cookies_spec.ts.js | 8 +- packages/server/lib/config.js | 6 +- packages/server/lib/project.js | 4 +- packages/server/lib/saved_state.js | 12 +- packages/server/lib/server.js | 10 +- packages/server/lib/util/args.js | 10 +- packages/server/lib/util/socket_allowed.ts | 46 +++++ packages/server/lib/util/socket_whitelist.ts | 46 ----- .../cypress/integration/app_spec.coffee | 3 +- .../integration/cookies_spec_baseurl.coffee | 8 +- .../cookies_spec_no_baseurl.coffee | 8 +- packages/server/test/unit/args_spec.js | 2 +- packages/server/test/unit/saved_state_spec.js | 4 +- packages/server/test/unit/screenshots_spec.js | 2 +- packages/server/test/unit/server_spec.js | 2 +- .../test/unit/util/socket_allowed_spec.ts | 42 ++++ .../test/unit/util/socket_whitelist_spec.ts | 42 ---- 28 files changed, 306 insertions(+), 356 deletions(-) create mode 100644 packages/server/lib/util/socket_allowed.ts delete mode 100644 packages/server/lib/util/socket_whitelist.ts create mode 100644 packages/server/test/unit/util/socket_allowed_spec.ts delete mode 100644 packages/server/test/unit/util/socket_whitelist_spec.ts diff --git a/circle.yml b/circle.yml index 02bf785edd1b..18d0cbea5cd7 100644 --- a/circle.yml +++ b/circle.yml @@ -77,14 +77,14 @@ commands: - run: name: Install latest Google Chrome (stable) command: | - if [ << parameters.browser >> == "chrome" ]; then + if [ <> == "chrome" ]; then echo "**** Running Chrome tests. Installing latest stable version of Google Chrome. ****" apt-get update apt-get install google-chrome-stable -y echo "**** Location of Google Chrome Installation: "`which google-chrome`" ****" echo "**** Google Chrome Version: "`google-chrome --version`" ****" else - echo "**** Not updating Chrome. Running tests in '<< parameters.browser >>' ****" + echo "**** Not updating Chrome. Running tests in '<>' ****" fi run-driver-integration-tests: @@ -105,7 +105,7 @@ commands: if [[ -v PACKAGES_RECORD_KEY ]]; then # internal PR CYPRESS_RECORD_KEY=$PACKAGES_RECORD_KEY \ - yarn cypress:run --record --parallel --group 5x-driver-<< parameters.browser >> --browser << parameters.browser >> + yarn cypress:run --record --parallel --group 5x-driver-<> --browser <> else # external PR TESTFILES=$(circleci tests glob "cypress/integration/**/*_spec.*" | circleci tests split --total=$CIRCLE_NODE_TOTAL) @@ -114,7 +114,7 @@ commands: if [[ -z "$TESTFILES" ]]; then echo "Empty list of test files" fi - yarn cypress:run --browser << parameters.browser >> --spec $TESTFILES + yarn cypress:run --browser <> --spec $TESTFILES fi working_directory: packages/driver - verify-mocha-results @@ -136,7 +136,7 @@ commands: command: | CYPRESS_KONFIG_ENV=production \ CYPRESS_RECORD_KEY=$PACKAGES_RECORD_KEY \ - yarn workspace @packages/runner cypress:run --record --parallel --group runner-integration-<< parameters.browser >> --browser <> + yarn workspace @packages/runner cypress:run --record --parallel --group runner-integration-<> --browser <> - store_test_results: path: /tmp/cypress - store_artifacts: @@ -156,7 +156,7 @@ commands: - attach_workspace: at: ~/ - run: - command: yarn workspace @packages/server test ./test/e2e/<< parameters.chunk >>*spec* --browser << parameters.browser >> + command: yarn workspace @packages/server test ./test/e2e/<>*spec* --browser <> - verify-mocha-results - store_test_results: path: /tmp/cypress @@ -194,15 +194,30 @@ commands: ## by default, assert that at least 1 test ran default: 0 steps: - - run: yarn verify:mocha:results << parameters.expectedResultCount >> + - run: yarn verify:mocha:results <> + clone-repo-and-checkout-release-branch: + description: | + Clones an external repo and then checks out the branch that matches NEXT_DEV_VERSION otherwise uses 'master' branch. + parameters: + repo: + description: "Name of the github repo to clone like: cypress-example-kitchensink" + type: string + steps: + - attach_workspace: + at: ~/ + - run: + name: "Cloning test project: <>" + command: | + git clone --depth 1 --no-single-branch https://github.com/cypress-io/<>.git /tmp/<> + cd /tmp/<> && (git checkout $NEXT_DEV_VERSION || true) test-binary-against-repo: description: | Takes the built binary and NPM package, clones given example repo and runs the new version of Cypress against it. parameters: repo: - description: Name of the repo like "cypress-example-kitchensink" + description: "Name of the github repo to clone like: cypress-example-kitchensink" type: string browser: description: Name of the browser to use, like "electron", "chrome", "firefox" @@ -232,18 +247,17 @@ commands: # make sure the binary and NPM package files are present - run: ls -l - run: ls -l cypress.zip cypress.tgz - - run: - name: Cloning project <> - command: git clone --depth 1 https://github.com/cypress-io/<>.git /tmp/<> + - clone-repo-and-checkout-release-branch: + repo: <> - when: - condition: << parameters.pull_request_id >> + condition: <> steps: - run: - name: Check out PR << parameters.pull_request_id >> + name: Check out PR <> working_directory: /tmp/<> command: | - git fetch origin pull/<< parameters.pull_request_id >>/head:pr-<< parameters.pull_request_id >> - git checkout pr-<< parameters.pull_request_id >> + git fetch origin pull/<>/head:pr-<> + git checkout pr-<> git log -n 2 - run: command: npm install @@ -266,42 +280,42 @@ commands: command: npm start --if-present background: true - when: - condition: << parameters.folder >> + condition: <> steps: - when: - condition: << parameters.browser >> + condition: <> steps: - run: - name: Run tests using browser "<< parameters.browser >>" - working_directory: /tmp/<>/<< parameters.folder >> + name: Run tests using browser "<>" + working_directory: /tmp/<>/<> command: | <> -- --browser <> - unless: - condition: << parameters.browser >> + condition: <> steps: - run: name: Run tests using command - working_directory: /tmp/<>/<< parameters.folder >> + working_directory: /tmp/<>/<> command: <> - store_artifacts: name: screenshots - path: /tmp/<>/<< parameters.folder >>/cypress/screenshots + path: /tmp/<>/<>/cypress/screenshots - store_artifacts: name: videos - path: /tmp/<>/<< parameters.folder >>/cypress/videos + path: /tmp/<>/<>/cypress/videos - unless: - condition: << parameters.folder >> + condition: <> steps: - when: - condition: << parameters.browser >> + condition: <> steps: - run: - name: Run tests using browser "<< parameters.browser >>" + name: Run tests using browser "<>" working_directory: /tmp/<> command: <> -- --browser <> - unless: - condition: << parameters.browser >> + condition: <> steps: - run: name: Run tests using command @@ -904,45 +918,39 @@ jobs: test-kitchensink: <<: *defaults steps: - - attach_workspace: - at: ~/ - - run: - name: Cloning test project - command: git clone https://github.com/cypress-io/cypress-example-kitchensink.git /tmp/repo + - clone-repo-and-checkout-release-branch: + repo: cypress-example-kitchensink - run: name: Install prod dependencies command: yarn --production - working_directory: /tmp/repo + working_directory: /tmp/cypress-example-kitchensink - run: name: Example server command: yarn start - working_directory: /tmp/repo + working_directory: /tmp/cypress-example-kitchensink background: true - run: name: Run Kitchensink example project - command: yarn cypress:run --project /tmp/repo + command: yarn cypress:run --project /tmp/cypress-example-kitchensink - store_artifacts: - path: /tmp/repo/cypress/screenshots + path: /tmp/cypress-example-kitchensink/cypress/screenshots - store_artifacts: - path: /tmp/repo/cypress/videos + path: /tmp/cypress-example-kitchensink/cypress/videos - store-npm-logs "test-kitchensink-against-staging": <<: *defaults steps: - - attach_workspace: - at: ~/ - - run: - name: Cloning test project - command: git clone https://github.com/cypress-io/cypress-example-kitchensink.git /tmp/repo + - clone-repo-and-checkout-release-branch: + repo: cypress-example-kitchensink - run: name: Install prod dependencies command: yarn --production - working_directory: /tmp/repo + working_directory: /tmp/cypress-example-kitchensink - run: name: Example server command: yarn start - working_directory: /tmp/repo + working_directory: /tmp/cypress-example-kitchensink background: true - run: name: Run Kitchensink example project @@ -951,24 +959,21 @@ jobs: CYPRESS_RECORD_KEY=$TEST_KITCHENSINK_RECORD_KEY \ CYPRESS_INTERNAL_ENV=staging \ CYPRESS_video=false \ - yarn cypress:run --project /tmp/repo --record + yarn cypress:run --project /tmp/cypress-example-kitchensink --record - store-npm-logs "test-against-staging": <<: *defaults steps: - - attach_workspace: - at: ~/ - - run: - name: Cloning test project - command: git clone https://github.com/cypress-io/cypress-test-tiny.git /tmp/repo + - clone-repo-and-checkout-release-branch: + repo: cypress-test-tiny - run: name: Run test project command: | CYPRESS_PROJECT_ID=$TEST_TINY_PROJECT_ID \ CYPRESS_RECORD_KEY=$TEST_TINY_RECORD_KEY \ CYPRESS_INTERNAL_ENV=staging \ - yarn cypress:run --project /tmp/repo --record + yarn cypress:run --project /tmp/cypress-example-kitchensink --record - store-npm-logs build-npm-package: @@ -1164,16 +1169,16 @@ jobs: # make sure we have cypress.zip received - run: ls -l - run: ls -l cypress.zip cypress.tgz - - run: mkdir << parameters.wd >> + - run: mkdir <> - run: node --version - run: npm --version - run: name: Create new NPM package ⚗️ - working_directory: << parameters.wd >> + working_directory: <> command: npm init -y - run: name: Install dependencies 📦 - working_directory: << parameters.wd >> + working_directory: <> environment: CYPRESS_INSTALL_BINARY: /root/cypress/cypress.zip # let's install Cypress, Jest and any other package that might conflict @@ -1183,7 +1188,7 @@ jobs: typescript jest @types/jest enzyme @types/enzyme - run: name: Test types clash ⚔️ - working_directory: << parameters.wd >> + working_directory: <> command: | echo "console.log('hello world')" > hello.ts npx tsc hello.ts --noEmit @@ -1207,16 +1212,16 @@ jobs: # make sure we have cypress.zip received - run: ls -l - run: ls -l cypress.zip cypress.tgz - - run: mkdir << parameters.wd >> + - run: mkdir <> - run: node --version - run: npm --version - run: name: Create new NPM package ⚗️ - working_directory: << parameters.wd >> + working_directory: <> command: npm init -y - run: name: Install dependencies 📦 - working_directory: << parameters.wd >> + working_directory: <> environment: CYPRESS_INSTALL_BINARY: /root/cypress/cypress.zip # let's install Cypress, Jest and any other package that might conflict @@ -1226,7 +1231,7 @@ jobs: typescript jest @types/jest enzyme @types/enzyme - run: name: Scaffold and test examples 🏗 - working_directory: << parameters.wd >> + working_directory: <> environment: CYPRESS_INTERNAL_FORCE_SCAFFOLD: "1" command: | @@ -1250,27 +1255,27 @@ jobs: # make sure we have cypress.zip received - run: ls -l - run: ls -l cypress.zip cypress.tgz - - run: mkdir << parameters.wd >> + - run: mkdir <> - run: node --version - run: npm --version - run: name: Create new NPM package ⚗️ - working_directory: << parameters.wd >> + working_directory: <> command: npm init -y - run: name: Install dependencies 📦 - working_directory: << parameters.wd >> + working_directory: <> environment: CYPRESS_INSTALL_BINARY: /root/cypress/cypress.zip command: | npm install /root/cypress/cypress.tgz typescript - run: name: Scaffold full TypeScript project 🏗 - working_directory: << parameters.wd >> + working_directory: <> command: npx @bahmutov/cly@1 init --typescript - run: name: Run project tests 🗳 - working_directory: << parameters.wd >> + working_directory: <> command: npx cypress run # install NPM + binary zip and run against staging API @@ -1282,9 +1287,8 @@ jobs: - run: ls -l # make sure we have the binary and NPM package - run: ls -l cypress.zip cypress.tgz - - run: - name: Cloning test project - command: git clone https://github.com/cypress-io/cypress-test-tiny.git /tmp/cypress-test-tiny + - clone-repo-and-checkout-release-branch: + repo: cypress-test-tiny - run: name: Install Cypress working_directory: /tmp/cypress-test-tiny diff --git a/cli/types/cypress.d.ts b/cli/types/cypress.d.ts index cb4adf2ca055..affe28640cd6 100644 --- a/cli/types/cypress.d.ts +++ b/cli/types/cypress.d.ts @@ -2130,7 +2130,7 @@ declare namespace Cypress { type Agent = SinonSpyAgent & T interface CookieDefaults { - whitelist: string | string[] | RegExp | ((cookie: any) => boolean) + preserve: string | string[] | RegExp | ((cookie: any) => boolean) } interface Failable { @@ -2616,7 +2616,7 @@ declare namespace Cypress { enable: boolean force404: boolean urlMatchingOptions: object - whitelist(xhr: Request): void + ignore(xhr: Request): void onAnyRequest(route: RouteOptions, proxy: any): void onAnyResponse(route: RouteOptions, proxy: any): void onAnyAbort(route: RouteOptions, proxy: any): void @@ -4806,7 +4806,7 @@ declare namespace Cypress { interface Server extends RouteOptions { enable: boolean - whitelist: (xhr: any) => boolean + ignore: (xhr: any) => boolean } interface Viewport { diff --git a/cli/types/tests/kitchen-sink.ts b/cli/types/tests/kitchen-sink.ts index ad0d299b102e..ae6ae2b5c9d8 100644 --- a/cli/types/tests/kitchen-sink.ts +++ b/cli/types/tests/kitchen-sink.ts @@ -35,7 +35,7 @@ cy.visit('https://www.acme.com/', { const serverOptions: Partial = { delay: 100, - whitelist: () => true + ignore: () => true } cy.server(serverOptions) diff --git a/packages/driver/cypress/integration/commands/cookies_spec.js b/packages/driver/cypress/integration/commands/cookies_spec.js index 6d2092626075..4cd21b40b484 100644 --- a/packages/driver/cypress/integration/commands/cookies_spec.js +++ b/packages/driver/cypress/integration/commands/cookies_spec.js @@ -1246,4 +1246,18 @@ describe('src/cy/commands/cookies', () => { }) }) }) + + context('Cypress.cookies.defaults', () => { + it('throws error on use of renamed whitelist option', (done) => { + cy.on('fail', (err) => { + expect(err.message).to.include('`Cypress.Cookies.defaults` `whitelist` option has been renamed to `preserve`. Please rename `whitelist` to `preserve`.') + + done() + }) + + Cypress.Cookies.defaults({ + whitelist: 'session_id', + }) + }) + }) }) diff --git a/packages/driver/cypress/integration/commands/xhr_spec.js b/packages/driver/cypress/integration/commands/xhr_spec.js index 1c7cd99a12ca..8bf33c750237 100644 --- a/packages/driver/cypress/integration/commands/xhr_spec.js +++ b/packages/driver/cypress/integration/commands/xhr_spec.js @@ -305,39 +305,6 @@ describe('src/cy/commands/xhr', () => { }) }) - // FIXME: I have no idea why this is skipped, this test is rly old - describe.skip('filtering requests', () => { - beforeEach(() => { - cy.server() - }) - - const extensions = { - html: 'ajax html', - js: '{foo: "bar"}', - css: 'body {}', - } - - _.each(extensions, (val, ext) => { - it(`filters out non ajax requests by default for extension: .${ext}`, (done) => { - cy.state('window').$.get(`/fixtures/app.${ext}`).done((res) => { - expect(res).to.eq(val) - - done() - }) - }) - }) - - it('can disable default filtering', (done) => { - // this should throw since it should return 404 when no - // route matches it - cy.server({ ignore: false }).window().then((w) => { - Promise.resolve(w.$.get('/fixtures/app.html')).catch(() => { - done() - }) - }) - }) - }) - describe('url rewriting', () => { it('has a FQDN absolute-relative url', () => { cy @@ -1139,6 +1106,14 @@ describe('src/cy/commands/xhr', () => { }) }) + it('sets ignore as function by default', () => { + cy.server() + cy.route('*', {}) + .then(() => { + expect(cy.state('server').getRoutes()[0].ignore).to.be.a('function') + }) + }) + it('passes down options.delay to routes', () => { cy .server({ delay: 100 }) @@ -1243,110 +1218,6 @@ describe('src/cy/commands/xhr', () => { }) }) - // FIXME: I have no idea why this is skipped, this test is rly old - context.skip('#server', () => { - beforeEach(function () { - const defaults = { - ignore: true, - respond: true, - delay: 10, - beforeRequest () {}, - afterResponse () {}, - onAbort () {}, - onError () {}, - onFilter () {}, - } - - this.options = (obj) => { - return _.extend(obj, defaults) - } - - this.create = cy.spy(this.Cypress.Server, 'create') - }) - - it('can accept an onRequest and onResponse callback', function (done) { - const onRequest = () => {} - const onResponse = () => {} - - cy.on('end', () => { - expect(this.create.getCall(0).args[1]).to.have.keys(_.keys(this.options({ onRequest, onResponse }))) - - done() - }) - - cy.server(onRequest, onResponse) - }) - - it('can accept onRequest and onRespond through options', function (done) { - const onRequest = () => {} - const onResponse = () => {} - - cy.on('end', () => { - expect(this.create.getCall(0).args[1]).to.have.keys(_.keys(this.options({ onRequest, onResponse }))) - - done() - }) - - cy.server({ onRequest, onResponse }) - }) - - describe('without sinon present', () => { - beforeEach(() => { - // force us to start from blank window - cy.state('$autIframe').prop('src', 'about:blank') - }) - - it('can start server with no errors', () => { - cy - .server() - .visit('http://localhost:3500/fixtures/sinon.html') - }) - - it('can add routes with no errors', () => { - cy - .server() - .route(/foo/, {}) - .visit('http://localhost:3500/fixtures/sinon.html') - }) - - it('routes xhr requests', () => { - cy - .server() - .route(/foo/, { foo: 'bar' }) - .visit('http://localhost:3500/fixtures/sinon.html') - .window().then((w) => { - return w.$.get('/foo') - }) - .then((resp) => { - expect(resp).to.deep.eq({ foo: 'bar' }) - }) - }) - - it('works with aliases', () => { - cy - .server() - .route(/foo/, { foo: 'bar' }).as('getFoo') - .visit('http://localhost:3500/fixtures/sinon.html') - .window().then((w) => { - return w.$.get('/foo') - }) - .wait('@getFoo').then((xhr) => { - expect(xhr.responseText).to.eq(JSON.stringify({ foo: 'bar' })) - }) - }) - - it('prevents XHR\'s from going out from sinon.html', () => { - cy - .server() - .route(/bar/, { bar: 'baz' }).as('getBar') - .visit('http://localhost:3500/fixtures/sinon.html') - .wait('@getBar').then((xhr) => { - expect(xhr.responseText).to.eq(JSON.stringify({ bar: 'baz' })) - }) - }) - }) - }) - context('#route', () => { beforeEach(function () { this.expectOptionsToBe = (opts) => { @@ -1909,6 +1780,16 @@ describe('src/cy/commands/xhr', () => { cy.route() }) + it('throws on use of whitelist option', (done) => { + cy.on('fail', (err) => { + expect(err.message).to.include('The `cy.server()` `whitelist` option has been renamed to `ignore`. Please rename `whitelist` to `ignore`.') + + done() + }) + + cy.server({ whitelist: () => { } }) + }) + it('url must be a string or regexp', (done) => { cy.on('fail', (err) => { expect(err.message).to.include('`cy.route()` was called with an invalid `url`. `url` must be either a string or regular expression.') @@ -2285,8 +2166,8 @@ describe('src/cy/commands/xhr', () => { }) }) - describe('whitelisting', () => { - it('does not send back 404s on whitelisted routes', () => { + describe('ignored routes', () => { + it('does not send back 404s on allowed routes', () => { cy .server() .window().then((win) => { @@ -2298,7 +2179,7 @@ describe('src/cy/commands/xhr', () => { }) // https://github.com/cypress-io/cypress/issues/7280 - it('ignores query params when whitelisting routes', () => { + it('ignores query params when filtering routes', () => { cy.server() cy.route(/url-with-query-param/, { foo: 'bar' }).as('getQueryParam') cy.window().then((win) => { @@ -2312,7 +2193,7 @@ describe('src/cy/commands/xhr', () => { }) // https://github.com/cypress-io/cypress/issues/7280 - it('ignores hashes when whitelisting routes', () => { + it('ignores hashes when filtering routes', () => { cy.server() cy.route(/url-with-hash/, { foo: 'bar' }).as('getHash') cy.window().then((win) => { @@ -2324,6 +2205,30 @@ describe('src/cy/commands/xhr', () => { cy.wait('@getHash').its('response.body') .should('deep.equal', { foo: 'bar' }) }) + + it('overrides ignoring resources when passed as option', () => { + cy.server({ ignore: () => false }) + cy.route('app.js', { foo: 'bar' }).as('getJSResource') + cy.route('index.html', '').as('getHTMLResource') + cy.route('style.css', 'body: {color: red;}').as('getCSSResource') + cy.window().then((win) => { + win.$.get('/fixtures/app.js') + win.$.get('/fixtures/style.css') + + return win.$.get('/fixtures/index.html') + }) + + // normally these resources would be ignored + // but overwriting ignore to return false allows all resources + cy.wait('@getJSResource').its('response.body') + .should('deep.equal', { foo: 'bar' }) + + cy.wait('@getHTMLResource').its('response.body') + .should('deep.equal', '') + + cy.wait('@getCSSResource').its('response.body') + .should('deep.equal', 'body: {color: red;}') + }) }) describe('route setup', () => { diff --git a/packages/driver/src/cy/commands/cookies.js b/packages/driver/src/cy/commands/cookies.js index 51e6d1f9ee0a..85c3170b7b30 100644 --- a/packages/driver/src/cy/commands/cookies.js +++ b/packages/driver/src/cy/commands/cookies.js @@ -95,7 +95,7 @@ module.exports = function (Commands, Cypress, cy, state, config) { return resp } - // iterate over all of these and ensure none are whitelisted + // iterate over all of these and ensure none are allowed // or preserved const cookies = Cypress.Cookies.getClearableCookies(resp) diff --git a/packages/driver/src/cy/keyboard.ts b/packages/driver/src/cy/keyboard.ts index 6f951fb07de6..3552a3da6d38 100644 --- a/packages/driver/src/cy/keyboard.ts +++ b/packages/driver/src/cy/keyboard.ts @@ -263,7 +263,7 @@ const shouldUpdateValue = (el: HTMLElement, key: KeyDetails, options: typeOption if (!(numberRe.test(potentialValue))) { debug('skipping inserting value since number input would be invalid', key.text, potentialValue) - // when typing in a number input, only certain whitelisted chars will insert text + // when typing in a number input, only certain allowed chars will insert text if (!key.text.match(isValidNumberInputChar)) { // https://github.com/cypress-io/cypress/issues/6055 // Should not remove old valid values when a new one is not a valid number char, just dismiss it with return diff --git a/packages/driver/src/cypress/cookies.js b/packages/driver/src/cypress/cookies.js index 01b92e441743..76d83d4fa1e8 100644 --- a/packages/driver/src/cypress/cookies.js +++ b/packages/driver/src/cypress/cookies.js @@ -9,7 +9,13 @@ let isDebuggingVerbose = false const preserved = {} const defaults = { - whitelist: null, + preserve: null, +} + +const warnOnWhitelistRenamed = (obj, type) => { + if (obj.whitelist) { + return $errUtils.throwErrByPath('cookies.whitelist_renamed', { args: { type } }) + } } const $Cookies = (namespace, domain) => { @@ -17,8 +23,8 @@ const $Cookies = (namespace, domain) => { return _.startsWith(name, namespace) } - const isWhitelisted = (cookie) => { - const w = defaults.whitelist + const isAllowed = (cookie) => { + const w = defaults.preserve if (w) { if (_.isString(w)) { @@ -76,7 +82,7 @@ const $Cookies = (namespace, domain) => { getClearableCookies (cookies = []) { return _.filter(cookies, (cookie) => { - return !isWhitelisted(cookie) && !removePreserved(cookie.name) + return !isAllowed(cookie) && !removePreserved(cookie.name) }) }, @@ -132,6 +138,8 @@ const $Cookies = (namespace, domain) => { }, defaults (obj = {}) { + warnOnWhitelistRenamed(obj, 'Cypress.Cookies.defaults') + // merge obj into defaults return _.extend(defaults, obj) }, diff --git a/packages/driver/src/cypress/error_messages.js b/packages/driver/src/cypress/error_messages.js index c5ae4f08f1cc..1f7d929017e5 100644 --- a/packages/driver/src/cypress/error_messages.js +++ b/packages/driver/src/cypress/error_messages.js @@ -301,6 +301,11 @@ module.exports = { - \`cy.clearCookie()\` - \`cy.clearCookies()\``, }, + whitelist_renamed (obj) { + return { + message: `\`${obj.type}\` \`whitelist\` option has been renamed to \`preserve\`. Please rename \`whitelist\` to \`preserve\`.`, + } + }, }, dom: { @@ -1286,6 +1291,7 @@ module.exports = { }, xhrurl_not_set: '`Server.options.xhrUrl` has not been set', unavailable: 'The XHR server is unavailable or missing. This should never happen and likely is a bug. Open an issue if you see this message.', + whitelist_renamed: `The ${cmd('server')} \`whitelist\` option has been renamed to \`ignore\`. Please rename \`whitelist\` to \`ignore\`.`, }, setCookie: { diff --git a/packages/driver/src/cypress/server.js b/packages/driver/src/cypress/server.js index 4610d8f91acf..097336edb019 100644 --- a/packages/driver/src/cypress/server.js +++ b/packages/driver/src/cypress/server.js @@ -65,7 +65,13 @@ const warnOnForce404Default = (obj) => { } } -const whitelist = (xhr) => { +const warnOnWhitelistRenamed = (obj, type) => { + if (obj.whitelist) { + return $errUtils.throwErrByPath('server.whitelist_renamed', { args: { type } }) + } +} + +const ignore = (xhr) => { const url = new URL(xhr.url) // https://github.com/cypress-io/cypress/issues/7280 @@ -74,7 +80,7 @@ const whitelist = (xhr) => { url.search = '' url.hash = '' - // whitelist if we're GET + looks like we're fetching regular resources + // allow if we're GET + looks like we're fetching regular resources return xhr.method === 'GET' && regularResourcesRe.test(url.href) } @@ -95,7 +101,7 @@ const serverDefaults = { urlMatchingOptions: { matchBase: true }, stripOrigin: _.identity, getUrlOptions: _.identity, - whitelist, // function whether to allow a request to go out (css/js/html/templates) etc + ignore, // function whether to allow a request to go out (css/js/html/templates) etc onOpen () {}, onSend () {}, onXhrAbort () {}, @@ -209,8 +215,8 @@ const create = (options = {}) => { return routes }, - isWhitelisted (xhr) { - return options.whitelist(xhr) + isIgnored (xhr) { + return options.ignore(xhr) }, shouldApplyStub (route) { @@ -257,9 +263,9 @@ const create = (options = {}) => { // return the 404 stub if we dont have any stubs // but we are stubbed - meaning we havent added any routes // but have started the server - // and this request shouldnt be whitelisted + // and this request shouldnt be allowed if (!routes.length && hasEnabledStubs && - options.force404 !== false && !server.isWhitelisted(xhr)) { + options.force404 !== false && !server.isIgnored(xhr)) { return get404Route() } @@ -268,8 +274,8 @@ const create = (options = {}) => { return nope() } - // bail if this xhr matches our whitelist - if (server.isWhitelisted(xhr)) { + // bail if this xhr matches our ignore list + if (server.isIgnored(xhr)) { return nope() } @@ -414,6 +420,7 @@ const create = (options = {}) => { set (obj) { warnOnStubDeprecation(obj, 'server') warnOnForce404Default(obj) + warnOnWhitelistRenamed(obj, 'server') // handle enable=true|false if (obj.enable != null) { @@ -648,8 +655,8 @@ const create = (options = {}) => { proxy._setRequestBody(requestBody) // log this out now since it's being sent officially - // unless its been whitelisted - if (!server.isWhitelisted(this)) { + // unless its not been ignored + if (!server.isIgnored(this)) { options.onSend(proxy, sendStack, route) } diff --git a/packages/example/bin/convert.js b/packages/example/bin/convert.js index 9e548350a03c..4936485cd923 100755 --- a/packages/example/bin/convert.js +++ b/packages/example/bin/convert.js @@ -24,6 +24,11 @@ function replaceStringsIn (file) { replace(eslintRe, "") replace("imgSrcToDataURL('/assets", "imgSrcToDataURL('https://example.cypress.io/assets") + // temporary for 5.0.0 + // TODO: remove this + replace("whitelist: 'session_id'", "preserve: 'session_id'") + replace("server.whitelist", "server.ignore") + fs.writeFile(file, str, function (err) { if (err) throw err diff --git a/packages/server/__snapshots__/2_cookies_spec.ts.js b/packages/server/__snapshots__/2_cookies_spec.ts.js index 7bc11a5d62cb..06024ba31b53 100644 --- a/packages/server/__snapshots__/2_cookies_spec.ts.js +++ b/packages/server/__snapshots__/2_cookies_spec.ts.js @@ -18,12 +18,12 @@ exports['e2e cookies with baseurl'] = ` cookies - with whitelist + with preserve ✓ can get all cookies ✓ resets cookies between tests correctly ✓ should be only two left now ✓ handles undefined cookies - without whitelist + without preserve ✓ sends set cookies to path ✓ handles expired cookies secure ✓ issue: #224 sets expired cookies between redirects @@ -122,12 +122,12 @@ exports['e2e cookies with no baseurl'] = ` cookies - with whitelist + with preserve ✓ can get all cookies ✓ resets cookies between tests correctly ✓ should be only two left now ✓ handles undefined cookies - without whitelist + without preserve ✓ sends cookies to localhost:2121 ✓ handles expired cookies secure ✓ issue: #224 sets expired cookies between redirects diff --git a/packages/server/lib/config.js b/packages/server/lib/config.js index 7f9cbd7596b7..a9dd933fa7dd 100644 --- a/packages/server/lib/config.js +++ b/packages/server/lib/config.js @@ -83,7 +83,7 @@ firefoxGcInterval\ `) // NOTE: If you add a config value, make sure to update the following -// - cli/types/index.d.ts (including whitelisted config options on TestOptions) +// - cli/types/index.d.ts (including allowed config options on TestOptions) // - cypress.schema.json // experimentalComponentTesting @@ -372,7 +372,7 @@ module.exports = { return _.includes(names, value) }, - whitelist (obj = {}) { + allowed (obj = {}) { const propertyNames = configKeys .concat(breakingConfigKeys) .concat(systemConfigKeys) @@ -427,7 +427,7 @@ module.exports = { debug('merged config with options, got %o', config) _ - .chain(this.whitelist(options)) + .chain(this.allowed(options)) .omit('env') .omit('browsers') .each((val, key) => { diff --git a/packages/server/lib/project.js b/packages/server/lib/project.js index d18a16676451..60d6e2b4504a 100644 --- a/packages/server/lib/project.js +++ b/packages/server/lib/project.js @@ -173,10 +173,10 @@ class Project extends EE { _initPlugins (cfg, options) { // only init plugins with the - // whitelisted config values to + // allowed config values to // prevent tampering with the // internals and breaking cypress - cfg = config.whitelist(cfg) + cfg = config.allowed(cfg) return plugins.init(cfg, { projectRoot: this.projectRoot, diff --git a/packages/server/lib/saved_state.js b/packages/server/lib/saved_state.js index dee0a7126890..4623b7d96d0c 100644 --- a/packages/server/lib/saved_state.js +++ b/packages/server/lib/saved_state.js @@ -9,7 +9,7 @@ const fs = require('./util/fs') const stateFiles = {} -const whitelist = ` +const allowed = ` appWidth appHeight appX @@ -64,7 +64,7 @@ const formStatePath = (projectRoot) => { }) } -const normalizeAndWhitelistSet = (set, key, value) => { +const normalizeAndAllowSet = (set, key, value) => { const valueObject = (() => { if (_.isString(key)) { const tmp = {} @@ -78,15 +78,15 @@ const normalizeAndWhitelistSet = (set, key, value) => { })() const invalidKeys = _.filter(_.keys(valueObject), (key) => { - return !_.includes(whitelist, key) + return !_.includes(allowed, key) }) if (invalidKeys.length) { // eslint-disable-next-line no-console - console.error(`WARNING: attempted to save state for non-whitelisted key(s): ${invalidKeys.join(', ')}. All keys must be whitelisted in server/lib/saved_state.js`) + console.error(`WARNING: attempted to save state for non-allowed key(s): ${invalidKeys.join(', ')}. All keys must be allowed in server/lib/saved_state.js`) } - return set(_.pick(valueObject, whitelist)) + return set(_.pick(valueObject, allowed)) } const create = (projectRoot, isTextTerminal) => { @@ -110,7 +110,7 @@ const create = (projectRoot, isTextTerminal) => { path: fullStatePath, }) - stateFile.set = _.wrap(stateFile.set.bind(stateFile), normalizeAndWhitelistSet) + stateFile.set = _.wrap(stateFile.set.bind(stateFile), normalizeAndAllowSet) stateFiles[fullStatePath] = stateFile diff --git a/packages/server/lib/server.js b/packages/server/lib/server.js index 02f7ccf206c5..440f39ba0956 100644 --- a/packages/server/lib/server.js +++ b/packages/server/lib/server.js @@ -28,7 +28,7 @@ const appData = require('./util/app_data') const statusCode = require('./util/status_code') const headersUtil = require('./util/headers') const allowDestroy = require('./util/server_destroy') -const { SocketWhitelist } = require('./util/socket_whitelist') +const { SocketAllowed } = require('./util/socket_allowed') const errors = require('./errors') const logger = require('./logger') const Socket = require('./socket') @@ -59,7 +59,7 @@ const _forceProxyMiddleware = function (clientRoute) { const trimmedUrl = _.trimEnd(req.proxiedUrl, '/') if (_isNonProxiedRequest(req) && !ALLOWED_PROXY_BYPASS_URLS.includes(trimmedUrl) && (trimmedUrl !== trimmedClientRoute)) { - // this request is non-proxied and non-whitelisted, redirect to the runner error page + // this request is non-proxied and non-allowed, redirect to the runner error page return res.redirect(clientRoute) } @@ -115,7 +115,7 @@ class Server { return new Server() } - this._socketWhitelist = new SocketWhitelist() + this._socketAllowed = new SocketAllowed() this._request = null this._middleware = null this._server = null @@ -278,7 +278,7 @@ class Server { this._server.on('connect', (req, socket, head) => { debug('Got CONNECT request from %s', req.url) - socket.once('upstream-connected', this._socketWhitelist.add) + socket.once('upstream-connected', this._socketAllowed.add) return this._httpsProxy.connect(req, socket, head, { onDirectConnection: (req) => { @@ -761,7 +761,7 @@ class Server { let host; let remoteOrigin if (req.url.startsWith(socketIoRoute)) { - if (!this._socketWhitelist.isRequestWhitelisted(req)) { + if (!this._socketAllowed.isRequestAllowed(req)) { socket.write('HTTP/1.1 400 Bad Request\r\n\r\nRequest not made via a Cypress-launched browser.') socket.end() } diff --git a/packages/server/lib/util/args.js b/packages/server/lib/util/args.js index 43184b043ea6..cabcdebf8546 100644 --- a/packages/server/lib/util/args.js +++ b/packages/server/lib/util/args.js @@ -13,7 +13,7 @@ const nestedObjectsInCurlyBracesRe = /\{(.+?)\}/g const nestedArraysInSquareBracketsRe = /\[(.+?)\]/g const everythingAfterFirstEqualRe = /=(.*)/ -const whitelist = 'appPath apiKey browser ci ciBuildId clearLogs config configFile cwd env execPath exit exitWithCode generateKey getKey group headed inspectBrk key logs mode outputPath parallel ping port project proxySource quiet record reporter reporterOptions returnPkg runMode runProject smokeTest spec tag updating version'.split(' ') +const allowList = 'appPath apiKey browser ci ciBuildId clearLogs config configFile cwd env execPath exit exitWithCode generateKey getKey group headed inspectBrk key logs mode outputPath parallel ping port project proxySource quiet record reporter reporterOptions returnPkg runMode runProject smokeTest spec tag updating version'.split(' ') // returns true if the given string has double quote character " // only at the last position. const hasStrayEndQuote = (s) => { @@ -189,14 +189,14 @@ module.exports = { alias, }) - const whitelisted = _.pick(argv, whitelist) + const allowed = _.pick(argv, allowList) // were we invoked from the CLI or directly? const invokedFromCli = Boolean(options.cwd) options = _ .chain(options) - .defaults(whitelisted) + .defaults(allowed) .omit(_.keys(alias)) // remove aliases .extend({ invokedFromCli }) .defaults({ @@ -320,11 +320,11 @@ module.exports = { toArray (obj = {}) { // goes in reverse, takes an object // and converts to an array by picking - // only the whitelisted properties and + // only the allowed properties and // mapping them to include the argument return _ .chain(obj) - .pick(...whitelist) + .pick(...allowList) .mapValues((val, key) => { return `--${key}=${stringify(val)}` }).values() diff --git a/packages/server/lib/util/socket_allowed.ts b/packages/server/lib/util/socket_allowed.ts new file mode 100644 index 000000000000..46ed8a32a386 --- /dev/null +++ b/packages/server/lib/util/socket_allowed.ts @@ -0,0 +1,46 @@ +import _ from 'lodash' +import Debug from 'debug' +import net from 'net' +import { Request } from 'express' + +const debug = Debug('cypress:server:util:socket_allowed') + +/** + * Utility to validate incoming, local socket connections against a list of + * expected client TCP ports. + */ +export class SocketAllowed { + allowedLocalPorts: number[] = [] + + /** + * Add a socket to the allowed list. + */ + add = (socket: net.Socket) => { + const { localPort } = socket + + debug('allowing socket %o', { localPort }) + this.allowedLocalPorts.push(localPort) + + socket.once('close', () => { + debug('allowed socket closed, removing %o', { localPort }) + this._remove(socket) + }) + } + + _remove (socket: net.Socket) { + _.pull(this.allowedLocalPorts, socket.localPort) + } + + /** + * Is this socket that this request originated allowed? + */ + isRequestAllowed (req: Request) { + const { remotePort, remoteAddress } = req.socket + const isAllowed = this.allowedLocalPorts.includes(remotePort!) + && ['127.0.0.1', '::1'].includes(remoteAddress!) + + debug('is incoming request allowed? %o', { isAllowed, reqUrl: req.url, remotePort, remoteAddress }) + + return isAllowed + } +} diff --git a/packages/server/lib/util/socket_whitelist.ts b/packages/server/lib/util/socket_whitelist.ts deleted file mode 100644 index 421426d417b6..000000000000 --- a/packages/server/lib/util/socket_whitelist.ts +++ /dev/null @@ -1,46 +0,0 @@ -import _ from 'lodash' -import Debug from 'debug' -import net from 'net' -import { Request } from 'express' - -const debug = Debug('cypress:server:util:socket_whitelist') - -/** - * Utility to validate incoming, local socket connections against a list of - * expected client TCP ports. - */ -export class SocketWhitelist { - whitelistedLocalPorts: number[] = [] - - /** - * Add a socket to the whitelist. - */ - add = (socket: net.Socket) => { - const { localPort } = socket - - debug('whitelisting socket %o', { localPort }) - this.whitelistedLocalPorts.push(localPort) - - socket.once('close', () => { - debug('whitelisted socket closed, removing %o', { localPort }) - this._remove(socket) - }) - } - - _remove (socket: net.Socket) { - _.pull(this.whitelistedLocalPorts, socket.localPort) - } - - /** - * Is this socket that this request originated from whitelisted? - */ - isRequestWhitelisted (req: Request) { - const { remotePort, remoteAddress } = req.socket - const isWhitelisted = this.whitelistedLocalPorts.includes(remotePort!) - && ['127.0.0.1', '::1'].includes(remoteAddress!) - - debug('is incoming request whitelisted? %o', { isWhitelisted, reqUrl: req.url, remotePort, remoteAddress }) - - return isWhitelisted - } -} diff --git a/packages/server/test/support/fixtures/projects/cookies/cypress/integration/app_spec.coffee b/packages/server/test/support/fixtures/projects/cookies/cypress/integration/app_spec.coffee index 32bdcc41fcd0..b80abfbddd1c 100644 --- a/packages/server/test/support/fixtures/projects/cookies/cypress/integration/app_spec.coffee +++ b/packages/server/test/support/fixtures/projects/cookies/cypress/integration/app_spec.coffee @@ -1,5 +1,5 @@ Cypress.Cookies.defaults({ - whitelist: "foo1" + preserve: "foo1" }) describe "Cookies", -> @@ -84,3 +84,4 @@ describe "Cookies", -> domain: "brian.dev.local" }) .its("body").should("deep.eq", {wow: "bob"}) + \ No newline at end of file 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 index a712c2597fe6..bea74bb33864 100644 --- 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 @@ -35,10 +35,10 @@ describe "cookies", -> beforeEach -> cy.wrap({foo: "bar"}) - context "with whitelist", -> + context "with preserve", -> before -> Cypress.Cookies.defaults({ - whitelist: "foo1" + preserve: "foo1" }) it "can get all cookies", -> @@ -104,10 +104,10 @@ describe "cookies", -> it "handles undefined cookies", -> cy.visit("/cookieWithNoName") - context "without whitelist", -> + context "without preserve", -> before -> Cypress.Cookies.defaults({ - whitelist: [] + preserve: [] }) it "sends set cookies to path", -> diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/cookies_spec_no_baseurl.coffee b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/cookies_spec_no_baseurl.coffee index 9fd762d558f9..7fdba2822b5f 100644 --- a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/cookies_spec_no_baseurl.coffee +++ b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/cookies_spec_no_baseurl.coffee @@ -5,10 +5,10 @@ describe "cookies", -> beforeEach -> cy.wrap({foo: "bar"}) - context "with whitelist", -> + context "with preserve", -> before -> Cypress.Cookies.defaults({ - whitelist: "foo1" + preserve: "foo1" }) it "can get all cookies", -> @@ -79,10 +79,10 @@ describe "cookies", -> it "handles undefined cookies", -> cy.visit("#{httpUrl}/cookieWithNoName") - context "without whitelist", -> + context "without preserve", -> before -> Cypress.Cookies.defaults({ - whitelist: [] + preserve: [] }) it "sends cookies to localhost:2121", -> diff --git a/packages/server/test/unit/args_spec.js b/packages/server/test/unit/args_spec.js index d7be0988e085..c7d896434b84 100644 --- a/packages/server/test/unit/args_spec.js +++ b/packages/server/test/unit/args_spec.js @@ -270,7 +270,7 @@ describe('lib/util/args', () => { expect(options.config).to.deep.eq(config) }) - it('whitelists config properties', function () { + it('allows config properties', function () { const options = this.setup('--config', 'foo=bar,port=1111,supportFile=path/to/support_file') expect(options.config.port).to.eq(1111) diff --git a/packages/server/test/unit/saved_state_spec.js b/packages/server/test/unit/saved_state_spec.js index 993e74248975..aadce5110414 100644 --- a/packages/server/test/unit/saved_state_spec.js +++ b/packages/server/test/unit/saved_state_spec.js @@ -62,7 +62,7 @@ describe('lib/saved_state', () => { }) }) - it('only saves whitelisted keys', () => { + it('only saves allowed keys', () => { return savedState.create() .then((state) => { return state.set({ foo: 'bar', appWidth: 20 }) @@ -81,7 +81,7 @@ describe('lib/saved_state', () => { .then((state) => { return state.set({ foo: 'bar', baz: 'qux' }) }).then(() => { - expect(console.error).to.be.calledWith('WARNING: attempted to save state for non-whitelisted key(s): foo, baz. All keys must be whitelisted in server/lib/saved_state.js') + expect(console.error).to.be.calledWith('WARNING: attempted to save state for non-allowed key(s): foo, baz. All keys must be allowed in server/lib/saved_state.js') }) }) }) diff --git a/packages/server/test/unit/screenshots_spec.js b/packages/server/test/unit/screenshots_spec.js index e14c68da4c96..b4b5aadce252 100644 --- a/packages/server/test/unit/screenshots_spec.js +++ b/packages/server/test/unit/screenshots_spec.js @@ -679,7 +679,7 @@ describe('lib/screenshots', () => { return sinon.stub(plugins, 'execute') }) - it('resolves whitelisted details if no after:screenshot plugin registered', function () { + it('resolves allowed details if no after:screenshot plugin registered', function () { plugins.has.returns(false) return screenshots.afterScreenshot(this.data, this.details).then((result) => { diff --git a/packages/server/test/unit/server_spec.js b/packages/server/test/unit/server_spec.js index fdc42b121614..c0025d36bbe0 100644 --- a/packages/server/test/unit/server_spec.js +++ b/packages/server/test/unit/server_spec.js @@ -338,7 +338,7 @@ describe('lib/server', () => { remoteAddress: '127.0.0.1', } - this.server._socketWhitelist.add({ + this.server._socketAllowed.add({ localPort: socket.remotePort, once: _.noop, }) diff --git a/packages/server/test/unit/util/socket_allowed_spec.ts b/packages/server/test/unit/util/socket_allowed_spec.ts new file mode 100644 index 000000000000..836e38ca8093 --- /dev/null +++ b/packages/server/test/unit/util/socket_allowed_spec.ts @@ -0,0 +1,42 @@ +import '../../spec_helper' + +import { expect } from 'chai' +import { Request } from 'express' +import { SocketAllowed } from '../../../lib/util/socket_allowed' +import { EventEmitter } from 'events' +import { Socket } from 'net' + +describe('lib/util/socket_allowed', function () { + let sw: SocketAllowed + + beforeEach(() => { + sw = new SocketAllowed() + }) + + context('#add', () => { + it('adds localPort to allowed list and removes it when closed', () => { + const socket = new EventEmitter as Socket + + // @ts-ignore readonly + socket.localPort = 12345 + + const req = { + socket: { + remotePort: socket.localPort, + remoteAddress: '127.0.0.1', + }, + } as Request + + expect(sw.allowedLocalPorts).to.deep.eq([]) + expect(sw.isRequestAllowed(req)).to.be.false + + sw.add(socket) + expect(sw.allowedLocalPorts).to.deep.eq([socket.localPort]) + expect(sw.isRequestAllowed(req)).to.be.true + + socket.emit('close') + expect(sw.allowedLocalPorts).to.deep.eq([]) + expect(sw.isRequestAllowed(req)).to.be.false + }) + }) +}) diff --git a/packages/server/test/unit/util/socket_whitelist_spec.ts b/packages/server/test/unit/util/socket_whitelist_spec.ts deleted file mode 100644 index e8fa34642671..000000000000 --- a/packages/server/test/unit/util/socket_whitelist_spec.ts +++ /dev/null @@ -1,42 +0,0 @@ -import '../../spec_helper' - -import { expect } from 'chai' -import { Request } from 'express' -import { SocketWhitelist } from '../../../lib/util/socket_whitelist' -import { EventEmitter } from 'events' -import { Socket } from 'net' - -describe('lib/util/socket_whitelist', function () { - let sw: SocketWhitelist - - beforeEach(() => { - sw = new SocketWhitelist() - }) - - context('#add', () => { - it('adds localPort to whitelist and removes it when closed', () => { - const socket = new EventEmitter as Socket - - // @ts-ignore readonly - socket.localPort = 12345 - - const req = { - socket: { - remotePort: socket.localPort, - remoteAddress: '127.0.0.1', - }, - } as Request - - expect(sw.whitelistedLocalPorts).to.deep.eq([]) - expect(sw.isRequestWhitelisted(req)).to.be.false - - sw.add(socket) - expect(sw.whitelistedLocalPorts).to.deep.eq([socket.localPort]) - expect(sw.isRequestWhitelisted(req)).to.be.true - - socket.emit('close') - expect(sw.whitelistedLocalPorts).to.deep.eq([]) - expect(sw.isRequestWhitelisted(req)).to.be.false - }) - }) -}) From 2e35094358667c662ef679149a53054eff7fcfd8 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Tue, 30 Jun 2020 09:14:57 -0400 Subject: [PATCH 07/42] chore: remove libgbm step from circle.yml --- circle.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/circle.yml b/circle.yml index 18d0cbea5cd7..6fc1db5536f9 100644 --- a/circle.yml +++ b/circle.yml @@ -1128,9 +1128,6 @@ jobs: - run: mkdir test-binary - run: node --version - run: npm --version - # TODO: this can be removed as soon as the minimum Node version is bumped to 10 - # see https://github.com/cypress-io/cypress-docker-images/pull/332 - - run: apt-get update && apt-get install -y libgbm-dev - run: name: Create new NPM package working_directory: test-binary From 9e754d526b01a090c11fb692ad93360d3132eb93 Mon Sep 17 00:00:00 2001 From: Kukhyeon Heo Date: Mon, 6 Jul 2020 22:38:02 +0900 Subject: [PATCH 08/42] TS: separate Window type for application under test (#7806) Co-authored-by: Oliver Joseph Ash --- cli/types/cypress.d.ts | 34 +++++++++++++++++++++----------- cli/types/index.d.ts | 2 +- cli/types/tests/actions.ts | 4 ++-- cli/types/tests/cypress-tests.ts | 18 +++++++++++++++++ cli/types/tests/kitchen-sink.ts | 6 ++++++ 5 files changed, 49 insertions(+), 15 deletions(-) diff --git a/cli/types/cypress.d.ts b/cli/types/cypress.d.ts index affe28640cd6..380e89e3cace 100644 --- a/cli/types/cypress.d.ts +++ b/cli/types/cypress.d.ts @@ -107,6 +107,16 @@ declare namespace Cypress { fromAutWindow: WindowPosition & { x: number, y: number } } + /** + * Window type for Application Under Test(AUT) + */ + type AUTWindow = Window & typeof globalThis & ApplicationWindow + + /** + * The interface for user-defined properties in Window object under test. + */ + interface ApplicationWindow {} // tslint:disable-line + /** * Several libraries are bundled with Cypress by default. * @@ -1038,7 +1048,7 @@ declare namespace Cypress { * * @see https://on.cypress.io/go */ - go(direction: HistoryDirection | number, options?: Partial): Chainable + go(direction: HistoryDirection | number, options?: Partial): Chainable /** * Get the current URL hash of the page that is currently active. @@ -1375,7 +1385,7 @@ declare namespace Cypress { * @example * cy.reload() */ - reload(options?: Partial): Chainable + reload(options?: Partial): Chainable /** * Reload the page without cache * @@ -1386,7 +1396,7 @@ declare namespace Cypress { * cy.visit('http://localhost:3000/admin') * cy.reload(true) */ - reload(forceReload: boolean): Chainable + reload(forceReload: boolean): Chainable /** * Make an HTTP GET request. @@ -1929,8 +1939,8 @@ declare namespace Cypress { * }) * */ - visit(url: string, options?: Partial): Chainable - visit(options: Partial & { url: string }): Chainable + visit(url: string, options?: Partial): Chainable + visit(options: Partial & { url: string }): Chainable /** * Wait for a number of milliseconds. @@ -2001,7 +2011,7 @@ declare namespace Cypress { }) ``` */ - window(options?: Partial): Chainable + window(options?: Partial): Chainable /** * Scopes all subsequent cy commands to within this element. @@ -2715,16 +2725,16 @@ declare namespace Cypress { /** * Called before your page has loaded all of its resources. * - * @param {Window} contentWindow the remote page's window object + * @param {AUTWindow} contentWindow the remote page's window object */ - onBeforeLoad(win: Window): void + onBeforeLoad(win: AUTWindow): void /** * Called once your page has fired its load event. * - * @param {Window} contentWindow the remote page's window object + * @param {AUTWindow} contentWindow the remote page's window object */ - onLoad(win: Window): void + onLoad(win: AUTWindow): void /** * Cypress will automatically apply the right authorization headers @@ -4632,12 +4642,12 @@ declare namespace Cypress { * Fires as the page begins to load, but before any of your applications JavaScript has executed. This fires at the exact same time as `cy.visit()` `onBeforeLoad` callback. Useful to modify the window on a page transition. * @see https://on.cypress.io/catalog-of-events#App-Events */ - (action: 'window:before:load', fn: (win: Window) => void): void + (action: 'window:before:load', fn: (win: AUTWindow) => void): void /** * Fires after all your resources have finished loading after a page transition. This fires at the exact same time as a `cy.visit()` `onLoad` callback. * @see https://on.cypress.io/catalog-of-events#App-Events */ - (action: 'window:load', fn: (win: Window) => void): void + (action: 'window:load', fn: (win: AUTWindow) => void): void /** * Fires when your application is about to navigate away. The real event object is provided to you. Your app may have set a `returnValue` on the event, which is useful to assert on. * @see https://on.cypress.io/catalog-of-events#App-Events diff --git a/cli/types/index.d.ts b/cli/types/index.d.ts index 8da316edd19d..64600eb51430 100644 --- a/cli/types/index.d.ts +++ b/cli/types/index.d.ts @@ -4,7 +4,7 @@ // Mike Woudenberg // Robbert van Markus // Nicholas Boll -// TypeScript Version: 3.0 +// TypeScript Version: 3.4 // Updated by the Cypress team: https://www.cypress.io/about/ /// diff --git a/cli/types/tests/actions.ts b/cli/types/tests/actions.ts index ae407691ce85..b022c1a8af6a 100644 --- a/cli/types/tests/actions.ts +++ b/cli/types/tests/actions.ts @@ -12,11 +12,11 @@ Cypress.on('window:alert', (text) => { }) Cypress.on('window:before:load', (win) => { - win // $ExpectType Window + win // $ExpectType AUTWindow }) Cypress.on('window:load', (win) => { - win // $ExpectType Window + win // $ExpectType AUTWindow }) Cypress.on('window:before:unload', (event) => { diff --git a/cli/types/tests/cypress-tests.ts b/cli/types/tests/cypress-tests.ts index ccc8e1cba734..75a5c01ba75a 100644 --- a/cli/types/tests/cypress-tests.ts +++ b/cli/types/tests/cypress-tests.ts @@ -287,6 +287,24 @@ cy subject // $ExpectType undefined }) +namespace CypressAUTWindowTests { + cy.go(2).then((win) => { + win // $ExpectType AUTWindow + }) + + cy.reload().then((win) => { + win // $ExpectType AUTWindow + }) + + cy.visit('https://google.com').then(win => { + win // $ExpectType AUTWindow + }) + + cy.window().then(win => { + win // $ExpectType AUTWindow + }) +} + namespace CypressOnTests { Cypress.on('uncaught:exception', (error, runnable) => { error // $ExpectType Error diff --git a/cli/types/tests/kitchen-sink.ts b/cli/types/tests/kitchen-sink.ts index ae6ae2b5c9d8..85a1509d2dfc 100644 --- a/cli/types/tests/kitchen-sink.ts +++ b/cli/types/tests/kitchen-sink.ts @@ -142,3 +142,9 @@ namespace BlobTests { dateUrl // $ExpectType string }) } + +cy.window().then(window => { + window // $ExpectType AUTWindow + + window.eval('1') +}) From 09d94856bcac20df96ad4073fb57a8d1d2e54b4a Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Mon, 6 Jul 2020 09:42:21 -0400 Subject: [PATCH 09/42] test typescript example recipe from this branch --- circle.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/circle.yml b/circle.yml index 6fc1db5536f9..5ec3cc5ef3f2 100644 --- a/circle.yml +++ b/circle.yml @@ -1311,11 +1311,12 @@ jobs: test-binary-against-recipe-pull-request: <<: *defaults steps: + # test a specific pull request by number from cypress-example-recipes - test-binary-against-repo: repo: cypress-example-recipes command: npm run test:ci - pull_request_id: 503 - folder: examples/stubbing-spying__window-fetch + pull_request_id: 515 + folder: examples/fundamentals__typescript "test-binary-against-kitchensink": <<: *defaults @@ -1640,11 +1641,11 @@ linux-workflow: &linux-workflow - build-binary - build-npm-package - test-binary-against-recipe-pull-request: - name: Test fetch polyfill + name: Test TypeScript recipe filters: branches: only: - - polyfill-fetch + - v5.0-release requires: - build-binary - build-npm-package From 8971ad1fa40473c10d14edbb2e5765ab580ee991 Mon Sep 17 00:00:00 2001 From: Kukhyeon Heo Date: Tue, 7 Jul 2020 14:28:51 +0900 Subject: [PATCH 10/42] Update blob-util to 2.0.2 (#7795) --- cli/package.json | 2 +- cli/scripts/utils.js | 1 - cli/types/cy-blob-util.d.ts | 2 +- .../util/breaking_change_warnings_spec.js | 41 +++++++++++++++++++ packages/driver/package.json | 2 +- packages/driver/src/cypress.js | 3 +- packages/driver/src/cypress/error_messages.js | 9 ++++ .../src/util/breaking_change_warning.ts | 34 +++++++++++++++ .../form_submission_multipart_spec.coffee | 8 +++- yarn.lock | 40 ++---------------- 10 files changed, 99 insertions(+), 43 deletions(-) create mode 100644 packages/driver/cypress/integration/util/breaking_change_warnings_spec.js create mode 100644 packages/driver/src/util/breaking_change_warning.ts diff --git a/cli/package.json b/cli/package.json index 52fc50f53f3a..7766dea27b96 100644 --- a/cli/package.json +++ b/cli/package.json @@ -26,6 +26,7 @@ "@types/sinonjs__fake-timers": "6.0.1", "@types/sizzle": "2.3.2", "arch": "2.1.2", + "blob-util": "2.0.2", "bluebird": "3.7.2", "cachedir": "2.3.0", "chalk": "4.1.0", @@ -63,7 +64,6 @@ "@babel/preset-env": "7.9.5", "@cypress/sinon-chai": "1.1.0", "@packages/root": "*", - "@types/blob-util": "1.3.3", "@types/bluebird": "3.5.29", "@types/chai": "4.2.7", "@types/chai-jquery": "1.1.40", diff --git a/cli/scripts/utils.js b/cli/scripts/utils.js index 8c215081d438..87389ed9826e 100644 --- a/cli/scripts/utils.js +++ b/cli/scripts/utils.js @@ -4,7 +4,6 @@ * definition files that we will need to include with our NPM package. */ const includeTypes = [ - 'blob-util', 'bluebird', 'lodash', 'mocha', diff --git a/cli/types/cy-blob-util.d.ts b/cli/types/cy-blob-util.d.ts index a70481fa30fc..e8fc61702f86 100644 --- a/cli/types/cy-blob-util.d.ts +++ b/cli/types/cy-blob-util.d.ts @@ -3,7 +3,7 @@ // so that Cypress can get and use the Blob type // tslint:disable-next-line:no-implicit-dependencies -import * as blobUtil from './blob-util' +import * as blobUtil from 'blob-util' export = BlobUtil export as namespace BlobUtil diff --git a/packages/driver/cypress/integration/util/breaking_change_warnings_spec.js b/packages/driver/cypress/integration/util/breaking_change_warnings_spec.js new file mode 100644 index 000000000000..3b5adc523310 --- /dev/null +++ b/packages/driver/cypress/integration/util/breaking_change_warnings_spec.js @@ -0,0 +1,41 @@ +describe('blob-util 2.x', () => { + it('arrayBufferToBlob', () => { + cy.on('fail', (err) => { + expect(err.message).to.include('no longer returns a `Promise`') + }) + + Cypress.Blob.arrayBufferToBlob('1234').then((blob) => { + // it should fail. + }) + }) + + it('base64StringToBlob', () => { + cy.on('fail', (err) => { + expect(err.message).to.include('no longer returns a `Promise`') + }) + + Cypress.Blob.base64StringToBlob('1234').then((blob) => { + // it should fail. + }) + }) + + it('binaryStringToBlob', () => { + cy.on('fail', (err) => { + expect(err.message).to.include('no longer returns a `Promise`') + }) + + Cypress.Blob.binaryStringToBlob('0100101').then((blob) => { + // it should fail. + }) + }) + + it('dataURLToBlob', () => { + cy.on('fail', (err) => { + expect(err.message).to.include('no longer returns a `Promise`') + }) + + Cypress.Blob.dataURLToBlob('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==').then((blob) => { + // it should fail. + }) + }) +}) diff --git a/packages/driver/package.json b/packages/driver/package.json index 03fdc5f7beda..ea44e4a1c98f 100644 --- a/packages/driver/package.json +++ b/packages/driver/package.json @@ -22,7 +22,7 @@ "angular": "1.8.0", "backbone": "1.4.0", "basic-auth": "2.0.1", - "blob-util": "1.3.0", + "blob-util": "2.0.2", "bluebird": "3.5.3", "body-parser": "1.19.0", "bootstrap": "4.4.1", diff --git a/packages/driver/src/cypress.js b/packages/driver/src/cypress.js index c17771339627..7adf60b03ff0 100644 --- a/packages/driver/src/cypress.js +++ b/packages/driver/src/cypress.js @@ -34,6 +34,7 @@ const $scriptUtils = require('./cypress/script_utils') const browserInfo = require('./cypress/browser') const resolvers = require('./cypress/resolvers') const debug = require('debug')('cypress:driver:cypress') +const { wrapBlobUtil } = require('./util/breaking_change_warning') const jqueryProxyFn = function (...args) { if (!this.cy) { @@ -613,7 +614,7 @@ $Cypress.prototype.SelectorPlayground = $SelectorPlayground $Cypress.prototype.utils = $utils $Cypress.prototype._ = _ $Cypress.prototype.moment = moment -$Cypress.prototype.Blob = blobUtil +$Cypress.prototype.Blob = wrapBlobUtil(blobUtil) $Cypress.prototype.Promise = Promise $Cypress.prototype.minimatch = minimatch $Cypress.prototype.sinon = sinon diff --git a/packages/driver/src/cypress/error_messages.js b/packages/driver/src/cypress/error_messages.js index 1f7d929017e5..68954d16d0b5 100644 --- a/packages/driver/src/cypress/error_messages.js +++ b/packages/driver/src/cypress/error_messages.js @@ -153,6 +153,15 @@ module.exports = { }, }, + breaking_change: { + blob_util2 (obj) { + return { + message: `\`${obj.functionName}()\` no longer returns a \`Promise\`. Update the use of \`${obj.functionName}()\` to expect a returned \`Blob\`.`, + docsUrl: 'https://on.cypress.io/migration-guide', + } + }, + }, + browser: { invalid_arg: '{{prefix}} must be passed a string, object, or an array. You passed: `{{obj}}`', }, diff --git a/packages/driver/src/util/breaking_change_warning.ts b/packages/driver/src/util/breaking_change_warning.ts new file mode 100644 index 000000000000..61671c661611 --- /dev/null +++ b/packages/driver/src/util/breaking_change_warning.ts @@ -0,0 +1,34 @@ +import $errUtil from '../cypress/error_utils' + +export function wrapBlobUtil (blobUtil) { + const breakingChanges = [ + 'arrayBufferToBlob', + 'base64StringToBlob', + 'binaryStringToBlob', + 'dataURLToBlob', + ] + + const obj = {} + + Object.keys(blobUtil).forEach((key) => { + if (breakingChanges.includes(key)) { + obj[key] = function (...args) { + const val = blobUtil[key](...args) + + val.then = function () { + $errUtil.throwErrByPath('breaking_change.blob_util2', { + args: { + functionName: key, + }, + }) + } + + return val + } + } else { + obj[key] = blobUtil[key] + } + }) + + return obj +} diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/form_submission_multipart_spec.coffee b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/form_submission_multipart_spec.coffee index 20f7bb960a1c..171b9109393d 100644 --- a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/form_submission_multipart_spec.coffee +++ b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/form_submission_multipart_spec.coffee @@ -18,13 +18,17 @@ Cypress.Commands.add 'setFile', { prevSubject: "element" }, (element, filePath) return cy.fixture(filePath, "base64") return fixtureOrReadFile(filePath).then (image) -> - return Blob.base64StringToBlob(image).then (blob) -> + return new Promise((resolve) => + blob = Blob.base64StringToBlob(image) elementNode = element[0] file = new File([ blob ], filePath, type: mimeType) dataTransfer = new DataTransfer dataTransfer.items.add(file) elementNode.files = dataTransfer.files - elementNode.dispatchEvent new Event("change", { bubbles: true }) + result = elementNode.dispatchEvent new Event("change", { bubbles: true }) + + resolve(result) + ) describe "
submissions", -> it "can submit a form correctly", -> diff --git a/yarn.lock b/yarn.lock index de18f201eb44..ecbe4282b311 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3806,11 +3806,6 @@ dependencies: "@babel/types" "^7.3.0" -"@types/blob-util@1.3.3": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@types/blob-util/-/blob-util-1.3.3.tgz#adba644ae34f88e1dd9a5864c66ad651caaf628a" - integrity sha512-4ahcL/QDnpjWA2Qs16ZMQif7HjGP2cw3AGjHabybjw7Vm1EKu+cfQN1D78BaZbS1WJNa1opSMF5HNMztx7lR0w== - "@types/bluebird@*": version "3.5.31" resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.31.tgz#d17fa0ec242b51c3db302481c557ce3813bf45cb" @@ -6978,18 +6973,10 @@ black-hole-stream@0.0.1: resolved "https://registry.yarnpkg.com/black-hole-stream/-/black-hole-stream-0.0.1.tgz#33b7a06b9f1e7453d6041b82974481d2152aea42" integrity sha1-M7ega58edFPWBBuCl0SB0hUq6kI= -blob-util@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-1.3.0.tgz#dbb4e8caffd50b5720d347e1169b6369ba34fe95" - integrity sha512-cjmYgWj8BQwoX+95rKkWvITL6PiEhSr19sX8qLRu+O6J2qmWmgUvxqhqJn425RFAwLovdDNnsCQ64RRHXjsXSg== - dependencies: - blob "0.0.4" - native-or-lie "1.0.2" - -blob@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921" - integrity sha1-vPEwUspURj8w+fx+lbmkdjCpSSE= +blob-util@2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb" + integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ== blob@0.0.5: version "0.0.5" @@ -14206,11 +14193,6 @@ image-size@0.8.3, image-size@^0.8.2: dependencies: queue "6.0.1" -immediate@~3.0.5: - version "3.0.6" - resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" - integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= - immutable@3.7.6: version "3.7.6" resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.7.6.tgz#13b4d3cb12befa15482a26fe1b2ebae640071e4b" @@ -16394,13 +16376,6 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -lie@*: - version "3.3.0" - resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" - integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== - dependencies: - immediate "~3.0.5" - liftoff@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-3.1.0.tgz#c9ba6081f908670607ee79062d700df062c52ed3" @@ -18247,13 +18222,6 @@ napi-build-utils@^1.0.1: resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== -native-or-lie@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/native-or-lie/-/native-or-lie-1.0.2.tgz#c870ee0ba0bf0ff11350595d216cfea68a6d8086" - integrity sha1-yHDuC6C/D/ETUFldIWz+poptgIY= - dependencies: - lie "*" - native-promise-only@~0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/native-promise-only/-/native-promise-only-0.8.1.tgz#20a318c30cb45f71fe7adfbf7b21c99c1472ef11" From daeca71ece7befa82e242884c3c94555b5db2e81 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Sun, 12 Jul 2020 23:06:13 -0400 Subject: [PATCH 11/42] remove this file after bad develop merge --- .../cypress/integration/request_spec.coffee | 181 ------------------ 1 file changed, 181 deletions(-) delete mode 100644 packages/server/test/support/fixtures/projects/e2e/cypress/integration/request_spec.coffee diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/request_spec.coffee b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/request_spec.coffee deleted file mode 100644 index 5b96e92d988c..000000000000 --- a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/request_spec.coffee +++ /dev/null @@ -1,181 +0,0 @@ -describe "redirects + requests", -> - it "gets and sets cookies from cy.request", -> - oneMinuteFromNow = Cypress.moment().add(1, "minute").unix() - - cy - .request("http://localhost:2293/") - .request("http://localhost:2293/cookies") - .its("body").should("deep.eq", { - "2293": "true" - "2293-session": "true" - }) - .getCookies().then (cookies) -> - console.log cookies - - expect(cookies[0].domain).to.eq("localhost") - expect(cookies[0].name).to.eq("2293") - expect(cookies[0].value).to.eq("true") - expect(cookies[0].httpOnly).to.eq(true) - expect(cookies[0].path).to.eq("/") - expect(cookies[0].secure).to.eq(false) - expect(cookies[0].expiry).to.be.closeTo(oneMinuteFromNow, 5) - - expect(cookies[1]).to.deep.eq(Cypress._.merge({ - domain: "localhost" - name: "2293-session" - value: "true" - httpOnly: false - path: "/" - secure: false - }, (if Cypress.isBrowser('firefox') then { sameSite: 'no_restriction' } else {}))) - - it "visits to a different superdomain will be resolved twice", -> - cy - .visit("http://localhost:2290") - .url() - .should("eq", "http://localhost:2292/") - .request("http://localhost:2290/cookies/one") - .its("body").should("deep.eq", {"2290": "true"}) - .request("http://localhost:2291/cookies/two") - .its("body").should("deep.eq", {"2291": "true"}) - .request("http://localhost:2292/cookies/three") - .its("body").should("deep.eq", {"2292": "true"}) - .request("http://localhost:2292/counts") - .its("body").should("deep.eq", { - "localhost:2290": 2 - "localhost:2291": 2 - "localhost:2292": 2 - "localhost:2293": 1 ## from the previous test - }) - - it "automatically follows redirects", -> - cy - .request("http://localhost:2294/redirect") - .then (resp) -> - expect(resp.status).to.eq(200) - expect(resp.body).to.eq("home") - - ## https://github.com/cypress-io/cypress/issues/5654 - it "can turn off following redirects that set a cookie", -> - cy - .request({ - url: "http://localhost:2294/redirectWithCookie" - followRedirect: false - }) - .then (resp) -> - expect(resp.status).to.eq(302) - - it "can turn off automatically following redirects", -> - cy - .request({ - url: "http://localhost:2294/redirect" - followRedirect: false - }) - .then (resp) -> - expect(resp.status).to.eq(302) - expect(resp.body).to.eq("Found. Redirecting to /home") - expect(resp.redirectedToUrl).to.eq("http://localhost:2294/home") - - it "follows all redirects even when they change methods", -> - cy - .request({ - method: "POST" - url: "http://localhost:2294/redirectPost" - }) - .then (resp) -> - expect(resp.status).to.eq(200) - expect(resp.body).to.eq("home") - - it "can submit json body", -> - cy - .request({ - method: "POST" - url: "http://localhost:2294/json" - body: { - foo: "bar" - baz: "quux" - } - }) - .its("body") - .should("deep.eq", { - foo: "bar" - baz: "quux" - }) - - it "can submit form url encoded body", -> - cy - .request({ - method: "POST" - url: "http://localhost:2294/form" - form: true - body: { - foo: "bar" - baz: "quux" - } - }) - .its("body") - .should("deep.eq", { - foo: "bar" - baz: "quux" - }) - - it "can send qs query params", -> - cy - .request({ - url: "http://localhost:2294/params" - qs: { - foo: "bar" - baz: "quux" - a: 1 - } - }) - .its("body") - .should("deep.eq", { - url: "/params?foo=bar&baz=quux&a=1" - params: { - foo: "bar" - baz: "quux" - a: "1" - } - }) - - it "passes even on non 2xx or 3xx status code", -> - cy - .request({ - url: "http://localhost:2294/statusCode?code=401" - failOnStatusCode: false - }) - .its("status").should("eq", 401) - .request({ - url: "http://localhost:2294/statusCode?code=500" - failOnStatusCode: false - }) - .its("status").should("eq", 500) - - it "sets Accept header to */* by default", -> - cy - .request("http://localhost:2294/headers") - .its("body") - .its("headers") - .its("accept") - .should("eq", "*/*") - - it "can override the accept header", -> - cy - .request({ - url: "http://localhost:2294/headers" - headers: { - Accept: "text/html" - } - }) - .its("body") - .its("headers") - .its("accept") - .should("eq", "text/html") - - it "issue #375: does not duplicate request cookies on 302 redirect", -> - cy - .request("http://localhost:2295/login") - .request("POST", "http://localhost:2295/login") - .its("body.cookie") - .should("eq", "session=2") From 6201c97dba04852df1e7f4aa4adb2ed27777e206 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Sun, 12 Jul 2020 23:27:37 -0400 Subject: [PATCH 12/42] fix failing test from bad develop merge --- .../projects/e2e/cypress/integration/request_spec.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/request_spec.js b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/request_spec.js index b0758fe9f164..c1577c5de469 100644 --- a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/request_spec.js +++ b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/request_spec.js @@ -20,14 +20,20 @@ describe('redirects + requests', () => { expect(cookies[0].secure).to.eq(false) expect(cookies[0].expiry).to.be.closeTo(oneMinuteFromNow, 5) - expect(cookies[1]).to.deep.eq({ + const expectedCookie = { domain: 'localhost', name: '2293-session', value: 'true', httpOnly: false, path: '/', secure: false, - }) + } + + if (Cypress.isBrowser('firefox')) { + expectedCookie.sameSite = 'no_restriction' + } + + expect(cookies[1]).to.deep.eq(expectedCookie) }) }) From cf936476e346cb19756318c9c8c84678054ab344 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Sun, 12 Jul 2020 23:33:27 -0400 Subject: [PATCH 13/42] add done() cb to verify failure is achieved --- .../util/breaking_change_warnings_spec.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/driver/cypress/integration/util/breaking_change_warnings_spec.js b/packages/driver/cypress/integration/util/breaking_change_warnings_spec.js index 3b5adc523310..5d3acf47228d 100644 --- a/packages/driver/cypress/integration/util/breaking_change_warnings_spec.js +++ b/packages/driver/cypress/integration/util/breaking_change_warnings_spec.js @@ -1,7 +1,8 @@ describe('blob-util 2.x', () => { - it('arrayBufferToBlob', () => { + it('arrayBufferToBlob', (done) => { cy.on('fail', (err) => { expect(err.message).to.include('no longer returns a `Promise`') + done() }) Cypress.Blob.arrayBufferToBlob('1234').then((blob) => { @@ -9,9 +10,10 @@ describe('blob-util 2.x', () => { }) }) - it('base64StringToBlob', () => { + it('base64StringToBlob', (done) => { cy.on('fail', (err) => { expect(err.message).to.include('no longer returns a `Promise`') + done() }) Cypress.Blob.base64StringToBlob('1234').then((blob) => { @@ -19,9 +21,10 @@ describe('blob-util 2.x', () => { }) }) - it('binaryStringToBlob', () => { + it('binaryStringToBlob', (done) => { cy.on('fail', (err) => { expect(err.message).to.include('no longer returns a `Promise`') + done() }) Cypress.Blob.binaryStringToBlob('0100101').then((blob) => { @@ -29,9 +32,10 @@ describe('blob-util 2.x', () => { }) }) - it('dataURLToBlob', () => { + it('dataURLToBlob', (done) => { cy.on('fail', (err) => { expect(err.message).to.include('no longer returns a `Promise`') + done() }) Cypress.Blob.dataURLToBlob('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==').then((blob) => { From 4617813be124714e7edbe690b78a896b948ddad8 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Sun, 12 Jul 2020 23:38:21 -0400 Subject: [PATCH 14/42] make cypress versions in errors consistent --- packages/server/lib/errors.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/server/lib/errors.js b/packages/server/lib/errors.js index 914eae146b65..78153766d5cc 100644 --- a/packages/server/lib/errors.js +++ b/packages/server/lib/errors.js @@ -137,9 +137,9 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { if (arg1 === 'canary') { str += '\n\n' str += stripIndent`\ - Note: In Cypress 4.0, Canary must be launched as \`chrome:canary\`, not \`canary\`. + Note: In Cypress version 4.0.0, Canary must be launched as \`chrome:canary\`, not \`canary\`. - See https://on.cypress.io/migration-guide for more information on breaking changes in 4.0.` + See https://on.cypress.io/migration-guide for more information on breaking changes in 4.0.0.` } return str @@ -648,7 +648,7 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { ${chalk.yellow(arg1)}` case 'SCREENSHOT_ON_HEADLESS_FAILURE_REMOVED': return stripIndent`\ - In Cypress v3.0.0 we removed the configuration option ${chalk.yellow('\`screenshotOnHeadlessFailure\`')} + In Cypress version 3.0.0 we removed the configuration option ${chalk.yellow('\`screenshotOnHeadlessFailure\`')} You now configure this behavior in your test code. @@ -925,7 +925,7 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { If you don't require screenshots or videos to be stored you can safely ignore this warning.` case 'EXPERIMENTAL_SAMESITE_REMOVED': return stripIndent`\ - The \`experimentalGetCookiesSameSite\` configuration option was removed in Cypress 5. Yielding the \`sameSite\` property is now the default behavior of the \`cy.cookie\` commands. + The \`experimentalGetCookiesSameSite\` configuration option was removed in Cypress version 5.0.0. Yielding the \`sameSite\` property is now the default behavior of the \`cy.cookie\` commands. You can safely remove this option from your config.` default: From f9c0137a7f2897801e9ece05be1f40f872da43e3 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Mon, 13 Jul 2020 00:26:09 -0400 Subject: [PATCH 15/42] remove outdated pgks --- yarn.lock | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/yarn.lock b/yarn.lock index 491794869b55..1f90888d356e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7915,7 +7915,7 @@ chai@4.2.0: pathval "^1.1.0" type-detect "^4.0.5" -chalk@*, chalk@^4.0.0, chalk@^4.1.0: +chalk@*, chalk@4.1.0, chalk@^4.0.0, chalk@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== @@ -7952,14 +7952,6 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3. escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" - integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - chalk@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" @@ -12451,7 +12443,7 @@ fs-extra@9.0.0: jsonfile "^6.0.1" universalify "^1.0.0" -fs-extra@9.0.1: +fs-extra@9.0.1, fs-extra@^9.0.0, fs-extra@^9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== @@ -12497,16 +12489,6 @@ fs-extra@^6.0.1: jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^9.0.0, fs-extra@^9.0.1: - version "9.0.1" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" - integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^1.0.0" - fs-minipass@^1.2.5: version "1.2.7" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" From db624e8502c4b25a6c9c4f77d2dd9fcb976b1b45 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Mon, 13 Jul 2020 00:26:39 -0400 Subject: [PATCH 16/42] fix node unsafe buffer warnings in convert-source-map and inline-source-map --- packages/server/package.json | 6 +++++- ...mbine-source-map++convert-source-map+1.1.3.patch | 13 +++++++++++++ .../server/patches/inline-source-map+0.6.2.patch | 13 +++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 packages/server/patches/combine-source-map++convert-source-map+1.1.3.patch create mode 100644 packages/server/patches/inline-source-map+0.6.2.patch diff --git a/packages/server/package.json b/packages/server/package.json index 7e33b7b8d4e5..06585db698e5 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -197,7 +197,11 @@ "productName": "Cypress", "workspaces": { "nohoist": [ - "foxdriver" + "foxdriver", + "**/@cypress/browserify-preprocessor", + "@cypress/browserify-preprocessor/**", + "**/browserify", + "browserify/**" ] }, "optionalDependencies": { diff --git a/packages/server/patches/combine-source-map++convert-source-map+1.1.3.patch b/packages/server/patches/combine-source-map++convert-source-map+1.1.3.patch new file mode 100644 index 000000000000..e5693da69c0d --- /dev/null +++ b/packages/server/patches/combine-source-map++convert-source-map+1.1.3.patch @@ -0,0 +1,13 @@ +diff --git a/node_modules/combine-source-map/node_modules/convert-source-map/index.js b/node_modules/combine-source-map/node_modules/convert-source-map/index.js +index bfe92d1..beae518 100644 +--- a/node_modules/combine-source-map/node_modules/convert-source-map/index.js ++++ b/node_modules/combine-source-map/node_modules/convert-source-map/index.js +@@ -9,7 +9,7 @@ var mapFileCommentRx = + /(?:\/\/[@#][ \t]+sourceMappingURL=([^\s'"]+?)[ \t]*$)|(?:\/\*[@#][ \t]+sourceMappingURL=([^\*]+?)[ \t]*(?:\*\/){1}[ \t]*$)/mg + + function decodeBase64(base64) { +- return new Buffer(base64, 'base64').toString(); ++ return Buffer.from(base64, 'base64').toString(); + } + + function stripComment(sm) { diff --git a/packages/server/patches/inline-source-map+0.6.2.patch b/packages/server/patches/inline-source-map+0.6.2.patch new file mode 100644 index 000000000000..5e5ae09a02aa --- /dev/null +++ b/packages/server/patches/inline-source-map+0.6.2.patch @@ -0,0 +1,13 @@ +diff --git a/node_modules/inline-source-map/index.js b/node_modules/inline-source-map/index.js +index df74d61..7641aad 100644 +--- a/node_modules/inline-source-map/index.js ++++ b/node_modules/inline-source-map/index.js +@@ -91,7 +91,7 @@ Generator.prototype.addSourceContent = function (sourceFile, sourcesContent) { + */ + Generator.prototype.base64Encode = function () { + var map = this.toString(); +- return new Buffer(map).toString('base64'); ++ return Buffer.from(map).toString('base64'); + }; + + /** From 915945116fffb2ceb1b0d241f93684ab9873b159 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Mon, 13 Jul 2020 01:23:38 -0400 Subject: [PATCH 17/42] fix browser error snapshots --- packages/server/__snapshots__/browsers_spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/__snapshots__/browsers_spec.js b/packages/server/__snapshots__/browsers_spec.js index c311767ebe9b..d7e3f9a23c14 100644 --- a/packages/server/__snapshots__/browsers_spec.js +++ b/packages/server/__snapshots__/browsers_spec.js @@ -37,7 +37,7 @@ Available browsers found on your system are: - chrome:canary - firefox -Note: In Cypress 4.0, Canary must be launched as \`chrome:canary\`, not \`canary\`. +Note: In Cypress version 4.0.0, Canary must be launched as \`chrome:canary\`, not \`canary\`. -See https://on.cypress.io/migration-guide for more information on breaking changes in 4.0. +See https://on.cypress.io/migration-guide for more information on breaking changes in 4.0.0. ` From 513531ba6a4d3a33135cf4b098dca5eb9674abd7 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Mon, 13 Jul 2020 01:44:49 -0400 Subject: [PATCH 18/42] go github go From 27e8c817d34a9ac70ff840d45938d16d3a225736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?La=C3=ADs=20Tomaz?= <41899092+laiscoolblue@users.noreply.github.com> Date: Mon, 13 Jul 2020 10:46:19 +0200 Subject: [PATCH 19/42] Rename configuration option blacklistHosts (#7622) Co-authored-by: Jennifer Shehane Co-authored-by: Brian Mann --- circle.yml | 12 +++++ cli/schema/cypress.schema.json | 4 +- .../desktop-gui/cypress/fixtures/config.json | 4 +- .../cypress/integration/settings_spec.js | 6 +-- .../integration/commands/agents_spec.js | 16 +++---- .../integration/commands/aliasing_spec.js | 8 ++-- packages/driver/src/cy/aliases.js | 4 +- packages/network/README.md | 2 +- .../network/lib/{blacklist.ts => blocked.ts} | 6 +-- packages/network/lib/index.ts | 4 +- .../{blacklist_spec.ts => blocked_spec.ts} | 10 ++--- packages/proxy/lib/http/request-middleware.ts | 16 +++---- .../test/unit/http/request-middleware.spec.ts | 2 +- ...st_hosts_spec.js => 1_block_hosts_spec.js} | 24 ++++------ packages/server/lib/browsers/firefox.ts | 2 +- packages/server/lib/config.js | 11 +++-- packages/server/lib/errors.js | 2 +- packages/server/lib/server.js | 15 ++++--- ...st_hosts_spec.js => 1_block_hosts_spec.js} | 7 +-- .../server/test/integration/cypress_spec.js | 44 +++++++++++-------- .../test/integration/http_requests_spec.js | 6 +-- ...ts_spec.coffee => block_hosts_spec.coffee} | 4 +- packages/server/test/unit/args_spec.js | 14 +++--- packages/server/test/unit/config_spec.js | 34 +++++++------- 24 files changed, 137 insertions(+), 120 deletions(-) rename packages/network/lib/{blacklist.ts => blocked.ts} (71%) rename packages/network/test/unit/{blacklist_spec.ts => blocked_spec.ts} (87%) rename packages/server/__snapshots__/{1_blacklist_hosts_spec.js => 1_block_hosts_spec.js} (83%) rename packages/server/test/e2e/{1_blacklist_hosts_spec.js => 1_block_hosts_spec.js} (82%) rename packages/server/test/support/fixtures/projects/e2e/cypress/integration/{blacklist_hosts_spec.coffee => block_hosts_spec.coffee} (91%) diff --git a/circle.yml b/circle.yml index 5ec3cc5ef3f2..64740734f74e 100644 --- a/circle.yml +++ b/circle.yml @@ -1590,6 +1590,12 @@ linux-workflow: &linux-workflow - test-kitchensink: requires: - build + filters: + branches: + ignore: + ## TODO: remove this upon merging the PR + - rename-blacklisthosts + - pull/7622 - test-kitchensink-against-staging: context: test-runner:record-tests filters: @@ -1640,6 +1646,12 @@ linux-workflow: &linux-workflow requires: - build-binary - build-npm-package + filters: + branches: + ignore: + ## TODO: remove this upon merging the PR + - rename-blacklisthosts + - pull/7622 - test-binary-against-recipe-pull-request: name: Test TypeScript recipe filters: diff --git a/cli/schema/cypress.schema.json b/cli/schema/cypress.schema.json index c3d2ffb22876..9ab63fce9cb2 100644 --- a/cli/schema/cypress.schema.json +++ b/cli/schema/cypress.schema.json @@ -175,7 +175,7 @@ "default": null, "description": "Enables you to override the default user agent the browser sends in all request headers. User agent values are typically used by servers to help identify the operating system, browser, and browser version. See User-Agent MDN Documentation for example user agent values here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent" }, - "blacklistHosts": { + "blockHosts": { "type": [ "string", "array" @@ -184,7 +184,7 @@ "type": "string" }, "default": null, - "description": "A String or Array of hosts that you wish to block traffic for. Please read the notes for examples on using this https://on.cypress.io/configuration#blacklistHosts" + "description": "A String or Array of hosts that you wish to block traffic for. Please read the notes for examples on using this https://on.cypress.io/configuration#blockHosts" }, "modifyObstructiveCode": { "type": "boolean", diff --git a/packages/desktop-gui/cypress/fixtures/config.json b/packages/desktop-gui/cypress/fixtures/config.json index f55c7483b7df..5da0c8d3e099 100644 --- a/packages/desktop-gui/cypress/fixtures/config.json +++ b/packages/desktop-gui/cypress/fixtures/config.json @@ -109,7 +109,7 @@ "cypressHostUrl": "http://localhost:2020", "cypressEnv": "development", "env": {}, - "blacklistHosts": [ + "blockHosts": [ "www.google-analytics.com", "hotjar.com" ], @@ -376,7 +376,7 @@ "from": "default", "value": true }, - "blacklistHosts": { + "blockHosts": { "from": "config", "value": [ "www.google-analytics.com", diff --git a/packages/desktop-gui/cypress/integration/settings_spec.js b/packages/desktop-gui/cypress/integration/settings_spec.js index 85eaf91711cd..7f43548f3828 100644 --- a/packages/desktop-gui/cypress/integration/settings_spec.js +++ b/packages/desktop-gui/cypress/integration/settings_spec.js @@ -154,7 +154,7 @@ describe('Settings', () => { cy.get('@config-vars') .contains('span', 'Electron').parent('span').should('have.class', 'plugin') - cy.contains('span', 'blacklistHosts').parents('div').first().find('span').first().click() + cy.contains('span', 'blockHosts').parents('div').first().find('span').first().click() cy.get('@config-vars') .contains('span', 'www.google-analytics.com').parent('span').should('have.class', 'config') @@ -207,8 +207,8 @@ describe('Settings', () => { cy.get('.line').contains('*.foobar.com, *.bazqux.com') }) - it('displays "array" values for blacklistHosts', () => { - cy.contains('.line', 'blacklistHosts').contains('www.google-analytics.com, hotjar.com') + it('displays "array" values for blockHosts', () => { + cy.contains('.line', 'blockHosts').contains('www.google-analytics.com, hotjar.com') }) it('opens help link on click', () => { diff --git a/packages/driver/cypress/integration/commands/agents_spec.js b/packages/driver/cypress/integration/commands/agents_spec.js index 0088dfc6c210..bff2ae3ea08f 100644 --- a/packages/driver/cypress/integration/commands/agents_spec.js +++ b/packages/driver/cypress/integration/commands/agents_spec.js @@ -352,11 +352,11 @@ describe('src/cy/commands/agents', () => { }).to.throw('`cy.as()` cannot be passed an empty string.') }) - _.each(['test', 'runnable', 'timeout', 'slow', 'skip', 'inspect'], (blacklist) => { - it(`throws on a blacklisted word: ${blacklist}`, () => { + _.each(['test', 'runnable', 'timeout', 'slow', 'skip', 'inspect'], (reserved) => { + it(`throws on a reserved word: ${reserved}`, () => { expect(() => { - cy.stub().as(blacklist) - }).to.throw(`\`cy.as()\` cannot be aliased as: \`${blacklist}\`. This word is reserved.`) + cy.stub().as(reserved) + }).to.throw(`\`cy.as()\` cannot be aliased as: \`${reserved}\`. This word is reserved.`) }) }) }) @@ -433,11 +433,11 @@ describe('src/cy/commands/agents', () => { }).to.throw('`cy.as()` cannot be passed an empty string.') }) - _.each(['test', 'runnable', 'timeout', 'slow', 'skip', 'inspect'], (blacklist) => { - it(`throws on a blacklisted word: ${blacklist}`, () => { + _.each(['test', 'runnable', 'timeout', 'slow', 'skip', 'inspect'], (reserved) => { + it(`throws on a reserved word: ${reserved}`, () => { expect(() => { - cy.stub().as(blacklist) - }).to.throw(`\`cy.as()\` cannot be aliased as: \`${blacklist}\`. This word is reserved.`) + cy.stub().as(reserved) + }).to.throw(`\`cy.as()\` cannot be aliased as: \`${reserved}\`. This word is reserved.`) }) }) }) diff --git a/packages/driver/cypress/integration/commands/aliasing_spec.js b/packages/driver/cypress/integration/commands/aliasing_spec.js index 3a88ff2d70fb..db17200a01cf 100644 --- a/packages/driver/cypress/integration/commands/aliasing_spec.js +++ b/packages/driver/cypress/integration/commands/aliasing_spec.js @@ -232,16 +232,16 @@ describe('src/cy/commands/aliasing', () => { cy.get('@my@Alias') }) - _.each(['test', 'runnable', 'timeout', 'slow', 'skip', 'inspect'], (blacklist) => { - it(`throws on a blacklisted word: ${blacklist}`, (done) => { + _.each(['test', 'runnable', 'timeout', 'slow', 'skip', 'inspect'], (reserved) => { + it(`throws on a reserved word: ${reserved}`, (done) => { cy.on('fail', (err) => { - expect(err.message).to.eq(`\`cy.as()\` cannot be aliased as: \`${blacklist}\`. This word is reserved.`) + expect(err.message).to.eq(`\`cy.as()\` cannot be aliased as: \`${reserved}\`. This word is reserved.`) expect(err.docsUrl).to.eq('https://on.cypress.io/as') done() }) - cy.get('div:first').as(blacklist) + cy.get('div:first').as(reserved) }) }) }) diff --git a/packages/driver/src/cy/aliases.js b/packages/driver/src/cy/aliases.js index 8bf2dff1c6ab..fe7db54847b8 100644 --- a/packages/driver/src/cy/aliases.js +++ b/packages/driver/src/cy/aliases.js @@ -6,7 +6,7 @@ const aliasRe = /^@.+/ const aliasDisplayRe = /^([@]+)/ const requestXhrRe = /\.request$/ -const blacklist = ['test', 'runnable', 'timeout', 'slow', 'skip', 'inspect'] +const reserved = ['test', 'runnable', 'timeout', 'slow', 'skip', 'inspect'] const aliasDisplayName = (name) => { return name.replace(aliasDisplayRe, '') @@ -38,7 +38,7 @@ const validateAlias = (alias) => { $errUtils.throwErrByPath('as.empty_string') } - if (blacklist.includes(alias)) { + if (reserved.includes(alias)) { return $errUtils.throwErrByPath('as.reserved_word', { args: { alias } }) } } diff --git a/packages/network/README.md b/packages/network/README.md index df06bbe38812..5263862e391a 100644 --- a/packages/network/README.md +++ b/packages/network/README.md @@ -8,7 +8,7 @@ You can see a list of the modules exported from this package in [./lib/index.ts] * `agent` is a HTTP/HTTPS [agent][1] with support for HTTP/HTTPS proxies and keepalive whenever possible * `allowDestroy` can be used to wrap a `net.Server` to add a `.destroy()` method -* `blacklist` is a utility for matching glob blacklists +* `blocked` is a utility for matching blocked globs * `concatStream` is a wrapper around [`concat-stream@1.6.2`][2] that makes it always yield a `Buffer` * `connect` contains utilities for making network connections, including `createRetryingSocket` * `cors` contains utilities for Cross-Origin Resource Sharing diff --git a/packages/network/lib/blacklist.ts b/packages/network/lib/blocked.ts similarity index 71% rename from packages/network/lib/blacklist.ts rename to packages/network/lib/blocked.ts index 1a6b7021ecba..1e59bbda5b91 100644 --- a/packages/network/lib/blacklist.ts +++ b/packages/network/lib/blocked.ts @@ -2,9 +2,9 @@ import _ from 'lodash' import minimatch from 'minimatch' import { stripProtocolAndDefaultPorts } from './uri' -export function matches (urlToCheck, blacklistHosts) { +export function matches (urlToCheck, blockHosts) { // normalize into flat array - blacklistHosts = [].concat(blacklistHosts) + blockHosts = [].concat(blockHosts) urlToCheck = stripProtocolAndDefaultPorts(urlToCheck) @@ -14,5 +14,5 @@ export function matches (urlToCheck, blacklistHosts) { return minimatch(urlToCheck, hostMatcher) } - return _.find(blacklistHosts, matchUrl) + return _.find(blockHosts, matchUrl) } diff --git a/packages/network/lib/index.ts b/packages/network/lib/index.ts index f40984659a19..e9064ae0b3ab 100644 --- a/packages/network/lib/index.ts +++ b/packages/network/lib/index.ts @@ -1,12 +1,12 @@ import agent from './agent' -import * as blacklist from './blacklist' +import * as blocked from './blocked' import * as connect from './connect' import * as cors from './cors' import * as uri from './uri' export { agent, - blacklist, + blocked, connect, cors, uri, diff --git a/packages/network/test/unit/blacklist_spec.ts b/packages/network/test/unit/blocked_spec.ts similarity index 87% rename from packages/network/test/unit/blacklist_spec.ts rename to packages/network/test/unit/blocked_spec.ts index 81e223b4f915..0e327503d490 100644 --- a/packages/network/test/unit/blacklist_spec.ts +++ b/packages/network/test/unit/blocked_spec.ts @@ -1,4 +1,4 @@ -import { blacklist } from '../..' +import { blocked } from '../..' import { expect } from 'chai' const hosts = [ @@ -10,22 +10,22 @@ const hosts = [ ] const matchesStr = function (url, host, val) { - const m = blacklist.matches(url, host) + const m = blocked.matches(url, host) expect(!!m).to.eq(val, `url: '${url}' did not pass`) } const matchesArray = function (url, val) { - const m = blacklist.matches(url, hosts) + const m = blocked.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) + expect(blocked.matches(url, hosts)).to.eq(host) } -describe('lib/blacklist', () => { +describe('lib/blocked', () => { it('handles hosts, ports, wildcards', () => { matchesArray('https://mail.google.com/foo', true) matchesArray('https://shop.apple.com/bar', true) diff --git a/packages/proxy/lib/http/request-middleware.ts b/packages/proxy/lib/http/request-middleware.ts index df9819330678..f8ab37cb6c00 100644 --- a/packages/proxy/lib/http/request-middleware.ts +++ b/packages/proxy/lib/http/request-middleware.ts @@ -1,6 +1,6 @@ import _ from 'lodash' import debugModule from 'debug' -import { blacklist, cors } from '@packages/network' +import { blocked, cors } from '@packages/network' import { HttpMiddleware } from './' export type RequestMiddleware = HttpMiddleware<{ @@ -46,15 +46,15 @@ const RedirectToClientRouteIfNotProxied: RequestMiddleware = function () { this.next() } -const EndRequestsToBlacklistedHosts: RequestMiddleware = function () { - const { blacklistHosts } = this.config +const EndRequestsToBlockedHosts: RequestMiddleware = function () { + const { blockHosts } = this.config - if (blacklistHosts) { - const matches = blacklist.matches(this.req.proxiedUrl, blacklistHosts) + if (blockHosts) { + const matches = blocked.matches(this.req.proxiedUrl, blockHosts) if (matches) { - this.res.set('x-cypress-matched-blacklisted-host', matches) - debug('blacklisting request %o', { + this.res.set('x-cypress-matched-blocked-host', matches) + debug('blocking request %o', { url: this.req.proxiedUrl, matches, }) @@ -149,7 +149,7 @@ export default { LogRequest, RedirectToClientRouteIfUnloaded, RedirectToClientRouteIfNotProxied, - EndRequestsToBlacklistedHosts, + EndRequestsToBlockedHosts, MaybeEndRequestWithBufferedResponse, StripUnsupportedAcceptEncoding, MaybeSetBasicAuthHeaders, diff --git a/packages/proxy/test/unit/http/request-middleware.spec.ts b/packages/proxy/test/unit/http/request-middleware.spec.ts index 5fb61b6839e9..07cd117c8754 100644 --- a/packages/proxy/test/unit/http/request-middleware.spec.ts +++ b/packages/proxy/test/unit/http/request-middleware.spec.ts @@ -8,7 +8,7 @@ describe('http/request-middleware', function () { 'LogRequest', 'RedirectToClientRouteIfUnloaded', 'RedirectToClientRouteIfNotProxied', - 'EndRequestsToBlacklistedHosts', + 'EndRequestsToBlockedHosts', 'MaybeEndRequestWithBufferedResponse', 'StripUnsupportedAcceptEncoding', 'MaybeSetBasicAuthHeaders', diff --git a/packages/server/__snapshots__/1_blacklist_hosts_spec.js b/packages/server/__snapshots__/1_block_hosts_spec.js similarity index 83% rename from packages/server/__snapshots__/1_blacklist_hosts_spec.js rename to packages/server/__snapshots__/1_block_hosts_spec.js index ddc788d39721..e96f050822b3 100644 --- a/packages/server/__snapshots__/1_blacklist_hosts_spec.js +++ b/packages/server/__snapshots__/1_block_hosts_spec.js @@ -1,4 +1,4 @@ -exports['e2e blacklist passes 1'] = ` +exports['e2e blockHosts passes 1'] = ` ==================================================================================================== @@ -7,18 +7,18 @@ exports['e2e blacklist passes 1'] = ` ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Cypress: 1.2.3 │ │ Browser: FooBrowser 88 │ - │ Specs: 1 found (blacklist_hosts_spec.coffee) │ - │ Searched: cypress/integration/blacklist_hosts_spec.coffee │ + │ Specs: 1 found (block_hosts_spec.coffee) │ + │ Searched: cypress/integration/block_hosts_spec.coffee │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: blacklist_hosts_spec.coffee (1 of 1) + Running: block_hosts_spec.coffee (1 of 1) - blacklist - ✓ forces blacklisted hosts to return 503 + block hosts + ✓ forces blocked hosts to return 503 1 passing @@ -33,18 +33,12 @@ exports['e2e blacklist passes 1'] = ` │ Pending: 0 │ │ Skipped: 0 │ │ Screenshots: 0 │ - │ Video: true │ + │ Video: false │ │ Duration: X seconds │ - │ Spec Ran: blacklist_hosts_spec.coffee │ + │ Spec Ran: block_hosts_spec.coffee │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ - (Video) - - - Started processing: Compressing to 32 CRF - - Finished processing: /XXX/XXX/XXX/cypress/videos/blacklist_hosts_spec.coffee.mp4 (X second) - - ==================================================================================================== (Run Finished) @@ -52,7 +46,7 @@ exports['e2e blacklist passes 1'] = ` Spec Tests Passing Failing Pending Skipped ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ ✔ blacklist_hosts_spec.coffee XX:XX 1 1 - - - │ + │ ✔ block_hosts_spec.coffee XX:XX 1 1 - - - │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ ✔ All specs passed! XX:XX 1 1 - - - diff --git a/packages/server/lib/browsers/firefox.ts b/packages/server/lib/browsers/firefox.ts index ff71136ba9de..f533955839e7 100644 --- a/packages/server/lib/browsers/firefox.ts +++ b/packages/server/lib/browsers/firefox.ts @@ -190,7 +190,7 @@ const defaultPreferences = { // Do not wait for the notification button security delay 'security.notification_enable_delay': 0, - // Ensure blocklist updates do not hit the network + // Ensure blocked updates do not hit the network 'services.settings.server': '', // Do not automatically fill sign-in forms with known usernames and diff --git a/packages/server/lib/config.js b/packages/server/lib/config.js index a9dd933fa7dd..7e4f9aad5580 100644 --- a/packages/server/lib/config.js +++ b/packages/server/lib/config.js @@ -57,7 +57,7 @@ folders.push('componentFolder') const configKeys = toWords(`\ animationDistanceThreshold fileServerFolder baseUrl fixturesFolder -blacklistHosts +blockHosts chromeWebSecurity modifyObstructiveCode integrationFolder env pluginsFile @@ -89,8 +89,9 @@ firefoxGcInterval\ // experimentalComponentTesting configKeys.push('componentFolder') -// Deprecated and retired public configuration properties +// Breaking public configuration properties, will error const breakingConfigKeys = toWords(`\ +blacklistHosts videoRecording screenshotOnHeadlessFailure trashAssetsBeforeHeadlessRuns @@ -125,7 +126,7 @@ const CONFIG_DEFAULTS = { isTextTerminal: false, reporter: 'spec', reporterOptions: null, - blacklistHosts: null, + blockHosts: null, clientRoute: '/__/', xhrRoute: '/xhrs/', socketIoRoute: '/__socket.io', @@ -182,7 +183,7 @@ const CONFIG_DEFAULTS = { const validationRules = { animationDistanceThreshold: v.isNumber, baseUrl: v.isFullyQualifiedUrl, - blacklistHosts: v.isStringOrArrayOfStrings, + blockHosts: v.isStringOrArrayOfStrings, browsers: v.isValidBrowserList, chromeWebSecurity: v.isBoolean, configFile: v.isStringOrFalse, @@ -249,6 +250,8 @@ const validateNoBreakingConfig = (cfg) => { return errors.throw('RENAMED_CONFIG_OPTION', key, 'trashAssetsBeforeRuns') case 'videoRecording': return errors.throw('RENAMED_CONFIG_OPTION', key, 'video') + case 'blacklistHosts': + return errors.throw('RENAMED_CONFIG_OPTION', key, 'blockHosts') case 'experimentalGetCookiesSameSite': return errors.warning('EXPERIMENTAL_SAMESITE_REMOVED') default: diff --git a/packages/server/lib/errors.js b/packages/server/lib/errors.js index 78153766d5cc..88b6676d6629 100644 --- a/packages/server/lib/errors.js +++ b/packages/server/lib/errors.js @@ -664,7 +664,7 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { Learn more at https://on.cypress.io/screenshot-api` case 'RENAMED_CONFIG_OPTION': return stripIndent`\ - A configuration option you have supplied has been renamed. + The ${chalk.yellow(arg1)} configuration option you have supplied has been renamed. Please rename ${chalk.yellow(arg1)} to ${chalk.blue(arg2)}` case 'CANNOT_CONNECT_BASE_URL': diff --git a/packages/server/lib/server.js b/packages/server/lib/server.js index fab463c55a08..e1d0f2cbd03c 100644 --- a/packages/server/lib/server.js +++ b/packages/server/lib/server.js @@ -15,7 +15,7 @@ const compression = require('compression') const debug = require('debug')('cypress:server:server') const { agent, - blacklist, + blocked, concatStream, cors, uri, @@ -238,7 +238,7 @@ class Server { createServer (app, config, project, request, onWarning) { return new Promise((resolve, reject) => { - const { port, fileServerFolder, socketIoRoute, baseUrl, blacklistHosts } = config + const { port, fileServerFolder, socketIoRoute, baseUrl, blockHosts } = config this._server = http.createServer(app) @@ -291,16 +291,17 @@ class Server { // if we are currently matching then we're // not making a direct connection anyway // so we only need to check this if we - // have blacklist hosts and are not matching. + // have blocked hosts and are not matching. // - // if we have blacklisted hosts lets + // if we have blocked hosts lets // see if this matches - if so then // we cannot allow it to make a direct // connection - if (blacklistHosts && !isMatching) { - isMatching = blacklist.matches(urlToCheck, blacklistHosts) - debug(`HTTPS request ${urlToCheck} matches blacklist?`, isMatching) + if (blockHosts && !isMatching) { + isMatching = blocked.matches(urlToCheck, blockHosts) + + debug(`HTTPS request ${urlToCheck} matches blockHosts?`, isMatching) } // make a direct connection only if diff --git a/packages/server/test/e2e/1_blacklist_hosts_spec.js b/packages/server/test/e2e/1_block_hosts_spec.js similarity index 82% rename from packages/server/test/e2e/1_blacklist_hosts_spec.js rename to packages/server/test/e2e/1_block_hosts_spec.js index ae38f96f5f0d..946f656ff6b1 100644 --- a/packages/server/test/e2e/1_blacklist_hosts_spec.js +++ b/packages/server/test/e2e/1_block_hosts_spec.js @@ -14,7 +14,7 @@ const onServer = function (app) { }) } -describe('e2e blacklist', () => { +describe('e2e blockHosts', () => { e2e.setup({ servers: [{ port: 3131, @@ -25,13 +25,14 @@ describe('e2e blacklist', () => { }], settings: { baseUrl: 'http://localhost:3232', - blacklistHosts: 'localhost:3131', + blockHosts: 'localhost:3131', + video: false, }, }) it('passes', function () { return e2e.exec(this, { - spec: 'blacklist_hosts_spec.coffee', + spec: 'block_hosts_spec.coffee', snapshot: true, }) }) diff --git a/packages/server/test/integration/cypress_spec.js b/packages/server/test/integration/cypress_spec.js index 627fd351ab30..762541c514be 100644 --- a/packages/server/test/integration/cypress_spec.js +++ b/packages/server/test/integration/cypress_spec.js @@ -889,25 +889,31 @@ describe('lib/cypress', () => { }) }) - it('logs error and exits when using an old configuration option: trashAssetsBeforeHeadlessRuns', function () { - return cypress.start([ - `--run-project=${this.todosPath}`, - '--config=trashAssetsBeforeHeadlessRuns=false', - ]) - .then(() => { - this.expectExitWithErr('RENAMED_CONFIG_OPTION', 'trashAssetsBeforeHeadlessRuns') - this.expectExitWithErr('RENAMED_CONFIG_OPTION', 'trashAssetsBeforeRuns') - }) - }) - - it('logs error and exits when using an old configuration option: videoRecording', function () { - return cypress.start([ - `--run-project=${this.todosPath}`, - '--config=videoRecording=false', - ]) - .then(() => { - this.expectExitWithErr('RENAMED_CONFIG_OPTION', 'videoRecording') - this.expectExitWithErr('RENAMED_CONFIG_OPTION', 'video') + const renamedConfigs = [ + { + old: 'trashAssetsBeforeHeadlessRuns', + new: 'trashAssetsBeforeRuns', + }, + { + old: 'videoRecording', + new: 'video', + }, + { + old: 'blacklistHosts', + new: 'blockHosts', + }, + ] + + renamedConfigs.forEach(function (config) { + it(`logs error and exits when using an old configuration option: ${config.old}`, function () { + return cypress.start([ + `--run-project=${this.todosPath}`, + `--config=${config.old}=''`, + ]) + .then(() => { + this.expectExitWithErr('RENAMED_CONFIG_OPTION', config.old) + this.expectExitWithErr('RENAMED_CONFIG_OPTION', config.new) + }) }) }) diff --git a/packages/server/test/integration/http_requests_spec.js b/packages/server/test/integration/http_requests_spec.js index 30b44bea47e6..501aaaafe979 100644 --- a/packages/server/test/integration/http_requests_spec.js +++ b/packages/server/test/integration/http_requests_spec.js @@ -3727,11 +3727,11 @@ describe('Routes', () => { }) }) - context('blacklisted hosts', () => { + context('blocked hosts', () => { beforeEach(function () { return this.setup({ config: { - blacklistHosts: [ + blockHosts: [ '*.google.com', 'shop.apple.com', 'cypress.io', @@ -3744,7 +3744,7 @@ describe('Routes', () => { it('returns 503 and custom headers for all hosts', function () { const expectedHeader = (res, val) => { - expect(res.headers['x-cypress-matched-blacklisted-host']).to.eq(val) + expect(res.headers['x-cypress-matched-blocked-host']).to.eq(val) } return Promise.all([ diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/blacklist_hosts_spec.coffee b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/block_hosts_spec.coffee similarity index 91% rename from packages/server/test/support/fixtures/projects/e2e/cypress/integration/blacklist_hosts_spec.coffee rename to packages/server/test/support/fixtures/projects/e2e/cypress/integration/block_hosts_spec.coffee index 47fecb9a4e39..16ee8ecc9552 100644 --- a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/blacklist_hosts_spec.coffee +++ b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/block_hosts_spec.coffee @@ -1,5 +1,5 @@ -describe "blacklist", -> - it "forces blacklisted hosts to return 503", -> +describe "block hosts", -> + it "forces blocked hosts to return 503", -> cy .visit("http://localhost:3232") diff --git a/packages/server/test/unit/args_spec.js b/packages/server/test/unit/args_spec.js index c7d896434b84..ae9f7801f35f 100644 --- a/packages/server/test/unit/args_spec.js +++ b/packages/server/test/unit/args_spec.js @@ -241,7 +241,7 @@ describe('lib/util/args', () => { const config = { pageLoadTimeout: 10000, waitForAnimations: false, - blacklistHosts: ['one.com', 'www.two.io'], + blockHosts: ['one.com', 'www.two.io'], hosts: { 'foobar.com': '127.0.0.1', }, @@ -254,7 +254,7 @@ describe('lib/util/args', () => { // as mixed usage const hosts = JSON.stringify(config.hosts) - const blacklistHosts = JSON.stringify(config.blacklistHosts) + const blockHosts = JSON.stringify(config.blockHosts) options = this.setup( '--config', @@ -262,7 +262,7 @@ describe('lib/util/args', () => { 'pageLoadTimeout=10000', 'waitForAnimations=false', `hosts=${hosts}`, - `blacklistHosts=${blacklistHosts}`, + `blockHosts=${blockHosts}`, ].join(','), ) @@ -319,7 +319,7 @@ describe('lib/util/args', () => { context('.toObject', () => { beforeEach(function () { this.hosts = { a: 'b', b: 'c' } - this.blacklistHosts = ['a.com', 'b.com'] + this.blockHosts = ['a.com', 'b.com'] this.specs = [ path.join(cwd, 'foo'), path.join(cwd, 'bar'), @@ -336,7 +336,7 @@ describe('lib/util/args', () => { env: this.env, hosts: this.hosts, requestTimeout: 1234, - blacklistHosts: this.blacklistHosts, + blockHosts: this.blockHosts, reporterOptions: { foo: 'bar', }, @@ -350,7 +350,7 @@ describe('lib/util/args', () => { '--get-key', '--env=foo=bar,baz=quux,bar=foo=quz', '--config', - `requestTimeout=1234,blacklistHosts=${s(this.blacklistHosts)},hosts=${s(this.hosts)}`, + `requestTimeout=1234,blockHosts=${s(this.blockHosts)},hosts=${s(this.hosts)}`, '--reporter-options=foo=bar', '--spec=foo,bar,baz', ) @@ -377,7 +377,7 @@ describe('lib/util/args', () => { it('can transpose back to an array', function () { const mergedConfig = JSON.stringify({ requestTimeout: this.config.requestTimeout, - blacklistHosts: this.blacklistHosts, + blockHosts: this.blockHosts, hosts: this.hosts, env: this.env, reporterOptions: { diff --git a/packages/server/test/unit/config_spec.js b/packages/server/test/unit/config_spec.js index e41d83f737ab..fcd74ce94ac4 100644 --- a/packages/server/test/unit/config_spec.js +++ b/packages/server/test/unit/config_spec.js @@ -649,27 +649,27 @@ describe('lib/config', () => { }) }) - context('blacklistHosts', () => { + context('blockHosts', () => { it('passes if a string', function () { - this.setup({ blacklistHosts: 'google.com' }) + this.setup({ blockHosts: 'google.com' }) return this.expectValidationPasses() }) it('passes if an array of strings', function () { - this.setup({ blacklistHosts: ['google.com'] }) + this.setup({ blockHosts: ['google.com'] }) return this.expectValidationPasses() }) it('fails if not a string or array', function () { - this.setup({ blacklistHosts: 5 }) + this.setup({ blockHosts: 5 }) return this.expectValidationFails('be a string or an array of strings') }) it('fails if not an array of strings', function () { - this.setup({ blacklistHosts: [5] }) + this.setup({ blockHosts: [5] }) this.expectValidationFails('be a string or an array of strings') return this.expectValidationFails('the value was: `[5]`') @@ -717,8 +717,8 @@ describe('lib/config', () => { } }) - it('includes blacklistHosts', function () { - return this.includes('blacklistHosts') + it('includes blockHosts', function () { + return this.includes('blockHosts') }) }) @@ -957,19 +957,19 @@ describe('lib/config', () => { return this.defaults('supportFile', false, { supportFile: false }) }) - it('blacklistHosts=null', function () { - return this.defaults('blacklistHosts', null) + it('blockHosts=null', function () { + return this.defaults('blockHosts', null) }) - it('blacklistHosts=[a,b]', function () { - return this.defaults('blacklistHosts', ['a', 'b'], { - blacklistHosts: ['a', 'b'], + it('blockHosts=[a,b]', function () { + return this.defaults('blockHosts', ['a', 'b'], { + blockHosts: ['a', 'b'], }) }) - it('blacklistHosts=a|b', function () { - return this.defaults('blacklistHosts', ['a', 'b'], { - blacklistHosts: ['a', 'b'], + it('blockHosts=a|b', function () { + return this.defaults('blockHosts', ['a', 'b'], { + blockHosts: ['a', 'b'], }) }) @@ -1108,7 +1108,7 @@ describe('lib/config', () => { projectId: { value: null, from: 'default' }, port: { value: 1234, from: 'cli' }, hosts: { value: null, from: 'default' }, - blacklistHosts: { value: null, from: 'default' }, + blockHosts: { value: null, from: 'default' }, browsers: { value: [], from: 'default' }, userAgent: { value: null, from: 'default' }, reporter: { value: 'json', from: 'cli' }, @@ -1184,7 +1184,7 @@ describe('lib/config', () => { projectId: { value: 'projectId123', from: 'env' }, port: { value: 2020, from: 'config' }, hosts: { value: null, from: 'default' }, - blacklistHosts: { value: null, from: 'default' }, + blockHosts: { value: null, from: 'default' }, browsers: { value: [], from: 'default' }, userAgent: { value: null, from: 'default' }, reporter: { value: 'spec', from: 'default' }, From c5012c0c2d3d02bdbedd247c131ebcad06e2d6d0 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Mon, 13 Jul 2020 13:47:50 -0400 Subject: [PATCH 20/42] fix ts-node buffer warning --- packages/ts/package.json | 1 + packages/ts/patches/ts-node+5.0.1.patch | 13 +++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 packages/ts/patches/ts-node+5.0.1.patch diff --git a/packages/ts/package.json b/packages/ts/package.json index 790850b48a5a..dbe88bcd7025 100644 --- a/packages/ts/package.json +++ b/packages/ts/package.json @@ -5,6 +5,7 @@ "main": "index.js", "scripts": { "clean-deps": "rm -rf node_modules", + "postinstall": "patch-package", "test": "yarn test-unit", "test-unit": "node test", "test-watch": "echo 'no watching of tests'" diff --git a/packages/ts/patches/ts-node+5.0.1.patch b/packages/ts/patches/ts-node+5.0.1.patch new file mode 100644 index 000000000000..b53b150ae8a7 --- /dev/null +++ b/packages/ts/patches/ts-node+5.0.1.patch @@ -0,0 +1,13 @@ +diff --git a/node_modules/ts-node/dist/index.js b/node_modules/ts-node/dist/index.js +index 2dfd704..c00aa39 100644 +--- a/node_modules/ts-node/dist/index.js ++++ b/node_modules/ts-node/dist/index.js +@@ -298,7 +298,7 @@ function readThrough(cachedir, shouldCache, memoryCache, compile, getExtension) + }; + } + function updateOutput(outputText, fileName, sourceMap, getExtension) { +- var base64Map = new Buffer(updateSourceMap(sourceMap, fileName), 'utf8').toString('base64'); ++ var base64Map = Buffer.from(updateSourceMap(sourceMap, fileName), 'utf8').toString('base64'); + var sourceMapContent = "data:application/json;charset=utf-8;base64," + base64Map; + var sourceMapLength = (path_1.basename(fileName) + ".map").length + (getExtension(fileName).length - path_1.extname(fileName).length); + return outputText.slice(0, -sourceMapLength) + sourceMapContent; From 09c72e3589cc64316b8b6aa3f34462a661a19474 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Fri, 17 Jul 2020 13:53:26 -0400 Subject: [PATCH 21/42] silence deprecation warnings rather than patch transient deps for Buffer warnings --- package.json | 2 +- packages/server/lib/plugins/child/index.js | 2 + ...ng.ts => suppress_unauthorized_warning.js} | 22 ++- packages/server/package.json | 6 +- ...source-map++convert-source-map+1.1.3.patch | 13 -- .../patches/inline-source-map+0.6.2.patch | 13 -- .../suppress_unauthorized_warning_spec.ts | 22 ++- yarn.lock | 183 ++---------------- 8 files changed, 51 insertions(+), 212 deletions(-) rename packages/server/lib/util/{suppress_unauthorized_warning.ts => suppress_unauthorized_warning.js} (52%) delete mode 100644 packages/server/patches/combine-source-map++convert-source-map+1.1.3.patch delete mode 100644 packages/server/patches/inline-source-map+0.6.2.patch diff --git a/package.json b/package.json index 1afd891b160f..4f1ccb4ba9f0 100644 --- a/package.json +++ b/package.json @@ -163,7 +163,7 @@ "mocha-multi-reporters": "1.1.7", "mock-fs": "4.9.0", "parse-github-repo-url": "1.4.1", - "patch-package": "6.2.0", + "patch-package": "6.2.2", "percy": "0.26.9", "plist": "2.1.0", "pluralize": "8.0.0", diff --git a/packages/server/lib/plugins/child/index.js b/packages/server/lib/plugins/child/index.js index c8bd4d47cd86..224259ce0972 100644 --- a/packages/server/lib/plugins/child/index.js +++ b/packages/server/lib/plugins/child/index.js @@ -1,5 +1,7 @@ require('graceful-fs').gracefulify(require('fs')) +require('../../util/suppress_unauthorized_warning').suppress() + const ipc = require('../util').wrapIpc(process) const { file: pluginsFile, projectRoot } = require('minimist')(process.argv.slice(2)) diff --git a/packages/server/lib/util/suppress_unauthorized_warning.ts b/packages/server/lib/util/suppress_unauthorized_warning.js similarity index 52% rename from packages/server/lib/util/suppress_unauthorized_warning.ts rename to packages/server/lib/util/suppress_unauthorized_warning.js index 29e2bfb39eef..7cde5918984a 100644 --- a/packages/server/lib/util/suppress_unauthorized_warning.ts +++ b/packages/server/lib/util/suppress_unauthorized_warning.js @@ -1,4 +1,4 @@ -import _ from 'lodash' +const _ = require('lodash') const originalEmitWarning = process.emitWarning @@ -9,22 +9,32 @@ let suppressed = false * we work on proper SSL verification. * https://github.com/cypress-io/cypress/issues/5248 */ -export function suppress () { +const suppress = () => { if (suppressed) { return } suppressed = true - process.emitWarning = (warning, ...args) => { + process.emitWarning = (warning, type, code, ...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) + // silence Buffer allocation warning since there are no + // security problems due to the way Cypress works + if (code === 'DEP0005') { + // https://github.com/nodejs/node/blob/master/lib/buffer.js#L176-L192 + + return + } + + return originalEmitWarning.call(process, warning, type, code, ...args) } } + +module.exports = { + suppress, +} diff --git a/packages/server/package.json b/packages/server/package.json index 06585db698e5..7e33b7b8d4e5 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -197,11 +197,7 @@ "productName": "Cypress", "workspaces": { "nohoist": [ - "foxdriver", - "**/@cypress/browserify-preprocessor", - "@cypress/browserify-preprocessor/**", - "**/browserify", - "browserify/**" + "foxdriver" ] }, "optionalDependencies": { diff --git a/packages/server/patches/combine-source-map++convert-source-map+1.1.3.patch b/packages/server/patches/combine-source-map++convert-source-map+1.1.3.patch deleted file mode 100644 index e5693da69c0d..000000000000 --- a/packages/server/patches/combine-source-map++convert-source-map+1.1.3.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/node_modules/combine-source-map/node_modules/convert-source-map/index.js b/node_modules/combine-source-map/node_modules/convert-source-map/index.js -index bfe92d1..beae518 100644 ---- a/node_modules/combine-source-map/node_modules/convert-source-map/index.js -+++ b/node_modules/combine-source-map/node_modules/convert-source-map/index.js -@@ -9,7 +9,7 @@ var mapFileCommentRx = - /(?:\/\/[@#][ \t]+sourceMappingURL=([^\s'"]+?)[ \t]*$)|(?:\/\*[@#][ \t]+sourceMappingURL=([^\*]+?)[ \t]*(?:\*\/){1}[ \t]*$)/mg - - function decodeBase64(base64) { -- return new Buffer(base64, 'base64').toString(); -+ return Buffer.from(base64, 'base64').toString(); - } - - function stripComment(sm) { diff --git a/packages/server/patches/inline-source-map+0.6.2.patch b/packages/server/patches/inline-source-map+0.6.2.patch deleted file mode 100644 index 5e5ae09a02aa..000000000000 --- a/packages/server/patches/inline-source-map+0.6.2.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/node_modules/inline-source-map/index.js b/node_modules/inline-source-map/index.js -index df74d61..7641aad 100644 ---- a/node_modules/inline-source-map/index.js -+++ b/node_modules/inline-source-map/index.js -@@ -91,7 +91,7 @@ Generator.prototype.addSourceContent = function (sourceFile, sourcesContent) { - */ - Generator.prototype.base64Encode = function () { - var map = this.toString(); -- return new Buffer(map).toString('base64'); -+ return Buffer.from(map).toString('base64'); - }; - - /** diff --git a/packages/server/test/unit/suppress_unauthorized_warning_spec.ts b/packages/server/test/unit/suppress_unauthorized_warning_spec.ts index b92972c337e9..b76b26fe023a 100644 --- a/packages/server/test/unit/suppress_unauthorized_warning_spec.ts +++ b/packages/server/test/unit/suppress_unauthorized_warning_spec.ts @@ -1,10 +1,12 @@ -import execa from 'execa' +import '../spec_helper' import { expect } from 'chai' +import execa from 'execa' +import proxyquire from 'proxyquire' const ERROR_MESSAGE = 'Setting the NODE_TLS_REJECT_UNAUTHORIZED' const TLS_CONNECT = `require('tls').connect().on('error', ()=>{});` -const SUPPRESS_WARNING = `require('@packages/ts/register'); require('${__dirname}/../../lib/util/suppress_unauthorized_warning').suppress();` +const SUPPRESS_WARNING = `require('${__dirname}/../../lib/util/suppress_unauthorized_warning').suppress();` describe('lib/util/suppress_unauthorized_warning', function () { it('tls.connect emits warning if NODE_TLS_REJECT_UNAUTHORIZED=0 and not suppressed', function () { @@ -29,4 +31,20 @@ describe('lib/util/suppress_unauthorized_warning', function () { expect(stderr).to.not.contain(ERROR_MESSAGE) }) }) + + it('does not emit buffer deprecation warnings', () => { + const emitWarning = sinon.spy(process, 'emitWarning') + + // force typescript to always be non-requireable + const { suppress } = proxyquire('../../lib/util/suppress_unauthorized_warning', {}) + + suppress() + + // eslint-disable-next-line no-buffer-constructor + new Buffer(0) + // eslint-disable-next-line no-buffer-constructor + new Buffer('asdf') + + expect(emitWarning).not.to.be.called + }) }) diff --git a/yarn.lock b/yarn.lock index 1f90888d356e..614b53801109 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7085,7 +7085,7 @@ bower-config@^1.4.0: untildify "^2.1.0" wordwrap "^0.0.3" -boxen@1.3.0, boxen@^1.2.1: +boxen@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b" integrity sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw== @@ -7826,11 +7826,6 @@ capture-exit@^2.0.0: dependencies: rsvp "^4.8.4" -capture-stack-trace@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d" - integrity sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw== - cardinal@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/cardinal/-/cardinal-2.1.1.tgz#7cc1055d822d212954d07b085dea251cc7bc5505" @@ -8173,11 +8168,6 @@ chromium-pickle-js@^0.2.0: resolved "https://registry.yarnpkg.com/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz#04a106672c18b085ab774d983dfa3ea138f22205" integrity sha1-BKEGZywYsIWrd02YPfo+oTjyIgU= -ci-info@^1.5.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497" - integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A== - ci-info@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" @@ -8835,18 +8825,6 @@ config-chain@^1.1.11: ini "^1.3.4" proto-list "~1.2.1" -configstore@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.2.tgz#c6f25defaeef26df12dd33414b001fe81a543f8f" - integrity sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw== - dependencies: - dot-prop "^4.1.0" - graceful-fs "^4.1.2" - make-dir "^1.0.0" - unique-string "^1.0.0" - write-file-atomic "^2.0.0" - xdg-basedir "^3.0.0" - configstore@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96" @@ -9152,13 +9130,6 @@ create-ecdh@^4.0.0: bn.js "^4.1.0" elliptic "^6.0.0" -create-error-class@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" - integrity sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y= - dependencies: - capture-stack-trace "^1.0.0" - create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" @@ -9295,11 +9266,6 @@ crypto-browserify@^3.0.0, crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" -crypto-random-string@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" - integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4= - crypto-random-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" @@ -10283,7 +10249,7 @@ dot-prop@^3.0.0: dependencies: is-obj "^1.0.0" -dot-prop@^4.1.0, dot-prop@^4.2.0: +dot-prop@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57" integrity sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ== @@ -13021,13 +12987,6 @@ global-agent@^2.0.2: semver "^7.3.2" serialize-error "^7.0.1" -global-dirs@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" - integrity sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU= - dependencies: - ini "^1.3.4" - global-dirs@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.0.1.tgz#acdf3bb6685bcd55cb35e8a052266569e9469201" @@ -13261,23 +13220,6 @@ good-listener@^1.2.2: dependencies: delegate "^3.1.2" -got@^6.7.1: - version "6.7.1" - resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" - integrity sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA= - dependencies: - create-error-class "^3.0.0" - duplexer3 "^0.1.4" - get-stream "^3.0.0" - is-redirect "^1.0.0" - is-retry-allowed "^1.0.0" - is-stream "^1.0.0" - lowercase-keys "^1.0.0" - safe-buffer "^5.0.1" - timed-out "^4.0.0" - unzip-response "^2.0.1" - url-parse-lax "^1.0.0" - got@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/got/-/got-8.3.2.tgz#1d23f64390e97f776cac52e5b936e5f514d2e937" @@ -14549,13 +14491,6 @@ is-ci@2.0.0, is-ci@^2.0.0: dependencies: ci-info "^2.0.0" -is-ci@^1.0.10: - version "1.2.1" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c" - integrity sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg== - dependencies: - ci-info "^1.5.0" - is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -14723,14 +14658,6 @@ is-installed-globally@0.3.2, is-installed-globally@^0.3.1: global-dirs "^2.0.1" is-path-inside "^3.0.1" -is-installed-globally@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" - integrity sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA= - dependencies: - global-dirs "^0.1.0" - is-path-inside "^1.0.0" - is-integer@^1.0.4: version "1.0.7" resolved "https://registry.yarnpkg.com/is-integer/-/is-integer-1.0.7.tgz#6bde81aacddf78b659b6629d629cadc51a886d5c" @@ -14764,11 +14691,6 @@ is-negated-glob@^1.0.0: resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" integrity sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI= -is-npm@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" - integrity sha1-8vtjpl5JBbQGyGBydloaTceTufQ= - is-npm@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" @@ -14910,11 +14832,6 @@ is-property@^1.0.0, is-property@^1.0.2: resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" integrity sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ= -is-redirect@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" - integrity sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ= - is-regex@^1.0.5, is-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.0.tgz#ece38e389e490df0dc21caea2bd596f987f767ff" @@ -14939,7 +14856,7 @@ is-relative@^1.0.0: dependencies: is-unc-path "^1.0.0" -is-retry-allowed@^1.0.0, is-retry-allowed@^1.1.0: +is-retry-allowed@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== @@ -14956,7 +14873,7 @@ is-ssh@^1.3.0: dependencies: protocols "^1.1.0" -is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0: +is-stream@^1.0.1, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= @@ -16251,13 +16168,6 @@ last-run@^1.1.0: default-resolution "^2.0.0" es6-weak-map "^2.0.1" -latest-version@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15" - integrity sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU= - dependencies: - package-json "^4.0.0" - latest-version@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" @@ -19371,16 +19281,6 @@ package-hash@^4.0.0: lodash.flattendeep "^4.4.0" release-zalgo "^1.0.0" -package-json@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed" - integrity sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0= - dependencies: - got "^6.7.1" - registry-auth-token "^3.0.1" - registry-url "^3.0.3" - semver "^5.1.0" - package-json@^6.3.0: version "6.5.0" resolved "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz#6feedaca35e75725876d0b0e64974697fed145b0" @@ -19637,10 +19537,10 @@ password-prompt@^1.0.7: ansi-escapes "^3.1.0" cross-spawn "^6.0.5" -patch-package@6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-6.2.0.tgz#677de858e352b6ca4e6cb48a6efde2cec9fde566" - integrity sha512-HWlQflaBBMjLBfOWomfolF8aqsFDeNbSNro1JDUgYqnVvPM5OILJ9DQdwIRiKmGaOsmHvhkl1FYkvv1I9r2ZJw== +patch-package@6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-6.2.2.tgz#71d170d650c65c26556f0d0fbbb48d92b6cc5f39" + integrity sha512-YqScVYkVcClUY0v8fF0kWOjDYopzIM8e3bj/RU1DPeEF14+dCGm6UeOYm4jvCyxqIEQ5/eJzmbWfDWnUleFNMg== dependencies: "@yarnpkg/lockfile" "^1.1.0" chalk "^2.4.2" @@ -19654,7 +19554,6 @@ patch-package@6.2.0: semver "^5.6.0" slash "^2.0.0" tmp "^0.0.33" - update-notifier "^2.5.0" path-browserify@0.0.1, path-browserify@~0.0.0: version "0.0.1" @@ -20193,11 +20092,6 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= -prepend-http@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" - integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= - prepend-http@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" @@ -21510,14 +21404,6 @@ registry-auth-token@3.3.2: rc "^1.1.6" safe-buffer "^5.0.1" -registry-auth-token@^3.0.1: - version "3.4.0" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.4.0.tgz#d7446815433f5d5ed6431cd5dca21048f66b397e" - integrity sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A== - dependencies: - rc "^1.1.6" - safe-buffer "^5.0.1" - registry-auth-token@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.1.1.tgz#40a33be1e82539460f94328b0f7f0f84c16d9479" @@ -21533,7 +21419,7 @@ registry-js@1.8.0: nan "^2.10.0" prebuild-install "^5.2.4" -registry-url@3.1.0, registry-url@^3.0.3: +registry-url@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942" integrity sha1-PU74cPc93h138M+aOBQyRE4XSUI= @@ -22291,13 +22177,6 @@ semver-compare@^1.0.0: resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= -semver-diff@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" - integrity sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY= - dependencies: - semver "^5.0.3" - semver-diff@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" @@ -22312,7 +22191,7 @@ semver-greatest-satisfied-range@^1.1.0: dependencies: sver-compat "^1.5.0" -"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: +"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.0.3, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -24222,7 +24101,7 @@ time-stamp@^1.0.0: resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" integrity sha1-dkpaEa9QVhkhsTPztE5hhofg9cM= -timed-out@^4.0.0, timed-out@^4.0.1: +timed-out@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= @@ -24926,13 +24805,6 @@ unique-stream@^2.0.2: json-stable-stringify-without-jsonify "^1.0.1" through2-filter "^3.0.0" -unique-string@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a" - integrity sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo= - dependencies: - crypto-random-string "^1.0.0" - unique-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" @@ -25004,11 +24876,6 @@ unused-filename@^1.0.0: modify-filename "^1.1.0" path-exists "^3.0.0" -unzip-response@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" - integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c= - upath@^1.1.0, upath@^1.1.1, upath@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" @@ -25022,22 +24889,6 @@ update-check@1.5.2: registry-auth-token "3.3.2" registry-url "3.1.0" -update-notifier@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.5.0.tgz#d0744593e13f161e406acb1d9408b72cad08aff6" - integrity sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw== - dependencies: - boxen "^1.2.1" - chalk "^2.0.1" - configstore "^3.0.0" - import-lazy "^2.1.0" - is-ci "^1.0.10" - is-installed-globally "^0.1.0" - is-npm "^1.0.0" - latest-version "^3.0.0" - semver-diff "^2.0.0" - xdg-basedir "^3.0.0" - update-notifier@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.0.tgz#4866b98c3bc5b5473c020b1250583628f9a328f3" @@ -25086,13 +24937,6 @@ url-join@^2.0.5: resolved "https://registry.yarnpkg.com/url-join/-/url-join-2.0.5.tgz#5af22f18c052a000a48d7b82c5e9c2e2feeda728" integrity sha1-WvIvGMBSoACkjXuCxenC4v7tpyg= -url-parse-lax@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" - integrity sha1-evjzA2Rem9eaJy56FKxovAYJ2nM= - dependencies: - prepend-http "^1.0.1" - url-parse-lax@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" @@ -26061,11 +25905,6 @@ xdg-basedir@^2.0.0: dependencies: os-homedir "^1.0.0" -xdg-basedir@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" - integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ= - xdg-basedir@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" From 8e167a60024701e0cb0686b619af503b00eee7b0 Mon Sep 17 00:00:00 2001 From: Chris Breiding Date: Tue, 21 Jul 2020 10:47:12 -0400 Subject: [PATCH 22/42] trigger new build From 87c2513b29ca0050b21bbbe052bbd9a5e560856c Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Thu, 23 Jul 2020 13:08:11 -0400 Subject: [PATCH 23/42] remove duplicate job inclusion --- circle.yml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/circle.yml b/circle.yml index c10c85696e9e..fc049d6853b5 100644 --- a/circle.yml +++ b/circle.yml @@ -1335,10 +1335,6 @@ jobs: command: npm run test:ci pull_request_id: 515 folder: examples/fundamentals__typescript - - test-binary-against-repo: - command: npm test - pull_request_id: 513 - folder: examples/fundamentals__module-api-wrap "test-binary-against-kitchensink": <<: *defaults @@ -1675,12 +1671,6 @@ linux-workflow: &linux-workflow ## TODO: remove this upon merging the PR - rename-blacklisthosts - pull/7622 - - test-binary-against-recipe-pull-request: - name: Test TypeScript recipe - filters: - branches: - only: - - v5.0-release # when working on a feature or a fix, # you are probably working in a branch # and you want to run a specific PR in the cypress-example-recipes From 87029a7f3e426cf2d104aa71849d6eeb74412e3e Mon Sep 17 00:00:00 2001 From: Chris Breiding Date: Thu, 23 Jul 2020 14:22:58 -0400 Subject: [PATCH 24/42] update foxdriver patch --- packages/server/patches/foxdriver+0.1.1.patch | 46 ----------------- patches/@benmalka+foxdriver+0.4.1.patch | 49 +++++++++++++++++++ 2 files changed, 49 insertions(+), 46 deletions(-) delete mode 100644 packages/server/patches/foxdriver+0.1.1.patch create mode 100644 patches/@benmalka+foxdriver+0.4.1.patch diff --git a/packages/server/patches/foxdriver+0.1.1.patch b/packages/server/patches/foxdriver+0.1.1.patch deleted file mode 100644 index 80eb15350c95..000000000000 --- a/packages/server/patches/foxdriver+0.1.1.patch +++ /dev/null @@ -1,46 +0,0 @@ -diff --git a/node_modules/foxdriver/build/logger.js b/node_modules/foxdriver/build/logger.js -index 80e315d..c160f30 100644 ---- a/node_modules/foxdriver/build/logger.js -+++ b/node_modules/foxdriver/build/logger.js -@@ -7,7 +7,7 @@ Object.defineProperty(exports, "__esModule", { - }); - exports.default = Logger; - --var _npmlog = _interopRequireDefault(require("npmlog")); -+const debug = require('debug')('foxdriver') - - var _package = _interopRequireDefault(require("../package.json")); - -@@ -16,10 +16,6 @@ var _package = _interopRequireDefault(require("../package.json")); - */ - const NPM_LEVELS = ['silly', 'verbose', 'debug', 'info', 'http', 'warn', 'error', 'chrome', 'firefox']; - --_npmlog.default.addLevel('debug', 1000, { -- fg: 'blue', -- bg: 'black' --}, 'dbug'); - - function Logger(component) { - const wrappedLogger = {}; -@@ -30,10 +26,8 @@ function Logger(component) { - - Object.defineProperty(wrappedLogger, 'level', { - get: () => { -- return _npmlog.default.level; - }, - set: newValue => { -- _npmlog.default.level = newValue; - }, - enumerable: true, - configurable: true -@@ -44,8 +38,8 @@ function Logger(component) { - - for (let level of NPM_LEVELS) { - wrappedLogger[level] = (...args) => { -- if (!process.env.DEBUG) return; -- return _npmlog.default[level](prefix, ...args); -+ // @see https://github.com/cypress-io/cypress/issues/7723 -+ debug(prefix, ...args) - }; - } - diff --git a/patches/@benmalka+foxdriver+0.4.1.patch b/patches/@benmalka+foxdriver+0.4.1.patch new file mode 100644 index 000000000000..5bf691110afb --- /dev/null +++ b/patches/@benmalka+foxdriver+0.4.1.patch @@ -0,0 +1,49 @@ +diff --git a/node_modules/@benmalka/foxdriver/build/logger.js b/node_modules/@benmalka/foxdriver/build/logger.js +index c28401c..bfb5da4 100644 +--- a/node_modules/@benmalka/foxdriver/build/logger.js ++++ b/node_modules/@benmalka/foxdriver/build/logger.js +@@ -5,9 +5,7 @@ Object.defineProperty(exports, "__esModule", { + }); + exports.default = Logger; + +-var _npmlog = require("npmlog"); +- +-var _npmlog2 = _interopRequireDefault(_npmlog); ++const debug = require('debug')('@benmalka/foxdriver') + + var _package = require("../package.json"); + +@@ -20,11 +18,6 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de + */ + const NPM_LEVELS = ['silly', 'verbose', 'debug', 'info', 'http', 'warn', 'error', 'chrome', 'firefox']; + +-_npmlog2.default.addLevel('debug', 1000, { +- fg: 'blue', +- bg: 'black' +-}, 'dbug'); +- + function Logger(component) { + const wrappedLogger = {}; + const prefix = _package2.default.name + (component ? `:${component}` : ''); +@@ -34,10 +27,8 @@ function Logger(component) { + + Object.defineProperty(wrappedLogger, 'level', { + get: () => { +- return _npmlog2.default.level; + }, + set: newValue => { +- _npmlog2.default.level = newValue; + }, + enumerable: true, + configurable: true +@@ -48,8 +39,8 @@ function Logger(component) { + + for (let level of NPM_LEVELS) { + wrappedLogger[level] = (...args) => { +- if (!process.env.DEBUG) return; +- return _npmlog2.default[level](prefix, ...args); ++ // @see https://github.com/cypress-io/cypress/issues/7723 ++ debug(prefix, ...args) + }; + } + From f65a50f411de12cc30e30027fda4ae1d61c85478 Mon Sep 17 00:00:00 2001 From: Chris Breiding Date: Mon, 27 Jul 2020 09:21:48 -0400 Subject: [PATCH 25/42] don't hoist @benmalka/foxdriver --- packages/server/package.json | 2 +- .../server/patches}/@benmalka+foxdriver+0.4.1.patch | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename {patches => packages/server/patches}/@benmalka+foxdriver+0.4.1.patch (100%) diff --git a/packages/server/package.json b/packages/server/package.json index 931b2a55ad6f..88e5702a8de4 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -198,7 +198,7 @@ "productName": "Cypress", "workspaces": { "nohoist": [ - "foxdriver" + "@benmalka/foxdriver" ] }, "optionalDependencies": { diff --git a/patches/@benmalka+foxdriver+0.4.1.patch b/packages/server/patches/@benmalka+foxdriver+0.4.1.patch similarity index 100% rename from patches/@benmalka+foxdriver+0.4.1.patch rename to packages/server/patches/@benmalka+foxdriver+0.4.1.patch From 95afe49bd1618aab442ff00df8d29b49fa781ee7 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Mon, 10 Aug 2020 11:30:37 -0400 Subject: [PATCH 26/42] put back blob-utils types --- cli/package.json | 1 + yarn.lock | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/cli/package.json b/cli/package.json index eaa56736fd91..a1f7fb244847 100644 --- a/cli/package.json +++ b/cli/package.json @@ -63,6 +63,7 @@ "@babel/preset-env": "7.9.5", "@cypress/sinon-chai": "1.1.0", "@packages/root": "*", + "@types/blob-util": "1.3.3", "@types/bluebird": "3.5.29", "@types/chai": "4.2.7", "@types/chai-jquery": "1.1.40", diff --git a/yarn.lock b/yarn.lock index 2abe0889b152..575a72931449 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3752,6 +3752,11 @@ dependencies: "@babel/types" "^7.3.0" +"@types/blob-util@1.3.3": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@types/blob-util/-/blob-util-1.3.3.tgz#adba644ae34f88e1dd9a5864c66ad651caaf628a" + integrity sha512-4ahcL/QDnpjWA2Qs16ZMQif7HjGP2cw3AGjHabybjw7Vm1EKu+cfQN1D78BaZbS1WJNa1opSMF5HNMztx7lR0w== + "@types/bluebird@*": version "3.5.32" resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.32.tgz#381e7b59e39f010d20bbf7e044e48f5caf1ab620" From d42be7bec09d8b9a43e6a18f0942fa5d8e2d3057 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Mon, 10 Aug 2020 11:51:37 -0400 Subject: [PATCH 27/42] fix(deps): electron@9.2.0 (#8235) Latest version for 5.0.0 release --- packages/electron/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/electron/package.json b/packages/electron/package.json index 02cf1619e906..8755ad56787b 100644 --- a/packages/electron/package.json +++ b/packages/electron/package.json @@ -24,7 +24,7 @@ "minimist": "1.2.5" }, "devDependencies": { - "electron": "9.0.5", + "electron": "9.2.0", "mocha": "3.5.3" }, "files": [ diff --git a/yarn.lock b/yarn.lock index 575a72931449..49e4b371cb25 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10437,10 +10437,10 @@ electron-to-chromium@^1.3.488: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.496.tgz#3f43d32930481d82ad3663d79658e7c59a58af0b" integrity sha512-TXY4mwoyowwi4Lsrq9vcTUYBThyc1b2hXaTZI13p8/FRhY2CTaq5lK+DVjhYkKiTLsKt569Xes+0J5JsVXFurQ== -electron@9.0.5: - version "9.0.5" - resolved "https://registry.yarnpkg.com/electron/-/electron-9.0.5.tgz#189ee117cc2a2777cccf40fae0766acec5faae57" - integrity sha512-bnL9H48LuQ250DML8xUscsKiuSu+xv5umXbpBXYJ0BfvYVmFfNbG3jCfhrsH7aP6UcQKVxOG1R/oQExd0EFneQ== +electron@9.2.0: + version "9.2.0" + resolved "https://registry.yarnpkg.com/electron/-/electron-9.2.0.tgz#d9fc8c8c9e5109669c366bd7b9ba83b06095d7a4" + integrity sha512-4ecZ3rcGg//Gk4fAK3Jo61T+uh36JhU6HHR/PTujQqQiBw1g4tNPd4R2hGGth2d+7FkRIs5GdRNef7h64fQEMw== dependencies: "@electron/get" "^1.0.1" "@types/node" "^12.0.12" From edfae7f1064cb0f838cf40b32f396ea5f4d6ca3b Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Mon, 10 Aug 2020 13:21:50 -0400 Subject: [PATCH 28/42] include blob-util as CLI prod utility --- cli/package.json | 2 +- yarn.lock | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/cli/package.json b/cli/package.json index a1f7fb244847..e294ad3104ad 100644 --- a/cli/package.json +++ b/cli/package.json @@ -26,6 +26,7 @@ "@types/sinonjs__fake-timers": "^6.0.1", "@types/sizzle": "^2.3.2", "arch": "^2.1.2", + "blob-util": "2.0.2", "bluebird": "^3.7.2", "cachedir": "^2.3.0", "chalk": "^4.1.0", @@ -63,7 +64,6 @@ "@babel/preset-env": "7.9.5", "@cypress/sinon-chai": "1.1.0", "@packages/root": "*", - "@types/blob-util": "1.3.3", "@types/bluebird": "3.5.29", "@types/chai": "4.2.7", "@types/chai-jquery": "1.1.40", diff --git a/yarn.lock b/yarn.lock index 49e4b371cb25..16e8ac465fbb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3752,11 +3752,6 @@ dependencies: "@babel/types" "^7.3.0" -"@types/blob-util@1.3.3": - version "1.3.3" - resolved "https://registry.yarnpkg.com/@types/blob-util/-/blob-util-1.3.3.tgz#adba644ae34f88e1dd9a5864c66ad651caaf628a" - integrity sha512-4ahcL/QDnpjWA2Qs16ZMQif7HjGP2cw3AGjHabybjw7Vm1EKu+cfQN1D78BaZbS1WJNa1opSMF5HNMztx7lR0w== - "@types/bluebird@*": version "3.5.32" resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.32.tgz#381e7b59e39f010d20bbf7e044e48f5caf1ab620" From 6c15dde9958ca7a07f346da1dbda7642fe029191 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Mon, 10 Aug 2020 13:22:14 -0400 Subject: [PATCH 29/42] build mac binary on branch v5.0-release --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index c93a3746a0ac..53d81dcbd7f0 100644 --- a/circle.yml +++ b/circle.yml @@ -8,7 +8,7 @@ macBuildFilters: &macBuildFilters branches: only: - develop - - investigate-spec-on-mac + - v5.0-release defaults: &defaults parallelism: 1 From 474bcddbd3464854d3f443b15ce85f4e92b9ed40 Mon Sep 17 00:00:00 2001 From: Chris Breiding Date: Mon, 10 Aug 2020 16:55:15 -0400 Subject: [PATCH 30/42] feat: Change default preprocessor to webpack (#7982) * make tests preprocessor agnostic * update eslintignore * put back deps needed for e2e test * remove obselete snapshot it was replaced by 1_typescript_support_spec.ts.js * switch from browserify to webpack preprocessor * cmon github * fix/update tests * bump preprocessor and update snapshots * update snapshots * bump preprocessor to gain json support * fix e2e tests with webpack-originated errors * bump preprocessor version, fix node globals * update snapshot * remove support for ? in file path * bump preprocessor version * bump preprocessor again * bump preprocessor * bump preprocessor * update snapshots * bump preprocessor version * bump preprocessor, quiet the paths plugin * add test verifying tsconfig paths work * bump batteries-included preprocessor and install latest webpack preprocessor beside it * update snapshots * put back snapshot * update snapshot * update snapshot Co-authored-by: Brian Mann --- .eslintignore | 2 +- .../1_busted_support_file_spec.js | 13 +- ...bel_es201_spec.js => 1_es_modules_spec.js} | 43 +- .../1_typescript_support_spec.js | 226 -------- .../1_typescript_support_spec.ts.js | 44 +- .../__snapshots__/5_spec_isolation_spec.js | 46 +- .../server/__snapshots__/5_stdout_spec.js | 12 +- .../server/__snapshots__/7_record_spec.js | 26 +- packages/server/lib/plugins/preprocessor.js | 13 +- packages/server/package.json | 13 +- .../test/e2e/1_busted_support_file_spec.js | 1 + ...bel_es201_spec.js => 1_es_modules_spec.js} | 7 +- .../test/e2e/1_typescript_support_spec.ts | 11 +- .../server/test/e2e/4_controllers_spec.js | 10 +- .../server/test/e2e/5_screenshots_spec.js | 1 + packages/server/test/e2e/5_stdout_spec.js | 1 + .../test/integration/http_requests_spec.js | 97 +--- ...ec.js => es_module_import_failing_spec.js} | 0 ...offee => es_modules_in_coffee_spec.coffee} | 0 ...ing_spec.ts => typescript_failing_spec.ts} | 2 +- ...ing_spec.ts => typescript_passing_spec.ts} | 0 .../ids/cypress/integration/foo.coffee | 3 +- .../projects/no-server/helpers/includes.js | 2 +- .../projects/no-server/my-tests/test1.js | 2 +- .../projects/ts-proj-with-paths/cypress.json | 4 + .../cypress/integration/app_spec.ts | 6 + .../projects/ts-proj-with-paths/src/main.ts | 1 + .../projects/ts-proj-with-paths/tsconfig.json | 8 + packages/server/test/support/helpers/e2e.ts | 6 + .../test/support/helpers/simple_tsify.js | 45 -- .../test/unit/plugins/preprocessor_spec.js | 36 +- yarn.lock | 493 ++++++++++++------ 32 files changed, 518 insertions(+), 656 deletions(-) rename packages/server/__snapshots__/{1_browserify_babel_es201_spec.js => 1_es_modules_spec.js} (84%) delete mode 100644 packages/server/__snapshots__/1_typescript_support_spec.js rename packages/server/test/e2e/{1_browserify_babel_es201_spec.js => 1_es_modules_spec.js} (65%) rename packages/server/test/support/fixtures/projects/e2e/cypress/integration/{browserify_babel_es2015_failing_spec.js => es_module_import_failing_spec.js} (100%) rename packages/server/test/support/fixtures/projects/e2e/cypress/integration/{browserify_babel_es2015_passing_spec.coffee => es_modules_in_coffee_spec.coffee} (100%) rename packages/server/test/support/fixtures/projects/e2e/cypress/integration/{browserify_typescript_failing_spec.ts => typescript_failing_spec.ts} (76%) rename packages/server/test/support/fixtures/projects/e2e/cypress/integration/{browserify_typescript_passing_spec.ts => typescript_passing_spec.ts} (100%) create mode 100644 packages/server/test/support/fixtures/projects/ts-proj-with-paths/cypress.json create mode 100644 packages/server/test/support/fixtures/projects/ts-proj-with-paths/cypress/integration/app_spec.ts create mode 100644 packages/server/test/support/fixtures/projects/ts-proj-with-paths/src/main.ts create mode 100644 packages/server/test/support/fixtures/projects/ts-proj-with-paths/tsconfig.json delete mode 100644 packages/server/test/support/helpers/simple_tsify.js diff --git a/.eslintignore b/.eslintignore index f89d1d0f27ff..086b6069fa73 100644 --- a/.eslintignore +++ b/.eslintignore @@ -26,7 +26,7 @@ packages/server/lib/scaffold/plugins/index.js packages/server/lib/scaffold/support/index.js packages/server/lib/scaffold/support/commands.js packages/server/test/support/fixtures/projects/e2e/cypress/integration/stdout_exit_early_failing_spec.js -packages/server/test/support/fixtures/projects/e2e/cypress/integration/browserify_typescript_failing_spec.ts +packages/server/test/support/fixtures/projects/e2e/cypress/integration/typescript_failing_spec.ts **/.projects **/*.d.ts diff --git a/packages/server/__snapshots__/1_busted_support_file_spec.js b/packages/server/__snapshots__/1_busted_support_file_spec.js index 5e07b4965537..13f96bdac038 100644 --- a/packages/server/__snapshots__/1_busted_support_file_spec.js +++ b/packages/server/__snapshots__/1_busted_support_file_spec.js @@ -21,7 +21,18 @@ Oops...we found an error preparing this test file: The error was: -Error: Cannot find module './does/not/exist' from '/foo/bar/.projects/busted-support-file/cypress/support' +Error: Webpack Compilation Error +./cypress/support/index.js +Module not found: Error: Can't resolve './does/not/exist' in '/foo/bar/.projects/busted-support-file/cypress/support' +Looked for and couldn't find the file at the following paths: +[/foo/bar/.projects/busted-support-file/cypress/support/does/not/exist] +[/foo/bar/.projects/busted-support-file/cypress/support/does/not/exist.js] +[/foo/bar/.projects/busted-support-file/cypress/support/does/not/exist.json] +[/foo/bar/.projects/busted-support-file/cypress/support/does/not/exist.jsx] +[/foo/bar/.projects/busted-support-file/cypress/support/does/not/exist.coffee] +[/foo/bar/.projects/busted-support-file/cypress/support/does/not/exist.ts] +[/foo/bar/.projects/busted-support-file/cypress/support/does/not/exist.tsx] + @ ./cypress/support/index.js 3:0-27 This occurred while Cypress was compiling and bundling your test code. This is usually caused by: diff --git a/packages/server/__snapshots__/1_browserify_babel_es201_spec.js b/packages/server/__snapshots__/1_es_modules_spec.js similarity index 84% rename from packages/server/__snapshots__/1_browserify_babel_es201_spec.js rename to packages/server/__snapshots__/1_es_modules_spec.js index aa760b12e4a9..07b9c4ae4b8a 100644 --- a/packages/server/__snapshots__/1_browserify_babel_es201_spec.js +++ b/packages/server/__snapshots__/1_es_modules_spec.js @@ -1,4 +1,4 @@ -exports['e2e browserify, babel, es2015 passes 1'] = ` +exports['e2e es modules passes 1'] = ` ==================================================================================================== @@ -7,14 +7,14 @@ exports['e2e browserify, babel, es2015 passes 1'] = ` ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Cypress: 1.2.3 │ │ Browser: FooBrowser 88 │ - │ Specs: 1 found (browserify_babel_es2015_passing_spec.coffee) │ - │ Searched: cypress/integration/browserify_babel_es2015_passing_spec.coffee │ + │ Specs: 1 found (es_modules_in_coffee_spec.coffee) │ + │ Searched: cypress/integration/es_modules_in_coffee_spec.coffee │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: browserify_babel_es2015_passing_spec.coffee (1 of 1) + Running: es_modules_in_coffee_spec.coffee (1 of 1) imports work @@ -37,15 +37,15 @@ exports['e2e browserify, babel, es2015 passes 1'] = ` │ Screenshots: 0 │ │ Video: true │ │ Duration: X seconds │ - │ Spec Ran: browserify_babel_es2015_passing_spec.coffee │ + │ Spec Ran: es_modules_in_coffee_spec.coffee │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ (Video) - Started processing: Compressing to 32 CRF - - Finished processing: /XXX/XXX/XXX/cypress/videos/browserify_babel_es2015_passing (X second) - _spec.coffee.mp4 + - Finished processing: /XXX/XXX/XXX/cypress/videos/es_modules_in_coffee_spec.coffe (X second) + e.mp4 ==================================================================================================== @@ -55,15 +55,14 @@ exports['e2e browserify, babel, es2015 passes 1'] = ` Spec Tests Passing Failing Pending Skipped ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ ✔ browserify_babel_es2015_passing_spe XX:XX 3 3 - - - │ - │ c.coffee │ + │ ✔ es_modules_in_coffee_spec.coffee XX:XX 3 3 - - - │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ ✔ All specs passed! XX:XX 3 3 - - - ` -exports['e2e browserify, babel, es2015 fails 1'] = ` +exports['e2e es modules fails 1'] = ` ==================================================================================================== @@ -72,26 +71,31 @@ exports['e2e browserify, babel, es2015 fails 1'] = ` ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Cypress: 1.2.3 │ │ Browser: FooBrowser 88 │ - │ Specs: 1 found (browserify_babel_es2015_failing_spec.js) │ - │ Searched: cypress/integration/browserify_babel_es2015_failing_spec.js │ + │ Specs: 1 found (es_module_import_failing_spec.js) │ + │ Searched: cypress/integration/es_module_import_failing_spec.js │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: browserify_babel_es2015_failing_spec.js (1 of 1) + Running: es_module_import_failing_spec.js (1 of 1) Oops...we found an error preparing this test file: - /foo/bar/.projects/e2e/cypress/integration/browserify_babel_es2015_failing_spec.js + /foo/bar/.projects/e2e/cypress/integration/es_module_import_failing_spec.js The error was: +Error: Webpack Compilation Error +./lib/fail.js +Module build failed (from [..]): SyntaxError: /foo/bar/.projects/e2e/lib/fail.js: Unexpected token (2:0) 1 | export default { > 2 | - | ^ while parsing file: /foo/bar/.projects/e2e/lib/fail.js + | ^ + + @ ./cypress/integration/es_module_import_failing_spec.js 3:0-25 This occurred while Cypress was compiling and bundling your test code. This is usually caused by: @@ -111,15 +115,15 @@ Fix the error in your code and re-run your tests. │ Screenshots: 0 │ │ Video: true │ │ Duration: X seconds │ - │ Spec Ran: browserify_babel_es2015_failing_spec.js │ + │ Spec Ran: es_module_import_failing_spec.js │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ (Video) - Started processing: Compressing to 32 CRF - - Finished processing: /XXX/XXX/XXX/cypress/videos/browserify_babel_es2015_failing (X second) - _spec.js.mp4 + - Finished processing: /XXX/XXX/XXX/cypress/videos/es_module_import_failing_spec.j (X second) + s.mp4 ==================================================================================================== @@ -129,8 +133,7 @@ Fix the error in your code and re-run your tests. Spec Tests Passing Failing Pending Skipped ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ ✖ browserify_babel_es2015_failing_spe XX:XX - - 1 - - │ - │ c.js │ + │ ✖ es_module_import_failing_spec.js XX:XX - - 1 - - │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ ✖ 1 of 1 failed (100%) XX:XX - - 1 - - diff --git a/packages/server/__snapshots__/1_typescript_support_spec.js b/packages/server/__snapshots__/1_typescript_support_spec.js deleted file mode 100644 index fe53ebdc7c7f..000000000000 --- a/packages/server/__snapshots__/1_typescript_support_spec.js +++ /dev/null @@ -1,226 +0,0 @@ -exports['e2e typescript spec passes 1'] = ` - -==================================================================================================== - - (Run Starting) - - ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ Cypress: 1.2.3 │ - │ Browser: FooBrowser 88 │ - │ Specs: 1 found (browserify_typescript_passing_spec.ts) │ - │ Searched: cypress/integration/browserify_typescript_passing_spec.ts │ - └────────────────────────────────────────────────────────────────────────────────────────────────┘ - - -──────────────────────────────────────────────────────────────────────────────────────────────────── - - Running: browserify_typescript_passing_spec.ts (1 of 1) - - - imports work - ✓ foo coffee - ✓ bar babel - ✓ dom jsx - - - 3 passing - - - (Results) - - ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ Tests: 3 │ - │ Passing: 3 │ - │ Failing: 0 │ - │ Pending: 0 │ - │ Skipped: 0 │ - │ Screenshots: 0 │ - │ Video: true │ - │ Duration: X seconds │ - │ Spec Ran: browserify_typescript_passing_spec.ts │ - └────────────────────────────────────────────────────────────────────────────────────────────────┘ - - - (Video) - - - Started processing: Compressing to 32 CRF - - Finished processing: /XXX/XXX/XXX/cypress/videos/browserify_typescript_passing_s (X second) - pec.ts.mp4 - - -==================================================================================================== - - (Run Finished) - - - Spec Tests Passing Failing Pending Skipped - ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ ✔ browserify_typescript_passing_spec. XX:XX 3 3 - - - │ - │ ts │ - └────────────────────────────────────────────────────────────────────────────────────────────────┘ - ✔ All specs passed! XX:XX 3 3 - - - - - -` - -exports['e2e typescript spec fails 1'] = ` - -==================================================================================================== - - (Run Starting) - - ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ Cypress: 1.2.3 │ - │ Browser: FooBrowser 88 │ - │ Specs: 1 found (browserify_typescript_failing_spec.ts) │ - │ Searched: cypress/integration/browserify_typescript_failing_spec.ts │ - └────────────────────────────────────────────────────────────────────────────────────────────────┘ - - -──────────────────────────────────────────────────────────────────────────────────────────────────── - - Running: browserify_typescript_failing_spec.ts (1 of 1) - -Oops...we found an error preparing this test file: - - /foo/bar/.projects/e2e/cypress/integration/browserify_typescript_failing_spec.ts - -The error was: - -/foo/bar/.projects/e2e/cypress/integration/browserify_typescript_failing_spec.ts:3 -describe('fail', - > ); - ^ -ParseError: Unexpected token - -This occurred while Cypress was compiling and bundling your test code. This is usually caused by: - -- A missing file or dependency -- A syntax error in the file or one of its dependencies - -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_typescript_failing_spec.ts │ - └────────────────────────────────────────────────────────────────────────────────────────────────┘ - - - (Video) - - - Started processing: Compressing to 32 CRF - - Finished processing: /XXX/XXX/XXX/cypress/videos/browserify_typescript_failing_s (X second) - pec.ts.mp4 - - -==================================================================================================== - - (Run Finished) - - - Spec Tests Passing Failing Pending Skipped - ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ ✖ browserify_typescript_failing_spec. XX:XX - - 1 - - │ - │ ts │ - └────────────────────────────────────────────────────────────────────────────────────────────────┘ - ✖ 1 of 1 failed (100%) XX:XX - - 1 - - - - -` - -exports['e2e typescript project passes 1'] = ` - -==================================================================================================== - - (Run Starting) - - ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ Cypress: 1.2.3 │ - │ Browser: FooBrowser 88 │ - │ Specs: 2 found (app_spec.ts, math.ts) │ - └────────────────────────────────────────────────────────────────────────────────────────────────┘ - - -──────────────────────────────────────────────────────────────────────────────────────────────────── - - Running: app_spec.ts (1 of 2) - - - ✓ is true - - 1 passing - - - (Results) - - ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ Tests: 1 │ - │ Passing: 1 │ - │ Failing: 0 │ - │ Pending: 0 │ - │ Skipped: 0 │ - │ Screenshots: 0 │ - │ Video: true │ - │ Duration: X seconds │ - │ Spec Ran: app_spec.ts │ - └────────────────────────────────────────────────────────────────────────────────────────────────┘ - - - (Video) - - - Started processing: Compressing to 32 CRF - - Finished processing: /XXX/XXX/XXX/cypress/videos/app_spec.ts.mp4 (X second) - - -──────────────────────────────────────────────────────────────────────────────────────────────────── - - Running: math.ts (2 of 2) - - - 0 passing - - - (Results) - - ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ Tests: 0 │ - │ Passing: 0 │ - │ Failing: 0 │ - │ Pending: 0 │ - │ Skipped: 0 │ - │ Screenshots: 0 │ - │ Video: true │ - │ Duration: X seconds │ - │ Spec Ran: math.ts │ - └────────────────────────────────────────────────────────────────────────────────────────────────┘ - - - (Video) - - - Started processing: Compressing to 32 CRF - - Finished processing: /XXX/XXX/XXX/cypress/videos/math.ts.mp4 (X second) - - -==================================================================================================== - - (Run Finished) - - - Spec Tests Passing Failing Pending Skipped - ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ ✔ app_spec.ts XX:XX 1 1 - - - │ - ├────────────────────────────────────────────────────────────────────────────────────────────────┤ - │ ✔ math.ts XX:XX - - - - - │ - └────────────────────────────────────────────────────────────────────────────────────────────────┘ - ✔ All specs passed! XX:XX 1 1 - - - - - -` diff --git a/packages/server/__snapshots__/1_typescript_support_spec.ts.js b/packages/server/__snapshots__/1_typescript_support_spec.ts.js index 3363bf4d83ca..4911a6df2557 100644 --- a/packages/server/__snapshots__/1_typescript_support_spec.ts.js +++ b/packages/server/__snapshots__/1_typescript_support_spec.ts.js @@ -7,14 +7,14 @@ exports['e2e typescript spec passes 1'] = ` ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Cypress: 1.2.3 │ │ Browser: FooBrowser 88 │ - │ Specs: 1 found (browserify_typescript_passing_spec.ts) │ - │ Searched: cypress/integration/browserify_typescript_passing_spec.ts │ + │ Specs: 1 found (typescript_passing_spec.ts) │ + │ Searched: cypress/integration/typescript_passing_spec.ts │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: browserify_typescript_passing_spec.ts (1 of 1) + Running: typescript_passing_spec.ts (1 of 1) imports work @@ -38,15 +38,14 @@ exports['e2e typescript spec passes 1'] = ` │ Screenshots: 0 │ │ Video: true │ │ Duration: X seconds │ - │ Spec Ran: browserify_typescript_passing_spec.ts │ + │ Spec Ran: typescript_passing_spec.ts │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ (Video) - Started processing: Compressing to 32 CRF - - Finished processing: /XXX/XXX/XXX/cypress/videos/browserify_typescript_passing_s (X second) - pec.ts.mp4 + - Finished processing: /XXX/XXX/XXX/cypress/videos/typescript_passing_spec.ts.mp4 (X second) ==================================================================================================== @@ -56,8 +55,7 @@ exports['e2e typescript spec passes 1'] = ` Spec Tests Passing Failing Pending Skipped ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ ✔ browserify_typescript_passing_spec. XX:XX 4 4 - - - │ - │ ts │ + │ ✔ typescript_passing_spec.ts XX:XX 4 4 - - - │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ ✔ All specs passed! XX:XX 4 4 - - - @@ -73,25 +71,31 @@ exports['e2e typescript spec fails 1'] = ` ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Cypress: 1.2.3 │ │ Browser: FooBrowser 88 │ - │ Specs: 1 found (browserify_typescript_failing_spec.ts) │ - │ Searched: cypress/integration/browserify_typescript_failing_spec.ts │ + │ Specs: 1 found (typescript_failing_spec.ts) │ + │ Searched: cypress/integration/typescript_failing_spec.ts │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: browserify_typescript_failing_spec.ts (1 of 1) + Running: typescript_failing_spec.ts (1 of 1) Oops...we found an error preparing this test file: - /foo/bar/.projects/e2e/cypress/integration/browserify_typescript_failing_spec.ts + /foo/bar/.projects/e2e/cypress/integration/typescript_failing_spec.ts The error was: -/foo/bar/.projects/e2e/cypress/integration/browserify_typescript_failing_spec.ts:3 -describe('fail', - > ); - ^ -ParseError: Unexpected token +Error: Webpack Compilation Error +./cypress/integration/typescript_failing_spec.tsXX:XX +Module parse failed: Unexpected token (4:19) +File was processed with these loaders: + * ../../../../node_modules/@cypress/webpack-batteries-included-preprocessor/node_modules/ts-loader/index.js +You may need an additional loader to handle the result of these loaders. +| // The code below is ignored by eslint +| // because it tests failing spec. +> describe('fail', - > ); +| This occurred while Cypress was compiling and bundling your test code. This is usually caused by: @@ -111,15 +115,14 @@ Fix the error in your code and re-run your tests. │ Screenshots: 0 │ │ Video: true │ │ Duration: X seconds │ - │ Spec Ran: browserify_typescript_failing_spec.ts │ + │ Spec Ran: typescript_failing_spec.ts │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ (Video) - Started processing: Compressing to 32 CRF - - Finished processing: /XXX/XXX/XXX/cypress/videos/browserify_typescript_failing_s (X second) - pec.ts.mp4 + - Finished processing: /XXX/XXX/XXX/cypress/videos/typescript_failing_spec.ts.mp4 (X second) ==================================================================================================== @@ -129,8 +132,7 @@ Fix the error in your code and re-run your tests. Spec Tests Passing Failing Pending Skipped ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ ✖ browserify_typescript_failing_spec. XX:XX - - 1 - - │ - │ ts │ + │ ✖ typescript_failing_spec.ts XX:XX - - 1 - - │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ ✖ 1 of 1 failed (100%) XX:XX - - 1 - - diff --git a/packages/server/__snapshots__/5_spec_isolation_spec.js b/packages/server/__snapshots__/5_spec_isolation_spec.js index a756c1dbb073..93dd31c25ea3 100644 --- a/packages/server/__snapshots__/5_spec_isolation_spec.js +++ b/packages/server/__snapshots__/5_spec_isolation_spec.js @@ -39,7 +39,7 @@ exports['e2e spec isolation fails'] = { "title": [ "\"before all\" hook" ], - "body": "function () {\n if (Cypress.browser.family === 'chromium' && Cypress.browser.name !== 'electron') {\n return Cypress.automation('remote:debugger:protocol', {\n command: 'Emulation.setDeviceMetricsOverride',\n params: {\n width: 1280,\n height: 720,\n deviceScaleFactor: 1,\n mobile: false,\n screenWidth: 1280,\n screenHeight: 720,\n },\n })\n .then(function () {\n // can't tell expect() not to log, so manually throwing here\n if (window.devicePixelRatio !== 1) {\n throw new Error('Setting devicePixelRatio to 1 failed');\n }\n });\n }\n}" + "body": "function () {\n if (Cypress.browser.family === 'chromium' && Cypress.browser.name !== 'electron') {\n return Cypress.automation('remote:debugger:protocol', {\n command: 'Emulation.setDeviceMetricsOverride',\n params: {\n width: 1280,\n height: 720,\n deviceScaleFactor: 1,\n mobile: false,\n screenWidth: 1280,\n screenHeight: 720\n }\n }).then(function () {\n // can't tell expect() not to log, so manually throwing here\n if (window.devicePixelRatio !== 1) {\n throw new Error('Setting devicePixelRatio to 1 failed');\n }\n });\n }\n}" }, { "hookId": "h2", @@ -47,7 +47,7 @@ exports['e2e spec isolation fails'] = { "title": [ "\"before each\" hook" ], - "body": "function () {\n throw new Error(\"fail1\");\n }" + "body": "function() {\n throw new Error(\"fail1\");\n }" }, { "hookId": "h3", @@ -55,7 +55,7 @@ exports['e2e spec isolation fails'] = { "title": [ "\"after each\" hook" ], - "body": "function () {\n throw new Error(\"fail2\");\n }" + "body": "function() {\n throw new Error(\"fail2\");\n }" }, { "hookId": "h4", @@ -63,7 +63,7 @@ exports['e2e spec isolation fails'] = { "title": [ "\"after all\" hook" ], - "body": "function () {\n throw new Error(\"fail3\");\n }" + "body": "function() {\n throw new Error(\"fail3\");\n }" } ], "tests": [ @@ -75,7 +75,7 @@ exports['e2e spec isolation fails'] = { "never gets here" ], "state": "failed", - "body": "function () { }", + "body": "function() {}", "stack": "Error: fail1\n\nBecause this error occurred during a `before each` hook we are skipping the remaining tests in the current suite: `beforeEach hooks`\n [stack trace lines]", "error": "fail1\n\nBecause this error occurred during a `before each` hook we are skipping the remaining tests in the current suite: `beforeEach hooks`", "timings": { @@ -125,7 +125,7 @@ exports['e2e spec isolation fails'] = { "runs this" ], "state": "failed", - "body": "function () { }", + "body": "function() {}", "stack": "Error: fail2\n\nBecause this error occurred during a `after each` hook we are skipping the remaining tests in the current suite: `afterEach hooks`\n [stack trace lines]", "error": "fail2\n\nBecause this error occurred during a `after each` hook we are skipping the remaining tests in the current suite: `afterEach hooks`", "timings": { @@ -155,7 +155,7 @@ exports['e2e spec isolation fails'] = { "does not run this" ], "state": "skipped", - "body": "function () { }", + "body": "function() {}", "stack": null, "error": null, "timings": null, @@ -172,7 +172,7 @@ exports['e2e spec isolation fails'] = { "runs this" ], "state": "passed", - "body": "function () { }", + "body": "function() {}", "stack": null, "error": null, "timings": { @@ -195,7 +195,7 @@ exports['e2e spec isolation fails'] = { "fails on this" ], "state": "failed", - "body": "function () { }", + "body": "function() {}", "stack": "Error: fail3\n\nBecause this error occurred during a `after all` hook we are skipping the remaining tests in the current suite: `after hooks`\n [stack trace lines]", "error": "fail3\n\nBecause this error occurred during a `after all` hook we are skipping the remaining tests in the current suite: `after hooks`", "timings": { @@ -287,7 +287,7 @@ exports['e2e spec isolation fails'] = { "title": [ "\"before all\" hook" ], - "body": "function () {\n if (Cypress.browser.family === 'chromium' && Cypress.browser.name !== 'electron') {\n return Cypress.automation('remote:debugger:protocol', {\n command: 'Emulation.setDeviceMetricsOverride',\n params: {\n width: 1280,\n height: 720,\n deviceScaleFactor: 1,\n mobile: false,\n screenWidth: 1280,\n screenHeight: 720,\n },\n })\n .then(function () {\n // can't tell expect() not to log, so manually throwing here\n if (window.devicePixelRatio !== 1) {\n throw new Error('Setting devicePixelRatio to 1 failed');\n }\n });\n }\n}" + "body": "function () {\n if (Cypress.browser.family === 'chromium' && Cypress.browser.name !== 'electron') {\n return Cypress.automation('remote:debugger:protocol', {\n command: 'Emulation.setDeviceMetricsOverride',\n params: {\n width: 1280,\n height: 720,\n deviceScaleFactor: 1,\n mobile: false,\n screenWidth: 1280,\n screenHeight: 720\n }\n }).then(function () {\n // can't tell expect() not to log, so manually throwing here\n if (window.devicePixelRatio !== 1) {\n throw new Error('Setting devicePixelRatio to 1 failed');\n }\n });\n }\n}" } ], "tests": [ @@ -298,7 +298,7 @@ exports['e2e spec isolation fails'] = { "fails1" ], "state": "failed", - "body": "function () {\n return cy.wrap(true, {\n timeout: 100\n }).should(\"be.false\");\n }", + "body": "function() {\n return cy.wrap(true, {\n timeout: 100\n }).should(\"be.false\");\n }", "stack": "AssertionError: Timed out retrying: expected true to be false\n [stack trace lines]", "error": "Timed out retrying: expected true to be false", "timings": { @@ -327,7 +327,7 @@ exports['e2e spec isolation fails'] = { "fails2" ], "state": "failed", - "body": "function () {\n throw new Error(\"fails2\");\n }", + "body": "function() {\n throw new Error(\"fails2\");\n }", "stack": "Error: fails2\n [stack trace lines]", "error": "fails2", "timings": { @@ -403,7 +403,7 @@ exports['e2e spec isolation fails'] = { "title": [ "\"before all\" hook" ], - "body": "function () {\n if (Cypress.browser.family === 'chromium' && Cypress.browser.name !== 'electron') {\n return Cypress.automation('remote:debugger:protocol', {\n command: 'Emulation.setDeviceMetricsOverride',\n params: {\n width: 1280,\n height: 720,\n deviceScaleFactor: 1,\n mobile: false,\n screenWidth: 1280,\n screenHeight: 720,\n },\n })\n .then(function () {\n // can't tell expect() not to log, so manually throwing here\n if (window.devicePixelRatio !== 1) {\n throw new Error('Setting devicePixelRatio to 1 failed');\n }\n });\n }\n}" + "body": "function () {\n if (Cypress.browser.family === 'chromium' && Cypress.browser.name !== 'electron') {\n return Cypress.automation('remote:debugger:protocol', {\n command: 'Emulation.setDeviceMetricsOverride',\n params: {\n width: 1280,\n height: 720,\n deviceScaleFactor: 1,\n mobile: false,\n screenWidth: 1280,\n screenHeight: 720\n }\n }).then(function () {\n // can't tell expect() not to log, so manually throwing here\n if (window.devicePixelRatio !== 1) {\n throw new Error('Setting devicePixelRatio to 1 failed');\n }\n });\n }\n}" }, { "hookId": "h2", @@ -411,7 +411,7 @@ exports['e2e spec isolation fails'] = { "title": [ "\"before all\" hook" ], - "body": "function () {\n return cy.wait(100);\n }" + "body": "function() {\n return cy.wait(100);\n }" }, { "hookId": "h3", @@ -419,7 +419,7 @@ exports['e2e spec isolation fails'] = { "title": [ "\"before each\" hook" ], - "body": "function () {\n return cy.wait(200);\n }" + "body": "function() {\n return cy.wait(200);\n }" }, { "hookId": "h5", @@ -427,7 +427,7 @@ exports['e2e spec isolation fails'] = { "title": [ "\"after each\" hook" ], - "body": "function () {\n return cy.wait(200);\n }" + "body": "function() {\n return cy.wait(200);\n }" }, { "hookId": "h4", @@ -435,7 +435,7 @@ exports['e2e spec isolation fails'] = { "title": [ "\"after all\" hook" ], - "body": "function () {\n return cy.wait(100);\n }" + "body": "function() {\n return cy.wait(100);\n }" } ], "tests": [ @@ -446,7 +446,7 @@ exports['e2e spec isolation fails'] = { "t1" ], "state": "passed", - "body": "function () {\n return cy.wrap(\"t1\").should(\"eq\", \"t1\");\n }", + "body": "function() {\n return cy.wrap(\"t1\").should(\"eq\", \"t1\");\n }", "stack": null, "error": null, "timings": { @@ -494,7 +494,7 @@ exports['e2e spec isolation fails'] = { "t2" ], "state": "passed", - "body": "function () {\n return cy.wrap(\"t2\").should(\"eq\", \"t2\");\n }", + "body": "function() {\n return cy.wrap(\"t2\").should(\"eq\", \"t2\");\n }", "stack": null, "error": null, "timings": { @@ -530,7 +530,7 @@ exports['e2e spec isolation fails'] = { "t3" ], "state": "passed", - "body": "function () {\n return cy.wrap(\"t3\").should(\"eq\", \"t3\");\n }", + "body": "function() {\n return cy.wrap(\"t3\").should(\"eq\", \"t3\");\n }", "stack": null, "error": null, "timings": { @@ -608,7 +608,7 @@ exports['e2e spec isolation fails'] = { "title": [ "\"before all\" hook" ], - "body": "function () {\n if (Cypress.browser.family === 'chromium' && Cypress.browser.name !== 'electron') {\n return Cypress.automation('remote:debugger:protocol', {\n command: 'Emulation.setDeviceMetricsOverride',\n params: {\n width: 1280,\n height: 720,\n deviceScaleFactor: 1,\n mobile: false,\n screenWidth: 1280,\n screenHeight: 720,\n },\n })\n .then(function () {\n // can't tell expect() not to log, so manually throwing here\n if (window.devicePixelRatio !== 1) {\n throw new Error('Setting devicePixelRatio to 1 failed');\n }\n });\n }\n}" + "body": "function () {\n if (Cypress.browser.family === 'chromium' && Cypress.browser.name !== 'electron') {\n return Cypress.automation('remote:debugger:protocol', {\n command: 'Emulation.setDeviceMetricsOverride',\n params: {\n width: 1280,\n height: 720,\n deviceScaleFactor: 1,\n mobile: false,\n screenWidth: 1280,\n screenHeight: 720\n }\n }).then(function () {\n // can't tell expect() not to log, so manually throwing here\n if (window.devicePixelRatio !== 1) {\n throw new Error('Setting devicePixelRatio to 1 failed');\n }\n });\n }\n}" }, { "hookId": "h2", @@ -616,7 +616,7 @@ exports['e2e spec isolation fails'] = { "title": [ "\"before each\" hook" ], - "body": "function () {\n return cy.wait(1000);\n }" + "body": "function() {\n return cy.wait(1000);\n }" } ], "tests": [ @@ -627,7 +627,7 @@ exports['e2e spec isolation fails'] = { "passes" ], "state": "passed", - "body": "function () {\n return cy.wrap(true).should(\"be.true\");\n }", + "body": "function() {\n return cy.wrap(true).should(\"be.true\");\n }", "stack": null, "error": null, "timings": { diff --git a/packages/server/__snapshots__/5_stdout_spec.js b/packages/server/__snapshots__/5_stdout_spec.js index bc8aa9edb69e..8b8abed0ca38 100644 --- a/packages/server/__snapshots__/5_stdout_spec.js +++ b/packages/server/__snapshots__/5_stdout_spec.js @@ -141,10 +141,14 @@ Oops...we found an error preparing this test file: The error was: -/foo/bar/.projects/e2e/cypress/integration/stdout_exit_early_failing_spec.js:1 -+ > - ^ -ParseError: Unexpected token +Error: Webpack Compilation Error +./cypress/integration/stdout_exit_early_failing_spec.js +Module build failed (from [..]): +SyntaxError: /foo/bar/.projects/e2e/cypress/integration/stdout_exit_early_failing_spec.js: Unexpected token (1:1) + +> 1 | +> + | ^ + 2 | This occurred while Cypress was compiling and bundling your test code. This is usually caused by: diff --git a/packages/server/__snapshots__/7_record_spec.js b/packages/server/__snapshots__/7_record_spec.js index 1421c521401b..c4c3303effa1 100644 --- a/packages/server/__snapshots__/7_record_spec.js +++ b/packages/server/__snapshots__/7_record_spec.js @@ -26,7 +26,18 @@ Oops...we found an error preparing this test file: The error was: -Error: Cannot find module '../it/does/not/exist' from '/foo/bar/.projects/e2e/cypress/integration' +Error: Webpack Compilation Error +./cypress/integration/record_error_spec.coffee +Module not found: Error: Can't resolve '../it/does/not/exist' in '/foo/bar/.projects/e2e/cypress/integration' +Looked for and couldn't find the file at the following paths: +[/foo/bar/.projects/e2e/cypress/it/does/not/exist] +[/foo/bar/.projects/e2e/cypress/it/does/not/exist.js] +[/foo/bar/.projects/e2e/cypress/it/does/not/exist.json] +[/foo/bar/.projects/e2e/cypress/it/does/not/exist.jsx] +[/foo/bar/.projects/e2e/cypress/it/does/not/exist.coffee] +[/foo/bar/.projects/e2e/cypress/it/does/not/exist.ts] +[/foo/bar/.projects/e2e/cypress/it/does/not/exist.tsx] + @ ./cypress/integration/record_error_spec.coffee 1:0-31 This occurred while Cypress was compiling and bundling your test code. This is usually caused by: @@ -962,7 +973,18 @@ Oops...we found an error preparing this test file: The error was: -Error: Cannot find module '../it/does/not/exist' from '/foo/bar/.projects/e2e/cypress/integration' +Error: Webpack Compilation Error +./cypress/integration/record_error_spec.coffee +Module not found: Error: Can't resolve '../it/does/not/exist' in '/foo/bar/.projects/e2e/cypress/integration' +Looked for and couldn't find the file at the following paths: +[/foo/bar/.projects/e2e/cypress/it/does/not/exist] +[/foo/bar/.projects/e2e/cypress/it/does/not/exist.js] +[/foo/bar/.projects/e2e/cypress/it/does/not/exist.json] +[/foo/bar/.projects/e2e/cypress/it/does/not/exist.jsx] +[/foo/bar/.projects/e2e/cypress/it/does/not/exist.coffee] +[/foo/bar/.projects/e2e/cypress/it/does/not/exist.ts] +[/foo/bar/.projects/e2e/cypress/it/does/not/exist.tsx] + @ ./cypress/integration/record_error_spec.coffee 1:0-31 This occurred while Cypress was compiling and bundling your test code. This is usually caused by: diff --git a/packages/server/lib/plugins/preprocessor.js b/packages/server/lib/plugins/preprocessor.js index 1007830688e5..5ea1ba77cca3 100644 --- a/packages/server/lib/plugins/preprocessor.js +++ b/packages/server/lib/plugins/preprocessor.js @@ -35,11 +35,12 @@ const baseEmitter = new EE() let fileObjects = {} let fileProcessors = {} -const createBrowserifyPreprocessor = function (options) { - debug('creating browserify preprocessor with options %o', options) - const browserify = require('@cypress/browserify-preprocessor') +const createPreprocessor = function (options) { + debug('creating webpack preprocessor with options %o', options) - return browserify(options) + const webpackPreprocessor = require('@cypress/webpack-batteries-included-preprocessor') + + return webpackPreprocessor(options) } const setDefaultPreprocessor = function (config) { @@ -51,7 +52,7 @@ const setDefaultPreprocessor = function (config) { typescript: tsPath, } - return plugins.register('file:preprocessor', API.createBrowserifyPreprocessor(options)) + return plugins.register('file:preprocessor', API.createPreprocessor(options)) } plugins.registerHandler((ipc) => { @@ -76,7 +77,7 @@ const API = { setDefaultPreprocessor, - createBrowserifyPreprocessor, + createPreprocessor, emitter: baseEmitter, diff --git a/packages/server/package.json b/packages/server/package.json index c897500faced..dd1141e14a65 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -21,18 +21,18 @@ }, "dependencies": { "@benmalka/foxdriver": "0.4.1", - "@cypress/browserify-preprocessor": "2.2.4", "@cypress/commit-info": "2.2.0", "@cypress/get-windows-proxy": "1.6.1", "@cypress/icons": "0.7.0", "@cypress/mocha-teamcity-reporter": "1.0.0", "@cypress/request": "2.88.5", "@cypress/request-promise": "4.2.6", + "@cypress/webpack-batteries-included-preprocessor": "2.0.0", + "@cypress/webpack-preprocessor": "5.4.3", "@ffmpeg-installer/ffmpeg": "1.0.20", "ansi_up": "4.0.4", "black-hole-stream": "0.0.1", "bluebird": "3.7.0", - "browserify": "16.3.0", "chai": "1.10.0", "chalk": "2.4.2", "check-more-types": "2.24.0", @@ -122,12 +122,7 @@ }, "devDependencies": { "@babel/core": "7.9.0", - "@babel/plugin-proposal-class-properties": "7.8.3", - "@babel/plugin-proposal-object-rest-spread": "7.9.0", - "@babel/plugin-transform-runtime": "7.9.0", "@babel/preset-env": "7.9.0", - "@babel/preset-react": "7.9.4", - "@babel/runtime": "7.9.2", "@cypress/debugging-proxy": "2.0.1", "@cypress/json-schemas": "5.34.2", "@cypress/sinon-chai": "1.1.0", @@ -147,15 +142,12 @@ "@types/chrome": "0.0.101", "awesome-typescript-loader": "5.2.1", "babel-loader": "8.1.0", - "babel-plugin-add-module-exports": "1.0.2", - "babelify": "10.0.0", "body-parser": "1.19.0", "chai-as-promised": "7.1.1", "chai-subset": "1.6.0", "chai-uuid": "1.0.6", "chokidar-cli": "1.2.2", "chrome-har-capturer": "0.13.4", - "coffeeify": "3.0.1", "cors": "2.8.5", "cross-env": "6.0.3", "devtools-protocol": "0.0.734984", @@ -181,7 +173,6 @@ "sinon": "5.1.1", "snap-shot-it": "7.9.3", "ssestream": "1.0.1", - "stream-to-promise": "1.1.1", "supertest": "4.0.2", "supertest-session": "4.0.0", "through2": "2.0.5", diff --git a/packages/server/test/e2e/1_busted_support_file_spec.js b/packages/server/test/e2e/1_busted_support_file_spec.js index 039a0a1fe24e..1a54dbbbe7c9 100644 --- a/packages/server/test/e2e/1_busted_support_file_spec.js +++ b/packages/server/test/e2e/1_busted_support_file_spec.js @@ -12,6 +12,7 @@ describe('e2e busted support file', () => { sanitizeScreenshotDimensions: true, snapshot: true, expectedExitCode: 1, + onStdout: e2e.normalizeWebpackErrors, }) }) }) diff --git a/packages/server/test/e2e/1_browserify_babel_es201_spec.js b/packages/server/test/e2e/1_es_modules_spec.js similarity index 65% rename from packages/server/test/e2e/1_browserify_babel_es201_spec.js rename to packages/server/test/e2e/1_es_modules_spec.js index 02d963be937e..cf5df3230e60 100644 --- a/packages/server/test/e2e/1_browserify_babel_es201_spec.js +++ b/packages/server/test/e2e/1_es_modules_spec.js @@ -1,11 +1,11 @@ const e2e = require('../support/helpers/e2e').default -describe('e2e browserify, babel, es2015', () => { +describe('e2e es modules', () => { e2e.setup() it('passes', function () { return e2e.exec(this, { - spec: 'browserify_babel_es2015_passing_spec.coffee', + spec: 'es_modules_in_coffee_spec.coffee', snapshot: true, noTypeScript: true, }) @@ -13,10 +13,11 @@ describe('e2e browserify, babel, es2015', () => { it('fails', function () { return e2e.exec(this, { - spec: 'browserify_babel_es2015_failing_spec.js', + spec: 'es_module_import_failing_spec.js', snapshot: true, expectedExitCode: 1, noTypeScript: true, + onStdout: e2e.normalizeWebpackErrors, }) }) }) diff --git a/packages/server/test/e2e/1_typescript_support_spec.ts b/packages/server/test/e2e/1_typescript_support_spec.ts index 3252ac68ba94..5007b65cfa41 100644 --- a/packages/server/test/e2e/1_typescript_support_spec.ts +++ b/packages/server/test/e2e/1_typescript_support_spec.ts @@ -8,16 +8,17 @@ describe('e2e typescript', function () { it('spec passes', function () { return e2e.exec(this, { - spec: 'browserify_typescript_passing_spec.ts', + spec: 'typescript_passing_spec.ts', snapshot: true, }) }) it('spec fails', function () { return e2e.exec(this, { - spec: 'browserify_typescript_failing_spec.ts', + spec: 'typescript_failing_spec.ts', snapshot: true, expectedExitCode: 1, + onStdout: e2e.normalizeWebpackErrors, }) }) @@ -30,6 +31,12 @@ describe('e2e typescript', function () { }) }) + it('respects tsconfig paths', function () { + return e2e.exec(this, { + project: Fixtures.projectPath('ts-proj-with-paths'), + }) + }) + it('handles tsconfig with module other than commonjs', function () { const projPath = Fixtures.projectPath('ts-proj-with-own-tsconfig') diff --git a/packages/server/test/e2e/4_controllers_spec.js b/packages/server/test/e2e/4_controllers_spec.js index d3d67403b412..06cef27690ee 100644 --- a/packages/server/test/e2e/4_controllers_spec.js +++ b/packages/server/test/e2e/4_controllers_spec.js @@ -20,14 +20,8 @@ describe('e2e plugins', () => { }) }) - it('handles specs with $, &, ?, + in file name', function () { - let relativeSpecPath = path.join('d?ir&1%', '%di?r2&', 's%p+ec&?.js') - - // windows doesn't support ? in file names - if (process.platform === 'win32') { - relativeSpecPath = specPath.replace(/\?/, '') - } - + it('handles specs with $, &, and + in file name', function () { + const relativeSpecPath = path.join('dir&1%', '%dir2&', 's%p+ec&.js') const specPath = path.join(e2eProject, 'cypress', 'integration', relativeSpecPath) return fs.outputFile(specPath, 'it(\'passes\', () => {})') diff --git a/packages/server/test/e2e/5_screenshots_spec.js b/packages/server/test/e2e/5_screenshots_spec.js index a5934ec1b605..3862dd90ddba 100644 --- a/packages/server/test/e2e/5_screenshots_spec.js +++ b/packages/server/test/e2e/5_screenshots_spec.js @@ -66,6 +66,7 @@ describe('e2e screenshots', () => { expectedExitCode: 4, snapshot: true, timeout: 180000, + onStdout: e2e.normalizeWebpackErrors, onRun (exec, browser) { return exec() .then(() => { diff --git a/packages/server/test/e2e/5_stdout_spec.js b/packages/server/test/e2e/5_stdout_spec.js index 969521025711..7e0f71e0f707 100644 --- a/packages/server/test/e2e/5_stdout_spec.js +++ b/packages/server/test/e2e/5_stdout_spec.js @@ -17,6 +17,7 @@ describe('e2e stdout', () => { spec: 'stdout_exit_early_failing_spec.js', snapshot: true, expectedExitCode: 1, + onStdout: e2e.normalizeWebpackErrors, }) }) diff --git a/packages/server/test/integration/http_requests_spec.js b/packages/server/test/integration/http_requests_spec.js index 851835bfa5a7..63c925783132 100644 --- a/packages/server/test/integration/http_requests_spec.js +++ b/packages/server/test/integration/http_requests_spec.js @@ -11,10 +11,6 @@ const path = require('path') const url = require('url') let zlib = require('zlib') const str = require('underscore.string') -const browserify = require('browserify') -const babelify = require('babelify') -const coffeeify = require('coffeeify') -const streamToPromise = require('stream-to-promise') const evilDns = require('evil-dns') const Promise = require('bluebird') const httpsServer = require(`${root}../https-proxy/test/helpers/https_server`) @@ -32,7 +28,6 @@ const fs = require(`${root}lib/util/fs`) const glob = require(`${root}lib/util/glob`) const CacheBuster = require(`${root}lib/util/cache_buster`) const Fixtures = require(`${root}test/support/helpers/fixtures`) -const simple_tsify = require(`${root}test/support/helpers/simple_tsify`) zlib = Promise.promisifyAll(zlib) @@ -46,10 +41,6 @@ const replaceAbsolutePaths = (content) => { return content.replace(absolutePathRegex, '"/') } -const removeSourceMap = (content) => { - return content.replace(sourceMapRegex, ';') -} - const removeWhitespace = function (c) { c = str.clean(c) c = str.lines(c).join(' ') @@ -61,36 +52,6 @@ const cleanResponseBody = (body) => { return replaceAbsolutePaths(removeWhitespace(body)) } -const browserifyFile = (filePath) => { - return streamToPromise( - browserify({ - entries: [filePath], - extensions: ['.js', '.jsx', '.coffee'], - cache: {}, - packageCache: {}, - transform: [ - [coffeeify, {}], - [babelify, { - plugins: ['add-module-exports', '@babel/plugin-proposal-class-properties', '@babel/plugin-proposal-object-rest-spread', '@babel/plugin-transform-runtime'], - presets: ['@babel/preset-env', '@babel/preset-react'], - }], - ], - }) - .bundle(), - ) -} - -const browserifyFileTs = (filePath) => { - return streamToPromise( - browserify(filePath) - .transform(coffeeify) - .transform(simple_tsify, { - typescript: require('typescript'), - }) - .bundle(), - ) -} - describe('Routes', () => { require('mocha-banner').register() @@ -605,22 +566,11 @@ describe('Routes', () => { }) }) - const checkTranspilation = function (body, file) { - const b = removeSourceMap(body).replace(/\n/g, '') - const f = file.toString().replace(/\n/g, '') - - expect(b).to.equal(f) - } - it('processes foo.coffee spec', function () { return this.rp('http://localhost:2020/__cypress/tests?p=cypress/integration/foo.coffee') .then((res) => { expect(res.statusCode).to.eq(200) - - return browserifyFileTs(Fixtures.path('projects/ids/cypress/integration/foo.coffee')) - .then((file) => { - return checkTranspilation(res.body, file) - }) + expect(res.body).to.include('expect("foo.coffee")') }) }) @@ -628,13 +578,7 @@ describe('Routes', () => { return this.rp('http://localhost:2020/__cypress/tests?p=cypress/integration/baz.js') .then((res) => { expect(res.statusCode).to.eq(200) - - return browserifyFileTs(Fixtures.path('projects/ids/cypress/integration/baz.js')) - .then((file) => { - checkTranspilation(res.body, file) - - expect(res.body).to.include('React.createElement(') - }) + expect(res.body).to.include('React.createElement(') }) }) @@ -643,8 +587,7 @@ describe('Routes', () => { .then((res) => { expect(res.statusCode).to.eq(200) expect(res.body).to.include('Cypress.action("spec:script:error", {') - - expect(res.body).to.include('Cannot find module') + expect(res.body).to.include('Module not found') }) }) }) @@ -653,8 +596,6 @@ describe('Routes', () => { beforeEach(function () { Fixtures.scaffold('ids') - // remove cached options - delete require.cache[require.resolve('@cypress/browserify-preprocessor')] sinon.stub(resolve, 'typescript').callsFake(() => { return null }) @@ -669,11 +610,7 @@ describe('Routes', () => { .then((res) => { expect(res.statusCode).to.eq(200) expect(res.body).to.match(sourceMapRegex) - - return browserifyFile(Fixtures.path('projects/ids/cypress/integration/foo.coffee')) - .then((file) => { - expect(removeSourceMap(res.body)).to.equal(file.toString()) - }) + expect(res.body).to.include('expect("foo.coffee")') }) }) @@ -682,13 +619,7 @@ describe('Routes', () => { .then((res) => { expect(res.statusCode).to.eq(200) expect(res.body).to.match(sourceMapRegex) - - return browserifyFile(Fixtures.path('projects/ids/cypress/integration/baz.js')) - .then((file) => { - expect(removeSourceMap(res.body)).to.equal(file.toString()) - - expect(res.body).to.include('React.createElement(') - }) + expect(res.body).to.include('React.createElement(') }) }) @@ -697,8 +628,7 @@ describe('Routes', () => { .then((res) => { expect(res.statusCode).to.eq(200) expect(res.body).to.include('Cypress.action("spec:script:error", {') - - expect(res.body).to.include('Cannot find module') + expect(res.body).to.include('Module not found') }) }) }) @@ -717,8 +647,7 @@ describe('Routes', () => { .then((res) => { expect(res.statusCode).to.eq(200) expect(res.body).to.include('Cypress.action("spec:script:error", {') - - expect(res.body).to.include('ParseError') + expect(res.body).to.include('Unexpected token') }) }) }) @@ -741,11 +670,7 @@ describe('Routes', () => { .then((res) => { expect(res.statusCode).to.eq(200) expect(res.body).to.match(sourceMapRegex) - - return browserifyFileTs(Fixtures.path('projects/no-server/my-tests/test1.js')) - .then((file) => { - expect(removeSourceMap(res.body)).to.equal(file.toString()) - }) + expect(res.body).to.include(`expect('no-server')`) }) }) @@ -754,11 +679,7 @@ describe('Routes', () => { .then((res) => { expect(res.statusCode).to.eq(200) expect(res.body).to.match(sourceMapRegex) - - return browserifyFileTs(Fixtures.path('projects/no-server/helpers/includes.js')) - .then((file) => { - expect(removeSourceMap(res.body)).to.equal(file.toString()) - }) + expect(res.body).to.include(`console.log('includes')`) }) }) }) 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/es_module_import_failing_spec.js similarity index 100% rename from packages/server/test/support/fixtures/projects/e2e/cypress/integration/browserify_babel_es2015_failing_spec.js rename to packages/server/test/support/fixtures/projects/e2e/cypress/integration/es_module_import_failing_spec.js diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/browserify_babel_es2015_passing_spec.coffee b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/es_modules_in_coffee_spec.coffee similarity index 100% rename from packages/server/test/support/fixtures/projects/e2e/cypress/integration/browserify_babel_es2015_passing_spec.coffee rename to packages/server/test/support/fixtures/projects/e2e/cypress/integration/es_modules_in_coffee_spec.coffee diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/browserify_typescript_failing_spec.ts b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/typescript_failing_spec.ts similarity index 76% rename from packages/server/test/support/fixtures/projects/e2e/cypress/integration/browserify_typescript_failing_spec.ts rename to packages/server/test/support/fixtures/projects/e2e/cypress/integration/typescript_failing_spec.ts index 09a8d2b75070..c476d7946782 100644 --- a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/browserify_typescript_failing_spec.ts +++ b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/typescript_failing_spec.ts @@ -1,3 +1,3 @@ // The code below is ignored by eslint // because it tests failing spec. -describe('fail', -> ) \ No newline at end of file +describe('fail', -> ) diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/browserify_typescript_passing_spec.ts b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/typescript_passing_spec.ts similarity index 100% rename from packages/server/test/support/fixtures/projects/e2e/cypress/integration/browserify_typescript_passing_spec.ts rename to packages/server/test/support/fixtures/projects/e2e/cypress/integration/typescript_passing_spec.ts diff --git a/packages/server/test/support/fixtures/projects/ids/cypress/integration/foo.coffee b/packages/server/test/support/fixtures/projects/ids/cypress/integration/foo.coffee index 74a9e8deb45e..f8e373a2222c 100644 --- a/packages/server/test/support/fixtures/projects/ids/cypress/integration/foo.coffee +++ b/packages/server/test/support/fixtures/projects/ids/cypress/integration/foo.coffee @@ -1,4 +1,5 @@ describe "foo [000]", -> it "bars [001]", -> + expect("foo.coffee").to.equal("foo.coffee") - it 'quux [002]' \ No newline at end of file + it 'quux [002]' 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 81969706f702..610c29484b78 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 () { - + console.log('includes') }) 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 e3a5395eee36..1ff93d7bd5e4 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,4 +1,4 @@ /* eslint-disable mocha/no-global-tests */ it('tests without a server', function () { - + expect('no-server').to.equal('no-server') }) diff --git a/packages/server/test/support/fixtures/projects/ts-proj-with-paths/cypress.json b/packages/server/test/support/fixtures/projects/ts-proj-with-paths/cypress.json new file mode 100644 index 000000000000..be9e6bf4576b --- /dev/null +++ b/packages/server/test/support/fixtures/projects/ts-proj-with-paths/cypress.json @@ -0,0 +1,4 @@ +{ + "supportFile": false, + "pluginsFile": false +} diff --git a/packages/server/test/support/fixtures/projects/ts-proj-with-paths/cypress/integration/app_spec.ts b/packages/server/test/support/fixtures/projects/ts-proj-with-paths/cypress/integration/app_spec.ts new file mode 100644 index 000000000000..af091afe0536 --- /dev/null +++ b/packages/server/test/support/fixtures/projects/ts-proj-with-paths/cypress/integration/app_spec.ts @@ -0,0 +1,6 @@ +import { expect } from 'chai' +import { appName } from '@app/main' + +it('verifies path mapping', () => { + expect(appName).to.equal('Best App Ever') +}) diff --git a/packages/server/test/support/fixtures/projects/ts-proj-with-paths/src/main.ts b/packages/server/test/support/fixtures/projects/ts-proj-with-paths/src/main.ts new file mode 100644 index 000000000000..8e774ef932a0 --- /dev/null +++ b/packages/server/test/support/fixtures/projects/ts-proj-with-paths/src/main.ts @@ -0,0 +1 @@ +export const appName = 'Best App Ever' diff --git a/packages/server/test/support/fixtures/projects/ts-proj-with-paths/tsconfig.json b/packages/server/test/support/fixtures/projects/ts-proj-with-paths/tsconfig.json new file mode 100644 index 000000000000..9da0a51cf8c5 --- /dev/null +++ b/packages/server/test/support/fixtures/projects/ts-proj-with-paths/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@app/*": ["src/*"] + }, + } +} diff --git a/packages/server/test/support/helpers/e2e.ts b/packages/server/test/support/helpers/e2e.ts index 2630569b8dc2..7ceb190cd7b3 100644 --- a/packages/server/test/support/helpers/e2e.ts +++ b/packages/server/test/support/helpers/e2e.ts @@ -753,6 +753,12 @@ const e2e = { `) } }, + + normalizeWebpackErrors (stdout) { + return stdout + .replace(/using description file: .* \(relative/g, 'using description file: [..] (relative') + .replace(/Module build failed \(from .*\)/g, 'Module build failed (from [..])') + }, } export { diff --git a/packages/server/test/support/helpers/simple_tsify.js b/packages/server/test/support/helpers/simple_tsify.js deleted file mode 100644 index 450c3dee010c..000000000000 --- a/packages/server/test/support/helpers/simple_tsify.js +++ /dev/null @@ -1,45 +0,0 @@ -// Copied from cypress-browserify-preprocessor for unit tests. - -let through = require('through2') - -const isJson = (code) => { - try { - JSON.parse(code) - } catch (e) { - return false - } - - return true -} - -// tsify doesn't have transpile-only option like ts-node or ts-loader. -// It means it should check types whenever spec file is changed -// and it slows down the test speed a lot. -// We skip this slow type-checking process by using transpileModule() api. -module.exports = function (b, opts) { - const chunks = [] - - return through( - (buf, enc, next) => { - chunks.push(buf.toString()) - next() - }, - function (next) { - const ts = opts.typescript - const text = chunks.join('') - - if (isJson(text)) { - this.push(text) - } else { - this.push(ts.transpileModule(text, { - compilerOptions: { - esModuleInterop: true, - jsx: 'react', - }, - }).outputText) - } - - next() - }, - ) -} diff --git a/packages/server/test/unit/plugins/preprocessor_spec.js b/packages/server/test/unit/plugins/preprocessor_spec.js index fd1b923c8d47..6f2da7c9b47d 100644 --- a/packages/server/test/unit/plugins/preprocessor_spec.js +++ b/packages/server/test/unit/plugins/preprocessor_spec.js @@ -89,28 +89,18 @@ describe('lib/plugins/preprocessor', () => { plugins._reset() sinon.stub(plugins, 'register') sinon.stub(plugins, 'execute').returns(() => {}) - const browserifyFn = function () {} - const browserify = sinon.stub().returns(browserifyFn) - - // mock default options - browserify.defaultOptions = { - browserifyOptions: { - extensions: [], - transform: [ - [], - ['babelify', { - presets: [], - extensions: [], - }], - ], - }, - } - mockery.registerMock('@cypress/browserify-preprocessor', browserify) + const userPreprocessorFn = function () {} + const userPreprocessor = sinon.stub().returns(userPreprocessorFn) + + userPreprocessor.defaultOptions = {} + + mockery.registerMock('@cypress/webpack-batteries-included-preprocessor', userPreprocessor) + preprocessor.getFile(this.filePath, this.config) - expect(plugins.register).to.be.calledWith('file:preprocessor', browserifyFn) - expect(browserify).to.be.called + expect(plugins.register).to.be.calledWith('file:preprocessor', userPreprocessorFn) + expect(userPreprocessor).to.be.called }) }) @@ -224,7 +214,7 @@ describe('lib/plugins/preprocessor', () => { const mockPlugin = {} sinon.stub(plugins, 'register') - sinon.stub(preprocessor, 'createBrowserifyPreprocessor').returns(mockPlugin) + sinon.stub(preprocessor, 'createPreprocessor').returns(mockPlugin) preprocessor.setDefaultPreprocessor(this.config) @@ -236,14 +226,14 @@ describe('lib/plugins/preprocessor', () => { basedir: monorepoRoot, }) - expect(preprocessor.createBrowserifyPreprocessor).to.be.calledWith({ typescript }) + expect(preprocessor.createPreprocessor).to.be.calledWith({ typescript }) }) it('does not have typescript if not found', function () { const mockPlugin = {} sinon.stub(plugins, 'register') - sinon.stub(preprocessor, 'createBrowserifyPreprocessor').returns(mockPlugin) + sinon.stub(preprocessor, 'createPreprocessor').returns(mockPlugin) sinon.stub(resolve, 'sync') .withArgs('typescript', { basedir: this.todosPath }) .throws(new Error('TypeScript not found')) @@ -252,7 +242,7 @@ describe('lib/plugins/preprocessor', () => { expect(plugins.register).to.be.calledWithExactly('file:preprocessor', mockPlugin) - expect(preprocessor.createBrowserifyPreprocessor).to.be.calledWith({ typescript: null }) + expect(preprocessor.createPreprocessor).to.be.calledWith({ typescript: null }) }) }) }) diff --git a/yarn.lock b/yarn.lock index 16e8ac465fbb..9325e5545a2b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -51,6 +51,28 @@ invariant "^2.2.4" semver "^5.5.0" +"@babel/core@7.10.5": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.10.5.tgz#1f15e2cca8ad9a1d78a38ddba612f5e7cdbbd330" + integrity sha512-O34LQooYVDXPl7QWCdW9p4NR+QlzOr7xShPPJz8GsuCU3/8ua/wqTr7gmnxXv+WBESiGU/G5s16i6tUvHkNb+w== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.10.5" + "@babel/helper-module-transforms" "^7.10.5" + "@babel/helpers" "^7.10.4" + "@babel/parser" "^7.10.5" + "@babel/template" "^7.10.4" + "@babel/traverse" "^7.10.5" + "@babel/types" "^7.10.5" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.19" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + "@babel/core@7.4.5": version "7.4.5" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.4.5.tgz#081f97e8ffca65a9b4b0fdc7e274e703f000c06a" @@ -125,6 +147,15 @@ lodash "^4.17.13" source-map "^0.5.0" +"@babel/generator@^7.10.5": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.10.5.tgz#1b903554bc8c583ee8d25f1e8969732e6b829a69" + integrity sha512-3vXxr3FEW7E7lJZiWQ3bM4+v/Vyr9C+hpolQ8BGFr9Y8Ri2tFLWTixmwKBafDujO1WVah4fhZBeU1bieKdghig== + dependencies: + "@babel/types" "^7.10.5" + jsesc "^2.5.1" + source-map "^0.5.0" + "@babel/helper-annotate-as-pure@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz#5bf0d495a3f757ac3bda48b5bf3b3ba309c72ba3" @@ -256,6 +287,19 @@ "@babel/types" "^7.10.4" lodash "^4.17.13" +"@babel/helper-module-transforms@^7.10.5": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.10.5.tgz#120c271c0b3353673fcdfd8c053db3c544a260d6" + integrity sha512-4P+CWMJ6/j1W915ITJaUkadLObmCRRSC234uctJfn/vHrsLNxsR8dwlcXv9ZhJWzl77awf+mWXSZEKt5t0OnlA== + dependencies: + "@babel/helper-module-imports" "^7.10.4" + "@babel/helper-replace-supers" "^7.10.4" + "@babel/helper-simple-access" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/types" "^7.10.5" + lodash "^4.17.19" + "@babel/helper-optimise-call-expression@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz#50dc96413d594f995a77905905b05893cd779673" @@ -349,6 +393,11 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.4.tgz#9eedf27e1998d87739fb5028a5120557c06a1a64" integrity sha512-8jHII4hf+YVDsskTF6WuMB3X4Eh+PsUkC2ljq22so5rHvH+T8BzyL94VOdyFLNR8tBSVXOTbNHOKpR4TfRxVtA== +"@babel/parser@^7.10.5": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.5.tgz#e7c6bf5a7deff957cec9f04b551e2762909d826b" + integrity sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ== + "@babel/plugin-proposal-async-generator-functions@^7.10.4", "@babel/plugin-proposal-async-generator-functions@^7.2.0", "@babel/plugin-proposal-async-generator-functions@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.4.tgz#4b65abb3d9bacc6c657aaa413e56696f9f170fc6" @@ -358,6 +407,14 @@ "@babel/helper-remap-async-to-generator" "^7.10.4" "@babel/plugin-syntax-async-generators" "^7.8.0" +"@babel/plugin-proposal-class-properties@7.10.4", "@babel/plugin-proposal-class-properties@^7.1.0", "@babel/plugin-proposal-class-properties@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz#a33bf632da390a59c7a8c570045d1115cd778807" + integrity sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-proposal-class-properties@7.3.0": version "7.3.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.3.0.tgz#272636bc0fa19a0bc46e601ec78136a173ea36cd" @@ -374,14 +431,6 @@ "@babel/helper-create-class-features-plugin" "^7.8.3" "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-proposal-class-properties@^7.1.0", "@babel/plugin-proposal-class-properties@^7.10.4": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz#a33bf632da390a59c7a8c570045d1115cd778807" - integrity sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-proposal-decorators@7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.8.3.tgz#2156860ab65c5abf068c3f67042184041066543e" @@ -423,6 +472,15 @@ "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-numeric-separator" "^7.10.4" +"@babel/plugin-proposal-object-rest-spread@7.10.4", "@babel/plugin-proposal-object-rest-spread@^7.0.0", "@babel/plugin-proposal-object-rest-spread@^7.10.4", "@babel/plugin-proposal-object-rest-spread@^7.4.4", "@babel/plugin-proposal-object-rest-spread@^7.9.0", "@babel/plugin-proposal-object-rest-spread@^7.9.5": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.4.tgz#50129ac216b9a6a55b3853fdd923e74bf553a4c0" + integrity sha512-6vh4SqRuLLarjgeOf4EaROJAHjvu9Gl+/346PbDH9yWbJyfnJ/ah3jmYKYtswEyCoWZiidvVHjHshd4WgjB9BA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-transform-parameters" "^7.10.4" + "@babel/plugin-proposal-object-rest-spread@7.3.2": version "7.3.2" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.3.2.tgz#6d1859882d4d778578e41f82cc5d7bf3d5daf6c1" @@ -439,15 +497,6 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-object-rest-spread" "^7.8.0" -"@babel/plugin-proposal-object-rest-spread@^7.0.0", "@babel/plugin-proposal-object-rest-spread@^7.10.4", "@babel/plugin-proposal-object-rest-spread@^7.4.4", "@babel/plugin-proposal-object-rest-spread@^7.9.0", "@babel/plugin-proposal-object-rest-spread@^7.9.5": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.10.4.tgz#50129ac216b9a6a55b3853fdd923e74bf553a4c0" - integrity sha512-6vh4SqRuLLarjgeOf4EaROJAHjvu9Gl+/346PbDH9yWbJyfnJ/ah3jmYKYtswEyCoWZiidvVHjHshd4WgjB9BA== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.0" - "@babel/plugin-transform-parameters" "^7.10.4" - "@babel/plugin-proposal-optional-catch-binding@^7.10.4", "@babel/plugin-proposal-optional-catch-binding@^7.2.0", "@babel/plugin-proposal-optional-catch-binding@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz#31c938309d24a78a49d68fdabffaa863758554dd" @@ -781,14 +830,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-react-display-name@^7.0.0", "@babel/plugin-transform-react-display-name@^7.8.3": +"@babel/plugin-transform-react-display-name@^7.0.0", "@babel/plugin-transform-react-display-name@^7.10.4", "@babel/plugin-transform-react-display-name@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.10.4.tgz#b5795f4e3e3140419c3611b7a2a3832b9aef328d" integrity sha512-Zd4X54Mu9SBfPGnEcaGcOrVAYOtjT2on8QZkLKEq1S/tHexG39d9XXGZv19VfRrDjPJzFmPfTAqOQS1pfFOujw== dependencies: "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-transform-react-jsx-development@^7.9.0": +"@babel/plugin-transform-react-jsx-development@^7.10.4", "@babel/plugin-transform-react-jsx-development@^7.9.0": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.10.4.tgz#6ec90f244394604623880e15ebc3c34c356258ba" integrity sha512-RM3ZAd1sU1iQ7rI2dhrZRZGv0aqzNQMbkIUCS1txYpi9wHQ2ZHNjo5TwX+UD6pvFW4AbWqLVYvKy5qJSAyRGjQ== @@ -797,7 +846,7 @@ "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-jsx" "^7.10.4" -"@babel/plugin-transform-react-jsx-self@^7.0.0", "@babel/plugin-transform-react-jsx-self@^7.9.0": +"@babel/plugin-transform-react-jsx-self@^7.0.0", "@babel/plugin-transform-react-jsx-self@^7.10.4", "@babel/plugin-transform-react-jsx-self@^7.9.0": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.10.4.tgz#cd301a5fed8988c182ed0b9d55e9bd6db0bd9369" integrity sha512-yOvxY2pDiVJi0axdTWHSMi5T0DILN+H+SaeJeACHKjQLezEzhLx9nEF9xgpBLPtkZsks9cnb5P9iBEi21En3gg== @@ -813,7 +862,15 @@ "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-jsx" "^7.10.4" -"@babel/plugin-transform-react-jsx@^7.0.0", "@babel/plugin-transform-react-jsx@^7.9.4": +"@babel/plugin-transform-react-jsx-source@^7.10.4": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.10.5.tgz#34f1779117520a779c054f2cdd9680435b9222b4" + integrity sha512-wTeqHVkN1lfPLubRiZH3o73f4rfon42HpgxUSs86Nc+8QIcm/B9s8NNVXu/gwGcOyd7yDib9ikxoDLxJP0UiDA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-jsx" "^7.10.4" + +"@babel/plugin-transform-react-jsx@^7.0.0", "@babel/plugin-transform-react-jsx@^7.10.4", "@babel/plugin-transform-react-jsx@^7.9.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.10.4.tgz#673c9f913948764a4421683b2bef2936968fddf2" integrity sha512-L+MfRhWjX0eI7Js093MM6MacKU4M6dnCRa/QPDwYMxjljzSCzzlzKzj9Pk4P3OtrPcxr2N3znR419nr3Xw+65A== @@ -823,6 +880,14 @@ "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-jsx" "^7.10.4" +"@babel/plugin-transform-react-pure-annotations@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.10.4.tgz#3eefbb73db94afbc075f097523e445354a1c6501" + integrity sha512-+njZkqcOuS8RaPakrnR9KvxjoG1ASJWpoIv/doyWngId88JoFlPlISenGXjrVacZUIALGUr6eodRs1vmPnF23A== + dependencies: + "@babel/helper-annotate-as-pure" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-regenerator@^7.10.4", "@babel/plugin-transform-regenerator@^7.4.5", "@babel/plugin-transform-regenerator@^7.8.7": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz#2015e59d839074e76838de2159db421966fd8b63" @@ -837,6 +902,16 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" +"@babel/plugin-transform-runtime@7.10.5": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.10.5.tgz#3b39b7b24830e0c2d8ff7a4489fe5cf99fbace86" + integrity sha512-tV4V/FjElJ9lQtyjr5xD2IFFbgY46r7EeVu5a8CpEKT5laheHKSlFeHjpkPppW3PqzGLAuv5k2qZX5LgVZIX5w== + dependencies: + "@babel/helper-module-imports" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + resolve "^1.8.1" + semver "^5.5.1" + "@babel/plugin-transform-runtime@7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.2.0.tgz#566bc43f7d0aedc880eaddbd29168d0f248966ea" @@ -847,23 +922,20 @@ resolve "^1.8.1" semver "^5.5.1" -"@babel/plugin-transform-runtime@7.9.0": - version "7.9.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.9.0.tgz#45468c0ae74cc13204e1d3b1f4ce6ee83258af0b" - integrity sha512-pUu9VSf3kI1OqbWINQ7MaugnitRss1z533436waNXp+0N3ur3zfut37sXiQMxkuCF4VUjwZucen/quskCh7NHw== - dependencies: - "@babel/helper-module-imports" "^7.8.3" - "@babel/helper-plugin-utils" "^7.8.3" - resolve "^1.8.1" - semver "^5.5.1" - -"@babel/plugin-transform-shorthand-properties@^7.10.4", "@babel/plugin-transform-shorthand-properties@^7.2.0", "@babel/plugin-transform-shorthand-properties@^7.8.3": +"@babel/plugin-transform-shorthand-properties@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz#9fd25ec5cdd555bb7f473e5e6ee1c971eede4dd6" integrity sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q== dependencies: "@babel/helper-plugin-utils" "^7.10.4" +"@babel/plugin-transform-shorthand-properties@^7.2.0", "@babel/plugin-transform-shorthand-properties@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz#28545216e023a832d4d3a1185ed492bcfeac08c8" + integrity sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-transform-spread@^7.10.4", "@babel/plugin-transform-spread@^7.2.0", "@babel/plugin-transform-spread@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.10.4.tgz#4e2c85ea0d6abaee1b24dcfbbae426fe8d674cff" @@ -926,6 +998,76 @@ core-js "^2.6.5" regenerator-runtime "^0.13.4" +"@babel/preset-env@7.10.4", "@babel/preset-env@^7.1.6": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.10.4.tgz#fbf57f9a803afd97f4f32e4f798bb62e4b2bef5f" + integrity sha512-tcmuQ6vupfMZPrLrc38d0sF2OjLT3/bZ0dry5HchNCQbrokoQi4reXqclvkkAT5b+gWc23meVWpve5P/7+w/zw== + dependencies: + "@babel/compat-data" "^7.10.4" + "@babel/helper-compilation-targets" "^7.10.4" + "@babel/helper-module-imports" "^7.10.4" + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-proposal-async-generator-functions" "^7.10.4" + "@babel/plugin-proposal-class-properties" "^7.10.4" + "@babel/plugin-proposal-dynamic-import" "^7.10.4" + "@babel/plugin-proposal-json-strings" "^7.10.4" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.10.4" + "@babel/plugin-proposal-numeric-separator" "^7.10.4" + "@babel/plugin-proposal-object-rest-spread" "^7.10.4" + "@babel/plugin-proposal-optional-catch-binding" "^7.10.4" + "@babel/plugin-proposal-optional-chaining" "^7.10.4" + "@babel/plugin-proposal-private-methods" "^7.10.4" + "@babel/plugin-proposal-unicode-property-regex" "^7.10.4" + "@babel/plugin-syntax-async-generators" "^7.8.0" + "@babel/plugin-syntax-class-properties" "^7.10.4" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + "@babel/plugin-syntax-json-strings" "^7.8.0" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + "@babel/plugin-syntax-top-level-await" "^7.10.4" + "@babel/plugin-transform-arrow-functions" "^7.10.4" + "@babel/plugin-transform-async-to-generator" "^7.10.4" + "@babel/plugin-transform-block-scoped-functions" "^7.10.4" + "@babel/plugin-transform-block-scoping" "^7.10.4" + "@babel/plugin-transform-classes" "^7.10.4" + "@babel/plugin-transform-computed-properties" "^7.10.4" + "@babel/plugin-transform-destructuring" "^7.10.4" + "@babel/plugin-transform-dotall-regex" "^7.10.4" + "@babel/plugin-transform-duplicate-keys" "^7.10.4" + "@babel/plugin-transform-exponentiation-operator" "^7.10.4" + "@babel/plugin-transform-for-of" "^7.10.4" + "@babel/plugin-transform-function-name" "^7.10.4" + "@babel/plugin-transform-literals" "^7.10.4" + "@babel/plugin-transform-member-expression-literals" "^7.10.4" + "@babel/plugin-transform-modules-amd" "^7.10.4" + "@babel/plugin-transform-modules-commonjs" "^7.10.4" + "@babel/plugin-transform-modules-systemjs" "^7.10.4" + "@babel/plugin-transform-modules-umd" "^7.10.4" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.10.4" + "@babel/plugin-transform-new-target" "^7.10.4" + "@babel/plugin-transform-object-super" "^7.10.4" + "@babel/plugin-transform-parameters" "^7.10.4" + "@babel/plugin-transform-property-literals" "^7.10.4" + "@babel/plugin-transform-regenerator" "^7.10.4" + "@babel/plugin-transform-reserved-words" "^7.10.4" + "@babel/plugin-transform-shorthand-properties" "^7.10.4" + "@babel/plugin-transform-spread" "^7.10.4" + "@babel/plugin-transform-sticky-regex" "^7.10.4" + "@babel/plugin-transform-template-literals" "^7.10.4" + "@babel/plugin-transform-typeof-symbol" "^7.10.4" + "@babel/plugin-transform-unicode-escapes" "^7.10.4" + "@babel/plugin-transform-unicode-regex" "^7.10.4" + "@babel/preset-modules" "^0.1.3" + "@babel/types" "^7.10.4" + browserslist "^4.12.0" + core-js-compat "^3.6.2" + invariant "^2.2.2" + levenary "^1.1.1" + semver "^5.5.0" + "@babel/preset-env@7.4.5": version "7.4.5" resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.4.5.tgz#2fad7f62983d5af563b5f3139242755884998a58" @@ -1112,76 +1254,6 @@ levenary "^1.1.1" semver "^5.5.0" -"@babel/preset-env@^7.1.6": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.10.4.tgz#fbf57f9a803afd97f4f32e4f798bb62e4b2bef5f" - integrity sha512-tcmuQ6vupfMZPrLrc38d0sF2OjLT3/bZ0dry5HchNCQbrokoQi4reXqclvkkAT5b+gWc23meVWpve5P/7+w/zw== - dependencies: - "@babel/compat-data" "^7.10.4" - "@babel/helper-compilation-targets" "^7.10.4" - "@babel/helper-module-imports" "^7.10.4" - "@babel/helper-plugin-utils" "^7.10.4" - "@babel/plugin-proposal-async-generator-functions" "^7.10.4" - "@babel/plugin-proposal-class-properties" "^7.10.4" - "@babel/plugin-proposal-dynamic-import" "^7.10.4" - "@babel/plugin-proposal-json-strings" "^7.10.4" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.10.4" - "@babel/plugin-proposal-numeric-separator" "^7.10.4" - "@babel/plugin-proposal-object-rest-spread" "^7.10.4" - "@babel/plugin-proposal-optional-catch-binding" "^7.10.4" - "@babel/plugin-proposal-optional-chaining" "^7.10.4" - "@babel/plugin-proposal-private-methods" "^7.10.4" - "@babel/plugin-proposal-unicode-property-regex" "^7.10.4" - "@babel/plugin-syntax-async-generators" "^7.8.0" - "@babel/plugin-syntax-class-properties" "^7.10.4" - "@babel/plugin-syntax-dynamic-import" "^7.8.0" - "@babel/plugin-syntax-json-strings" "^7.8.0" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.0" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" - "@babel/plugin-syntax-optional-chaining" "^7.8.0" - "@babel/plugin-syntax-top-level-await" "^7.10.4" - "@babel/plugin-transform-arrow-functions" "^7.10.4" - "@babel/plugin-transform-async-to-generator" "^7.10.4" - "@babel/plugin-transform-block-scoped-functions" "^7.10.4" - "@babel/plugin-transform-block-scoping" "^7.10.4" - "@babel/plugin-transform-classes" "^7.10.4" - "@babel/plugin-transform-computed-properties" "^7.10.4" - "@babel/plugin-transform-destructuring" "^7.10.4" - "@babel/plugin-transform-dotall-regex" "^7.10.4" - "@babel/plugin-transform-duplicate-keys" "^7.10.4" - "@babel/plugin-transform-exponentiation-operator" "^7.10.4" - "@babel/plugin-transform-for-of" "^7.10.4" - "@babel/plugin-transform-function-name" "^7.10.4" - "@babel/plugin-transform-literals" "^7.10.4" - "@babel/plugin-transform-member-expression-literals" "^7.10.4" - "@babel/plugin-transform-modules-amd" "^7.10.4" - "@babel/plugin-transform-modules-commonjs" "^7.10.4" - "@babel/plugin-transform-modules-systemjs" "^7.10.4" - "@babel/plugin-transform-modules-umd" "^7.10.4" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.10.4" - "@babel/plugin-transform-new-target" "^7.10.4" - "@babel/plugin-transform-object-super" "^7.10.4" - "@babel/plugin-transform-parameters" "^7.10.4" - "@babel/plugin-transform-property-literals" "^7.10.4" - "@babel/plugin-transform-regenerator" "^7.10.4" - "@babel/plugin-transform-reserved-words" "^7.10.4" - "@babel/plugin-transform-shorthand-properties" "^7.10.4" - "@babel/plugin-transform-spread" "^7.10.4" - "@babel/plugin-transform-sticky-regex" "^7.10.4" - "@babel/plugin-transform-template-literals" "^7.10.4" - "@babel/plugin-transform-typeof-symbol" "^7.10.4" - "@babel/plugin-transform-unicode-escapes" "^7.10.4" - "@babel/plugin-transform-unicode-regex" "^7.10.4" - "@babel/preset-modules" "^0.1.3" - "@babel/types" "^7.10.4" - browserslist "^4.12.0" - core-js-compat "^3.6.2" - invariant "^2.2.2" - levenary "^1.1.1" - semver "^5.5.0" - "@babel/preset-flow@^7.0.0": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.10.4.tgz#e0d9c72f8cb02d1633f6a5b7b16763aa2edf659f" @@ -1212,6 +1284,19 @@ "@babel/plugin-transform-react-jsx-self" "^7.0.0" "@babel/plugin-transform-react-jsx-source" "^7.0.0" +"@babel/preset-react@7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.10.4.tgz#92e8a66d816f9911d11d4cc935be67adfc82dbcf" + integrity sha512-BrHp4TgOIy4M19JAfO1LhycVXOPWdDbTRep7eVyatf174Hff+6Uk53sDyajqZPu8W1qXRBiYOfIamek6jA7YVw== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-transform-react-display-name" "^7.10.4" + "@babel/plugin-transform-react-jsx" "^7.10.4" + "@babel/plugin-transform-react-jsx-development" "^7.10.4" + "@babel/plugin-transform-react-jsx-self" "^7.10.4" + "@babel/plugin-transform-react-jsx-source" "^7.10.4" + "@babel/plugin-transform-react-pure-annotations" "^7.10.4" + "@babel/preset-react@7.9.4": version "7.9.4" resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.9.4.tgz#c6c97693ac65b6b9c0b4f25b948a8f665463014d" @@ -1262,6 +1347,13 @@ pirates "^4.0.0" source-map-support "^0.5.16" +"@babel/runtime@7.10.5", "@babel/runtime@^7.10.2": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.5.tgz#303d8bd440ecd5a491eae6117fd3367698674c5c" + integrity sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/runtime@7.3.1": version "7.3.1" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.3.1.tgz#574b03e8e8a9898eaf4a872a92ea20b7846f6f2a" @@ -1269,17 +1361,10 @@ dependencies: regenerator-runtime "^0.12.0" -"@babel/runtime@7.9.2": - version "7.9.2" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06" - integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q== - dependencies: - regenerator-runtime "^0.13.4" - -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.4.tgz#a6724f1a6b8d2f6ea5236dbfe58c7d7ea9c5eb99" - integrity sha512-UpTN5yUJr9b4EX2CnGNWIvER7Ab83ibv0pcvvHc4UOdrBI5jb8bj+32cCwPX6xu0mt2daFNjYhoi+X7beH0RSw== +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": + version "7.9.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.6.tgz#a9102eb5cadedf3f31d08a9ecf294af7827ea29f" + integrity sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ== dependencies: regenerator-runtime "^0.13.4" @@ -1307,6 +1392,21 @@ globals "^11.1.0" lodash "^4.17.13" +"@babel/traverse@^7.10.5": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.10.5.tgz#77ce464f5b258be265af618d8fddf0536f20b564" + integrity sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.10.5" + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.10.4" + "@babel/parser" "^7.10.5" + "@babel/types" "^7.10.5" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.19" + "@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.6.1", "@babel/types@^7.7.0", "@babel/types@^7.9.0", "@babel/types@^7.9.5": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.4.tgz#369517188352e18219981efd156bfdb199fff1ee" @@ -1316,6 +1416,15 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" +"@babel/types@^7.10.5": + version "7.10.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.5.tgz#d88ae7e2fde86bfbfe851d4d81afa70a997b5d15" + integrity sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q== + dependencies: + "@babel/helper-validator-identifier" "^7.10.4" + lodash "^4.17.19" + to-fast-properties "^2.0.0" + "@bahmutov/all-paths@1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@bahmutov/all-paths/-/all-paths-1.0.2.tgz#9ae0dcdf9022dd6e5e14d7fda3479e6a330d035b" @@ -1391,30 +1500,6 @@ resolved "https://registry.yarnpkg.com/@cypress/bower-kendo-ui/-/bower-kendo-ui-0.0.2.tgz#62ea93d7f0653c0b91a7a4e5e9ede9d26d5990ea" integrity sha1-YuqT1/BlPAuRp6Tl6e3p0m1ZkOo= -"@cypress/browserify-preprocessor@2.2.4": - version "2.2.4" - resolved "https://registry.yarnpkg.com/@cypress/browserify-preprocessor/-/browserify-preprocessor-2.2.4.tgz#a2ff5a6a06938f7f1e78eb6de0e492641cf8561c" - integrity sha512-kMjkIFe6qka8Tkm9N3BrMB+Nn7WEAHIzEd3gfVoDL17Tr40xyOnKGuMhEkff1scd3RV3bjQxwQ9BQ6kI2nToAQ== - dependencies: - "@babel/core" "7.4.5" - "@babel/plugin-proposal-class-properties" "7.3.0" - "@babel/plugin-proposal-object-rest-spread" "7.3.2" - "@babel/plugin-transform-runtime" "7.2.0" - "@babel/preset-env" "7.4.5" - "@babel/preset-react" "7.0.0" - "@babel/runtime" "7.3.1" - babel-plugin-add-module-exports "1.0.2" - babelify "10.0.0" - bluebird "3.5.3" - browserify "16.2.3" - coffeeify "3.0.1" - coffeescript "1.12.7" - debug "4.1.1" - fs-extra "7.0.1" - lodash.clonedeep "4.5.0" - through2 "^2.0.0" - watchify "3.11.1" - "@cypress/browserify-preprocessor@3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@cypress/browserify-preprocessor/-/browserify-preprocessor-3.0.0.tgz#2d1fa6a96ed7130a1b172c540448a5955cbc1264" @@ -1689,6 +1774,27 @@ dependencies: css.escape "^1.5.1" +"@cypress/webpack-batteries-included-preprocessor@2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@cypress/webpack-batteries-included-preprocessor/-/webpack-batteries-included-preprocessor-2.0.0.tgz#b64883e0bd6fcb5433211a547179a76581e45cb3" + integrity sha512-0LNQ7bhKVDDTF/P5FIP7FQrEbiRSwcWv4Qur7fEQoSHs+06pu0HYtNhIT83nG1X2BCIRmIqGvZGiuJ4ABZrT0w== + dependencies: + "@babel/core" "7.10.5" + "@babel/plugin-proposal-class-properties" "7.10.4" + "@babel/plugin-proposal-object-rest-spread" "7.10.4" + "@babel/plugin-transform-runtime" "7.10.5" + "@babel/preset-env" "7.10.4" + "@babel/preset-react" "7.10.4" + "@babel/runtime" "7.10.5" + babel-loader "8.1.0" + babel-plugin-add-module-exports "1.0.2" + coffee-loader "0.9.0" + coffeescript "1.12.7" + ts-loader "8.0.1" + tsconfig "7.0.0" + tsconfig-paths-webpack-plugin "3.2.0" + webpack "4.43.0" + "@cypress/webpack-preprocessor@5.4.1": version "5.4.1" resolved "https://registry.yarnpkg.com/@cypress/webpack-preprocessor/-/webpack-preprocessor-5.4.1.tgz#eb58f6cd02932a95653c1a674cfd769da2409806" @@ -1707,6 +1813,15 @@ debug "4.1.1" lodash "4.17.19" +"@cypress/webpack-preprocessor@5.4.3": + version "5.4.3" + resolved "https://registry.yarnpkg.com/@cypress/webpack-preprocessor/-/webpack-preprocessor-5.4.3.tgz#16cd450f15a7e95523a4e4e48b7ced3e318c4957" + integrity sha512-WomyLH7mtCkRpN6unHUfZcX/tl8lfL3MqSniSvlg6wTtQLmlYM3VPDUQjZ4mywQ+TOE9TkSEQyVeJT7SRksxUg== + dependencies: + bluebird "3.7.1" + debug "4.1.1" + lodash "4.17.19" + "@cypress/what-is-circular@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@cypress/what-is-circular/-/what-is-circular-1.0.1.tgz#c88adb7106a4e1624e403512fc87c18e9700c877" @@ -4029,6 +4144,11 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd" integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ== +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= + "@types/linkify-it@*": version "2.1.0" resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-2.1.0.tgz#ea3dd64c4805597311790b61e872cbd1ed2cd806" @@ -4246,6 +4366,16 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== +"@types/strip-bom@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/strip-bom/-/strip-bom-3.0.0.tgz#14a8ec3956c2e81edb7520790aecf21c290aebd2" + integrity sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I= + +"@types/strip-json-comments@0.0.30": + version "0.0.30" + resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1" + integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ== + "@types/tapable@*": version "1.0.6" resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.6.tgz#a9ca4b70a18b270ccb2bc0aaafefd1d486b7ea74" @@ -5078,7 +5208,7 @@ any-observable@^0.3.0: resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.3.0.tgz#af933475e5806a67d0d7df090dd5e8bef65d119b" integrity sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog== -any-promise@^1.0.0, any-promise@^1.1.0: +any-promise@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= @@ -6966,11 +7096,6 @@ bluebird@^2.9.30, bluebird@^2.9.33: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" integrity sha1-U0uQM8AiyVecVro7Plpcqvu2UOE= -bluebird@~3.0.6: - version "3.0.6" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.0.6.tgz#f2488f325782f66d174842f481992e2faba56f38" - integrity sha1-8kiPMleC9m0XSEL0gZkuL6ulbzg= - blueimp-md5@^2.3.0: version "2.16.0" resolved "https://registry.yarnpkg.com/blueimp-md5/-/blueimp-md5-2.16.0.tgz#9018bb805e4ee05512e0e8cbdb9305eeecbdc87c" @@ -8434,6 +8559,13 @@ coffee-lex@^9.1.5: resolved "https://registry.yarnpkg.com/coffee-lex/-/coffee-lex-9.1.5.tgz#6f46f33df539de3c00831d47bbe115991336aa4e" integrity sha512-94lUMjs1vhlV86vnbCCnnIYzR4oszuO4qJzKUuKOLidiksh/UyQFOzPusjmLJt6vy5CNB0d/KtaceqV84zr46g== +coffee-loader@0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/coffee-loader/-/coffee-loader-0.9.0.tgz#6deabd336062ddc6d773da4dfd16367fc7107bd6" + integrity sha512-VSoQ5kWr6Yfjn4RDpVbba2XMs3XG1ZXtLakPRt8dNfUcNU9h+1pocpdUUEd7NK9rLDwrju4yonhxrL8aMr5tww== + dependencies: + loader-utils "^1.0.2" + coffee-script@1.12.5: version "1.12.5" resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.12.5.tgz#809f4585419112bbfe46a073ad7543af18c27346" @@ -12246,15 +12378,6 @@ fs-constants@^1.0.0: resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== -fs-extra@7.0.1, fs-extra@^7.0.0, fs-extra@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" - integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== - dependencies: - graceful-fs "^4.1.2" - jsonfile "^4.0.0" - universalify "^0.1.0" - fs-extra@8.1.0, fs-extra@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" @@ -12310,6 +12433,15 @@ fs-extra@^6.0.1: jsonfile "^4.0.0" universalify "^0.1.0" +fs-extra@^7.0.0, fs-extra@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-extra@^9.0.0, fs-extra@^9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" @@ -22903,21 +23035,6 @@ stream-splicer@^2.0.0: inherits "^2.0.1" readable-stream "^2.0.2" -stream-to-array@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/stream-to-array/-/stream-to-array-2.3.0.tgz#bbf6b39f5f43ec30bc71babcb37557acecf34353" - integrity sha1-u/azn19D7DC8cbq8s3VXrOzzQ1M= - dependencies: - any-promise "^1.1.0" - -stream-to-promise@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/stream-to-promise/-/stream-to-promise-1.1.1.tgz#838f5df7c92b8c9ba8fb95d7aa3ac312eec7527f" - integrity sha1-g49d98krjJuo+5XXqjrDEu7HUn8= - dependencies: - bluebird "~3.0.6" - stream-to-array "~2.3.0" - strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" @@ -23143,7 +23260,7 @@ strip-indent@^3.0.0: dependencies: min-indent "^1.0.0" -strip-json-comments@2.0.1, strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: +strip-json-comments@2.0.1, strip-json-comments@^2.0.0, strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= @@ -23915,6 +24032,17 @@ ts-loader@7.0.4: micromatch "^4.0.0" semver "^6.0.0" +ts-loader@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-8.0.1.tgz#9670dcbce2a8c8506d01a37fee042350d02c8c21" + integrity sha512-I9Nmly0ufJoZRMuAT9d5ijsC2B7oSPvUnOJt/GhgoATlPGYfa17VicDKPcqwUCrHpOkCxr/ybLYwbnS4cOxmvQ== + dependencies: + chalk "^2.3.0" + enhanced-resolve "^4.0.0" + loader-utils "^1.0.2" + micromatch "^4.0.0" + semver "^6.0.0" + ts-node@5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-5.0.1.tgz#78e5d1cb3f704de1b641e43b76be2d4094f06f81" @@ -23951,6 +24079,35 @@ ts-node@8.5.4: source-map-support "^0.5.6" yn "^3.0.0" +tsconfig-paths-webpack-plugin@3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-3.2.0.tgz#6e70bd42915ad0efb64d3385163f0c1270f3e04d" + integrity sha512-S/gOOPOkV8rIL4LurZ1vUdYCVgo15iX9ZMJ6wx6w2OgcpT/G4wMyHB6WM+xheSqGMrWKuxFul+aXpCju3wmj/g== + dependencies: + chalk "^2.3.0" + enhanced-resolve "^4.0.0" + tsconfig-paths "^3.4.0" + +tsconfig-paths@^3.4.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" + integrity sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.0" + strip-bom "^3.0.0" + +tsconfig@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/tsconfig/-/tsconfig-7.0.0.tgz#84538875a4dc216e5c4a5432b3a4dec3d54e91b7" + integrity sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw== + dependencies: + "@types/strip-bom" "^3.0.0" + "@types/strip-json-comments" "0.0.30" + strip-bom "^3.0.0" + strip-json-comments "^2.0.0" + tslib@^1, tslib@^1.0.0, tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.13.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" From daeab10e46d552b3975c55d370bfbcb301f8a4e7 Mon Sep 17 00:00:00 2001 From: Chris Breiding Date: Mon, 10 Aug 2020 18:18:30 -0400 Subject: [PATCH 31/42] fix: Remove default esModuleInterop option from ts-node and preprocessor (#8143) * make tests preprocessor agnostic * update eslintignore * put back deps needed for e2e test * remove obselete snapshot it was replaced by 1_typescript_support_spec.ts.js * switch from browserify to webpack preprocessor * cmon github * fix/update tests * bump preprocessor and update snapshots * update snapshots * bump preprocessor to gain json support * fix e2e tests with webpack-originated errors * bump preprocessor version, fix node globals * update snapshot * remove support for ? in file path * bump preprocessor version * bump preprocessor again * bump preprocessor * bump preprocessor * update snapshots * bump preprocessor version * bump preprocessor, quiet the paths plugin * add test verifying tsconfig paths work * refactor registering ts-node * separate spec/support file typescript tests from plugins file typescript tests * fix unit tests * fix: Remove esModuleInterop default from ts-node (#7808) * Remove esModuleInterop from ts-node. * Add e2e test. * Fix test. Change test name. Add comment. * Fix test snapshot name. * update snapshotting Co-authored-by: Chris Breiding * clean up e2e project * bump preprocessor to 1.3.2, which removes esModuleInterop default value * improve esmoduleinterop e2e tests * change spec file * bump batteries-included preprocessor and install latest webpack preprocessor beside it * update snapshots * put back snapshot * update snapshot * update snapshot Co-authored-by: Kukhyeon Heo Co-authored-by: Brian Mann --- .eslintignore | 2 +- ...s => 1_typescript_spec_support_spec.ts.js} | 40 ++++++------------- .../server/lib/plugins/child/run_plugins.js | 20 +--------- packages/server/lib/project.js | 20 +--------- packages/server/lib/util/ts-node-options.js | 29 -------------- packages/server/lib/util/ts-node.js | 34 ++++++++++++++++ .../test/e2e/1_typescript_plugins_spec.ts | 26 ++++++++++++ ...c.ts => 1_typescript_spec_support_spec.ts} | 23 ++--------- ...pec.ts => typescript_syntax_error_spec.ts} | 0 .../ts-proj-esmoduleinterop-true/cypress.json | 3 ++ .../cypress/integration/passing_spec.ts | 3 ++ .../cypress/plugins/commonjs-export.js | 3 ++ .../cypress/plugins/index.ts | 12 ++++++ .../tsconfig.json | 5 +++ .../ts-proj-with-module-esnext/cypress.json | 3 ++ .../uses_cy_task_in_ts_plugins_file_spec.js | 5 +++ .../cypress/plugins/greeting.ts | 3 ++ .../cypress/plugins/index.ts | 0 .../tsconfig.json | 0 .../ts-proj-with-own-tsconfig/cypress.json | 1 - .../cypress/integration/app_spec.ts | 5 --- .../cypress/integration/js-spec.js | 11 ----- .../cypress/integration/math.ts | 3 -- .../cypress/plugins/greeting.ts | 3 -- .../cypress/support/.eslintrc.json | 9 ----- .../cypress/support/commands.ts | 13 ------ .../cypress/support/index.ts | 20 ---------- .../ts-proj/cypress/integration/app_spec.ts | 1 + .../plugins/commonjs-export-function.js | 1 + .../projects/ts-proj/cypress/plugins/index.ts | 20 ++++------ packages/server/test/unit/config_spec.js | 4 +- packages/server/test/unit/project_spec.js | 1 - 32 files changed, 129 insertions(+), 194 deletions(-) rename packages/server/__snapshots__/{1_typescript_support_spec.ts.js => 1_typescript_spec_support_spec.ts.js} (87%) delete mode 100644 packages/server/lib/util/ts-node-options.js create mode 100644 packages/server/lib/util/ts-node.js create mode 100644 packages/server/test/e2e/1_typescript_plugins_spec.ts rename packages/server/test/e2e/{1_typescript_support_spec.ts => 1_typescript_spec_support_spec.ts} (54%) rename packages/server/test/support/fixtures/projects/e2e/cypress/integration/{typescript_failing_spec.ts => typescript_syntax_error_spec.ts} (100%) create mode 100644 packages/server/test/support/fixtures/projects/ts-proj-esmoduleinterop-true/cypress.json create mode 100644 packages/server/test/support/fixtures/projects/ts-proj-esmoduleinterop-true/cypress/integration/passing_spec.ts create mode 100644 packages/server/test/support/fixtures/projects/ts-proj-esmoduleinterop-true/cypress/plugins/commonjs-export.js create mode 100644 packages/server/test/support/fixtures/projects/ts-proj-esmoduleinterop-true/cypress/plugins/index.ts create mode 100644 packages/server/test/support/fixtures/projects/ts-proj-esmoduleinterop-true/tsconfig.json create mode 100644 packages/server/test/support/fixtures/projects/ts-proj-with-module-esnext/cypress.json create mode 100644 packages/server/test/support/fixtures/projects/ts-proj-with-module-esnext/cypress/integration/uses_cy_task_in_ts_plugins_file_spec.js create mode 100644 packages/server/test/support/fixtures/projects/ts-proj-with-module-esnext/cypress/plugins/greeting.ts rename packages/server/test/support/fixtures/projects/{ts-proj-with-own-tsconfig => ts-proj-with-module-esnext}/cypress/plugins/index.ts (100%) rename packages/server/test/support/fixtures/projects/{ts-proj-with-own-tsconfig => ts-proj-with-module-esnext}/tsconfig.json (100%) delete mode 100644 packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress.json delete mode 100644 packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/integration/app_spec.ts delete mode 100644 packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/integration/js-spec.js delete mode 100644 packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/integration/math.ts delete mode 100644 packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/plugins/greeting.ts delete mode 100644 packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/support/.eslintrc.json delete mode 100644 packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/support/commands.ts delete mode 100644 packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/support/index.ts create mode 100644 packages/server/test/support/fixtures/projects/ts-proj/cypress/plugins/commonjs-export-function.js diff --git a/.eslintignore b/.eslintignore index 086b6069fa73..7dd7fe603e53 100644 --- a/.eslintignore +++ b/.eslintignore @@ -26,7 +26,7 @@ packages/server/lib/scaffold/plugins/index.js packages/server/lib/scaffold/support/index.js packages/server/lib/scaffold/support/commands.js packages/server/test/support/fixtures/projects/e2e/cypress/integration/stdout_exit_early_failing_spec.js -packages/server/test/support/fixtures/projects/e2e/cypress/integration/typescript_failing_spec.ts +packages/server/test/support/fixtures/projects/e2e/cypress/integration/typescript_syntax_error_spec.ts **/.projects **/*.d.ts diff --git a/packages/server/__snapshots__/1_typescript_support_spec.ts.js b/packages/server/__snapshots__/1_typescript_spec_support_spec.ts.js similarity index 87% rename from packages/server/__snapshots__/1_typescript_support_spec.ts.js rename to packages/server/__snapshots__/1_typescript_spec_support_spec.ts.js index 4911a6df2557..330167f09d38 100644 --- a/packages/server/__snapshots__/1_typescript_support_spec.ts.js +++ b/packages/server/__snapshots__/1_typescript_spec_support_spec.ts.js @@ -1,4 +1,4 @@ -exports['e2e typescript spec passes 1'] = ` +exports['e2e typescript in spec and support file spec passes 1'] = ` ==================================================================================================== @@ -62,7 +62,7 @@ exports['e2e typescript spec passes 1'] = ` ` -exports['e2e typescript spec fails 1'] = ` +exports['e2e typescript in spec and support file spec fails with syntax error 1'] = ` ==================================================================================================== @@ -71,23 +71,23 @@ exports['e2e typescript spec fails 1'] = ` ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ │ Cypress: 1.2.3 │ │ Browser: FooBrowser 88 │ - │ Specs: 1 found (typescript_failing_spec.ts) │ - │ Searched: cypress/integration/typescript_failing_spec.ts │ + │ Specs: 1 found (typescript_syntax_error_spec.ts) │ + │ Searched: cypress/integration/typescript_syntax_error_spec.ts │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ ──────────────────────────────────────────────────────────────────────────────────────────────────── - Running: typescript_failing_spec.ts (1 of 1) + Running: typescript_syntax_error_spec.ts (1 of 1) Oops...we found an error preparing this test file: - /foo/bar/.projects/e2e/cypress/integration/typescript_failing_spec.ts + /foo/bar/.projects/e2e/cypress/integration/typescript_syntax_error_spec.ts The error was: Error: Webpack Compilation Error -./cypress/integration/typescript_failing_spec.tsXX:XX +./cypress/integration/typescript_syntax_error_spec.tsXX:XX Module parse failed: Unexpected token (4:19) File was processed with these loaders: * ../../../../node_modules/@cypress/webpack-batteries-included-preprocessor/node_modules/ts-loader/index.js @@ -115,14 +115,15 @@ Fix the error in your code and re-run your tests. │ Screenshots: 0 │ │ Video: true │ │ Duration: X seconds │ - │ Spec Ran: typescript_failing_spec.ts │ + │ Spec Ran: typescript_syntax_error_spec.ts │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ (Video) - Started processing: Compressing to 32 CRF - - Finished processing: /XXX/XXX/XXX/cypress/videos/typescript_failing_spec.ts.mp4 (X second) + - Finished processing: /XXX/XXX/XXX/cypress/videos/typescript_syntax_error_spec.ts (X second) + .mp4 ==================================================================================================== @@ -132,14 +133,14 @@ Fix the error in your code and re-run your tests. Spec Tests Passing Failing Pending Skipped ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ ✖ typescript_failing_spec.ts XX:XX - - 1 - - │ + │ ✖ typescript_syntax_error_spec.ts XX:XX - - 1 - - │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ ✖ 1 of 1 failed (100%) XX:XX - - 1 - - ` -exports['e2e typescript project passes 1'] = ` +exports['e2e typescript in spec and support file project passes 1'] = ` ==================================================================================================== @@ -227,20 +228,3 @@ exports['e2e typescript project passes 1'] = ` ` - -exports['typescript with tsconfig run'] = ` - (Run Finished) - - - Spec Tests Passing Failing Pending Skipped - ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ ✔ app_spec.ts XX:XX 1 1 - - - │ - ├────────────────────────────────────────────────────────────────────────────────────────────────┤ - │ ✔ js-spec.js XX:XX 2 2 - - - │ - ├────────────────────────────────────────────────────────────────────────────────────────────────┤ - │ ✔ math.ts XX:XX - - - - - │ - └────────────────────────────────────────────────────────────────────────────────────────────────┘ - ✔ All specs passed! XX:XX 3 3 - - - - - -` diff --git a/packages/server/lib/plugins/child/run_plugins.js b/packages/server/lib/plugins/child/run_plugins.js index 1f70bb3f2706..894ad24a2331 100644 --- a/packages/server/lib/plugins/child/run_plugins.js +++ b/packages/server/lib/plugins/child/run_plugins.js @@ -4,15 +4,13 @@ const _ = require('lodash') const debug = require('debug')('cypress:server:plugins:child') const Promise = require('bluebird') -const tsnode = require('ts-node') -const resolve = require('resolve') const errors = require('../../errors') const preprocessor = require('./preprocessor') const task = require('./task') const util = require('../util') const validateEvent = require('./validate_event') -const tsNodeOptions = require('../../util/ts-node-options') +const { registerTsNode } = require('../../util/ts-node') const ARRAY_METHODS = ['concat', 'push', 'unshift', 'slice', 'pop', 'shift', 'slice', 'splice', 'filter', 'map', 'forEach', 'reduce', 'reverse', 'splice', 'includes'] @@ -183,21 +181,7 @@ module.exports = (ipc, pluginsFile, projectRoot) => { }) if (!tsRegistered) { - try { - const tsPath = resolve.sync('typescript', { - basedir: projectRoot, - }) - - const tsOptions = tsNodeOptions.getTsNodeOptions(tsPath) - - debug('typescript path: %s', tsPath) - debug('registering plugins TS with options %o', tsOptions) - - tsnode.register(tsOptions) - } catch (e) { - debug(`typescript doesn't exist. ts-node setup failed.`) - debug('error message: %s', e.message) - } + registerTsNode(projectRoot) // ensure typescript is only registered once tsRegistered = true diff --git a/packages/server/lib/project.js b/packages/server/lib/project.js index b734863ec39e..bde157744fbb 100644 --- a/packages/server/lib/project.js +++ b/packages/server/lib/project.js @@ -6,8 +6,6 @@ const Promise = require('bluebird') const commitInfo = require('@cypress/commit-info') const la = require('lazy-ass') const check = require('check-more-types') -const tsnode = require('ts-node') -const resolve = require('resolve') const scaffoldDebug = require('debug')('cypress:server:scaffold') const debug = require('debug')('cypress:server:project') const cwd = require('./cwd') @@ -31,7 +29,7 @@ const keys = require('./util/keys') const settings = require('./util/settings') const specsUtil = require('./util/specs') const { escapeFilenameInUrl } = require('./util/escape_filename') -const tsNodeOptions = require('./util/ts-node-options') +const { registerTsNode } = require('./util/ts-node') const localCwd = cwd() @@ -103,21 +101,7 @@ class Project extends EE { return scaffold.plugins(path.dirname(cfg.pluginsFile), cfg) } }).then((cfg) => { - try { - const tsPath = resolve.sync('typescript', { - basedir: this.projectRoot, - }) - - const tsOptions = tsNodeOptions.getTsNodeOptions(tsPath) - - debug('typescript path: %s', tsPath) - debug('registering project TS with options %o', tsOptions) - - tsnode.register(tsOptions) - } catch (e) { - debug(`typescript doesn't exist. ts-node setup failed.`) - debug('error message %s', e.message) - } + registerTsNode(this.projectRoot) return cfg }).then((cfg) => { diff --git a/packages/server/lib/util/ts-node-options.js b/packages/server/lib/util/ts-node-options.js deleted file mode 100644 index de72ee032712..000000000000 --- a/packages/server/lib/util/ts-node-options.js +++ /dev/null @@ -1,29 +0,0 @@ -// returns options for ts-node registration -// https://github.com/TypeStrong/ts-node -const _ = require('lodash') - -/** - * Default ts - node options.We want to output CommonJS modules. - * And we want to run fast - thus transpile only mode (no type checking) -*/ -const tsOptions = { - transpileOnly: true, - compilerOptions: { - module: 'CommonJS', - esModuleInterop: true, - }, -} - -/** - * Returns combined object with ts-node options. - * @param {string} tsPath Path to TypeScript - */ -function getTsNodeOptions (tsPath) { - const merged = _.cloneDeep(tsOptions) - - merged.compiler = tsPath - - return merged -} - -module.exports = { getTsNodeOptions } diff --git a/packages/server/lib/util/ts-node.js b/packages/server/lib/util/ts-node.js new file mode 100644 index 000000000000..fa81234c9b24 --- /dev/null +++ b/packages/server/lib/util/ts-node.js @@ -0,0 +1,34 @@ +const debug = require('debug')('cypress:server:ts-node') +const tsnode = require('ts-node') +const resolve = require('resolve') + +const getTsNodeOptions = (tsPath) => { + return { + compiler: tsPath, // use the user's installed typescript + compilerOptions: { + module: 'CommonJS', + }, + transpileOnly: true, // transpile only (no type-check) for speed + } +} + +const registerTsNode = (projectRoot) => { + try { + const tsPath = resolve.sync('typescript', { + basedir: projectRoot, + }) + const tsOptions = getTsNodeOptions(tsPath) + + debug('typescript path: %s', tsPath) + debug('registering project TS with options %o', tsOptions) + + tsnode.register(tsOptions) + } catch (err) { + debug(`typescript doesn't exist. ts-node setup failed.`) + debug('error message: %s', err.message) + } +} + +module.exports = { + registerTsNode, +} diff --git a/packages/server/test/e2e/1_typescript_plugins_spec.ts b/packages/server/test/e2e/1_typescript_plugins_spec.ts new file mode 100644 index 000000000000..5954530e9367 --- /dev/null +++ b/packages/server/test/e2e/1_typescript_plugins_spec.ts @@ -0,0 +1,26 @@ +import e2e from '../support/helpers/e2e' +import Fixtures from '../support/helpers/fixtures' + +describe('e2e typescript in plugins file', function () { + e2e.setup() + + it('handles tsconfig with module other than commonjs', function () { + return e2e.exec(this, { + project: Fixtures.projectPath('ts-proj-with-module-esnext'), + }) + }) + + // https://github.com/cypress-io/cypress/issues/7575 + it('defaults to esModuleInterop: false', function () { + return e2e.exec(this, { + project: Fixtures.projectPath('ts-proj'), + }) + }) + + // https://github.com/cypress-io/cypress/issues/7575 + it('allows esModuleInterop to be overridden with true via tsconfig.json', function () { + return e2e.exec(this, { + project: Fixtures.projectPath('ts-proj-esmoduleinterop-true'), + }) + }) +}) diff --git a/packages/server/test/e2e/1_typescript_support_spec.ts b/packages/server/test/e2e/1_typescript_spec_support_spec.ts similarity index 54% rename from packages/server/test/e2e/1_typescript_support_spec.ts rename to packages/server/test/e2e/1_typescript_spec_support_spec.ts index 5007b65cfa41..82dfa6f8fdfa 100644 --- a/packages/server/test/e2e/1_typescript_support_spec.ts +++ b/packages/server/test/e2e/1_typescript_spec_support_spec.ts @@ -1,9 +1,7 @@ -import snapshot from 'snap-shot-it' - import e2e from '../support/helpers/e2e' import Fixtures from '../support/helpers/fixtures' -describe('e2e typescript', function () { +describe('e2e typescript in spec and support file', function () { e2e.setup() it('spec passes', function () { @@ -13,9 +11,9 @@ describe('e2e typescript', function () { }) }) - it('spec fails', function () { + it('spec fails with syntax error', function () { return e2e.exec(this, { - spec: 'typescript_failing_spec.ts', + spec: 'typescript_syntax_error_spec.ts', snapshot: true, expectedExitCode: 1, onStdout: e2e.normalizeWebpackErrors, @@ -36,19 +34,4 @@ describe('e2e typescript', function () { project: Fixtures.projectPath('ts-proj-with-paths'), }) }) - - it('handles tsconfig with module other than commonjs', function () { - const projPath = Fixtures.projectPath('ts-proj-with-own-tsconfig') - - return e2e.exec(this, { - project: projPath, - config: { - video: false, - }, - }).then((result) => { - const runSummary = e2e.leaveRunFinishedTable(e2e.normalizeStdout(result.stdout)) - - snapshot('typescript with tsconfig run', runSummary) - }) - }) }) diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/typescript_failing_spec.ts b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/typescript_syntax_error_spec.ts similarity index 100% rename from packages/server/test/support/fixtures/projects/e2e/cypress/integration/typescript_failing_spec.ts rename to packages/server/test/support/fixtures/projects/e2e/cypress/integration/typescript_syntax_error_spec.ts diff --git a/packages/server/test/support/fixtures/projects/ts-proj-esmoduleinterop-true/cypress.json b/packages/server/test/support/fixtures/projects/ts-proj-esmoduleinterop-true/cypress.json new file mode 100644 index 000000000000..0c2bdde8665b --- /dev/null +++ b/packages/server/test/support/fixtures/projects/ts-proj-esmoduleinterop-true/cypress.json @@ -0,0 +1,3 @@ +{ + "supportFile": false +} diff --git a/packages/server/test/support/fixtures/projects/ts-proj-esmoduleinterop-true/cypress/integration/passing_spec.ts b/packages/server/test/support/fixtures/projects/ts-proj-esmoduleinterop-true/cypress/integration/passing_spec.ts new file mode 100644 index 000000000000..99a13400edf9 --- /dev/null +++ b/packages/server/test/support/fixtures/projects/ts-proj-esmoduleinterop-true/cypress/integration/passing_spec.ts @@ -0,0 +1,3 @@ +it('passes', () => { + expect(true).to.be.true +}) diff --git a/packages/server/test/support/fixtures/projects/ts-proj-esmoduleinterop-true/cypress/plugins/commonjs-export.js b/packages/server/test/support/fixtures/projects/ts-proj-esmoduleinterop-true/cypress/plugins/commonjs-export.js new file mode 100644 index 000000000000..bb41bc70595c --- /dev/null +++ b/packages/server/test/support/fixtures/projects/ts-proj-esmoduleinterop-true/cypress/plugins/commonjs-export.js @@ -0,0 +1,3 @@ +exports.export1 = 'export1' + +exports.export2 = 'export2' diff --git a/packages/server/test/support/fixtures/projects/ts-proj-esmoduleinterop-true/cypress/plugins/index.ts b/packages/server/test/support/fixtures/projects/ts-proj-esmoduleinterop-true/cypress/plugins/index.ts new file mode 100644 index 000000000000..e9e9e040af8b --- /dev/null +++ b/packages/server/test/support/fixtures/projects/ts-proj-esmoduleinterop-true/cypress/plugins/index.ts @@ -0,0 +1,12 @@ +/// + +import commonjsExports from './commonjs-export' + +if (commonjsExports.export1 !== 'export1' || commonjsExports.export2 !== 'export2') { + throw new Error('Imported values do not match exported values') +} + +// Default Cypress plugin function +export default (on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions) => { + +} diff --git a/packages/server/test/support/fixtures/projects/ts-proj-esmoduleinterop-true/tsconfig.json b/packages/server/test/support/fixtures/projects/ts-proj-esmoduleinterop-true/tsconfig.json new file mode 100644 index 000000000000..2f98042715ab --- /dev/null +++ b/packages/server/test/support/fixtures/projects/ts-proj-esmoduleinterop-true/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "esModuleInterop": true + } +} diff --git a/packages/server/test/support/fixtures/projects/ts-proj-with-module-esnext/cypress.json b/packages/server/test/support/fixtures/projects/ts-proj-with-module-esnext/cypress.json new file mode 100644 index 000000000000..9c5417cd8268 --- /dev/null +++ b/packages/server/test/support/fixtures/projects/ts-proj-with-module-esnext/cypress.json @@ -0,0 +1,3 @@ +{ + "supportFolder": false +} diff --git a/packages/server/test/support/fixtures/projects/ts-proj-with-module-esnext/cypress/integration/uses_cy_task_in_ts_plugins_file_spec.js b/packages/server/test/support/fixtures/projects/ts-proj-with-module-esnext/cypress/integration/uses_cy_task_in_ts_plugins_file_spec.js new file mode 100644 index 000000000000..8dada9612545 --- /dev/null +++ b/packages/server/test/support/fixtures/projects/ts-proj-with-module-esnext/cypress/integration/uses_cy_task_in_ts_plugins_file_spec.js @@ -0,0 +1,5 @@ +describe('uses task that is in typescript plugins file', () => { + it('calls task', () => { + cy.task('hello', 'TS').should('equal', 'Hello, TS!') + }) +}) diff --git a/packages/server/test/support/fixtures/projects/ts-proj-with-module-esnext/cypress/plugins/greeting.ts b/packages/server/test/support/fixtures/projects/ts-proj-with-module-esnext/cypress/plugins/greeting.ts new file mode 100644 index 000000000000..4bb20cd18999 --- /dev/null +++ b/packages/server/test/support/fixtures/projects/ts-proj-with-module-esnext/cypress/plugins/greeting.ts @@ -0,0 +1,3 @@ +export const asyncGreeting = async (greeting: string) => { + return Promise.resolve(`Hello, ${greeting}!`) +} diff --git a/packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/plugins/index.ts b/packages/server/test/support/fixtures/projects/ts-proj-with-module-esnext/cypress/plugins/index.ts similarity index 100% rename from packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/plugins/index.ts rename to packages/server/test/support/fixtures/projects/ts-proj-with-module-esnext/cypress/plugins/index.ts diff --git a/packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/tsconfig.json b/packages/server/test/support/fixtures/projects/ts-proj-with-module-esnext/tsconfig.json similarity index 100% rename from packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/tsconfig.json rename to packages/server/test/support/fixtures/projects/ts-proj-with-module-esnext/tsconfig.json diff --git a/packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress.json b/packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress.json deleted file mode 100644 index 0967ef424bce..000000000000 --- a/packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/integration/app_spec.ts b/packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/integration/app_spec.ts deleted file mode 100644 index d539f78bfb6f..000000000000 --- a/packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/integration/app_spec.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { add } from './math' - -it('is true', () => { - expect(add(1, 2)).to.eq(3) -}) diff --git a/packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/integration/js-spec.js b/packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/integration/js-spec.js deleted file mode 100644 index 1f7c6d518cc5..000000000000 --- a/packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/integration/js-spec.js +++ /dev/null @@ -1,11 +0,0 @@ -import { add } from './math' - -describe('JS spec', () => { - it('adds 2 and 2 together', () => { - expect(add(2, 2)).to.equal(4) - }) - - it('calls task', () => { - cy.task('hello', 'TS').should('equal', 'Hello, TS!') - }) -}) diff --git a/packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/integration/math.ts b/packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/integration/math.ts deleted file mode 100644 index e29e78dc31cf..000000000000 --- a/packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/integration/math.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const add = (a: number, b: number) => { - return a + b -} diff --git a/packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/plugins/greeting.ts b/packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/plugins/greeting.ts deleted file mode 100644 index eaa232c40687..000000000000 --- a/packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/plugins/greeting.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const asyncGreeting = async (greeting) => { - return Promise.resolve(`Hello, ${greeting}!`) -} diff --git a/packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/support/.eslintrc.json b/packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/support/.eslintrc.json deleted file mode 100644 index ff5b2657922f..000000000000 --- a/packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/support/.eslintrc.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "parserOptions": { - "parser": "@typescript-eslint/parser", - "project": "packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/tsconfig.json" - }, - "rules": { - "@typescript-eslint/no-misused-promises": "error" - } -} \ No newline at end of file diff --git a/packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/support/commands.ts b/packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/support/commands.ts deleted file mode 100644 index 9dc223e893e5..000000000000 --- a/packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/support/commands.ts +++ /dev/null @@ -1,13 +0,0 @@ -/// - -// Copied an example command from https://on.cypress.io/custom-commands -Cypress.Commands.add('clickLink', (label: string | number | RegExp) => { - cy.get('a').contains(label).click() -}) - -// https://github.com/cypress-io/cypress/issues/7510 -// The code below fails when @typescript-eslint/no-misused-promises is error -// and the return type of function in Cypress.Commands.add doesn't support Chainable. -Cypress.Commands.add('dataCy', (value) => { - return cy.get(`[data-cy=${value}]`) -}) diff --git a/packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/support/index.ts b/packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/support/index.ts deleted file mode 100644 index d68db96df269..000000000000 --- a/packages/server/test/support/fixtures/projects/ts-proj-with-own-tsconfig/cypress/support/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -// *********************************************************** -// This example support/index.js is processed and -// loaded automatically before your test files. -// -// This is a great place to put global configuration and -// behavior that modifies Cypress. -// -// You can change the location of this file or turn off -// automatically serving support files with the -// 'supportFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/configuration -// *********************************************************** - -// Import commands.js using ES2015 syntax: -import './commands' - -// Alternatively you can use CommonJS syntax: -// require('./commands') diff --git a/packages/server/test/support/fixtures/projects/ts-proj/cypress/integration/app_spec.ts b/packages/server/test/support/fixtures/projects/ts-proj/cypress/integration/app_spec.ts index d539f78bfb6f..ceacd67310bb 100644 --- a/packages/server/test/support/fixtures/projects/ts-proj/cypress/integration/app_spec.ts +++ b/packages/server/test/support/fixtures/projects/ts-proj/cypress/integration/app_spec.ts @@ -1,5 +1,6 @@ import { add } from './math' it('is true', () => { + // @ts-ignore expect(add(1, 2)).to.eq(3) }) diff --git a/packages/server/test/support/fixtures/projects/ts-proj/cypress/plugins/commonjs-export-function.js b/packages/server/test/support/fixtures/projects/ts-proj/cypress/plugins/commonjs-export-function.js new file mode 100644 index 000000000000..0c0c42d5b58c --- /dev/null +++ b/packages/server/test/support/fixtures/projects/ts-proj/cypress/plugins/commonjs-export-function.js @@ -0,0 +1 @@ +module.exports = () => {} diff --git a/packages/server/test/support/fixtures/projects/ts-proj/cypress/plugins/index.ts b/packages/server/test/support/fixtures/projects/ts-proj/cypress/plugins/index.ts index a728da18c1f9..7f27d46b4c1a 100644 --- a/packages/server/test/support/fixtures/projects/ts-proj/cypress/plugins/index.ts +++ b/packages/server/test/support/fixtures/projects/ts-proj/cypress/plugins/index.ts @@ -1,17 +1,13 @@ -// Copied an example from https://docs.cypress.io/api/plugins/browser-launch-api.html#Use-fake-video-for-webcam-testing - /// -export default (on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions) => { - on('before:browser:launch', (browser, launchOptions) => { - if (browser.family === 'chromium' && browser.name !== 'electron') { - // Mac/Linux - //launchOptions.args.push('--use-file-for-fake-video-capture=cypress/fixtures/my-video.y4m') +import fn from './commonjs-export-function' - // Windows - // launchOptions.args.push('--use-file-for-fake-video-capture=c:\\path\\to\\video\\my-video.y4m') - } +// if esModuleInterop is forced to be true, this will error // with 'fn is +// not a function'. instead, we allow the tsconfig.json to determine the value +// of esModuleInterop +fn() + +// Default Cypress plugin function +export default (on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions) => { - return launchOptions - }) } diff --git a/packages/server/test/unit/config_spec.js b/packages/server/test/unit/config_spec.js index d08e498da4b5..7503f88f62eb 100644 --- a/packages/server/test/unit/config_spec.js +++ b/packages/server/test/unit/config_spec.js @@ -1856,7 +1856,7 @@ describe('lib/config', () => { }) it('sets the pluginsFile to index.ts if it exists', () => { - const projectRoot = path.join(process.cwd(), 'test/support/fixtures/projects/ts-proj') + const projectRoot = path.join(process.cwd(), 'test/support/fixtures/projects/ts-proj-with-module-esnext') const obj = { projectRoot, @@ -1873,7 +1873,7 @@ describe('lib/config', () => { }) it('sets the pluginsFile to index.ts if it exists (without ts require hook)', () => { - const projectRoot = path.join(process.cwd(), 'test/support/fixtures/projects/ts-proj') + const projectRoot = path.join(process.cwd(), 'test/support/fixtures/projects/ts-proj-with-module-esnext') const pluginsFolder = `${projectRoot}/cypress/plugins` const pluginsFilename = `${pluginsFolder}/index.ts` diff --git a/packages/server/test/unit/project_spec.js b/packages/server/test/unit/project_spec.js index be4865e24531..1cae5ac3428c 100644 --- a/packages/server/test/unit/project_spec.js +++ b/packages/server/test/unit/project_spec.js @@ -354,7 +354,6 @@ This option will not have an effect in Some-other-name. Tests that rely on web s compiler: projTsPath, compilerOptions: { module: 'CommonJS', - esModuleInterop: true, }, }) }) From 01cc5e96d005c09c6972563c05e285dd0e825e99 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Mon, 10 Aug 2020 18:29:14 -0400 Subject: [PATCH 32/42] block the percy snapshot until the collapsible animation has finished --- packages/desktop-gui/cypress/integration/settings_spec.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/desktop-gui/cypress/integration/settings_spec.js b/packages/desktop-gui/cypress/integration/settings_spec.js index 7f43548f3828..c12317dd0ebe 100644 --- a/packages/desktop-gui/cypress/integration/settings_spec.js +++ b/packages/desktop-gui/cypress/integration/settings_spec.js @@ -116,9 +116,17 @@ describe('Settings', () => { .should('not.contain', '0:Chrome') cy.contains('span', 'browsers').parents('div').first().find('span').first().click() + cy.get('.config-vars').invoke('text') .should('contain', '0:Chrome') + // make sure the main collapsible content + // has finished animating and that it has + // an empty inline style attribute + cy.get('.rc-collapse-content') + .should('not.have.class', 'rc-collapse-anim') + .should('have.attr', 'style', '') + cy.percySnapshot() }) From 860a20af302eb4d56077d3445809ef6519909fe3 Mon Sep 17 00:00:00 2001 From: Ben Kucera <14625260+Bkucera@users.noreply.github.com> Date: Mon, 10 Aug 2020 18:36:45 -0400 Subject: [PATCH 33/42] feat: support test retries (#3968) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add retries e2e test * restore runner/test/eslintrc * use mocha pass event, move runner.spec to runner package * fix .eslintignore * remove npmInstall logic in helper/e2e script, force custom reporters to use our mocha * temp 04/09/20 [skip ci] * add retries output to server/reporter, fix mocha pass event order, cleanup * e2e tests - dont run electron tests in other browsers * Update readme to reflect how to start server for cypress tests * fix after merge * fix .coffee -> .js after merge * fix attempt.tsx * fix runnable titles emitted to terminal reporters * fix more tests: update snapshots, fix 7_record_spec, 8_reporters_spec * remove styling for 'attempt-error-region' so it's not indented - This was the older styling before error improvements and is no longer necessary. * try 2: fix rerun before/after hooks * fix runner with only, runner snapshots, lint fixes * temp 04/29/20 [skip ci] * backport changes from test-retries * change logic to rerun before hooks after top navigation * fix windowSize for browser e2e test * fix windowSize for xvfb chrome in e2e test * ok fine, just disable screenshots * fix after merge: decaffed navigation.js * update server/unit test snapshots * fix after merge: decaffed aliases.js * fix usage of cypress --parallel flag in circle.yml * fix circle.yml integration-tests jobs * fix decaf related typo * fix circle.yml separate command for runner-integration-tests * update runner/integration tests + snapshot after error improvements * fix runner/integration snapshots for chrome/ff stacktrace differences * rerun ci * fix passing --parallel to runner-integration tests * perf: faster lookup for hooks without runnables * fix afterAll hook switch logic * simplify mocha prototype patches * fix decaf utils.coffee after merge * backport to before/after fix * backport to before/after fix 2 * cleanup from decaf, fix ui/package.json * update helpers, simplify runner.spec * fix lint-types errors, flaky spec * fix noExit passed to e2e test inline options * cleanup snapshot utility - refactor to use util file * remove before/after changes * make cy obj a class instance * cleanup/unmerge before/after fixes PR... * more cleanup * add comment * fix runner.spec * cleanup snapshot utility more, cleanup reporter.spec * fix after merge * minor rename variable * fix after merge: decaffed files * fix specName in reporterHeader, spec_helper require * replace reporter specPath usages with spec object from config * cleanup, fix specs, fix types tests * fix config spec paths in isolated runner, fix snapshot plugin button * combine runner.spec.js and runner_spec.js * fix incorrect merge * minor minor cleanup * rename driver/test/cypress to driver/test * use yarn workspace over lerna for individual package commands * add error message to driver start * remove usage of wait-on * update , import string * fix driver/readme * fix readmes after regex replace * revert wait-on changes * Revert "revert wait-on changes" This reverts commit 6de684cf3484fca8f7d8d6fe396bee248b62f819. * update yarn.lock * fix broken path in spec * fix broken paths in specs with @packages/driver * move runner/test/cypress into runner/cypress * start server in pluginsFile in runner/cypress tests * fix more broken spec paths * fix broken paths after runner/cypress folder move * move type definition loading for driver/cypress into dedicated file * move internal-types to "types" folder, fix driver/index.d.ts * fix type-check in packages/runner. not exactly sure why * fix runner type-check by excluding test folder in tsconfig * bump timeout on e2e/8_error_ui_spec * update snapshot utility, rename tests in runner/runner.spec, fix README yarn commands * delete old spec * fix snapshot naming, remove redundant test in reporter_spec * fix file renames after merge * rename runner/ snapshot * update server/unit/reporter_spec snapshot * update runner/runner_spec snapshot * rename runner snapshot file * address feedback: move server reporter snapshot specs out * address feedback: add comment about exposing globals * fix test-retries after merging isolated-runner * fix runner/test helper, update snapshot * address feedback: split out runner/retries spec, move reporter/ui tests to runner/ui spec (mostly done), various cleanup * fix scrolling, attempt opening, update snapshots * fix e2e support file * fix 5_spec_isolation * fix mislabeling attempt screenshots * only add test results prevAttempts if exists * fix reporter/unit tests, server/unit tests * remove dead code, fix test isOpen * update snapshots for retries.mochaEvents, fix snapshot error in state hydration test, remove dead snapshots * new moduleAPI schema using attempts array, fix wrapping errors from hook retries, update snapshots * add displayError, null out fields in moduleAPI schema * change default retries to {runMode:2, openMode:0} * fix reporter type-check * upgrade json-schemas, update snapshots * reformat error.stack to be only stacktrace, update snapshots * fix stacktrace replacing in 5_spec_isolation * fix navigation error causing infinite reloading, bump timeout on e2e/8_error_ui * fix server/unit tests for new schema * fix reporter/unit tests * fix reporting duplicate screenshots using cy.screenshot during test retry * update snapshot for 6_uncaught_support_file_spec * bump x-route-version: 3 * fix test.tsx collapsible content, css, fix e2e/8_error_ui, e2e projects excluding retries * fix css, fix padding in runnable-instruments, fix runner/integration tests * fixup after merge * fix reporter/runner to work with split hooks * update api tests, runner/cypress tests, reporter * fix 5_spec_isolation snapshots, fix runner/cypress errors.spec, fix null reference in test.tsx * fix e2e/non_root spec, fix type_check, fix reporter/unit tests * setup percy snapshots in runner/cypress, fix driver/runner test:after:run event, add tests for only,skip in runner/cypress, fix retried css * add customPercySnapshot * fix circle.yml * fix circle.yml 2 * fix circle.yml 3 * add warning for incompatible retries plugin * add more percy snapshots * fix firefox screenshot resolution in e2e test * Fix testConfigOverrides not affecting viewport (#8006) * finish adding percy snapshots to runner/cypress retries spec, update error msgs, add tests to be fixed * remove .only * fixing missing repo argument * fix testConfigOverrides usage with retries, fix test * fix issues from previous merge * add script that can query CircleCI workflow status * add circleci job to poll * add retries * try yarn lock * retry, percy finalize * check for current running job * do not swallow request error * better print * use job name from circle environment * use debug instead * renamed circle polling script * refactor circle to conditionally run percy-finalize when env var is available - pass job-names to wait on as an argument * use multi-line strings and quote --job-names - rename —circle-jobs to —job-names * add comment * only poll until the jobs to wait for are blocked or running * fix running hooks at correct depth after attempt fails from hook and will retry, update e2e snapshots * fix reporter/unit tests, remove unused toggleOpen code * move custom percy command into @packages/ui-components and apply them to desktop-gui * halt percy finalize job if env variable is not set * if only I could code * update runner/cypress mochaEvent snapshots, fix e2e firefox resolution * fix css for attempt border-left, fix attempt-tag open/close icon, add color to attempt collapsible dot * try percy set viewport width * set default retries back to {runMode:0, openMode:0} * formatting: add backticks to warning message * write explicit test for screenshot overwriting behavior, fix snapshots after changing retries defaults * fix e2e.it.only` * cleanup whitespace * update snapshots * fix cypress module API types for new result schema * build and upload binary for test-retries branch too (linux) * add pre-release PR comment * fix pre-release commit comment * rename runner/cypress test * update retries.ui.spec test titles * fix after merge: use most recent attempt for before/after hooks * add suite title to hook error in runner/cypress tests Co-authored-by: Jennifer Shehane Co-authored-by: Brian Mann Co-authored-by: Gleb Bahmutov --- .eslintrc.json | 3 +- circle.yml | 80 +- cli/schema/cypress.schema.json | 12 + cli/types/cypress-npm-api.d.ts | 22 +- cli/types/cypress.d.ts | 12 + package.json | 1 + packages/desktop-gui/cypress/support/index.js | 2 +- .../integration/commands/screenshot_spec.js | 16 +- packages/driver/package.json | 5 + packages/driver/src/cy/commands/navigation.js | 3 + packages/driver/src/cy/commands/screenshot.js | 3 + packages/driver/src/cypress.js | 39 +- packages/driver/src/cypress/cy.js | 2 + packages/driver/src/cypress/error_messages.js | 31 + packages/driver/src/cypress/log.js | 23 +- packages/driver/src/cypress/mocha.js | 149 +- packages/driver/src/cypress/runner.js | 432 +- packages/driver/src/cypress/stack_utils.js | 42 +- packages/driver/src/cypress/utils.js | 18 +- .../cypress/integration/aliases_spec.js | 1 + .../cypress/integration/shortcuts_spec.ts | 2 +- packages/reporter/cypress/support/util.js | 29 + .../reporter/src/attempts/attempt-model.ts | 201 + packages/reporter/src/attempts/attempts.scss | 150 + packages/reporter/src/attempts/attempts.tsx | 102 + .../src/collapsible/collapsible.spec.tsx | 1 - .../reporter/src/collapsible/collapsible.tsx | 1 - .../reporter/src/commands/command-model.ts | 3 - packages/reporter/src/commands/commands.scss | 1 + packages/reporter/src/errors/test-error.tsx | 7 +- packages/reporter/src/header/stats-store.ts | 1 + .../src/instruments/instrument-model.ts | 3 +- packages/reporter/src/lib/base.scss | 5 + packages/reporter/src/lib/events.spec.ts | 21 +- packages/reporter/src/lib/events.ts | 21 +- packages/reporter/src/lib/mixins.scss | 32 + packages/reporter/src/lib/variables.scss | 1 + packages/reporter/src/main-runner.scss | 1 + packages/reporter/src/main.scss | 1 + .../src/runnables/runnable-and-suite.spec.tsx | 2 +- .../src/runnables/runnable-and-suite.tsx | 1 + .../reporter/src/runnables/runnable-model.ts | 4 +- .../src/runnables/runnables-store.spec.ts | 72 +- .../reporter/src/runnables/runnables-store.ts | 77 +- .../reporter/src/runnables/runnables.scss | 37 +- .../reporter/src/runnables/runnables.spec.tsx | 4 +- .../src/runnables/suite-model.spec.ts | 2 +- .../reporter/src/runnables/suite-model.ts | 4 + packages/reporter/src/test/test-model.spec.ts | 308 +- packages/reporter/src/test/test-model.ts | 252 +- packages/reporter/src/test/test.spec.tsx | 121 +- packages/reporter/src/test/test.tsx | 65 +- .../__snapshots__/retries.mochaEvents.spec.js | 6458 +++++++++++++++++ .../__snapshots__/runner.mochaEvents.spec.js | 1338 ++-- .../integration/reporter.errors.spec.js | 2 +- .../integration/retries.mochaEvents.spec.js | 338 + .../cypress/integration/retries.ui.spec.js | 491 ++ .../integration/runner.mochaEvents.spec.js | 27 +- .../cypress/integration/runner.ui.spec.js | 51 + packages/runner/cypress/plugins/index.js | 3 + .../plugins/snapshot/snapshotCommand.js | 2 +- packages/runner/cypress/support/helpers.js | 128 +- packages/runner/cypress/support/index.js | 1 + .../1_caught_uncaught_hook_errors_spec.js | 8 +- .../server/__snapshots__/3_plugins_spec.js | 139 +- .../server/__snapshots__/3_retries_spec.ts.js | 134 + .../3_runnable_execution_spec.ts.js | 38 +- .../__snapshots__/5_screenshots_spec.js | 45 +- .../__snapshots__/5_spec_isolation_spec.js | 1087 ++- .../server/__snapshots__/7_record_spec.js | 68 +- .../server/__snapshots__/8_reporters_spec.js | 12 +- .../server/__snapshots__/reporter_spec.js | 45 +- packages/server/lib/api.js | 2 +- packages/server/lib/browsers/firefox-util.ts | 9 + packages/server/lib/config.js | 5 +- packages/server/lib/errors.js | 12 +- packages/server/lib/modes/record.js | 1 - packages/server/lib/modes/run.js | 16 +- packages/server/lib/plugins/index.js | 12 + packages/server/lib/reporter.js | 116 +- packages/server/lib/screenshots.js | 6 +- packages/server/lib/util/stack_utils.ts | 44 + packages/server/lib/util/validation.js | 49 +- packages/server/package.json | 2 +- .../e2e/1_caught_uncaught_hook_errors_spec.js | 40 +- packages/server/test/e2e/3_plugins_spec.js | 9 + packages/server/test/e2e/3_retries_spec.ts | 21 + .../test/e2e/3_runnable_execution_spec.ts | 2 +- .../server/test/e2e/5_screenshots_spec.js | 2 +- .../server/test/e2e/5_spec_isolation_spec.js | 83 +- packages/server/test/e2e/7_record_spec.js | 18 +- packages/server/test/e2e/8_reporters_spec.js | 10 +- .../fixtures/projects/e2e/cypress.json | 4 +- .../cypress/integration/screenshots_spec.js | 7 + .../projects/e2e/cypress/plugins/index.js | 9 +- .../projects/e2e/cypress/support/index.js | 9 +- .../projects/e2e/cypress/support/util.js | 2 +- .../beforehook-and-test-navigation.js | 12 + .../after_screenshot_overwrite_spec.coffee | 8 + .../projects/plugin-retries/cypress.json | 1 + .../cypress/integration/main.spec.js | 2 + .../read-only-project-root/cypress.json | 4 +- .../fixtures/projects/retries-2/cypress.json | 3 + .../cypress/integration/fail-twice.js | 8 + .../retries-2/cypress/plugins/index.js | 14 + .../projects/task-not-registered/cypress.json | 4 +- .../test/support/fixtures/projects/utils.js | 11 + .../cypress.json | 4 +- .../cypress.json | 4 +- .../cypress.json | 4 +- .../webpack-preprocessor/cypress.json | 4 +- packages/server/test/support/helpers/e2e.ts | 13 +- .../server/test/support/helpers/fixtures.js | 8 + packages/server/test/unit/api_spec.js | 6 +- packages/server/test/unit/config_spec.js | 2 + packages/server/test/unit/modes/run_spec.js | 8 +- packages/server/test/unit/reporter_spec.js | 2 +- .../cypress/support/customPercyCommand.js | 51 + scripts/wait-on-circle-jobs.js | 167 + yarn.lock | 177 +- 120 files changed, 12037 insertions(+), 1813 deletions(-) create mode 100644 packages/reporter/cypress/support/util.js create mode 100644 packages/reporter/src/attempts/attempt-model.ts create mode 100644 packages/reporter/src/attempts/attempts.scss create mode 100644 packages/reporter/src/attempts/attempts.tsx create mode 100644 packages/reporter/src/lib/mixins.scss create mode 100644 packages/runner/__snapshots__/retries.mochaEvents.spec.js create mode 100644 packages/runner/cypress/integration/retries.mochaEvents.spec.js create mode 100644 packages/runner/cypress/integration/retries.ui.spec.js create mode 100644 packages/server/__snapshots__/3_retries_spec.ts.js create mode 100644 packages/server/lib/util/stack_utils.ts create mode 100644 packages/server/test/e2e/3_retries_spec.ts create mode 100644 packages/server/test/support/fixtures/projects/plugin-after-screenshot/cypress/integration/after_screenshot_overwrite_spec.coffee create mode 100644 packages/server/test/support/fixtures/projects/plugin-retries/cypress.json create mode 100644 packages/server/test/support/fixtures/projects/plugin-retries/cypress/integration/main.spec.js create mode 100644 packages/server/test/support/fixtures/projects/retries-2/cypress.json create mode 100644 packages/server/test/support/fixtures/projects/retries-2/cypress/integration/fail-twice.js create mode 100644 packages/server/test/support/fixtures/projects/retries-2/cypress/plugins/index.js create mode 100644 packages/server/test/support/fixtures/projects/utils.js create mode 100644 packages/ui-components/cypress/support/customPercyCommand.js create mode 100644 scripts/wait-on-circle-jobs.js diff --git a/.eslintrc.json b/.eslintrc.json index 5da4255366f5..4d6129e600ad 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -6,7 +6,8 @@ "plugin:@cypress/dev/general" ], "rules": { - "prefer-spread": "off" + "prefer-spread": "off", + "prefer-rest-params": "off" }, "settings": { "react": { diff --git a/circle.yml b/circle.yml index 53d81dcbd7f0..506a08063c09 100644 --- a/circle.yml +++ b/circle.yml @@ -129,14 +129,22 @@ commands: browser: description: browser shortname to target type: string + percy: + description: enable percy + type: boolean + default: false steps: - attach_workspace: at: ~/ - run: command: | + cmd=$([[ <> == 'true' ]] && echo 'yarn percy exec --') || true + CYPRESS_KONFIG_ENV=production \ CYPRESS_RECORD_KEY=$PACKAGES_RECORD_KEY \ - yarn workspace @packages/runner cypress:run --record --parallel --group runner-integration-<> --browser <> + PERCY_PARALLEL_NONCE=$CIRCLE_WORKFLOW_ID \ + PERCY_PARALLEL_TOTAL=-1 \ + $cmd yarn workspace @packages/runner cypress:run --record --parallel --group runner-integration-<> --browser <> - store_test_results: path: /tmp/cypress - store_artifacts: @@ -211,6 +219,7 @@ commands: command: | git clone --depth 1 --no-single-branch https://github.com/cypress-io/<>.git /tmp/<> cd /tmp/<> && (git checkout $NEXT_DEV_VERSION || true) + test-binary-against-repo: description: | Takes the built binary and NPM package, clones given example repo @@ -337,6 +346,17 @@ commands: path: /tmp/<>/cypress/videos - store-npm-logs + wait-on-circle-jobs: + description: Polls certain Circle CI jobs until they finish + parameters: + job-names: + description: comma separated list of circle ci job names to wait for + type: string + steps: + - run: + name: "Waiting on Circle CI jobs: <>" + command: node ./scripts/wait-on-circle-jobs.js --job-names="<>" + jobs: ## code checkout and yarn installs build: @@ -400,6 +420,37 @@ jobs: command: node cli/bin/cypress info --dev - store-npm-logs + # a special job that keeps polling Circle and when all + # individual jobs are finished, it closes the Percy build + percy-finalize: + <<: *defaults + executor: cy-doc + parameters: + required_env_var: + type: env_var_name + steps: + - attach_workspace: + at: ~/ + - run: + # if this is an external pull request, the environment variables + # are NOT set for security reasons, thus no need to poll - + # and no need to finalize Percy, since there will be no visual tests + name: Check if <> is set + command: | + if [[ -v <> ]]; then + echo "Internal PR, good to go" + else + echo "This is an external PR, cannot access other services" + circleci-agent step halt + fi + - wait-on-circle-jobs: + job-names: > + desktop-gui-integration-tests-2x, + desktop-gui-component-tests, + cli-visual-tests, + runner-integration-tests-chrome, + - run: npx percy finalize --all + cli-visual-tests: <<: *defaults parallelism: 1 @@ -701,6 +752,7 @@ jobs: steps: - run-runner-integration-tests: browser: chrome + percy: true runner-integration-tests-firefox: <<: *defaults @@ -879,13 +931,6 @@ jobs: command: node index.js working_directory: packages/launcher - percy-finalize: - <<: *defaults - steps: - - run: - name: "finalizes percy builds" - command: npx percy finalize --all - build-binary: <<: *defaults shell: /bin/bash --login @@ -1369,6 +1414,11 @@ jobs: command: npm run test:ci pull_request_id: 515 folder: examples/fundamentals__typescript + - test-binary-against-repo: + repo: cypress-example-recipes + command: npm test + pull_request_id: 513 + folder: examples/fundamentals__module-api-wrap "test-binary-against-kitchensink": <<: *defaults @@ -1493,6 +1543,11 @@ linux-workflow: &linux-workflow name: Linux lint requires: - build + - percy-finalize: + context: test-runner:poll-circle-workflow + required_env_var: PERCY_TOKEN # skips job if not defined (external PR) + requires: + - build - lint-types: requires: - build @@ -1624,12 +1679,6 @@ linux-workflow: &linux-workflow requires: - build - - percy-finalize: - requires: - - desktop-gui-integration-tests-2x - - desktop-gui-component-tests - - cli-visual-tests - # various testing scenarios, like building full binary # and testing it on a real project - test-against-staging: @@ -1666,6 +1715,7 @@ linux-workflow: &linux-workflow branches: only: - develop + - test-retries requires: - build-npm-package - build-binary: @@ -1677,6 +1727,7 @@ linux-workflow: &linux-workflow branches: only: - develop + - test-retries requires: - build-binary - test-npm-module-on-minimum-node-version: @@ -1733,6 +1784,7 @@ linux-workflow: &linux-workflow branches: only: - develop + - test-retries requires: - upload-npm-package - upload-binary diff --git a/cli/schema/cypress.schema.json b/cli/schema/cypress.schema.json index 7a7365b1bf7e..12b7c37052e3 100644 --- a/cli/schema/cypress.schema.json +++ b/cli/schema/cypress.schema.json @@ -243,6 +243,18 @@ "type": "boolean", "default": false, "description": "Polyfills `window.fetch` to enable Network spying and stubbing" + }, + "retries": { + "type": [ + "object", + "number", + "null" + ], + "default": { + "runMode": 0, + "openMode": 0 + }, + "description": "The number of times to retry a failing. Can be configured to apply only in runMode or openMode" } } } diff --git a/cli/types/cypress-npm-api.d.ts b/cli/types/cypress-npm-api.d.ts index 40721b8fc0c9..805bb2ca3ca0 100644 --- a/cli/types/cypress-npm-api.d.ts +++ b/cli/types/cypress-npm-api.d.ts @@ -7,6 +7,11 @@ // but for now describe it as an ambient module declare namespace CypressCommandLine { + interface TestError { + name: string + message: string + stack: string + } /** * All options that one can pass to "cypress.run" * @see https://on.cypress.io/module-api#cypress-run @@ -166,14 +171,16 @@ declare namespace CypressCommandLine { title: string[] state: string body: string - /** - * Error stack string if there is an error - */ - stack: string | null - /** - * Error message if there is an error + /** + * Error string as it's presented in console if the test fails */ - error: string | null + displayError: string | null + attempts: AttemptResult[] + } + + interface AttemptResult { + state: string + error: TestError | null timings: any failedFromHookId: hookId | null wallClockStartedAt: dateTimeISO @@ -199,6 +206,7 @@ declare namespace CypressCommandLine { name: string testId: testId takenAt: dateTimeISO + testAttemptIndex: number /** * Absolute path to the saved image */ diff --git a/cli/types/cypress.d.ts b/cli/types/cypress.d.ts index 78913827033b..a37ca35278f2 100644 --- a/cli/types/cypress.d.ts +++ b/cli/types/cypress.d.ts @@ -334,6 +334,11 @@ declare namespace Cypress { */ getFirefoxGcInterval(): number | null | undefined + /** + * @returns the number of test retries currently enabled for the run + */ + getTestRetries(): number | null + /** * Checks if a variable is a valid instance of `cy` or a `cy` chainable. * @@ -2563,6 +2568,13 @@ declare namespace Cypress { * the `includeShadowDom` option to some DOM commands. */ experimentalShadowDomSupport: boolean + /** + * Number of times to retry a failed test. + * If a number is set, tests will retry in both runMode and openMode. + * To enable test retries only in runMode, set e.g. `{ openMode: null, runMode: 2 }` + * @default null + */ + retries: Nullable, openMode: Nullable}> } interface TestConfigOverrides extends Partial> { diff --git a/package.json b/package.json index 325cbfbfb62f..546cfe200c91 100644 --- a/package.json +++ b/package.json @@ -136,6 +136,7 @@ "fs-extra": "8.1.0", "gift": "0.10.2", "globby": "10.0.1", + "got": "11.5.1", "gulp": "4.0.2", "gulp-awspublish": "4.0.0", "gulp-debug": "4.0.0", diff --git a/packages/desktop-gui/cypress/support/index.js b/packages/desktop-gui/cypress/support/index.js index f81291bfc71f..026e9b5ba5ed 100644 --- a/packages/desktop-gui/cypress/support/index.js +++ b/packages/desktop-gui/cypress/support/index.js @@ -1,4 +1,4 @@ -require('@percy/cypress') +require('@packages/ui-components/cypress/support/customPercyCommand') require('cypress-react-unit-test/dist/hooks') const BluebirdPromise = require('bluebird') diff --git a/packages/driver/cypress/integration/commands/screenshot_spec.js b/packages/driver/cypress/integration/commands/screenshot_spec.js index dcb584a8d9d3..8e65dcd7315e 100644 --- a/packages/driver/cypress/integration/commands/screenshot_spec.js +++ b/packages/driver/cypress/integration/commands/screenshot_spec.js @@ -19,6 +19,7 @@ describe('src/cy/commands/screenshot', () => { takenAt: new Date().toISOString(), name: 'name', blackout: ['.foo'], + testAttemptIndex: 0, duration: 100, } @@ -49,7 +50,7 @@ describe('src/cy/commands/screenshot', () => { Cypress.action('runner:runnable:after:run:async', test, runnable) .then(() => { - expect(Cypress.action).not.to.be.calledWith('cy:test:set:state') + expect(Cypress.action).not.to.be.calledWith('test:set:state') expect(Cypress.automation).not.to.be.called }) .finally(() => { @@ -68,7 +69,7 @@ describe('src/cy/commands/screenshot', () => { Cypress.action('runner:runnable:after:run:async', test, runnable) .then(() => { - expect(Cypress.action).not.to.be.calledWith('cy:test:set:state') + expect(Cypress.action).not.to.be.calledWith('test:set:state') expect(Cypress.automation).not.to.be.called }) }) @@ -89,7 +90,7 @@ describe('src/cy/commands/screenshot', () => { Cypress.action('runner:runnable:after:run:async', test, runnable) .then(() => { - expect(Cypress.action).not.to.be.calledWith('cy:test:set:state') + expect(Cypress.action).not.to.be.calledWith('test:set:state') expect(Cypress.automation).not.to.be.called }) }) @@ -137,6 +138,7 @@ describe('src/cy/commands/screenshot', () => { waitForCommandSynchronization: true, disableTimersAndAnimations: true, blackout: [], + testAttemptIndex: 0, }) expect(Cypress.action).to.be.calledWith('cy:after:screenshot', { @@ -147,6 +149,7 @@ describe('src/cy/commands/screenshot', () => { waitForCommandSynchronization: true, disableTimersAndAnimations: true, blackout: [], + testAttemptIndex: 0, }) }) }) @@ -183,6 +186,7 @@ describe('src/cy/commands/screenshot', () => { testFailure: true, blackout: [], scaled: true, + testAttemptIndex: 0, }) }) }) @@ -225,6 +229,7 @@ describe('src/cy/commands/screenshot', () => { simple: false, scaled: true, blackout: [], + testAttemptIndex: 0, }) }) }) @@ -264,6 +269,7 @@ describe('src/cy/commands/screenshot', () => { testFailure: true, scaled: true, blackout: [], + testAttemptIndex: 0, }) }) }) @@ -406,6 +412,7 @@ describe('src/cy/commands/screenshot', () => { waitForCommandSynchronization: false, disableTimersAndAnimations: true, blackout: ['.foo'], + testAttemptIndex: 0, }) }) }) @@ -425,6 +432,7 @@ describe('src/cy/commands/screenshot', () => { waitForCommandSynchronization: false, disableTimersAndAnimations: true, blackout: ['.foo'], + testAttemptIndex: 0, }) }) }) @@ -446,6 +454,7 @@ describe('src/cy/commands/screenshot', () => { waitForCommandSynchronization: true, disableTimersAndAnimations: true, blackout: [], + testAttemptIndex: 0, }) }) }) @@ -466,6 +475,7 @@ describe('src/cy/commands/screenshot', () => { waitForCommandSynchronization: false, disableTimersAndAnimations: true, blackout: ['.foo'], + testAttemptIndex: 0, }) }) }) diff --git a/packages/driver/package.json b/packages/driver/package.json index ec0a48cc8f7f..4b5d00a0b637 100644 --- a/packages/driver/package.json +++ b/packages/driver/package.json @@ -18,7 +18,12 @@ "@cypress/what-is-circular": "1.0.1", "@packages/network": "*", "@packages/runner": "*", + "@packages/server": "*", "@packages/ts": "*", + "@types/chalk": "^2.2.0", + "@types/common-tags": "^1.8.0", + "@types/lodash": "^4.14.123", + "@types/mocha": "^5.2.6", "angular": "1.8.0", "backbone": "1.4.0", "basic-auth": "2.0.1", diff --git a/packages/driver/src/cy/commands/navigation.js b/packages/driver/src/cy/commands/navigation.js index 7a234465a514..15f67911e3ec 100644 --- a/packages/driver/src/cy/commands/navigation.js +++ b/packages/driver/src/cy/commands/navigation.js @@ -842,6 +842,9 @@ module.exports = (Commands, Cypress, cy, state, config) => { if (previousDomainVisited && (remote.originPolicy !== existing.originPolicy)) { // if we've already visited a new superDomain // then die else we'd be in a terrible endless loop + // we also need to disable retries to prevent the endless loop + $utils.getTestFromRunnable(state('runnable'))._retries = 0 + return cannotVisitDifferentOrigin(remote.origin, previousDomainVisited, remote, existing, options._log) } diff --git a/packages/driver/src/cy/commands/screenshot.js b/packages/driver/src/cy/commands/screenshot.js index 708383cb6a96..d924bcbae8e2 100644 --- a/packages/driver/src/cy/commands/screenshot.js +++ b/packages/driver/src/cy/commands/screenshot.js @@ -7,6 +7,7 @@ const Promise = require('bluebird') const $Screenshot = require('../../cypress/screenshot') const $dom = require('../../dom') const $errUtils = require('../../cypress/error_utils') +const $utils = require('../../cypress/utils') const getViewportHeight = (state) => { // TODO this doesn't seem correct @@ -54,6 +55,7 @@ const automateScreenshot = (state, options = {}) => { titles, testId: runnable.id, takenPaths: state('screenshotPaths'), + testAttemptIndex: $utils.getTestFromRunnable(runnable)._currentRetry, }, _.omit(options, 'runnable', 'timeout', 'log', 'subject')) const automate = () => { @@ -304,6 +306,7 @@ const takeScreenshot = (Cypress, state, screenshotConfig, options = {}) => { const getOptions = (isOpen) => { return { id: runnable.id, + testAttemptIndex: $utils.getTestFromRunnable(runnable)._currentRetry, isOpen, appOnly: isAppOnly(screenshotConfig), scale: getShouldScale(screenshotConfig), diff --git a/packages/driver/src/cypress.js b/packages/driver/src/cypress.js index 33c8494a66c5..280803024402 100644 --- a/packages/driver/src/cypress.js +++ b/packages/driver/src/cypress.js @@ -165,6 +165,19 @@ class $Cypress { this.config = $SetterGetter.create(config) this.env = $SetterGetter.create(env) this.getFirefoxGcInterval = $FirefoxForcedGc.createIntervalGetter(this.config) + this.getTestRetries = function () { + const testRetries = this.config('retries') + + if (_.isNumber(testRetries)) { + return testRetries + } + + if (_.isObject(testRetries)) { + return testRetries[this.config('isInteractive') ? 'openMode' : 'runMode'] + } + + return null + } this.Cookies = $Cookies.create(config.namespace, d) @@ -269,11 +282,6 @@ class $Cypress { break - case 'runner:set:runnable': - // when there is a hook / test (runnable) that - // is about to be invoked - return this.cy.setRunnable(...args) - case 'runner:suite:start': // mocha runner started processing a suite if (this.config('isTextTerminal')) { @@ -323,6 +331,8 @@ class $Cypress { case 'runner:pass': // mocha runner calculated a pass + // this is delayed from when mocha would normally fire it + // since we fire it after all afterEach hooks have ran if (this.config('isTextTerminal')) { return this.emit('mocha', 'pass', ...args) } @@ -359,6 +369,16 @@ class $Cypress { break } + // retry event only fired in mocha version 6+ + // https://github.com/mochajs/mocha/commit/2a76dd7589e4a1ed14dd2a33ab89f182e4c4a050 + case 'runner:retry': { + // mocha runner calculated a pass + if (this.config('isTextTerminal')) { + this.emit('mocha', 'retry', ...args) + } + + break + } case 'mocha:runnable:run': return this.runner.onRunnableRun(...args) @@ -367,7 +387,14 @@ class $Cypress { // get back to a clean slate this.cy.reset(...args) - return this.emit('test:before:run', ...args) + if (this.config('isTextTerminal')) { + // needed for handling test retries + this.emit('mocha', 'test:before:run', args[0]) + } + + this.emit('test:before:run', ...args) + + break case 'runner:test:before:run:async': // TODO: handle timeouts here? or in the runner? diff --git a/packages/driver/src/cypress/cy.js b/packages/driver/src/cypress/cy.js index 3cdbc2052d18..66708bad974e 100644 --- a/packages/driver/src/cypress/cy.js +++ b/packages/driver/src/cypress/cy.js @@ -1282,6 +1282,8 @@ const create = function (specWindow, Cypress, Cookies, state, config, log) { state('runnable', runnable) + state('test', $utils.getTestFromRunnable(runnable)) + state('ctx', runnable.ctx) const { fn } = runnable diff --git a/packages/driver/src/cypress/error_messages.js b/packages/driver/src/cypress/error_messages.js index 6a39e79a8e7a..1436bc622c31 100644 --- a/packages/driver/src/cypress/error_messages.js +++ b/packages/driver/src/cypress/error_messages.js @@ -884,6 +884,33 @@ module.exports = { {{error}}`, docsUrl: 'https://on.cypress.io/returning-promise-and-invoking-done-callback', }, + manually_set_retries_test: stripIndent`\ + Mocha \`this.retries()\` syntax is not supported. + + To configure retries use the following syntax: + + \`\`\` + it('{{title}}', { retries: {{numRetries}} }, () => { + ... + }) + \`\`\` + + https://on.cypress.io/test-retries + `, + manually_set_retries_suite: stripIndent`\ + Mocha \`this.retries()\` syntax is not supported. + + To configure retries use the following syntax: + + \`\`\` + describe('{{title}}', { retries: {{numRetries}} }, () => { + ... + }) + \`\`\` + + https://on.cypress.io/test-retries + `, + }, navigation: { @@ -1595,6 +1622,10 @@ module.exports = { msg += 'all of the remaining tests.' } + if ((obj.hookName === 'after all' || obj.hookName === 'before all') && obj.retries > 0) { + msg += `\n\nAlthough you have test retries enabled, we do not retry tests when \`before all\` or \`after all\` hooks fail` + } + return msg }, error (obj) { diff --git a/packages/driver/src/cypress/log.js b/packages/driver/src/cypress/log.js index 363856aec9b1..0d8a91aab247 100644 --- a/packages/driver/src/cypress/log.js +++ b/packages/driver/src/cypress/log.js @@ -13,7 +13,7 @@ const $errUtils = require('./error_utils') const groupsOrTableRe = /^(groups|table)$/ const parentOrChildRe = /parent|child/ const SNAPSHOT_PROPS = 'id snapshots $el url coords highlightAttr scrollBy viewportWidth viewportHeight'.split(' ') -const DISPLAY_PROPS = 'id alias aliasType callCount displayName end err event functionName hookId instrument isStubbed message method name numElements numResponses referencesAlias renderProps state testId timeout type url visible wallClockStartedAt'.split(' ') +const DISPLAY_PROPS = 'id alias aliasType callCount displayName end err event functionName hookId instrument isStubbed message method name numElements numResponses referencesAlias renderProps state testId timeout type url visible wallClockStartedAt testCurrentRetry'.split(' ') const BLACKLIST_PROPS = 'snapshots'.split(' ') let delay = null @@ -90,10 +90,12 @@ const countLogsByTests = function (tests = {}) { return _ .chain(tests) - .map((test, key) => { - return [].concat(test.agents, test.routes, test.commands) - }).flatten() - .compact() + .flatMap((test) => { + return [test, test.prevAttempts] + }) + .flatMap((tests) => { + return [].concat(tests.agents, tests.routes, tests.commands) + }).compact() .union([{ id: 0 }]) .map('id') .max() @@ -167,6 +169,16 @@ const defaults = function (state, config, obj) { const runnable = state('runnable') + const getTestAttemptFromRunnable = (runnable) => { + if (!runnable) { + return + } + + const t = $utils.getTestFromRunnable(runnable) + + return t._currentRetry || 0 + } + return _.defaults(obj, { id: (counter += 1), state: 'pending', @@ -174,6 +186,7 @@ const defaults = function (state, config, obj) { url: state('url'), hookId: state('hookId'), testId: runnable ? runnable.id : undefined, + testCurrentRetry: getTestAttemptFromRunnable(state('runnable')), viewportWidth: state('viewportWidth'), viewportHeight: state('viewportHeight'), referencesAlias: undefined, diff --git a/packages/driver/src/cypress/mocha.js b/packages/driver/src/cypress/mocha.js index 04a248533ef0..bb44c2ef8313 100644 --- a/packages/driver/src/cypress/mocha.js +++ b/packages/driver/src/cypress/mocha.js @@ -1,5 +1,8 @@ +/* eslint-disable prefer-rest-params */ + const _ = require('lodash') const $errUtils = require('./error_utils') +const { getTestFromRunnable } = require('./utils') const $stackUtils = require('./stack_utils') // in the browser mocha is coming back @@ -7,13 +10,19 @@ const $stackUtils = require('./stack_utils') const mocha = require('mocha') const Mocha = mocha.Mocha != null ? mocha.Mocha : mocha -const { Test, Runner, Runnable } = Mocha +const { Test, Runner, Runnable, Hook, Suite } = Mocha const runnerRun = Runner.prototype.run const runnerFail = Runner.prototype.fail +const runnerRunTests = Runner.prototype.runTests const runnableRun = Runnable.prototype.run const runnableClearTimeout = Runnable.prototype.clearTimeout const runnableResetTimeout = Runnable.prototype.resetTimeout +const testRetries = Test.prototype.retries +const testClone = Test.prototype.clone +const suiteAddTest = Suite.prototype.addTest +const suiteRetries = Suite.prototype.retries +const hookRetries = Hook.prototype.retries // don't let mocha polute the global namespace delete window.mocha @@ -241,6 +250,62 @@ const restoreRunnableRun = () => { Runnable.prototype.run = runnableRun } +const restoreSuiteRetries = () => { + Suite.prototype.retries = suiteRetries +} + +function restoreTestClone () { + Test.prototype.clone = testClone +} + +function restoreRunnerRunTests () { + Runner.prototype.runTests = runnerRunTests +} + +function restoreSuiteAddTest () { + Mocha.Suite.prototype.addTest = suiteAddTest +} +const restoreHookRetries = () => { + Hook.prototype.retries = hookRetries +} + +const patchSuiteRetries = () => { + Suite.prototype.retries = function (...args) { + if (args[0] !== undefined && args[0] > -1) { + const err = $errUtils.cypressErrByPath('mocha.manually_set_retries_suite', { + args: { + title: this.title, + numRetries: args[0] ?? 2, + }, + }) + + throw err + } + + return suiteRetries.apply(this, args) + } +} + +const patchHookRetries = () => { + Hook.prototype.retries = function (...args) { + if (args[0] !== undefined && args[0] > -1) { + const err = $errUtils.cypressErrByPath('mocha.manually_set_retries_suite', { + args: { + title: this.parent.title, + numRetries: args[0] ?? 2, + }, + }) + + // so this error doesn't cause a retry + getTestFromRunnable(this)._retries = -1 + + throw err + } + + return hookRetries.apply(this, args) + } +} + // matching the current Runner.prototype.fail except // changing the logic for determing whether this is a valid err const patchRunnerFail = () => { @@ -274,6 +339,50 @@ const patchRunnableRun = (Cypress) => { } } +function patchTestClone () { + Test.prototype.clone = function () { + if (this._retriesBeforeEachFailedTestFn) { + this.fn = this._retriesBeforeEachFailedTestFn + } + + const ret = testClone.apply(this, arguments) + + // carry over testConfigOverrides + ret.cfg = this.cfg + + // carry over test.id + ret.id = this.id + + return ret + } +} + +function patchRunnerRunTests () { + Runner.prototype.runTests = function () { + const suite = arguments[0] + + const _slice = suite.tests.slice + + // HACK: we need to dynamically enqueue tests to suite.tests during a test run + // however Mocha calls `.slice` on this property and thus we no longer have a reference + // to the internal test queue. So we replace the .slice method + // in a way that we keep a reference to the returned array. we name it suite.testsQueue + suite.tests.slice = function () { + this.slice = _slice + + const ret = _slice.apply(this, arguments) + + suite.testsQueue = ret + + return ret + } + + const ret = runnerRunTests.apply(this, arguments) + + return ret + } +} + const patchRunnableClearTimeout = () => { Runnable.prototype.clearTimeout = function (...args) { // call the original @@ -283,6 +392,34 @@ const patchRunnableClearTimeout = () => { } } +function patchSuiteAddTest (Cypress) { + Mocha.Suite.prototype.addTest = function (...args) { + const test = args[0] + + const ret = suiteAddTest.apply(this, args) + + test.retries = function (...args) { + if (args[0] !== undefined && args[0] > -1) { + const err = $errUtils.cypressErrByPath('mocha.manually_set_retries_test', { + args: { + title: test.title, + numRetries: args[0] ?? 2, + }, + }) + + // so this error doesn't cause a retry + test._retries = -1 + + throw err + } + + return testRetries.apply(this, args) + } + + return ret + } +} + const patchRunnableResetTimeout = () => { Runnable.prototype.resetTimeout = function () { const runnable = this @@ -319,6 +456,11 @@ const restore = () => { restoreRunnableRun() restoreRunnableClearTimeout() restoreRunnableResetTimeout() + restoreSuiteRetries() + restoreHookRetries() + restoreRunnerRunTests() + restoreTestClone() + restoreSuiteAddTest() } const override = (Cypress) => { @@ -326,6 +468,11 @@ const override = (Cypress) => { patchRunnableRun(Cypress) patchRunnableClearTimeout() patchRunnableResetTimeout() + patchSuiteRetries() + patchHookRetries() + patchRunnerRunTests() + patchTestClone() + patchSuiteAddTest(Cypress) } const create = (specWindow, Cypress, config) => { diff --git a/packages/driver/src/cypress/runner.js b/packages/driver/src/cypress/runner.js index ab8f6a6625c8..2f0178aa5919 100644 --- a/packages/driver/src/cypress/runner.js +++ b/packages/driver/src/cypress/runner.js @@ -18,7 +18,7 @@ const TEST_BEFORE_RUN_EVENT = 'runner:test:before:run' const TEST_AFTER_RUN_EVENT = 'runner:test:after:run' const RUNNABLE_LOGS = 'routes agents commands hooks'.split(' ') -const RUNNABLE_PROPS = 'id order title root hookName hookId err state failedFromHookId body speed type duration wallClockStartedAt wallClockDuration timings file originalTitle invocationDetails'.split(' ') +const RUNNABLE_PROPS = 'id order title root hookName hookId err state failedFromHookId body speed type duration wallClockStartedAt wallClockDuration timings file originalTitle invocationDetails final currentRetry retries'.split(' ') const debug = require('debug')('cypress:driver:runner') @@ -54,9 +54,8 @@ const runnableAfterRunAsync = (runnable, Cypress) => { runnable.clearTimeout() return Promise.try(() => { - if (!fired('runner:runnable:after:run:async', runnable)) { - return fire('runner:runnable:after:run:async', runnable, Cypress) - } + // NOTE: other events we do not fire more than once, but this needed to change for test-retries + return fire('runner:runnable:after:run:async', runnable, Cypress) }) } @@ -224,12 +223,14 @@ const findLastTestInSuite = (suite, fn = _.identity) => { const getAllSiblingTests = (suite, getTestById) => { const tests = [] - suite.eachTest((test) => { + suite.eachTest((testRunnable) => { // iterate through each of our suites tests. // this will iterate through all nested tests // as well. and then we add it only if its // in our filtered tests array - if (getTestById(test.id)) { + const test = getTestById(testRunnable.id) + + if (test) { return tests.push(test) } }) @@ -291,7 +292,7 @@ const lastTestThatWillRunInSuite = (test, tests) => { } const isLastTest = (test, tests) => { - return test === _.last(tests) + return test.id === _.get(_.last(tests), 'id') } const isRootSuite = (suite) => { @@ -308,73 +309,94 @@ const overrideRunnerHook = (Cypress, _runner, getTestById, getTest, setTest, get // monkey patch the hook event so we can wrap // 'test:after:run' around all of // the hooks surrounding a test runnable - const _runnerHook = _runner.hook - - _runner.hook = function (name, fn) { - const allTests = getTests() + // const _runnerHook = _runner.hook - const changeFnToRunAfterHooks = () => { - const originalFn = fn - - const test = getTest() - - fn = function () { - setTest(null) - - testAfterRun(test, Cypress) - - // and now invoke next(err) - return originalFn.apply(window, arguments) - } + _runner.hook = $utils.monkeypatchBefore(_runner.hook, function (name, fn) { + if (name !== 'afterAll' && name !== 'afterEach') { + return } - switch (name) { - case 'afterEach': { - const t = getTest() + const test = getTest() + const allTests = getTests() - // find all of the filtered _tests which share - // the same parent suite as our current _test - const tests = getAllSiblingTests(t.parent, getTestById) + let shouldFireTestAfterRun = _.noop - // make sure this test isnt the last test overall but also - // isnt the last test in our filtered parent suite's tests array - if (this.suite.root && (t !== _.last(allTests)) && (t !== _.last(tests))) { - changeFnToRunAfterHooks() + switch (name) { + case 'afterEach': + shouldFireTestAfterRun = () => { + // find all of the grep'd tests which share + // the same parent suite as our current test + const tests = getAllSiblingTests(test.parent, getTestById) + + if (this.suite.root) { + _runner._shouldBufferSuiteEnd = true + + // make sure this test isnt the last test overall but also + // isnt the last test in our filtered parent suite's tests array + if (test.final === false || (test !== _.last(allTests)) && (test !== _.last(tests))) { + return true + } + } } break - } - case 'afterAll': { - // find all of the filtered allTests which share - // the same parent suite as our current _test - const t = getTest() - - if (t) { - const siblings = getAllSiblingTests(t.parent, getTestById) - - // 1. if we're the very last test in the entire allTests - // we wait until the root suite fires - // 2. else if we arent the last nested suite we fire if we're - // the last test that will run - - if ( - (isRootSuite(this.suite) && isLastTest(t, allTests)) || - (!isLastSuite(this.suite, allTests) && lastTestThatWillRunInSuite(t, siblings)) - ) { - changeFnToRunAfterHooks() + case 'afterAll': + shouldFireTestAfterRun = () => { + // find all of the filtered allTests which share + // the same parent suite as our current _test + // const t = getTest() + + if (test) { + const siblings = getAllSiblingTests(test.parent, getTestById) + + // 1. if we're the very last test in the entire allTests + // we wait until the root suite fires + // 2. else if we arent the last nested suite we fire if we're + // the last test that will run + + if ( + (isRootSuite(this.suite) && isLastTest(test, allTests)) || + (!isLastSuite(this.suite, allTests) && lastTestThatWillRunInSuite(test, siblings)) + ) { + return true + } } } break - } default: break } - return _runnerHook.call(this, name, fn) - } + const newArgs = [name, $utils.monkeypatchBefore(fn, + function () { + if (!shouldFireTestAfterRun()) return + + setTest(null) + + if (test.final !== false) { + test.final = true + if (test.state === 'passed') { + Cypress.action('runner:pass', wrap(test)) + } + + Cypress.action('runner:test:end', wrap(test)) + + _runner._shouldBufferSuiteEnd = false + _runner._onTestAfterRun.map((fn) => { + return fn() + }) + + _runner._onTestAfterRun = [] + } + + testAfterRun(test, Cypress) + })] + + return newArgs + }) } const getTestResults = (tests) => { @@ -443,8 +465,6 @@ const normalizeAll = (suite, initialTests = {}, setTestsById, setTests, onRunnab const normalize = (runnable, tests, initialTests, onRunnable, onLogsById, getTestId, getHookId) => { const normalizeRunnable = (runnable) => { - let i - runnable.id = getTestId() // tests have a type of 'test' whereas suites do not have a type property @@ -458,8 +478,27 @@ const normalize = (runnable, tests, initialTests, onRunnable, onLogsById, getTes // if we have a runnable in the initial state // then merge in existing properties into the runnable - i = initialTests[runnable.id] + const i = initialTests[runnable.id] + + let prevAttempts + if (i) { + prevAttempts = [] + + if (i.prevAttempts) { + prevAttempts = _.map(i.prevAttempts, (test) => { + if (test) { + _.each(RUNNABLE_LOGS, (type) => { + return _.each(test[type], onLogsById) + }) + } + + // reduce this runnable down to its props + // and collections + return wrapAll(test) + }) + } + _.each(RUNNABLE_LOGS, (type) => { return _.each(i[type], onLogsById) }) @@ -472,7 +511,13 @@ const normalize = (runnable, tests, initialTests, onRunnable, onLogsById, getTes // reduce this runnable down to its props // and collections - return wrapAll(runnable) + const test = wrapAll(runnable) + + if (prevAttempts) { + test.prevAttempts = prevAttempts + } + + return test } const push = (test) => { @@ -506,7 +551,7 @@ const normalize = (runnable, tests, initialTests, onRunnable, onLogsById, getTes normalizedSuite.tests = _.map(suite._onlyTests, (test) => { const normalizedTest = normalizeRunnable(test, initialTests, onRunnable, onLogsById, getTestId, getHookId) - push(normalizedTest) + push(test) return normalizedTest }) @@ -541,17 +586,13 @@ const normalize = (runnable, tests, initialTests, onRunnable, onLogsById, getTes return normalizedRunnable } -const hookFailed = (hook, err, hookName, getTest, getTestFromHookOrFindTest) => { +const hookFailed = (hook, err, getTest, getTestFromHookOrFindTest) => { // NOTE: sometimes mocha will fail a hook without having emitted on('hook') // event, so this hook might not have currentTest set correctly // in which case we need to lookup the test const test = getTest() || getTestFromHookOrFindTest(hook) - test.err = err - test.state = 'failed' - test.duration = hook.duration // TODO: nope (?) - test.hookName = hookName // TODO: why are we doing this? - test.failedFromHookId = hook.hookId + setHookFailureProps(test, hook, err) if (hook.alreadyEmittedMocha) { test.alreadyEmittedMocha = true @@ -560,6 +601,17 @@ const hookFailed = (hook, err, hookName, getTest, getTestFromHookOrFindTest) => } } +const setHookFailureProps = (test, hook, err) => { + err = $errUtils.wrapErr(err) + const hookName = getHookName(hook) + + test.err = err + test.state = 'failed' + test.duration = hook.duration // TODO: nope (?) + test.hookName = hookName // TODO: why are we doing this? + test.failedFromHookId = hook.hookId +} + function getTestFromRunnable (runnable) { switch (runnable.type) { case 'hook': @@ -594,18 +646,31 @@ const _runnerListeners = (_runner, Cypress, _emissions, getTestById, getTest, se return Cypress.action('runner:suite:start', wrap(suite)) }) + _runner._shouldBufferSuiteEnd = false + _runner._onTestAfterRun = [] + _runner.on('suite end', (suite) => { + const handleSuiteEnd = () => { // cleanup our suite + its hooks - forceGc(suite) - eachHookInSuite(suite, forceGc) + forceGc(suite) + eachHookInSuite(suite, forceGc) - if (_emissions.ended[suite.id]) { - return + if (_emissions.ended[suite.id]) { + return + } + + _emissions.ended[suite.id] = true + + Cypress.action('runner:suite:end', wrap(suite)) } - _emissions.ended[suite.id] = true + if (_runner._shouldBufferSuiteEnd) { + _runner._onTestAfterRun = _runner._onTestAfterRun.concat([handleSuiteEnd]) - return Cypress.action('runner:suite:end', wrap(suite)) + return + } + + return handleSuiteEnd() }) _runner.on('hook', (hook) => { @@ -665,11 +730,22 @@ const _runnerListeners = (_runner, Cypress, _emissions, getTestById, getTest, se _emissions.ended[test.id] = true - return Cypress.action('runner:test:end', wrap(test)) + // NOTE: we wait to send 'test end' until after hooks run + // return Cypress.action('runner:test:end', wrap(test)) }) - _runner.on('pass', (test) => { - return Cypress.action('runner:pass', wrap(test)) + // Ignore the 'pass' event since we emit our own + // _runner.on('pass', (test) => { + // return Cypress.action('runner:pass', wrap(test)) + // }) + + /** + * Mocha retry event is only fired in Mocha version 6+ + * https://github.com/mochajs/mocha/commit/2a76dd7589e4a1ed14dd2a33ab89f182e4c4a050 + */ + _runner.on('retry', (test, err) => { + test.err = $errUtils.wrapErr(err) + Cypress.action('runner:retry', wrap(test), test.err) }) // if a test is pending mocha will only @@ -702,11 +778,13 @@ const _runnerListeners = (_runner, Cypress, _emissions, getTestById, getTest, se const tests = getAllSiblingTests(test.parent, getTestById) if (_.last(tests) !== test) { + test.final = true + return fire(TEST_AFTER_RUN_EVENT, test, Cypress) } }) - return _runner.on('fail', (runnable, err) => { + _runner.on('fail', (runnable, err) => { let hookName const isHook = runnable.type === 'hook' @@ -716,6 +794,7 @@ const _runnerListeners = (_runner, Cypress, _emissions, getTestById, getTest, se const parentTitle = runnable.parent.title hookName = getHookName(runnable) + const test = getTest() || getTestFromHookOrFindTest(runnable) // append a friendly message to the error indicating // we're skipping the remaining tests in this suite @@ -724,6 +803,7 @@ const _runnerListeners = (_runner, Cypress, _emissions, getTestById, getTest, se $errUtils.errByPath('uncaught.error_in_hook', { parentTitle, hookName, + retries: test._retries, }).message, ) } @@ -751,7 +831,7 @@ const _runnerListeners = (_runner, Cypress, _emissions, getTestById, getTest, se // if a hook fails (such as a before) then the test will never // get run and we'll need to make sure we set the test so that // the TEST_AFTER_RUN_EVENT fires correctly - return hookFailed(runnable, runnable.err, hookName, getTest, getTestFromHookOrFindTest) + return hookFailed(runnable, runnable.err, getTest, getTestFromHookOrFindTest) } }) } @@ -779,13 +859,18 @@ const create = (specWindow, mocha, Cypress, cy) => { const suite = hook.parent + let foundTest + if (hook.hookName === 'after all') { - return findLastTestInSuite(suite, isNotAlreadyRunTest) + foundTest = findLastTestInSuite(suite, isNotAlreadyRunTest) + } else if (hook.hookName === 'before all') { + foundTest = findTestInSuite(suite, isNotAlreadyRunTest) } - if (hook.hookName === 'before all') { - return findTestInSuite(suite, isNotAlreadyRunTest) - } + // if test has retried, we getTestById will give us the last attempt + foundTest = foundTest && getTestById(foundTest.id) + + return foundTest } const onScriptError = (err) => { @@ -837,6 +922,7 @@ const create = (specWindow, mocha, Cypress, cy) => { let _testsById = {} const _testsQueue = [] const _testsQueueById = {} + // only used during normalization const _runnables = [] const _logsById = {} let _emissions = { @@ -867,6 +953,7 @@ const create = (specWindow, mocha, Cypress, cy) => { } const onRunnable = (r) => { + // set defualt retries at onRunnable time instead of onRunnableRun return _runnables.push(r) } @@ -891,8 +978,103 @@ const create = (specWindow, mocha, Cypress, cy) => { return _testsById[id] } + const replaceRunnable = (runnable, id) => { + const testsQueueIndex = _.findIndex(_testsQueue, { id }) + + _testsQueue.splice(testsQueueIndex, 1, runnable) + + _testsQueueById[id] = runnable + + const testsIndex = _.findIndex(_tests, { id }) + + _tests.splice(testsIndex, 1, runnable) + + _testsById[id] = runnable + } + overrideRunnerHook(Cypress, _runner, getTestById, getTest, setTest, getTests) + // this forces mocha to enqueue a duplicate test in the case of test retries + const replacePreviousAttemptWith = (test) => { + const prevAttempt = _testsById[test.id] + + const prevAttempts = prevAttempt.prevAttempts || [] + + const newPrevAttempts = prevAttempts.concat([prevAttempt]) + + delete prevAttempt.prevAttempts + + test.prevAttempts = newPrevAttempts + + replaceRunnable(test, test.id) + } + + const maybeHandleRetry = (runnable, err) => { + const r = runnable + const isHook = r.type === 'hook' + const isTest = r.type === 'test' + const test = getTest() || getTestFromHook(runnable, getTestById) + const isBeforeEachHook = isHook && !!r.hookName.match(/before each/) + const isAfterEachHook = isHook && !!r.hookName.match(/after each/) + const retryAbleRunnable = isTest || isBeforeEachHook || isAfterEachHook + const willRetry = (test._currentRetry < test._retries) && retryAbleRunnable + + const fail = function () { + return err + } + const noFail = function () { + return + } + + if (err) { + if (willRetry) { + test.state = 'failed' + test.final = false + } + + if (willRetry && isBeforeEachHook) { + delete runnable.err + test._retriesBeforeEachFailedTestFn = test.fn + + // this prevents afterEach hooks that exist at a deeper level than the failing one from running + // we will always skip remaining beforeEach hooks since they will always be same level or deeper + test._skipHooksWithLevelGreaterThan = runnable.titlePath().length + setHookFailureProps(test, runnable, err) + test.fn = function () { + throw err + } + + return noFail() + } + + if (willRetry && isAfterEachHook) { + // if we've already failed this attempt from an afterEach hook then we've already enqueud another attempt + // so return early + if (test._retriedFromAfterEachHook) { + return noFail() + } + + setHookFailureProps(test, runnable, err) + + const newTest = test.clone() + + newTest._currentRetry = test._currentRetry + 1 + + test.parent.testsQueue.unshift(newTest) + + // this prevents afterEach hooks that exist at a deeper (or same) level than the failing one from running + test._skipHooksWithLevelGreaterThan = runnable.titlePath().length - 1 + test._retriedFromAfterEachHook = true + + Cypress.action('runner:retry', wrap(test), test.err) + + return noFail() + } + } + + return fail() + } + return { onScriptError, @@ -961,6 +1143,12 @@ const create = (specWindow, mocha, Cypress, cy) => { return _next() } + // first time seeing a retried test + // that hasn't already replaced our test + if (test._currentRetry > 0 && _testsById[test.id] !== test) { + replacePreviousAttemptWith(test) + } + // closure for calculating the actual // runtime of a runnables fn exection duration // and also the run of the runnable:after:run:async event @@ -998,6 +1186,27 @@ const create = (specWindow, mocha, Cypress, cy) => { // associated _runnables will share this state if (!fired(TEST_BEFORE_RUN_EVENT, test)) { fire(TEST_BEFORE_RUN_EVENT, test, Cypress) + + // this is the earliest we can set test._retries since test:before:run + // will load in testConfigOverrides (per test configuration) + const retries = Cypress.getTestRetries() ?? -1 + + test._retries = retries + } + + const isHook = runnable.type === 'hook' + + const isAfterEachHook = isHook && runnable.hookName.match(/after each/) + const isBeforeEachHook = isHook && runnable.hookName.match(/before each/) + + // if we've been told to skip hooks at a certain nested level + // this happens if we're handling a runnable that is going to retry due to failing in a hook + const shouldSkipRunnable = test._skipHooksWithLevelGreaterThan != null + && isHook + && (isBeforeEachHook || isAfterEachHook && runnable.titlePath().length > test._skipHooksWithLevelGreaterThan) + + if (shouldSkipRunnable) { + return _next.call(this) } const next = (err) => { @@ -1038,7 +1247,7 @@ const create = (specWindow, mocha, Cypress, cy) => { break } - return _next(err) + return _next.call(runnable, err) } const onNext = (err) => { @@ -1058,6 +1267,8 @@ const create = (specWindow, mocha, Cypress, cy) => { runnable.err = $errUtils.wrapErr(err) } + err = maybeHandleRetry(runnable, err) + return runnableAfterRunAsync(runnable, Cypress) .then(() => { // once we complete callback with the @@ -1163,21 +1374,13 @@ const create = (specWindow, mocha, Cypress, cy) => { // search through all of the tests // until we find the current test // and break then - for (let test of _tests) { - if (test.id === id) { + for (let testRunnable of _tests) { + if (testRunnable.id === id) { break } else { - test = wrapAll(test) + const test = serializeTest(testRunnable) - _.each(RUNNABLE_LOGS, (type) => { - let logs - - logs = test[type] - - if (logs) { - test[type] = _.map(logs, $Log.toSerializedJSON) - } - }) + test.prevAttempts = _.map(testRunnable.prevAttempts, serializeTest) tests[test.id] = test } @@ -1210,9 +1413,7 @@ const create = (specWindow, mocha, Cypress, cy) => { getDisplayPropsForLog: $Log.getDisplayProps, getConsolePropsForLogById (logId) { - let attrs - - attrs = _logsById[logId] + const attrs = _logsById[logId] if (attrs) { return $Log.getConsoleProps(attrs) @@ -1220,25 +1421,13 @@ const create = (specWindow, mocha, Cypress, cy) => { }, getSnapshotPropsForLogById (logId) { - let attrs - - attrs = _logsById[logId] + const attrs = _logsById[logId] if (attrs) { return $Log.getSnapshotProps(attrs) } }, - getErrorByTestId (testId) { - let test - - test = getTestById(testId) - - if (test) { - return $errUtils.wrapErr(test.err) - } - }, - resumeAtTest (id, emissions = {}) { _resumedAtTestIndex = getTestIndexFromId(id) @@ -1287,7 +1476,6 @@ const create = (specWindow, mocha, Cypress, cy) => { // we dont need to hold a log reference // to anything in memory when we're headless // because you cannot inspect any logs - let existing if (!isInteractive) { return @@ -1308,7 +1496,7 @@ const create = (specWindow, mocha, Cypress, cy) => { _testsQueue.push(test) } - existing = _logsById[attrs.id] + const existing = _logsById[attrs.id] if (existing) { // because log:state:changed may @@ -1343,6 +1531,24 @@ const create = (specWindow, mocha, Cypress, cy) => { } } +const mixinLogs = (test) => { + _.each(RUNNABLE_LOGS, (type) => { + const logs = test[type] + + if (logs) { + test[type] = _.map(logs, $Log.toSerializedJSON) + } + }) +} + +const serializeTest = (test) => { + const wrappedTest = wrapAll(test) + + mixinLogs(wrappedTest) + + return wrappedTest +} + module.exports = { create, } diff --git a/packages/driver/src/cypress/stack_utils.js b/packages/driver/src/cypress/stack_utils.js index ccb7daac37b4..08e9efff4637 100644 --- a/packages/driver/src/cypress/stack_utils.js +++ b/packages/driver/src/cypress/stack_utils.js @@ -3,6 +3,7 @@ const { codeFrameColumns } = require('@babel/code-frame') const errorStackParser = require('error-stack-parser') const path = require('path') +const { getStackLines, replacedStack, stackWithoutMessage, splitStack, unsplitStack } = require('@packages/server/lib/util/stack_utils') const $sourceMapUtils = require('./source_map_utils') const $utils = require('./utils') @@ -11,36 +12,6 @@ const stackLineRegex = /^\s*(at )?.*@?\(?.*\:\d+\:\d+\)?$/ const customProtocolRegex = /^[^:\/]+:\/+/ const STACK_REPLACEMENT_MARKER = '__stackReplacementMarker' -// returns tuple of [message, stack] -const splitStack = (stack) => { - const lines = stack.split('\n') - - return _.reduce(lines, (memo, line) => { - if (memo.messageEnded || stackLineRegex.test(line)) { - memo.messageEnded = true - memo[1].push(line) - } else { - memo[0].push(line) - } - - return memo - }, [[], []]) -} - -const unsplitStack = (messageLines, stackLines) => { - return _.castArray(messageLines).concat(stackLines).join('\n') -} - -const getStackLines = (stack) => { - const [, stackLines] = splitStack(stack) - - return stackLines -} - -const stackWithoutMessage = (stack) => { - return getStackLines(stack).join('\n') -} - const hasCrossFrameStacks = (specWindow) => { // get rid of the top lines since they naturally have different line:column const normalize = (stack) => { @@ -323,17 +294,6 @@ const normalizedUserInvocationStack = (userInvocationStack) => { return normalizeStackIndentation(winnowedStackLines) } -const replacedStack = (err, newStack) => { - // if err already lacks a stack or we've removed the stack - // for some reason, keep it stackless - if (!err.stack) return err.stack - - const errString = err.toString() - const stackLines = getStackLines(newStack) - - return unsplitStack(errString, stackLines) -} - module.exports = { getCodeFrame, getSourceStack, diff --git a/packages/driver/src/cypress/utils.js b/packages/driver/src/cypress/utils.js index 53344c4130af..8c1943d6d4f6 100644 --- a/packages/driver/src/cypress/utils.js +++ b/packages/driver/src/cypress/utils.js @@ -55,6 +55,18 @@ module.exports = { return console.log(...msgs) }, + monkeypatchBefore (origFn, fn) { + return function () { + const newArgs = fn.apply(this, arguments) + + if (newArgs !== undefined) { + return origFn.apply(this, newArgs) + } + + return origFn.apply(this, arguments) + } + }, + unwrapFirst (val) { // this method returns the first item in an array // and if its still a jquery object, then we return @@ -89,7 +101,7 @@ module.exports = { return _.reduce(props, (memo, prop) => { if (_.has(obj, prop) || obj[prop] !== undefined) { - memo[prop] = obj[prop] + memo[prop] = _.result(obj, prop) } return memo @@ -297,6 +309,10 @@ module.exports = { return Math.sqrt((deltaX * deltaX) + (deltaY * deltaY)) }, + getTestFromRunnable (r) { + return r.ctx.currentTest || r + }, + memoize (func, cacheInstance = new Map()) { const memoized = function (...args) { const key = args[0] diff --git a/packages/reporter/cypress/integration/aliases_spec.js b/packages/reporter/cypress/integration/aliases_spec.js index 852860b23275..1eacc6fc0b17 100644 --- a/packages/reporter/cypress/integration/aliases_spec.js +++ b/packages/reporter/cypress/integration/aliases_spec.js @@ -10,6 +10,7 @@ const addLog = function (runner, log) { renderProps: {}, state: 'passed', testId: 'r3', + testCurrentRetry: 0, type: 'parent', url: 'http://example.com', } diff --git a/packages/reporter/cypress/integration/shortcuts_spec.ts b/packages/reporter/cypress/integration/shortcuts_spec.ts index b1de68dd581e..2606d2fef04d 100644 --- a/packages/reporter/cypress/integration/shortcuts_spec.ts +++ b/packages/reporter/cypress/integration/shortcuts_spec.ts @@ -90,7 +90,7 @@ describe('controls', function () { // need to add an input since this environment is isolated $body.append('') }) - .get('#temp-input').type('r') + .get('#temp-input').type('r', { force: true }) .then(() => { expect(runner.emit).not.to.have.been.calledWith('runner:restart') }) diff --git a/packages/reporter/cypress/support/util.js b/packages/reporter/cypress/support/util.js new file mode 100644 index 000000000000..0b5cc710de4f --- /dev/null +++ b/packages/reporter/cypress/support/util.js @@ -0,0 +1,29 @@ +const _ = Cypress._ + +const sendLog = (runner, log, event) => { + const defaultLog = { + event: false, + hookName: 'test', + id: _.uniqueId('l'), + instrument: 'command', + renderProps: {}, + state: 'passed', + testId: 'r3', + type: 'parent', + url: 'http://example.com', + } + + runner.emit(event, _.extend(defaultLog, log)) +} + +export const updateLog = (runner, log) => { + sendLog(runner, log, 'reporter:log:state:changed') +} + +export const addLog = (runner, log) => { + sendLog(runner, log, 'reporter:log:add') +} + +export const addLogs = (runner, logs) => { + _.forEach(logs, addLog.bind(null, runner)) +} diff --git a/packages/reporter/src/attempts/attempt-model.ts b/packages/reporter/src/attempts/attempt-model.ts new file mode 100644 index 000000000000..cb8a9966cbf4 --- /dev/null +++ b/packages/reporter/src/attempts/attempt-model.ts @@ -0,0 +1,201 @@ +import _ from 'lodash' +import { action, computed, observable } from 'mobx' + +import Agent, { AgentProps } from '../agents/agent-model' +import Command, { CommandProps } from '../commands/command-model' +import Err from '../errors/err-model' +import Route, { RouteProps } from '../routes/route-model' +import Test, { UpdatableTestProps, TestProps, TestState } from '../test/test-model' +import Hook, { HookName } from '../hooks/hook-model' +import { FileDetails } from '@packages/ui-components' +import { LogProps } from '../runnables/runnables-store' +import Log from '../instruments/instrument-model' + +export default class Attempt { + @observable agents: Agent[] = [] + @observable commands: Command[] = [] + @observable err = new Err({}) + @observable hooks: Hook[] = [] + // TODO: make this an enum with states: 'QUEUED, ACTIVE, INACTIVE' + @observable isActive: boolean | null = null + @observable routes: Route[] = [] + @observable _state?: TestState | null = null + @observable _invocationCount: number = 0 + @observable invocationDetails?: FileDetails + @observable hookCount: { [name in HookName]: number } = { + 'before all': 0, + 'before each': 0, + 'after all': 0, + 'after each': 0, + 'test body': 0, + } + @observable _isOpen: boolean|null = null + + @observable isOpenWhenLast: boolean | null = null + _callbackAfterUpdate: Function | null = null + testId: string + + @observable id: number + test: Test + + _logs: {[key: string]: Log} = {} + + constructor (props: TestProps, test: Test) { + this.testId = props.id + this.id = props.currentRetry || 0 + this.test = test + this._state = props.state + this.err.update(props.err) + + this.invocationDetails = props.invocationDetails + + this.hooks = _.map(props.hooks, (hook) => new Hook(hook)) + + _.each(props.agents, this.addLog) + _.each(props.commands, this.addLog) + _.each(props.routes, this.addLog) + } + + @computed get hasCommands () { + return !!this.commands.length + } + + @computed get isLongRunning () { + return this.isActive && this._hasLongRunningCommand + } + + @computed get _hasLongRunningCommand () { + return _.some(this.commands, (command) => { + return command.isLongRunning + }) + } + + @computed get state () { + return this._state || (this.isActive ? 'active' : 'processing') + } + + @computed get isLast () { + return this.id === this.test.lastAttempt.id + } + + @computed get isOpen () { + if (this._isOpen !== null) { + return this._isOpen + } + + // prev attempts open by default while test is running, otherwise only the last is open + return this.test.isActive || this.isLast + } + + addLog = (props: LogProps) => { + switch (props.instrument) { + case 'command': { + return this._addCommand(props as CommandProps) + } + case 'agent': { + return this._addAgent(props as AgentProps) + } + case 'route': { + return this._addRoute(props as RouteProps) + } + default: { + throw new Error(`Attempted to add log for unknown instrument: ${props.instrument}`) + } + } + } + + updateLog (props: LogProps) { + const log = this._logs[props.id] + + if (log) { + log.update(props) + } + } + + commandMatchingErr () { + return _(this.hooks) + .map((hook) => { + return hook.commandMatchingErr(this.err) + }) + .compact() + .last() + } + + @action start () { + this.isActive = true + } + + @action update (props: UpdatableTestProps) { + if (props.state) { + this._state = props.state + } + + this.err.update(props.err) + + if (props.hookId) { + const hook = _.find(this.hooks, { hookId: props.hookId }) + + if (hook && props.err) { + hook.failed = true + } + } + + if (props.isOpen != null) { + this.isOpenWhenLast = props.isOpen + } + } + + @action finish (props: UpdatableTestProps) { + this.update(props) + this.isActive = false + } + + _addAgent (props: AgentProps) { + const agent = new Agent(props) + + this._logs[props.id] = agent + this.agents.push(agent) + + return agent + } + + _addRoute (props: RouteProps) { + const route = new Route(props) + + this._logs[props.id] = route + this.routes.push(route) + + return route + } + + _addCommand (props: CommandProps) { + const command = new Command(props) + + this._logs[props.id] = command + + this.commands.push(command) + + const hookIndex = _.findIndex(this.hooks, { hookId: command.hookId }) + + const hook = this.hooks[hookIndex] + + hook.addCommand(command) + + // make sure that hooks are in order of invocation + if (hook.invocationOrder === undefined) { + hook.invocationOrder = this._invocationCount++ + + if (hook.invocationOrder !== hookIndex) { + this.hooks[hookIndex] = this.hooks[hook.invocationOrder] + this.hooks[hook.invocationOrder] = hook + } + } + + // assign number if non existent + if (hook.hookNumber === undefined) { + hook.hookNumber = ++this.hookCount[hook.hookName] + } + + return command + } +} diff --git a/packages/reporter/src/attempts/attempts.scss b/packages/reporter/src/attempts/attempts.scss new file mode 100644 index 000000000000..c6c94e0a116c --- /dev/null +++ b/packages/reporter/src/attempts/attempts.scss @@ -0,0 +1,150 @@ +.reporter { + .attempts { + .attempt-item > .collapsible > .collapsible-header-wrapper { + display: none; + } + + &.has-multiple-attempts .attempt-item > .collapsible > .collapsible-header-wrapper { + display: flex; + } + } + + .attempt-item { + margin-bottom: 7px; + + > .collapsible { + position: relative; + margin-right: 20px; + .collapsible-header-inner { + outline: none; + } + + &:before { + border-left: 1px solid #dcdcdc; + content: ''; + left: 5px; + position: absolute; + top: 22px; + height: 15px; + } + + &.is-open:before { + display: none; + } + } + + &:last-child > .collapsible:before { + display: none; + } + + > .is-open .open-close-indicator { + i.fa-angle-down { + margin-top: 0; + } + + i.fa-angle-up { + order: 1; + margin-top: -4px; + } + } + + .open-close-indicator { + display: flex; + flex-direction: column; + + i { + margin-right: 5px; + + &.fa-angle-down { + margin-top: -4px; + } + } + } + } + + + .attempt-state-active { + .attempt-state { + + @include runnable-state-active; + } + } + .attempt-state-processing { + .attempt-state { + + @include runnable-state-processing; + } + } + .attempt-state-failed { + .attempt-state { + @include runnable-state-failed; + } + + .attempt-name:after { + color: $fail; + } + } + .attempt-state-passed { + .attempt-state { + + @include runnable-state-passed; + } + + + .attempt-name:after { + color: $pass; + } + } + + + .attempt-name { + display: flex; + justify-content: flex-end; + position: relative; + width: 100%; + + &:before { + border-top: 1px solid #dcdcdc; + content: ''; + left: 15px; + position: absolute; + right: 0; + top: 13px; + } + + &:after { + color: #a2a2a2; + content: '•'; + left: 3px; + position: absolute; + top: 6px; + } + + .attempt-tag { + align-items: center; + border: 1px solid #d5d5d5; + border-radius: 7px; + box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.20); + display: flex; + font-size: 11px; + padding: 2px 5px; + position: relative; + background-color: #fff; + user-select: none; + cursor: pointer; + + &:hover { + background-color: #e8e8e8; + } + } + + .collapsible-indicator, + .collapsible-more { + display: none; + } + + .attempt-state { + margin-left: 3px; + } + } +} diff --git a/packages/reporter/src/attempts/attempts.tsx b/packages/reporter/src/attempts/attempts.tsx new file mode 100644 index 000000000000..f2c13c7d2e0e --- /dev/null +++ b/packages/reporter/src/attempts/attempts.tsx @@ -0,0 +1,102 @@ +import cs from 'classnames' +import _ from 'lodash' +import { observer } from 'mobx-react' +import React, { Component } from 'react' + +import Agents from '../agents/agents' +import Collapsible from '../collapsible/collapsible' +import Hooks from '../hooks/hooks' +import Routes from '../routes/routes' +import TestError from '../errors/test-error' +import TestModel from '../test/test-model' +import AttemptModel from './attempt-model' + +const NoCommands = () => ( +
    +
  • + No commands were issued in this test. +
  • +
+) + +const AttemptHeader = ({ index }:{index: number}) => ( + + + + + + Attempt {index + 1} + + +) + +function renderAttemptContent (model: AttemptModel) { + // performance optimization - don't render contents if not open + + return ( +
+ + +
+ {model.hasCommands ? : } +
+ +
+ +
+
+ ) +} + +@observer +class Attempt extends Component<{model: AttemptModel, scrollIntoView: Function}> { + componentDidUpdate () { + this.props.scrollIntoView() + } + + render () { + const { model } = this.props + + // HACK: causes component update when command log is added + model.commands.length + + return ( +
  • + } + headerClass='attempt-name' + isOpen={model.isOpen} + > + {renderAttemptContent(model)} + +
  • + ) + } +} + +const Attempts = observer(({ test, scrollIntoView }: {test: TestModel, scrollIntoView: Function}) => { + return (
      + {_.map(test.attempts, (attempt) => { + return ( + + ) + })} +
    ) +}) + +export { Attempt, AttemptHeader, NoCommands } + +export default Attempts diff --git a/packages/reporter/src/collapsible/collapsible.spec.tsx b/packages/reporter/src/collapsible/collapsible.spec.tsx index 41996a32b1ad..f9b74eec2bc4 100644 --- a/packages/reporter/src/collapsible/collapsible.spec.tsx +++ b/packages/reporter/src/collapsible/collapsible.spec.tsx @@ -1,6 +1,5 @@ import React from 'react' import { shallow } from 'enzyme' - import Collapsible from './collapsible' describe('', () => { diff --git a/packages/reporter/src/collapsible/collapsible.tsx b/packages/reporter/src/collapsible/collapsible.tsx index e36363db36a5..c9d0533d651a 100644 --- a/packages/reporter/src/collapsible/collapsible.tsx +++ b/packages/reporter/src/collapsible/collapsible.tsx @@ -11,7 +11,6 @@ interface Props { headerExtras?: ReactNode containerRef?: RefObject contentClass?: string - toggleOpen?: (isOpen: boolean) => any } interface State { diff --git a/packages/reporter/src/commands/command-model.ts b/packages/reporter/src/commands/command-model.ts index aacafa18a744..6e4d344b6f05 100644 --- a/packages/reporter/src/commands/command-model.ts +++ b/packages/reporter/src/commands/command-model.ts @@ -110,9 +110,6 @@ export default class Command extends Instrument { if (this._becameNonPending()) { clearTimeout(this._pendingTimeout as TimeoutID) - action('became:inactive', () => { - return this.isLongRunning = false - })() } this._prevState = this.state diff --git a/packages/reporter/src/commands/commands.scss b/packages/reporter/src/commands/commands.scss index 3a5287a6a21a..7694559e58d5 100644 --- a/packages/reporter/src/commands/commands.scss +++ b/packages/reporter/src/commands/commands.scss @@ -222,6 +222,7 @@ .command-state-pending .command-number { i { + line-height: 18px; display: inline-block; } diff --git a/packages/reporter/src/errors/test-error.tsx b/packages/reporter/src/errors/test-error.tsx index ed18a7713b48..b2733676fb11 100644 --- a/packages/reporter/src/errors/test-error.tsx +++ b/packages/reporter/src/errors/test-error.tsx @@ -10,7 +10,7 @@ import ErrorStack from '../errors/error-stack' import events from '../lib/events' import FlashOnClick from '../lib/flash-on-click' import { onEnterOrSpace } from '../lib/util' -import TestModel from '../test/test-model' +import Attempt from '../attempts/attempt-model' interface DocsUrlProps { url: string | string[] @@ -31,7 +31,8 @@ const DocsUrl = ({ url }: DocsUrlProps) => { } interface TestErrorProps { - model: TestModel + model: Attempt + isTestError?: boolean } const TestError = observer((props: TestErrorProps) => { @@ -40,7 +41,7 @@ const TestError = observer((props: TestErrorProps) => { md.enable(['backticks', 'emphasis', 'escape']) const onPrint = () => { - events.emit('show:error', props.model.id) + events.emit('show:error', props.model) } const _onPrintClick = (e: MouseEvent) => { diff --git a/packages/reporter/src/header/stats-store.ts b/packages/reporter/src/header/stats-store.ts index e6b4cdd10fd0..b28446611c49 100644 --- a/packages/reporter/src/header/stats-store.ts +++ b/packages/reporter/src/header/stats-store.ts @@ -65,6 +65,7 @@ class StatsStore { this._currentTime = Date.now() } + @action incrementCount (type: TestState) { const countKey = `num${_.capitalize(type)}` diff --git a/packages/reporter/src/instruments/instrument-model.ts b/packages/reporter/src/instruments/instrument-model.ts index ac6a26fa381d..020e87a15781 100644 --- a/packages/reporter/src/instruments/instrument-model.ts +++ b/packages/reporter/src/instruments/instrument-model.ts @@ -16,10 +16,11 @@ export interface InstrumentProps { name?: string message?: string type?: string + testCurrentRetry: number state?: string | null referencesAlias?: Alias instrument?: 'agent' | 'command' | 'route' - testId: number + testId: string } export default class Log { diff --git a/packages/reporter/src/lib/base.scss b/packages/reporter/src/lib/base.scss index c5faa2e2242c..faa343748095 100644 --- a/packages/reporter/src/lib/base.scss +++ b/packages/reporter/src/lib/base.scss @@ -4,10 +4,15 @@ body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input .reporter { background-color: #F6F6F6; + bottom: 0; color: #555; display: flex; flex-direction: column; font-size: 12px; + left: 0; + position: absolute; + right: 0; + top: 0; * { box-sizing: border-box; diff --git a/packages/reporter/src/lib/events.spec.ts b/packages/reporter/src/lib/events.spec.ts index c5d6d1b70962..05b95ec857e5 100644 --- a/packages/reporter/src/lib/events.spec.ts +++ b/packages/reporter/src/lib/events.spec.ts @@ -217,11 +217,16 @@ describe('events', () => { expect(runnablesStore.runnableFinished).to.have.been.calledWith('the runnable') }) - it('increments the stats count on test:after:run', () => { - runner.on.withArgs('test:after:run').callArgWith(1, { state: 'passed' }) + it('increments the stats count on test:after:run if final: true', () => { + runner.on.withArgs('test:after:run').callArgWith(1, { state: 'passed', final: true }) expect(statsStore.incrementCount).to.have.been.calledWith('passed') }) + it('does not increment the stats count on test:after:run if not final: true', () => { + runner.on.withArgs('test:after:run').callArgWith(1, { state: 'passed' }) + expect(statsStore.incrementCount).not.to.have.been.called + }) + it('pauses the appState with next command name on paused', () => { runner.on.withArgs('paused').callArgWith(1, 'next command') expect(appState.pause).to.have.been.calledWith('next command') @@ -303,12 +308,12 @@ describe('events', () => { }) it('emits runner:console:error with test id on show:error', () => { - const err = { isCommandErr: false } + const test = { err: { isCommandErr: false } } - runnablesStore.testById.returns({ err }) - events.emit('show:error', 'test id') + runnablesStore.testById.returns(test) + events.emit('show:error', test) expect(runner.emit).to.have.been.calledWith('runner:console:error', { - err, + err: test.err, commandId: undefined, }) }) @@ -319,7 +324,7 @@ describe('events', () => { } } runnablesStore.testById.returns(test) - events.emit('show:error', 'test id') + events.emit('show:error', test) expect(runner.emit).to.have.been.calledWith('runner:console:error', { err: test.err, commandId: 'matching command id', @@ -332,7 +337,7 @@ describe('events', () => { } } runnablesStore.testById.returns(test) - events.emit('show:error', 'test id') + events.emit('show:error', test) expect(runner.emit).to.have.been.calledWith('runner:console:error', { err: test.err, commandId: undefined, diff --git a/packages/reporter/src/lib/events.ts b/packages/reporter/src/lib/events.ts index 156dffe89894..76105016ff78 100644 --- a/packages/reporter/src/lib/events.ts +++ b/packages/reporter/src/lib/events.ts @@ -4,7 +4,7 @@ import appState, { AppState } from './app-state' import runnablesStore, { RunnablesStore, RootRunnable, LogProps } from '../runnables/runnables-store' import statsStore, { StatsStore, StatsStoreStartInfo } from '../header/stats-store' import scroller, { Scroller } from './scroller' -import TestModel, { TestProps, UpdateTestCallback } from '../test/test-model' +import TestModel, { UpdatableTestProps, UpdateTestCallback, TestProps } from '../test/test-model' const localBus = new EventEmitter() @@ -93,17 +93,19 @@ const events: Events = { } })) - runner.on('test:before:run:async', action('test:before:run:async', (runnable: TestModel) => { + runner.on('test:before:run:async', action('test:before:run:async', (runnable: TestProps) => { runnablesStore.runnableStarted(runnable) })) - runner.on('test:after:run', action('test:after:run', (runnable: TestModel) => { + runner.on('test:after:run', action('test:after:run', (runnable: TestProps) => { runnablesStore.runnableFinished(runnable) - statsStore.incrementCount(runnable.state) + if (runnable.final) { + statsStore.incrementCount(runnable.state!) + } })) - runner.on('test:set:state', action('test:set:state', (runnable: TestProps, cb: UpdateTestCallback) => { - runnablesStore.updateTest(runnable, cb) + runner.on('test:set:state', action('test:set:state', (props: UpdatableTestProps, cb: UpdateTestCallback) => { + runnablesStore.updateTest(props, cb) })) runner.on('paused', action('paused', (nextCommandName: string) => { @@ -160,13 +162,12 @@ const events: Events = { runner.emit('runner:console:log', commandId) }) - localBus.on('show:error', (testId: number) => { - const test = runnablesStore.testById(testId) - const command = test.err.isCommandErr && test.commandMatchingErr() + localBus.on('show:error', (test: TestModel) => { + const command = test.err.isCommandErr ? test.commandMatchingErr() : null runner.emit('runner:console:error', { err: test.err, - commandId: command ? command.id : undefined, + commandId: command?.id, }) }) diff --git a/packages/reporter/src/lib/mixins.scss b/packages/reporter/src/lib/mixins.scss new file mode 100644 index 000000000000..98ebbc41e1c9 --- /dev/null +++ b/packages/reporter/src/lib/mixins.scss @@ -0,0 +1,32 @@ +@mixin runnable-state-active { + @extend .#{$fa-css-prefix}-sync-alt; + @extend .#{$fa-css-prefix}-spin; +} + +@mixin runnable-state-processing { + @extend .far; + @extend .#{$fa-css-prefix}-square; + color: #888; + line-height: 18px; // @extend .far overrides line-height, so we need to set it again + +} + +@mixin runnable-state-skipped { + @extend .#{$fa-css-prefix}-ban; + color: #888; +} + +@mixin runnable-state-failed { + @extend .#{$fa-css-prefix}-times; + color: $fail; +} + +@mixin runnable-state-passed { + @extend .#{$fa-css-prefix}-check; + color: $pass; +} + +@mixin runnable-state-pending { + @extend .#{$fa-css-prefix}-circle-notch; + color: lighten($pending, 20%); +} diff --git a/packages/reporter/src/lib/variables.scss b/packages/reporter/src/lib/variables.scss index 40357fc625b3..ef26084a7105 100644 --- a/packages/reporter/src/lib/variables.scss +++ b/packages/reporter/src/lib/variables.scss @@ -5,6 +5,7 @@ $pinned: #9442ca; $yellow-dark: #FFB61C; $yellow-medium: lighten($yellow-dark, 25%); $yellow-lightest: #ffffee; +$retried: #f0ec98; $link-text: #3380FF; diff --git a/packages/reporter/src/main-runner.scss b/packages/reporter/src/main-runner.scss index ada4216e7ec7..aa522c4c6375 100644 --- a/packages/reporter/src/main-runner.scss +++ b/packages/reporter/src/main-runner.scss @@ -1,6 +1,7 @@ // this file is imported by the runner's main.scss // if you update this file, also update main.scss @import 'lib/variables'; +@import 'lib/mixins'; @import 'lib/base'; @import 'lib/tooltip'; @import '../../../node_modules/@reach/dialog/styles.css'; diff --git a/packages/reporter/src/main.scss b/packages/reporter/src/main.scss index bb9459db9c90..22e43c1ec838 100644 --- a/packages/reporter/src/main.scss +++ b/packages/reporter/src/main.scss @@ -1,6 +1,7 @@ // this file is used when developing the reporter in isolation via cypress tests // if you update this file, also update main-runner.scss @import 'lib/variables'; +@import 'lib/mixins'; @import 'lib/fonts'; @import 'lib/base'; @import 'lib/tooltip'; diff --git a/packages/reporter/src/runnables/runnable-and-suite.spec.tsx b/packages/reporter/src/runnables/runnable-and-suite.spec.tsx index 6be4df7ca02d..d6a65f490bac 100644 --- a/packages/reporter/src/runnables/runnable-and-suite.spec.tsx +++ b/packages/reporter/src/runnables/runnable-and-suite.spec.tsx @@ -104,7 +104,7 @@ describe('', () => { }) it('renders a runnable for each child', () => { - const component = shallow() + const component = shallow() expect(component.find(Runnable).length).to.equal(2) }) diff --git a/packages/reporter/src/runnables/runnable-and-suite.tsx b/packages/reporter/src/runnables/runnable-and-suite.tsx index b07d83f667b6..1023d6a969ff 100644 --- a/packages/reporter/src/runnables/runnable-and-suite.tsx +++ b/packages/reporter/src/runnables/runnable-and-suite.tsx @@ -48,6 +48,7 @@ class Runnable extends Component { return (
  • } export default class Runnable { - @observable id: number + @observable id: string @observable shouldRender: boolean = false @observable title?: string @observable level: number diff --git a/packages/reporter/src/runnables/runnables-store.spec.ts b/packages/reporter/src/runnables/runnables-store.spec.ts index 79efbc9da28c..1e5106b29fd8 100644 --- a/packages/reporter/src/runnables/runnables-store.spec.ts +++ b/packages/reporter/src/runnables/runnables-store.spec.ts @@ -33,30 +33,28 @@ const scrollerStub = () => { const createHook = (hookId: string) => { return { hookId, hookName: 'before each' } as HookProps } -const createTest = (id: number) => { - return { id, title: `test ${id}`, hooks: [], state: 'processing' } as TestProps +const createTest = (id: string) => { + return { id, title: `test ${id}`, hooks: [], state: 'processing', currentRetry: 0 } as TestProps } -const createSuite = (id: number, tests: Array, suites: Array) => { +const createSuite = (id: string, tests: Array, suites: Array) => { return { id, title: `suite ${id}`, tests, suites, hooks: [] } as SuiteProps } -const createAgent = (id: number, testId: number) => { - return { id, testId, instrument: 'agent' } as AgentProps +const createAgent = (id: number, testId: string) => { + return { id, testId, instrument: 'agent', callCount: 0, testCurrentRetry: 0, functionName: 'foo' } as AgentProps } -const createCommand = (id: number, testId: number, hookId?: string) => { +const createCommand = (id: number, testId: string, hookId?: string) => { return { id, testId, instrument: 'command', hookId } as CommandProps } -const createRoute = (id: number, testId: number) => { +const createRoute = (id: number, testId: string) => { return { id, testId, instrument: 'route' } as RouteProps } const createRootRunnable = () => { return { - tests: [createTest(1)], + tests: [createTest('1')], suites: [ - createSuite(1, [createTest(2), createTest(3)], [ - createSuite(3, [createTest(4)], []), createSuite(4, [createTest(5)], []), - ]), - createSuite(2, [createTest(6)], []), + createSuite('1', [createTest('2'), createTest('3')], [createSuite('3', [createTest('4')], []), createSuite('4', [createTest('5')], [])]), + createSuite('2', [createTest('6')], []), ], } as RootRunnable } @@ -100,14 +98,14 @@ describe('runnables store', () => { it('adds logs to tests when specified', () => { const rootRunnable = createRootRunnable() - rootRunnable.tests![0].agents = [createAgent(1, 1), createAgent(2, 1), createAgent(3, 1)] - rootRunnable.tests![0].commands = [createCommand(1, 1, 'h1')] - rootRunnable.tests![0].routes = [createRoute(1, 1), createRoute(2, 1)] + rootRunnable.tests![0].agents = [createAgent(1, '1'), createAgent(2, '1'), createAgent(3, '1')] + rootRunnable.tests![0].commands = [createCommand(1, '1', 'h1')] + rootRunnable.tests![0].routes = [createRoute(1, '1'), createRoute(2, '1')] rootRunnable.tests![0].hooks = [createHook('h1')] instance.setRunnables(rootRunnable) - expect((instance.runnables[0] as TestModel).agents.length).to.equal(3) - expect((instance.runnables[0] as TestModel).commands.length).to.equal(1) - expect((instance.runnables[0] as TestModel).routes.length).to.equal(2) + expect((instance.runnables[0] as TestModel).lastAttempt.agents.length).to.equal(3) + expect((instance.runnables[0] as TestModel).lastAttempt.commands.length).to.equal(1) + expect((instance.runnables[0] as TestModel).lastAttempt.routes.length).to.equal(2) }) it('sets the appropriate nesting levels', () => { @@ -142,17 +140,17 @@ describe('runnables store', () => { }) it('sets .hasTests flag to false if there are no tests', () => { - instance.setRunnables({ tests: [], suites: [createSuite(1, [], []), createSuite(2, [], [])] }) + instance.setRunnables({ tests: [], suites: [createSuite('1', [], []), createSuite('2', [], [])] }) expect(instance.hasTests).to.be.false }) it('sets .hasSingleTest flag to true if there is only one test', () => { - instance.setRunnables({ tests: [], suites: [createSuite(1, [], []), createSuite(2, [createTest(1)], [])] }) + instance.setRunnables({ tests: [], suites: [createSuite('1', [], []), createSuite('2', [createTest('1')], [])] }) expect(instance.hasSingleTest).to.be.true }) it('sets .hasSingleTest flag to false if there are no tests', () => { - instance.setRunnables({ tests: [], suites: [createSuite(1, [], []), createSuite(2, [], [])] }) + instance.setRunnables({ tests: [], suites: [createSuite('1', [], []), createSuite('2', [], [])] }) expect(instance.hasSingleTest).to.be.false }) @@ -162,7 +160,7 @@ describe('runnables store', () => { }) it('starts rendering the runnables on requestAnimationFrame', () => { - instance.setRunnables({ tests: [], suites: [createSuite(1, [], []), createSuite(2, [createTest(1)], [])] }) + instance.setRunnables({ tests: [], suites: [createSuite('1', [], []), createSuite('2', [createTest('1')], [])] }) expect(instance.runnables[0].shouldRender).to.be.true expect(instance.runnables[1].shouldRender).to.be.true expect((instance.runnables[1] as SuiteModel).children[0].shouldRender).to.be.true @@ -202,44 +200,44 @@ describe('runnables store', () => { context('#runnableStarted', () => { it('starts the test with the given id', () => { - instance.setRunnables({ tests: [createTest(1)], suites: [] }) - instance.runnableStarted({ id: 1 } as TestModel) + instance.setRunnables({ tests: [createTest('1')], suites: [] }) + instance.runnableStarted({ id: '1' } as TestProps) expect((instance.runnables[0] as TestModel).isActive).to.be.true }) }) context('#runnableFinished', () => { it('finishes the test with the given id', () => { - instance.setRunnables({ tests: [createTest(1)], suites: [] }) - instance.runnableStarted({ id: 1 } as TestModel) - instance.runnableFinished({ id: 1 } as TestModel) + instance.setRunnables({ tests: [createTest('1')], suites: [] }) + instance.runnableStarted({ id: '1' } as TestProps) + instance.runnableFinished({ id: '1' } as TestProps) expect((instance.runnables[0] as TestModel).isActive).to.be.false }) }) context('#testByid', () => { it('returns the test with the given id', () => { - instance.setRunnables({ tests: [createTest(1), createTest(3)], suites: [] }) - expect(instance.testById(3).title).to.be.equal('test 3') + instance.setRunnables({ tests: [createTest('1'), createTest('3')], suites: [] }) + expect(instance.testById('3').title).to.be.equal('test 3') }) }) context('#updateLog', () => { it('updates the log', () => { - const test = createTest(1) + const test = createTest('1') test.hooks = [createHook('h1')] instance.setRunnables({ tests: [test] }) - instance.addLog(createCommand(1, 1, 'h1')) - instance.updateLog({ id: 1, name: 'new name' } as LogProps) - expect(instance.testById(1).commands[0].name).to.equal('new name') + instance.addLog(createCommand(1, '1', 'h1')) + instance.updateLog({ id: 1, testId: '1', name: 'new name' } as LogProps) + expect(instance.testById('1').lastAttempt.commands[0].name).to.equal('new name') }) }) context('#reset', () => { it('resets flags to default values', () => { - instance.setRunnables({ tests: [createTest(1)] }) + instance.setRunnables({ tests: [createTest('1')] }) instance.attemptingShowSnapshot = true instance.showingSnapshot = true instance.reset() @@ -252,15 +250,15 @@ describe('runnables store', () => { }) it('resets runnables', () => { - instance.setRunnables({ tests: [createTest(1)] }) + instance.setRunnables({ tests: [createTest('1')] }) instance.reset() expect(instance.runnables.length).to.equal(0) }) it('resets tests', () => { - instance.setRunnables({ tests: [createTest(1)] }) + instance.setRunnables({ tests: [createTest('1')] }) instance.reset() - expect(instance.testById(1)).to.be.undefined + expect(instance.testById('1')).to.be.undefined }) }) }) diff --git a/packages/reporter/src/runnables/runnables-store.ts b/packages/reporter/src/runnables/runnables-store.ts index 72a03eb382ba..bdb3d1d916e6 100644 --- a/packages/reporter/src/runnables/runnables-store.ts +++ b/packages/reporter/src/runnables/runnables-store.ts @@ -8,7 +8,7 @@ import RouteModel, { RouteProps } from '../routes/route-model' import scroller, { Scroller } from '../lib/scroller' import { HookProps } from '../hooks/hook-model' import SuiteModel, { SuiteProps } from './suite-model' -import TestModel, { TestProps, UpdateTestCallback } from '../test/test-model' +import TestModel, { TestProps, UpdateTestCallback, UpdatableTestProps } from '../test/test-model' import RunnableModel from './runnable-model' const defaults = { @@ -29,7 +29,7 @@ export type LogProps = AgentProps | CommandProps | RouteProps export type RunnableArray = Array -type Log = AgentModel | CommandModel | RouteModel +export type Log = AgentModel | CommandModel | RouteModel export interface RootRunnable { hooks?: Array @@ -102,15 +102,11 @@ class RunnablesStore { } _createTest (props: TestProps, level: number) { - const test = new TestModel(props, level) + const test = new TestModel(props, level, this) this._runnablesQueue.push(test) this._tests[test.id] = test - _.each(props.agents, this.addLog.bind(this)) - _.each(props.commands, this.addLog.bind(this)) - _.each(props.routes, this.addLog.bind(this)) - return test } @@ -151,66 +147,35 @@ class RunnablesStore { this._initialScrollTop = initialScrollTop } - updateTest (props: TestProps, cb: UpdateTestCallback) { + updateTest (props: UpdatableTestProps, cb: UpdateTestCallback) { this._withTest(props.id, (test) => { - return test.update(props, cb) + test.update(props, cb) }) } - runnableStarted ({ id }: TestModel) { - this._withTest(id, (test) => { - return test.start() + runnableStarted (props: TestProps) { + this._withTest(props.id, (test) => { + test.start(props) }) } - runnableFinished (props: TestModel) { + runnableFinished (props: TestProps) { this._withTest(props.id, (test) => { - return test.finish(props) + test.finish(props) }) } - testById (id: number) { + testById (id: string) { return this._tests[id] } addLog (log: LogProps) { - switch (log.instrument) { - case 'command': { - const command = new CommandModel(log as CommandProps) - - this._logs[log.id] = command - this._withTest(log.testId, (test) => { - return test.addCommand(command) - }) - - break - } - case 'agent': { - const agent = new AgentModel(log as AgentProps) - - this._logs[log.id] = agent - this._withTest(log.testId, (test) => { - return test.addAgent(agent) - }) - - break - } - case 'route': { - const route = new RouteModel(log as RouteProps) - - this._logs[log.id] = route - this._withTest(log.testId, (test) => { - return test.addRoute(route) - }) - - break - } - default: - throw new Error(`Attempted to add log for unknown instrument: ${log.instrument}`) - } + this._withTest(log.testId, (test) => { + test.addLog(log) + }) } - _withTest (id: number, cb: ((test: TestModel) => void)) { + _withTest (id: string, cb: ((test: TestModel) => void)) { // we get events for suites and tests, but only tests change during a run, // so if the id isn't found in this._tests, we ignore it b/c it's a suite const test = this._tests[id] @@ -218,13 +183,10 @@ class RunnablesStore { if (test) cb(test) } - updateLog (log: LogProps) { - const found = this._logs[log.id] - - if (found) { - // The type of found is Log (one of Agent, Command, Route). So, we need any here. - found.update(log as any) - } + updateLog (props: LogProps) { + this._withTest(props.testId, (test) => { + test.updateLog(props) + }) } reset () { @@ -234,7 +196,6 @@ class RunnablesStore { this.runnables = [] this._tests = {} - this._logs = {} this._runnablesQueue = [] } } diff --git a/packages/reporter/src/runnables/runnables.scss b/packages/reporter/src/runnables/runnables.scss index e0c445ff6162..2ae964dba666 100644 --- a/packages/reporter/src/runnables/runnables.scss +++ b/packages/reporter/src/runnables/runnables.scss @@ -57,7 +57,7 @@ } } - &.test.hover { + .attempt-item:hover { > .runnable-wrapper .runnable-controls i.fa-redo { visibility: visible !important; } @@ -69,12 +69,11 @@ &.runnable-active { .runnable-state { - @extend .#{$fa-css-prefix}-sync-alt; - @extend .#{$fa-css-prefix}-spin; + @include runnable-state-active; } } - .runnable-state { + .runnable-state,.attempt-state { display: inline-block; line-height: 18px; margin-right: 5px; @@ -90,12 +89,10 @@ color: #bbbcbd; } - &.test.runnable-processing { + + &.test.runnable-processing { .runnable-state { - @extend .far; - line-height: 18px; // @extend .far overrides line-height, so we need to set it again - @extend .#{$fa-css-prefix}-square; - color: #888; + @include runnable-state-processing; } } @@ -119,10 +116,14 @@ border-left: 5px solid $pass; } + .runnable-retried > div > .runnable-wrapper, + .runnable-retried > div > .runnable-instruments { + border-left: 5px solid $retried; + } + &.runnable-skipped > .runnable-wrapper { .runnable-state { - @extend .#{$fa-css-prefix}-ban; - color: #888; + @include runnable-state-skipped; } .runnable-title { @@ -137,8 +138,7 @@ &.test.runnable-failed { .runnable-state { - @extend .#{$fa-css-prefix}-times; - color: $fail; + @include runnable-state-failed; } } @@ -152,21 +152,18 @@ &.test.runnable-passed { .runnable-state { - @extend .#{$fa-css-prefix}-check; - color: $pass; + @include runnable-state-passed; } } &.test.runnable-pending { + .runnable-state { + @include runnable-state-pending; + } .runnable-title { color: lighten($pending, 25%); } - .runnable-state { - @extend .#{$fa-css-prefix}-circle-notch; - color: lighten($pending, 20%); - } - .runnable-commands-region { display: none; } diff --git a/packages/reporter/src/runnables/runnables.spec.tsx b/packages/reporter/src/runnables/runnables.spec.tsx index e545727d0fbc..e92c126e7f19 100644 --- a/packages/reporter/src/runnables/runnables.spec.tsx +++ b/packages/reporter/src/runnables/runnables.spec.tsx @@ -46,7 +46,7 @@ describe('', () => { it('renders when there are runnables', () => { const component = shallow( , @@ -134,7 +134,7 @@ describe('', () => { context('', () => { it('renders a runnable for each runnable in model', () => { - const component = shallow() + const component = shallow() expect(component.find('Runnable').length).to.equal(2) }) diff --git a/packages/reporter/src/runnables/suite-model.spec.ts b/packages/reporter/src/runnables/suite-model.spec.ts index ec10badaee74..82a2523bb0f1 100644 --- a/packages/reporter/src/runnables/suite-model.spec.ts +++ b/packages/reporter/src/runnables/suite-model.spec.ts @@ -2,7 +2,7 @@ import Suite from './suite-model' import TestModel from '../test/test-model' const suiteWithChildren = (children: Array>) => { - const suite = new Suite({ id: 1, title: '', hooks: [] }, 0) + const suite = new Suite({ id: '1', title: '', hooks: [] }, 0) suite.children = children as Array diff --git a/packages/reporter/src/runnables/suite-model.ts b/packages/reporter/src/runnables/suite-model.ts index 47c2a26e009f..f227115071b7 100644 --- a/packages/reporter/src/runnables/suite-model.ts +++ b/packages/reporter/src/runnables/suite-model.ts @@ -32,6 +32,10 @@ export default class Suite extends Runnable { return _.map(this.children, 'state') } + @computed get hasRetried (): boolean { + return _.some(this.children, (v) => v.hasRetried) + } + @computed get _anyChildrenFailed () { return _.some(this._childStates, (state) => { return state === 'failed' diff --git a/packages/reporter/src/test/test-model.spec.ts b/packages/reporter/src/test/test-model.spec.ts index 9c9fb00e6533..bf811b8db008 100644 --- a/packages/reporter/src/test/test-model.spec.ts +++ b/packages/reporter/src/test/test-model.spec.ts @@ -1,37 +1,56 @@ -import { HookProps } from '../hooks/hook-model' -import Command, { CommandProps } from '../commands/command-model' -import Agent from '../agents/agent-model' -import Route from '../routes/route-model' import Err from '../errors/err-model' - -import TestModel, { TestProps } from './test-model' - -const commandHook: (hookId: string) => Partial = (hookId: string) => { - return { - hookId, - isMatchingEvent: () => { - return false - }, - } +import _ from 'lodash' +import TestModel, { TestProps, UpdatableTestProps } from './test-model' +import CommandModel, { CommandProps } from '../commands/command-model' +import { RouteProps } from '../routes/route-model' +import { RunnablesStore } from '../runnables/runnables-store' +import { AgentProps } from '../agents/agent-model' + +const createTest = (props: Partial = {}, store = {}) => { + const defaults = { + currentRetry: 0, + id: 'r3', + prevAttempts: [], + state: null, + hooks: [], + } as TestProps + + return new TestModel(_.defaults(props, defaults), 0, store as RunnablesStore) +} +const createCommand = (props: Partial = {}) => { + const defaults = { + instrument: 'command', + hookName: '', + id: 1, + hookId: 'r3', + numElements: 1, + testCurrentRetry: 0, + testId: 'r3', + timeout: 4000, + wallClockStartedAt: new Date().toString(), + + } as CommandProps + + return _.defaults(props, defaults) } describe('Test model', () => { context('.state', () => { it('is the "state" when it exists', () => { - const test = new TestModel({ id: 1, state: 'passed' } as TestProps, 0) + const test = createTest({ state: 'passed' }) expect(test.state).to.equal('passed') }) it('is active when there is no state and isActive is true', () => { - const test = new TestModel({ id: 1 } as TestProps, 0) + const test = createTest() - test.isActive = true + test.lastAttempt.isActive = true expect(test.state).to.equal('active') }) it('is processing when there is no state and isActive is falsey', () => { - const test = new TestModel({ id: 1 } as TestProps, 0) + const test = createTest() expect(test.state).to.equal('processing') }) @@ -39,201 +58,262 @@ describe('Test model', () => { context('.isLongRunning', () => { it('start out not long running', () => { - const test = new TestModel({ id: 1 } as TestProps, 0) + const test = createTest() expect(test.isLongRunning).to.be.false }) it('is not long running if active but without a long running command', () => { - const test = new TestModel({ id: 1 } as TestProps, 0) + const test = createTest() - test.start() + test.start({} as TestProps) expect(test.isLongRunning).to.be.false }) it('becomes long running if active and has a long running command', () => { - const test = new TestModel({ id: 1, hooks: [{ hookId: 'h1' } as HookProps] } as TestProps, 0) + const test = createTest() + + test.start({} as TestProps) + const command = test.addLog(createCommand()) as CommandModel - test.start() - test.addCommand({ isLongRunning: true, hookId: 'h1' } as Command) + command.isLongRunning = true expect(test.isLongRunning).to.be.true }) it('becomes not long running if it becomes inactive', () => { - const test = new TestModel({ id: 1, hooks: [{ hookId: 'h1' } as HookProps] } as TestProps, 0) + const test = createTest() - test.start() - test.addCommand({ isLongRunning: true, hookId: 'h1' } as Command) - test.finish({}) + test.start({} as TestProps) + const command = test.addLog(createCommand()) as CommandModel + + command.isLongRunning = true + + test.finish({} as UpdatableTestProps) expect(test.isLongRunning).to.be.false }) }) context('#addAgent', () => { it('adds the agent to the agents collection', () => { - const test = new TestModel({ id: 1 } as TestProps, 0) + const test = createTest() - test.addAgent({} as Agent) - expect(test.agents.length).to.equal(1) + test.addLog({ instrument: 'agent' } as AgentProps) + expect(test.lastAttempt.agents.length).to.equal(1) }) }) context('#addRoute', () => { it('adds the route to the routes collection', () => { - const test = new TestModel({ id: 1 } as TestProps, 0) + const test = createTest() - test.addRoute({} as Route) - expect(test.routes.length).to.equal(1) + test.addLog({ instrument: 'route' } as RouteProps) + expect(test.lastAttempt.routes.length).to.equal(1) }) }) context('#addCommand', () => { it('adds the command to the commands collection', () => { - const test = new TestModel({ id: 1, hooks: [{ hookId: 'h1' } as HookProps] } as TestProps, 0) + const test = createTest() + + test.addLog(createCommand()) + expect(test.lastAttempt.commands.length).to.equal(1) + }) - test.addCommand({ hookId: 'h1' } as Command) - expect(test.commands.length).to.equal(1) + it('creates a hook and adds the command to it if it does not exist', () => { + const test = createTest({ hooks: [ + { hookName: 'before each', hookId: 'h1' }, + ] }) + + test.addLog(createCommand({ instrument: 'command', hookId: 'h1' })) + expect(test.lastAttempt.hooks.length).to.equal(2) + expect(test.lastAttempt.hooks[0].hookName).equal('before each') + expect(test.lastAttempt.hooks[0].commands.length).to.equal(1) + }) + + it('adds the command to an existing hook if it already exists', () => { + const test = createTest({ hooks: [{ hookId: 'h1', hookName: 'before each' }] }) + const commandProps = createCommand({ + hookId: 'h1', + }) + + const command = test.addLog(commandProps) as CommandModel + + command.isMatchingEvent = () => false + + expect(test.lastAttempt.hooks.length).to.equal(2) + expect(test.lastAttempt.hooks[0].hookName).to.equal('before each') + expect(test.lastAttempt.hooks[0].commands.length).to.equal(1) + test.addLog(createCommand({ hookId: 'h1' })) + expect(test.lastAttempt.hooks.length).to.equal(2) + expect(test.lastAttempt.hooks[0].commands.length).to.equal(2) }) it('adds the command to the correct hook', () => { - const test = new TestModel({ - id: 1, + const test = createTest({ hooks: [ - { hookId: 'h1' } as HookProps, - { hookId: 'h2' } as HookProps, + { hookId: 'h1', hookName: 'before each' }, + { hookId: 'h2', hookName: 'before each' }, ], - } as TestProps, 0) + }) - test.addCommand(commandHook('h1') as Command) - expect(test.hooks[0].commands.length).to.equal(1) - expect(test.hooks[1].commands.length).to.equal(0) - expect(test.hooks[2].commands.length).to.equal(0) + test.addLog(createCommand({ hookId: 'h1' })) + expect(test.lastAttempt.hooks[0].commands.length).to.equal(1) + expect(test.lastAttempt.hooks[1].commands.length).to.equal(0) + expect(test.lastAttempt.hooks[2].commands.length).to.equal(0) - test.addCommand(commandHook('1') as Command) - expect(test.hooks[0].commands.length).to.equal(1) - expect(test.hooks[1].commands.length).to.equal(1) - expect(test.hooks[2].commands.length).to.equal(0) + test.addLog(createCommand({ hookId: 'h2' })) + expect(test.lastAttempt.hooks[0].commands.length).to.equal(1) + expect(test.lastAttempt.hooks[1].commands.length).to.equal(1) + expect(test.lastAttempt.hooks[2].commands.length).to.equal(0) }) it('moves hooks into the correct order', () => { - const test = new TestModel({ - id: 1, + const test = createTest({ hooks: [ - { hookId: 'h1' } as HookProps, - { hookId: 'h2' } as HookProps, + { hookId: 'h1', hookName: 'before all' }, + { hookId: 'h2', hookName: 'before each' }, ], - } as TestProps, 0) + }) - test.addCommand(commandHook('h2') as Command) - expect(test.hooks[0].hookId).to.equal('h2') - expect(test.hooks[0].invocationOrder).to.equal(0) - expect(test.hooks[0].commands.length).to.equal(1) + test.addLog(createCommand({ hookId: 'h2' })) + expect(test.lastAttempt.hooks[0].hookId).to.equal('h2') + expect(test.lastAttempt.hooks[0].invocationOrder).to.equal(0) + expect(test.lastAttempt.hooks[0].commands.length).to.equal(1) - test.addCommand(commandHook('h1') as Command) - expect(test.hooks[1].hookId).to.equal('h1') - expect(test.hooks[1].invocationOrder).to.equal(1) - expect(test.hooks[1].commands.length).to.equal(1) + test.addLog(createCommand({ hookId: 'h1' })) + expect(test.lastAttempt.hooks[1].hookId).to.equal('h1') + expect(test.lastAttempt.hooks[1].invocationOrder).to.equal(1) + expect(test.lastAttempt.hooks[1].commands.length).to.equal(1) }) it('counts and assigns the number of each hook type', () => { - const test = new TestModel({ - id: 1, + const test = createTest({ hooks: [ - { hookId: 'h1', hookName: 'before each' } as HookProps, - { hookId: 'h2', hookName: 'after each' } as HookProps, - { hookId: 'h3', hookName: 'before each' } as HookProps, + { hookId: 'h1', hookName: 'before each' }, + { hookId: 'h2', hookName: 'after each' }, + { hookId: 'h3', hookName: 'before each' }, ], - } as TestProps, 0) - - test.addCommand(commandHook('h1') as Command) - expect(test.hookCount['before each']).to.equal(1) - expect(test.hookCount['after each']).to.equal(0) - expect(test.hooks[0].hookNumber).to.equal(1) - - test.addCommand(commandHook('h1') as Command) - expect(test.hookCount['before each']).to.equal(1) - expect(test.hookCount['after each']).to.equal(0) - expect(test.hooks[0].hookNumber).to.equal(1) - - test.addCommand(commandHook('h3') as Command) - expect(test.hookCount['before each']).to.equal(2) - expect(test.hookCount['after each']).to.equal(0) - expect(test.hooks[1].hookNumber).to.equal(2) - - test.addCommand(commandHook('h2') as Command) - expect(test.hookCount['before each']).to.equal(2) - expect(test.hookCount['after each']).to.equal(1) - expect(test.hooks[2].hookNumber).to.equal(1) + }) + + test.addLog(createCommand({ hookId: 'h1' })) + expect(test.lastAttempt.hookCount['before each']).to.equal(1) + expect(test.lastAttempt.hookCount['after each']).to.equal(0) + expect(test.lastAttempt.hooks[0].hookNumber).to.equal(1) + + test.addLog(createCommand({ hookId: 'h1' })) + expect(test.lastAttempt.hookCount['before each']).to.equal(1) + expect(test.lastAttempt.hookCount['after each']).to.equal(0) + expect(test.lastAttempt.hooks[0].hookNumber).to.equal(1) + + test.addLog(createCommand({ hookId: 'h3' })) + expect(test.lastAttempt.hookCount['before each']).to.equal(2) + expect(test.lastAttempt.hookCount['after each']).to.equal(0) + expect(test.lastAttempt.hooks[1].hookNumber).to.equal(2) + + test.addLog(createCommand({ hookId: 'h2' })) + expect(test.lastAttempt.hookCount['before each']).to.equal(2) + expect(test.lastAttempt.hookCount['after each']).to.equal(1) + expect(test.lastAttempt.hooks[2].hookNumber).to.equal(1) }) }) context('#start', () => { it('sets the test as active', () => { - const test = new TestModel({ id: 1 } as TestProps, 0) + const test = createTest() - test.start() + test.start({} as TestProps) expect(test.isActive).to.be.true }) }) context('#finish', () => { it('sets the test as inactive', () => { - const test = new TestModel({ id: 1 } as TestProps, 0) + const test = createTest() - test.finish({}) + test.finish({} as UpdatableTestProps) expect(test.isActive).to.be.false }) it('updates the state of the test', () => { - const test = new TestModel({ id: 1 } as TestProps, 0) + const test = createTest() - test.finish({ state: 'failed' }) + test.finish({ state: 'failed' } as UpdatableTestProps) expect(test.state).to.equal('failed') }) it('updates the test err', () => { - const test = new TestModel({ id: 1 } as TestProps, 0) + const test = createTest() - test.finish({ err: { name: 'SomeError' } as Err }) + test.finish({ err: { name: 'SomeError' } as Err } as UpdatableTestProps) expect(test.err.name).to.equal('SomeError') }) it('sets the hook to failed if it exists', () => { - const test = new TestModel({ id: 1, hooks: [{ hookId: 'h1' } as HookProps] } as TestProps, 0) + const test = createTest({ hooks: [{ hookId: 'h1', hookName: 'before each' }] }) - test.addCommand({ hookId: 'h1' } as Command) - test.finish({ hookId: 'h1' }) - expect(test.hooks[0].failed).to.be.true + test.addLog(createCommand({ instrument: 'command' })) + test.finish({ hookId: 'h1', err: { message: 'foo' } as Err } as UpdatableTestProps) + expect(test.lastAttempt.hooks[1].failed).to.be.true }) it('does not throw error if hook does not exist', () => { - const test = new TestModel({ id: 1 } as TestProps, 0) + const test = createTest() expect(() => { - test.finish({ hookId: 'h1' }) + test.finish({ hookId: 'h1' } as UpdatableTestProps) }).not.to.throw() }) }) context('#commandMatchingErr', () => { it('returns last command matching the error', () => { - const test = new TestModel({ id: 1, err: { message: 'SomeError' } as Err, hooks: [{ hookId: 'h1' } as HookProps] } as TestProps, 0) - - test.addCommand(new Command({ err: { message: 'SomeError' } as Err, hookId: 'h1' } as CommandProps)) - test.addCommand(new Command({ err: {} as Err, hookId: 'h1' } as CommandProps)) - test.addCommand(new Command({ err: { message: 'SomeError' } as Err, hookId: 'h1' } as CommandProps)) - test.addCommand(new Command({ err: {} as Err, hookId: 'h1' } as CommandProps)) - test.addCommand(new Command({ name: 'The One', err: { message: 'SomeError' } as Err, hookId: 'h1' } as CommandProps)) + const test = createTest({ err: { message: 'SomeError' } as Err, hooks: [ + { hookId: 'h1', hookName: 'before each' }, + { hookId: 'h2', hookName: 'before each' }, + ] }) + + test.addLog(createCommand({ err: { message: 'SomeError' } as Err, hookId: 'h1' })) + test.addLog(createCommand({ err: {} as Err, hookId: 'h1' })) + test.addLog(createCommand({ err: { message: 'SomeError' } as Err, hookId: 'h1' })) + test.addLog(createCommand({ err: {} as Err, hookId: 'h2' })) + test.addLog(createCommand({ name: 'The One', err: { message: 'SomeError' } as Err, hookId: 'h2' })) expect(test.commandMatchingErr()!.name).to.equal('The One') }) it('returns undefined if there are no commands with errors', () => { - const test = new TestModel({ id: 1, err: { message: 'SomeError' } as Err, hooks: [{ hookId: 'h1' } as HookProps] } as TestProps, 0) + const test = createTest({ err: { message: 'SomeError' } as Err, hooks: [ + { hookId: 'h1', hookName: 'before each' }, + { hookId: 'h2', hookName: 'before each' }, + { hookId: 'h3', hookName: 'before each' }, + ] }) - test.addCommand(new Command({ hookId: 'h1' } as CommandProps)) - test.addCommand(new Command({ hookId: 'h1' } as CommandProps)) - test.addCommand(new Command({ hookId: 'h1' } as CommandProps)) expect(test.commandMatchingErr()).to.be.undefined }) }) + + context('#isOpen', () => { + it('false by default', () => { + const test = createTest() + + test.start({} as TestProps) + + expect(test.isOpen).eq(false) + }) + + it('true when the model is long running', () => { + const test = createTest() + + test.start({} as TestProps) + const command = test.addLog(createCommand()) as CommandModel + + command.isLongRunning = true + expect(test.isOpen).eq(true) + }) + + it('true when there is only one test', () => { + const test = createTest({}, { hasSingleTest: true }) + + expect(test.isOpen).eq(true) + }) + }) }) diff --git a/packages/reporter/src/test/test-model.ts b/packages/reporter/src/test/test-model.ts index 1be6515bf78e..ab60b961bc42 100644 --- a/packages/reporter/src/test/test-model.ts +++ b/packages/reporter/src/test/test-model.ts @@ -1,195 +1,205 @@ import _ from 'lodash' -import { action, autorun, computed, observable, observe } from 'mobx' +import { action, computed, observable } from 'mobx' import { FileDetails } from '@packages/ui-components' +import Attempt from '../attempts/attempt-model' import Err from '../errors/err-model' -import Hook, { HookName } from '../hooks/hook-model' +import { HookProps } from '../hooks/hook-model' import Runnable, { RunnableProps } from '../runnables/runnable-model' -import Command, { CommandProps } from '../commands/command-model' -import Agent, { AgentProps } from '../agents/agent-model' -import Route, { RouteProps } from '../routes/route-model' +import { CommandProps } from '../commands/command-model' +import { AgentProps } from '../agents/agent-model' +import { RouteProps } from '../routes/route-model' +import { RunnablesStore, LogProps } from '../runnables/runnables-store' export type TestState = 'active' | 'failed' | 'pending' | 'passed' | 'processing' export type UpdateTestCallback = () => void export interface TestProps extends RunnableProps { - state: TestState + state: TestState | null err?: Err isOpen?: boolean agents?: Array commands?: Array routes?: Array + hooks: Array + prevAttempts?: Array + currentRetry: number + retries?: number + final?: boolean invocationDetails?: FileDetails } export interface UpdatableTestProps { + id: TestProps['id'] state?: TestProps['state'] err?: TestProps['err'] hookId?: string isOpen?: TestProps['isOpen'] + currentRetry?: TestProps['currentRetry'] + retries?: TestProps['retries'] } export default class Test extends Runnable { - @observable agents: Array = [] - @observable commands: Array = [] - @observable err = new Err({}) - @observable hooks: Array = [] - // TODO: make this an enum with states: 'QUEUED, ACTIVE, INACTIVE' - @observable isActive: boolean | null = null - @observable isLongRunning = false - @observable isOpen = false - @observable routes: Array = [] - @observable _state?: TestState | null = null - @observable _invocationCount: number = 0 - @observable invocationDetails?: FileDetails - @observable hookCount: { [name in HookName]: number } = { - 'before all': 0, - 'before each': 0, - 'after all': 0, - 'after each': 0, - 'test body': 0, - } type = 'test' - callbackAfterUpdate: (() => void) | null = null + _callbackAfterUpdate: UpdateTestCallback | null = null + hooks: HookProps[] + invocationDetails?: FileDetails + + @observable attempts: Attempt[] = [] + @observable _isOpen: boolean | null = null + @observable isOpenWhenActive: Boolean | null = null + @observable _isFinished = false - constructor (props: TestProps, level: number) { + constructor (props: TestProps, level: number, private store: RunnablesStore) { super(props, level) - this._state = props.state - this.err.update(props.err) - this.invocationDetails = props.invocationDetails - this.hooks = _.map(props.hooks, (hook) => new Hook(hook)) - this.hooks.push(new Hook({ - hookId: this.id.toString(), + this.hooks = [...props.hooks, { + hookId: props.id.toString(), hookName: 'test body', - invocationDetails: this.invocationDetails, - })) - - autorun(() => { - // if at any point, a command goes long running, set isLongRunning - // to true until the test becomes inactive - if (!this.isActive) { - action('became:inactive', () => { - return this.isLongRunning = false - })() - } else if (this._hasLongRunningCommand) { - action('became:long:running', () => { - return this.isLongRunning = true - })() - } - }) + invocationDetails: props.invocationDetails, + }] + + _.each(props.prevAttempts || [], (attempt) => this._addAttempt(attempt)) + + this._addAttempt(props) } - @computed get _hasLongRunningCommand () { - return _.some(this.commands, (command) => { - return command.isLongRunning + @computed get isLongRunning () { + return _.some(this.attempts, (attempt: Attempt) => { + return attempt.isLongRunning }) } - @computed get state () { - return this._state || (this.isActive ? 'active' : 'processing') + @computed get isOpen () { + if (this._isOpen === null) { + return Boolean(this.state === 'failed' + || this.isLongRunning + || this.isActive && (this.hasMultipleAttempts || this.isOpenWhenActive) + || this.store.hasSingleTest) + } + + return this._isOpen } - addAgent (agent: Agent) { - this.agents.push(agent) + @computed get state () { + return this.lastAttempt ? this.lastAttempt.state : 'active' } - addRoute (route: Route) { - this.routes.push(route) + @computed get err () { + return this.lastAttempt ? this.lastAttempt.err : new Err({}) } - addCommand (command: Command) { - this.commands.push(command) + @computed get lastAttempt () { + return _.last(this.attempts) as Attempt + } - const hookIndex = _.findIndex(this.hooks, { hookId: command.hookId }) + @computed get hasMultipleAttempts () { + return this.attempts.length > 1 + } - const hook = this.hooks[hookIndex] + @computed get hasRetried () { + return this.state === 'passed' && this.hasMultipleAttempts + } - hook.addCommand(command) + // TODO: make this an enum with states: 'QUEUED, ACTIVE, INACTIVE' + @computed get isActive (): boolean { + return _.some(this.attempts, { isActive: true }) + } - // make sure that hooks are in order of invocation - if (hook.invocationOrder === undefined) { - hook.invocationOrder = this._invocationCount++ + @computed get currentRetry () { + return this.attempts.length - 1 + } - if (hook.invocationOrder !== hookIndex) { - this.hooks[hookIndex] = this.hooks[hook.invocationOrder] - this.hooks[hook.invocationOrder] = hook - } - } + isLastAttempt (attemptModel: Attempt) { + return this.lastAttempt === attemptModel + } - // assign number if non existent - if (hook.hookNumber === undefined) { - hook.hookNumber = ++this.hookCount[hook.hookName] - } + addLog = (props: LogProps) => { + return this._withAttempt(props.testCurrentRetry, (attempt: Attempt) => { + return attempt.addLog(props) + }) } - start () { - this.isActive = true + updateLog (props: LogProps) { + this._withAttempt(props.testCurrentRetry, (attempt: Attempt) => { + attempt.updateLog(props) + }) } - update ({ state, err, hookId, isOpen }: UpdatableTestProps, cb?: UpdateTestCallback) { - let hadChanges = false + @action start (props: TestProps) { + let attempt = this.getAttemptByIndex(props.currentRetry) + + if (!attempt) { + attempt = this._addAttempt(props) + } - const disposer = observe(this, (change) => { - hadChanges = true + attempt.start() + } - disposer() + @action update (props: UpdatableTestProps, cb: UpdateTestCallback) { + if (props.isOpen != null) { + this.setIsOpenWhenActive(props.isOpen) - // apply change as-is - return change - }) + if (this.isOpen !== props.isOpen) { + this._callbackAfterUpdate = cb - if (cb) { - this.callbackAfterUpdate = () => { - this.callbackAfterUpdate = null - cb() + return } } - this._state = state - this.err.update(err) - if (isOpen != null) { - this.isOpen = isOpen - } + cb() + } - if (hookId) { - const hook = _.find(this.hooks, { hookId }) + // this is called to sync up the command log UI for the sake of + // screenshots, so we only ever need to open the last attempt + setIsOpenWhenActive (isOpen: boolean) { + this.isOpenWhenActive = isOpen + } - if (hook) { - hook.failed = true - } + callbackAfterUpdate () { + if (this._callbackAfterUpdate) { + this._callbackAfterUpdate() + this._callbackAfterUpdate = null } + } - // if we had no changes then react will - // never fire componentDidUpdate and - // so we need to manually call our callback - // https://github.com/cypress-io/cypress/issues/674#issuecomment-366495057 - if (!hadChanges) { - // unbind the listener if no changes - disposer() - - // if we had a callback, invoke it - if (this.callbackAfterUpdate) { - this.callbackAfterUpdate() - } - } + @action finish (props: UpdatableTestProps) { + this._isFinished = !(props.retries && props.currentRetry) || props.currentRetry >= props.retries + + this._withAttempt(props.currentRetry || 0, (attempt: Attempt) => { + attempt.finish(props) + }) } - finish (props: UpdatableTestProps) { - this.update(props) - this.isActive = false + getAttemptByIndex (attemptIndex: number) { + if (attemptIndex >= this.attempts.length) return + + return this.attempts[attemptIndex || 0] } commandMatchingErr () { - return _(this.hooks) - .map((hook) => { - return hook.commandMatchingErr(this.err) - }) - .compact() - .last() + return this.lastAttempt.commandMatchingErr() + } + + _addAttempt = (props: TestProps) => { + props.invocationDetails = this.invocationDetails + props.hooks = this.hooks + const attempt = new Attempt(props, this) + + this.attempts.push(attempt) + + return attempt + } + + _withAttempt (attemptIndex: number, cb: (attempt: Attempt) => T) { + const attempt = this.getAttemptByIndex(attemptIndex) + + if (attempt) return cb(attempt) + + return null } } diff --git a/packages/reporter/src/test/test.spec.tsx b/packages/reporter/src/test/test.spec.tsx index f6792086d03a..2bc07054ca41 100644 --- a/packages/reporter/src/test/test.spec.tsx +++ b/packages/reporter/src/test/test.spec.tsx @@ -1,24 +1,22 @@ -import _ from 'lodash' import React from 'react' -import { mount, shallow, ReactWrapper } from 'enzyme' +import { shallow, mount, ReactWrapper } from 'enzyme' import sinon, { SinonSpy } from 'sinon' - -import Hooks from '../hooks/hooks' - -import Test, { NoCommands } from './test' -import TestModel from './test-model' +import _ from 'lodash' +import Test from './test' +import TestModel, { TestState } from './test-model' import { Scroller } from '../lib/scroller' import { AppState } from '../lib/app-state' const appStateStub = (props?: Partial) => { - return _.extend({ + return { autoScrollingEnabled: true, isRunning: true, - }, props) + ...props, + } as AppState } const model = (props?: Partial) => { - return _.extend({ + return { agents: [], commands: [], hooks: [], @@ -30,8 +28,10 @@ const model = (props?: Partial) => { shouldRender: true, state: 'passed', title: 'some title', - type: 'test', - }, props) + callbackAfterUpdate: () => {}, + toggleOpen: sinon.stub(), + ...props, + } as any } type ScrollerStub = Scroller & { @@ -42,6 +42,8 @@ const scrollerStub = () => ({ scrollIntoView: sinon.spy(), } as ScrollerStub) +const setTestState = (test:TestModel, state:TestState) => _.extend(test, { state }) + describe('', () => { it('does not render when it should not render', () => { const component = shallow() @@ -49,88 +51,18 @@ describe('', () => { expect(component).to.be.empty }) - context('open/closed', () => { - it('renders without is-open class by default', () => { - const component = mount() - - expect(component.find('.collapsible').first()).not.to.have.className('is-open') - }) - - it('renders with is-open class when the model state is failed', () => { - const component = mount() - - expect(component.find('.collapsible').first()).to.have.className('is-open') - }) - - it('renders with is-open class when the model is long running', () => { - const component = mount() - - expect(component.find('.collapsible').first()).to.have.className('is-open') - }) - - it('renders with is-open class when there is only one test', () => { - const component = mount() - - expect(component.find('.collapsible').first()).to.have.className('is-open') - }) - - context('toggling', () => { - it('renders without is-open class when already open', () => { - const component = mount() - - component.find('.collapsible-header').first().simulate('click') - expect(component.find('.collapsible').first()).not.to.have.className('is-open') - }) - - it('renders with is-open class when not already open', () => { - const component = mount() - - component.find('.collapsible-header').first().simulate('click') - expect(component.find('.collapsible').first()).to.have.className('is-open') - }) - - it('renders without is-open class when toggled again', () => { - const component = mount() - - component.find('.collapsible-header').first().simulate('click') - component.find('.collapsible-header').first().simulate('click') - expect(component.find('.collapsible').first()).not.to.have.className('is-open') - }) - }) - }) - context('contents', () => { it('does not render the contents if not open', () => { - const component = mount() + const component = mount() expect(component.find('.runnable-instruments')).to.be.empty }) it('renders the contents if open', () => { - const component = mount() + const component = mount() expect(component.find('.runnable-instruments')).not.to.be.empty }) - - it('renders if there are commands', () => { - const component = shallow() - - expect(component.find(Hooks)).to.exist - }) - - it('renders is no commands', () => { - const component = shallow() - - expect(component.find(NoCommands)).to.exist - }) - - it('stops propagation when clicked', () => { - const component = mount() - const e = { stopPropagation: sinon.spy() } - - component.find('.collapsible-header').first().simulate('click', e) - expect(e.stopPropagation).to.have.been.called - }) }) context('scrolling into view', () => { @@ -195,11 +127,11 @@ describe('', () => { expect(scroller.scrollIntoView).not.to.have.been.called }) - it('does not scroll into view if model.isActive is null', () => { + it('does not scroll into view if model.state is processing', () => { mount( , ) @@ -215,30 +147,21 @@ describe('', () => { beforeEach(() => { appState = appStateStub({ autoScrollingEnabled: false, isRunning: false }) - testModel = model({ isActive: null }) + testModel = model({ state: 'processing' }) component = mount() }) - it('scrolls into view if auto-scrolling is enabled, app is running, the model should render, and the model.isActive is null', () => { - appState.id = 'fooo' - appState.autoScrollingEnabled = true - appState.isRunning = true - testModel.isActive = true - testModel.shouldRender = true - component.instance()!.componentDidUpdate!({}, {}) - expect(scroller.scrollIntoView).to.have.been.calledWith((component.instance() as any).containerRef.current) - }) - it('does not scroll into view if auto-scrolling is disabled', () => { appState.isRunning = true - testModel.isActive = true + setTestState(testModel, 'processing') component.instance()!.componentDidUpdate!({}, {}) expect(scroller.scrollIntoView).not.to.have.been.called }) it('does not scroll into view if app is not running', () => { appState.autoScrollingEnabled = true - testModel.isActive = true + setTestState(testModel, 'processing') + component.instance()!.componentDidUpdate!({}, {}) expect(scroller.scrollIntoView).not.to.have.been.called }) diff --git a/packages/reporter/src/test/test.tsx b/packages/reporter/src/test/test.tsx index 07f8550f553e..10556594a68a 100644 --- a/packages/reporter/src/test/test.tsx +++ b/packages/reporter/src/test/test.tsx @@ -1,31 +1,20 @@ -import { action, observable } from 'mobx' import { observer } from 'mobx-react' import React, { Component, createRef, RefObject } from 'react' // @ts-ignore import Tooltip from '@cypress/react-tooltip' +import events, { Events } from '../lib/events' import appState, { AppState } from '../lib/app-state' import Collapsible from '../collapsible/collapsible' import { indent } from '../lib/util' import runnablesStore, { RunnablesStore } from '../runnables/runnables-store' -import scroller, { Scroller } from '../lib/scroller' - -import Hooks from '../hooks/hooks' -import Agents from '../agents/agents' -import Routes from '../routes/routes' -import TestError from '../errors/test-error' - import TestModel from './test-model' +import scroller, { Scroller } from '../lib/scroller' -const NoCommands = observer(() => ( -
      -
    • - No commands were issued in this test. -
    • -
    -)) +import Attempts from '../attempts/attempts' interface Props { + events: Events appState: AppState runnablesStore: RunnablesStore scroller: Scroller @@ -35,13 +24,12 @@ interface Props { @observer class Test extends Component { static defaultProps = { + events, appState, runnablesStore, scroller, } - @observable isOpen: boolean | null = null - containerRef: RefObject constructor (props: Props) { @@ -56,19 +44,14 @@ class Test extends Component { componentDidUpdate () { this._scrollIntoView() - - const cb = this.props.model.callbackAfterUpdate - - if (cb) { - cb() - } + this.props.model.callbackAfterUpdate() } _scrollIntoView () { const { appState, model, scroller } = this.props - const { isActive, shouldRender } = model + const { state, shouldRender } = model - if (appState.autoScrollingEnabled && appState.isRunning && shouldRender && isActive != null) { + if (appState.autoScrollingEnabled && appState.isRunning && shouldRender && state !== 'processing') { window.requestAnimationFrame(() => { // since this executes async in a RAF the ref might be null if (this.containerRef.current) { @@ -90,7 +73,7 @@ class Test extends Component { headerClass='runnable-wrapper' headerStyle={{ paddingLeft: indent(model.level) }} contentClass='runnable-instruments' - isOpen={this._shouldBeOpen()} + isOpen={model.isOpen} > {this._contents()} @@ -119,37 +102,11 @@ class Test extends Component { return (
    - - -
    - {model.commands.length ? : } -
    - + + this._scrollIntoView()} />
    ) } - - _shouldBeOpen () { - // if this.isOpen is non-null, prefer that since the user has - // explicity chosen to open or close the test - if (this.isOpen !== null) return this.isOpen - - // otherwise, look at reasons to auto-open the test - return this.props.model.state === 'failed' - || this.props.model.isOpen - || this.props.model.isLongRunning - || this.props.runnablesStore.hasSingleTest - } - - @action _toggleOpen = () => { - if (this.isOpen === null) { - this.isOpen = !this._shouldBeOpen() - } else { - this.isOpen = !this.isOpen - } - } } -export { NoCommands } - export default Test diff --git a/packages/runner/__snapshots__/retries.mochaEvents.spec.js b/packages/runner/__snapshots__/retries.mochaEvents.spec.js new file mode 100644 index 000000000000..cff75b2f8808 --- /dev/null +++ b/packages/runner/__snapshots__/retries.mochaEvents.spec.js @@ -0,0 +1,6458 @@ +exports['src/cypress/runner retries mochaEvents simple retry #1'] = [ + [ + "mocha", + "start", + { + "start": "match.date" + } + ], + [ + "mocha", + "suite", + { + "id": "r1", + "title": "", + "root": true, + "type": "suite", + "file": "relative/path/to/spec.js", + "retries": -1 + } + ], + [ + "mocha", + "suite", + { + "id": "r2", + "title": "suite 1", + "root": false, + "type": "suite", + "file": null, + "retries": -1 + } + ], + [ + "mocha", + "test", + { + "id": "r3", + "order": 1, + "title": "test 1", + "body": "[body]", + "type": "test", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r3", + "order": 1, + "title": "test 1", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "retry", + { + "id": "r3", + "order": 1, + "title": "test 1", + "err": "{Object 9}", + "state": "failed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": false, + "currentRetry": 0, + "retries": 1 + }, + { + "message": "[error message]", + "name": "AssertionError", + "stack": "match.string", + "sourceMappedStack": "match.string", + "parsedStack": "match.array", + "actual": null, + "showDiff": false + } + ], + [ + "mocha", + "test:after:run", + { + "id": "r3", + "order": 1, + "title": "test 1", + "err": "{Object 9}", + "state": "failed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "wallClockDuration": "match.number", + "timings": { + "lifecycle": "match.number", + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": false, + "currentRetry": 0, + "retries": 1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r3", + "title": "test 1", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "currentRetry": 1, + "retries": 1 + } + ], + [ + "mocha", + "pass", + { + "id": "r3", + "title": "test 1", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + }, + "file": null, + "final": true, + "currentRetry": 1, + "retries": 1 + } + ], + [ + "mocha", + "test end", + { + "id": "r3", + "title": "test 1", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + }, + "file": null, + "final": true, + "currentRetry": 1, + "retries": 1 + } + ], + [ + "mocha", + "suite end", + { + "id": "r2", + "title": "suite 1", + "root": false, + "type": "suite", + "file": null, + "retries": -1 + } + ], + [ + "mocha", + "test:after:run", + { + "id": "r3", + "title": "test 1", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "wallClockDuration": "match.number", + "timings": { + "lifecycle": "match.number", + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + }, + "file": null, + "final": true, + "currentRetry": 1, + "retries": 1 + } + ], + [ + "mocha", + "suite end", + { + "id": "r1", + "title": "", + "root": true, + "type": "suite", + "file": "relative/path/to/spec.js", + "retries": -1 + } + ], + [ + "mocha", + "end", + { + "end": "match.date" + } + ] +] + +exports['src/cypress/runner retries mochaEvents test retry with hooks #1'] = [ + [ + "mocha", + "start", + { + "start": "match.date" + } + ], + [ + "mocha", + "suite", + { + "id": "r1", + "title": "", + "root": true, + "type": "suite", + "file": "relative/path/to/spec.js", + "retries": -1 + } + ], + [ + "mocha", + "suite", + { + "id": "r2", + "title": "suite 1", + "root": false, + "type": "suite", + "file": null, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"before all\" hook", + "hookName": "before all", + "hookId": "h1", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r3", + "order": 1, + "title": "test 1", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"before all\" hook", + "hookName": "before all", + "hookId": "h1", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test", + { + "id": "r3", + "order": 1, + "title": "test 1", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before all": [ + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": 1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h2", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h2", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "retry", + { + "id": "r3", + "order": 1, + "title": "test 1", + "err": "{Object 9}", + "state": "failed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before all": [ + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": false, + "currentRetry": 0, + "retries": 1 + }, + { + "message": "[error message]", + "name": "AssertionError", + "stack": "match.string", + "sourceMappedStack": "match.string", + "parsedStack": "match.array", + "actual": null, + "showDiff": false + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:after:run", + { + "id": "r3", + "order": 1, + "title": "test 1", + "err": "{Object 9}", + "state": "failed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "wallClockDuration": "match.number", + "timings": { + "lifecycle": "match.number", + "before all": [ + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": false, + "currentRetry": 0, + "retries": 1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h2", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r3", + "title": "test 1", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "currentRetry": 1, + "retries": 1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h2", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"after all\" hook", + "hookName": "after all", + "hookId": "h3", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"after all\" hook", + "hookName": "after all", + "hookId": "h3", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "pass", + { + "id": "r3", + "title": "test 1", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "after all": [ + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "final": true, + "currentRetry": 1, + "retries": 1 + } + ], + [ + "mocha", + "test end", + { + "id": "r3", + "title": "test 1", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "after all": [ + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "final": true, + "currentRetry": 1, + "retries": 1 + } + ], + [ + "mocha", + "suite end", + { + "id": "r2", + "title": "suite 1", + "root": false, + "type": "suite", + "file": null, + "retries": -1 + } + ], + [ + "mocha", + "test:after:run", + { + "id": "r3", + "title": "test 1", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "wallClockDuration": "match.number", + "timings": { + "lifecycle": "match.number", + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "after all": [ + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "final": true, + "currentRetry": 1, + "retries": 1 + } + ], + [ + "mocha", + "suite end", + { + "id": "r1", + "title": "", + "root": true, + "type": "suite", + "file": "relative/path/to/spec.js", + "retries": -1 + } + ], + [ + "mocha", + "end", + { + "end": "match.date" + } + ] +] + +exports['src/cypress/runner retries mochaEvents test retry with [only] #1'] = [ + [ + "mocha", + "start", + { + "start": "match.date" + } + ], + [ + "mocha", + "suite", + { + "id": "r1", + "title": "", + "root": true, + "type": "suite", + "file": "relative/path/to/spec.js", + "retries": -1 + } + ], + [ + "mocha", + "suite", + { + "id": "r4", + "title": "suite 1", + "root": false, + "type": "suite", + "file": null, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r5", + "title": "\"before all\" hook", + "hookName": "before all", + "hookId": "h1", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r5", + "order": 2, + "title": "test 2", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r5", + "title": "\"before all\" hook", + "hookName": "before all", + "hookId": "h1", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test", + { + "id": "r5", + "order": 2, + "title": "test 2", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before all": [ + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": 1 + } + ], + [ + "mocha", + "hook", + { + "id": "r5", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h2", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r5", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h2", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "retry", + { + "id": "r5", + "order": 2, + "title": "test 2", + "err": "{Object 9}", + "state": "failed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before all": [ + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": false, + "currentRetry": 0, + "retries": 1 + }, + { + "message": "[error message]", + "name": "AssertionError", + "stack": "match.string", + "sourceMappedStack": "match.string", + "parsedStack": "match.array", + "actual": null, + "showDiff": false + } + ], + [ + "mocha", + "hook", + { + "id": "r5", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r5", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:after:run", + { + "id": "r5", + "order": 2, + "title": "test 2", + "err": "{Object 9}", + "state": "failed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "wallClockDuration": "match.number", + "timings": { + "lifecycle": "match.number", + "before all": [ + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": false, + "currentRetry": 0, + "retries": 1 + } + ], + [ + "mocha", + "hook", + { + "id": "r5", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h2", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r5", + "title": "test 2", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "currentRetry": 1, + "retries": 1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r5", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h2", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r5", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r5", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r5", + "title": "\"after all\" hook", + "hookName": "after all", + "hookId": "h3", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r5", + "title": "\"after all\" hook", + "hookName": "after all", + "hookId": "h3", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "pass", + { + "id": "r5", + "title": "test 2", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "after all": [ + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "final": true, + "currentRetry": 1, + "retries": 1 + } + ], + [ + "mocha", + "test end", + { + "id": "r5", + "title": "test 2", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "after all": [ + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "final": true, + "currentRetry": 1, + "retries": 1 + } + ], + [ + "mocha", + "suite end", + { + "id": "r4", + "title": "suite 1", + "root": false, + "type": "suite", + "file": null, + "retries": -1 + } + ], + [ + "mocha", + "test:after:run", + { + "id": "r5", + "title": "test 2", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "wallClockDuration": "match.number", + "timings": { + "lifecycle": "match.number", + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "after all": [ + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "final": true, + "currentRetry": 1, + "retries": 1 + } + ], + [ + "mocha", + "suite end", + { + "id": "r1", + "title": "", + "root": true, + "type": "suite", + "file": "relative/path/to/spec.js", + "retries": -1 + } + ], + [ + "mocha", + "end", + { + "end": "match.date" + } + ] +] + +exports['src/cypress/runner retries mochaEvents can retry from [beforeEach] #1'] = [ + [ + "mocha", + "start", + { + "start": "match.date" + } + ], + [ + "mocha", + "suite", + { + "id": "r1", + "title": "", + "root": true, + "type": "suite", + "file": "relative/path/to/spec.js", + "retries": -1 + } + ], + [ + "mocha", + "suite", + { + "id": "r2", + "title": "suite 1", + "root": false, + "type": "suite", + "file": null, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"before all\" hook", + "hookName": "before all", + "hookId": "h1", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r3", + "order": 1, + "title": "test 1", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"before all\" hook", + "hookName": "before all", + "hookId": "h1", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test", + { + "id": "r3", + "order": 1, + "title": "test 1", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before all": [ + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h6", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": 1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h2", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h2", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h3", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h3", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "retry", + { + "id": "r3", + "order": 1, + "title": "test 1", + "hookName": "before each", + "err": "{Object 9}", + "state": "failed", + "failedFromHookId": "h3", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before all": [ + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h6", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": false, + "currentRetry": 0, + "retries": 1 + }, + { + "message": "[error message]", + "name": "AssertionError", + "stack": "match.string", + "sourceMappedStack": "match.string", + "parsedStack": "match.array", + "actual": null, + "showDiff": false + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h6", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h6", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:after:run", + { + "id": "r3", + "order": 1, + "title": "test 1", + "hookName": "before each", + "err": "{Object 9}", + "state": "failed", + "failedFromHookId": "h3", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "wallClockDuration": "match.number", + "timings": { + "lifecycle": "match.number", + "before all": [ + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h6", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": false, + "currentRetry": 0, + "retries": 1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h2", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r3", + "title": "test 1", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "currentRetry": 1, + "retries": 1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h2", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h3", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h3", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h6", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h6", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"after all\" hook", + "hookName": "after all", + "hookId": "h5", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"after all\" hook", + "hookName": "after all", + "hookId": "h5", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "pass", + { + "id": "r3", + "title": "test 1", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h6", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "after all": [ + { + "hookId": "h5", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "final": true, + "currentRetry": 1, + "retries": 1 + } + ], + [ + "mocha", + "test end", + { + "id": "r3", + "title": "test 1", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h6", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "after all": [ + { + "hookId": "h5", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "final": true, + "currentRetry": 1, + "retries": 1 + } + ], + [ + "mocha", + "suite end", + { + "id": "r2", + "title": "suite 1", + "root": false, + "type": "suite", + "file": null, + "retries": -1 + } + ], + [ + "mocha", + "test:after:run", + { + "id": "r3", + "title": "test 1", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "wallClockDuration": "match.number", + "timings": { + "lifecycle": "match.number", + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h6", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "after all": [ + { + "hookId": "h5", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "final": true, + "currentRetry": 1, + "retries": 1 + } + ], + [ + "mocha", + "suite end", + { + "id": "r1", + "title": "", + "root": true, + "type": "suite", + "file": "relative/path/to/spec.js", + "retries": -1 + } + ], + [ + "mocha", + "end", + { + "end": "match.date" + } + ] +] + +exports['src/cypress/runner retries mochaEvents can retry from [afterEach] #1'] = [ + [ + "mocha", + "start", + { + "start": "match.date" + } + ], + [ + "mocha", + "suite", + { + "id": "r1", + "title": "", + "root": true, + "type": "suite", + "file": "relative/path/to/spec.js", + "retries": -1 + } + ], + [ + "mocha", + "suite", + { + "id": "r2", + "title": "suite 1", + "root": false, + "type": "suite", + "file": null, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"before all\" hook", + "hookName": "before all", + "hookId": "h2", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r3", + "order": 1, + "title": "test 1", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"before all\" hook", + "hookName": "before all", + "hookId": "h2", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test", + { + "id": "r3", + "order": 1, + "title": "test 1", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before all": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "before each": [ + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h6", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": 2 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h3", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h3", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h6", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h6", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h1", + "body": "[body]", + "type": "hook", + "file": "relative/path/to/spec.js", + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "retry", + { + "id": "r3", + "order": 1, + "title": "test 1", + "hookName": "after each", + "err": "{Object 9}", + "state": "failed", + "failedFromHookId": "h1", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before all": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "before each": [ + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h6", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": false, + "currentRetry": 0, + "retries": 2 + }, + { + "message": "[error message]", + "name": "AssertionError", + "stack": "match.string", + "sourceMappedStack": "match.string", + "parsedStack": "match.array", + "actual": null, + "showDiff": false + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h1", + "err": "{Object 9}", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": "relative/path/to/spec.js", + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:after:run", + { + "id": "r3", + "order": 1, + "title": "test 1", + "hookName": "after each", + "err": "{Object 9}", + "state": "failed", + "failedFromHookId": "h1", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "wallClockDuration": "match.number", + "timings": { + "lifecycle": "match.number", + "before all": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "before each": [ + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h6", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": false, + "currentRetry": 0, + "retries": 2 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h3", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r3", + "title": "test 1", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "currentRetry": 1, + "retries": 2 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h3", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h6", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h6", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h1", + "err": "{Object 9}", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": "relative/path/to/spec.js", + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h1", + "err": "{Object 9}", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": "relative/path/to/spec.js", + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "pass", + { + "id": "r3", + "title": "test 1", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before each": [ + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h6", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "final": true, + "currentRetry": 1, + "retries": 2 + } + ], + [ + "mocha", + "test end", + { + "id": "r3", + "title": "test 1", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before each": [ + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h6", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "final": true, + "currentRetry": 1, + "retries": 2 + } + ], + [ + "mocha", + "test:after:run", + { + "id": "r3", + "title": "test 1", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "wallClockDuration": "match.number", + "timings": { + "lifecycle": "match.number", + "before each": [ + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h6", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "final": true, + "currentRetry": 1, + "retries": 2 + } + ], + [ + "mocha", + "test", + { + "id": "r4", + "order": 2, + "title": "test 2", + "body": "[body]", + "type": "test", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r4", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h3", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r4", + "order": 2, + "title": "test 2", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r4", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h3", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r4", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r4", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r4", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h6", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r4", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h6", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r4", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h1", + "err": "{Object 9}", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": "relative/path/to/spec.js", + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r4", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h1", + "err": "{Object 9}", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": "relative/path/to/spec.js", + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "pass", + { + "id": "r4", + "order": 2, + "title": "test 2", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before each": [ + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h6", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 2 + } + ], + [ + "mocha", + "test end", + { + "id": "r4", + "order": 2, + "title": "test 2", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before each": [ + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h6", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 2 + } + ], + [ + "mocha", + "test:after:run", + { + "id": "r4", + "order": 2, + "title": "test 2", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "wallClockDuration": "match.number", + "timings": { + "lifecycle": "match.number", + "before each": [ + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h6", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 2 + } + ], + [ + "mocha", + "test", + { + "id": "r5", + "order": 3, + "title": "test 3", + "body": "[body]", + "type": "test", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r5", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h3", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r5", + "order": 3, + "title": "test 3", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r5", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h3", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r5", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r5", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r5", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h6", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r5", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h6", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r5", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h1", + "err": "{Object 9}", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": "relative/path/to/spec.js", + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r5", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h1", + "err": "{Object 9}", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": "relative/path/to/spec.js", + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r5", + "title": "\"after all\" hook", + "hookName": "after all", + "hookId": "h5", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r5", + "title": "\"after all\" hook", + "hookName": "after all", + "hookId": "h5", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "pass", + { + "id": "r5", + "order": 3, + "title": "test 3", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before each": [ + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h6", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "after all": [ + { + "hookId": "h5", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 2 + } + ], + [ + "mocha", + "test end", + { + "id": "r5", + "order": 3, + "title": "test 3", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before each": [ + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h6", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "after all": [ + { + "hookId": "h5", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 2 + } + ], + [ + "mocha", + "test:after:run", + { + "id": "r5", + "order": 3, + "title": "test 3", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "wallClockDuration": "match.number", + "timings": { + "lifecycle": "match.number", + "before each": [ + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h6", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "after all": [ + { + "hookId": "h5", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 2 + } + ], + [ + "mocha", + "suite end", + { + "id": "r2", + "title": "suite 1", + "root": false, + "type": "suite", + "file": null, + "retries": -1 + } + ], + [ + "mocha", + "suite", + { + "id": "r6", + "title": "suite 2", + "root": false, + "type": "suite", + "file": null, + "retries": -1 + } + ], + [ + "mocha", + "test", + { + "id": "r7", + "order": 4, + "title": "test 1", + "body": "[body]", + "type": "test", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r7", + "order": 4, + "title": "test 1", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r7", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h7", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "retry", + { + "id": "r7", + "order": 4, + "title": "test 1", + "hookName": "after each", + "err": "{Object 9}", + "state": "failed", + "failedFromHookId": "h7", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h7", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": false, + "currentRetry": 0, + "retries": 2 + }, + { + "message": "[error message]", + "name": "AssertionError", + "stack": "match.string", + "sourceMappedStack": "match.string", + "parsedStack": "match.array", + "actual": null, + "showDiff": false + } + ], + [ + "mocha", + "hook end", + { + "id": "r7", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h7", + "err": "{Object 9}", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r7", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h1", + "err": "{Object 9}", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": "relative/path/to/spec.js", + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r7", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h1", + "err": "{Object 9}", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": "relative/path/to/spec.js", + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:after:run", + { + "id": "r7", + "order": 4, + "title": "test 1", + "hookName": "after each", + "err": "{Object 9}", + "state": "failed", + "failedFromHookId": "h7", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "wallClockDuration": "match.number", + "timings": { + "lifecycle": "match.number", + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h7", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": false, + "currentRetry": 0, + "retries": 2 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r7", + "title": "test 1", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "currentRetry": 1, + "retries": 2 + } + ], + [ + "mocha", + "hook", + { + "id": "r7", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h7", + "err": "{Object 9}", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "retry", + { + "id": "r7", + "title": "test 1", + "hookName": "after each", + "err": "{Object 9}", + "state": "failed", + "failedFromHookId": "h7", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h7", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "final": false, + "currentRetry": 1, + "retries": 2 + }, + { + "message": "[error message]", + "name": "AssertionError", + "stack": "match.string", + "sourceMappedStack": "match.string", + "parsedStack": "match.array", + "actual": null, + "showDiff": false + } + ], + [ + "mocha", + "hook end", + { + "id": "r7", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h7", + "err": "{Object 9}", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r7", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h1", + "err": "{Object 9}", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": "relative/path/to/spec.js", + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r7", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h1", + "err": "{Object 9}", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": "relative/path/to/spec.js", + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:after:run", + { + "id": "r7", + "title": "test 1", + "hookName": "after each", + "err": "{Object 9}", + "state": "failed", + "failedFromHookId": "h7", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "wallClockDuration": "match.number", + "timings": { + "lifecycle": "match.number", + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h7", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "final": false, + "currentRetry": 1, + "retries": 2 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r7", + "title": "test 1", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "currentRetry": 2, + "retries": 2 + } + ], + [ + "mocha", + "hook", + { + "id": "r7", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h7", + "err": "{Object 9}", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r7", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h7", + "err": "{Object 9}", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r7", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h1", + "err": "{Object 9}", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": "relative/path/to/spec.js", + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r7", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h1", + "err": "{Object 9}", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": "relative/path/to/spec.js", + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "pass", + { + "id": "r7", + "title": "test 1", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h7", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "final": true, + "currentRetry": 2, + "retries": 2 + } + ], + [ + "mocha", + "test end", + { + "id": "r7", + "title": "test 1", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h7", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "final": true, + "currentRetry": 2, + "retries": 2 + } + ], + [ + "mocha", + "test:after:run", + { + "id": "r7", + "title": "test 1", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "wallClockDuration": "match.number", + "timings": { + "lifecycle": "match.number", + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h7", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "final": true, + "currentRetry": 2, + "retries": 2 + } + ], + [ + "mocha", + "suite end", + { + "id": "r6", + "title": "suite 2", + "root": false, + "type": "suite", + "file": null, + "retries": -1 + } + ], + [ + "mocha", + "suite", + { + "id": "r8", + "title": "suite 3", + "root": false, + "type": "suite", + "file": null, + "retries": -1 + } + ], + [ + "mocha", + "test", + { + "id": "r9", + "order": 5, + "title": "test 1", + "body": "[body]", + "type": "test", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r9", + "order": 5, + "title": "test 1", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r9", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h1", + "err": "{Object 9}", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": "relative/path/to/spec.js", + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r9", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h1", + "err": "{Object 9}", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": "relative/path/to/spec.js", + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "pass", + { + "id": "r9", + "order": 5, + "title": "test 1", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 2 + } + ], + [ + "mocha", + "test end", + { + "id": "r9", + "order": 5, + "title": "test 1", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 2 + } + ], + [ + "mocha", + "suite end", + { + "id": "r8", + "title": "suite 3", + "root": false, + "type": "suite", + "file": null, + "retries": -1 + } + ], + [ + "mocha", + "test:after:run", + { + "id": "r9", + "order": 5, + "title": "test 1", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "wallClockDuration": "match.number", + "timings": { + "lifecycle": "match.number", + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 2 + } + ], + [ + "mocha", + "suite end", + { + "id": "r1", + "title": "", + "root": true, + "type": "suite", + "file": "relative/path/to/spec.js", + "retries": -1 + } + ], + [ + "mocha", + "end", + { + "end": "match.date" + } + ] +] + +exports['src/cypress/runner retries mochaEvents cant retry from [before] #1'] = [ + [ + "mocha", + "start", + { + "start": "match.date" + } + ], + [ + "mocha", + "suite", + { + "id": "r1", + "title": "", + "root": true, + "type": "suite", + "file": "relative/path/to/spec.js", + "retries": -1 + } + ], + [ + "mocha", + "suite", + { + "id": "r2", + "title": "suite 1", + "root": false, + "type": "suite", + "file": null, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"before all\" hook", + "hookName": "before all", + "hookId": "h1", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r3", + "order": 1, + "title": "test 1", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "fail", + { + "id": "r3", + "title": "\"before all\" hook for \"test 1\"", + "hookName": "before all", + "hookId": "h1", + "err": "{Object 9}", + "state": "failed", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "originalTitle": "\"before all\" hook", + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + }, + { + "message": "[error message]", + "name": "AssertionError", + "stack": "match.string", + "sourceMappedStack": "match.string", + "parsedStack": "match.array", + "actual": null, + "showDiff": false + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"after all\" hook", + "hookName": "after all", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"after all\" hook", + "hookName": "after all", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "suite end", + { + "id": "r2", + "title": "suite 1", + "root": false, + "type": "suite", + "file": null, + "retries": -1 + } + ], + [ + "mocha", + "test end", + { + "id": "r3", + "order": 1, + "title": "test 1", + "hookName": "before all", + "err": "{Object 9}", + "state": "failed", + "failedFromHookId": "h1", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before all": [ + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "after all": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 1 + } + ], + [ + "mocha", + "test:after:run", + { + "id": "r3", + "order": 1, + "title": "test 1", + "hookName": "before all", + "err": "{Object 9}", + "state": "failed", + "failedFromHookId": "h1", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "wallClockDuration": "match.number", + "timings": { + "lifecycle": "match.number", + "before all": [ + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "after all": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 1 + } + ], + [ + "mocha", + "suite end", + { + "id": "r1", + "title": "", + "root": true, + "type": "suite", + "file": "relative/path/to/spec.js", + "retries": -1 + } + ], + [ + "mocha", + "end", + { + "end": "match.date" + } + ] +] + +exports['src/cypress/runner retries mochaEvents cant retry from [after] #1'] = [ + [ + "mocha", + "start", + { + "start": "match.date" + } + ], + [ + "mocha", + "suite", + { + "id": "r1", + "title": "", + "root": true, + "type": "suite", + "file": "relative/path/to/spec.js", + "retries": -1 + } + ], + [ + "mocha", + "suite", + { + "id": "r2", + "title": "suite 1", + "root": false, + "type": "suite", + "file": null, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"before all\" hook", + "hookName": "before all", + "hookId": "h1", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r3", + "order": 1, + "title": "test 1", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"before all\" hook", + "hookName": "before all", + "hookId": "h1", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test", + { + "id": "r3", + "order": 1, + "title": "test 1", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before all": [ + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h5", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h6", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "after all": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": 1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h2", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h2", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h3", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h3", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h5", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h5", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h6", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h6", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"after all\" hook", + "hookName": "after all", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "fail", + { + "id": "r3", + "title": "\"after all\" hook for \"test 1\"", + "hookName": "after all", + "hookId": "h4", + "err": "{Object 9}", + "state": "failed", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "originalTitle": "\"after all\" hook", + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + }, + { + "message": "[error message]", + "name": "AssertionError", + "stack": "match.string", + "sourceMappedStack": "match.string", + "parsedStack": "match.array", + "actual": null, + "showDiff": false + } + ], + [ + "mocha", + "test end", + { + "id": "r3", + "order": 1, + "title": "test 1", + "hookName": "after all", + "err": "{Object 9}", + "state": "failed", + "failedFromHookId": "h4", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before all": [ + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h5", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h6", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "after all": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 1 + } + ], + [ + "mocha", + "suite end", + { + "id": "r2", + "title": "suite 1", + "root": false, + "type": "suite", + "file": null, + "retries": -1 + } + ], + [ + "mocha", + "test:after:run", + { + "id": "r3", + "order": 1, + "title": "test 1", + "hookName": "after all", + "err": "{Object 9}", + "state": "failed", + "failedFromHookId": "h4", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "wallClockDuration": "match.number", + "timings": { + "lifecycle": "match.number", + "before all": [ + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h5", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + { + "hookId": "h6", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "after all": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 1 + } + ], + [ + "mocha", + "suite end", + { + "id": "r1", + "title": "", + "root": true, + "type": "suite", + "file": "relative/path/to/spec.js", + "retries": -1 + } + ], + [ + "mocha", + "end", + { + "end": "match.date" + } + ] +] + +exports['src/cypress/runner retries mochaEvents three tests with retry #1'] = [ + [ + "mocha", + "start", + { + "start": "match.date" + } + ], + [ + "mocha", + "suite", + { + "id": "r1", + "title": "", + "root": true, + "type": "suite", + "file": "relative/path/to/spec.js", + "retries": -1 + } + ], + [ + "mocha", + "suite", + { + "id": "r2", + "title": "suite 1", + "root": false, + "type": "suite", + "file": null, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"before all\" hook", + "hookName": "before all", + "hookId": "h1", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r3", + "order": 1, + "title": "test 1", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"before all\" hook", + "hookName": "before all", + "hookId": "h1", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test", + { + "id": "r3", + "order": 1, + "title": "test 1", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before all": [ + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": 2 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h2", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h2", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "pass", + { + "id": "r3", + "order": 1, + "title": "test 1", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before all": [ + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 2 + } + ], + [ + "mocha", + "test end", + { + "id": "r3", + "order": 1, + "title": "test 1", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before all": [ + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 2 + } + ], + [ + "mocha", + "test:after:run", + { + "id": "r3", + "order": 1, + "title": "test 1", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "wallClockDuration": "match.number", + "timings": { + "lifecycle": "match.number", + "before all": [ + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 2 + } + ], + [ + "mocha", + "test", + { + "id": "r4", + "order": 2, + "title": "test 2", + "body": "[body]", + "type": "test", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r4", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h2", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r4", + "order": 2, + "title": "test 2", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r4", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h2", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "retry", + { + "id": "r4", + "order": 2, + "title": "test 2", + "err": "{Object 9}", + "state": "failed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": false, + "currentRetry": 0, + "retries": 2 + }, + { + "message": "[error message]", + "name": "AssertionError", + "stack": "match.string", + "sourceMappedStack": "match.string", + "parsedStack": "match.array", + "actual": null, + "showDiff": false + } + ], + [ + "mocha", + "hook", + { + "id": "r4", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r4", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:after:run", + { + "id": "r4", + "order": 2, + "title": "test 2", + "err": "{Object 9}", + "state": "failed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "wallClockDuration": "match.number", + "timings": { + "lifecycle": "match.number", + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": false, + "currentRetry": 0, + "retries": 2 + } + ], + [ + "mocha", + "hook", + { + "id": "r4", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h2", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r4", + "title": "test 2", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "currentRetry": 1, + "retries": 2 + } + ], + [ + "mocha", + "hook end", + { + "id": "r4", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h2", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "retry", + { + "id": "r4", + "title": "test 2", + "err": "{Object 9}", + "state": "failed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "final": false, + "currentRetry": 1, + "retries": 2 + }, + { + "message": "[error message]", + "name": "AssertionError", + "stack": "match.string", + "sourceMappedStack": "match.string", + "parsedStack": "match.array", + "actual": null, + "showDiff": false + } + ], + [ + "mocha", + "hook", + { + "id": "r4", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r4", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:after:run", + { + "id": "r4", + "title": "test 2", + "err": "{Object 9}", + "state": "failed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "wallClockDuration": "match.number", + "timings": { + "lifecycle": "match.number", + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "final": false, + "currentRetry": 1, + "retries": 2 + } + ], + [ + "mocha", + "hook", + { + "id": "r4", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h2", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r4", + "title": "test 2", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "currentRetry": 2, + "retries": 2 + } + ], + [ + "mocha", + "hook end", + { + "id": "r4", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h2", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r4", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r4", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "pass", + { + "id": "r4", + "title": "test 2", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "final": true, + "currentRetry": 2, + "retries": 2 + } + ], + [ + "mocha", + "test end", + { + "id": "r4", + "title": "test 2", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "final": true, + "currentRetry": 2, + "retries": 2 + } + ], + [ + "mocha", + "test:after:run", + { + "id": "r4", + "title": "test 2", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "wallClockDuration": "match.number", + "timings": { + "lifecycle": "match.number", + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "final": true, + "currentRetry": 2, + "retries": 2 + } + ], + [ + "mocha", + "test", + { + "id": "r5", + "order": 3, + "title": "test 3", + "body": "[body]", + "type": "test", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r5", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h2", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r5", + "order": 3, + "title": "test 3", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r5", + "title": "\"before each\" hook", + "hookName": "before each", + "hookId": "h2", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r5", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r5", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r5", + "title": "\"after all\" hook", + "hookName": "after all", + "hookId": "h3", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r5", + "title": "\"after all\" hook", + "hookName": "after all", + "hookId": "h3", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "pass", + { + "id": "r5", + "order": 3, + "title": "test 3", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "after all": [ + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 2 + } + ], + [ + "mocha", + "test end", + { + "id": "r5", + "order": 3, + "title": "test 3", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "after all": [ + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 2 + } + ], + [ + "mocha", + "suite end", + { + "id": "r2", + "title": "suite 1", + "root": false, + "type": "suite", + "file": null, + "retries": -1 + } + ], + [ + "mocha", + "test:after:run", + { + "id": "r5", + "order": 3, + "title": "test 3", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "wallClockDuration": "match.number", + "timings": { + "lifecycle": "match.number", + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "after all": [ + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 2 + } + ], + [ + "mocha", + "suite end", + { + "id": "r1", + "title": "", + "root": true, + "type": "suite", + "file": "relative/path/to/spec.js", + "retries": -1 + } + ], + [ + "mocha", + "end", + { + "end": "match.date" + } + ] +] + +exports['src/cypress/runner retries mochaEvents screenshots retry screenshot in test body #1'] = [ + "take:screenshot", + { + "titles": [ + "suite 1", + "test 1" + ], + "testId": "r3", + "testAttemptIndex": "match.match(0)", + "capture": "fullPage", + "clip": { + "x": 0, + "y": 0, + "width": 1000, + "height": 660 + }, + "viewport": { + "width": 1000, + "height": 660 + }, + "scaled": false, + "blackout": [], + "startTime": "match.string", + "current": 1, + "total": 1 + } +] + +exports['src/cypress/runner retries mochaEvents screenshots retry screenshot in test body #2'] = { + "id": "r3", + "testAttemptIndex": "match.match(0)", + "isOpen": false, + "appOnly": true, + "scale": false, + "waitForCommandSynchronization": false, + "disableTimersAndAnimations": true, + "blackout": [] +} + +exports['serialize state - retries'] = { + "currentId": "r6", + "tests": { + "r3": { + "id": "r3", + "order": 1, + "title": "test 1", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": 1, + "wallClockStartedAt": "1970-01-01T00:00:00.000Z", + "wallClockDuration": 1, + "timings": { + "lifecycle": 1, + "before all": [ + { + "hookId": "h1", + "fnDuration": 1, + "afterFnDuration": 1 + } + ], + "before each": [ + { + "hookId": "h2", + "fnDuration": 1, + "afterFnDuration": 1 + } + ], + "test": { + "fnDuration": 1, + "afterFnDuration": 1 + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": 1, + "afterFnDuration": 1 + } + ], + "after all": [ + { + "hookId": "h3", + "fnDuration": 1, + "afterFnDuration": 1 + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 1, + "hooks": [], + "prevAttempts": [] + }, + "r5": { + "id": "r5", + "title": "test 1", + "state": "passed", + "body": "[body]", + "type": "test", + "duration": 1, + "wallClockStartedAt": "1970-01-01T00:00:00.000Z", + "wallClockDuration": 1, + "timings": { + "lifecycle": 1, + "test": { + "fnDuration": 1, + "afterFnDuration": 1 + } + }, + "file": null, + "final": true, + "currentRetry": 1, + "retries": 1, + "prevAttempts": [ + { + "id": "r5", + "order": 2, + "title": "test 1", + "err": "{Object 9}", + "state": "failed", + "body": "[body]", + "type": "test", + "duration": 1, + "wallClockStartedAt": "1970-01-01T00:00:00.000Z", + "wallClockDuration": 1, + "timings": { + "lifecycle": 1, + "test": { + "fnDuration": 1, + "afterFnDuration": 1 + } + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": false, + "currentRetry": 0, + "retries": 1, + "hooks": [] + } + ] + } + }, + "startTime": "1970-01-01T00:00:00.000Z", + "emissions": { + "started": { + "r1": true, + "r2": true, + "r3": true, + "r4": true, + "r5": true, + "r6": true + }, + "ended": { + "r3": true, + "r2": true, + "r5": true + } + }, + "passed": 2, + "failed": 0, + "pending": 0, + "numLogs": 0 +} diff --git a/packages/runner/__snapshots__/runner.mochaEvents.spec.js b/packages/runner/__snapshots__/runner.mochaEvents.spec.js index 97daa2d5fa39..b32f5dd5eb04 100644 --- a/packages/runner/__snapshots__/runner.mochaEvents.spec.js +++ b/packages/runner/__snapshots__/runner.mochaEvents.spec.js @@ -14,7 +14,8 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i "title": "", "root": true, "type": "suite", - "file": "relative/path/to/spec.js" + "file": "relative/path/to/spec.js", + "retries": -1 } ], [ @@ -25,7 +26,8 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i "title": "suite 1", "root": false, "type": "suite", - "file": null + "file": null, + "retries": -1 } ], [ @@ -39,7 +41,25 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i "body": "[body]", "type": "hook", "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r3", + "order": 1, + "title": "test 1", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -57,7 +77,9 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i "duration": "match.number", "file": null, "originalTitle": "\"before all\" hook", - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 }, { "message": "[error message]", @@ -77,7 +99,40 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i "title": "suite 1", "root": false, "type": "suite", - "file": null + "file": null, + "retries": -1 + } + ], + [ + "mocha", + "test end", + { + "id": "r3", + "order": 1, + "title": "test 1", + "hookName": "before all", + "err": "{Object 9}", + "state": "failed", + "failedFromHookId": "h1", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before all": [ + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 0 } ], [ @@ -107,7 +162,10 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i ] }, "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 0 } ], [ @@ -118,7 +176,8 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i "title": "", "root": true, "type": "suite", - "file": "relative/path/to/spec.js" + "file": "relative/path/to/spec.js", + "retries": -1 } ], [ @@ -146,7 +205,8 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i "title": "", "root": true, "type": "suite", - "file": "relative/path/to/spec.js" + "file": "relative/path/to/spec.js", + "retries": -1 } ], [ @@ -157,7 +217,8 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i "title": "suite 1", "root": false, "type": "suite", - "file": null + "file": null, + "retries": -1 } ], [ @@ -170,7 +231,9 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i "body": "[body]", "type": "test", "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -184,7 +247,25 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i "body": "[body]", "type": "hook", "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r3", + "order": 1, + "title": "test 1", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -202,7 +283,9 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i "duration": "match.number", "file": null, "originalTitle": "\"before each\" hook", - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 }, { "message": "[error message]", @@ -214,6 +297,38 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i "showDiff": false } ], + [ + "mocha", + "test end", + { + "id": "r3", + "order": 1, + "title": "test 1", + "hookName": "before each", + "err": "{Object 9}", + "state": "failed", + "failedFromHookId": "h1", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "before each": [ + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 0 + } + ], [ "mocha", "suite end", @@ -222,7 +337,8 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i "title": "suite 1", "root": false, "type": "suite", - "file": null + "file": null, + "retries": -1 } ], [ @@ -252,7 +368,10 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i ] }, "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 0 } ], [ @@ -263,7 +382,8 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i "title": "", "root": true, "type": "suite", - "file": "relative/path/to/spec.js" + "file": "relative/path/to/spec.js", + "retries": -1 } ], [ @@ -291,7 +411,8 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i "title": "", "root": true, "type": "suite", - "file": "relative/path/to/spec.js" + "file": "relative/path/to/spec.js", + "retries": -1 } ], [ @@ -302,80 +423,39 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i "title": "suite 1", "root": false, "type": "suite", - "file": null - } - ], - [ - "mocha", - "test", - { - "id": "r3", - "order": 1, - "title": "test 1", - "body": "[body]", - "type": "test", "file": null, - "invocationDetails": "{Object 8}" + "retries": -1 } ], [ "mocha", - "pass", + "test", { "id": "r3", "order": 1, "title": "test 1", - "state": "passed", "body": "[body]", "type": "test", - "duration": "match.number", - "wallClockStartedAt": "match.date", - "timings": { - "lifecycle": "match.number", - "test": { - "fnDuration": "match.number", - "afterFnDuration": "match.number" - }, - "after each": [ - { - "hookId": "h1", - "fnDuration": "match.number", - "afterFnDuration": "match.number" - } - ] - }, "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ "mocha", - "test end", + "test:before:run", { "id": "r3", "order": 1, "title": "test 1", - "state": "passed", "body": "[body]", "type": "test", - "duration": "match.number", "wallClockStartedAt": "match.date", - "timings": { - "lifecycle": "match.number", - "test": { - "fnDuration": "match.number", - "afterFnDuration": "match.number" - }, - "after each": [ - { - "hookId": "h1", - "fnDuration": "match.number", - "afterFnDuration": "match.number" - } - ] - }, "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -389,7 +469,9 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i "body": "[body]", "type": "hook", "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -407,7 +489,9 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i "duration": "match.number", "file": null, "originalTitle": "\"after each\" hook", - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 }, { "message": "[error message]", @@ -419,6 +503,42 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i "showDiff": false } ], + [ + "mocha", + "test end", + { + "id": "r3", + "order": 1, + "title": "test 1", + "hookName": "after each", + "err": "{Object 9}", + "state": "failed", + "failedFromHookId": "h1", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 0 + } + ], [ "mocha", "suite end", @@ -427,7 +547,8 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i "title": "suite 1", "root": false, "type": "suite", - "file": null + "file": null, + "retries": -1 } ], [ @@ -461,7 +582,10 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i ] }, "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 0 } ], [ @@ -472,7 +596,8 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i "title": "", "root": true, "type": "suite", - "file": "relative/path/to/spec.js" + "file": "relative/path/to/spec.js", + "retries": -1 } ], [ @@ -500,7 +625,8 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i "title": "", "root": true, "type": "suite", - "file": "relative/path/to/spec.js" + "file": "relative/path/to/spec.js", + "retries": -1 } ], [ @@ -511,7 +637,8 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i "title": "suite 1", "root": false, "type": "suite", - "file": null + "file": null, + "retries": -1 } ], [ @@ -524,7 +651,25 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i "body": "[body]", "type": "test", "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r3", + "order": 1, + "title": "test 1", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -547,7 +692,10 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i } }, "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 0 } ], [ @@ -570,7 +718,10 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i } }, "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 0 } ], [ @@ -594,7 +745,10 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i } }, "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 0 } ], [ @@ -607,67 +761,25 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i "body": "[body]", "type": "test", "file": null, - "invocationDetails": "{Object 8}" - } - ], - [ - "mocha", - "pass", - { - "id": "r4", - "order": 2, - "title": "test 2", - "state": "passed", - "body": "[body]", - "type": "test", - "duration": "match.number", - "wallClockStartedAt": "match.date", - "timings": { - "lifecycle": "match.number", - "test": { - "fnDuration": "match.number", - "afterFnDuration": "match.number" - }, - "after all": [ - { - "hookId": "h1", - "fnDuration": "match.number", - "afterFnDuration": "match.number" - } - ] - }, - "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ "mocha", - "test end", + "test:before:run", { "id": "r4", "order": 2, "title": "test 2", - "state": "passed", "body": "[body]", "type": "test", - "duration": "match.number", "wallClockStartedAt": "match.date", - "timings": { - "lifecycle": "match.number", - "test": { - "fnDuration": "match.number", - "afterFnDuration": "match.number" - }, - "after all": [ - { - "hookId": "h1", - "fnDuration": "match.number", - "afterFnDuration": "match.number" - } - ] - }, "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -681,7 +793,9 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i "body": "[body]", "type": "hook", "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -699,7 +813,9 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i "duration": "match.number", "file": null, "originalTitle": "\"after all\" hook", - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 }, { "message": "[error message]", @@ -713,14 +829,51 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i ], [ "mocha", - "suite end", + "test end", { - "id": "r2", - "title": "suite 1", - "root": false, - "type": "suite", - "file": null - } + "id": "r4", + "order": 2, + "title": "test 2", + "hookName": "after all", + "err": "{Object 9}", + "state": "failed", + "failedFromHookId": "h1", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "timings": { + "lifecycle": "match.number", + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after all": [ + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 0 + } + ], + [ + "mocha", + "suite end", + { + "id": "r2", + "title": "suite 1", + "root": false, + "type": "suite", + "file": null, + "retries": -1 + } ], [ "mocha", @@ -753,7 +906,10 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i ] }, "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 0 } ], [ @@ -764,7 +920,8 @@ exports['src/cypress/runner tests finish with correct state hook failures fail i "title": "", "root": true, "type": "suite", - "file": "relative/path/to/spec.js" + "file": "relative/path/to/spec.js", + "retries": -1 } ], [ @@ -792,7 +949,8 @@ exports['src/cypress/runner tests finish with correct state mocha grep fail with "title": "", "root": true, "type": "suite", - "file": "relative/path/to/spec.js" + "file": "relative/path/to/spec.js", + "retries": -1 } ], [ @@ -803,7 +961,8 @@ exports['src/cypress/runner tests finish with correct state mocha grep fail with "title": "suite 1", "root": false, "type": "suite", - "file": null + "file": null, + "retries": -1 } ], [ @@ -817,7 +976,25 @@ exports['src/cypress/runner tests finish with correct state mocha grep fail with "body": "[body]", "type": "hook", "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r5", + "order": 2, + "title": "test 2", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -832,7 +1009,9 @@ exports['src/cypress/runner tests finish with correct state mocha grep fail with "type": "hook", "duration": "match.number", "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -840,6 +1019,7 @@ exports['src/cypress/runner tests finish with correct state mocha grep fail with "test", { "id": "r5", + "order": 2, "title": "test 2", "body": "[body]", "type": "test", @@ -880,7 +1060,9 @@ exports['src/cypress/runner tests finish with correct state mocha grep fail with ] }, "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": 0 } ], [ @@ -894,7 +1076,9 @@ exports['src/cypress/runner tests finish with correct state mocha grep fail with "body": "[body]", "type": "hook", "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -909,7 +1093,9 @@ exports['src/cypress/runner tests finish with correct state mocha grep fail with "type": "hook", "duration": "match.number", "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -917,6 +1103,7 @@ exports['src/cypress/runner tests finish with correct state mocha grep fail with "fail", { "id": "r5", + "order": 2, "title": "test 2", "err": "{Object 9}", "state": "failed", @@ -960,7 +1147,9 @@ exports['src/cypress/runner tests finish with correct state mocha grep fail with ] }, "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": 0 }, { "message": "[error message]", @@ -972,57 +1161,6 @@ exports['src/cypress/runner tests finish with correct state mocha grep fail with "showDiff": false } ], - [ - "mocha", - "test end", - { - "id": "r5", - "title": "test 2", - "err": "{Object 9}", - "state": "failed", - "body": "[body]", - "type": "test", - "duration": "match.number", - "wallClockStartedAt": "match.date", - "timings": { - "lifecycle": "match.number", - "before all": [ - { - "hookId": "h1", - "fnDuration": "match.number", - "afterFnDuration": "match.number" - } - ], - "before each": [ - { - "hookId": "h2", - "fnDuration": "match.number", - "afterFnDuration": "match.number" - } - ], - "test": { - "fnDuration": "match.number", - "afterFnDuration": "match.number" - }, - "after each": [ - { - "hookId": "h4", - "fnDuration": "match.number", - "afterFnDuration": "match.number" - } - ], - "after all": [ - { - "hookId": "h3", - "fnDuration": "match.number", - "afterFnDuration": "match.number" - } - ] - }, - "file": null, - "invocationDetails": "{Object 8}" - } - ], [ "mocha", "hook", @@ -1034,7 +1172,9 @@ exports['src/cypress/runner tests finish with correct state mocha grep fail with "body": "[body]", "type": "hook", "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -1049,7 +1189,9 @@ exports['src/cypress/runner tests finish with correct state mocha grep fail with "type": "hook", "duration": "match.number", "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -1063,7 +1205,9 @@ exports['src/cypress/runner tests finish with correct state mocha grep fail with "body": "[body]", "type": "hook", "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -1078,14 +1222,17 @@ exports['src/cypress/runner tests finish with correct state mocha grep fail with "type": "hook", "duration": "match.number", "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ "mocha", - "test:after:run", + "test end", { "id": "r5", + "order": 2, "title": "test 2", "err": "{Object 9}", "state": "failed", @@ -1093,7 +1240,6 @@ exports['src/cypress/runner tests finish with correct state mocha grep fail with "type": "test", "duration": "match.number", "wallClockStartedAt": "match.date", - "wallClockDuration": "match.number", "timings": { "lifecycle": "match.number", "before all": [ @@ -1130,7 +1276,10 @@ exports['src/cypress/runner tests finish with correct state mocha grep fail with ] }, "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 0 } ], [ @@ -1141,7 +1290,64 @@ exports['src/cypress/runner tests finish with correct state mocha grep fail with "title": "suite 1", "root": false, "type": "suite", - "file": null + "file": null, + "retries": -1 + } + ], + [ + "mocha", + "test:after:run", + { + "id": "r5", + "order": 2, + "title": "test 2", + "err": "{Object 9}", + "state": "failed", + "body": "[body]", + "type": "test", + "duration": "match.number", + "wallClockStartedAt": "match.date", + "wallClockDuration": "match.number", + "timings": { + "lifecycle": "match.number", + "before all": [ + { + "hookId": "h1", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "before each": [ + { + "hookId": "h2", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "test": { + "fnDuration": "match.number", + "afterFnDuration": "match.number" + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ], + "after all": [ + { + "hookId": "h3", + "fnDuration": "match.number", + "afterFnDuration": "match.number" + } + ] + }, + "file": null, + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 0 } ], [ @@ -1152,7 +1358,8 @@ exports['src/cypress/runner tests finish with correct state mocha grep fail with "title": "", "root": true, "type": "suite", - "file": "relative/path/to/spec.js" + "file": "relative/path/to/spec.js", + "retries": -1 } ], [ @@ -1180,7 +1387,8 @@ exports['src/cypress/runner tests finish with correct state mocha grep pass with "title": "", "root": true, "type": "suite", - "file": "relative/path/to/spec.js" + "file": "relative/path/to/spec.js", + "retries": -1 } ], [ @@ -1191,7 +1399,8 @@ exports['src/cypress/runner tests finish with correct state mocha grep pass with "title": "suite 1", "root": false, "type": "suite", - "file": null + "file": null, + "retries": -1 } ], [ @@ -1205,7 +1414,25 @@ exports['src/cypress/runner tests finish with correct state mocha grep pass with "body": "[body]", "type": "hook", "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r5", + "order": 2, + "title": "test 2", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -1220,7 +1447,9 @@ exports['src/cypress/runner tests finish with correct state mocha grep pass with "type": "hook", "duration": "match.number", "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -1228,6 +1457,7 @@ exports['src/cypress/runner tests finish with correct state mocha grep pass with "test", { "id": "r5", + "order": 2, "title": "test 2", "body": "[body]", "type": "test", @@ -1268,7 +1498,9 @@ exports['src/cypress/runner tests finish with correct state mocha grep pass with ] }, "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": 0 } ], [ @@ -1282,7 +1514,9 @@ exports['src/cypress/runner tests finish with correct state mocha grep pass with "body": "[body]", "type": "hook", "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -1297,7 +1531,75 @@ exports['src/cypress/runner tests finish with correct state mocha grep pass with "type": "hook", "duration": "match.number", "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r5", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r5", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r5", + "title": "\"after all\" hook", + "hookName": "after all", + "hookId": "h3", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r5", + "title": "\"after all\" hook", + "hookName": "after all", + "hookId": "h3", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -1305,6 +1607,7 @@ exports['src/cypress/runner tests finish with correct state mocha grep pass with "pass", { "id": "r5", + "order": 2, "title": "test 2", "state": "passed", "body": "[body]", @@ -1347,7 +1650,10 @@ exports['src/cypress/runner tests finish with correct state mocha grep pass with ] }, "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 0 } ], [ @@ -1355,6 +1661,7 @@ exports['src/cypress/runner tests finish with correct state mocha grep pass with "test end", { "id": "r5", + "order": 2, "title": "test 2", "state": "passed", "body": "[body]", @@ -1397,65 +1704,22 @@ exports['src/cypress/runner tests finish with correct state mocha grep pass with ] }, "file": null, - "invocationDetails": "{Object 8}" - } - ], - [ - "mocha", - "hook", - { - "id": "r5", - "title": "\"after each\" hook", - "hookName": "after each", - "hookId": "h4", - "body": "[body]", - "type": "hook", - "file": null, - "invocationDetails": "{Object 8}" - } - ], - [ - "mocha", - "hook end", - { - "id": "r5", - "title": "\"after each\" hook", - "hookName": "after each", - "hookId": "h4", - "body": "[body]", - "type": "hook", - "duration": "match.number", - "file": null, - "invocationDetails": "{Object 8}" - } - ], - [ - "mocha", - "hook", - { - "id": "r5", - "title": "\"after all\" hook", - "hookName": "after all", - "hookId": "h3", - "body": "[body]", - "type": "hook", - "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 0 } ], [ "mocha", - "hook end", + "suite end", { - "id": "r5", - "title": "\"after all\" hook", - "hookName": "after all", - "hookId": "h3", - "body": "[body]", - "type": "hook", - "duration": "match.number", + "id": "r4", + "title": "suite 1", + "root": false, + "type": "suite", "file": null, - "invocationDetails": "{Object 8}" + "retries": -1 } ], [ @@ -1463,6 +1727,7 @@ exports['src/cypress/runner tests finish with correct state mocha grep pass with "test:after:run", { "id": "r5", + "order": 2, "title": "test 2", "state": "passed", "body": "[body]", @@ -1506,18 +1771,10 @@ exports['src/cypress/runner tests finish with correct state mocha grep pass with ] }, "file": null, - "invocationDetails": "{Object 8}" - } - ], - [ - "mocha", - "suite end", - { - "id": "r4", - "title": "suite 1", - "root": false, - "type": "suite", - "file": null + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 0 } ], [ @@ -1528,7 +1785,8 @@ exports['src/cypress/runner tests finish with correct state mocha grep pass with "title": "", "root": true, "type": "suite", - "file": "relative/path/to/spec.js" + "file": "relative/path/to/spec.js", + "retries": -1 } ], [ @@ -1542,78 +1800,7 @@ exports['src/cypress/runner tests finish with correct state mocha grep pass with exports['serialize state - hooks'] = { "currentId": "r6", - "tests": { - "r3": { - "id": "r3", - "order": 1, - "title": "test 1", - "state": "passed", - "body": "stub", - "type": "test", - "duration": 1, - "wallClockStartedAt": "1970-01-01T00:00:00.000Z", - "wallClockDuration": 1, - "timings": { - "lifecycle": 1, - "before all": [ - { - "hookId": "h1", - "fnDuration": 1, - "afterFnDuration": 1 - } - ], - "before each": [ - { - "hookId": "h2", - "fnDuration": 1, - "afterFnDuration": 1 - } - ], - "test": { - "fnDuration": 1, - "afterFnDuration": 1 - }, - "after each": [ - { - "hookId": "h4", - "fnDuration": 1, - "afterFnDuration": 1 - } - ], - "after all": [ - { - "hookId": "h3", - "fnDuration": 1, - "afterFnDuration": 1 - } - ] - }, - "file": null, - "invocationDetails": "{Object 8}", - "hooks": [] - }, - "r5": { - "id": "r5", - "order": 2, - "title": "test 1", - "state": "passed", - "body": "stub", - "type": "test", - "duration": 1, - "wallClockStartedAt": "1970-01-01T00:00:00.000Z", - "wallClockDuration": 1, - "timings": { - "lifecycle": 1, - "test": { - "fnDuration": 1, - "afterFnDuration": 1 - } - }, - "file": null, - "invocationDetails": "{Object 8}", - "hooks": [] - } - }, + "tests": "{Object 2}", "startTime": "1970-01-01T00:00:00.000Z", "emissions": { "started": { @@ -1645,6 +1832,7 @@ exports['src/cypress/runner other specs screenshots screenshot after failed test "test 1" ], "testId": "r3", + "testAttemptIndex": 0, "simple": true, "testFailure": true, "capture": "runner", @@ -1681,7 +1869,8 @@ exports['src/cypress/runner mocha events simple single test #1'] = [ "title": "", "root": true, "type": "suite", - "file": "relative/path/to/spec.js" + "file": "relative/path/to/spec.js", + "retries": -1 } ], [ @@ -1692,7 +1881,8 @@ exports['src/cypress/runner mocha events simple single test #1'] = [ "title": "suite 1", "root": false, "type": "suite", - "file": null + "file": null, + "retries": -1 } ], [ @@ -1705,7 +1895,25 @@ exports['src/cypress/runner mocha events simple single test #1'] = [ "body": "[body]", "type": "test", "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r3", + "order": 1, + "title": "test 1", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -1728,7 +1936,10 @@ exports['src/cypress/runner mocha events simple single test #1'] = [ } }, "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 0 } ], [ @@ -1751,7 +1962,10 @@ exports['src/cypress/runner mocha events simple single test #1'] = [ } }, "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 0 } ], [ @@ -1762,7 +1976,8 @@ exports['src/cypress/runner mocha events simple single test #1'] = [ "title": "suite 1", "root": false, "type": "suite", - "file": null + "file": null, + "retries": -1 } ], [ @@ -1786,7 +2001,10 @@ exports['src/cypress/runner mocha events simple single test #1'] = [ } }, "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 0 } ], [ @@ -1797,7 +2015,8 @@ exports['src/cypress/runner mocha events simple single test #1'] = [ "title": "", "root": true, "type": "suite", - "file": "relative/path/to/spec.js" + "file": "relative/path/to/spec.js", + "retries": -1 } ], [ @@ -1825,7 +2044,8 @@ exports['src/cypress/runner mocha events simple three tests #1'] = [ "title": "", "root": true, "type": "suite", - "file": "relative/path/to/spec.js" + "file": "relative/path/to/spec.js", + "retries": -1 } ], [ @@ -1836,7 +2056,8 @@ exports['src/cypress/runner mocha events simple three tests #1'] = [ "title": "suite 1", "root": false, "type": "suite", - "file": null + "file": null, + "retries": -1 } ], [ @@ -1850,7 +2071,25 @@ exports['src/cypress/runner mocha events simple three tests #1'] = [ "body": "[body]", "type": "hook", "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r3", + "order": 1, + "title": "test 1", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -1865,7 +2104,9 @@ exports['src/cypress/runner mocha events simple three tests #1'] = [ "type": "hook", "duration": "match.number", "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -1907,7 +2148,9 @@ exports['src/cypress/runner mocha events simple three tests #1'] = [ ] }, "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": 0 } ], [ @@ -1921,7 +2164,9 @@ exports['src/cypress/runner mocha events simple three tests #1'] = [ "body": "[body]", "type": "hook", "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -1936,7 +2181,42 @@ exports['src/cypress/runner mocha events simple three tests #1'] = [ "type": "hook", "duration": "match.number", "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r3", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r3", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -1980,7 +2260,10 @@ exports['src/cypress/runner mocha events simple three tests #1'] = [ ] }, "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 0 } ], [ @@ -2024,36 +2307,10 @@ exports['src/cypress/runner mocha events simple three tests #1'] = [ ] }, "file": null, - "invocationDetails": "{Object 8}" - } - ], - [ - "mocha", - "hook", - { - "id": "r3", - "title": "\"after each\" hook", - "hookName": "after each", - "hookId": "h4", - "body": "[body]", - "type": "hook", - "file": null, - "invocationDetails": "{Object 8}" - } - ], - [ - "mocha", - "hook end", - { - "id": "r3", - "title": "\"after each\" hook", - "hookName": "after each", - "hookId": "h4", - "body": "[body]", - "type": "hook", - "duration": "match.number", - "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 0 } ], [ @@ -2098,7 +2355,10 @@ exports['src/cypress/runner mocha events simple three tests #1'] = [ ] }, "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 0 } ], [ @@ -2111,7 +2371,9 @@ exports['src/cypress/runner mocha events simple three tests #1'] = [ "body": "[body]", "type": "test", "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -2126,7 +2388,25 @@ exports['src/cypress/runner mocha events simple three tests #1'] = [ "type": "hook", "duration": "match.number", "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r4", + "order": 2, + "title": "test 2", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -2141,7 +2421,43 @@ exports['src/cypress/runner mocha events simple three tests #1'] = [ "type": "hook", "duration": "match.number", "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r4", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r4", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -2178,7 +2494,10 @@ exports['src/cypress/runner mocha events simple three tests #1'] = [ ] }, "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 0 } ], [ @@ -2215,37 +2534,10 @@ exports['src/cypress/runner mocha events simple three tests #1'] = [ ] }, "file": null, - "invocationDetails": "{Object 8}" - } - ], - [ - "mocha", - "hook", - { - "id": "r4", - "title": "\"after each\" hook", - "hookName": "after each", - "hookId": "h4", - "body": "[body]", - "type": "hook", - "duration": "match.number", - "file": null, - "invocationDetails": "{Object 8}" - } - ], - [ - "mocha", - "hook end", - { - "id": "r4", - "title": "\"after each\" hook", - "hookName": "after each", - "hookId": "h4", - "body": "[body]", - "type": "hook", - "duration": "match.number", - "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 0 } ], [ @@ -2283,7 +2575,10 @@ exports['src/cypress/runner mocha events simple three tests #1'] = [ ] }, "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 0 } ], [ @@ -2296,7 +2591,9 @@ exports['src/cypress/runner mocha events simple three tests #1'] = [ "body": "[body]", "type": "test", "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -2311,7 +2608,25 @@ exports['src/cypress/runner mocha events simple three tests #1'] = [ "type": "hook", "duration": "match.number", "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "test:before:run", + { + "id": "r5", + "order": 3, + "title": "test 3", + "body": "[body]", + "type": "test", + "wallClockStartedAt": "match.date", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -2326,7 +2641,76 @@ exports['src/cypress/runner mocha events simple three tests #1'] = [ "type": "hook", "duration": "match.number", "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r5", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r5", + "title": "\"after each\" hook", + "hookName": "after each", + "hookId": "h4", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook", + { + "id": "r5", + "title": "\"after all\" hook", + "hookName": "after all", + "hookId": "h3", + "body": "[body]", + "type": "hook", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 + } + ], + [ + "mocha", + "hook end", + { + "id": "r5", + "title": "\"after all\" hook", + "hookName": "after all", + "hookId": "h3", + "body": "[body]", + "type": "hook", + "duration": "match.number", + "file": null, + "invocationDetails": "{Object 8}", + "currentRetry": 0, + "retries": -1 } ], [ @@ -2370,7 +2754,10 @@ exports['src/cypress/runner mocha events simple three tests #1'] = [ ] }, "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 0 } ], [ @@ -2414,66 +2801,10 @@ exports['src/cypress/runner mocha events simple three tests #1'] = [ ] }, "file": null, - "invocationDetails": "{Object 8}" - } - ], - [ - "mocha", - "hook", - { - "id": "r5", - "title": "\"after each\" hook", - "hookName": "after each", - "hookId": "h4", - "body": "[body]", - "type": "hook", - "duration": "match.number", - "file": null, - "invocationDetails": "{Object 8}" - } - ], - [ - "mocha", - "hook end", - { - "id": "r5", - "title": "\"after each\" hook", - "hookName": "after each", - "hookId": "h4", - "body": "[body]", - "type": "hook", - "duration": "match.number", - "file": null, - "invocationDetails": "{Object 8}" - } - ], - [ - "mocha", - "hook", - { - "id": "r5", - "title": "\"after all\" hook", - "hookName": "after all", - "hookId": "h3", - "body": "[body]", - "type": "hook", - "file": null, - "invocationDetails": "{Object 8}" - } - ], - [ - "mocha", - "hook end", - { - "id": "r5", - "title": "\"after all\" hook", - "hookName": "after all", - "hookId": "h3", - "body": "[body]", - "type": "hook", - "duration": "match.number", - "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 0 } ], [ @@ -2484,7 +2815,8 @@ exports['src/cypress/runner mocha events simple three tests #1'] = [ "title": "suite 1", "root": false, "type": "suite", - "file": null + "file": null, + "retries": -1 } ], [ @@ -2529,7 +2861,10 @@ exports['src/cypress/runner mocha events simple three tests #1'] = [ ] }, "file": null, - "invocationDetails": "{Object 8}" + "invocationDetails": "{Object 8}", + "final": true, + "currentRetry": 0, + "retries": 0 } ], [ @@ -2540,7 +2875,8 @@ exports['src/cypress/runner mocha events simple three tests #1'] = [ "title": "", "root": true, "type": "suite", - "file": "relative/path/to/spec.js" + "file": "relative/path/to/spec.js", + "retries": -1 } ], [ diff --git a/packages/runner/cypress/integration/reporter.errors.spec.js b/packages/runner/cypress/integration/reporter.errors.spec.js index 29d621de9cb8..236760cdbaae 100644 --- a/packages/runner/cypress/integration/reporter.errors.spec.js +++ b/packages/runner/cypress/integration/reporter.errors.spec.js @@ -1,7 +1,7 @@ const helpers = require('../support/helpers') const _ = Cypress._ -const { runIsolatedCypress } = helpers.createCypress() +const { runIsolatedCypress } = helpers.createCypress({ config: { isTextTerminal: true, retries: 0 } }) export const verifyFailure = (options) => { const { diff --git a/packages/runner/cypress/integration/retries.mochaEvents.spec.js b/packages/runner/cypress/integration/retries.mochaEvents.spec.js new file mode 100644 index 000000000000..252185cc327b --- /dev/null +++ b/packages/runner/cypress/integration/retries.mochaEvents.spec.js @@ -0,0 +1,338 @@ +const helpers = require('../support/helpers') + +const { shouldHaveTestResults, getRunState, cleanseRunStateMap } = helpers +const { runIsolatedCypress, snapshotMochaEvents, getAutCypress } = helpers.createCypress({ config: { retries: 2, isTextTerminal: true } }) +const { sinon } = Cypress +const match = Cypress.sinon.match + +const threeTestsWithRetry = { + suites: { + 'suite 1': { + hooks: ['before', 'beforeEach', 'afterEach', 'after'], + tests: [ + 'test 1', + { name: 'test 2', fail: 2 }, + 'test 3', + ], + }, + }, +} + +describe('src/cypress/runner retries mochaEvents', () => { + // NOTE: for test-retries + it('can set retry config', () => { + runIsolatedCypress({}, { config: { retries: 1 } }) + .then(({ autCypress }) => { + expect(autCypress.config()).to.has.property('retries', 1) + }) + }) + + it('simple retry', () => { + runIsolatedCypress({ + suites: { + 'suite 1': { + tests: [ + { name: 'test 1', + fail: 1, + }, + ], + }, + }, + }, { config: { retries: 1 } }) + .then(snapshotMochaEvents) + }) + + it('test retry with hooks', () => { + runIsolatedCypress({ + suites: { + 'suite 1': { + hooks: ['before', 'beforeEach', 'afterEach', 'after'], + tests: [{ name: 'test 1', fail: 1 }], + }, + }, + }, { config: { retries: 1 } }) + .then(snapshotMochaEvents) + }) + + it('test retry with [only]', () => { + runIsolatedCypress({ + suites: { + 'suite 1': { + hooks: ['before', 'beforeEach', 'afterEach', 'after'], + tests: [ + { name: 'test 1' }, + { name: 'test 2', fail: 1, only: true }, + { name: 'test 3' }, + ], + }, + }, + }, { config: { retries: 1 } }) + .then(snapshotMochaEvents) + }) + + it('can retry from [beforeEach]', () => { + runIsolatedCypress({ + suites: { + 'suite 1': { + hooks: [ + 'before', + 'beforeEach', + { type: 'beforeEach', fail: 1 }, + 'beforeEach', + 'afterEach', + 'after', + ], + tests: [{ name: 'test 1' }], + }, + }, + }, { config: { retries: 1 } }) + .then(snapshotMochaEvents) + }) + + it('can retry from [afterEach]', () => { + runIsolatedCypress({ + hooks: [{ type: 'afterEach', fail: 1 }], + suites: { + 'suite 1': { + hooks: [ + 'before', + 'beforeEach', + 'beforeEach', + 'afterEach', + 'after', + ], + tests: [{ name: 'test 1' }, 'test 2', 'test 3'], + }, + 'suite 2': { + hooks: [{ type: 'afterEach', fail: 2 }], + tests: ['test 1'], + }, + 'suite 3': { + tests: ['test 1'], + }, + }, + }, { config: { retries: 2, isTextTerminal: true } }) + + .then(snapshotMochaEvents) + }) + + it('cant retry from [before]', () => { + runIsolatedCypress({ + suites: { + 'suite 1': { + hooks: [ + { type: 'before', fail: 1 }, + 'beforeEach', + 'beforeEach', + 'afterEach', + 'afterEach', + 'after', + ], + tests: [{ name: 'test 1' }], + }, + }, + }, { config: { retries: 1 } }) + .then(snapshotMochaEvents) + }) + + it('cant retry from [after]', () => { + runIsolatedCypress({ + suites: { + 'suite 1': { + hooks: [ + 'before', + 'beforeEach', + 'beforeEach', + 'afterEach', + 'afterEach', + { type: 'after', fail: 1 }, + ], + tests: [{ name: 'test 1' }], + }, + }, + }, { config: { retries: 1 } }) + .then(snapshotMochaEvents) + }) + + it('three tests with retry', () => { + runIsolatedCypress(threeTestsWithRetry, { + config: { + retries: 2, + }, + }) + .then(snapshotMochaEvents) + }) + + describe('screenshots', () => { + it('retry screenshot in test body', () => { + let onAfterScreenshot + + runIsolatedCypress({ + suites: { + 'suite 1': { + tests: [ + { + name: 'test 1', + fn: () => { + cy.screenshot() + cy.then(() => assert(false)) + }, + eval: true, + }, + ], + }, + }, + }, { config: { retries: 1 }, + onBeforeRun ({ autCypress }) { + autCypress.Screenshot.onAfterScreenshot = cy.stub() + onAfterScreenshot = cy.stub() + autCypress.on('after:screenshot', onAfterScreenshot) + }, + }) + .then(({ autCypress }) => { + expect(autCypress.automation.withArgs('take:screenshot')).callCount(4) + expect(autCypress.automation.withArgs('take:screenshot').args).matchDeep([ + { 1: { testAttemptIndex: 0 } }, + { 1: { testAttemptIndex: 0 } }, + { 1: { testAttemptIndex: 1 } }, + { 1: { testAttemptIndex: 1 } }, + ]) + + expect(autCypress.automation.withArgs('take:screenshot').args[0]).matchSnapshot({ startTime: match.string, testAttemptIndex: match(0) }) + expect(onAfterScreenshot.args[0][0]).to.matchSnapshot({ testAttemptIndex: match(0) }) + expect(onAfterScreenshot.args[2][0]).to.matchDeep({ testAttemptIndex: 1 }) + }) + }) + + it('retry screenshot in hook', () => { + let onAfterScreenshot + + runIsolatedCypress({ + suites: { + 'suite 1': { + hooks: [ + { + type: 'beforeEach', + fn: () => { + cy.screenshot() + cy.then(() => assert(false)) + }, + eval: true, + }, + ], + tests: [ + { + name: 'test 1', + }, + ], + }, + }, + }, { config: { retries: 1 }, + onBeforeRun ({ autCypress }) { + autCypress.Screenshot.onAfterScreenshot = cy.stub() + onAfterScreenshot = cy.stub() + autCypress.on('after:screenshot', onAfterScreenshot) + }, + }) + .then(({ autCypress }) => { + expect(autCypress.automation.withArgs('take:screenshot')).callCount(4) + expect(autCypress.automation.withArgs('take:screenshot').args).matchDeep([ + { 1: { testAttemptIndex: 0 } }, + { 1: { testAttemptIndex: 0 } }, + { 1: { testAttemptIndex: 1 } }, + { 1: { testAttemptIndex: 1 } }, + ]) + + expect(onAfterScreenshot.args[0][0]).matchDeep({ testAttemptIndex: 0 }) + expect(onAfterScreenshot.args[2][0]).matchDeep({ testAttemptIndex: 1 }) + }) + }) + }) + + describe('save/reload state', () => { + const serializeState = () => { + return getRunState(getAutCypress()) + } + + const loadStateFromSnapshot = (cypressConfig, name) => { + cy.task('getSnapshot', { + file: Cypress.spec.name, + exactSpecName: name, + }) + .then((state) => { + cypressConfig[1].state = state + }) + } + + // NOTE: for test-retries + describe('retries rehydrate spec state after navigation', () => { + let realState + + let runCount = 0 + const failThenSerialize = () => { + if (!runCount++) { + assert(false, 'stub 3 fail') + } + + assert(true, 'stub 3 pass') + + return realState = serializeState() + } + + let runCount2 = 0 + const failOnce = () => { + if (!runCount2++) { + assert(false, 'stub 2 fail') + } + + assert(true, 'stub 2 pass') + } + + const stub1 = sinon.stub() + const stub2 = sinon.stub().callsFake(failOnce) + const stub3 = sinon.stub().callsFake(failThenSerialize) + + let cypressConfig = [ + { + suites: { + 'suite 1': { + hooks: [ + 'before', + 'beforeEach', + 'afterEach', + 'after', + ], + tests: [{ name: 'test 1', fn: stub1 }], + }, + 'suite 2': { + tests: [ + { name: 'test 1', fn: stub2 }, + { name: 'test 2', fn: stub3 }, + 'test 3', + ], + }, + }, + }, { config: { retries: 1 } }, + ] + + it('1/2', () => { + runIsolatedCypress(...cypressConfig) + .then(shouldHaveTestResults(4, 0)) + .then(() => { + expect(realState).to.matchSnapshot(cleanseRunStateMap, 'serialize state - retries') + }) + }) + + it('2/2', () => { + loadStateFromSnapshot(cypressConfig, 'serialize state - retries') + runIsolatedCypress(...cypressConfig) + .then(shouldHaveTestResults(4, 0)) + .then(() => { + expect(stub1).to.calledOnce + expect(stub2).to.calledTwice + expect(stub3).calledThrice + }) + }) + }) + }) +}) diff --git a/packages/runner/cypress/integration/retries.ui.spec.js b/packages/runner/cypress/integration/retries.ui.spec.js new file mode 100644 index 000000000000..e373a5a972f2 --- /dev/null +++ b/packages/runner/cypress/integration/retries.ui.spec.js @@ -0,0 +1,491 @@ +const helpers = require('../support/helpers') + +const { shouldHaveTestResults, containText } = helpers +const { runIsolatedCypress } = helpers.createCypress({ config: { retries: 2 } }) + +const getAttemptTag = (sel) => { + return cy.get(`.test.runnable:contains(${sel}) .attempt-tag`) +} + +const shouldBeOpen = ($el) => cy.wrap($el).parentsUntil('.collapsible').last().parent().should('have.class', 'is-open') + +const attemptTag = (sel) => `.attempt-tag:contains(Attempt ${sel})` +const cyReject = (fn) => { + return () => { + try { + fn() + } catch (e) { + cy.state('reject')(e) + } + } +} + +describe('runner/cypress retries.ui.spec', { viewportWidth: 600, viewportHeight: 900 }, () => { + it('collapses tests that retry and pass', () => { + runIsolatedCypress({ + suites: { + 'suite 1': { + tests: [ + { name: 'test pass', fail: 0 }, + { name: 'test pass on 2nd attempt', fail: 1 }, + ], + }, + }, + }) + .then(shouldHaveTestResults(2, 0)) + + cy.percySnapshot() + }) + + it('collapses prev attempts and keeps final one open on failure', () => { + runIsolatedCypress({ + suites: { + 'suite 1': { + tests: [ + { name: 'test 1', + fail: true, + }, + { name: 'test 2', + + }, + ], + }, + }, + }, { config: { retries: 2 } }) + .then(shouldHaveTestResults(1, 1)) + + cy.percySnapshot() + }) + + it('can toggle failed prev attempt open and log its error', () => { + runIsolatedCypress({ + suites: { + 'suite 1': { + tests: [ + { name: 'test 1', fail: 1 }, + { name: 'test 2', fail: 2 }, + { name: 'test 3', fail: 1 }, + ], + }, + }, + }, { config: { retries: 1 } }) + .then(shouldHaveTestResults(2, 1)) + .then(() => { + cy.contains('Attempt 1') + .click() + .closest('.attempt-item') + .find('.runnable-err-print') + .click() + + cy.get('@console_error').should('be.calledWithMatch', 'AssertionError: test 2') + }) + + cy.percySnapshot() + }) + + it('opens attempt on each attempt failure for the screenshot, and closes after test passes', { retries: 2 }, () => { + let stub + + runIsolatedCypress( + { + suites: { + 's1': { + tests: [ + 't1', + { + name: 't2', + fail: 3, + }, + 't3', + ], + }, + }, + }, { config: { retries: 3, isTextTerminal: true }, + onBeforeRun ({ autCypress }) { + let attempt = 0 + + stub = cy.stub().callsFake(cyReject(() => { + attempt++ + + const $attemptCollapsible = cy.$$(attemptTag(attempt)) + .parentsUntil('.collapsible').last().parent() + + expect($attemptCollapsible).have.class('is-open') + })) + + autCypress.Screenshot.onAfterScreenshot = stub + }, + }, + ).then(() => { + expect(stub).callCount(3) + cy.get('.test.runnable:contains(t2)').then(($el) => { + expect($el).not.class('is-open') + }) + }) + }) + + it('includes routes, spies, hooks, and commands in attempt', () => { + runIsolatedCypress({ + suites: { + 's1': { + hooks: [{ type: 'beforeEach', fail: 1, agents: true }], + tests: [{ name: 't1', fail: 1, agents: true }], + }, + }, + }) + .then(shouldHaveTestResults(1, 0)) + .then(() => { + cy.get(attemptTag(1)).click().parentsUntil('.collapsible').last().parent().within(() => { + cy.get('.instruments-container').should('contain', 'Spies / Stubs (1)') + cy.get('.instruments-container').should('contain', 'Routes (1)') + cy.get('.runnable-err').should('contain', 'AssertionError') + }) + + cy.get(attemptTag(2)).click().parentsUntil('.collapsible').last().parent().within(() => { + cy.get('.instruments-container').should('contain', 'Spies / Stubs (2)') + cy.get('.instruments-container').should('contain', 'Routes (2)') + cy.get('.runnable-err').should('contain', 'AssertionError') + }) + + cy.get(attemptTag(3)).parentsUntil('.collapsible').last().parent().within(() => { + cy.get('.instruments-container').should('contain', 'Spies / Stubs (2)') + cy.get('.instruments-container').should('contain', 'Routes (2)') + cy.get('.runnable-err').should('not.contain', 'AssertionError') + }) + }) + + cy.percySnapshot() + }) + + describe('only', () => { + it('test retry with [only]', () => { + runIsolatedCypress({ + suites: { + 'suite 1': { + hooks: ['before', 'beforeEach', 'afterEach', 'after'], + tests: [ + { name: 'test 1' }, + { name: 'test 2', fail: 1, only: true }, + { name: 'test 3' }, + ], + }, + }, + }, { config: { retries: 1 } }) + .then(shouldHaveTestResults(1, 0)) + .then(() => { + cy.contains('test 2') + cy.contains('test 1').should('not.exist') + cy.contains('test 3').should('not.exist') + }) + + cy.percySnapshot() + }) + }) + + describe('beforeAll', () => { + // TODO: make beforeAll hooks retry + it('tests do not retry when beforeAll fails', () => { + runIsolatedCypress({ + suites: { + 'suite 1': { + hooks: [ + { type: 'before', fail: 1 }, + 'beforeEach', + 'beforeEach', + 'afterEach', + 'afterEach', + 'after', + ], + tests: ['test 1'], + }, + }, + }, { config: { retries: 1 } }) + .then(shouldHaveTestResults(0, 1)) + .then(() => { + cy.contains('Although you have test retries') + }) + + cy.percySnapshot() + }) + + // TODO: future versions should run all hooks associated with test on retry + it('before all hooks are not run on the second attempt when fails outside of beforeAll', () => { + runIsolatedCypress({ + suites: { + 'suite 1': { + hooks: ['before', 'beforeEach', 'afterEach', 'after'], + tests: [{ name: 'test 1', fail: 1 }], + }, + }, + }, { config: { retries: 1 } }) + .then(shouldHaveTestResults(1, 0)) + .then(() => { + cy.contains('test') + cy.contains('after all') + cy.contains('before all').should('not.exist') + }) + + cy.percySnapshot() + }) + }) + + describe('beforeEach', () => { + it('beforeEach hooks retry on failure, but only run same-level afterEach hooks', () => { + runIsolatedCypress({ + hooks: [{ type: 'beforeEach', fail: 1 }], + suites: { + 'suite 1': { + hooks: [ + 'before', + 'beforeEach', + { type: 'beforeEach', fail: 1 }, + 'beforeEach', + 'afterEach', + 'after', + ], + tests: [{ name: 'test 1' }], + }, + }, + }, { config: { retries: 2 } }) + .then(shouldHaveTestResults(1, 0)) + .then(() => { + cy.contains('Attempt 1').click() + cy.get('.attempt-1 .hook-item .collapsible:contains(before each)').find('.command-state-failed') + cy.get('.attempt-1 .hook-item .collapsible:contains(before each (2))').should('not.exist') + cy.get('.attempt-1 .hook-item .collapsible:contains(test body)').should('not.exist') + cy.get('.attempt-1 .hook-item .collapsible:contains(after each)').should('not.exist') + cy.get('.attempt-1 .hook-item .collapsible:contains(after all)').should('not.exist') + + cy.contains('Attempt 2').click() + cy.get('.attempt-2 .hook-item .collapsible:contains(before each)') + cy.get('.attempt-2 .hook-item .collapsible:contains(before each (2))') + cy.get('.attempt-2 .hook-item .collapsible:contains(before each (3))').find('.command-state-failed') + cy.get('.attempt-2 .hook-item .collapsible:contains(test body)').should('not.exist') + cy.get('.attempt-2 .hook-item .collapsible:contains(after each)') + cy.get('.attempt-2 .hook-item .collapsible:contains(after all)').should('not.exist') + + cy.get('.attempt-3 .hook-item .collapsible:contains(before each)') + cy.get('.attempt-3 .hook-item .collapsible:contains(before each (2))') + cy.get('.attempt-3 .hook-item .collapsible:contains(before each (3))') + cy.get('.attempt-3 .hook-item .collapsible:contains(before each (4))') + cy.get('.attempt-3 .hook-item .collapsible:contains(test body)') + cy.get('.attempt-3 .hook-item .collapsible:contains(after each)') + cy.get('.attempt-3 .hook-item .collapsible:contains(after all)') + }) + + cy.percySnapshot() + }) + + it('beforeEach retried tests skip remaining tests in suite', () => { + runIsolatedCypress({ suites: { + 'beforeEach hooks': { + hooks: [{ type: 'beforeEach', fail: true }], + tests: ['fails in beforeEach', 'skips this'], + }, + + } }, { config: { retries: 1 } }) + .then(shouldHaveTestResults(0, 1, 0)) + + cy.percySnapshot() + }) + }) + + describe('afterEach', () => { + it('afterEach hooks retry on failure, but only run higher-level afterEach hooks', () => { + runIsolatedCypress({ + hooks: [{ type: 'afterEach', fail: 2 }], + suites: { + 's1': { + hooks: [{ type: 'afterEach', fail: 1 }, 'afterEach', 'after'], + tests: ['t1'], + }, + + }, + }, { config: { retries: 2 } }) + .then(shouldHaveTestResults(1, 0)) + .then(() => { + cy.contains('Attempt 1') + .click() + .then(shouldBeOpen) + + cy.get('.attempt-1 .hook-item .collapsible:contains(after each (1))').find('.command-state-failed') + cy.get('.attempt-1 .hook-item .collapsible:contains(after each (2))') + cy.get('.attempt-1 .hook-item .collapsible:contains(after each (3))').should('not.exist') + cy.get('.attempt-1 .hook-item .collapsible:contains(after all)').should('not.exist') + + cy.contains('Attempt 2').click() + .then(shouldBeOpen) + + cy.get('.attempt-2 .hook-item .collapsible:contains(after each (1))') + cy.get('.attempt-2 .hook-item .collapsible:contains(after each (2))') + cy.get('.attempt-2 .hook-item .collapsible:contains(after each (3))').find('.command-state-failed') + cy.get('.attempt-2 .hook-item .collapsible:contains(after all)').should('not.exist') + + cy.get('.attempt-tag').should('have.length', 3) + cy.get('.attempt-2 .hook-item .collapsible:contains(after each (1))') + cy.get('.attempt-2 .hook-item .collapsible:contains(after each (2))') + cy.get('.attempt-2 .hook-item .collapsible:contains(after each (3))') + cy.get('.attempt-3 .hook-item .collapsible:contains(after all)') + }) + + cy.percySnapshot() + }) + + it('afterEach retried tests skip remaining tests in suite', () => { + runIsolatedCypress({ suites: { + 'afterEach hooks': { + hooks: [{ type: 'afterEach', fail: true }], + tests: ['fails in afterEach', 'skips this'], + }, + + } }, { config: { retries: 1 } }) + .then(shouldHaveTestResults(0, 1, 0)) + + cy.percySnapshot() + }) + }) + + describe('afterAll', () => { + it('only run afterAll hook on last attempt', () => { + runIsolatedCypress({ + suites: { + 'suite 1': { + hooks: [ + 'before', + 'beforeEach', + 'afterEach', + 'after', + ], + tests: [ + { name: 'test 1' }, + { name: 'test 2' }, + { name: 'test 3', fail: 1 }, + ], + }, + }, + }, { config: { retries: 1 } }) + .then(shouldHaveTestResults(3, 0)) + .then(() => { + cy.contains('test 3').click() + getAttemptTag('test 3').first().click() + cy.contains('.attempt-1', 'after all').should('not.exist') + cy.contains('.attempt-2', 'after all') + }) + }) + + it('tests do not retry when afterAll fails', () => { + runIsolatedCypress({ + suites: { + 'suite 1': { + hooks: [ + 'before', + 'beforeEach', + 'beforeEach', + 'afterEach', + 'afterEach', + { type: 'after', fail: 1 }, + ], + tests: [{ name: 'test 1' }], + }, + }, + }, { config: { retries: 1 } }) + .then(shouldHaveTestResults(0, 1)) + .then(() => { + cy.contains('Although you have test retries') + cy.get('.runnable-err-print').click() + cy.get('@console_error').its('lastCall').should('be.calledWithMatch', 'Error') + }) + + cy.percySnapshot() + }) + }) + + describe('can configure retries', () => { + const haveCorrectError = ($el) => cy.wrap($el).last().parentsUntil('.collapsible').last().parent().find('.runnable-err').should('contain', 'Unspecified AssertionError') + + it('via config value', () => { + runIsolatedCypress({ + suites: { + 'suite 1': () => { + it('[no retry]', { retries: 0 }, () => assert(false)) + it('[1 retry]', { retries: 1 }, () => assert(false)) + it('[2 retries]', { retries: 2 }, () => assert(false)) + it('[open mode, no retry]', { retries: { runMode: 2, openMode: 0 } }, () => assert(false)) + it('[run mode, retry]', { retries: { runMode: 1, openMode: 0 }, isInteractive: false }, () => assert(false)) + it('[open mode, 2 retries]', { isInteractive: true }, () => assert(false)) + describe('suite 2', { retries: 1 }, () => { + it('[set retries on suite]', () => assert(false)) + }) + }, + }, + }) + .then(shouldHaveTestResults(0, 7)) + .then(() => { + getAttemptTag('[no retry]').should('have.length', 1).then(haveCorrectError) + getAttemptTag('[1 retry]').should('have.length', 2).then(haveCorrectError) + getAttemptTag('[2 retries]').should('have.length', 3).then(haveCorrectError) + getAttemptTag('[open mode, no retry]').should('have.length', 1).then(haveCorrectError) + getAttemptTag('[run mode, retry]').should('have.length', 2).then(haveCorrectError) + getAttemptTag('[open mode, 2 retries]').should('have.length', 3).then(haveCorrectError) + getAttemptTag('[set retries on suite]').should('have.length', 2).then(haveCorrectError) + }) + }) + + it('throws when set via this.retries in test', () => { + runIsolatedCypress({ + suites: { + 'suite 1' () { + it('tries to set mocha retries', function () { + this.retries(null) + }) + }, + }, + }) + .then(shouldHaveTestResults(0, 1)) + .then(() => { + cy.get('.runnable-err').should(containText(`it('tries to set mocha retries', { retries: 2 }, () => `)) + }) + + cy.percySnapshot() + }) + + it('throws when set via this.retries in hook', () => { + runIsolatedCypress({ + suites: { + 'suite 1' () { + beforeEach(function () { + this.retries(0) + }) + + it('foo', () => {}) + }, + }, + }) + .then(shouldHaveTestResults(0, 1)) + .then(() => { + cy.get('.runnable-err').should(containText(`describe('suite 1', { retries: 0 }, () => `)) + }) + + cy.percySnapshot() + }) + + it('throws when set via this.retries in suite', () => { + runIsolatedCypress({ + suites: { + 'suite 1' () { + this.retries(3) + it('test 1', () => { + }) + }, + }, + }) + .then(shouldHaveTestResults(0, 1)) + .then(() => { + cy.get('.runnable-err') + .should(containText(`describe('suite 1', { retries: 3 }, () => `)) + }) + + cy.percySnapshot() + }) + }) +}) diff --git a/packages/runner/cypress/integration/runner.mochaEvents.spec.js b/packages/runner/cypress/integration/runner.mochaEvents.spec.js index a55892fba141..6987ecc7cbd4 100644 --- a/packages/runner/cypress/integration/runner.mochaEvents.spec.js +++ b/packages/runner/cypress/integration/runner.mochaEvents.spec.js @@ -3,7 +3,7 @@ const sinon = require('sinon') const helpers = require('../support/helpers') const { cleanseRunStateMap, shouldHaveTestResults, getRunState } = helpers -const { runIsolatedCypress, snapshotMochaEvents, onInitialized, getAutCypress } = helpers.createCypress() +const { runIsolatedCypress, snapshotMochaEvents, getAutCypress } = helpers.createCypress({ config: { isTextTerminal: true, retries: 0 } }) const simpleSingleTest = { suites: { 'suite 1': { tests: [{ name: 'test 1' }] } }, @@ -239,17 +239,24 @@ describe('src/cypress/runner', () => { }) }) - describe('screenshots', () => { - let onAfterScreenshotListener - - beforeEach(() => { - onInitialized((autCypress) => { - autCypress.Screenshot.onAfterScreenshot = cy.stub() - onAfterScreenshotListener = cy.stub() - autCypress.on('after:screenshot', onAfterScreenshotListener) - }) + it('buffer mocha pass event when fail in afterEach hooks', () => { + runIsolatedCypress({ + suites: { + 'suite 1': { + suites: { + 'suite 1-1': { + hooks: [{ type: 'afterEach', fail: true }], + tests: ['test 1'], + }, + }, + }, + }, + }).then(({ mochaStubs }) => { + expect(_.find(mochaStubs.args, { 1: 'pass' })).not.exist }) + }) + describe('screenshots', () => { it('screenshot after failed test', () => { runIsolatedCypress({ suites: { diff --git a/packages/runner/cypress/integration/runner.ui.spec.js b/packages/runner/cypress/integration/runner.ui.spec.js index ba63c81f9f1f..66bd90d01a95 100644 --- a/packages/runner/cypress/integration/runner.ui.spec.js +++ b/packages/runner/cypress/integration/runner.ui.spec.js @@ -147,6 +147,31 @@ describe('src/cypress/runner', () => { describe('hook failures', () => { describe('test failures w/ hooks', () => { + it('test [only]', () => { + runIsolatedCypress({ + suites: { + 'suite 1': { + hooks: ['before', 'beforeEach', 'afterEach', 'after'], + tests: [ + { name: 'test 1' }, + { name: 'test 2', only: true }, + { name: 'test 3' }, + ], + }, + }, + }).then(shouldHaveTestResults(1, 0)) + }) + + it('test [pending]', () => { + runIsolatedCypress(() => { + before(() => {}) + it('t1') + it('t2') + it('t3') + after(() => {}) + }).then(shouldHaveTestResults(0, 0, 3)) + }) + it('fail with [before]', () => { runIsolatedCypress({ suites: { @@ -263,5 +288,31 @@ describe('src/cypress/runner', () => { }) .then(shouldHaveTestResults(0, 1)) }) + + it('scrolls each command into view', () => { + // HACK to assert on the dom DURING the runIsolatedCypress run + // we expect the last command item to be scrolled into view before + // the test ends + cy.now('get', '.command-number:contains(25)') + .then(($el) => { + return new Promise((resolve) => { + requestAnimationFrame(() => { + expect($el).visible + resolve() + }) + }) + }) + .catch((e) => cy.state('reject')(e)) + + runIsolatedCypress(() => { + describe('s1', () => { + // eslint-disable-next-line + it('t1', (done) => { + cy.timeout(10) + Cypress._.times(25, () => expect(true).ok) + }) + }) + }) + }) }) }) diff --git a/packages/runner/cypress/plugins/index.js b/packages/runner/cypress/plugins/index.js index e5158967e1ca..4e3fd18039e6 100644 --- a/packages/runner/cypress/plugins/index.js +++ b/packages/runner/cypress/plugins/index.js @@ -1,11 +1,14 @@ // static file server that serves fixtures needed for testing require('@packages/driver/cypress/plugins/server') const { getSnapshot, saveSnapshot } = require('./snapshot/snapshotPlugin') +const percyHealthCheck = require('@percy/cypress/task') /** * @type {Cypress.PluginConfig} */ module.exports = (on) => { + on('task', percyHealthCheck) + on('task', { getSnapshot, saveSnapshot, diff --git a/packages/runner/cypress/plugins/snapshot/snapshotCommand.js b/packages/runner/cypress/plugins/snapshot/snapshotCommand.js index 4e021a871bb7..1a460ecb2cef 100644 --- a/packages/runner/cypress/plugins/snapshot/snapshotCommand.js +++ b/packages/runner/cypress/plugins/snapshot/snapshotCommand.js @@ -21,7 +21,7 @@ function throwErr (e, message, exp, ctx) { } } -function getMatchDeepMessage ({ act, exp }) { +function getMatchDeepMessage (act, exp) { return `Expected **${chai.util.objDisplay(act)}** to deep match: **${chai.util.objDisplay(exp)}**` } diff --git a/packages/runner/cypress/support/helpers.js b/packages/runner/cypress/support/helpers.js index 5ecabd962046..dba886e316fe 100644 --- a/packages/runner/cypress/support/helpers.js +++ b/packages/runner/cypress/support/helpers.js @@ -37,6 +37,18 @@ const mochaEventCleanseMap = { end: match.date, } +const cleanseRunStateMap = { + ...eventCleanseMap, + 'err.stack': '[err stack]', + wallClockStartedAt: new Date(0), + wallClockDuration: 1, + fnDuration: 1, + afterFnDuration: 1, + lifecycle: 1, + duration: 1, + startTime: new Date(0), +} + const spyOn = (obj, prop, fn) => { const _fn = obj[prop] @@ -49,7 +61,7 @@ const spyOn = (obj, prop, fn) => { } } -function createCypress () { +function createCypress (defaultOptions = {}) { /** * @type {sinon.SinonStub} */ @@ -84,19 +96,13 @@ function createCypress () { window.Cypress = backupCypress }) - let onInitializedListeners = [] - - const onInitialized = function (fn) { - onInitializedListeners.push(fn) - } - /** * Spawns an isolated Cypress runner as the AUT, with provided spec/fixture and optional state/config * @param {string | ()=>void | {[key:string]: any}} mochaTestsOrFile * @param {{state?: any, config?: any}} opts */ const runIsolatedCypress = (mochaTestsOrFile, opts = {}) => { - _.defaultsDeep(opts, { + _.defaultsDeep(opts, defaultOptions, { state: {}, config: { video: false }, onBeforeRun () {}, @@ -106,9 +112,9 @@ function createCypress () { .then({ timeout: 60000 }, (win) => { win.runnerWs.destroy() - allStubs = cy.stub().snapshot(enableStubSnapshots) - mochaStubs = cy.stub().snapshot(enableStubSnapshots) - setRunnablesStub = cy.stub().snapshot(enableStubSnapshots) + allStubs = cy.stub().snapshot(enableStubSnapshots).log(false) + mochaStubs = cy.stub().snapshot(enableStubSnapshots).log(false) + setRunnablesStub = cy.stub().snapshot(enableStubSnapshots).log(false) return new Promise((resolve) => { const runIsolatedCypress = () => { @@ -118,7 +124,7 @@ function createCypress () { const emitMap = autCypress.emitMap const emitThen = autCypress.emitThen - cy.stub(autCypress, 'automation').snapshot(enableStubSnapshots) + cy.stub(autCypress, 'automation').log(false).snapshot(enableStubSnapshots) .callThrough() .withArgs('clear:cookies') .resolves({ @@ -177,7 +183,9 @@ function createCypress () { spyOn(autCypress.mocha.getRunner(), 'fail', (...args) => { Cypress.log({ - name: 'Runner Fail', + name: 'Runner (fail event)', + ended: true, + event: true, message: `${args[1]}`, state: 'failed', consoleProps: () => { @@ -191,28 +199,26 @@ function createCypress () { // TODO: clean this up, sinon doesn't like wrapping things multiple times // and this catches that error try { - cy.spy(cy.state('window').console, 'log').as('console_log') - cy.spy(cy.state('window').console, 'error').as('console_error') + cy.spy(cy.state('window').console, 'log').as('console_log').log(false) + cy.spy(cy.state('window').console, 'error').as('console_error').log(false) } catch (_e) { // console was already wrapped, noop } - onInitializedListeners.forEach((fn) => fn(autCypress)) - onInitializedListeners = [] autCypress.run((failed) => { resolve({ failed, mochaStubs, autCypress, win }) }) } - cy.spy(win.eventManager.reporterBus, 'emit').snapshot(enableStubSnapshots).as('reporterBus') - cy.spy(win.eventManager.localBus, 'emit').snapshot(enableStubSnapshots).as('localBus') + cy.spy(win.eventManager.reporterBus, 'emit').snapshot(enableStubSnapshots).log(false).as('reporterBus') + cy.spy(win.eventManager.localBus, 'emit').snapshot(enableStubSnapshots).log(false).as('localBus') - cy.stub(win.runnerWs, 'emit').snapshot(enableStubSnapshots) + cy.stub(win.runnerWs, 'emit').snapshot(enableStubSnapshots).log(false) .withArgs('watch:test:file') .callsFake(() => { autCypress = win.Cypress - cy.stub(autCypress, 'onSpecWindow').snapshot(enableStubSnapshots).callsFake((specWindow) => { + cy.stub(autCypress, 'onSpecWindow').snapshot(enableStubSnapshots).log(false).callsFake((specWindow) => { autCypress.onSpecWindow.restore() opts.onBeforeRun({ specWindow, win, autCypress }) @@ -238,7 +244,7 @@ function createCypress () { specWindow.describe = () => {} }) - cy.stub(autCypress, 'run').snapshot(enableStubSnapshots).callsFake(runIsolatedCypress) + cy.stub(autCypress, 'run').snapshot(enableStubSnapshots).log(false).callsFake(runIsolatedCypress) }) .withArgs('is:automation:client:connected') .yieldsAsync(true) @@ -271,16 +277,17 @@ function createCypress () { .yieldsAsync({ response: {} }) const c = _.extend({}, Cypress.config(), { - isTextTerminal: true, + isTextTerminal: false, spec: { relative: 'relative/path/to/spec.js', absolute: '/absolute/path/to/spec.js', + name: 'empty_spec.js', }, }, opts.config) c.state = {} - cy.stub(win.runnerWs, 'on').snapshot(enableStubSnapshots) + cy.stub(win.runnerWs, 'on').snapshot(enableStubSnapshots).log(false) win.Runner.start(win.document.getElementById('app'), window.btoa(JSON.stringify(c))) }) @@ -290,7 +297,6 @@ function createCypress () { return { runIsolatedCypress, snapshotMochaEvents, - onInitialized, getAutCypress, } } @@ -301,7 +307,7 @@ const createHooks = (win, hooks = []) => { hook = { type: hook } } - let { type, fail, fn } = hook + let { type, fail, fn, agents } = hook if (fn) { if (hook.eval) { @@ -321,24 +327,34 @@ const createHooks = (win, hooks = []) => { if (fail) { const numFailures = fail - return win[type](() => { + return win[type](function () { + const message = `${type} - ${this._runnable.parent.title || 'root'}` + + if (agents) { + registerAgents(win) + } + if (_.isNumber(fail) && fail-- <= 0) { debug(`hook pass after (${numFailures}) failures: ${type}`) - win.assert(true, type) + win.assert(true, message) return } - debug(`hook fail: ${type}`) + if (agents) { + failCypressCommand(win, type) + } else { + debug(`hook fail: ${type}`) - win.assert(false, type) + win.assert(false, message) - throw new Error(`hook failed: ${type}`) + throw new Error(`hook failed: ${type}`) + } }) } - return win[type](() => { - win.assert(true, type) + return win[type](function () { + win.assert(true, `${type} - ${this._runnable.parent.title || 'root'}`) debug(`hook pass: ${type}`) }) }) @@ -350,7 +366,7 @@ const createTests = (win, tests = []) => { test = { name: test } } - let { name, pending, fail, fn, only } = test + let { name, pending, fail, fn, only, agents } = test let it = win.it @@ -379,6 +395,10 @@ const createTests = (win, tests = []) => { if (fail) { return it(name, () => { + if (agents) { + registerAgents(win) + } + if (_.isNumber(fail) && fail-- === 0) { debug(`test pass after retry: ${name}`) win.assert(true, name) @@ -386,10 +406,14 @@ const createTests = (win, tests = []) => { return } - debug(`test fail: ${name}`) - win.assert(false, name) + if (agents) { + failCypressCommand(win, name) + } else { + debug(`test fail: ${name}`) + win.assert(false, name) - throw new Error(`test fail: ${name}`) + throw new Error(`test fail: ${name}`) + } }) } @@ -400,6 +424,16 @@ const createTests = (win, tests = []) => { }) } +const failCypressCommand = (win, name) => win.cy.wrap(name).then(() => win.assert(false, name)) +const registerAgents = (win) => { + const obj = { foo: 'bar' } + + win.cy.stub(obj, 'foo') + win.cy.wrap(obj).should('exist') + win.cy.server() + win.cy.route('https://example.com') +} + const createSuites = (win, suites = {}) => { _.each(suites, (obj, suiteName) => { let fn = () => { @@ -434,27 +468,13 @@ const evalFn = (win, fn) => { } } -const cleanseRunStateMap = { - wallClockStartedAt: new Date(0), - wallClockDuration: 1, - fnDuration: 1, - afterFnDuration: 1, - lifecycle: 1, - duration: 1, - startTime: new Date(0), - 'err.stack': '[err stack]', - sourceMappedStack: match.string, - parsedStack: match.array, - invocationDetails: stringifyShort, -} - -const shouldHaveTestResults = (expPassed, expFailed) => { - return ({ failed }) => { - expect(failed, 'resolve with failure count').eq(failed) +const shouldHaveTestResults = (expPassed, expFailed, expPending) => { + return () => { expPassed = expPassed || '--' expFailed = expFailed || '--' cy.get('header .passed .num').should('have.text', `${expPassed}`) cy.get('header .failed .num').should('have.text', `${expFailed}`) + if (expPending) cy.get('header .pending .num').should('have.text', `${expPending}`) } } diff --git a/packages/runner/cypress/support/index.js b/packages/runner/cypress/support/index.js index e69de29bb2d1..565e08fd47c7 100644 --- a/packages/runner/cypress/support/index.js +++ b/packages/runner/cypress/support/index.js @@ -0,0 +1 @@ +require('@packages/ui-components/cypress/support/customPercyCommand') diff --git a/packages/server/__snapshots__/1_caught_uncaught_hook_errors_spec.js b/packages/server/__snapshots__/1_caught_uncaught_hook_errors_spec.js index 5c66ddd225a4..797be373f43c 100644 --- a/packages/server/__snapshots__/1_caught_uncaught_hook_errors_spec.js +++ b/packages/server/__snapshots__/1_caught_uncaught_hook_errors_spec.js @@ -1,4 +1,4 @@ -exports['e2e caught and uncaught hooks errors failing1 1'] = ` +exports['e2e caught and uncaught hooks errors / failing1'] = ` ==================================================================================================== @@ -110,7 +110,7 @@ Because this error occurred during a \`before all\` hook we are skipping the rem ` -exports['e2e caught and uncaught hooks errors failing2 1'] = ` +exports['e2e caught and uncaught hooks errors / failing2'] = ` ==================================================================================================== @@ -203,7 +203,7 @@ Because this error occurred during a \`before each\` hook we are skipping the re ` -exports['e2e caught and uncaught hooks errors failing3 1'] = ` +exports['e2e caught and uncaught hooks errors / failing3'] = ` ==================================================================================================== @@ -287,7 +287,7 @@ Because this error occurred during a \`before each\` hook we are skipping all of ` -exports['e2e caught and uncaught hooks errors failing4 1'] = ` +exports['e2e caught and uncaught hooks errors / failing4'] = ` ==================================================================================================== diff --git a/packages/server/__snapshots__/3_plugins_spec.js b/packages/server/__snapshots__/3_plugins_spec.js index 6f8fd40476fc..ebe43b1b2a64 100644 --- a/packages/server/__snapshots__/3_plugins_spec.js +++ b/packages/server/__snapshots__/3_plugins_spec.js @@ -311,7 +311,7 @@ exports['e2e plugins calls after:screenshot for cy.screenshot() and failure scre │ Failing: 1 │ │ Pending: 0 │ │ Skipped: 0 │ - │ Screenshots: 4 │ + │ Screenshots: 3 │ │ Video: true │ │ Duration: X seconds │ │ Spec Ran: after_screenshot_spec.coffee │ @@ -323,7 +323,6 @@ exports['e2e plugins calls after:screenshot for cy.screenshot() and failure scre - /XXX/XXX/XXX/screenshot-replacement.png (YxX) - /XXX/XXX/XXX/cypress/screenshots/after_screenshot_spec.coffee/ignored-values.png (YxX) - /XXX/XXX/XXX/cypress/screenshots/after_screenshot_spec.coffee/invalid-return.png (YxX) - - /XXX/XXX/XXX/screenshot-replacement.png (YxX) (Video) @@ -446,3 +445,139 @@ The following are valid events: [stack trace lines] ` + +exports['e2e plugins does not report more screenshots than exist if user overwrites screenshot in afterScreenshot hook 1'] = ` + +==================================================================================================== + + (Run Starting) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Cypress: 1.2.3 │ + │ Browser: FooBrowser 88 │ + │ Specs: 1 found (after_screenshot_overwrite_spec.coffee) │ + │ Searched: cypress/integration/after_screenshot_overwrite_spec.coffee │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: after_screenshot_overwrite_spec.coffee (1 of 1) + + + ✓ cy.screenshot() - replacement + ✓ cy.screenshot() - replacement + ✓ cy.screenshot() - replacement + + 3 passing + + + (Results) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Tests: 3 │ + │ Passing: 3 │ + │ Failing: 0 │ + │ Pending: 0 │ + │ Skipped: 0 │ + │ Screenshots: 1 │ + │ Video: true │ + │ Duration: X seconds │ + │ Spec Ran: after_screenshot_overwrite_spec.coffee │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + + (Screenshots) + + - /XXX/XXX/XXX/screenshot-replacement.png (2x2) + + + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /XXX/XXX/XXX/cypress/videos/after_screenshot_overwrite_spec (X second) + .coffee.mp4 + + +==================================================================================================== + + (Run Finished) + + + Spec Tests Passing Failing Pending Skipped + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ ✔ after_screenshot_overwrite_spec.cof XX:XX 3 3 - - - │ + │ fee │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + ✔ All specs passed! XX:XX 3 3 - - - + + +` + +exports['e2e plugins does not report more screenshots than exist if user overwrites previous screenshot in afterScreenshot 1'] = ` + +==================================================================================================== + + (Run Starting) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Cypress: 1.2.3 │ + │ Browser: FooBrowser 88 │ + │ Specs: 1 found (after_screenshot_overwrite_spec.coffee) │ + │ Searched: cypress/integration/after_screenshot_overwrite_spec.coffee │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: after_screenshot_overwrite_spec.coffee (1 of 1) + + + ✓ cy.screenshot() - replacement + ✓ cy.screenshot() - replacement + ✓ cy.screenshot() - replacement + + 3 passing + + + (Results) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Tests: 3 │ + │ Passing: 3 │ + │ Failing: 0 │ + │ Pending: 0 │ + │ Skipped: 0 │ + │ Screenshots: 1 │ + │ Video: true │ + │ Duration: X seconds │ + │ Spec Ran: after_screenshot_overwrite_spec.coffee │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + + (Screenshots) + + - /XXX/XXX/XXX/screenshot-replacement.png (2x2) + + + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /XXX/XXX/XXX/cypress/videos/after_screenshot_overwrite_spec (X second) + .coffee.mp4 + + +==================================================================================================== + + (Run Finished) + + + Spec Tests Passing Failing Pending Skipped + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ ✔ after_screenshot_overwrite_spec.cof XX:XX 3 3 - - - │ + │ fee │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + ✔ All specs passed! XX:XX 3 3 - - - + + +` diff --git a/packages/server/__snapshots__/3_retries_spec.ts.js b/packages/server/__snapshots__/3_retries_spec.ts.js new file mode 100644 index 000000000000..39bea32e843c --- /dev/null +++ b/packages/server/__snapshots__/3_retries_spec.ts.js @@ -0,0 +1,134 @@ +exports['retries / supports retries'] = ` + +==================================================================================================== + + (Run Starting) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Cypress: 1.2.3 │ + │ Browser: FooBrowser 88 │ + │ Specs: 1 found (fail-twice.js) │ + │ Searched: cypress/integration/fail-twice.js │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: fail-twice.js (1 of 1) + + + (Attempt 1 of 3) fail twice + (Attempt 2 of 3) fail twice + ✓ fail twice + + 1 passing + + + (Results) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Tests: 1 │ + │ Passing: 1 │ + │ Failing: 0 │ + │ Pending: 0 │ + │ Skipped: 0 │ + │ Screenshots: 2 │ + │ Video: true │ + │ Duration: X seconds │ + │ Spec Ran: fail-twice.js │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + + (Screenshots) + + - /XXX/XXX/XXX/cypress/screenshots/fail-twice.js/fail twice (failed).png (1280x720) + - /XXX/XXX/XXX/cypress/screenshots/fail-twice.js/fail twice (failed) (attempt 2).p (1280x720) + ng + + + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /XXX/XXX/XXX/cypress/videos/fail-twice.js.mp4 (X second) + + +==================================================================================================== + + (Run Finished) + + + Spec Tests Passing Failing Pending Skipped + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ ✔ fail-twice.js XX:XX 1 1 - - - │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + ✔ All specs passed! XX:XX 1 1 - - - + + +` + +exports['retries / warns about retries plugin'] = ` +We've detected that the incompatible plugin \`cypress-plugin-retries\` is installed at \`node_modules/cypress-plugin-retries\`. + +Test retries is now supported in Cypress version \`5.0.0\`. + +Remove the plugin from your dependencies to silence this warning. + +https://on.cypress.io/test-retries + + +==================================================================================================== + + (Run Starting) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Cypress: 1.2.3 │ + │ Browser: FooBrowser 88 │ + │ Specs: 1 found (main.spec.js) │ + │ Searched: cypress/integration/main.spec.js │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: main.spec.js (1 of 1) + + + ✓ foo + + 1 passing + + + (Results) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Tests: 1 │ + │ Passing: 1 │ + │ Failing: 0 │ + │ Pending: 0 │ + │ Skipped: 0 │ + │ Screenshots: 0 │ + │ Video: true │ + │ Duration: X seconds │ + │ Spec Ran: main.spec.js │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /XXX/XXX/XXX/cypress/videos/main.spec.js.mp4 (X second) + + +==================================================================================================== + + (Run Finished) + + + Spec Tests Passing Failing Pending Skipped + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ ✔ main.spec.js XX:XX 1 1 - - - │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + ✔ All specs passed! XX:XX 1 1 - - - + + +` diff --git a/packages/server/__snapshots__/3_runnable_execution_spec.ts.js b/packages/server/__snapshots__/3_runnable_execution_spec.ts.js index 06dc92d31c09..3fe12b45bce5 100644 --- a/packages/server/__snapshots__/3_runnable_execution_spec.ts.js +++ b/packages/server/__snapshots__/3_runnable_execution_spec.ts.js @@ -24,9 +24,12 @@ exports['e2e runnable execution / cannot navigate in before hook and test'] = ` ✓ test 1) causes domain navigation + navigation error in beforeEach + 2) "before each" hook for "never gets here" + 2 passing - 1 failing + 2 failing 1) suite causes domain navigation: @@ -51,15 +54,40 @@ You may need to restructure some of your test code to avoid this problem. https://on.cypress.io/cannot-visit-different-origin-domain [stack trace lines] + 2) navigation error in beforeEach + "before each" hook for "never gets here": + CypressError: \`cy.visit()\` failed because you are attempting to visit a URL that is of a different origin. + +The new URL is considered a different origin because the following parts of the URL are different: + + > port + +You may only \`cy.visit()\` same-origin URLs within a single test. + +The previous URL you visited was: + + > 'http://localhost:4545' + +You're attempting to visit this URL: + + > 'http://localhost:5656' + +You may need to restructure some of your test code to avoid this problem. + +https://on.cypress.io/cannot-visit-different-origin-domain + +Because this error occurred during a \`before each\` hook we are skipping the remaining tests in the current suite: \`navigation error in beforeEach\` + [stack trace lines] + (Results) ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ Tests: 3 │ + │ Tests: 4 │ │ Passing: 2 │ - │ Failing: 1 │ + │ Failing: 2 │ │ Pending: 0 │ │ Skipped: 0 │ │ Screenshots: 0 │ @@ -83,9 +111,9 @@ https://on.cypress.io/cannot-visit-different-origin-domain Spec Tests Passing Failing Pending Skipped ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ ✖ beforehook-and-test-navigation.js XX:XX 3 2 1 - - │ + │ ✖ beforehook-and-test-navigation.js XX:XX 4 2 2 - - │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ - ✖ 1 of 1 failed (100%) XX:XX 3 2 1 - - + ✖ 1 of 1 failed (100%) XX:XX 4 2 2 - - ` diff --git a/packages/server/__snapshots__/5_screenshots_spec.js b/packages/server/__snapshots__/5_screenshots_spec.js index e4fef3d1c010..b513571f7ce3 100644 --- a/packages/server/__snapshots__/5_screenshots_spec.js +++ b/packages/server/__snapshots__/5_screenshots_spec.js @@ -29,8 +29,11 @@ exports['e2e screenshots / passes'] = ` ✓ accepts screenshot after multiple tries if somehow app has pixels that match helper pixels ✓ can capture element screenshots ✓ retries each screenshot for up to XX:XX + (Attempt 1 of 3) screenshots in a retried test + (Attempt 2 of 3) screenshots in a retried test + 2) screenshots in a retried test ✓ ensures unique paths for non-named screenshots - 2) ensures unique paths when there's a non-named screenshot and a failure + 3) ensures unique paths when there's a non-named screenshot and a failure ✓ properly resizes the AUT iframe - does not take a screenshot for a pending test ✓ adds padding to element screenshot when specified @@ -41,10 +44,10 @@ exports['e2e screenshots / passes'] = ` ✓ can clip fullPage screenshots ✓ can clip element screenshots before hooks - 3) "before all" hook for "empty test 1" + 4) "before all" hook for "empty test 1" each hooks - 4) "before each" hook for "empty test 2" - 5) "after each" hook for "empty test 2" + 5) "before each" hook for "empty test 2" + 6) "after each" hook for "empty test 2" really long test title aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ✓ takes a screenshot ✓ takes another screenshot @@ -52,7 +55,7 @@ exports['e2e screenshots / passes'] = ` 20 passing 1 pending - 5 failing + 6 failing 1) taking screenshots generates pngs on failure: @@ -60,11 +63,16 @@ exports['e2e screenshots / passes'] = ` [stack trace lines] 2) taking screenshots + screenshots in a retried test: + Error: fail + [stack trace lines] + + 3) taking screenshots ensures unique paths when there's a non-named screenshot and a failure: Error: failing on purpose [stack trace lines] - 3) taking screenshots + 4) taking screenshots before hooks "before all" hook for "empty test 1": Error: before hook failing @@ -72,7 +80,7 @@ exports['e2e screenshots / passes'] = ` Because this error occurred during a \`before all\` hook we are skipping the remaining tests in the current suite: \`before hooks\` [stack trace lines] - 4) taking screenshots + 5) taking screenshots each hooks "before each" hook for "empty test 2": Error: before each hook failed @@ -80,7 +88,7 @@ Because this error occurred during a \`before all\` hook we are skipping the rem Because this error occurred during a \`before each\` hook we are skipping the remaining tests in the current suite: \`each hooks\` [stack trace lines] - 5) taking screenshots + 6) taking screenshots each hooks "after each" hook for "empty test 2": Error: after each hook failed @@ -94,12 +102,12 @@ Because this error occurred during a \`after each\` hook we are skipping the rem (Results) ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ Tests: 25 │ + │ Tests: 26 │ │ Passing: 20 │ - │ Failing: 4 │ + │ Failing: 5 │ │ Pending: 1 │ │ Skipped: 0 │ - │ Screenshots: 28 │ + │ Screenshots: 34 │ │ Video: true │ │ Duration: X seconds │ │ Spec Ran: screenshots_spec.js │ @@ -121,6 +129,17 @@ Because this error occurred during a \`after each\` hook we are skipping the rem - /XXX/XXX/XXX/cypress/screenshots/screenshots_spec.js/element.png (400x300) - /XXX/XXX/XXX/cypress/screenshots/screenshots_spec.js/taking screenshots -- retri (200x1300) es each screenshot for up to XX:XX.png + - /XXX/XXX/XXX/cypress/screenshots/screenshots_spec.js/retrying-test.png (1000x1316) + - /XXX/XXX/XXX/cypress/screenshots/screenshots_spec.js/taking screenshots -- scree (1280x720) + nshots in a retried test (failed).png + - /XXX/XXX/XXX/cypress/screenshots/screenshots_spec.js/retrying-test (attempt 2).p (1000x1316) + ng + - /XXX/XXX/XXX/cypress/screenshots/screenshots_spec.js/taking screenshots -- scree (1280x720) + nshots in a retried test (failed) (attempt 2).png + - /XXX/XXX/XXX/cypress/screenshots/screenshots_spec.js/retrying-test (attempt 3).p (1000x1316) + ng + - /XXX/XXX/XXX/cypress/screenshots/screenshots_spec.js/taking screenshots -- scree (1280x720) + nshots in a retried test (failed) (attempt 3).png - /XXX/XXX/XXX/cypress/screenshots/screenshots_spec.js/taking screenshots -- ensur (1280x720) es unique paths for non-named screenshots.png - /XXX/XXX/XXX/cypress/screenshots/screenshots_spec.js/taking screenshots -- ensur (1280x720) @@ -167,9 +186,9 @@ Because this error occurred during a \`after each\` hook we are skipping the rem Spec Tests Passing Failing Pending Skipped ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ ✖ screenshots_spec.js XX:XX 25 20 4 1 - │ + │ ✖ screenshots_spec.js XX:XX 26 20 5 1 - │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ - ✖ 1 of 1 failed (100%) XX:XX 25 20 4 1 - + ✖ 1 of 1 failed (100%) XX:XX 26 20 5 1 - ` diff --git a/packages/server/__snapshots__/5_spec_isolation_spec.js b/packages/server/__snapshots__/5_spec_isolation_spec.js index 93dd31c25ea3..74edb00023b7 100644 --- a/packages/server/__snapshots__/5_spec_isolation_spec.js +++ b/packages/server/__snapshots__/5_spec_isolation_spec.js @@ -24,8 +24,8 @@ exports['e2e spec isolation fails'] = { "reporter": "spec", "reporterStats": { "suites": 5, - "tests": 4, - "passes": 3, + "tests": 5, + "passes": 1, "pending": 1, "failures": 3, "start": "2018-02-01T20:14:19.323Z", @@ -35,14 +35,6 @@ exports['e2e spec isolation fails'] = { "hooks": [ { "hookId": "h1", - "hookName": "before all", - "title": [ - "\"before all\" hook" - ], - "body": "function () {\n if (Cypress.browser.family === 'chromium' && Cypress.browser.name !== 'electron') {\n return Cypress.automation('remote:debugger:protocol', {\n command: 'Emulation.setDeviceMetricsOverride',\n params: {\n width: 1280,\n height: 720,\n deviceScaleFactor: 1,\n mobile: false,\n screenWidth: 1280,\n screenHeight: 720\n }\n }).then(function () {\n // can't tell expect() not to log, so manually throwing here\n if (window.devicePixelRatio !== 1) {\n throw new Error('Setting devicePixelRatio to 1 failed');\n }\n });\n }\n}" - }, - { - "hookId": "h2", "hookName": "before each", "title": [ "\"before each\" hook" @@ -50,7 +42,7 @@ exports['e2e spec isolation fails'] = { "body": "function() {\n throw new Error(\"fail1\");\n }" }, { - "hookId": "h3", + "hookId": "h2", "hookName": "after each", "title": [ "\"after each\" hook" @@ -58,7 +50,7 @@ exports['e2e spec isolation fails'] = { "body": "function() {\n throw new Error(\"fail2\");\n }" }, { - "hookId": "h4", + "hookId": "h3", "hookName": "after all", "title": [ "\"after all\" hook" @@ -76,29 +68,31 @@ exports['e2e spec isolation fails'] = { ], "state": "failed", "body": "function() {}", - "stack": "Error: fail1\n\nBecause this error occurred during a `before each` hook we are skipping the remaining tests in the current suite: `beforeEach hooks`\n [stack trace lines]", - "error": "fail1\n\nBecause this error occurred during a `before each` hook we are skipping the remaining tests in the current suite: `beforeEach hooks`", - "timings": { - "lifecycle": 100, - "before all": [ - { - "hookId": "h1", - "fnDuration": 400, - "afterFnDuration": 200 - } - ], - "before each": [ - { - "hookId": "h2", - "fnDuration": 400, - "afterFnDuration": 200 - } - ] - }, - "failedFromHookId": "h2", - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234, - "videoTimestamp": 9999 + "displayError": "Error: fail1\n\nBecause this error occurred during a `before each` hook we are skipping the remaining tests in the current suite: `beforeEach hooks`\n [stack trace lines]", + "attempts": [ + { + "state": "failed", + "error": { + "name": "Error", + "message": "fail1\n\nBecause this error occurred during a `before each` hook we are skipping the remaining tests in the current suite: `beforeEach hooks`", + "stack": "[stack trace lines]" + }, + "timings": { + "lifecycle": 100, + "before each": [ + { + "hookId": "h1", + "fnDuration": 400, + "afterFnDuration": 200 + } + ] + }, + "failedFromHookId": "h1", + "wallClockStartedAt": "2018-02-01T20:14:19.323Z", + "wallClockDuration": 1234, + "videoTimestamp": 9999 + } + ] }, { "testId": "r6", @@ -109,13 +103,18 @@ exports['e2e spec isolation fails'] = { ], "state": "pending", "body": "", - "stack": null, - "error": null, - "timings": null, - "failedFromHookId": null, - "wallClockStartedAt": null, - "wallClockDuration": null, - "videoTimestamp": null + "displayError": null, + "attempts": [ + { + "state": "pending", + "error": null, + "timings": null, + "failedFromHookId": null, + "wallClockStartedAt": null, + "wallClockDuration": null, + "videoTimestamp": null + } + ] }, { "testId": "r8", @@ -126,26 +125,35 @@ exports['e2e spec isolation fails'] = { ], "state": "failed", "body": "function() {}", - "stack": "Error: fail2\n\nBecause this error occurred during a `after each` hook we are skipping the remaining tests in the current suite: `afterEach hooks`\n [stack trace lines]", - "error": "fail2\n\nBecause this error occurred during a `after each` hook we are skipping the remaining tests in the current suite: `afterEach hooks`", - "timings": { - "lifecycle": 100, - "test": { - "fnDuration": 400, - "afterFnDuration": 200 - }, - "after each": [ - { - "hookId": "h3", - "fnDuration": 400, - "afterFnDuration": 200 - } - ] - }, - "failedFromHookId": "h3", - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234, - "videoTimestamp": 9999 + "displayError": "Error: fail2\n\nBecause this error occurred during a `after each` hook we are skipping the remaining tests in the current suite: `afterEach hooks`\n [stack trace lines]", + "attempts": [ + { + "state": "failed", + "error": { + "name": "Error", + "message": "fail2\n\nBecause this error occurred during a `after each` hook we are skipping the remaining tests in the current suite: `afterEach hooks`", + "stack": "[stack trace lines]" + }, + "timings": { + "lifecycle": 100, + "test": { + "fnDuration": 400, + "afterFnDuration": 200 + }, + "after each": [ + { + "hookId": "h2", + "fnDuration": 400, + "afterFnDuration": 200 + } + ] + }, + "failedFromHookId": "h2", + "wallClockStartedAt": "2018-02-01T20:14:19.323Z", + "wallClockDuration": 1234, + "videoTimestamp": 9999 + } + ] }, { "testId": "r9", @@ -156,13 +164,18 @@ exports['e2e spec isolation fails'] = { ], "state": "skipped", "body": "function() {}", - "stack": null, - "error": null, - "timings": null, - "failedFromHookId": null, - "wallClockStartedAt": null, - "wallClockDuration": null, - "videoTimestamp": null + "displayError": null, + "attempts": [ + { + "state": "skipped", + "error": null, + "timings": null, + "failedFromHookId": null, + "wallClockStartedAt": null, + "wallClockDuration": null, + "videoTimestamp": null + } + ] }, { "testId": "r11", @@ -173,19 +186,24 @@ exports['e2e spec isolation fails'] = { ], "state": "passed", "body": "function() {}", - "stack": null, - "error": null, - "timings": { - "lifecycle": 100, - "test": { - "fnDuration": 400, - "afterFnDuration": 200 + "displayError": null, + "attempts": [ + { + "state": "passed", + "error": null, + "timings": { + "lifecycle": 100, + "test": { + "fnDuration": 400, + "afterFnDuration": 200 + } + }, + "failedFromHookId": null, + "wallClockStartedAt": "2018-02-01T20:14:19.323Z", + "wallClockDuration": 1234, + "videoTimestamp": 9999 } - }, - "failedFromHookId": null, - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234, - "videoTimestamp": 9999 + ] }, { "testId": "r12", @@ -196,26 +214,35 @@ exports['e2e spec isolation fails'] = { ], "state": "failed", "body": "function() {}", - "stack": "Error: fail3\n\nBecause this error occurred during a `after all` hook we are skipping the remaining tests in the current suite: `after hooks`\n [stack trace lines]", - "error": "fail3\n\nBecause this error occurred during a `after all` hook we are skipping the remaining tests in the current suite: `after hooks`", - "timings": { - "lifecycle": 100, - "test": { - "fnDuration": 400, - "afterFnDuration": 200 - }, - "after all": [ - { - "hookId": "h4", - "fnDuration": 400, - "afterFnDuration": 200 - } - ] - }, - "failedFromHookId": "h4", - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234, - "videoTimestamp": 9999 + "displayError": "Error: fail3\n\nBecause this error occurred during a `after all` hook we are skipping the remaining tests in the current suite: `after hooks`\n [stack trace lines]", + "attempts": [ + { + "state": "failed", + "error": { + "name": "Error", + "message": "fail3\n\nBecause this error occurred during a `after all` hook we are skipping the remaining tests in the current suite: `after hooks`", + "stack": "[stack trace lines]" + }, + "timings": { + "lifecycle": 100, + "test": { + "fnDuration": 400, + "afterFnDuration": 200 + }, + "after all": [ + { + "hookId": "h3", + "fnDuration": 400, + "afterFnDuration": 200 + } + ] + }, + "failedFromHookId": "h3", + "wallClockStartedAt": "2018-02-01T20:14:19.323Z", + "wallClockDuration": 1234, + "videoTimestamp": 9999 + } + ] } ], "error": null, @@ -225,6 +252,7 @@ exports['e2e spec isolation fails'] = { "screenshotId": "some-random-id", "name": null, "testId": "r4", + "testAttemptIndex": 0, "takenAt": "2018-02-01T20:14:19.323Z", "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_hook_spec.coffee/simple failing hook spec -- beforeEach hooks -- never gets here -- before each hook (failed).png", "height": 720, @@ -234,6 +262,7 @@ exports['e2e spec isolation fails'] = { "screenshotId": "some-random-id", "name": null, "testId": "r8", + "testAttemptIndex": 0, "takenAt": "2018-02-01T20:14:19.323Z", "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_hook_spec.coffee/simple failing hook spec -- afterEach hooks -- runs this -- after each hook (failed).png", "height": 720, @@ -243,6 +272,7 @@ exports['e2e spec isolation fails'] = { "screenshotId": "some-random-id", "name": null, "testId": "r12", + "testAttemptIndex": 0, "takenAt": "2018-02-01T20:14:19.323Z", "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_hook_spec.coffee/simple failing hook spec -- after hooks -- fails on this -- after all hook (failed).png", "height": 720, @@ -280,16 +310,7 @@ exports['e2e spec isolation fails'] = { "end": "2018-02-01T20:14:19.323Z", "duration": 1234 }, - "hooks": [ - { - "hookId": "h1", - "hookName": "before all", - "title": [ - "\"before all\" hook" - ], - "body": "function () {\n if (Cypress.browser.family === 'chromium' && Cypress.browser.name !== 'electron') {\n return Cypress.automation('remote:debugger:protocol', {\n command: 'Emulation.setDeviceMetricsOverride',\n params: {\n width: 1280,\n height: 720,\n deviceScaleFactor: 1,\n mobile: false,\n screenWidth: 1280,\n screenHeight: 720\n }\n }).then(function () {\n // can't tell expect() not to log, so manually throwing here\n if (window.devicePixelRatio !== 1) {\n throw new Error('Setting devicePixelRatio to 1 failed');\n }\n });\n }\n}" - } - ], + "hooks": [], "tests": [ { "testId": "r3", @@ -299,26 +320,28 @@ exports['e2e spec isolation fails'] = { ], "state": "failed", "body": "function() {\n return cy.wrap(true, {\n timeout: 100\n }).should(\"be.false\");\n }", - "stack": "AssertionError: Timed out retrying: expected true to be false\n [stack trace lines]", - "error": "Timed out retrying: expected true to be false", - "timings": { - "lifecycle": 100, - "before all": [ - { - "hookId": "h1", - "fnDuration": 400, - "afterFnDuration": 200 - } - ], - "test": { - "fnDuration": 400, - "afterFnDuration": 200 + "displayError": "AssertionError: Timed out retrying: expected true to be false\n [stack trace lines]", + "attempts": [ + { + "state": "failed", + "error": { + "name": "AssertionError", + "message": "Timed out retrying: expected true to be false", + "stack": "[stack trace lines]" + }, + "timings": { + "lifecycle": 100, + "test": { + "fnDuration": 400, + "afterFnDuration": 200 + } + }, + "failedFromHookId": null, + "wallClockStartedAt": "2018-02-01T20:14:19.323Z", + "wallClockDuration": 1234, + "videoTimestamp": 9999 } - }, - "failedFromHookId": null, - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234, - "videoTimestamp": 9999 + ] }, { "testId": "r4", @@ -328,19 +351,28 @@ exports['e2e spec isolation fails'] = { ], "state": "failed", "body": "function() {\n throw new Error(\"fails2\");\n }", - "stack": "Error: fails2\n [stack trace lines]", - "error": "fails2", - "timings": { - "lifecycle": 100, - "test": { - "fnDuration": 400, - "afterFnDuration": 200 + "displayError": "Error: fails2\n [stack trace lines]", + "attempts": [ + { + "state": "failed", + "error": { + "name": "Error", + "message": "fails2", + "stack": "[stack trace lines]" + }, + "timings": { + "lifecycle": 100, + "test": { + "fnDuration": 400, + "afterFnDuration": 200 + } + }, + "failedFromHookId": null, + "wallClockStartedAt": "2018-02-01T20:14:19.323Z", + "wallClockDuration": 1234, + "videoTimestamp": 9999 } - }, - "failedFromHookId": null, - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234, - "videoTimestamp": 9999 + ] } ], "error": null, @@ -350,6 +382,7 @@ exports['e2e spec isolation fails'] = { "screenshotId": "some-random-id", "name": null, "testId": "r3", + "testAttemptIndex": 0, "takenAt": "2018-02-01T20:14:19.323Z", "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_spec.coffee/simple failing spec -- fails1 (failed).png", "height": 720, @@ -359,6 +392,7 @@ exports['e2e spec isolation fails'] = { "screenshotId": "some-random-id", "name": null, "testId": "r4", + "testAttemptIndex": 0, "takenAt": "2018-02-01T20:14:19.323Z", "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_spec.coffee/simple failing spec -- fails2 (failed).png", "height": 720, @@ -403,18 +437,10 @@ exports['e2e spec isolation fails'] = { "title": [ "\"before all\" hook" ], - "body": "function () {\n if (Cypress.browser.family === 'chromium' && Cypress.browser.name !== 'electron') {\n return Cypress.automation('remote:debugger:protocol', {\n command: 'Emulation.setDeviceMetricsOverride',\n params: {\n width: 1280,\n height: 720,\n deviceScaleFactor: 1,\n mobile: false,\n screenWidth: 1280,\n screenHeight: 720\n }\n }).then(function () {\n // can't tell expect() not to log, so manually throwing here\n if (window.devicePixelRatio !== 1) {\n throw new Error('Setting devicePixelRatio to 1 failed');\n }\n });\n }\n}" - }, - { - "hookId": "h2", - "hookName": "before all", - "title": [ - "\"before all\" hook" - ], "body": "function() {\n return cy.wait(100);\n }" }, { - "hookId": "h3", + "hookId": "h2", "hookName": "before each", "title": [ "\"before each\" hook" @@ -422,7 +448,7 @@ exports['e2e spec isolation fails'] = { "body": "function() {\n return cy.wait(200);\n }" }, { - "hookId": "h5", + "hookId": "h4", "hookName": "after each", "title": [ "\"after each\" hook" @@ -430,7 +456,7 @@ exports['e2e spec isolation fails'] = { "body": "function() {\n return cy.wait(200);\n }" }, { - "hookId": "h4", + "hookId": "h3", "hookName": "after all", "title": [ "\"after all\" hook" @@ -447,45 +473,45 @@ exports['e2e spec isolation fails'] = { ], "state": "passed", "body": "function() {\n return cy.wrap(\"t1\").should(\"eq\", \"t1\");\n }", - "stack": null, - "error": null, - "timings": { - "lifecycle": 100, - "before all": [ - { - "hookId": "h1", - "fnDuration": 400, - "afterFnDuration": 200 + "displayError": null, + "attempts": [ + { + "state": "passed", + "error": null, + "timings": { + "lifecycle": 100, + "before all": [ + { + "hookId": "h1", + "fnDuration": 400, + "afterFnDuration": 200 + } + ], + "before each": [ + { + "hookId": "h2", + "fnDuration": 400, + "afterFnDuration": 200 + } + ], + "test": { + "fnDuration": 400, + "afterFnDuration": 200 + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": 400, + "afterFnDuration": 200 + } + ] }, - { - "hookId": "h2", - "fnDuration": 400, - "afterFnDuration": 200 - } - ], - "before each": [ - { - "hookId": "h3", - "fnDuration": 400, - "afterFnDuration": 200 - } - ], - "test": { - "fnDuration": 400, - "afterFnDuration": 200 - }, - "after each": [ - { - "hookId": "h5", - "fnDuration": 400, - "afterFnDuration": 200 - } - ] - }, - "failedFromHookId": null, - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234, - "videoTimestamp": 9999 + "failedFromHookId": null, + "wallClockStartedAt": "2018-02-01T20:14:19.323Z", + "wallClockDuration": 1234, + "videoTimestamp": 9999 + } + ] }, { "testId": "r4", @@ -495,33 +521,38 @@ exports['e2e spec isolation fails'] = { ], "state": "passed", "body": "function() {\n return cy.wrap(\"t2\").should(\"eq\", \"t2\");\n }", - "stack": null, - "error": null, - "timings": { - "lifecycle": 100, - "before each": [ - { - "hookId": "h3", - "fnDuration": 400, - "afterFnDuration": 200 - } - ], - "test": { - "fnDuration": 400, - "afterFnDuration": 200 - }, - "after each": [ - { - "hookId": "h5", - "fnDuration": 400, - "afterFnDuration": 200 - } - ] - }, - "failedFromHookId": null, - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234, - "videoTimestamp": 9999 + "displayError": null, + "attempts": [ + { + "state": "passed", + "error": null, + "timings": { + "lifecycle": 100, + "before each": [ + { + "hookId": "h2", + "fnDuration": 400, + "afterFnDuration": 200 + } + ], + "test": { + "fnDuration": 400, + "afterFnDuration": 200 + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": 400, + "afterFnDuration": 200 + } + ] + }, + "failedFromHookId": null, + "wallClockStartedAt": "2018-02-01T20:14:19.323Z", + "wallClockDuration": 1234, + "videoTimestamp": 9999 + } + ] }, { "testId": "r5", @@ -531,40 +562,45 @@ exports['e2e spec isolation fails'] = { ], "state": "passed", "body": "function() {\n return cy.wrap(\"t3\").should(\"eq\", \"t3\");\n }", - "stack": null, - "error": null, - "timings": { - "lifecycle": 100, - "before each": [ - { - "hookId": "h3", - "fnDuration": 400, - "afterFnDuration": 200 - } - ], - "test": { - "fnDuration": 400, - "afterFnDuration": 200 - }, - "after each": [ - { - "hookId": "h5", - "fnDuration": 400, - "afterFnDuration": 200 - } - ], - "after all": [ - { - "hookId": "h4", - "fnDuration": 400, - "afterFnDuration": 200 - } - ] - }, - "failedFromHookId": null, - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234, - "videoTimestamp": 9999 + "displayError": null, + "attempts": [ + { + "state": "passed", + "error": null, + "timings": { + "lifecycle": 100, + "before each": [ + { + "hookId": "h2", + "fnDuration": 400, + "afterFnDuration": 200 + } + ], + "test": { + "fnDuration": 400, + "afterFnDuration": 200 + }, + "after each": [ + { + "hookId": "h4", + "fnDuration": 400, + "afterFnDuration": 200 + } + ], + "after all": [ + { + "hookId": "h3", + "fnDuration": 400, + "afterFnDuration": 200 + } + ] + }, + "failedFromHookId": null, + "wallClockStartedAt": "2018-02-01T20:14:19.323Z", + "wallClockDuration": 1234, + "videoTimestamp": 9999 + } + ] } ], "error": null, @@ -604,14 +640,6 @@ exports['e2e spec isolation fails'] = { "hooks": [ { "hookId": "h1", - "hookName": "before all", - "title": [ - "\"before all\" hook" - ], - "body": "function () {\n if (Cypress.browser.family === 'chromium' && Cypress.browser.name !== 'electron') {\n return Cypress.automation('remote:debugger:protocol', {\n command: 'Emulation.setDeviceMetricsOverride',\n params: {\n width: 1280,\n height: 720,\n deviceScaleFactor: 1,\n mobile: false,\n screenWidth: 1280,\n screenHeight: 720\n }\n }).then(function () {\n // can't tell expect() not to log, so manually throwing here\n if (window.devicePixelRatio !== 1) {\n throw new Error('Setting devicePixelRatio to 1 failed');\n }\n });\n }\n}" - }, - { - "hookId": "h2", "hookName": "before each", "title": [ "\"before each\" hook" @@ -628,33 +656,31 @@ exports['e2e spec isolation fails'] = { ], "state": "passed", "body": "function() {\n return cy.wrap(true).should(\"be.true\");\n }", - "stack": null, - "error": null, - "timings": { - "lifecycle": 100, - "before all": [ - { - "hookId": "h1", - "fnDuration": 400, - "afterFnDuration": 200 - } - ], - "before each": [ - { - "hookId": "h2", - "fnDuration": 400, - "afterFnDuration": 200 - } - ], - "test": { - "fnDuration": 400, - "afterFnDuration": 200 + "displayError": null, + "attempts": [ + { + "state": "passed", + "error": null, + "timings": { + "lifecycle": 100, + "before each": [ + { + "hookId": "h1", + "fnDuration": 400, + "afterFnDuration": 200 + } + ], + "test": { + "fnDuration": 400, + "afterFnDuration": 200 + } + }, + "failedFromHookId": null, + "wallClockStartedAt": "2018-02-01T20:14:19.323Z", + "wallClockDuration": 1234, + "videoTimestamp": 9999 } - }, - "failedFromHookId": null, - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234, - "videoTimestamp": 9999 + ] } ], "error": null, @@ -677,3 +703,492 @@ exports['e2e spec isolation fails'] = { "cypressVersion": "9.9.9", "config": {} } + +exports['e2e spec_isolation / failing with retries enabled'] = ` + +==================================================================================================== + + (Run Starting) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Cypress: 1.2.3 │ + │ Browser: FooBrowser 88 │ + │ Specs: 1 found (simple_failing_hook_spec.coffee) │ + │ Searched: cypress/integration/simple_failing_hook_spec.coffee │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: simple_failing_hook_spec.coffee (1 of 1) + + + simple failing hook spec + beforeEach hooks + (Attempt 1 of 2) never gets here + 1) "before each" hook for "never gets here" + pending + - is pending + afterEach hooks + (Attempt 1 of 2) runs this + 2) "after each" hook for "runs this" + after hooks + ✓ runs this + 3) "after all" hook for "fails on this" + + + 1 passing + 1 pending + 3 failing + + 1) simple failing hook spec + beforeEach hooks + "before each" hook for "never gets here": + Error: fail1 + +Because this error occurred during a \`before each\` hook we are skipping the remaining tests in the current suite: \`beforeEach hooks\` + [stack trace lines] + + 2) simple failing hook spec + afterEach hooks + "after each" hook for "runs this": + Error: fail2 + +Because this error occurred during a \`after each\` hook we are skipping the remaining tests in the current suite: \`afterEach hooks\` + [stack trace lines] + + 3) simple failing hook spec + after hooks + "after all" hook for "fails on this": + Error: fail3 + +Because this error occurred during a \`after all\` hook we are skipping the remaining tests in the current suite: \`after hooks\` + +Although you have test retries enabled, we do not retry tests when \`before all\` or \`after all\` hooks fail + [stack trace lines] + + + + + (Results) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Tests: 6 │ + │ Passing: 1 │ + │ Failing: 3 │ + │ Pending: 1 │ + │ Skipped: 1 │ + │ Screenshots: 5 │ + │ Video: true │ + │ Duration: X seconds │ + │ Spec Ran: simple_failing_hook_spec.coffee │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + + (Screenshots) + + - /XXX/XXX/XXX/cypress/screenshots/simple_failing_hook_spec.coffee/simple failing (1280x720) + hook spec -- beforeEach hooks -- never gets here (failed).png + - /XXX/XXX/XXX/cypress/screenshots/simple_failing_hook_spec.coffee/simple failing (1280x720) + hook spec -- beforeEach hooks -- never gets here -- before each hook (failed) (a + ttempt 2).png + - /XXX/XXX/XXX/cypress/screenshots/simple_failing_hook_spec.coffee/simple failing (1280x720) + hook spec -- afterEach hooks -- runs this -- after each hook (failed).png + - /XXX/XXX/XXX/cypress/screenshots/simple_failing_hook_spec.coffee/simple failing (1280x720) + hook spec -- afterEach hooks -- runs this -- after each hook (failed) (attempt 2 + ).png + - /XXX/XXX/XXX/cypress/screenshots/simple_failing_hook_spec.coffee/simple failing (1280x720) + hook spec -- after hooks -- fails on this -- after all hook (failed).png + + + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /XXX/XXX/XXX/cypress/videos/simple_failing_hook_spec.coffee (X second) + .mp4 + + +==================================================================================================== + + (Run Finished) + + + Spec Tests Passing Failing Pending Skipped + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ ✖ simple_failing_hook_spec.coffee XX:XX 6 1 3 1 1 │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + ✖ 1 of 1 failed (100%) XX:XX 6 1 3 1 1 + + +` + +exports['failing with retries enabled'] = { + "startedTestsAt": "2018-02-01T20:14:19.323Z", + "endedTestsAt": "2018-02-01T20:14:19.323Z", + "totalDuration": 5555, + "totalSuites": 5, + "totalTests": 6, + "totalFailed": 3, + "totalPassed": 1, + "totalPending": 1, + "totalSkipped": 1, + "runs": [ + { + "stats": { + "suites": 5, + "tests": 6, + "passes": 1, + "pending": 1, + "skipped": 1, + "failures": 3, + "wallClockStartedAt": "2018-02-01T20:14:19.323Z", + "wallClockEndedAt": "2018-02-01T20:14:19.323Z", + "wallClockDuration": 1234 + }, + "reporter": "spec", + "reporterStats": { + "suites": 5, + "tests": 5, + "passes": 1, + "pending": 1, + "failures": 3, + "start": "2018-02-01T20:14:19.323Z", + "end": "2018-02-01T20:14:19.323Z", + "duration": 1234 + }, + "hooks": [ + { + "hookId": "h1", + "hookName": "before each", + "title": [ + "\"before each\" hook" + ], + "body": "function() {\n throw new Error(\"fail1\");\n }" + }, + { + "hookId": "h2", + "hookName": "after each", + "title": [ + "\"after each\" hook" + ], + "body": "function() {\n throw new Error(\"fail2\");\n }" + }, + { + "hookId": "h3", + "hookName": "after all", + "title": [ + "\"after all\" hook" + ], + "body": "function() {\n throw new Error(\"fail3\");\n }" + } + ], + "tests": [ + { + "testId": "r4", + "title": [ + "simple failing hook spec", + "beforeEach hooks", + "never gets here" + ], + "state": "failed", + "body": "function() {}", + "displayError": "Error: fail1\n\nBecause this error occurred during a `before each` hook we are skipping the remaining tests in the current suite: `beforeEach hooks`\n [stack trace lines]", + "attempts": [ + { + "state": "failed", + "error": { + "name": "Error", + "message": "fail1\n\nBecause this error occurred during a `before each` hook we are skipping the remaining tests in the current suite: `beforeEach hooks`", + "stack": "[stack trace lines]" + }, + "timings": { + "lifecycle": 100, + "before each": [ + { + "hookId": "h1", + "fnDuration": 400, + "afterFnDuration": 200 + } + ] + }, + "failedFromHookId": "h1", + "wallClockStartedAt": "2018-02-01T20:14:19.323Z", + "wallClockDuration": 1234, + "videoTimestamp": 9999 + }, + { + "state": "failed", + "error": { + "name": "Error", + "message": "fail1", + "stack": "[stack trace lines]" + }, + "timings": { + "lifecycle": 100, + "before each": [ + { + "hookId": "h1", + "fnDuration": 400, + "afterFnDuration": 200 + } + ], + "test": { + "fnDuration": 400, + "afterFnDuration": 200 + } + }, + "failedFromHookId": "h1", + "wallClockStartedAt": "2018-02-01T20:14:19.323Z", + "wallClockDuration": 1234, + "videoTimestamp": 9999 + } + ] + }, + { + "testId": "r6", + "title": [ + "simple failing hook spec", + "pending", + "is pending" + ], + "state": "pending", + "body": "", + "displayError": null, + "attempts": [ + { + "state": "pending", + "error": null, + "timings": null, + "failedFromHookId": null, + "wallClockStartedAt": null, + "wallClockDuration": null, + "videoTimestamp": null + } + ] + }, + { + "testId": "r8", + "title": [ + "simple failing hook spec", + "afterEach hooks", + "runs this" + ], + "state": "failed", + "body": "function() {}", + "displayError": "Error: fail2\n\nBecause this error occurred during a `after each` hook we are skipping the remaining tests in the current suite: `afterEach hooks`\n [stack trace lines]", + "attempts": [ + { + "state": "failed", + "error": { + "name": "Error", + "message": "fail2\n\nBecause this error occurred during a `after each` hook we are skipping the remaining tests in the current suite: `afterEach hooks`", + "stack": "[stack trace lines]" + }, + "timings": { + "lifecycle": 100, + "test": { + "fnDuration": 400, + "afterFnDuration": 200 + }, + "after each": [ + { + "hookId": "h2", + "fnDuration": 400, + "afterFnDuration": 200 + } + ] + }, + "failedFromHookId": "h2", + "wallClockStartedAt": "2018-02-01T20:14:19.323Z", + "wallClockDuration": 1234, + "videoTimestamp": 9999 + }, + { + "state": "failed", + "error": { + "name": "Error", + "message": "fail2", + "stack": "[stack trace lines]" + }, + "timings": { + "lifecycle": 100, + "test": { + "fnDuration": 400, + "afterFnDuration": 200 + }, + "after each": [ + { + "hookId": "h2", + "fnDuration": 400, + "afterFnDuration": 200 + } + ] + }, + "failedFromHookId": "h2", + "wallClockStartedAt": "2018-02-01T20:14:19.323Z", + "wallClockDuration": 1234, + "videoTimestamp": 9999 + } + ] + }, + { + "testId": "r9", + "title": [ + "simple failing hook spec", + "afterEach hooks", + "does not run this" + ], + "state": "skipped", + "body": "function() {}", + "displayError": null, + "attempts": [ + { + "state": "skipped", + "error": null, + "timings": null, + "failedFromHookId": null, + "wallClockStartedAt": null, + "wallClockDuration": null, + "videoTimestamp": null + } + ] + }, + { + "testId": "r11", + "title": [ + "simple failing hook spec", + "after hooks", + "runs this" + ], + "state": "passed", + "body": "function() {}", + "displayError": null, + "attempts": [ + { + "state": "passed", + "error": null, + "timings": { + "lifecycle": 100, + "test": { + "fnDuration": 400, + "afterFnDuration": 200 + } + }, + "failedFromHookId": null, + "wallClockStartedAt": "2018-02-01T20:14:19.323Z", + "wallClockDuration": 1234, + "videoTimestamp": 9999 + } + ] + }, + { + "testId": "r12", + "title": [ + "simple failing hook spec", + "after hooks", + "fails on this" + ], + "state": "failed", + "body": "function() {}", + "displayError": "Error: fail3\n\nBecause this error occurred during a `after all` hook we are skipping the remaining tests in the current suite: `after hooks`\n\nAlthough you have test retries enabled, we do not retry tests when `before all` or `after all` hooks fail\n [stack trace lines]", + "attempts": [ + { + "state": "failed", + "error": { + "name": "Error", + "message": "fail3\n\nBecause this error occurred during a `after all` hook we are skipping the remaining tests in the current suite: `after hooks`\n\nAlthough you have test retries enabled, we do not retry tests when `before all` or `after all` hooks fail", + "stack": "[stack trace lines]" + }, + "timings": { + "lifecycle": 100, + "test": { + "fnDuration": 400, + "afterFnDuration": 200 + }, + "after all": [ + { + "hookId": "h3", + "fnDuration": 400, + "afterFnDuration": 200 + } + ] + }, + "failedFromHookId": "h3", + "wallClockStartedAt": "2018-02-01T20:14:19.323Z", + "wallClockDuration": 1234, + "videoTimestamp": 9999 + } + ] + } + ], + "error": null, + "video": "/foo/bar/.projects/e2e/cypress/videos/simple_failing_hook_spec.coffee.mp4", + "screenshots": [ + { + "screenshotId": "some-random-id", + "name": null, + "testId": "r4", + "testAttemptIndex": 0, + "takenAt": "2018-02-01T20:14:19.323Z", + "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_hook_spec.coffee/simple failing hook spec -- beforeEach hooks -- never gets here (failed).png", + "height": 720, + "width": 1280 + }, + { + "screenshotId": "some-random-id", + "name": null, + "testId": "r4", + "testAttemptIndex": 1, + "takenAt": "2018-02-01T20:14:19.323Z", + "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_hook_spec.coffee/simple failing hook spec -- beforeEach hooks -- never gets here -- before each hook (failed) (attempt 2).png", + "height": 720, + "width": 1280 + }, + { + "screenshotId": "some-random-id", + "name": null, + "testId": "r8", + "testAttemptIndex": 0, + "takenAt": "2018-02-01T20:14:19.323Z", + "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_hook_spec.coffee/simple failing hook spec -- afterEach hooks -- runs this -- after each hook (failed).png", + "height": 720, + "width": 1280 + }, + { + "screenshotId": "some-random-id", + "name": null, + "testId": "r8", + "testAttemptIndex": 1, + "takenAt": "2018-02-01T20:14:19.323Z", + "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_hook_spec.coffee/simple failing hook spec -- afterEach hooks -- runs this -- after each hook (failed) (attempt 2).png", + "height": 720, + "width": 1280 + }, + { + "screenshotId": "some-random-id", + "name": null, + "testId": "r12", + "testAttemptIndex": 0, + "takenAt": "2018-02-01T20:14:19.323Z", + "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_hook_spec.coffee/simple failing hook spec -- after hooks -- fails on this -- after all hook (failed).png", + "height": 720, + "width": 1280 + } + ], + "spec": { + "name": "simple_failing_hook_spec.coffee", + "relative": "cypress/integration/simple_failing_hook_spec.coffee", + "absolute": "/foo/bar/.projects/e2e/cypress/integration/simple_failing_hook_spec.coffee", + "specType": "integration" + }, + "shouldUploadVideo": true + } + ], + "browserPath": "path/to/browser", + "browserName": "FooBrowser", + "browserVersion": "88", + "osName": "FooOS", + "osVersion": "1234", + "cypressVersion": "9.9.9", + "config": {} +} diff --git a/packages/server/__snapshots__/7_record_spec.js b/packages/server/__snapshots__/7_record_spec.js index c4c3303effa1..75d9ba2d15c2 100644 --- a/packages/server/__snapshots__/7_record_spec.js +++ b/packages/server/__snapshots__/7_record_spec.js @@ -11,7 +11,7 @@ exports['e2e record passing passes 1'] = ` │ e, record_uncaught_spec.coffee) │ │ Searched: cypress/integration/record* │ │ Params: Tag: false, Group: false, Parallel: false │ - │ Run URL: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 │ + │ Run URL: https://dashboard.cypress.io/projects/cjvoj7/runs/12 │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ @@ -247,7 +247,7 @@ We dynamically generated a new test to display this failure. ─────────────────────────────────────────────────────────────────────────────────────────────────────── - Recorded Run: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 + Recorded Run: https://dashboard.cypress.io/projects/cjvoj7/runs/12 ` @@ -348,7 +348,7 @@ exports['e2e record api interaction errors create instance does not update insta │ Specs: 1 found (record_pass_spec.coffee) │ │ Searched: cypress/integration/record_pass* │ │ Params: Tag: false, Group: false, Parallel: false │ - │ Run URL: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 │ + │ Run URL: https://dashboard.cypress.io/projects/cjvoj7/runs/12 │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ Warning: We encountered an error talking to our servers. @@ -407,7 +407,7 @@ StatusCodeError: 500 - "Internal Server Error" ─────────────────────────────────────────────────────────────────────────────────────────────────────── - Recorded Run: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 + Recorded Run: https://dashboard.cypress.io/projects/cjvoj7/runs/12 ` @@ -424,7 +424,7 @@ exports['e2e record api interaction errors update instance does not update insta │ Specs: 1 found (record_pass_spec.coffee) │ │ Searched: cypress/integration/record_pass* │ │ Params: Tag: false, Group: false, Parallel: false │ - │ Run URL: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 │ + │ Run URL: https://dashboard.cypress.io/projects/cjvoj7/runs/12 │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ @@ -488,7 +488,7 @@ StatusCodeError: 500 - "Internal Server Error" ─────────────────────────────────────────────────────────────────────────────────────────────────────── - Recorded Run: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 + Recorded Run: https://dashboard.cypress.io/projects/cjvoj7/runs/12 ` @@ -505,7 +505,7 @@ exports['e2e record api interaction errors update instance stdout warns but proc │ Specs: 1 found (record_pass_spec.coffee) │ │ Searched: cypress/integration/record_pass* │ │ Params: Tag: false, Group: false, Parallel: false │ - │ Run URL: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 │ + │ Run URL: https://dashboard.cypress.io/projects/cjvoj7/runs/12 │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ @@ -570,7 +570,7 @@ StatusCodeError: 500 - "Internal Server Error" ─────────────────────────────────────────────────────────────────────────────────────────────────────── - Recorded Run: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 + Recorded Run: https://dashboard.cypress.io/projects/cjvoj7/runs/12 ` @@ -626,7 +626,7 @@ exports['e2e record video recording does not upload when not enabled 1'] = ` │ Specs: 1 found (record_pass_spec.coffee) │ │ Searched: cypress/integration/record_pass* │ │ Params: Tag: false, Group: false, Parallel: false │ - │ Run URL: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 │ + │ Run URL: https://dashboard.cypress.io/projects/cjvoj7/runs/12 │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ @@ -684,7 +684,7 @@ exports['e2e record video recording does not upload when not enabled 1'] = ` ─────────────────────────────────────────────────────────────────────────────────────────────────────── - Recorded Run: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 + Recorded Run: https://dashboard.cypress.io/projects/cjvoj7/runs/12 ` @@ -701,7 +701,7 @@ exports['e2e record api interaction errors uploading assets warns but proceeds 1 │ Specs: 1 found (record_pass_spec.coffee) │ │ Searched: cypress/integration/record_pass* │ │ Params: Tag: false, Group: false, Parallel: false │ - │ Run URL: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 │ + │ Run URL: https://dashboard.cypress.io/projects/cjvoj7/runs/12 │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ @@ -766,7 +766,7 @@ exports['e2e record api interaction errors uploading assets warns but proceeds 1 ─────────────────────────────────────────────────────────────────────────────────────────────────────── - Recorded Run: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 + Recorded Run: https://dashboard.cypress.io/projects/cjvoj7/runs/12 ` @@ -882,7 +882,7 @@ exports['e2e record parallelization passes in parallel with group 1'] = ` │ e, record_uncaught_spec.coffee) │ │ Searched: cypress/integration/record* │ │ Params: Tag: nightly, Group: prod-e2e, Parallel: true │ - │ Run URL: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 │ + │ Run URL: https://dashboard.cypress.io/projects/cjvoj7/runs/12 │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ @@ -940,7 +940,7 @@ exports['e2e record parallelization passes in parallel with group 1'] = ` ─────────────────────────────────────────────────────────────────────────────────────────────────────── - Recorded Run: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 + Recorded Run: https://dashboard.cypress.io/projects/cjvoj7/runs/12 ` @@ -958,7 +958,7 @@ exports['e2e record parallelization passes in parallel with group 2'] = ` │ e, record_uncaught_spec.coffee) │ │ Searched: cypress/integration/record* │ │ Params: Tag: nightly, Group: prod-e2e, Parallel: true │ - │ Run URL: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 │ + │ Run URL: https://dashboard.cypress.io/projects/cjvoj7/runs/12 │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ @@ -1152,7 +1152,7 @@ We dynamically generated a new test to display this failure. ─────────────────────────────────────────────────────────────────────────────────────────────────────── - Recorded Run: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 + Recorded Run: https://dashboard.cypress.io/projects/cjvoj7/runs/12 ` @@ -1286,7 +1286,7 @@ exports['e2e record api interaction errors create instance 500 does not proceed │ Specs: 1 found (record_pass_spec.coffee) │ │ Searched: cypress/integration/record_pass* │ │ Params: Tag: nightly, Group: foo, Parallel: true │ - │ Run URL: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 │ + │ Run URL: https://dashboard.cypress.io/projects/cjvoj7/runs/12 │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ We encountered an unexpected error talking to our servers. @@ -1314,7 +1314,7 @@ exports['e2e record api interaction errors update instance 500 does not proceed │ Specs: 1 found (record_pass_spec.coffee) │ │ Searched: cypress/integration/record_pass* │ │ Params: Tag: nightly, Group: foo, Parallel: true │ - │ Run URL: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 │ + │ Run URL: https://dashboard.cypress.io/projects/cjvoj7/runs/12 │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ @@ -1402,7 +1402,7 @@ StatusCodeError: 500 - "Internal Server Error" │ Specs: 1 found (record_pass_spec.coffee) │ │ Searched: cypress/integration/record_pass* │ │ Params: Tag: nightly, Group: foo, Parallel: true │ - │ Run URL: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 │ + │ Run URL: https://dashboard.cypress.io/projects/cjvoj7/runs/12 │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ We encountered an unexpected error talking to our servers. @@ -1467,7 +1467,7 @@ StatusCodeError: 500 - "Internal Server Error" ─────────────────────────────────────────────────────────────────────────────────────────────────────── - Recorded Run: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 + Recorded Run: https://dashboard.cypress.io/projects/cjvoj7/runs/12 ` @@ -1611,7 +1611,7 @@ https://on.cypress.io/dashboard/organizations/org-id-1234/billing │ Specs: 1 found (record_pass_spec.coffee) │ │ Searched: cypress/integration/record_pass* │ │ Params: Tag: false, Group: false, Parallel: false │ - │ Run URL: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 │ + │ Run URL: https://dashboard.cypress.io/projects/cjvoj7/runs/12 │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ @@ -1669,7 +1669,7 @@ https://on.cypress.io/dashboard/organizations/org-id-1234/billing ─────────────────────────────────────────────────────────────────────────────────────────────────────── - Recorded Run: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 + Recorded Run: https://dashboard.cypress.io/projects/cjvoj7/runs/12 ` @@ -1691,7 +1691,7 @@ https://on.cypress.io/dashboard/organizations/org-id-1234/billing │ Specs: 1 found (record_pass_spec.coffee) │ │ Searched: cypress/integration/record_pass* │ │ Params: Tag: false, Group: false, Parallel: false │ - │ Run URL: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 │ + │ Run URL: https://dashboard.cypress.io/projects/cjvoj7/runs/12 │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ @@ -1749,7 +1749,7 @@ https://on.cypress.io/dashboard/organizations/org-id-1234/billing ─────────────────────────────────────────────────────────────────────────────────────────────────────── - Recorded Run: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 + Recorded Run: https://dashboard.cypress.io/projects/cjvoj7/runs/12 ` @@ -1771,7 +1771,7 @@ https://on.cypress.io/dashboard/organizations/org-id-1234/billing │ Specs: 1 found (record_pass_spec.coffee) │ │ Searched: cypress/integration/record_pass* │ │ Params: Tag: false, Group: false, Parallel: false │ - │ Run URL: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 │ + │ Run URL: https://dashboard.cypress.io/projects/cjvoj7/runs/12 │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ @@ -1829,7 +1829,7 @@ https://on.cypress.io/dashboard/organizations/org-id-1234/billing ─────────────────────────────────────────────────────────────────────────────────────────────────────── - Recorded Run: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 + Recorded Run: https://dashboard.cypress.io/projects/cjvoj7/runs/12 ` @@ -1851,7 +1851,7 @@ https://on.cypress.io/dashboard/organizations/org-id-1234/billing │ Specs: 1 found (record_pass_spec.coffee) │ │ Searched: cypress/integration/record_pass* │ │ Params: Tag: false, Group: false, Parallel: false │ - │ Run URL: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 │ + │ Run URL: https://dashboard.cypress.io/projects/cjvoj7/runs/12 │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ @@ -1909,7 +1909,7 @@ https://on.cypress.io/dashboard/organizations/org-id-1234/billing ─────────────────────────────────────────────────────────────────────────────────────────────────────── - Recorded Run: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 + Recorded Run: https://dashboard.cypress.io/projects/cjvoj7/runs/12 ` @@ -1931,7 +1931,7 @@ https://on.cypress.io/dashboard/organizations/org-id-1234/billing │ Specs: 1 found (record_pass_spec.coffee) │ │ Searched: cypress/integration/record_pass* │ │ Params: Tag: false, Group: false, Parallel: false │ - │ Run URL: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 │ + │ Run URL: https://dashboard.cypress.io/projects/cjvoj7/runs/12 │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ @@ -1989,7 +1989,7 @@ https://on.cypress.io/dashboard/organizations/org-id-1234/billing ─────────────────────────────────────────────────────────────────────────────────────────────────────── - Recorded Run: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 + Recorded Run: https://dashboard.cypress.io/projects/cjvoj7/runs/12 ` @@ -2011,7 +2011,7 @@ https://on.cypress.io/dashboard/organizations/org-id-1234/billing │ Specs: 1 found (record_pass_spec.coffee) │ │ Searched: cypress/integration/record_pass* │ │ Params: Tag: false, Group: false, Parallel: false │ - │ Run URL: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 │ + │ Run URL: https://dashboard.cypress.io/projects/cjvoj7/runs/12 │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ @@ -2069,7 +2069,7 @@ https://on.cypress.io/dashboard/organizations/org-id-1234/billing ─────────────────────────────────────────────────────────────────────────────────────────────────────── - Recorded Run: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 + Recorded Run: https://dashboard.cypress.io/projects/cjvoj7/runs/12 ` @@ -2095,7 +2095,7 @@ Details: │ Specs: 1 found (record_pass_spec.coffee) │ │ Searched: cypress/integration/record_pass* │ │ Params: Tag: false, Group: false, Parallel: false │ - │ Run URL: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 │ + │ Run URL: https://dashboard.cypress.io/projects/cjvoj7/runs/12 │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ @@ -2153,7 +2153,7 @@ Details: ─────────────────────────────────────────────────────────────────────────────────────────────────────── - Recorded Run: https://dashboard.cypress.io/#/projects/cjvoj7/runs/12 + Recorded Run: https://dashboard.cypress.io/projects/cjvoj7/runs/12 ` diff --git a/packages/server/__snapshots__/8_reporters_spec.js b/packages/server/__snapshots__/8_reporters_spec.js index 706d8c13fa0e..55b815c5dfd0 100644 --- a/packages/server/__snapshots__/8_reporters_spec.js +++ b/packages/server/__snapshots__/8_reporters_spec.js @@ -224,15 +224,13 @@ exports['e2e reporters mochawesome fails with mochawesome-1.5.2 npm custom repor pending - is pending afterEach hooks - ✓ runs this 2) "after each" hook for "runs this" after hooks ✓ runs this - ✓ fails on this 3) "after all" hook for "fails on this" - 3 passing + 1 passing 1 pending 3 failing @@ -404,15 +402,13 @@ exports['e2e reporters mochawesome fails with mochawesome-2.3.1 npm custom repor pending - is pending afterEach hooks - ✓ runs this 2) "after each" hook for "runs this" after hooks ✓ runs this - ✓ fails on this 3) "after all" hook for "fails on this" - 3 passing + 1 passing 1 pending 3 failing @@ -584,15 +580,13 @@ exports['e2e reporters mochawesome fails with mochawesome-3.0.1 npm custom repor pending - is pending afterEach hooks - ✓ runs this 2) "after each" hook for "runs this" after hooks ✓ runs this - ✓ fails on this 3) "after all" hook for "fails on this" - 3 passing + 1 passing 1 pending 3 failing diff --git a/packages/server/__snapshots__/reporter_spec.js b/packages/server/__snapshots__/reporter_spec.js index 5d5fdae8205a..6678e29bf06a 100644 --- a/packages/server/__snapshots__/reporter_spec.js +++ b/packages/server/__snapshots__/reporter_spec.js @@ -27,17 +27,21 @@ exports['lib/reporter #stats has reporterName stats, reporterStats, etc 1'] = { ], "state": "failed", "body": "", - "stack": [ - 1, - 2, - 3 - ], - "error": "foo", - "timings": null, - "failedFromHookId": null, - "wallClockStartedAt": null, - "wallClockDuration": null, - "videoTimestamp": null + "displayError": "at foo:1:1\nat bar:1:1\nat baz:1:1", + "attempts": [ + { + "state": "failed", + "error": { + "message": "foo", + "stack": "at foo:1:1\nat bar:1:1\nat baz:1:1" + }, + "timings": null, + "failedFromHookId": null, + "wallClockStartedAt": null, + "wallClockDuration": null, + "videoTimestamp": null + } + ] }, { "testId": "r5", @@ -48,13 +52,18 @@ exports['lib/reporter #stats has reporterName stats, reporterStats, etc 1'] = { ], "state": "pending", "body": "", - "stack": null, - "error": null, - "timings": null, - "failedFromHookId": null, - "wallClockStartedAt": null, - "wallClockDuration": null, - "videoTimestamp": null + "displayError": null, + "attempts": [ + { + "state": "pending", + "error": null, + "timings": null, + "failedFromHookId": null, + "wallClockStartedAt": null, + "wallClockDuration": null, + "videoTimestamp": null + } + ] } ] } diff --git a/packages/server/lib/api.js b/packages/server/lib/api.js index 176274721b43..a9f547bfc3ee 100644 --- a/packages/server/lib/api.js +++ b/packages/server/lib/api.js @@ -268,7 +268,7 @@ module.exports = { json: true, timeout: options.timeout != null ? options.timeout : SIXTY_SECONDS, headers: { - 'x-route-version': '2', + 'x-route-version': '3', }, body: _.pick(options, [ 'stats', diff --git a/packages/server/lib/browsers/firefox-util.ts b/packages/server/lib/browsers/firefox-util.ts index 41b329a90c69..82b08cb06e9f 100644 --- a/packages/server/lib/browsers/firefox-util.ts +++ b/packages/server/lib/browsers/firefox-util.ts @@ -70,6 +70,9 @@ const getPrimaryTab = Bluebird.method((browser) => { }) const attachToTabMemory = Bluebird.method((tab) => { + // TODO: figure out why tab.memory is sometimes undefined + if (!tab.memory) return + if (tab.memory.isAttached) { return } @@ -186,6 +189,9 @@ export default { const gc = (tab) => { return () => { + // TODO: figure out why tab.memory is sometimes undefined + if (!tab.memory) return + let start = Date.now() return tab.memory.forceGarbageCollection() @@ -198,6 +204,9 @@ export default { const cc = (tab) => { return () => { + // TODO: figure out why tab.memory is sometimes undefined + if (!tab.memory) return + let start = Date.now() return tab.memory.forceCycleCollection() diff --git a/packages/server/lib/config.js b/packages/server/lib/config.js index d927f8dcaa5c..5de52ab03c43 100644 --- a/packages/server/lib/config.js +++ b/packages/server/lib/config.js @@ -80,7 +80,8 @@ screenshotOnRunFailure watchForFileChanges waitForAnimations resolvedNodeVersion nodeVersion resolvedNodePath -firefoxGcInterval\ +firefoxGcInterval +retries `) // NOTE: If you add a config value, make sure to update the following @@ -180,6 +181,7 @@ const CONFIG_DEFAULTS = { experimentalSourceRewriting: false, experimentalShadowDomSupport: false, experimentalFetchPolyfill: false, + retries: { runMode: 0, openMode: 0 }, } const validationRules = { @@ -228,6 +230,7 @@ const validationRules = { experimentalSourceRewriting: v.isBoolean, experimentalShadowDomSupport: v.isBoolean, experimentalFetchPolyfill: v.isBoolean, + retries: v.isValidRetriesConfig, } const convertRelativeToAbsolutePaths = (projectRoot, obj, defaults = {}) => { diff --git a/packages/server/lib/errors.js b/packages/server/lib/errors.js index 509a50118dfe..fe9e18f81abd 100644 --- a/packages/server/lib/errors.js +++ b/packages/server/lib/errors.js @@ -925,9 +925,19 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { If you don't require screenshots or videos to be stored you can safely ignore this warning.` case 'EXPERIMENTAL_SAMESITE_REMOVED': return stripIndent`\ - The \`experimentalGetCookiesSameSite\` configuration option was removed in Cypress version 5.0.0. Yielding the \`sameSite\` property is now the default behavior of the \`cy.cookie\` commands. + The \`experimentalGetCookiesSameSite\` configuration option was removed in Cypress version \`5.0.0\`. Yielding the \`sameSite\` property is now the default behavior of the \`cy.cookie\` commands. You can safely remove this option from your config.` + case 'INCOMPATIBLE_PLUGIN_RETRIES': + return stripIndent`\ + We've detected that the incompatible plugin \`cypress-plugin-retries\` is installed at \`${arg1}\`. + + Test retries is now supported in Cypress version \`5.0.0\`. + + Remove the plugin from your dependencies to silence this warning. + + https://on.cypress.io/test-retries + ` default: } } diff --git a/packages/server/lib/modes/record.js b/packages/server/lib/modes/record.js index ea0023bdf20a..a74d858f6841 100644 --- a/packages/server/lib/modes/record.js +++ b/packages/server/lib/modes/record.js @@ -227,7 +227,6 @@ const updateInstance = (options = {}) => { error, video, hooks, - stdout: null, // don't send stdout with the instance payload to prevent requests that are too large. stdout will later get uploaded separately anyway. instanceId, screenshots, reporterStats, diff --git a/packages/server/lib/modes/run.js b/packages/server/lib/modes/run.js index cc8bb57c46f6..50155414ebe6 100644 --- a/packages/server/lib/modes/run.js +++ b/packages/server/lib/modes/run.js @@ -916,7 +916,16 @@ module.exports = { browserOpts.automationMiddleware = { onAfterResponse: (message, data, resp) => { if (message === 'take:screenshot' && resp) { - screenshots.push(this.screenshotMetadata(data, resp)) + const existingScreenshot = _.findIndex(screenshots, { path: resp.path }) + + if (existingScreenshot !== -1) { + // NOTE: saving screenshots to the same path will overwrite the previous one + // so we shouldn't report more screenshots than exist on disk. + // this happens when cy.screenshot is used in a retried test + screenshots.splice(existingScreenshot, 1, this.screenshotMetadata(data, resp)) + } else { + screenshots.push(this.screenshotMetadata(data, resp)) + } } return resp @@ -1112,12 +1121,14 @@ module.exports = { const { tests, stats } = obj + const attempts = _.flatMap(tests, (test) => test.attempts) + 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) + Reporter.setVideoTimestamp(startedVideoCapture, attempts) } // we should upload the video if we upload on passes (by default) @@ -1160,6 +1171,7 @@ module.exports = { screenshotId: random.id(), name: data.name || null, testId: data.testId, + testAttemptIndex: data.testAttemptIndex, takenAt: resp.takenAt, path: resp.path, height: resp.dimensions.height, diff --git a/packages/server/lib/plugins/index.js b/packages/server/lib/plugins/index.js index f40cf579b0c0..d9a2f3b271cc 100644 --- a/packages/server/lib/plugins/index.js +++ b/packages/server/lib/plugins/index.js @@ -2,6 +2,7 @@ const _ = require('lodash') const cp = require('child_process') const path = require('path') const debug = require('debug')('cypress:server:plugins') +const resolve = require('resolve') const Promise = require('bluebird') const errors = require('../errors') const util = require('./util') @@ -38,6 +39,17 @@ const registerHandler = (handler) => { const init = (config, options) => { debug('plugins.init', config.pluginsFile) + // test and warn for incompatible plugin + try { + const retriesPluginPath = path.dirname(resolve.sync('cypress-plugin-retries', { + basedir: options.projectRoot, + })) + + options.onWarning(errors.get('INCOMPATIBLE_PLUGIN_RETRIES', path.relative(options.projectRoot, retriesPluginPath))) + } catch (e) { + // noop, incompatible plugin not installed + } + return new Promise((_resolve, _reject) => { // provide a safety net for fulfilling the promise because the // 'handleError' function below can potentially be triggered diff --git a/packages/server/lib/reporter.js b/packages/server/lib/reporter.js index b702a390734a..84119a80fd73 100644 --- a/packages/server/lib/reporter.js +++ b/packages/server/lib/reporter.js @@ -1,11 +1,13 @@ const _ = require('lodash') const path = require('path') +const stackUtils = require('./util/stack_utils') // mocha-* is used to allow us to have later versions of mocha specified in devDependencies // and prevents accidently upgrading this one // TODO: look into upgrading this to version in driver const Mocha = require('mocha-7.0.1') const mochaReporters = require('mocha-7.0.1/lib/reporters') const mochaCreateStatsCollector = require('mocha-7.0.1/lib/stats-collector') +const mochaColor = mochaReporters.Base.color const debug = require('debug')('cypress:server:reporter') const Promise = require('bluebird') @@ -99,6 +101,10 @@ const createRunnable = function (obj, parent) { runnable.sync = obj.sync runnable.duration = obj.duration runnable.state = obj.state != null ? obj.state : 'skipped' // skipped by default + runnable._retries = obj._retries + // shouldn't need to set _currentRetry, but we'll do it anyways + runnable._currentRetry = obj._currentRetry + if (runnable.body == null) { runnable.body = body } @@ -110,10 +116,42 @@ const createRunnable = function (obj, parent) { return runnable } +const mochaProps = { + 'currentRetry': '_currentRetry', + 'retries': '_retries', +} + +const toMochaProps = (testProps) => { + return _.each(mochaProps, (val, key) => { + if (testProps.hasOwnProperty(key)) { + testProps[val] = testProps[key] + + return delete testProps[key] + } + }) +} + const mergeRunnable = (eventName) => { return (function (testProps, runnables) { + toMochaProps(testProps) + const runnable = runnables[testProps.id] + if (eventName === 'test:before:run') { + if (testProps._currentRetry > runnable._currentRetry) { + debug('test retried:', testProps.title) + const prevAttempts = runnable.prevAttempts || [] + + delete runnable.prevAttempts + const prevAttempt = _.cloneDeep(runnable) + + delete runnable.failedFromHookId + delete runnable.err + delete runnable.hookName + testProps.prevAttempts = prevAttempts.concat([prevAttempt]) + } + } + return _.extend(runnable, testProps) }) } @@ -172,6 +210,12 @@ const setDate = function (obj, runnables, stats) { return null } +const orNull = function (prop) { + if (prop == null) return null + + return prop +} + const events = { 'start': setDate, 'end': setDate, @@ -180,11 +224,13 @@ const events = { 'test': mergeRunnable('test'), 'test end': mergeRunnable('test end'), 'hook': safelyMergeRunnable, + 'retry': true, 'hook end': safelyMergeRunnable, 'pass': mergeRunnable('pass'), 'pending': mergeRunnable('pending'), 'fail': mergeErr, 'test:after:run': mergeRunnable('test:after:run'), // our own custom event + 'test:before:run': mergeRunnable('test:before:run'), // our own custom event } const reporters = { @@ -201,6 +247,7 @@ class Reporter { this.reporterName = reporterName this.projectRoot = projectRoot this.reporterOptions = reporterOptions + this.normalizeTest = this.normalizeTest.bind(this) } setRunnables (rootRunnable) { @@ -219,6 +266,18 @@ class Reporter { this.runner = new Mocha.Runner(rootRunnable) mochaCreateStatsCollector(this.runner) + if (this.reporterName === 'spec') { + this.runner.on('retry', (test) => { + const runnable = this.runnables[test.id] + const padding = ' '.repeat(runnable.titlePath().length) + const retryMessage = mochaColor('medium', `(Attempt ${test.currentRetry + 1} of ${test.retries + 1})`) + + // Log: `(Attempt 1 of 2) test title` when a test retries + // eslint-disable-next-line no-console + return console.log(`${padding}${retryMessage} ${test.title}`) + }) + } + this.reporter = new this.mocha._reporter(this.runner, { reporterOptions: this.reporterOptions, }) @@ -260,7 +319,7 @@ class Reporter { args = this.parseArgs(event, args) if (args) { - return (this.runner != null ? this.runner.emit.apply(this.runner, args) : undefined) + return this.runner && this.runner.emit.apply(this.runner, args) } } @@ -292,39 +351,32 @@ class Reporter { } normalizeTest (test = {}) { - let wcs - const get = (prop) => { - return _.get(test, prop, null) - } - - // use this or null - wcs = get('wallClockStartedAt') - - if (wcs) { - // convert to actual date object - wcs = new Date(wcs) - } - - // wallClockDuration: - // this is the 'real' duration of wall clock time that the - // user 'felt' when the test run. it includes everything - // from hooks, to the test itself, to lifecycle, and event - // async browser compute time. this number is likely higher - // than summing the durations of the timings. - // - return { - testId: get('id'), + const normalizedTest = { + testId: orNull(test.id), title: getParentTitle(test), - state: get('state'), - body: get('body'), - stack: get('err.stack'), - error: get('err.message'), - timings: get('timings'), - failedFromHookId: get('failedFromHookId'), - wallClockStartedAt: wcs, - wallClockDuration: get('wallClockDuration'), - videoTimestamp: null, // always start this as null + state: orNull(test.state), + body: orNull(test.body), + displayError: orNull(test.err && test.err.stack), + attempts: _.map([test].concat(test.prevAttempts || []), (attempt) => { + const err = attempt.err && { + name: attempt.err.name, + message: attempt.err.message, + stack: stackUtils.stackWithoutMessage(attempt.err.stack), + } + + return { + state: orNull(attempt.state), + error: orNull(err), + timings: orNull(attempt.timings), + failedFromHookId: orNull(attempt.failedFromHookId), + wallClockStartedAt: orNull(attempt.wallClockStartedAt && new Date(attempt.wallClockStartedAt)), + wallClockDuration: orNull(attempt.wallClockDuration), + videoTimestamp: null, + } + }), } + + return normalizedTest } end () { diff --git a/packages/server/lib/screenshots.js b/packages/server/lib/screenshots.js index d5974c6991bf..52e84ffe7b49 100644 --- a/packages/server/lib/screenshots.js +++ b/packages/server/lib/screenshots.js @@ -340,6 +340,10 @@ const getPath = function (data, ext, screenshotsFolder) { names[index] = `${names[index]} (failed)` } + if (data.testAttemptIndex > 0) { + names[index] = `${names[index]} (attempt ${data.testAttemptIndex + 1})` + } + const withoutExt = path.join(screenshotsFolder, ...specNames, ...names) return ensureUniquePath(withoutExt, ext) @@ -484,7 +488,7 @@ module.exports = { const duration = new Date() - new Date(data.startTime) details = _.extend({}, data, details, { duration }) - details = _.pick(details, 'size', 'takenAt', 'dimensions', 'multipart', 'pixelRatio', 'name', 'specName', 'testFailure', 'path', 'scaled', 'blackout', 'duration') + details = _.pick(details, 'testAttemptIndex', 'size', 'takenAt', 'dimensions', 'multipart', 'pixelRatio', 'name', 'specName', 'testFailure', 'path', 'scaled', 'blackout', 'duration') if (!plugins.has('after:screenshot')) { return Promise.resolve(details) diff --git a/packages/server/lib/util/stack_utils.ts b/packages/server/lib/util/stack_utils.ts new file mode 100644 index 000000000000..1018a367a2a5 --- /dev/null +++ b/packages/server/lib/util/stack_utils.ts @@ -0,0 +1,44 @@ +import _ from 'lodash' + +const stackLineRegex = /^\s*(at )?.*@?\(?.*\:\d+\:\d+\)?$/ + +// returns tuple of [message, stack] +export const splitStack = (stack: string) => { + const lines = stack.split('\n') + + return _.reduce(lines, (memo, line) => { + if (memo.messageEnded || stackLineRegex.test(line)) { + memo.messageEnded = true + memo[1].push(line) + } else { + memo[0].push(line) + } + + return memo + }, [[], []] as any[] & {messageEnded: boolean}) +} + +export const unsplitStack = (messageLines, stackLines) => { + return _.castArray(messageLines).concat(stackLines).join('\n') +} + +export const getStackLines = (stack) => { + const [, stackLines] = splitStack(stack) + + return stackLines +} + +export const stackWithoutMessage = (stack) => { + return getStackLines(stack).join('\n') +} + +export const replacedStack = (err, newStack) => { + // if err already lacks a stack or we've removed the stack + // for some reason, keep it stackless + if (!err.stack) return err.stack + + const errString = err.toString() + const stackLines = getStackLines(newStack) + + return unsplitStack(errString, stackLines) +} diff --git a/packages/server/lib/util/validation.js b/packages/server/lib/util/validation.js index 3dfcb9b13264..72f25304797d 100644 --- a/packages/server/lib/util/validation.js +++ b/packages/server/lib/util/validation.js @@ -103,6 +103,21 @@ const isValidBrowserList = (key, browsers) => { return true } +const isValidRetriesConfig = (key, value) => { + const isNullOrNumber = isOneOf([_.isNumber, _.isNull]) + + if ( + isNullOrNumber(value) + || (_.isEqual(_.keys(value), ['runMode', 'openMode'])) + && isNullOrNumber(value.runMode) + && isNullOrNumber(value.openMode) + ) { + return true + } + + return errMsg(key, value, 'a number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls') +} + const isValidFirefoxGcInterval = (key, value) => { const isIntervalValue = (val) => { if (isNumber(val)) { @@ -122,6 +137,24 @@ const isValidFirefoxGcInterval = (key, value) => { return errMsg(key, value, 'a positive number or null or an object with "openMode" and "runMode" as keys and positive numbers or nulls as values') } +const isOneOf = (...values) => { + return (key, value) => { + if (values.some((v) => { + if (typeof value === 'function') { + return value(v) + } + + return v === value + })) { + return true + } + + const strings = values.map(str).join(', ') + + return errMsg(key, value, `one of these values: ${strings}`) + } +} + module.exports = { isValidBrowser, @@ -129,6 +162,8 @@ module.exports = { isValidFirefoxGcInterval, + isValidRetriesConfig, + isNumber (key, value) { if (value == null || isNumber(value)) { return true @@ -214,17 +249,5 @@ module.exports = { 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}`) - } - }, + isOneOf, } diff --git a/packages/server/package.json b/packages/server/package.json index dd1141e14a65..2b23e600ea82 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -124,7 +124,7 @@ "@babel/core": "7.9.0", "@babel/preset-env": "7.9.0", "@cypress/debugging-proxy": "2.0.1", - "@cypress/json-schemas": "5.34.2", + "@cypress/json-schemas": "5.35.0", "@cypress/sinon-chai": "1.1.0", "@packages/desktop-gui": "*", "@packages/electron": "*", diff --git a/packages/server/test/e2e/1_caught_uncaught_hook_errors_spec.js b/packages/server/test/e2e/1_caught_uncaught_hook_errors_spec.js index c629420088c6..f292ed4678fc 100644 --- a/packages/server/test/e2e/1_caught_uncaught_hook_errors_spec.js +++ b/packages/server/test/e2e/1_caught_uncaught_hook_errors_spec.js @@ -8,35 +8,27 @@ describe('e2e caught and uncaught hooks errors', () => { }, }) - it('failing1', function () { - return e2e.exec(this, { - spec: 'hook_caught_error_failing_spec.coffee', - snapshot: true, - expectedExitCode: 3, - }) + e2e.it('failing1', { + spec: 'hook_caught_error_failing_spec.coffee', + snapshot: true, + expectedExitCode: 3, }) - it('failing2', function () { - return e2e.exec(this, { - spec: 'hook_uncaught_error_failing_spec.coffee', - snapshot: true, - expectedExitCode: 1, - }) + e2e.it('failing2', { + spec: 'hook_uncaught_error_failing_spec.coffee', + snapshot: true, + expectedExitCode: 1, }) - it('failing3', function () { - return e2e.exec(this, { - spec: 'hook_uncaught_root_error_failing_spec.coffee', - snapshot: true, - expectedExitCode: 1, - }) + e2e.it('failing3', { + spec: 'hook_uncaught_root_error_failing_spec.coffee', + snapshot: true, + expectedExitCode: 1, }) - it('failing4', function () { - return e2e.exec(this, { - spec: 'hook_uncaught_error_events_failing_spec.coffee', - snapshot: true, - expectedExitCode: 1, - }) + e2e.it('failing4', { + spec: 'hook_uncaught_error_events_failing_spec.coffee', + snapshot: true, + expectedExitCode: 1, }) }) diff --git a/packages/server/test/e2e/3_plugins_spec.js b/packages/server/test/e2e/3_plugins_spec.js index c218039ed88c..c7428f701533 100644 --- a/packages/server/test/e2e/3_plugins_spec.js +++ b/packages/server/test/e2e/3_plugins_spec.js @@ -150,6 +150,15 @@ describe('e2e plugins', function () { }) }) + // https://github.com/cypress-io/cypress/issues/8079 + it('does not report more screenshots than exist if user overwrites previous screenshot in afterScreenshot', function () { + return e2e.exec(this, { + spec: 'after_screenshot_overwrite_spec.coffee', + project: pluginAfterScreenshot, + snapshot: true, + }) + }) + it('fails when invalid event is registered', function () { return e2e.exec(this, { spec: 'app_spec.js', diff --git a/packages/server/test/e2e/3_retries_spec.ts b/packages/server/test/e2e/3_retries_spec.ts new file mode 100644 index 000000000000..1129093470a3 --- /dev/null +++ b/packages/server/test/e2e/3_retries_spec.ts @@ -0,0 +1,21 @@ +import e2e from '../support/helpers/e2e' +import Fixtures from '../support/helpers/fixtures' + +const it = e2e.it + +describe('retries', () => { + e2e.setup() + + it('supports retries', { + project: Fixtures.projectPath('retries-2'), + spec: 'fail-twice.js', + snapshot: true, + }) + + it('warns about retries plugin', { + project: Fixtures.projectPath('plugin-retries'), + spec: 'main.spec.js', + stubPackage: 'cypress-plugin-retries', + snapshot: true, + }) +}) diff --git a/packages/server/test/e2e/3_runnable_execution_spec.ts b/packages/server/test/e2e/3_runnable_execution_spec.ts index 361475a13526..769d05ba9d4b 100644 --- a/packages/server/test/e2e/3_runnable_execution_spec.ts +++ b/packages/server/test/e2e/3_runnable_execution_spec.ts @@ -24,7 +24,7 @@ describe('e2e runnable execution', () => { project: Fixtures.projectPath('hooks-after-rerun'), spec: 'beforehook-and-test-navigation.js', snapshot: true, - expectedExitCode: 1, + expectedExitCode: 2, }) e2e.it('runnables run correct number of times with navigation', { diff --git a/packages/server/test/e2e/5_screenshots_spec.js b/packages/server/test/e2e/5_screenshots_spec.js index 3862dd90ddba..7dafd8a1d2b1 100644 --- a/packages/server/test/e2e/5_screenshots_spec.js +++ b/packages/server/test/e2e/5_screenshots_spec.js @@ -63,7 +63,7 @@ describe('e2e screenshots', () => { // the test title as the file name e2e.it('passes', { spec: 'screenshots_spec.js', - expectedExitCode: 4, + expectedExitCode: 5, snapshot: true, timeout: 180000, onStdout: e2e.normalizeWebpackErrors, diff --git a/packages/server/test/e2e/5_spec_isolation_spec.js b/packages/server/test/e2e/5_spec_isolation_spec.js index 9756d610294a..4eb22675fbb5 100644 --- a/packages/server/test/e2e/5_spec_isolation_spec.js +++ b/packages/server/test/e2e/5_spec_isolation_spec.js @@ -90,9 +90,12 @@ const expectRunsToHaveCorrectStats = (runs = []) => { expectStartToBeBeforeEnd(run, 'stats.wallClockStartedAt', 'stats.wallClockEndedAt') expectStartToBeBeforeEnd(run, 'reporterStats.start', 'reporterStats.end') - // grab all the wallclock durations for all tests + // grab all the wallclock durations for all test (and retried attempts) // because our duration should be at least this - const wallClocks = _.sumBy(run.tests, 'wallClockDuration') + + const attempts = _.flatMap(run.tests, (test) => test.attempts) + + const wallClocks = _.sumBy(attempts, 'wallClockDuration') // ensure each run's duration is around the sum // of all tests wallclock duration @@ -100,7 +103,7 @@ const expectRunsToHaveCorrectStats = (runs = []) => { run, 'stats.wallClockDuration', wallClocks, - wallClocks + 200, // add 200ms to account for padding + wallClocks + 400, // add 400ms to account for padding 1234, ) @@ -108,7 +111,7 @@ const expectRunsToHaveCorrectStats = (runs = []) => { run, 'reporterStats.duration', wallClocks, - wallClocks + 200, // add 200ms to account for padding + wallClocks + 400, // add 400ms to account for padding 1234, ) @@ -118,11 +121,17 @@ const expectRunsToHaveCorrectStats = (runs = []) => { run.spec.absolute = e2e.normalizeStdout(run.spec.absolute) + _.each(run.tests, (test) => { + if (test.displayError) { + test.displayError = e2e.normalizeStdout(test.displayError) + } + }) + // now make sure that each tests wallclock duration // is around the sum of all of its timings - run.tests.forEach((test) => { + attempts.forEach((attempt) => { // cannot sum an object, must use array of values - const timings = _.sumBy(_.values(test.timings), (val) => { + const timings = _.sumBy(_.values(attempt.timings), (val) => { if (_.isArray(val)) { // array for hooks return _.sumBy(val, addFnAndAfterFn) @@ -137,7 +146,7 @@ const expectRunsToHaveCorrectStats = (runs = []) => { }) expectDurationWithin( - test, + attempt, 'wallClockDuration', timings, timings + 80, // add 80ms to account for padding @@ -145,21 +154,21 @@ const expectRunsToHaveCorrectStats = (runs = []) => { ) // now reset all the test timings - normalizeTestTimings(test, 'timings') + normalizeTestTimings(attempt, 'timings') // normalize stack - if (test.stack) { - test.stack = e2e.normalizeStdout(test.stack) + if (attempt.error) { + attempt.error.stack = e2e.normalizeStdout(attempt.error.stack).trim() } - if (test.wallClockStartedAt) { - const d = new Date(test.wallClockStartedAt) + if (attempt.wallClockStartedAt) { + const d = new Date(attempt.wallClockStartedAt) - expect(d.toJSON()).to.eq(test.wallClockStartedAt) - test.wallClockStartedAt = STATIC_DATE + expect(d.toJSON()).to.eq(attempt.wallClockStartedAt) + attempt.wallClockStartedAt = STATIC_DATE - expect(test.videoTimestamp).to.be.a('number') - test.videoTimestamp = 9999 + expect(attempt.videoTimestamp).to.be.a('number') + attempt.videoTimestamp = 9999 } }) @@ -252,4 +261,46 @@ describe('e2e spec_isolation', () => { }) }, }) + + e2e.it('failing with retries enabled', { + spec: 'simple_failing_hook_spec.coffee', + outputPath, + snapshot: true, + expectedExitCode: 3, + config: { + retries: 1, + }, + async onRun (execFn) { + await execFn() + const json = await fs.readJsonAsync(outputPath) + + expect(json.config).to.be.an('object') + expect(json.config.projectName).to.eq('e2e') + expect(json.config.projectRoot).to.eq(e2ePath) + 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', + }) + + expect(json.totalTests).to.eq(_.sum([json.totalFailed, json.totalPassed, json.totalPending, json.totalSkipped])) + expectStartToBeBeforeEnd(json, 'startedTestsAt', 'endedTestsAt') + expectDurationWithin(json, 'totalDuration', _.sumBy(json.runs, 'stats.wallClockDuration'), _.sumBy(json.runs, 'stats.wallClockDuration'), 5555) + expect(json.runs).to.have.length(1) + expectRunsToHaveCorrectStats(json.runs) + + snapshot('failing with retries enabled', json) + }, + }) }) diff --git a/packages/server/test/e2e/7_record_spec.js b/packages/server/test/e2e/7_record_spec.js index 51bd2bdebd2f..3a98528e9015 100644 --- a/packages/server/test/e2e/7_record_spec.js +++ b/packages/server/test/e2e/7_record_spec.js @@ -192,7 +192,7 @@ const defaultRoutes = [ }, { method: 'put', url: '/instances/:id', - req: 'putInstanceRequest@2.0.0', + req: 'putInstanceRequest@3.0.0', resSchema: 'putInstanceResponse@2.0.0', res: sendUploadUrls, }, { @@ -239,6 +239,7 @@ describe('e2e record', () => { }) .get('stdout') .then((stdout) => { + console.log(stdout) expect(stdout).to.include('Run URL:') expect(stdout).to.include(runUrl) @@ -260,6 +261,7 @@ describe('e2e record', () => { // grab the second set of 5 const secondInstanceSet = urls.slice(5, 10) + console.log(secondInstanceSet) expect(secondInstanceSet).to.have.members([ `POST /runs/${runId}/instances`, `PUT /instances/${instanceId}`, @@ -338,7 +340,7 @@ describe('e2e record', () => { expect(secondInstancePut.body.error).to.be.null expect(secondInstancePut.body.tests).to.have.length(2) - expect(secondInstancePut.body.hooks).to.have.length(2) + expect(secondInstancePut.body.hooks).to.have.length(1) expect(secondInstancePut.body.screenshots).to.have.length(1) expect(secondInstancePut.body.stats.tests).to.eq(2) expect(secondInstancePut.body.stats.failures).to.eq(1) @@ -362,7 +364,7 @@ describe('e2e record', () => { expect(thirdInstancePut.body.error).to.be.null expect(thirdInstancePut.body.tests).to.have.length(2) - expect(thirdInstancePut.body.hooks).to.have.length(1) + expect(thirdInstancePut.body.hooks).to.have.length(0) expect(thirdInstancePut.body.screenshots).to.have.length(1) expect(thirdInstancePut.body.stats.tests).to.eq(2) expect(thirdInstancePut.body.stats.passes).to.eq(1) @@ -387,7 +389,7 @@ describe('e2e record', () => { expect(fourthInstancePut.body.error).to.be.null expect(fourthInstancePut.body.tests).to.have.length(1) - expect(fourthInstancePut.body.hooks).to.have.length(1) + expect(fourthInstancePut.body.hooks).to.have.length(0) expect(fourthInstancePut.body.screenshots).to.have.length(1) expect(fourthInstancePut.body.stats.tests).to.eq(1) expect(fourthInstancePut.body.stats.failures).to.eq(1) @@ -869,7 +871,7 @@ describe('e2e record', () => { routes[2] = { method: 'put', url: '/instances/:id', - req: 'putInstanceRequest@2.0.0', + req: 'putInstanceRequest@3.0.0', res (req, res) { return res.sendStatus(500) }, @@ -1169,7 +1171,7 @@ describe('e2e record', () => { }, { method: 'put', url: '/instances/:id', - req: 'putInstanceRequest@2.0.0', + req: 'putInstanceRequest@3.0.0', res (req, res) { return res.sendStatus(500) }, @@ -1216,7 +1218,7 @@ describe('e2e record', () => { }, { method: 'put', url: '/instances/:id', - req: 'putInstanceRequest@2.0.0', + req: 'putInstanceRequest@3.0.0', resSchema: 'putInstanceResponse@2.0.0', res: sendUploadUrls, }, { @@ -1287,7 +1289,7 @@ describe('e2e record', () => { }, { method: 'put', url: '/instances/:id', - req: 'putInstanceRequest@2.0.0', + req: 'putInstanceRequest@3.0.0', resSchema: 'putInstanceResponse@2.0.0', res: sendUploadUrls, }, { diff --git a/packages/server/test/e2e/8_reporters_spec.js b/packages/server/test/e2e/8_reporters_spec.js index c22481e8009c..f2349eb97cc9 100644 --- a/packages/server/test/e2e/8_reporters_spec.js +++ b/packages/server/test/e2e/8_reporters_spec.js @@ -117,7 +117,7 @@ describe('e2e reporters', () => { .then((xml) => { expect(xml).to.include('

    simple failing hook spec

    ') - expect(xml).to.include('
    3 Failed Hooks
    ') + expect(xml).to.not.include('.status-item-hooks') }) } @@ -125,10 +125,12 @@ describe('e2e reporters', () => { .then((json) => { // mochawesome does not consider hooks to be // 'failures' but it does collect them in 'other' + // HOWEVER we now change how mocha events fire to make mocha stats reflect ours expect(json.stats).to.be.an('object') - expect(json.stats.failures).to.eq(0) - - expect(json.stats.other).to.eq(3) + expect(json.stats.passes).to.eq(1) + expect(json.stats.failures).to.eq(3) + expect(json.stats.skipped).to.eq(1) + expect(json.stats.other).to.eq(0) }) }) }) diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress.json b/packages/server/test/support/fixtures/projects/e2e/cypress.json index 9e26dfeeb6e6..0f77b44749ce 100644 --- a/packages/server/test/support/fixtures/projects/e2e/cypress.json +++ b/packages/server/test/support/fixtures/projects/e2e/cypress.json @@ -1 +1,3 @@ -{} \ No newline at end of file +{ + "retries": null +} 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 index 64645d0fe7ef..6718492889f2 100644 --- 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 @@ -163,6 +163,13 @@ describe('taking screenshots', () => { }) }) + it('screenshots in a retried test', { retries: 2 }, () => { + cy.screenshot('retrying-test') + .then(() => { + throw new Error('fail') + }) + }) + it('ensures unique paths for non-named screenshots', () => { cy.screenshot({ capture: 'runner' }) cy.screenshot({ capture: 'runner' }) 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 35d8fca9fd01..b88d7624d7c3 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 @@ -6,6 +6,7 @@ const http = require('http') const Jimp = require('jimp') const path = require('path') const Promise = require('bluebird') +const { useFixedFirefoxResolution } = require('../../../utils') module.exports = (on, config) => { let performance = { @@ -45,13 +46,7 @@ module.exports = (on, config) => { }) on('before:browser:launch', (browser, options) => { - if (browser.family === 'firefox' && !config.env['NO_RESIZE']) { - // this is needed to ensure correct error screenshot / video recording - // resolution of exactly 1280x720 (height must account for firefox url bar) - options.args = options.args.concat( - ['-width', '1280', '-height', '794'], - ) - } + useFixedFirefoxResolution(browser, options, config) if (browser.family === 'firefox' && process.env.FIREFOX_FORCE_STRICT_SAMESITE) { // @see https://www.jardinesoftware.net/2019/10/28/samesite-by-default-in-2020/ 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 27e017398261..7d300bff722d 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 @@ -1,5 +1,8 @@ -before(function () { - if (Cypress.browser.family === 'chromium' && Cypress.browser.name !== 'electron') { +import _ from 'lodash' + +// we don't use a `before` here since that would show up in run results and cause confusion during test debugging +const before = _.once(function () { + if (Cypress.isBrowser([{ name: '!electron', family: 'chromium' }])) { return Cypress.automation('remote:debugger:protocol', { command: 'Emulation.setDeviceMetricsOverride', params: { @@ -19,3 +22,5 @@ before(function () { }) } }) + +Cypress.on('test:before:run:async', before) diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/support/util.js b/packages/server/test/support/fixtures/projects/e2e/cypress/support/util.js index 2ae8eb8fe3a2..da5ebf6b0ccd 100644 --- a/packages/server/test/support/fixtures/projects/e2e/cypress/support/util.js +++ b/packages/server/test/support/fixtures/projects/e2e/cypress/support/util.js @@ -85,7 +85,7 @@ export const verify = (ctx, options) => { // code frames will show `fail(this,()=>` as the 1st line cy.get('.test-err-code-frame pre span').should('include.text', 'fail(this,()=>') - cy.contains('.test-err-code-frame .runnable-err-file-path', openInIdePath.relative) + cy.contains('.test-err-code-frame .runnable-err-file-path span', openInIdePath.relative) .click() .should(() => { expect(runnerWs.emit.withArgs('open:file')).to.be.calledTwice diff --git a/packages/server/test/support/fixtures/projects/hooks-after-rerun/cypress/integration/beforehook-and-test-navigation.js b/packages/server/test/support/fixtures/projects/hooks-after-rerun/cypress/integration/beforehook-and-test-navigation.js index e1b1b31523a6..9b2bebc88e26 100644 --- a/packages/server/test/support/fixtures/projects/hooks-after-rerun/cypress/integration/beforehook-and-test-navigation.js +++ b/packages/server/test/support/fixtures/projects/hooks-after-rerun/cypress/integration/beforehook-and-test-navigation.js @@ -23,3 +23,15 @@ describe('suite', () => { cy.visit(urls[2]) }) }) + +describe('navigation error in beforeEach', () => { + before(() => { + cy.visit(urls[1]) + }) + + beforeEach(() => { + cy.visit(urls[2]) + }) + + it('never gets here', () => {}) +}) diff --git a/packages/server/test/support/fixtures/projects/plugin-after-screenshot/cypress/integration/after_screenshot_overwrite_spec.coffee b/packages/server/test/support/fixtures/projects/plugin-after-screenshot/cypress/integration/after_screenshot_overwrite_spec.coffee new file mode 100644 index 000000000000..600d04a52b56 --- /dev/null +++ b/packages/server/test/support/fixtures/projects/plugin-after-screenshot/cypress/integration/after_screenshot_overwrite_spec.coffee @@ -0,0 +1,8 @@ +Cypress._.times 3, () => + it "cy.screenshot() - replacement", -> + cy.screenshot("replace-me", { capture: "runner" }, { + onAfterScreenshot: (details) -> + expect(details.path).to.include("screenshot-replacement.png") + expect(details.size).to.equal(1047) + expect(details.dimensions).to.eql({ width: 1, height: 1 }) + }) diff --git a/packages/server/test/support/fixtures/projects/plugin-retries/cypress.json b/packages/server/test/support/fixtures/projects/plugin-retries/cypress.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/packages/server/test/support/fixtures/projects/plugin-retries/cypress.json @@ -0,0 +1 @@ +{} diff --git a/packages/server/test/support/fixtures/projects/plugin-retries/cypress/integration/main.spec.js b/packages/server/test/support/fixtures/projects/plugin-retries/cypress/integration/main.spec.js new file mode 100644 index 000000000000..ba620e423491 --- /dev/null +++ b/packages/server/test/support/fixtures/projects/plugin-retries/cypress/integration/main.spec.js @@ -0,0 +1,2 @@ +it('foo', () => { +}) diff --git a/packages/server/test/support/fixtures/projects/read-only-project-root/cypress.json b/packages/server/test/support/fixtures/projects/read-only-project-root/cypress.json index 0967ef424bce..0f77b44749ce 100644 --- a/packages/server/test/support/fixtures/projects/read-only-project-root/cypress.json +++ b/packages/server/test/support/fixtures/projects/read-only-project-root/cypress.json @@ -1 +1,3 @@ -{} +{ + "retries": null +} diff --git a/packages/server/test/support/fixtures/projects/retries-2/cypress.json b/packages/server/test/support/fixtures/projects/retries-2/cypress.json new file mode 100644 index 000000000000..cd6342eef42e --- /dev/null +++ b/packages/server/test/support/fixtures/projects/retries-2/cypress.json @@ -0,0 +1,3 @@ +{ + "retries": 2 +} diff --git a/packages/server/test/support/fixtures/projects/retries-2/cypress/integration/fail-twice.js b/packages/server/test/support/fixtures/projects/retries-2/cypress/integration/fail-twice.js new file mode 100644 index 000000000000..214e7233b376 --- /dev/null +++ b/packages/server/test/support/fixtures/projects/retries-2/cypress/integration/fail-twice.js @@ -0,0 +1,8 @@ +let count = 0 + +it('fail twice', () => { + count++ + if (count < 3) { + throw new Error(`failed attempt #${count}`) + } +}) diff --git a/packages/server/test/support/fixtures/projects/retries-2/cypress/plugins/index.js b/packages/server/test/support/fixtures/projects/retries-2/cypress/plugins/index.js new file mode 100644 index 000000000000..27a4eac6e871 --- /dev/null +++ b/packages/server/test/support/fixtures/projects/retries-2/cypress/plugins/index.js @@ -0,0 +1,14 @@ +/// + +const { useFixedFirefoxResolution } = require('../../../utils') + +/** + * @type {Cypress.PluginConfig} + */ +module.exports = (on, config) => { + on('before:browser:launch', (browser, options) => { + useFixedFirefoxResolution(browser, options, config) + + return options + }) +} diff --git a/packages/server/test/support/fixtures/projects/task-not-registered/cypress.json b/packages/server/test/support/fixtures/projects/task-not-registered/cypress.json index 0967ef424bce..0f77b44749ce 100644 --- a/packages/server/test/support/fixtures/projects/task-not-registered/cypress.json +++ b/packages/server/test/support/fixtures/projects/task-not-registered/cypress.json @@ -1 +1,3 @@ -{} +{ + "retries": null +} diff --git a/packages/server/test/support/fixtures/projects/utils.js b/packages/server/test/support/fixtures/projects/utils.js new file mode 100644 index 000000000000..71f37842f5f3 --- /dev/null +++ b/packages/server/test/support/fixtures/projects/utils.js @@ -0,0 +1,11 @@ +module.exports = { + useFixedFirefoxResolution (browser, options, config) { + if (browser.family === 'firefox' && !config.env['NO_RESIZE']) { + // this is needed to ensure correct error screenshot / video recording + // resolution of exactly 1280x720 (height must account for firefox url bar) + options.args = options.args.concat( + ['-width', '1280', '-height', '794'], + ) + } + }, +} diff --git a/packages/server/test/support/fixtures/projects/webpack-preprocessor-awesome-typescript-loader/cypress.json b/packages/server/test/support/fixtures/projects/webpack-preprocessor-awesome-typescript-loader/cypress.json index 0967ef424bce..0f77b44749ce 100644 --- a/packages/server/test/support/fixtures/projects/webpack-preprocessor-awesome-typescript-loader/cypress.json +++ b/packages/server/test/support/fixtures/projects/webpack-preprocessor-awesome-typescript-loader/cypress.json @@ -1 +1,3 @@ -{} +{ + "retries": null +} diff --git a/packages/server/test/support/fixtures/projects/webpack-preprocessor-ts-loader-compiler-options/cypress.json b/packages/server/test/support/fixtures/projects/webpack-preprocessor-ts-loader-compiler-options/cypress.json index 0967ef424bce..0f77b44749ce 100644 --- a/packages/server/test/support/fixtures/projects/webpack-preprocessor-ts-loader-compiler-options/cypress.json +++ b/packages/server/test/support/fixtures/projects/webpack-preprocessor-ts-loader-compiler-options/cypress.json @@ -1 +1,3 @@ -{} +{ + "retries": null +} diff --git a/packages/server/test/support/fixtures/projects/webpack-preprocessor-ts-loader/cypress.json b/packages/server/test/support/fixtures/projects/webpack-preprocessor-ts-loader/cypress.json index 0967ef424bce..0f77b44749ce 100644 --- a/packages/server/test/support/fixtures/projects/webpack-preprocessor-ts-loader/cypress.json +++ b/packages/server/test/support/fixtures/projects/webpack-preprocessor-ts-loader/cypress.json @@ -1 +1,3 @@ -{} +{ + "retries": null +} diff --git a/packages/server/test/support/fixtures/projects/webpack-preprocessor/cypress.json b/packages/server/test/support/fixtures/projects/webpack-preprocessor/cypress.json index 0967ef424bce..0f77b44749ce 100644 --- a/packages/server/test/support/fixtures/projects/webpack-preprocessor/cypress.json +++ b/packages/server/test/support/fixtures/projects/webpack-preprocessor/cypress.json @@ -1 +1,3 @@ -{} +{ + "retries": null +} diff --git a/packages/server/test/support/helpers/e2e.ts b/packages/server/test/support/helpers/e2e.ts index 7ceb190cd7b3..3147f43e158e 100644 --- a/packages/server/test/support/helpers/e2e.ts +++ b/packages/server/test/support/helpers/e2e.ts @@ -619,6 +619,10 @@ const e2e = { ctx.skip() } + if (options.stubPackage) { + Fixtures.installStubPackage(options.project, options.stubPackage) + } + args = ['index.js'].concat(args) let stdout = '' @@ -727,8 +731,13 @@ const e2e = { // pipe these to our current process // so we can see them in the terminal // color it so we can tell which is test output - sp.stdout.pipe(ColorOutput()).pipe(process.stdout) - sp.stderr.pipe(ColorOutput()).pipe(process.stderr) + sp.stdout + .pipe(ColorOutput()) + .pipe(process.stdout) + + sp.stderr + .pipe(ColorOutput()) + .pipe(process.stderr) sp.stdout.on('data', (buf) => stdout += buf.toString()) sp.stderr.on('data', (buf) => stderr += buf.toString()) diff --git a/packages/server/test/support/helpers/fixtures.js b/packages/server/test/support/helpers/fixtures.js index 56858cc2f10e..857f2b5a8d6c 100644 --- a/packages/server/test/support/helpers/fixtures.js +++ b/packages/server/test/support/helpers/fixtures.js @@ -49,6 +49,14 @@ module.exports = { return fs.removeSync(tmpDir) }, + async installStubPackage (projectPath, pkgName) { + const pathToPkg = path.join(projectPath, 'node_modules', pkgName) + + await fs.outputJSON(path.join(projectPath, 'package.json'), { name: 'some-project' }) + await fs.mkdirp(pathToPkg) + await fs.outputFile(path.join(pathToPkg, 'index.js'), '') + }, + // returns the path to project fixture // in the tmpDir project (...args) { diff --git a/packages/server/test/unit/api_spec.js b/packages/server/test/unit/api_spec.js index 6a72cf4548ab..67cc36c38cfc 100644 --- a/packages/server/test/unit/api_spec.js +++ b/packages/server/test/unit/api_spec.js @@ -568,7 +568,7 @@ describe('lib/api', () => { it('PUTs /instances/:id', function () { nock(API_BASEURL) - .matchHeader('x-route-version', '2') + .matchHeader('x-route-version', '3') .matchHeader('x-os-name', 'linux') .matchHeader('x-cypress-version', pkg.version) .put('/instances/instance-id-123', this.putProps) @@ -579,7 +579,7 @@ describe('lib/api', () => { it('PUT /instances/:id failure formatting', () => { nock(API_BASEURL) - .matchHeader('x-route-version', '2') + .matchHeader('x-route-version', '3') .matchHeader('x-os-name', 'linux') .matchHeader('x-cypress-version', pkg.version) .put('/instances/instance-id-123') @@ -609,7 +609,7 @@ describe('lib/api', () => { it('handles timeouts', () => { nock(API_BASEURL) - .matchHeader('x-route-version', '2') + .matchHeader('x-route-version', '3') .matchHeader('x-os-name', 'linux') .matchHeader('x-cypress-version', pkg.version) .put('/instances/instance-id-123') diff --git a/packages/server/test/unit/config_spec.js b/packages/server/test/unit/config_spec.js index 7503f88f62eb..941bbdf75417 100644 --- a/packages/server/test/unit/config_spec.js +++ b/packages/server/test/unit/config_spec.js @@ -1163,6 +1163,7 @@ describe('lib/config', () => { componentFolder: { value: 'cypress/component', from: 'default' }, experimentalShadowDomSupport: { value: false, from: 'default' }, experimentalFetchPolyfill: { value: false, from: 'default' }, + retries: { value: { runMode: 0, openMode: 0 }, from: 'default' }, }) }) }) @@ -1240,6 +1241,7 @@ describe('lib/config', () => { componentFolder: { value: 'cypress/component', from: 'default' }, experimentalShadowDomSupport: { value: false, from: 'default' }, experimentalFetchPolyfill: { value: false, from: 'default' }, + retries: { value: { runMode: 0, openMode: 0 }, from: 'default' }, env: { foo: { value: 'foo', diff --git a/packages/server/test/unit/modes/run_spec.js b/packages/server/test/unit/modes/run_spec.js index 20c3c6f67814..ec852e3075c1 100644 --- a/packages/server/test/unit/modes/run_spec.js +++ b/packages/server/test/unit/modes/run_spec.js @@ -360,7 +360,7 @@ describe('lib/modes/run', () => { const screenshots = [{}, {}, {}] const endVideoCapture = sinon.stub().resolves() const results = { - tests: [4, 5, 6], + tests: [{ attempts: [1] }, { attempts: [2] }, { attempts: [3] }], stats: { tests: 1, passes: 2, @@ -371,9 +371,6 @@ describe('lib/modes/run', () => { } sinon.stub(Reporter, 'setVideoTimestamp') - .withArgs(startedVideoCapture, results.tests) - .returns([1, 2, 3]) - sinon.stub(runMode, 'postProcessRecording').resolves() sinon.spy(runMode, 'displayResults') sinon.spy(runMode, 'displayScreenshots') @@ -399,6 +396,7 @@ describe('lib/modes/run', () => { }) .then((obj) => { // since video was recording, there was a delay to let video finish + expect(Reporter.setVideoTimestamp).calledWith(startedVideoCapture, [1, 2, 3]) expect(runMode.getVideoRecordingDelay).to.have.returned(1000) expect(Promise.prototype.delay).to.be.calledWith(1000) expect(runMode.postProcessRecording).to.be.calledWith('foo.mp4', 'foo-compressed.mp4', 32, true) @@ -413,7 +411,7 @@ describe('lib/modes/run', () => { hooks: null, reporterStats: null, shouldUploadVideo: true, - tests: [1, 2, 3], + tests: results.tests, spec: { path: 'cypress/integration/spec.js', }, diff --git a/packages/server/test/unit/reporter_spec.js b/packages/server/test/unit/reporter_spec.js index 6888b89131df..a8a75a1080d2 100644 --- a/packages/server/test/unit/reporter_spec.js +++ b/packages/server/test/unit/reporter_spec.js @@ -32,7 +32,7 @@ describe('lib/reporter', () => { sync: true, err: { message: 'foo', - stack: [1, 2, 3], + stack: 'at foo:1:1\nat bar:1:1\nat baz:1:1', }, }, { diff --git a/packages/ui-components/cypress/support/customPercyCommand.js b/packages/ui-components/cypress/support/customPercyCommand.js new file mode 100644 index 000000000000..be1041d59c3e --- /dev/null +++ b/packages/ui-components/cypress/support/customPercyCommand.js @@ -0,0 +1,51 @@ +require('@percy/cypress') +const _ = require('lodash') + +function customPercySnapshot ( + origFn, + name, + options = {}, +) { + if (_.isObject(name)) { + options = name + name = null + } + + const opts = _.defaults({}, options, { + elementOverrides: { '.stats .duration': ($el) => $el.text('XX.XX'), '.cy-tooltip': true }, + widths: [Cypress.config().viewportWidth], + }) + + /** + * @type {Mocha.Test} + */ + const test = cy.state('test') + + const titlePath = test.titlePath() + + const screenshotName = titlePath.concat(name).filter(Boolean).join(' > ') + + _.each(opts.elementOverrides, (v, k) => { + // eslint-disable-next-line cypress/no-assigning-return-values + const $el = cy.$$(k) + + if (_.isFunction(v)) { + v($el) + + return + } + + $el.css({ visibility: 'hidden' }) + }) + + // if we're in interactive mode via (cypress open) + // then bail immediately + if (Cypress.config().isInteractive) { + return cy.log('percy: skipping snapshot in interactive mode') + } + + return origFn(screenshotName, { + widths: opts.widths, + }) +} +Cypress.Commands.overwrite('percySnapshot', customPercySnapshot) diff --git a/scripts/wait-on-circle-jobs.js b/scripts/wait-on-circle-jobs.js new file mode 100644 index 000000000000..09eee4afb5b4 --- /dev/null +++ b/scripts/wait-on-circle-jobs.js @@ -0,0 +1,167 @@ +/* eslint-disable no-console */ + +const _ = require('lodash') +const minimist = require('minimist') +const Promise = require('bluebird') +const retry = require('bluebird-retry') +const got = require('got') +// always print the debug logs +const debug = require('debug')('*') + +// we expect CircleCI to set the current polling job name +const jobName = process.env.CIRCLE_JOB || 'wait-on-circle-jobs' + +const workflowId = process.env.CIRCLE_WORKFLOW_ID + +const getAuth = () => `${process.env.CIRCLE_TOKEN}:` + +if (!process.env.CIRCLE_TOKEN) { + console.error('Cannot find CIRCLE_TOKEN') + process.exit(1) +} + +if (!process.env.CIRCLE_WORKFLOW_ID) { + console.error('Cannot find CIRCLE_WORKFLOW_ID') + process.exit(1) +} + +const args = minimist(process.argv.slice(2), { boolean: false }) + +const jobNames = _ +.chain(args['job-names']) +.split(',') +.without('true') +.map(_.trim) +.compact() +.value() + +if (!jobNames.length) { + console.error('Missing argument: --job-names') + console.error('You must pass a comma separated list of Circle CI job names to wait for.') + process.exit(1) +} + +debug('received circle jobs: %o', jobNames) + +/* eslint-disable-next-line no-unused-vars */ +const getWorkflow = async (workflowId) => { + const auth = getAuth() + const url = `https://${auth}@circleci.com/api/v2/workflow/${workflowId}` + const response = await got(url).json() + + // returns something like + // { + // pipeline_id: '5b937e8b-6138-41ad-b8d0-1c1969c4dad1', + // id: '566ffe9a-62d4-45cd-9a27-9882139e0121', + // name: 'linux', + // project_slug: 'gh/cypress-io/cypress', + // status: 'failed', + // started_by: '45ae8c6a-4686-4e71-a078-fb7a3b9d9e59', + // pipeline_number: 12461, + // created_at: '2020-07-20T19:45:41Z', + // stopped_at: '2020-07-20T20:06:54Z' + // } + + return response +} + +/** + * Job status + * - blocked (has not run yet) + * - running (currently running) + * - failed | success +*/ +const getJobStatus = async (workfowId) => { + const auth = getAuth() + // typo at https://circleci.com/docs/2.0/api-intro/ + // to retrieve all jobs, the url is "/workflow/:id/job" + const url = `https://${auth}@circleci.com/api/v2/workflow/${workflowId}/job` + const response = await got(url).json() + + // returns something like + // { + // next_page_token: null, + // items: [ + // { + // dependencies: [], + // job_number: 400959, + // id: '7021bcc7-90c1-47d9-bf99-c0372a4f8f49', + // started_at: '2020-07-20T19:45:46Z', + // name: 'build', + // project_slug: 'gh/cypress-io/cypress', + // status: 'success', + // type: 'build', + // stopped_at: '2020-07-20T19:50:07Z' + // } + // ] + // } + return response +} + +const waitForAllJobs = async (workflowId) => { + let response + + try { + response = await getJobStatus(workflowId) + } catch (e) { + console.error(e) + process.exit(1) + } + + // if a job is pending, its status will be "blocked" + const blockedJobs = _.filter(response.items, { status: 'blocked' }) + const failedJobs = _.filter(response.items, { status: 'failed' }) + const runningJobs = _.filter(response.items, { status: 'running' }) + + const blockedJobNames = _.map(blockedJobs, 'name') + const runningJobNames = _.map(runningJobs, 'name') + + debug('failed jobs %o', _.map(failedJobs, 'name')) + debug('blocked jobs %o', blockedJobNames) + debug('running jobs %o', runningJobNames) + + if (!runningJobs.length || (runningJobs.length === 1 && runningJobs[0].name === jobName)) { + // there are no more jobs to run, or this is the last running job + console.log('all jobs are done, finishing this job') + + return Promise.resolve() + } + + const futureOrRunning = _.union(blockedJobs, runningJobNames) + const jobsToWaitFor = _.intersection(jobNames, futureOrRunning) + + debug('jobs to wait for %o', jobsToWaitFor) + + if (!jobsToWaitFor.length) { + console.log('No more jobs to wait for!') + + return Promise.resolve() + } + + return Promise.reject(new Error('Jobs have not finished')) +} + +// finished, has one failed job +// const workflowId = '566ffe9a-62d4-45cd-9a27-9882139e0121' +// pending workflow +// jobs that have not run have "status: 'blocked'" + +// getWorkflow(workflowId).then(console.log, console.error) +// getWorkflowJobs(workflowId).then(console.log, console.error) + +const seconds = (s) => s * 1000 +const minutes = (m) => m * 60 * 1000 + +// https://github.com/demmer/bluebird-retry +retry(waitForAllJobs.bind(null, workflowId), { + timeout: minutes(30), // max time for this job + interval: seconds(30), // poll intervals + max_interval: seconds(30), +}).then(() => { + console.log('all done') +}, (err) => { + console.error(err) + process.exit(1) +}) + +// getJobStatus(workflowId).then(console.log, console.error) diff --git a/yarn.lock b/yarn.lock index 9325e5545a2b..fd9b2dc9a4ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1649,6 +1649,16 @@ lodash.merge "^4.6.2" lodash.omit "^4.5.0" +"@cypress/json-schemas@5.35.0": + version "5.35.0" + resolved "https://registry.yarnpkg.com/@cypress/json-schemas/-/json-schemas-5.35.0.tgz#9a699f680a7c58809f743b951f02107fd7b29b80" + integrity sha512-EyCPTw9k3fOkDu1n3zWwRdIGQp6ehZRbCNwYCFtFXxgXW1IK/jTO6ese1mDAIjcfQgLOpBKnj77+ahIC093p7w== + dependencies: + "@cypress/schema-tools" "4.7.4" + lodash.clonedeep "^4.5.0" + lodash.merge "^4.6.2" + lodash.omit "^4.5.0" + "@cypress/listr-verbose-renderer@^0.4.1": version "0.4.1" resolved "https://registry.yarnpkg.com/@cypress/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#a77492f4b11dcc7c446a34b3e28721afd33c642a" @@ -3745,6 +3755,11 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow== +"@sindresorhus/is@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-3.0.0.tgz#78fabc5e295adb6e1ef57eaafe4cc5d7aa35b183" + integrity sha512-kqA5I6Yun7PBHk8WN9BBP1c7FfN2SrD05GuVSEYPqDb4nerv7HqYfgBfMIKmT/EuejURkJKLZuLyGKGs6WEG9w== + "@sinonjs/commons@^1", "@sinonjs/commons@^1.2.0", "@sinonjs/commons@^1.3.0", "@sinonjs/commons@^1.4.0", "@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.7.2": version "1.8.0" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.0.tgz#c8d68821a854c555bba172f3b06959a0039b236d" @@ -3829,6 +3844,13 @@ dependencies: defer-to-connect "^1.0.1" +"@szmarczak/http-timer@^4.0.5": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.5.tgz#bfbd50211e9dfa51ba07da58a14cdfd333205152" + integrity sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ== + dependencies: + defer-to-connect "^2.0.0" + "@types/anymatch@*": version "1.3.1" resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" @@ -3885,6 +3907,16 @@ "@types/connect" "*" "@types/node" "*" +"@types/cacheable-request@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.1.tgz#5d22f3dded1fd3a84c0bbeb5039a7419c2c91976" + integrity sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ== + dependencies: + "@types/http-cache-semantics" "*" + "@types/keyv" "*" + "@types/node" "*" + "@types/responselike" "*" + "@types/caseless@*": version "0.12.2" resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.2.tgz#f65d3d6389e01eeb458bd54dc8f52b95a9463bc8" @@ -3925,6 +3957,13 @@ resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.7.tgz#1c8c25cbf6e59ffa7d6b9652c78e547d9a41692d" integrity sha512-luq8meHGYwvky0O7u0eQZdA7B4Wd9owUCqvbw2m3XCrCU8mplYOujMBbvyS547AxJkC+pGnd0Cm15eNxEUNU8g== +"@types/chalk@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@types/chalk/-/chalk-2.2.0.tgz#b7f6e446f4511029ee8e3f43075fb5b73fbaa0ba" + integrity sha512-1zzPV9FDe1I/WHhRkf9SNgqtRJWZqrBWgu7JGveuHmmyR9CnAPCie2N/x+iHrgnpYBIcCJWHBoMRv2TRWktsvw== + dependencies: + chalk "*" + "@types/cheerio@*": version "0.22.21" resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.21.tgz#5e37887de309ba11b2e19a6e14cad7874b31a8a3" @@ -3956,6 +3995,11 @@ resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== +"@types/common-tags@^1.8.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@types/common-tags/-/common-tags-1.8.0.tgz#79d55e748d730b997be5b7fce4b74488d8b26a6b" + integrity sha512-htRqZr5qn8EzMelhX/Xmx142z218lLyGaeZ3YR8jlze4TATRU9huKKvuBmAJEW4LCC4pnY1N6JAm6p85fMHjhg== + "@types/concat-stream@1.6.0": version "1.6.0" resolved "https://registry.yarnpkg.com/@types/concat-stream/-/concat-stream-1.6.0.tgz#394dbe0bb5fee46b38d896735e8b68ef2390d00d" @@ -4096,6 +4140,11 @@ "@types/tapable" "*" "@types/webpack" "*" +"@types/http-cache-semantics@*": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz#9140779736aa2655635ee756e2467d787cfe8a2a" + integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A== + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" @@ -4149,12 +4198,19 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/keyv@*": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.1.tgz#e45a45324fca9dab716ab1230ee249c9fb52cfa7" + integrity sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw== + dependencies: + "@types/node" "*" + "@types/linkify-it@*": version "2.1.0" resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-2.1.0.tgz#ea3dd64c4805597311790b61e872cbd1ed2cd806" integrity sha512-Q7DYAOi9O/+cLLhdaSvKdaumWyHbm7HAk/bFwwyTuU0arR5yyCeW5GOoqt4tJTpDRxhpx9Q8kQL6vMpuw9hDSw== -"@types/lodash@4.14.149": +"@types/lodash@4.14.149", "@types/lodash@^4.14.123": version "4.14.149" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.149.tgz#1342d63d948c6062838fbf961012f74d4e638440" integrity sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ== @@ -4193,7 +4249,7 @@ resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.0.tgz#69a23a3ad29caf0097f06eda59b361ee2f0639f6" integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY= -"@types/mocha@5.2.7": +"@types/mocha@5.2.7", "@types/mocha@^5.2.6": version "5.2.7" resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea" integrity sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ== @@ -4318,6 +4374,13 @@ "@types/tough-cookie" "*" form-data "^2.5.0" +"@types/responselike@*", "@types/responselike@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" + integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA== + dependencies: + "@types/node" "*" + "@types/serve-static@*": version "1.13.4" resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.4.tgz#6662a93583e5a6cabca1b23592eb91e12fa80e7c" @@ -7749,6 +7812,11 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" +cacheable-lookup@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.3.tgz#049fdc59dffdd4fc285e8f4f82936591bd59fec3" + integrity sha512-W+JBqF9SWe18A72XFzN/V/CULFzPm7sBXzzR6ekkE+3tLG72wFZrBiBZhrZuDoYexop4PHJVdFAKb/Nj9+tm9w== + cacheable-request@^2.1.1: version "2.1.4" resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-2.1.4.tgz#0d808801b6342ad33c91df9d0b44dc09b91e5c3d" @@ -7775,6 +7843,19 @@ cacheable-request@^6.0.0: normalize-url "^4.1.0" responselike "^1.0.2" +cacheable-request@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.1.tgz#062031c2856232782ed694a257fa35da93942a58" + integrity sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^4.0.0" + lowercase-keys "^2.0.0" + normalize-url "^4.1.0" + responselike "^2.0.0" + cached-path-relative@^1.0.0, cached-path-relative@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/cached-path-relative/-/cached-path-relative-1.0.2.tgz#a13df4196d26776220cc3356eb147a52dba2c6db" @@ -8006,6 +8087,14 @@ chai@4.2.0: pathval "^1.1.0" type-detect "^4.0.5" +chalk@*, chalk@^4.0.0, chalk@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@1.x.x, chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -8043,14 +8132,6 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.0.0, chalk@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" - integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - chalk@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.4.0.tgz#5199a3ddcd0c1efe23bc08c1b027b06176e0c64f" @@ -9703,6 +9784,13 @@ decompress-response@^4.2.0: dependencies: mimic-response "^2.0.0" +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + dedent@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" @@ -9778,6 +9866,11 @@ defer-to-connect@^1.0.1: resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== +defer-to-connect@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.0.tgz#83d6b199db041593ac84d781b5222308ccf4c2c1" + integrity sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg== + define-properties@^1.1.2, define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" @@ -13217,6 +13310,23 @@ good-listener@^1.2.2: dependencies: delegate "^3.1.2" +got@11.5.1: + version "11.5.1" + resolved "https://registry.yarnpkg.com/got/-/got-11.5.1.tgz#bf098a270fe80b3fb88ffd5a043a59ebb0a391db" + integrity sha512-reQEZcEBMTGnujmQ+Wm97mJs/OK6INtO6HmLI+xt3+9CvnRwWjXutUvb2mqr+Ao4Lu05Rx6+udx9sOQAmExMxA== + dependencies: + "@sindresorhus/is" "^3.0.0" + "@szmarczak/http-timer" "^4.0.5" + "@types/cacheable-request" "^6.0.1" + "@types/responselike" "^1.0.0" + cacheable-lookup "^5.0.3" + cacheable-request "^7.0.1" + decompress-response "^6.0.0" + http2-wrapper "^1.0.0-beta.5.0" + lowercase-keys "^2.0.0" + p-cancelable "^2.0.0" + responselike "^2.0.0" + got@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/got/-/got-8.3.2.tgz#1d23f64390e97f776cac52e5b936e5f514d2e937" @@ -13873,6 +13983,14 @@ http-status-codes@1.4.0: resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-1.4.0.tgz#6e4c15d16ff3a9e2df03b89f3a55e1aae05fb477" integrity sha512-JrT3ua+WgH8zBD3HEJYbeEgnuQaAnUeRRko/YojPAJjGmIfGD3KPU/asLdsLwKjfxOmQe5nXMQ0pt/7MyapVbQ== +http2-wrapper@^1.0.0-beta.5.0: + version "1.0.0-beta.5.2" + resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.0-beta.5.2.tgz#8b923deb90144aea65cf834b016a340fc98556f3" + integrity sha512-xYz9goEyBnC8XwXDTuC/MZ6t+MrKVQZOk4s7+PaDkwIsQd8IwqvM+0M6bA/2lvG8GHXcPdf+MejTUeO2LCPCeQ== + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.0.0" + https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" @@ -15793,6 +15911,11 @@ json-buffer@3.0.0: resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-fixer@^1.3.1: version "1.5.2" resolved "https://registry.yarnpkg.com/json-fixer/-/json-fixer-1.5.2.tgz#1c99f7f2e93106a105f1311fec7c13c6925a0a93" @@ -16025,6 +16148,13 @@ keyv@^3.0.0: dependencies: json-buffer "3.0.0" +keyv@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.1.tgz#9fe703cb4a94d6d11729d320af033307efd02ee6" + integrity sha512-xz6Jv6oNkbhrFCvCP7HQa8AaII8y8LRpoSm661NOKLr4uHuBwhX4epXrPQgF3+xdJnN4Esm5X0xwY4bOlALOtw== + dependencies: + json-buffer "3.0.1" + kind-of@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-1.1.0.tgz#140a3d2d41a36d2efcfa9377b62c24f8495a5c44" @@ -17284,6 +17414,11 @@ mimic-response@^2.0.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + min-document@^2.19.0: version "2.19.0" resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" @@ -18967,6 +19102,11 @@ p-cancelable@^1.0.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== +p-cancelable@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e" + integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg== + p-defer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" @@ -20293,6 +20433,11 @@ quick-lru@^4.0.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + quote@0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/quote/-/quote-0.4.0.tgz#10839217f6c1362b89194044d29b233fd7f32f01" @@ -21440,6 +21585,11 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= +resolve-alpn@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.0.0.tgz#745ad60b3d6aff4b4a48e01b8c0bdc70959e0e8c" + integrity sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA== + resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" @@ -21539,6 +21689,13 @@ responselike@1.0.2, responselike@^1.0.2: dependencies: lowercase-keys "^1.0.0" +responselike@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723" + integrity sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw== + dependencies: + lowercase-keys "^2.0.0" + restore-cursor@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" From 966fbf0e421ba99bb0df5845493fd1842654323a Mon Sep 17 00:00:00 2001 From: Ben Kucera <14625260+Bkucera@users.noreply.github.com> Date: Tue, 11 Aug 2020 08:58:40 -0400 Subject: [PATCH 34/42] fix: remove private properties, move screenshots in module API results (#8148) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add retries output to server/reporter, fix mocha pass event order, cleanup * e2e tests - dont run electron tests in other browsers * Update readme to reflect how to start server for cypress tests * fix after merge * fix .coffee -> .js after merge * fix attempt.tsx * fix runnable titles emitted to terminal reporters * fix more tests: update snapshots, fix 7_record_spec, 8_reporters_spec * remove styling for 'attempt-error-region' so it's not indented - This was the older styling before error improvements and is no longer necessary. * try 2: fix rerun before/after hooks * fix runner with only, runner snapshots, lint fixes * temp 04/29/20 [skip ci] * backport changes from test-retries * change logic to rerun before hooks after top navigation * fix windowSize for browser e2e test * fix windowSize for xvfb chrome in e2e test * ok fine, just disable screenshots * fix after merge: decaffed navigation.js * update server/unit test snapshots * fix after merge: decaffed aliases.js * fix usage of cypress --parallel flag in circle.yml * fix circle.yml integration-tests jobs * fix decaf related typo * fix circle.yml separate command for runner-integration-tests * update runner/integration tests + snapshot after error improvements * fix runner/integration snapshots for chrome/ff stacktrace differences * rerun ci * fix passing --parallel to runner-integration tests * perf: faster lookup for hooks without runnables * fix afterAll hook switch logic * simplify mocha prototype patches * fix decaf utils.coffee after merge * backport to before/after fix * backport to before/after fix 2 * cleanup from decaf, fix ui/package.json * update helpers, simplify runner.spec * fix lint-types errors, flaky spec * fix noExit passed to e2e test inline options * cleanup snapshot utility - refactor to use util file * remove before/after changes * make cy obj a class instance * cleanup/unmerge before/after fixes PR... * more cleanup * add comment * fix runner.spec * cleanup snapshot utility more, cleanup reporter.spec * fix after merge * minor rename variable * fix after merge: decaffed files * fix specName in reporterHeader, spec_helper require * replace reporter specPath usages with spec object from config * cleanup, fix specs, fix types tests * fix config spec paths in isolated runner, fix snapshot plugin button * combine runner.spec.js and runner_spec.js * fix incorrect merge * minor minor cleanup * rename driver/test/cypress to driver/test * use yarn workspace over lerna for individual package commands * add error message to driver start * remove usage of wait-on * update , import string * fix driver/readme * fix readmes after regex replace * revert wait-on changes * Revert "revert wait-on changes" This reverts commit 6de684cf3484fca8f7d8d6fe396bee248b62f819. * update yarn.lock * fix broken path in spec * fix broken paths in specs with @packages/driver * move runner/test/cypress into runner/cypress * start server in pluginsFile in runner/cypress tests * fix more broken spec paths * fix broken paths after runner/cypress folder move * move type definition loading for driver/cypress into dedicated file * move internal-types to "types" folder, fix driver/index.d.ts * fix type-check in packages/runner. not exactly sure why * fix runner type-check by excluding test folder in tsconfig * bump timeout on e2e/8_error_ui_spec * update snapshot utility, rename tests in runner/runner.spec, fix README yarn commands * delete old spec * fix snapshot naming, remove redundant test in reporter_spec * fix file renames after merge * rename runner/ snapshot * update server/unit/reporter_spec snapshot * update runner/runner_spec snapshot * rename runner snapshot file * address feedback: move server reporter snapshot specs out * address feedback: add comment about exposing globals * fix test-retries after merging isolated-runner * fix runner/test helper, update snapshot * address feedback: split out runner/retries spec, move reporter/ui tests to runner/ui spec (mostly done), various cleanup * fix scrolling, attempt opening, update snapshots * fix e2e support file * fix 5_spec_isolation * fix mislabeling attempt screenshots * only add test results prevAttempts if exists * fix reporter/unit tests, server/unit tests * remove dead code, fix test isOpen * update snapshots for retries.mochaEvents, fix snapshot error in state hydration test, remove dead snapshots * new moduleAPI schema using attempts array, fix wrapping errors from hook retries, update snapshots * add displayError, null out fields in moduleAPI schema * change default retries to {runMode:2, openMode:0} * fix reporter type-check * upgrade json-schemas, update snapshots * reformat error.stack to be only stacktrace, update snapshots * fix stacktrace replacing in 5_spec_isolation * fix navigation error causing infinite reloading, bump timeout on e2e/8_error_ui * fix server/unit tests for new schema * fix reporter/unit tests * fix reporting duplicate screenshots using cy.screenshot during test retry * update snapshot for 6_uncaught_support_file_spec * bump x-route-version: 3 * fix test.tsx collapsible content, css, fix e2e/8_error_ui, e2e projects excluding retries * fix css, fix padding in runnable-instruments, fix runner/integration tests * fixup after merge * fix reporter/runner to work with split hooks * update api tests, runner/cypress tests, reporter * fix 5_spec_isolation snapshots, fix runner/cypress errors.spec, fix null reference in test.tsx * fix e2e/non_root spec, fix type_check, fix reporter/unit tests * setup percy snapshots in runner/cypress, fix driver/runner test:after:run event, add tests for only,skip in runner/cypress, fix retried css * add customPercySnapshot * fix circle.yml * fix circle.yml 2 * fix circle.yml 3 * add warning for incompatible retries plugin * add more percy snapshots * fix firefox screenshot resolution in e2e test * Fix testConfigOverrides not affecting viewport (#8006) * finish adding percy snapshots to runner/cypress retries spec, update error msgs, add tests to be fixed * remove .only * fixing missing repo argument * fix testConfigOverrides usage with retries, fix test * fix issues from previous merge * add script that can query CircleCI workflow status * add circleci job to poll * add retries * try yarn lock * retry, percy finalize * check for current running job * do not swallow request error * better print * use job name from circle environment * use debug instead * renamed circle polling script * refactor circle to conditionally run percy-finalize when env var is available - pass job-names to wait on as an argument * use multi-line strings and quote --job-names - rename —circle-jobs to —job-names * add comment * only poll until the jobs to wait for are blocked or running * fix running hooks at correct depth after attempt fails from hook and will retry, update e2e snapshots * fix reporter/unit tests, remove unused toggleOpen code * move custom percy command into @packages/ui-components and apply them to desktop-gui * halt percy finalize job if env variable is not set * if only I could code * update runner/cypress mochaEvent snapshots, fix e2e firefox resolution * fix css for attempt border-left, fix attempt-tag open/close icon, add color to attempt collapsible dot * try percy set viewport width * set default retries back to {runMode:0, openMode:0} * formatting: add backticks to warning message * write explicit test for screenshot overwriting behavior, fix snapshots after changing retries defaults * fix e2e.it.only` * cleanup whitespace * update snapshots * remap module API result, move e2e timing tests from 5_spec_isolation into 7_record_spec, unit test * fix cypress module API types for new result schema * build and upload binary for test-retries branch too (linux) * update module api typedefs for moving screenshot info * cleanup typedef * small refactor, add cypress-inspect-brk feat to e2e tests, fix typedefs for moduleapi * add pre-release PR comment * fix pre-release commit comment * rename runner/cypress test * update retries.ui.spec test titles * fix after merge: use most recent attempt for before/after hooks * add suite title to hook error in runner/cypress tests * fix snapshot Co-authored-by: Jennifer Shehane Co-authored-by: Brian Mann Co-authored-by: Gleb Bahmutov --- cli/types/cypress-npm-api.d.ts | 24 +- .../__snapshots__/5_spec_isolation_spec.js | 628 +++++------------- .../server/__snapshots__/7_record_spec.js | 265 ++++++++ packages/server/lib/modes/run.js | 39 +- packages/server/lib/util/obj_utils.ts | 48 ++ .../server/test/e2e/5_spec_isolation_spec.js | 273 +------- packages/server/test/e2e/7_record_spec.js | 304 ++++----- packages/server/test/scripts/run.js | 4 + packages/server/test/support/helpers/e2e.ts | 9 + .../test/support/helpers/resultsUtils.ts | 302 +++++++++ .../server/test/unit/util/obj_utils_spec.ts | 29 + 11 files changed, 1043 insertions(+), 882 deletions(-) create mode 100644 packages/server/lib/util/obj_utils.ts create mode 100644 packages/server/test/support/helpers/resultsUtils.ts create mode 100644 packages/server/test/unit/util/obj_utils_spec.ts diff --git a/cli/types/cypress-npm-api.d.ts b/cli/types/cypress-npm-api.d.ts index 805bb2ca3ca0..d3b29eccd58e 100644 --- a/cli/types/cypress-npm-api.d.ts +++ b/cli/types/cypress-npm-api.d.ts @@ -7,6 +7,7 @@ // but for now describe it as an ambient module declare namespace CypressCommandLine { + type HookName = 'before' | 'beforeEach' | 'afterEach' | 'after' interface TestError { name: string message: string @@ -159,15 +160,12 @@ declare namespace CypressCommandLine { // small utility types to better express meaning of other types type dateTimeISO = string type ms = number - type hookId = string - type testId = string type pixels = number /** * Cypress single test result */ interface TestResult { - testId: testId title: string[] state: string body: string @@ -181,19 +179,17 @@ declare namespace CypressCommandLine { interface AttemptResult { state: string error: TestError | null - timings: any - failedFromHookId: hookId | null - wallClockStartedAt: dateTimeISO - wallClockDuration: ms + startedAt: dateTimeISO + duration: ms videoTimestamp: ms + screenshots: ScreenshotInformation[] } /** * Information about a single "before", "beforeEach", "afterEach" and "after" hook. */ interface HookInformation { - hookId: hookId - hookName: 'before' | 'beforeEach' | 'afterEach' | 'after' + hookName: HookName title: string[] body: string } @@ -202,11 +198,8 @@ declare namespace CypressCommandLine { * Information about a single screenshot. */ interface ScreenshotInformation { - screenshotId: string name: string - testId: testId takenAt: dateTimeISO - testAttemptIndex: number /** * Absolute path to the saved image */ @@ -229,9 +222,9 @@ declare namespace CypressCommandLine { pending: number skipped: number failures: number - wallClockStartedAt: dateTimeISO - wallClockEndedAt: dateTimeISO - wallClockDuration: ms + startedAt: dateTimeISO + endedAt: dateTimeISO + duration: ms }, /** * Reporter name like "spec" @@ -246,7 +239,6 @@ declare namespace CypressCommandLine { tests: TestResult[] error: string | null video: string | null - screenshots: ScreenshotInformation[] /** * information about the spec test file. */ diff --git a/packages/server/__snapshots__/5_spec_isolation_spec.js b/packages/server/__snapshots__/5_spec_isolation_spec.js index 74edb00023b7..6f439cfdc402 100644 --- a/packages/server/__snapshots__/5_spec_isolation_spec.js +++ b/packages/server/__snapshots__/5_spec_isolation_spec.js @@ -17,9 +17,9 @@ exports['e2e spec isolation fails'] = { "pending": 1, "skipped": 1, "failures": 3, - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockEndedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234 + "duration": 1234, + "startedAt": "2018-02-01T20:14:19.323Z", + "endedAt": "2018-02-01T20:14:19.323Z" }, "reporter": "spec", "reporterStats": { @@ -34,7 +34,6 @@ exports['e2e spec isolation fails'] = { }, "hooks": [ { - "hookId": "h1", "hookName": "before each", "title": [ "\"before each\" hook" @@ -42,7 +41,6 @@ exports['e2e spec isolation fails'] = { "body": "function() {\n throw new Error(\"fail1\");\n }" }, { - "hookId": "h2", "hookName": "after each", "title": [ "\"after each\" hook" @@ -50,7 +48,6 @@ exports['e2e spec isolation fails'] = { "body": "function() {\n throw new Error(\"fail2\");\n }" }, { - "hookId": "h3", "hookName": "after all", "title": [ "\"after all\" hook" @@ -60,7 +57,6 @@ exports['e2e spec isolation fails'] = { ], "tests": [ { - "testId": "r4", "title": [ "simple failing hook spec", "beforeEach hooks", @@ -77,25 +73,22 @@ exports['e2e spec isolation fails'] = { "message": "fail1\n\nBecause this error occurred during a `before each` hook we are skipping the remaining tests in the current suite: `beforeEach hooks`", "stack": "[stack trace lines]" }, - "timings": { - "lifecycle": 100, - "before each": [ - { - "hookId": "h1", - "fnDuration": 400, - "afterFnDuration": 200 - } - ] - }, - "failedFromHookId": "h1", - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234, - "videoTimestamp": 9999 + "videoTimestamp": 9999, + "duration": 1234, + "startedAt": "2018-02-01T20:14:19.323Z", + "screenshots": [ + { + "name": null, + "takenAt": "2018-02-01T20:14:19.323Z", + "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_hook_spec.coffee/simple failing hook spec -- beforeEach hooks -- never gets here -- before each hook (failed).png", + "height": 720, + "width": 1280 + } + ] } ] }, { - "testId": "r6", "title": [ "simple failing hook spec", "pending", @@ -108,16 +101,14 @@ exports['e2e spec isolation fails'] = { { "state": "pending", "error": null, - "timings": null, - "failedFromHookId": null, - "wallClockStartedAt": null, - "wallClockDuration": null, - "videoTimestamp": null + "videoTimestamp": null, + "duration": null, + "startedAt": null, + "screenshots": [] } ] }, { - "testId": "r8", "title": [ "simple failing hook spec", "afterEach hooks", @@ -134,29 +125,22 @@ exports['e2e spec isolation fails'] = { "message": "fail2\n\nBecause this error occurred during a `after each` hook we are skipping the remaining tests in the current suite: `afterEach hooks`", "stack": "[stack trace lines]" }, - "timings": { - "lifecycle": 100, - "test": { - "fnDuration": 400, - "afterFnDuration": 200 - }, - "after each": [ - { - "hookId": "h2", - "fnDuration": 400, - "afterFnDuration": 200 - } - ] - }, - "failedFromHookId": "h2", - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234, - "videoTimestamp": 9999 + "videoTimestamp": 9999, + "duration": 1234, + "startedAt": "2018-02-01T20:14:19.323Z", + "screenshots": [ + { + "name": null, + "takenAt": "2018-02-01T20:14:19.323Z", + "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_hook_spec.coffee/simple failing hook spec -- afterEach hooks -- runs this -- after each hook (failed).png", + "height": 720, + "width": 1280 + } + ] } ] }, { - "testId": "r9", "title": [ "simple failing hook spec", "afterEach hooks", @@ -169,16 +153,14 @@ exports['e2e spec isolation fails'] = { { "state": "skipped", "error": null, - "timings": null, - "failedFromHookId": null, - "wallClockStartedAt": null, - "wallClockDuration": null, - "videoTimestamp": null + "videoTimestamp": null, + "duration": null, + "startedAt": null, + "screenshots": [] } ] }, { - "testId": "r11", "title": [ "simple failing hook spec", "after hooks", @@ -191,22 +173,14 @@ exports['e2e spec isolation fails'] = { { "state": "passed", "error": null, - "timings": { - "lifecycle": 100, - "test": { - "fnDuration": 400, - "afterFnDuration": 200 - } - }, - "failedFromHookId": null, - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234, - "videoTimestamp": 9999 + "videoTimestamp": 9999, + "duration": 1234, + "startedAt": "2018-02-01T20:14:19.323Z", + "screenshots": [] } ] }, { - "testId": "r12", "title": [ "simple failing hook spec", "after hooks", @@ -223,62 +197,24 @@ exports['e2e spec isolation fails'] = { "message": "fail3\n\nBecause this error occurred during a `after all` hook we are skipping the remaining tests in the current suite: `after hooks`", "stack": "[stack trace lines]" }, - "timings": { - "lifecycle": 100, - "test": { - "fnDuration": 400, - "afterFnDuration": 200 - }, - "after all": [ - { - "hookId": "h3", - "fnDuration": 400, - "afterFnDuration": 200 - } - ] - }, - "failedFromHookId": "h3", - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234, - "videoTimestamp": 9999 + "videoTimestamp": 9999, + "duration": 1234, + "startedAt": "2018-02-01T20:14:19.323Z", + "screenshots": [ + { + "name": null, + "takenAt": "2018-02-01T20:14:19.323Z", + "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_hook_spec.coffee/simple failing hook spec -- after hooks -- fails on this -- after all hook (failed).png", + "height": 720, + "width": 1280 + } + ] } ] } ], "error": null, "video": "/foo/bar/.projects/e2e/cypress/videos/simple_failing_hook_spec.coffee.mp4", - "screenshots": [ - { - "screenshotId": "some-random-id", - "name": null, - "testId": "r4", - "testAttemptIndex": 0, - "takenAt": "2018-02-01T20:14:19.323Z", - "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_hook_spec.coffee/simple failing hook spec -- beforeEach hooks -- never gets here -- before each hook (failed).png", - "height": 720, - "width": 1280 - }, - { - "screenshotId": "some-random-id", - "name": null, - "testId": "r8", - "testAttemptIndex": 0, - "takenAt": "2018-02-01T20:14:19.323Z", - "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_hook_spec.coffee/simple failing hook spec -- afterEach hooks -- runs this -- after each hook (failed).png", - "height": 720, - "width": 1280 - }, - { - "screenshotId": "some-random-id", - "name": null, - "testId": "r12", - "testAttemptIndex": 0, - "takenAt": "2018-02-01T20:14:19.323Z", - "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_hook_spec.coffee/simple failing hook spec -- after hooks -- fails on this -- after all hook (failed).png", - "height": 720, - "width": 1280 - } - ], "spec": { "name": "simple_failing_hook_spec.coffee", "relative": "cypress/integration/simple_failing_hook_spec.coffee", @@ -295,9 +231,9 @@ exports['e2e spec isolation fails'] = { "pending": 0, "skipped": 0, "failures": 2, - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockEndedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234 + "duration": 1234, + "startedAt": "2018-02-01T20:14:19.323Z", + "endedAt": "2018-02-01T20:14:19.323Z" }, "reporter": "spec", "reporterStats": { @@ -313,7 +249,6 @@ exports['e2e spec isolation fails'] = { "hooks": [], "tests": [ { - "testId": "r3", "title": [ "simple failing spec", "fails1" @@ -329,22 +264,22 @@ exports['e2e spec isolation fails'] = { "message": "Timed out retrying: expected true to be false", "stack": "[stack trace lines]" }, - "timings": { - "lifecycle": 100, - "test": { - "fnDuration": 400, - "afterFnDuration": 200 + "videoTimestamp": 9999, + "duration": 1234, + "startedAt": "2018-02-01T20:14:19.323Z", + "screenshots": [ + { + "name": null, + "takenAt": "2018-02-01T20:14:19.323Z", + "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_spec.coffee/simple failing spec -- fails1 (failed).png", + "height": 720, + "width": 1280 } - }, - "failedFromHookId": null, - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234, - "videoTimestamp": 9999 + ] } ] }, { - "testId": "r4", "title": [ "simple failing spec", "fails2" @@ -360,45 +295,24 @@ exports['e2e spec isolation fails'] = { "message": "fails2", "stack": "[stack trace lines]" }, - "timings": { - "lifecycle": 100, - "test": { - "fnDuration": 400, - "afterFnDuration": 200 + "videoTimestamp": 9999, + "duration": 1234, + "startedAt": "2018-02-01T20:14:19.323Z", + "screenshots": [ + { + "name": null, + "takenAt": "2018-02-01T20:14:19.323Z", + "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_spec.coffee/simple failing spec -- fails2 (failed).png", + "height": 720, + "width": 1280 } - }, - "failedFromHookId": null, - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234, - "videoTimestamp": 9999 + ] } ] } ], "error": null, "video": "/foo/bar/.projects/e2e/cypress/videos/simple_failing_spec.coffee.mp4", - "screenshots": [ - { - "screenshotId": "some-random-id", - "name": null, - "testId": "r3", - "testAttemptIndex": 0, - "takenAt": "2018-02-01T20:14:19.323Z", - "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_spec.coffee/simple failing spec -- fails1 (failed).png", - "height": 720, - "width": 1280 - }, - { - "screenshotId": "some-random-id", - "name": null, - "testId": "r4", - "testAttemptIndex": 0, - "takenAt": "2018-02-01T20:14:19.323Z", - "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_spec.coffee/simple failing spec -- fails2 (failed).png", - "height": 720, - "width": 1280 - } - ], "spec": { "name": "simple_failing_spec.coffee", "relative": "cypress/integration/simple_failing_spec.coffee", @@ -415,9 +329,9 @@ exports['e2e spec isolation fails'] = { "pending": 0, "skipped": 0, "failures": 0, - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockEndedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234 + "duration": 1234, + "startedAt": "2018-02-01T20:14:19.323Z", + "endedAt": "2018-02-01T20:14:19.323Z" }, "reporter": "spec", "reporterStats": { @@ -432,7 +346,6 @@ exports['e2e spec isolation fails'] = { }, "hooks": [ { - "hookId": "h1", "hookName": "before all", "title": [ "\"before all\" hook" @@ -440,7 +353,6 @@ exports['e2e spec isolation fails'] = { "body": "function() {\n return cy.wait(100);\n }" }, { - "hookId": "h2", "hookName": "before each", "title": [ "\"before each\" hook" @@ -448,7 +360,6 @@ exports['e2e spec isolation fails'] = { "body": "function() {\n return cy.wait(200);\n }" }, { - "hookId": "h4", "hookName": "after each", "title": [ "\"after each\" hook" @@ -456,7 +367,6 @@ exports['e2e spec isolation fails'] = { "body": "function() {\n return cy.wait(200);\n }" }, { - "hookId": "h3", "hookName": "after all", "title": [ "\"after all\" hook" @@ -466,7 +376,6 @@ exports['e2e spec isolation fails'] = { ], "tests": [ { - "testId": "r3", "title": [ "simple hooks spec", "t1" @@ -478,43 +387,14 @@ exports['e2e spec isolation fails'] = { { "state": "passed", "error": null, - "timings": { - "lifecycle": 100, - "before all": [ - { - "hookId": "h1", - "fnDuration": 400, - "afterFnDuration": 200 - } - ], - "before each": [ - { - "hookId": "h2", - "fnDuration": 400, - "afterFnDuration": 200 - } - ], - "test": { - "fnDuration": 400, - "afterFnDuration": 200 - }, - "after each": [ - { - "hookId": "h4", - "fnDuration": 400, - "afterFnDuration": 200 - } - ] - }, - "failedFromHookId": null, - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234, - "videoTimestamp": 9999 + "videoTimestamp": 9999, + "duration": 1234, + "startedAt": "2018-02-01T20:14:19.323Z", + "screenshots": [] } ] }, { - "testId": "r4", "title": [ "simple hooks spec", "t2" @@ -526,36 +406,14 @@ exports['e2e spec isolation fails'] = { { "state": "passed", "error": null, - "timings": { - "lifecycle": 100, - "before each": [ - { - "hookId": "h2", - "fnDuration": 400, - "afterFnDuration": 200 - } - ], - "test": { - "fnDuration": 400, - "afterFnDuration": 200 - }, - "after each": [ - { - "hookId": "h4", - "fnDuration": 400, - "afterFnDuration": 200 - } - ] - }, - "failedFromHookId": null, - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234, - "videoTimestamp": 9999 + "videoTimestamp": 9999, + "duration": 1234, + "startedAt": "2018-02-01T20:14:19.323Z", + "screenshots": [] } ] }, { - "testId": "r5", "title": [ "simple hooks spec", "t3" @@ -567,45 +425,16 @@ exports['e2e spec isolation fails'] = { { "state": "passed", "error": null, - "timings": { - "lifecycle": 100, - "before each": [ - { - "hookId": "h2", - "fnDuration": 400, - "afterFnDuration": 200 - } - ], - "test": { - "fnDuration": 400, - "afterFnDuration": 200 - }, - "after each": [ - { - "hookId": "h4", - "fnDuration": 400, - "afterFnDuration": 200 - } - ], - "after all": [ - { - "hookId": "h3", - "fnDuration": 400, - "afterFnDuration": 200 - } - ] - }, - "failedFromHookId": null, - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234, - "videoTimestamp": 9999 + "videoTimestamp": 9999, + "duration": 1234, + "startedAt": "2018-02-01T20:14:19.323Z", + "screenshots": [] } ] } ], "error": null, "video": "/foo/bar/.projects/e2e/cypress/videos/simple_hooks_spec.coffee.mp4", - "screenshots": [], "spec": { "name": "simple_hooks_spec.coffee", "relative": "cypress/integration/simple_hooks_spec.coffee", @@ -622,9 +451,9 @@ exports['e2e spec isolation fails'] = { "pending": 0, "skipped": 0, "failures": 0, - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockEndedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234 + "duration": 1234, + "startedAt": "2018-02-01T20:14:19.323Z", + "endedAt": "2018-02-01T20:14:19.323Z" }, "reporter": "spec", "reporterStats": { @@ -639,7 +468,6 @@ exports['e2e spec isolation fails'] = { }, "hooks": [ { - "hookId": "h1", "hookName": "before each", "title": [ "\"before each\" hook" @@ -649,7 +477,6 @@ exports['e2e spec isolation fails'] = { ], "tests": [ { - "testId": "r3", "title": [ "simple passing spec", "passes" @@ -661,31 +488,16 @@ exports['e2e spec isolation fails'] = { { "state": "passed", "error": null, - "timings": { - "lifecycle": 100, - "before each": [ - { - "hookId": "h1", - "fnDuration": 400, - "afterFnDuration": 200 - } - ], - "test": { - "fnDuration": 400, - "afterFnDuration": 200 - } - }, - "failedFromHookId": null, - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234, - "videoTimestamp": 9999 + "videoTimestamp": 9999, + "duration": 1234, + "startedAt": "2018-02-01T20:14:19.323Z", + "screenshots": [] } ] } ], "error": null, "video": "/foo/bar/.projects/e2e/cypress/videos/simple_passing_spec.coffee.mp4", - "screenshots": [], "spec": { "name": "simple_passing_spec.coffee", "relative": "cypress/integration/simple_passing_spec.coffee", @@ -841,9 +653,9 @@ exports['failing with retries enabled'] = { "pending": 1, "skipped": 1, "failures": 3, - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockEndedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234 + "duration": 1234, + "startedAt": "2018-02-01T20:14:19.323Z", + "endedAt": "2018-02-01T20:14:19.323Z" }, "reporter": "spec", "reporterStats": { @@ -858,7 +670,6 @@ exports['failing with retries enabled'] = { }, "hooks": [ { - "hookId": "h1", "hookName": "before each", "title": [ "\"before each\" hook" @@ -866,7 +677,6 @@ exports['failing with retries enabled'] = { "body": "function() {\n throw new Error(\"fail1\");\n }" }, { - "hookId": "h2", "hookName": "after each", "title": [ "\"after each\" hook" @@ -874,7 +684,6 @@ exports['failing with retries enabled'] = { "body": "function() {\n throw new Error(\"fail2\");\n }" }, { - "hookId": "h3", "hookName": "after all", "title": [ "\"after all\" hook" @@ -884,7 +693,6 @@ exports['failing with retries enabled'] = { ], "tests": [ { - "testId": "r4", "title": [ "simple failing hook spec", "beforeEach hooks", @@ -901,20 +709,18 @@ exports['failing with retries enabled'] = { "message": "fail1\n\nBecause this error occurred during a `before each` hook we are skipping the remaining tests in the current suite: `beforeEach hooks`", "stack": "[stack trace lines]" }, - "timings": { - "lifecycle": 100, - "before each": [ - { - "hookId": "h1", - "fnDuration": 400, - "afterFnDuration": 200 - } - ] - }, - "failedFromHookId": "h1", - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234, - "videoTimestamp": 9999 + "videoTimestamp": 9999, + "duration": 1234, + "startedAt": "2018-02-01T20:14:19.323Z", + "screenshots": [ + { + "name": null, + "takenAt": "2018-02-01T20:14:19.323Z", + "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_hook_spec.coffee/simple failing hook spec -- beforeEach hooks -- never gets here (failed).png", + "height": 720, + "width": 1280 + } + ] }, { "state": "failed", @@ -923,29 +729,22 @@ exports['failing with retries enabled'] = { "message": "fail1", "stack": "[stack trace lines]" }, - "timings": { - "lifecycle": 100, - "before each": [ - { - "hookId": "h1", - "fnDuration": 400, - "afterFnDuration": 200 - } - ], - "test": { - "fnDuration": 400, - "afterFnDuration": 200 + "videoTimestamp": 9999, + "duration": 1234, + "startedAt": "2018-02-01T20:14:19.323Z", + "screenshots": [ + { + "name": null, + "takenAt": "2018-02-01T20:14:19.323Z", + "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_hook_spec.coffee/simple failing hook spec -- beforeEach hooks -- never gets here -- before each hook (failed) (attempt 2).png", + "height": 720, + "width": 1280 } - }, - "failedFromHookId": "h1", - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234, - "videoTimestamp": 9999 + ] } ] }, { - "testId": "r6", "title": [ "simple failing hook spec", "pending", @@ -958,16 +757,14 @@ exports['failing with retries enabled'] = { { "state": "pending", "error": null, - "timings": null, - "failedFromHookId": null, - "wallClockStartedAt": null, - "wallClockDuration": null, - "videoTimestamp": null + "videoTimestamp": null, + "duration": null, + "startedAt": null, + "screenshots": [] } ] }, { - "testId": "r8", "title": [ "simple failing hook spec", "afterEach hooks", @@ -984,24 +781,18 @@ exports['failing with retries enabled'] = { "message": "fail2\n\nBecause this error occurred during a `after each` hook we are skipping the remaining tests in the current suite: `afterEach hooks`", "stack": "[stack trace lines]" }, - "timings": { - "lifecycle": 100, - "test": { - "fnDuration": 400, - "afterFnDuration": 200 - }, - "after each": [ - { - "hookId": "h2", - "fnDuration": 400, - "afterFnDuration": 200 - } - ] - }, - "failedFromHookId": "h2", - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234, - "videoTimestamp": 9999 + "videoTimestamp": 9999, + "duration": 1234, + "startedAt": "2018-02-01T20:14:19.323Z", + "screenshots": [ + { + "name": null, + "takenAt": "2018-02-01T20:14:19.323Z", + "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_hook_spec.coffee/simple failing hook spec -- afterEach hooks -- runs this -- after each hook (failed).png", + "height": 720, + "width": 1280 + } + ] }, { "state": "failed", @@ -1010,29 +801,22 @@ exports['failing with retries enabled'] = { "message": "fail2", "stack": "[stack trace lines]" }, - "timings": { - "lifecycle": 100, - "test": { - "fnDuration": 400, - "afterFnDuration": 200 - }, - "after each": [ - { - "hookId": "h2", - "fnDuration": 400, - "afterFnDuration": 200 - } - ] - }, - "failedFromHookId": "h2", - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234, - "videoTimestamp": 9999 + "videoTimestamp": 9999, + "duration": 1234, + "startedAt": "2018-02-01T20:14:19.323Z", + "screenshots": [ + { + "name": null, + "takenAt": "2018-02-01T20:14:19.323Z", + "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_hook_spec.coffee/simple failing hook spec -- afterEach hooks -- runs this -- after each hook (failed) (attempt 2).png", + "height": 720, + "width": 1280 + } + ] } ] }, { - "testId": "r9", "title": [ "simple failing hook spec", "afterEach hooks", @@ -1045,16 +829,14 @@ exports['failing with retries enabled'] = { { "state": "skipped", "error": null, - "timings": null, - "failedFromHookId": null, - "wallClockStartedAt": null, - "wallClockDuration": null, - "videoTimestamp": null + "videoTimestamp": null, + "duration": null, + "startedAt": null, + "screenshots": [] } ] }, { - "testId": "r11", "title": [ "simple failing hook spec", "after hooks", @@ -1067,22 +849,14 @@ exports['failing with retries enabled'] = { { "state": "passed", "error": null, - "timings": { - "lifecycle": 100, - "test": { - "fnDuration": 400, - "afterFnDuration": 200 - } - }, - "failedFromHookId": null, - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234, - "videoTimestamp": 9999 + "videoTimestamp": 9999, + "duration": 1234, + "startedAt": "2018-02-01T20:14:19.323Z", + "screenshots": [] } ] }, { - "testId": "r12", "title": [ "simple failing hook spec", "after hooks", @@ -1099,82 +873,24 @@ exports['failing with retries enabled'] = { "message": "fail3\n\nBecause this error occurred during a `after all` hook we are skipping the remaining tests in the current suite: `after hooks`\n\nAlthough you have test retries enabled, we do not retry tests when `before all` or `after all` hooks fail", "stack": "[stack trace lines]" }, - "timings": { - "lifecycle": 100, - "test": { - "fnDuration": 400, - "afterFnDuration": 200 - }, - "after all": [ - { - "hookId": "h3", - "fnDuration": 400, - "afterFnDuration": 200 - } - ] - }, - "failedFromHookId": "h3", - "wallClockStartedAt": "2018-02-01T20:14:19.323Z", - "wallClockDuration": 1234, - "videoTimestamp": 9999 + "videoTimestamp": 9999, + "duration": 1234, + "startedAt": "2018-02-01T20:14:19.323Z", + "screenshots": [ + { + "name": null, + "takenAt": "2018-02-01T20:14:19.323Z", + "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_hook_spec.coffee/simple failing hook spec -- after hooks -- fails on this -- after all hook (failed).png", + "height": 720, + "width": 1280 + } + ] } ] } ], "error": null, "video": "/foo/bar/.projects/e2e/cypress/videos/simple_failing_hook_spec.coffee.mp4", - "screenshots": [ - { - "screenshotId": "some-random-id", - "name": null, - "testId": "r4", - "testAttemptIndex": 0, - "takenAt": "2018-02-01T20:14:19.323Z", - "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_hook_spec.coffee/simple failing hook spec -- beforeEach hooks -- never gets here (failed).png", - "height": 720, - "width": 1280 - }, - { - "screenshotId": "some-random-id", - "name": null, - "testId": "r4", - "testAttemptIndex": 1, - "takenAt": "2018-02-01T20:14:19.323Z", - "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_hook_spec.coffee/simple failing hook spec -- beforeEach hooks -- never gets here -- before each hook (failed) (attempt 2).png", - "height": 720, - "width": 1280 - }, - { - "screenshotId": "some-random-id", - "name": null, - "testId": "r8", - "testAttemptIndex": 0, - "takenAt": "2018-02-01T20:14:19.323Z", - "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_hook_spec.coffee/simple failing hook spec -- afterEach hooks -- runs this -- after each hook (failed).png", - "height": 720, - "width": 1280 - }, - { - "screenshotId": "some-random-id", - "name": null, - "testId": "r8", - "testAttemptIndex": 1, - "takenAt": "2018-02-01T20:14:19.323Z", - "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_hook_spec.coffee/simple failing hook spec -- afterEach hooks -- runs this -- after each hook (failed) (attempt 2).png", - "height": 720, - "width": 1280 - }, - { - "screenshotId": "some-random-id", - "name": null, - "testId": "r12", - "testAttemptIndex": 0, - "takenAt": "2018-02-01T20:14:19.323Z", - "path": "/foo/bar/.projects/e2e/cypress/screenshots/simple_failing_hook_spec.coffee/simple failing hook spec -- after hooks -- fails on this -- after all hook (failed).png", - "height": 720, - "width": 1280 - } - ], "spec": { "name": "simple_failing_hook_spec.coffee", "relative": "cypress/integration/simple_failing_hook_spec.coffee", diff --git a/packages/server/__snapshots__/7_record_spec.js b/packages/server/__snapshots__/7_record_spec.js index 75d9ba2d15c2..a9d38b4101fd 100644 --- a/packages/server/__snapshots__/7_record_spec.js +++ b/packages/server/__snapshots__/7_record_spec.js @@ -2157,3 +2157,268 @@ Details: ` + +exports['e2e record passing passes 2'] = [ + { + "stats": { + "suites": 1, + "tests": 2, + "passes": 0, + "pending": 0, + "skipped": 1, + "failures": 1, + "wallClockStartedAt": "2018-02-01T20:14:19.323Z", + "wallClockEndedAt": "2018-02-01T20:14:19.323Z", + "wallClockDuration": 1234 + }, + "tests": [ + { + "testId": "r3", + "title": [ + "record fails", + "fails 1" + ], + "state": "failed", + "body": "function() {}", + "displayError": "Error: foo\n\nBecause this error occurred during a `before each` hook we are skipping the remaining tests in the current suite: `record fails`\n [stack trace lines]", + "attempts": [ + { + "state": "failed", + "error": { + "name": "Error", + "message": "foo\n\nBecause this error occurred during a `before each` hook we are skipping the remaining tests in the current suite: `record fails`", + "stack": "[stack trace lines]" + }, + "timings": { + "lifecycle": 100, + "before each": [ + { + "hookId": "h1", + "fnDuration": 400, + "afterFnDuration": 200 + } + ] + }, + "failedFromHookId": "h1", + "wallClockStartedAt": "2018-02-01T20:14:19.323Z", + "wallClockDuration": 1234, + "videoTimestamp": 9999 + } + ] + }, + { + "testId": "r4", + "title": [ + "record fails", + "is skipped" + ], + "state": "skipped", + "body": "function() {}", + "displayError": null, + "attempts": [ + { + "state": "skipped", + "error": null, + "timings": null, + "failedFromHookId": null, + "wallClockStartedAt": null, + "wallClockDuration": null, + "videoTimestamp": null + } + ] + } + ], + "error": null, + "video": true, + "hooks": [ + { + "hookId": "h1", + "hookName": "before each", + "title": [ + "\"before each\" hook" + ], + "body": "function() {\n throw new Error(\"foo\");\n }" + } + ], + "screenshots": [ + { + "screenshotId": "some-random-id", + "name": null, + "testId": "r3", + "testAttemptIndex": 0, + "takenAt": "2018-02-01T20:14:19.323Z", + "height": 720, + "width": 1280 + } + ], + "cypressConfig": {}, + "reporterStats": { + "suites": 1, + "tests": 1, + "passes": 0, + "pending": 0, + "failures": 1, + "start": "2018-02-01T20:14:19.323Z", + "end": "2018-02-01T20:14:19.323Z", + "duration": 1234 + } + }, + { + "stats": { + "suites": 1, + "tests": 2, + "passes": 1, + "pending": 1, + "skipped": 0, + "failures": 0, + "wallClockStartedAt": "2018-02-01T20:14:19.323Z", + "wallClockEndedAt": "2018-02-01T20:14:19.323Z", + "wallClockDuration": 1234 + }, + "tests": [ + { + "testId": "r3", + "title": [ + "record pass", + "passes" + ], + "state": "passed", + "body": "function() {\n cy.visit(\"/scrollable.html\");\n return cy.viewport(400, 400).get(\"#box\").screenshot('yay it passes');\n }", + "displayError": null, + "attempts": [ + { + "state": "passed", + "error": null, + "timings": { + "lifecycle": 100, + "test": { + "fnDuration": 400, + "afterFnDuration": 200 + } + }, + "failedFromHookId": null, + "wallClockStartedAt": "2018-02-01T20:14:19.323Z", + "wallClockDuration": 1234, + "videoTimestamp": 9999 + } + ] + }, + { + "testId": "r4", + "title": [ + "record pass", + "is pending" + ], + "state": "pending", + "body": "", + "displayError": null, + "attempts": [ + { + "state": "pending", + "error": null, + "timings": null, + "failedFromHookId": null, + "wallClockStartedAt": null, + "wallClockDuration": null, + "videoTimestamp": null + } + ] + } + ], + "error": null, + "video": true, + "hooks": [], + "screenshots": [ + { + "screenshotId": "some-random-id", + "name": "yay it passes", + "testId": "r3", + "testAttemptIndex": 0, + "takenAt": "2018-02-01T20:14:19.323Z", + "height": 1002, + "width": 202 + } + ], + "cypressConfig": {}, + "reporterStats": { + "suites": 1, + "tests": 2, + "passes": 1, + "pending": 1, + "failures": 0, + "start": "2018-02-01T20:14:19.323Z", + "end": "2018-02-01T20:14:19.323Z", + "duration": 1234 + } + }, + { + "stats": { + "suites": 0, + "tests": 1, + "passes": 0, + "pending": 0, + "skipped": 0, + "failures": 1, + "wallClockStartedAt": "2018-02-01T20:14:19.323Z", + "wallClockEndedAt": "2018-02-01T20:14:19.323Z", + "wallClockDuration": 1234 + }, + "tests": [ + { + "testId": "r2", + "title": [ + "An uncaught error was detected outside of a test" + ], + "state": "failed", + "body": "function throwErr() {\n throw err;\n }", + "displayError": "Error: The following error originated from your test code, not from Cypress.\n\n > instantly fails\n\nWhen Cypress detects uncaught errors originating from your test code it will automatically fail the current test.\n\nCypress could not associate this error to any specific test.\n\nWe dynamically generated a new test to display this failure.\n [stack trace lines]", + "attempts": [ + { + "state": "failed", + "error": { + "name": "Error", + "message": "The following error originated from your test code, not from Cypress.\n\n > instantly fails\n\nWhen Cypress detects uncaught errors originating from your test code it will automatically fail the current test.\n\nCypress could not associate this error to any specific test.\n\nWe dynamically generated a new test to display this failure.", + "stack": "[stack trace lines]" + }, + "timings": { + "lifecycle": 100, + "test": { + "fnDuration": 400, + "afterFnDuration": 200 + } + }, + "failedFromHookId": null, + "wallClockStartedAt": "2018-02-01T20:14:19.323Z", + "wallClockDuration": 1234, + "videoTimestamp": 9999 + } + ] + } + ], + "error": null, + "video": true, + "hooks": [], + "screenshots": [ + { + "screenshotId": "some-random-id", + "name": null, + "testId": "r2", + "testAttemptIndex": 0, + "takenAt": "2018-02-01T20:14:19.323Z", + "height": 720, + "width": 1280 + } + ], + "cypressConfig": {}, + "reporterStats": { + "suites": 0, + "tests": 1, + "passes": 0, + "pending": 0, + "failures": 1, + "start": "2018-02-01T20:14:19.323Z", + "end": "2018-02-01T20:14:19.323Z", + "duration": 1234 + } + } +] diff --git a/packages/server/lib/modes/run.js b/packages/server/lib/modes/run.js index 50155414ebe6..dff9299ec8a2 100644 --- a/packages/server/lib/modes/run.js +++ b/packages/server/lib/modes/run.js @@ -1,4 +1,4 @@ -/* eslint-disable no-console */ +/* eslint-disable no-console, @cypress/dev/arrow-body-multiline-braces */ const _ = require('lodash') const la = require('lazy-ass') const pkg = require('@packages/root') @@ -30,6 +30,7 @@ const electronApp = require('../util/electron-app') const settings = require('../util/settings') const chromePolicyCheck = require('../util/chrome_policy_check') const experiments = require('../experiments') +const objUtils = require('../util/obj_utils') const DELAY_TO_LET_VIDEO_FINISH_MS = 1000 @@ -1260,7 +1261,41 @@ module.exports = { debug('final results of all runs: %o', results) - return writeOutput(outputPath, results).return(results) + const { each, remapKeys, remove, renameKey, setValue } = objUtils + + // Remap module API result json to remove private props and rename props to make more user-friendly + const moduleAPIResults = remapKeys(results, { + runs: each((run) => ({ + tests: each((test) => ({ + attempts: each((attempt, i) => ({ + timings: remove, + failedFromHookId: remove, + wallClockDuration: renameKey('duration'), + wallClockStartedAt: renameKey('startedAt'), + wallClockEndedAt: renameKey('endedAt'), + screenshots: setValue( + _(run.screenshots) + .filter({ testId: test.testId, testAttemptIndex: i }) + .map((screenshot) => _.omit(screenshot, + ['screenshotId', 'testId', 'testAttemptIndex'])) + .value(), + ), + })), + testId: remove, + })), + hooks: each({ + hookId: remove, + }), + stats: { + wallClockDuration: renameKey('duration'), + wallClockStartedAt: renameKey('startedAt'), + wallClockEndedAt: renameKey('endedAt'), + }, + screenshots: remove, + })), + }) + + return writeOutput(outputPath, moduleAPIResults).return(moduleAPIResults) }) }, diff --git a/packages/server/lib/util/obj_utils.ts b/packages/server/lib/util/obj_utils.ts new file mode 100644 index 000000000000..c768bd2daca0 --- /dev/null +++ b/packages/server/lib/util/obj_utils.ts @@ -0,0 +1,48 @@ +import _ from 'lodash' + +const traverse = (obj, mapObj, parent?, key?) => { + if (_.isFunction(mapObj)) { + mapObj(parent, key, obj) + + return + } + + if (_.isObject(mapObj)) { + _.each(mapObj, (mapVal, mapKey) => { + traverse(obj[mapKey], mapVal, obj, mapKey) + }) + } +} + +export const remapKeys = (fromObj, toObj) => { + fromObj = _.cloneDeep(fromObj) + + traverse(fromObj, toObj) + + return fromObj +} + +export const remove = (obj, key) => delete obj[key] + +export const renameKey = (newName) => { + return (obj, key, val) => { + delete obj[key] + obj[newName] = val + } +} + +export const setValue = (defaultVal) => { + return (obj, key) => { + obj[key] = defaultVal + } +} + +export const each = (fn) => { + return (__, ___, arr) => { + return _.each(arr, (val, i) => { + const mapObj = _.isFunction(fn) ? fn(val, i) : fn + + traverse(val, mapObj) + }) + } +} diff --git a/packages/server/test/e2e/5_spec_isolation_spec.js b/packages/server/test/e2e/5_spec_isolation_spec.js index 4eb22675fbb5..17f47704770c 100644 --- a/packages/server/test/e2e/5_spec_isolation_spec.js +++ b/packages/server/test/e2e/5_spec_isolation_spec.js @@ -1,17 +1,16 @@ -const _ = require('lodash') +// TODO: rename this file to 5_module_api_spec + const path = require('path') -const moment = require('moment') const snapshot = require('snap-shot-it') const fs = require('../../lib/util/fs') const e2e = require('../support/helpers/e2e').default const Fixtures = require('../support/helpers/fixtures') +const { expectCorrectModuleApiResult } = require('../support/helpers/resultsUtils') const e2ePath = Fixtures.projectPath('e2e') const outputPath = path.join(e2ePath, 'output.json') -const STATIC_DATE = '2018-02-01T20:14:19.323Z' - const specs = [ 'simple_passing_spec.coffee', 'simple_hooks_spec.coffee', @@ -19,178 +18,6 @@ const specs = [ 'simple_failing_h*_spec.coffee', // simple failing hook spec ].join(',') -const expectStartToBeBeforeEnd = function (obj, start, end) { - const s = _.get(obj, start) - const e = _.get(obj, end) - - expect( - moment(s).isBefore(e), - `expected start: ${s} to be before end: ${e}`, - ).to.be.true - - // once valid, mutate and set static dates - _.set(obj, start, STATIC_DATE) - - return _.set(obj, end, STATIC_DATE) -} - -const expectDurationWithin = function (obj, duration, low, high, reset) { - const d = _.get(obj, duration) - - // bail if we don't have a duration - if (!_.isNumber(d)) { - return - } - - // ensure the duration is within range - expect(d, duration).to.be.within(low, high) - - // once valid, mutate and set static range - return _.set(obj, duration, reset) -} - -const normalizeTestTimings = function (obj, timings) { - const t = _.get(obj, timings) - - // bail if we don't have any timings - if (!t) { - return - } - - return _.set(obj, 'timings', _.mapValues(t, (val, key) => { - switch (key) { - case 'lifecycle': - // ensure that lifecycle is under 500ms - expect(val, 'lifecycle').to.be.within(0, 500) - - // reset to 100 - return 100 - case 'test': - // ensure test fn duration is within 1500ms - expectDurationWithin(val, 'fnDuration', 0, 1500, 400) - // ensure test after fn duration is within 500ms - expectDurationWithin(val, 'afterFnDuration', 0, 500, 200) - - return val - default: - return _.map(val, (hook) => { - // ensure test fn duration is within 1500ms - expectDurationWithin(hook, 'fnDuration', 0, 1500, 400) - // ensure test after fn duration is within 500ms - expectDurationWithin(hook, 'afterFnDuration', 0, 500, 200) - - return hook - }) - } - })) -} - -const expectRunsToHaveCorrectStats = (runs = []) => { - return runs.forEach((run) => { - expectStartToBeBeforeEnd(run, 'stats.wallClockStartedAt', 'stats.wallClockEndedAt') - expectStartToBeBeforeEnd(run, 'reporterStats.start', 'reporterStats.end') - - // grab all the wallclock durations for all test (and retried attempts) - // because our duration should be at least this - - const attempts = _.flatMap(run.tests, (test) => test.attempts) - - const wallClocks = _.sumBy(attempts, 'wallClockDuration') - - // ensure each run's duration is around the sum - // of all tests wallclock duration - expectDurationWithin( - run, - 'stats.wallClockDuration', - wallClocks, - wallClocks + 400, // add 400ms to account for padding - 1234, - ) - - expectDurationWithin( - run, - 'reporterStats.duration', - wallClocks, - wallClocks + 400, // add 400ms to account for padding - 1234, - ) - - const addFnAndAfterFn = (obj) => { - return obj.fnDuration + obj.afterFnDuration - } - - run.spec.absolute = e2e.normalizeStdout(run.spec.absolute) - - _.each(run.tests, (test) => { - if (test.displayError) { - test.displayError = e2e.normalizeStdout(test.displayError) - } - }) - - // now make sure that each tests wallclock duration - // is around the sum of all of its timings - attempts.forEach((attempt) => { - // cannot sum an object, must use array of values - const timings = _.sumBy(_.values(attempt.timings), (val) => { - if (_.isArray(val)) { - // array for hooks - return _.sumBy(val, addFnAndAfterFn) - } - - if (_.isObject(val)) { - // obj for test itself - return addFnAndAfterFn(val) - } - - return val - }) - - expectDurationWithin( - attempt, - 'wallClockDuration', - timings, - timings + 80, // add 80ms to account for padding - 1234, - ) - - // now reset all the test timings - normalizeTestTimings(attempt, 'timings') - - // normalize stack - if (attempt.error) { - attempt.error.stack = e2e.normalizeStdout(attempt.error.stack).trim() - } - - if (attempt.wallClockStartedAt) { - const d = new Date(attempt.wallClockStartedAt) - - expect(d.toJSON()).to.eq(attempt.wallClockStartedAt) - attempt.wallClockStartedAt = STATIC_DATE - - expect(attempt.videoTimestamp).to.be.a('number') - attempt.videoTimestamp = 9999 - } - }) - - // normalize video path - run.video = e2e.normalizeStdout(run.video) - - run.screenshots = _.map(run.screenshots, (screenshot) => { - expect(screenshot.screenshotId).to.have.length('5') - - const d = new Date(screenshot.takenAt) - - expect(d.toJSON()).to.eq(screenshot.takenAt) - screenshot.takenAt = STATIC_DATE - - screenshot.screenshotId = 'some-random-id' - screenshot.path = e2e.normalizeStdout(screenshot.path) - - return screenshot - }) - }) -} - describe('e2e spec_isolation', () => { e2e.setup() @@ -199,66 +26,19 @@ describe('e2e spec_isolation', () => { outputPath, snapshot: false, expectedExitCode: 5, - onRun (exec) { - return exec() - .then(() => { - // now what we want to do is read in the outputPath - // and snapshot it so its what we expect after normalizing it - return 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, - ) + async onRun (exec) { + await exec() - // should be 4 total runs - expect(json.runs).to.have.length(4) - - expectRunsToHaveCorrectStats(json.runs) + // now what we want to do is read in the outputPath + // and snapshot it so its what we expect after normalizing it + const json = await fs.readJsonAsync(outputPath) - return snapshot('e2e spec isolation fails', json, { allowSharedSnapshot: true }) - }) + // also mutates into normalized obj ready for snapshot + expectCorrectModuleApiResult(json, { + e2ePath, runs: 4, }) + + snapshot('e2e spec isolation fails', json, { allowSharedSnapshot: true }) }, }) @@ -274,31 +54,8 @@ describe('e2e spec_isolation', () => { await execFn() const json = await fs.readJsonAsync(outputPath) - expect(json.config).to.be.an('object') - expect(json.config.projectName).to.eq('e2e') - expect(json.config.projectRoot).to.eq(e2ePath) - 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', - }) - - expect(json.totalTests).to.eq(_.sum([json.totalFailed, json.totalPassed, json.totalPending, json.totalSkipped])) - expectStartToBeBeforeEnd(json, 'startedTestsAt', 'endedTestsAt') - expectDurationWithin(json, 'totalDuration', _.sumBy(json.runs, 'stats.wallClockDuration'), _.sumBy(json.runs, 'stats.wallClockDuration'), 5555) - expect(json.runs).to.have.length(1) - expectRunsToHaveCorrectStats(json.runs) + // also mutates into normalized obj ready for snapshot + expectCorrectModuleApiResult(json, { e2ePath, runs: 1 }) snapshot('failing with retries enabled', json) }, diff --git a/packages/server/test/e2e/7_record_spec.js b/packages/server/test/e2e/7_record_spec.js index 3a98528e9015..295fe8755fcd 100644 --- a/packages/server/test/e2e/7_record_spec.js +++ b/packages/server/test/e2e/7_record_spec.js @@ -3,10 +3,11 @@ const path = require('path') const Promise = require('bluebird') const bodyParser = require('body-parser') const jsonSchemas = require('@cypress/json-schemas').api +const snapshot = require('snap-shot-it') const e2e = require('../support/helpers/e2e').default const fs = require('../../lib/util/fs') const Fixtures = require('../support/helpers/fixtures') - +const { expectRunsToHaveCorrectTimings } = require('../support/helpers/resultsUtils') const postRunResponseWithWarnings = jsonSchemas.getExample('postRunResponse')('2.2.0') const postRunResponse = _.assign({}, postRunResponseWithWarnings, { warnings: [] }) const postRunInstanceResponse = jsonSchemas.getExample('postRunInstanceResponse')('2.1.0') @@ -228,8 +229,8 @@ describe('e2e record', () => { context('passing', () => { setup(defaultRoutes) - it('passes', function () { - return e2e.exec(this, { + it('passes', async function () { + const { stdout } = await e2e.exec(this, { key: 'f858a2bc-b469-4e48-be67-0876339ee7e1', spec: 'record*', record: true, @@ -237,176 +238,179 @@ describe('e2e record', () => { outputPath, expectedExitCode: 3, }) - .get('stdout') - .then((stdout) => { - console.log(stdout) - expect(stdout).to.include('Run URL:') - expect(stdout).to.include(runUrl) - - const urls = getRequestUrls() - - // first create run request - expect(urls[0]).to.eq('POST /runs') - - // grab the first set of 4 - const firstInstanceSet = urls.slice(1, 5) - - expect(firstInstanceSet).to.deep.eq([ - `POST /runs/${runId}/instances`, - `PUT /instances/${instanceId}`, - 'PUT /videos/video.mp4', - `PUT /instances/${instanceId}/stdout`, - ]) - - // grab the second set of 5 - const secondInstanceSet = urls.slice(5, 10) - - console.log(secondInstanceSet) - expect(secondInstanceSet).to.have.members([ - `POST /runs/${runId}/instances`, - `PUT /instances/${instanceId}`, - 'PUT /videos/video.mp4', - 'PUT /screenshots/1.png', - `PUT /instances/${instanceId}/stdout`, - ]) - - // grab the third set of 5 - const thirdInstanceSet = urls.slice(10, 14) - - // no video because no tests failed - expect(thirdInstanceSet).to.deep.eq([ - `POST /runs/${runId}/instances`, - `PUT /instances/${instanceId}`, - 'PUT /screenshots/1.png', - `PUT /instances/${instanceId}/stdout`, - ]) - - // grab the forth set of 5 - const forthInstanceSet = urls.slice(14, 19) - - expect(forthInstanceSet).to.have.members([ - `POST /runs/${runId}/instances`, - `PUT /instances/${instanceId}`, - 'PUT /videos/video.mp4', - 'PUT /screenshots/1.png', - `PUT /instances/${instanceId}/stdout`, - ]) - - const postRun = requests[0] - - // ensure its relative to projectRoot - expect(postRun.body.specs).to.deep.eq([ - 'cypress/integration/record_error_spec.coffee', - 'cypress/integration/record_fail_spec.coffee', - 'cypress/integration/record_pass_spec.coffee', - 'cypress/integration/record_uncaught_spec.coffee', - ]) - - expect(postRun.body.projectId).to.eq('pid123') - expect(postRun.body.recordKey).to.eq('f858a2bc-b469-4e48-be67-0876339ee7e1') - expect(postRun.body.specPattern).to.eq('cypress/integration/record*') - - const firstInstance = requests[1] - - expect(firstInstance.body.groupId).to.eq(groupId) - expect(firstInstance.body.machineId).to.eq(machineId) - expect(firstInstance.body.spec).to.eq( - 'cypress/integration/record_error_spec.coffee', - ) - const firstInstancePut = requests[2] + console.log(stdout) + expect(stdout).to.include('Run URL:') + expect(stdout).to.include(runUrl) - expect(firstInstancePut.body.error).to.include('Oops...we found an error preparing this test file') - expect(firstInstancePut.body.tests).to.be.null - expect(firstInstancePut.body.hooks).to.be.null - expect(firstInstancePut.body.screenshots).to.have.length(0) - expect(firstInstancePut.body.stats.tests).to.eq(0) - expect(firstInstancePut.body.stats.failures).to.eq(1) - expect(firstInstancePut.body.stats.passes).to.eq(0) + const urls = getRequestUrls() - const firstInstanceStdout = requests[4] + // first create run request + expect(urls[0]).to.eq('POST /runs') - expect(firstInstanceStdout.body.stdout).to.include('record_error_spec.coffee') + // grab the first set of 4 + const firstInstanceSet = urls.slice(1, 5) - const secondInstance = requests[5] + expect(firstInstanceSet).to.deep.eq([ + `POST /runs/${runId}/instances`, + `PUT /instances/${instanceId}`, + 'PUT /videos/video.mp4', + `PUT /instances/${instanceId}/stdout`, + ]) - expect(secondInstance.body.groupId).to.eq(groupId) - expect(secondInstance.body.machineId).to.eq(machineId) - expect(secondInstance.body.spec).to.eq( - 'cypress/integration/record_fail_spec.coffee', - ) + // grab the second set of 5 + const secondInstanceSet = urls.slice(5, 10) - const secondInstancePut = requests[6] + console.log(secondInstanceSet) + expect(secondInstanceSet).to.have.members([ + `POST /runs/${runId}/instances`, + `PUT /instances/${instanceId}`, + 'PUT /videos/video.mp4', + 'PUT /screenshots/1.png', + `PUT /instances/${instanceId}/stdout`, + ]) - 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.screenshots).to.have.length(1) - expect(secondInstancePut.body.stats.tests).to.eq(2) - expect(secondInstancePut.body.stats.failures).to.eq(1) - expect(secondInstancePut.body.stats.passes).to.eq(0) - expect(secondInstancePut.body.stats.skipped).to.eq(1) + // grab the third set of 5 + const thirdInstanceSet = urls.slice(10, 14) - const secondInstanceStdout = requests[9] + // no video because no tests failed + expect(thirdInstanceSet).to.deep.eq([ + `POST /runs/${runId}/instances`, + `PUT /instances/${instanceId}`, + 'PUT /screenshots/1.png', + `PUT /instances/${instanceId}/stdout`, + ]) - expect(secondInstanceStdout.body.stdout).to.include('record_fail_spec.coffee') - expect(secondInstanceStdout.body.stdout).not.to.include('record_error_spec.coffee') + // grab the forth set of 5 + const forthInstanceSet = urls.slice(14, 19) - const thirdInstance = requests[10] + expect(forthInstanceSet).to.have.members([ + `POST /runs/${runId}/instances`, + `PUT /instances/${instanceId}`, + 'PUT /videos/video.mp4', + 'PUT /screenshots/1.png', + `PUT /instances/${instanceId}/stdout`, + ]) - expect(thirdInstance.body.groupId).to.eq(groupId) - expect(thirdInstance.body.machineId).to.eq(machineId) - expect(thirdInstance.body.spec).to.eq( - 'cypress/integration/record_pass_spec.coffee', - ) + const postRun = requests[0] - const thirdInstancePut = requests[11] + // ensure its relative to projectRoot + expect(postRun.body.specs).to.deep.eq([ + 'cypress/integration/record_error_spec.coffee', + 'cypress/integration/record_fail_spec.coffee', + 'cypress/integration/record_pass_spec.coffee', + 'cypress/integration/record_uncaught_spec.coffee', + ]) - 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.screenshots).to.have.length(1) - expect(thirdInstancePut.body.stats.tests).to.eq(2) - expect(thirdInstancePut.body.stats.passes).to.eq(1) - expect(thirdInstancePut.body.stats.failures).to.eq(0) - expect(thirdInstancePut.body.stats.pending).to.eq(1) + expect(postRun.body.projectId).to.eq('pid123') + expect(postRun.body.recordKey).to.eq('f858a2bc-b469-4e48-be67-0876339ee7e1') + expect(postRun.body.specPattern).to.eq('cypress/integration/record*') - const thirdInstanceStdout = requests[13] + const firstInstance = requests[1] - expect(thirdInstanceStdout.body.stdout).to.include('record_pass_spec.coffee') - expect(thirdInstanceStdout.body.stdout).not.to.include('record_error_spec.coffee') - expect(thirdInstanceStdout.body.stdout).not.to.include('record_fail_spec.coffee') + expect(firstInstance.body.groupId).to.eq(groupId) + expect(firstInstance.body.machineId).to.eq(machineId) + expect(firstInstance.body.spec).to.eq( + 'cypress/integration/record_error_spec.coffee', + ) - const fourthInstance = requests[14] + const firstInstancePut = requests[2] - expect(fourthInstance.body.groupId).to.eq(groupId) - expect(fourthInstance.body.machineId).to.eq(machineId) - expect(fourthInstance.body.spec).to.eq( - 'cypress/integration/record_uncaught_spec.coffee', - ) + expect(firstInstancePut.body.error).to.include('Oops...we found an error preparing this test file') + expect(firstInstancePut.body.tests).to.be.null + expect(firstInstancePut.body.hooks).to.be.null + expect(firstInstancePut.body.screenshots).to.have.length(0) + expect(firstInstancePut.body.stats.tests).to.eq(0) + expect(firstInstancePut.body.stats.failures).to.eq(1) + expect(firstInstancePut.body.stats.passes).to.eq(0) - const fourthInstancePut = requests[15] + const firstInstanceStdout = requests[4] - 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.screenshots).to.have.length(1) - expect(fourthInstancePut.body.stats.tests).to.eq(1) - expect(fourthInstancePut.body.stats.failures).to.eq(1) - expect(fourthInstancePut.body.stats.passes).to.eq(0) + expect(firstInstanceStdout.body.stdout).to.include('record_error_spec.coffee') - const forthInstanceStdout = requests[18] + const secondInstance = requests[5] - expect(forthInstanceStdout.body.stdout).to.include('record_uncaught_spec.coffee') - expect(forthInstanceStdout.body.stdout).not.to.include('record_error_spec.coffee') - expect(forthInstanceStdout.body.stdout).not.to.include('record_fail_spec.coffee') - expect(forthInstanceStdout.body.stdout).not.to.include('record_pass_spec.coffee') + expect(secondInstance.body.groupId).to.eq(groupId) + expect(secondInstance.body.machineId).to.eq(machineId) + expect(secondInstance.body.spec).to.eq( + 'cypress/integration/record_fail_spec.coffee', + ) - return fs.readJsonAsync(outputPath) - .then((results) => { - expect(results.runUrl).to.equal(runUrl) - }) - }) + const 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.screenshots).to.have.length(1) + expect(secondInstancePut.body.stats.tests).to.eq(2) + expect(secondInstancePut.body.stats.failures).to.eq(1) + expect(secondInstancePut.body.stats.passes).to.eq(0) + expect(secondInstancePut.body.stats.skipped).to.eq(1) + + const secondInstanceStdout = requests[9] + + expect(secondInstanceStdout.body.stdout).to.include('record_fail_spec.coffee') + expect(secondInstanceStdout.body.stdout).not.to.include('record_error_spec.coffee') + + const thirdInstance = requests[10] + + expect(thirdInstance.body.groupId).to.eq(groupId) + expect(thirdInstance.body.machineId).to.eq(machineId) + expect(thirdInstance.body.spec).to.eq( + 'cypress/integration/record_pass_spec.coffee', + ) + + const 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.screenshots).to.have.length(1) + expect(thirdInstancePut.body.stats.tests).to.eq(2) + expect(thirdInstancePut.body.stats.passes).to.eq(1) + expect(thirdInstancePut.body.stats.failures).to.eq(0) + expect(thirdInstancePut.body.stats.pending).to.eq(1) + + const thirdInstanceStdout = requests[13] + + expect(thirdInstanceStdout.body.stdout).to.include('record_pass_spec.coffee') + expect(thirdInstanceStdout.body.stdout).not.to.include('record_error_spec.coffee') + expect(thirdInstanceStdout.body.stdout).not.to.include('record_fail_spec.coffee') + + const fourthInstance = requests[14] + + expect(fourthInstance.body.groupId).to.eq(groupId) + expect(fourthInstance.body.machineId).to.eq(machineId) + expect(fourthInstance.body.spec).to.eq( + 'cypress/integration/record_uncaught_spec.coffee', + ) + + const 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.screenshots).to.have.length(1) + expect(fourthInstancePut.body.stats.tests).to.eq(1) + expect(fourthInstancePut.body.stats.failures).to.eq(1) + expect(fourthInstancePut.body.stats.passes).to.eq(0) + + const forthInstanceStdout = requests[18] + + expect(forthInstanceStdout.body.stdout).to.include('record_uncaught_spec.coffee') + expect(forthInstanceStdout.body.stdout).not.to.include('record_error_spec.coffee') + expect(forthInstanceStdout.body.stdout).not.to.include('record_fail_spec.coffee') + expect(forthInstanceStdout.body.stdout).not.to.include('record_pass_spec.coffee') + + const runs = requests.filter((v) => v.body.tests).map((v) => v.body) + + expectRunsToHaveCorrectTimings(runs) + + snapshot(runs) + + const results = await fs.readJsonAsync(outputPath) + + expect(results.runUrl).to.equal(runUrl) }) }) diff --git a/packages/server/test/scripts/run.js b/packages/server/test/scripts/run.js index 0a1ab38e1c52..43c28a91a67f 100644 --- a/packages/server/test/scripts/run.js +++ b/packages/server/test/scripts/run.js @@ -155,6 +155,10 @@ if (options.exit === false) { env.NO_EXIT = '1' } +if (options['cypress-inspect-brk']) { + env.CYPRESS_INSPECT_BRK = '1' +} + const cmd = `${commandAndArguments.command} ${ commandAndArguments.args.join(' ')}` diff --git a/packages/server/test/support/helpers/e2e.ts b/packages/server/test/support/helpers/e2e.ts index 3147f43e158e..1cb89a46ffa5 100644 --- a/packages/server/test/support/helpers/e2e.ts +++ b/packages/server/test/support/helpers/e2e.ts @@ -442,6 +442,14 @@ const e2e = { }, options (ctx, options = {}) { + if (options.inspectBrk != null) { + throw new Error(` + passing { inspectBrk: true } to e2e options is no longer supported + Please pass the --cypress-inspect-brk flag to the test command instead + e.g. "yarn test test/e2e/1_async_timeouts_spec.js --cypress-inspect-brk" + `) + } + _.defaults(options, { browser: 'electron', headed: process.env.HEADED || false, @@ -452,6 +460,7 @@ const e2e = { sanitizeScreenshotDimensions: false, normalizeStdoutAvailableBrowsers: true, noExit: process.env.NO_EXIT, + inspectBrk: process.env.CYPRESS_INSPECT_BRK, }) if (options.exit != null) { diff --git a/packages/server/test/support/helpers/resultsUtils.ts b/packages/server/test/support/helpers/resultsUtils.ts new file mode 100644 index 000000000000..ff57eaeec8a4 --- /dev/null +++ b/packages/server/test/support/helpers/resultsUtils.ts @@ -0,0 +1,302 @@ +import e2e from './e2e' +import moment from 'moment' +import _ from 'lodash' + +const expect = global.expect as unknown as Chai.ExpectStatic + +const STATIC_DATE = '2018-02-01T20:14:19.323Z' + +const expectDurationWithin = function (obj, duration, low, high, reset) { + const d = _.get(obj, duration) + + // bail if we don't have a duration + if (!_.isNumber(d)) { + return + } + + // ensure the duration is within range + expect(d, duration).to.be.within(low, high) + + // once valid, mutate and set static range + return _.set(obj, duration, reset) +} + +const expectStartToBeBeforeEnd = function (obj, start, end) { + const s = _.get(obj, start) + const e = _.get(obj, end) + + expect( + moment(s).isBefore(e), + `expected start: ${s} to be before end: ${e}`, + ).to.be.true + + // once valid, mutate and set static dates + _.set(obj, start, STATIC_DATE) + + return _.set(obj, end, STATIC_DATE) +} + +const normalizeTestTimings = function (obj, timings) { + const t = _.get(obj, timings) + + // bail if we don't have any timings + if (!t) { + return + } + + return _.set(obj, 'timings', _.mapValues(t, (val, key) => { + switch (key) { + case 'lifecycle': + // ensure that lifecycle is under 500ms + expect(val, 'lifecycle').to.be.within(0, 500) + + // reset to 100 + return 100 + case 'test': + // ensure test fn duration is within 1500ms + expectDurationWithin(val, 'fnDuration', 0, 1500, 400) + // ensure test after fn duration is within 500ms + expectDurationWithin(val, 'afterFnDuration', 0, 500, 200) + + return val + default: + return _.map(val, (hook) => { + // ensure test fn duration is within 1500ms + expectDurationWithin(hook, 'fnDuration', 0, 1500, 400) + // ensure test after fn duration is within 500ms + expectDurationWithin(hook, 'afterFnDuration', 0, 500, 200) + + return hook + }) + } + })) +} + +export const expectRunsToHaveCorrectTimings = (runs = []) => { + runs.forEach((run) => { + expect(run.cypressConfig).to.be.a('object') + run.cypressConfig = {} + expectStartToBeBeforeEnd(run, 'stats.wallClockStartedAt', 'stats.wallClockEndedAt') + expectStartToBeBeforeEnd(run, 'reporterStats.start', 'reporterStats.end') + + // grab all the wallclock durations for all test (and retried attempts) + // because our duration should be at least this + + const attempts = _.flatMap(run.tests, (test) => test.attempts) + + const wallClocks = _.sumBy(attempts, 'wallClockDuration') + + // ensure each run's duration is around the sum + // of all tests wallclock duration + + // TODO: if this remains flaky, increase padding here + // and add an additional non-e2e performance test with baseline p95 + expectDurationWithin( + run, + 'stats.wallClockDuration', + wallClocks, + wallClocks + 400, // add 400ms to account for padding + 1234, + ) + + expectDurationWithin( + run, + 'reporterStats.duration', + wallClocks, + wallClocks + 400, // add 400ms to account for padding + 1234, + ) + + const addFnAndAfterFn = (obj) => { + return obj.fnDuration + obj.afterFnDuration + } + + _.each(run.tests, (test) => { + if (test.displayError) { + test.displayError = e2e.normalizeStdout(test.displayError) + } + }) + + // now make sure that each tests wallclock duration + // is around the sum of all of its timings + attempts.forEach((attempt) => { + if (attempt.error) { + attempt.error.stack = e2e.normalizeStdout(attempt.error.stack).trim() + } + + // cannot sum an object, must use array of values + const timings = _.sumBy(_.values(attempt.timings), (val) => { + if (_.isArray(val)) { + // array for hooks + return _.sumBy(val, addFnAndAfterFn) + } + + if (_.isObject(val)) { + // obj for test itself + return addFnAndAfterFn(val) + } + + return val + }) + + expectDurationWithin( + attempt, + 'wallClockDuration', + timings, + timings + 80, // add 80ms to account for padding + 1234, + ) + + // now reset all the test timings + normalizeTestTimings(attempt, 'timings') + + if (attempt.wallClockStartedAt) { + const d = new Date(attempt.wallClockStartedAt) + + expect(d.toJSON()).to.eq(attempt.wallClockStartedAt) + attempt.wallClockStartedAt = STATIC_DATE + + expect(attempt.videoTimestamp).to.be.a('number') + attempt.videoTimestamp = 9999 + } + }) + + run.screenshots = _.map(run.screenshots, (screenshot) => { + expect(screenshot.screenshotId).to.have.length(5) + screenshot.screenshotId = 'some-random-id' + + const d = new Date(screenshot.takenAt) + + expect(d.toJSON()).to.eq(screenshot.takenAt) + screenshot.takenAt = STATIC_DATE + + return screenshot + }) + }) +} + +export const expectCorrectModuleApiResult = (json, opts: { + e2ePath: string + runs: number +}) => { + // should be n runs + expect(json.runs).to.have.length(opts.runs) + + // 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(opts.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, + ]), + ) + + // ensure totalDuration matches all of the stats durations + expectDurationWithin( + json, + 'totalDuration', + _.sumBy(json.runs, 'stats.duration'), + _.sumBy(json.runs, 'stats.duration'), + 5555, + ) + + expectStartToBeBeforeEnd(json, 'startedTestsAt', 'endedTestsAt') + + json.runs.forEach((run) => { + expectStartToBeBeforeEnd(run, 'stats.startedAt', 'stats.endedAt') + expectStartToBeBeforeEnd(run, 'reporterStats.start', 'reporterStats.end') + + const attempts = _.flatMap(run.tests, (test) => test.attempts) + + const wallClocks = _.sumBy(attempts, 'duration') + + // ensure each run's duration is around the sum + // of all tests wallclock duration + expectDurationWithin( + run, + 'stats.duration', + wallClocks, + wallClocks + 400, // add 400ms to account for padding + 1234, + ) + + expectDurationWithin( + run, + 'reporterStats.duration', + wallClocks, + wallClocks + 400, // add 400ms to account for padding + 1234, + ) + + run.spec.absolute = e2e.normalizeStdout(run.spec.absolute) + + _.each(run.tests, (test) => { + if (test.displayError) { + test.displayError = e2e.normalizeStdout(test.displayError) + } + }) + + attempts.forEach((attempt) => { + // normalize stack + if (attempt.error) { + attempt.error.stack = e2e.normalizeStdout(attempt.error.stack).trim() + } + + // normalize startedAt + if (attempt.startedAt) { + const d = new Date(attempt.startedAt) + + expect(d.toJSON()).to.eq(attempt.startedAt) + attempt.startedAt = STATIC_DATE + + expect(attempt.videoTimestamp).to.be.a('number') + attempt.videoTimestamp = 9999 + } + + attempt.screenshots.forEach((screenshot) => { + // expect(screenshot.screenshotId).to.have.length(5) + + const d = new Date(screenshot.takenAt) + + expect(d.toJSON()).to.eq(screenshot.takenAt) + screenshot.takenAt = STATIC_DATE + + // screenshot.screenshotId = 'some-random-id' + screenshot.path = e2e.normalizeStdout(screenshot.path) + }) + + if (attempt.duration) { + expect(attempt.duration).to.be.a('number') + attempt.duration = 1234 + } + }) + + // normalize video path + run.video = e2e.normalizeStdout(run.video) + }) +} diff --git a/packages/server/test/unit/util/obj_utils_spec.ts b/packages/server/test/unit/util/obj_utils_spec.ts new file mode 100644 index 000000000000..8de42a8cecf2 --- /dev/null +++ b/packages/server/test/unit/util/obj_utils_spec.ts @@ -0,0 +1,29 @@ +import * as objUtils from '../../../lib/util/obj_utils' +import { expect } from 'chai' + +const { each, remapKeys, renameKey, setValue, remove } = objUtils + +describe('obj_utils', () => { + context('#remapKeys', () => { + it('returns cloned object with renamed, removed, and modified key/values', () => { + const initial = { + foos: [{ id: 1, renameMe: 'foo' }, { id: 2, renameMe: 'bar' }, { id: 3, renameMe: 'baz' }], + foos2: [{ id: 1, renameMe: 'foo' }, { id: 2, renameMe: 'bar' }, { id: 3, renameMe: 'baz' }], + bar: 'foobar', + } + + const result = remapKeys(initial, { + foos: each((foo, i) => ({ renameMe: renameKey('newName'), id: setValue(i) })), + foos2: each({ renameMe: remove }), + bar: remove, + }) + + expect(result).deep.eq({ + foos: [{ id: 0, newName: 'foo' }, { id: 1, newName: 'bar' }, { id: 2, newName: 'baz' }], + foos2: [{ id: 1 }, { id: 2 }, { id: 3 }], + }) + + expect(result).not.eq(initial) + }) + }) +}) From 21748faddc738b3e4c12a574c8879bd84be42d6d Mon Sep 17 00:00:00 2001 From: Kevin Old Date: Tue, 11 Aug 2020 09:24:44 -0500 Subject: [PATCH 35/42] chore: test v5 binary against Cypress Real World App (#8240) --- circle.yml | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index 506a08063c09..3d4b99adf48d 100644 --- a/circle.yml +++ b/circle.yml @@ -254,6 +254,10 @@ commands: description: Whether to use wait-on to wait on a server to be booted type: string default: "" + server-start-command: + description: Server start command for repo + type: string + default: "npm start --if-present" steps: - attach_workspace: at: ~/ @@ -290,7 +294,7 @@ commands: command: npm run build --if-present - run: working_directory: /tmp/<> - command: npm start --if-present + command: <> background: true - run: condition: <> @@ -1488,6 +1492,16 @@ jobs: browser: firefox command: "npm run cypress:run" + "test-binary-against-cypress-realworld-app": + <<: *defaults + steps: + - test-binary-against-repo: + repo: cypress-realworld-app + browser: chrome + server-start-command: "npm run start:ci" + command: "npm run cypress:run" + wait-on: http://localhost:3000 + test-binary-as-specific-user: <<: *defaults steps: @@ -1831,6 +1845,16 @@ linux-workflow: &linux-workflow <<: *testBinaryFirefox - test-binary-against-piechopper-firefox: <<: *testBinaryFirefox + - test-binary-against-cypress-realworld-app: + filters: + branches: + only: + - develop + - kevin-v5.0-release-rwa + - v5.0-release + requires: + - build-npm-package + - build-binary - test-binary-as-specific-user: name: "test binary as a non-root user" From dc523eb390be6b7fe1aa56699d41b5f0f69bd3f8 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 11 Aug 2020 13:14:33 -0400 Subject: [PATCH 36/42] chore: install specific Node version on Mac before building binary (#8242) --- circle.yml | 110 ++++++++++++++++++++++---- electron-builder.json | 3 +- package.json | 4 +- scripts/check-node-version.js | 13 ---- scripts/load-nvm.sh | 11 +++ yarn.lock | 142 ++++++++++++++++++---------------- 6 files changed, 184 insertions(+), 99 deletions(-) create mode 100755 scripts/load-nvm.sh diff --git a/circle.yml b/circle.yml index 3d4b99adf48d..bf2a857d04f2 100644 --- a/circle.yml +++ b/circle.yml @@ -9,6 +9,7 @@ macBuildFilters: &macBuildFilters only: - develop - v5.0-release + - install-node-on-circleci-mac defaults: &defaults parallelism: 1 @@ -60,12 +61,37 @@ executors: # https://circleci.com/docs/2.0/testing-ios/#supported-xcode-versions mac: macos: - ## Node 12.12.0 (yarn 1.19.1) + # Executor should have Node >= required version xcode: "11.2.1" environment: PLATFORM: mac commands: + install-required-node: + # https://discuss.circleci.com/t/switch-nodejs-version-on-machine-executor-solved/26675/2 + description: Install Node version matching .node-version + steps: + - run: + name: Install NVM + # TODO: determine why we get the missing .nvmrc file error + command: | + export NODE_VERSION=$(cat .node-version) + echo "Installing Node $NODE_VERSION" + cp .node-version .nvmrc + curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.35.3/install.sh | bash + - run: + # https://github.com/nvm-sh/nvm#nvmrc + name: Install Node + command: | + . ./scripts/load-nvm.sh + echo "before nvm install" + nvm install + echo "before nvm use" + nvm use + echo "before nvm alias default" + nvm alias default + node --version + install-latest-chrome: description: Install latest Google Chrome (stable) parameters: @@ -367,6 +393,7 @@ jobs: <<: *defaults steps: - checkout + - install-required-node - run: name: Print working folder command: echo $PWD @@ -375,20 +402,36 @@ jobs: command: echo $(yarn global bin) - run: name: print Node version - command: node -v + command: | + . ./scripts/load-nvm.sh + echo "nvm use default" + nvm use default + node -v - run: name: print yarn version command: yarn -v - - run: yarn check-node-version + - run: + name: check Node version + command: | + . ./scripts/load-nvm.sh + yarn check-node-version ## make sure the TERM is set to 'xterm' in node (Linux only) ## else colors (and tests) will fail ## See the following information ## * http://andykdocs.de/development/Docker/Fixing+the+Docker+TERM+variable+issue ## * https://unix.stackexchange.com/questions/43945/whats-the-difference-between-various-term-variables - - run: yarn check-terminal + - run: + name: Check terminal + command: | + . ./scripts/load-nvm.sh + yarn check-terminal - - run: yarn stop-only-all + - run: + name: Stop .only + command: | + . ./scripts/load-nvm.sh + yarn stop-only-all - restore_cache: name: Restore yarn cache @@ -399,7 +442,11 @@ jobs: - run: ls $(yarn global bin)/../lib/node_modules # try several times, because flaky NPM installs ... - - run: yarn --frozen-lockfile || yarn --frozen-lockfile + - run: + name: install and build + command: | + . ./scripts/load-nvm.sh + yarn --frozen-lockfile || yarn --frozen-lockfile - run: name: Top level packages command: yarn list --depth=0 || true @@ -417,8 +464,13 @@ jobs: steps: - attach_workspace: at: ~/ - ## this will catch .only's in js/coffee as well - - run: yarn lint + - install-required-node + ## this will catch ".only"s in js/coffee as well + - run: + name: Linting 🧹 + command: | + . ./scripts/load-nvm.sh + yarn lint - run: name: cypress info (dev) command: node cli/bin/cypress info --dev @@ -530,10 +582,18 @@ jobs: steps: - attach_workspace: at: ~/ - # make sure mocha runs - - run: yarn test-mocha + - install-required-node + - run: + name: Mocha tests + command: | + . ./scripts/load-nvm.sh + yarn test-mocha # test binary build code - - run: yarn test-scripts + - run: + name: Test scripts + command: | + . ./scripts/load-nvm.sh + yarn test-scripts server-unit-tests: <<: *defaults @@ -969,6 +1029,7 @@ jobs: - attach_workspace: at: ~/ - run: $(yarn bin)/print-arch + - install-required-node - run: environment: DEBUG: electron-builder,electron-osx-sign* @@ -977,8 +1038,15 @@ jobs: # if this is a forked pull request, the NEXT_DEV_VERSION environment variable # won't be set and we will use default version, since we are not going to # upload the dev binary build anywhere - command: yarn binary-build --platform $PLATFORM --version ${NEXT_DEV_VERSION:-0.0.0-development} - - run: yarn binary-zip --platform $PLATFORM + command: | + . ./scripts/load-nvm.sh + node --version + yarn binary-build --platform $PLATFORM --version ${NEXT_DEV_VERSION:-0.0.0-development} + - run: + name: Zip the binary + command: | + . ./scripts/load-nvm.sh + yarn binary-zip --platform $PLATFORM # Cypress binary file should be zipped to cypress.zip - run: ls -l *.zip - store-npm-logs @@ -1011,6 +1079,7 @@ jobs: steps: - clone-repo-and-checkout-release-branch: repo: cypress-example-kitchensink + - install-required-node - run: name: Install prod dependencies command: yarn --production @@ -1022,7 +1091,9 @@ jobs: background: true - run: name: Run Kitchensink example project - command: yarn cypress:run --project /tmp/cypress-example-kitchensink + command: | + . ./scripts/load-nvm.sh + yarn cypress:run --project /tmp/cypress-example-kitchensink - store_artifacts: path: /tmp/cypress-example-kitchensink/cypress/screenshots - store_artifacts: @@ -1072,13 +1143,20 @@ jobs: steps: - attach_workspace: at: ~/ - - run: yarn check-next-dev-version + - install-required-node + - run: + name: Check next dev version + command: | + . ./scripts/load-nvm.sh + yarn check-next-dev-version - run: name: bump NPM version command: yarn version --no-git-tag-version --new-version ${NEXT_DEV_VERSION:-0.0.0-development} - run: name: build NPM package - command: yarn build --scope cypress + command: | + . ./scripts/load-nvm.sh + yarn build --scope cypress - run: command: ls -la types working_directory: cli/build diff --git a/electron-builder.json b/electron-builder.json index 75b5402687ae..d9685b1ef238 100644 --- a/electron-builder.json +++ b/electron-builder.json @@ -19,7 +19,8 @@ "./build/mac/Cypress.app/Contents/Resources/app/packages/server/node_modules/trash/lib/macos-trash", "./build/mac/Cypress.app/Contents/Resources/app/packages/server/node_modules/babel-plugin-add-module-exports/node_modules/fsevents/build/Release/.node", "./build/mac/Cypress.app/Contents/Resources/app/packages/server/node_modules/babel-plugin-add-module-exports/node_modules/fsevents/build/Release/fse.node", - "./build/mac/Cypress.app/Contents/Resources/app/packages/server/node_modules/fsevents/fsevents.node" + "./build/mac/Cypress.app/Contents/Resources/app/packages/server/node_modules/fsevents/fsevents.node", + "./build/mac/Cypress.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Helpers/chrome_crashpad_handler" ] }, "linux": { diff --git a/package.json b/package.json index 546cfe200c91..51e3951dcc4c 100644 --- a/package.json +++ b/package.json @@ -121,8 +121,8 @@ "debug": "4.1.1", "decaffeinate": "6.0.9", "del": "3.0.0", - "electron-builder": "22.6.1", - "electron-notarize": "0.2.1", + "electron-builder": "22.8.0", + "electron-notarize": "1.0.0", "enzyme-adapter-react-16": "1.12.1", "eslint": "6.8.0", "eslint-plugin-cypress": "2.11.1", diff --git a/scripts/check-node-version.js b/scripts/check-node-version.js index 9ccc2c3b42b3..aef89a5b5a71 100644 --- a/scripts/check-node-version.js +++ b/scripts/check-node-version.js @@ -3,23 +3,10 @@ const assert = require('assert') // TODO make this check a 3rd party little tool -// on CircleCI Mac machine, we need to use on of the laer executors -// that already has Node 10 / 11 -const isMac = () => { - return os.platform() === 'darwin' -} - const isWindows = () => { return os.platform() === 'win32' } -if (isMac() && process.env.CIRCLECI) { - // eslint-disable-next-line no-console - console.log('Skipping Node version check on CircleCI Mac') - - return -} - // if we're windows + in appveyor... if (isWindows() && process.env.APPVEYOR) { // check to ensure that the cpuArch + nodeArch are in sync diff --git a/scripts/load-nvm.sh b/scripts/load-nvm.sh new file mode 100755 index 000000000000..d2259dfb81ca --- /dev/null +++ b/scripts/load-nvm.sh @@ -0,0 +1,11 @@ +# loads previously installed NVM +# USE: +# - run: +# name: check Node version +# command: | +# . ./scripts/load-nvm.sh +# yarn check-node-version + +export NVM_DIR="$HOME/.nvm" +[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" +export NODE_VERSION=$(cat .node-version) diff --git a/yarn.lock b/yarn.lock index fd9b2dc9a4ae..2361f8981fd1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4098,13 +4098,20 @@ resolved "https://registry.yarnpkg.com/@types/filewriter/-/filewriter-0.0.28.tgz#c054e8af4d9dd75db4e63abc76f885168714d4b3" integrity sha1-wFTor02d11205jq8dviFFocU1LM= -"@types/fs-extra@^8.0.1", "@types/fs-extra@^8.1.0": +"@types/fs-extra@^8.0.1": version "8.1.1" resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.1.1.tgz#1e49f22d09aa46e19b51c0b013cb63d0d923a068" integrity sha512-TcUlBem321DFQzBNuz8p0CLLKp0VvF/XH9E4KHNmgwyp4E3AfgI5cjiIVZWlbfThBop2qxFIh4+LeY6hVWWZ2w== dependencies: "@types/node" "*" +"@types/fs-extra@^9.0.1": + version "9.0.1" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.1.tgz#91c8fc4c51f6d5dbe44c2ca9ab09310bd00c7918" + integrity sha512-B42Sxuaz09MhC3DDeW5kubRcQ5by4iuVQ0cRRWM2lggLzAa/KVom0Aft/208NgMvNQQZ86s5rVcqDdn/SH0/mg== + dependencies: + "@types/node" "*" + "@types/glob@7.1.1": version "7.1.1" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" @@ -5297,26 +5304,26 @@ app-builder-bin@3.5.9: resolved "https://registry.yarnpkg.com/app-builder-bin/-/app-builder-bin-3.5.9.tgz#a3ac0c25286bac68357321cb2eaf7128b0bc0a4f" integrity sha512-NSjtqZ3x2kYiDp3Qezsgukx/AUzKPr3Xgf9by4cYt05ILWGAptepeeu0Uv+7MO+41o6ujhLixTou8979JGg2Kg== -app-builder-lib@22.6.1: - version "22.6.1" - resolved "https://registry.yarnpkg.com/app-builder-lib/-/app-builder-lib-22.6.1.tgz#f17bfbde1bbb26ae438e450b66005bf6714feb30" - integrity sha512-ENL7r+H7IBfDb4faeLASgndsXrAT7AV7m7yJjcpbFDXYma6an7ZWGFIvR0HJrsfiC5TIB8kdLJ/aMSImrrSi/Q== +app-builder-lib@22.8.0: + version "22.8.0" + resolved "https://registry.yarnpkg.com/app-builder-lib/-/app-builder-lib-22.8.0.tgz#342a8976f50ae35cfd07412dbfd4f6c895b32eac" + integrity sha512-RGaIRjCUrqkmh6QOGsyekQPEOaVynHfmeh8JZuyUymFYUOFdzBbPamkA2nhBVBTkkgfjRHsxK7LhedFKPzvWEQ== dependencies: "7zip-bin" "~5.0.3" "@develar/schema-utils" "~2.6.5" async-exit-hook "^2.0.1" bluebird-lst "^1.0.9" - builder-util "22.6.1" - builder-util-runtime "8.7.0" + builder-util "22.8.0" + builder-util-runtime "8.7.2" chromium-pickle-js "^0.2.0" debug "^4.1.1" - ejs "^3.1.2" - electron-publish "22.6.1" - fs-extra "^9.0.0" - hosted-git-info "^3.0.4" + ejs "^3.1.3" + electron-publish "22.8.0" + fs-extra "^9.0.1" + hosted-git-info "^3.0.5" is-ci "^2.0.0" isbinaryfile "^4.0.6" - js-yaml "^3.13.1" + js-yaml "^3.14.0" lazy-val "^1.0.4" minimatch "^3.0.4" normalize-package-data "^2.5.0" @@ -7686,30 +7693,30 @@ buffer@~5.2.1: base64-js "^1.0.2" ieee754 "^1.1.4" -builder-util-runtime@8.7.0: - version "8.7.0" - resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-8.7.0.tgz#e48ad004835c8284662e8eaf47a53468c66e8e8d" - integrity sha512-G1AqqVM2vYTrSFR982c1NNzwXKrGLQjVjaZaWQdn4O6Z3YKjdMDofw88aD9jpyK9ZXkrCxR0tI3Qe9wNbyTlXg== +builder-util-runtime@8.7.2: + version "8.7.2" + resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-8.7.2.tgz#d93afc71428a12789b437e13850e1fa7da956d72" + integrity sha512-xBqv+8bg6cfnzAQK1k3OGpfaHg+QkPgIgpEkXNhouZ0WiUkyZCftuRc2LYzQrLucFywpa14Xbc6+hTbpq83yRA== dependencies: debug "^4.1.1" sax "^1.2.4" -builder-util@22.6.1: - version "22.6.1" - resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-22.6.1.tgz#78172c3634da460325277ef798994592e595eff3" - integrity sha512-A9cF+bSHqRTSKIUHEyE92Tl0Uh12N7yZRH9bccIL3gRUwtp6ulF28LsjNIWTSQ1clZo2M895cT5PCrKzjPQFVg== +builder-util@22.8.0: + version "22.8.0" + resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-22.8.0.tgz#01684085d1f2370b1bd182f69cbd007426f63f64" + integrity sha512-H80P1JzVy3TGpi63x81epQDK24XalL034+jAZlrPb5IhLtYmnNNdxCCAVJvg3VjSISd73Y71O+uhqCxWpqbPHw== dependencies: "7zip-bin" "~5.0.3" "@types/debug" "^4.1.5" - "@types/fs-extra" "^8.1.0" + "@types/fs-extra" "^9.0.1" app-builder-bin "3.5.9" bluebird-lst "^1.0.9" - builder-util-runtime "8.7.0" - chalk "^4.0.0" + builder-util-runtime "8.7.2" + chalk "^4.1.0" debug "^4.1.1" - fs-extra "^9.0.0" + fs-extra "^9.0.1" is-ci "^2.0.0" - js-yaml "^3.13.1" + js-yaml "^3.14.0" source-map-support "^0.5.19" stat-mode "^1.0.0" temp-file "^3.3.7" @@ -10294,16 +10301,16 @@ disparity@3.0.0: ansi-styles "^4.1.0" diff "^4.0.1" -dmg-builder@22.6.1: - version "22.6.1" - resolved "https://registry.yarnpkg.com/dmg-builder/-/dmg-builder-22.6.1.tgz#5777a9eb6904db5bf1f4c69addbf462f5f9bf4e4" - integrity sha512-jUTN0acP15puzevtQASj7QEPgUGpedWSuSnOwR/++JbeYRTwU2oro09h/KZnaeMcxgxjdmT3tYLJeY1XUfPbRg== +dmg-builder@22.8.0: + version "22.8.0" + resolved "https://registry.yarnpkg.com/dmg-builder/-/dmg-builder-22.8.0.tgz#2b17127837ed444db3086317eda5cf8912f6e6a9" + integrity sha512-orePWjcrl97SYLA8F/6UUtbXJSoZCYu5KOP1lVqD4LOomr8bjGDyEVYZmZYcg5WqKmXucdmO6OpqgzH/aRMMuA== dependencies: - app-builder-lib "22.6.1" - builder-util "22.6.1" - fs-extra "^9.0.0" - iconv-lite "^0.5.1" - js-yaml "^3.13.1" + app-builder-lib "22.8.0" + builder-util "22.8.0" + fs-extra "^9.0.1" + iconv-lite "^0.6.2" + js-yaml "^3.14.0" sanitize-filename "^1.6.3" doctrine@^2.1.0: @@ -10545,26 +10552,26 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -ejs@^3.1.2: +ejs@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.3.tgz#514d967a8894084d18d3d47bd169a1c0560f093d" integrity sha512-wmtrUGyfSC23GC/B1SMv2ogAUgbQEtDmTIhfqielrG5ExIM9TP4UoYdi90jLF1aTcsWCJNEO0UrgKzP0y3nTSg== dependencies: jake "^10.6.1" -electron-builder@22.6.1: - version "22.6.1" - resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-22.6.1.tgz#9cc704356ecba1342ff1c94d610aad1f3c6a8b02" - integrity sha512-3/VNg9GfXKHM53TilFtfF1+bsAR8THK1XHgeqCpsiequa02J9jTPc/DhpCUKQPkrs6/EIGxP7uboop7XYoew0Q== +electron-builder@22.8.0: + version "22.8.0" + resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-22.8.0.tgz#d2c9fc5438c834e41fd794a271fca200165a3bad" + integrity sha512-dUv4F3srJouqxhWivtKqSoQP4Df6vYgjooGdzms+iYMTFi9f0b4LlEbr7kgsPvte8zAglee7VOGOODkCRJDkUQ== dependencies: "@types/yargs" "^15.0.5" - app-builder-lib "22.6.1" + app-builder-lib "22.8.0" bluebird-lst "^1.0.9" - builder-util "22.6.1" - builder-util-runtime "8.7.0" - chalk "^4.0.0" - dmg-builder "22.6.1" - fs-extra "^9.0.0" + builder-util "22.8.0" + builder-util-runtime "8.7.2" + chalk "^4.1.0" + dmg-builder "22.8.0" + fs-extra "^9.0.1" is-ci "^2.0.0" lazy-val "^1.0.4" read-config-file "6.0.0" @@ -10595,7 +10602,15 @@ electron-is-dev@^1.0.1: resolved "https://registry.yarnpkg.com/electron-is-dev/-/electron-is-dev-1.2.0.tgz#2e5cea0a1b3ccf1c86f577cee77363ef55deb05e" integrity sha512-R1oD5gMBPS7PVU8gJwH6CtT0e6VSoD0+SzSnYpNm+dBkcijgA+K7VAMHDfnRq/lkKPZArpzplTW6jfiMYosdzw== -electron-notarize@0.2.1, electron-notarize@^0.2.0: +electron-notarize@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/electron-notarize/-/electron-notarize-1.0.0.tgz#bc925b1ccc3f79e58e029e8c4706572b01a9fd8f" + integrity sha512-dsib1IAquMn0onCrNMJ6gtEIZn/azG8hZMCYOuZIMVMUeRMgBYHK1s5TK9P8xAcrAjh/2aN5WYHzgVSWX314og== + dependencies: + debug "^4.1.1" + fs-extra "^9.0.1" + +electron-notarize@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/electron-notarize/-/electron-notarize-0.2.1.tgz#759e8006decae19134f82996ed910db26d9192cc" integrity sha512-oZ6/NhKeXmEKNROiFmRNfytqu3cxqC95sjooG7kBXQVEUSQkZnbiAhxVh5jXngL881G197pbwpeVPJyM7Ikmxw== @@ -10638,19 +10653,19 @@ electron-packager@14.1.1: semver "^6.0.0" yargs-parser "^16.0.0" -electron-publish@22.6.1: - version "22.6.1" - resolved "https://registry.yarnpkg.com/electron-publish/-/electron-publish-22.6.1.tgz#d5381220d3e0f3bfa869c5a059fd253a561e0f8a" - integrity sha512-/MkS47ospdSfAFW5Jp52OzYou14HhGJpZ51uAc3GJ5rCfACeqpimC/n1ajRLE3hcXxTWfd3t9MCuClq5jrUO5w== +electron-publish@22.8.0: + version "22.8.0" + resolved "https://registry.yarnpkg.com/electron-publish/-/electron-publish-22.8.0.tgz#7f410fe043abc5d3d896c4ee9eea7a43ea352c7d" + integrity sha512-uM0Zdi9hUqqGOrPj478v7toTvV1Kgto1w11rIiI168batiXAJvNLD8VZRfehOrZT0ibUyZlw8FtxoGCrjyHUOw== dependencies: - "@types/fs-extra" "^8.1.0" + "@types/fs-extra" "^9.0.1" bluebird-lst "^1.0.9" - builder-util "22.6.1" - builder-util-runtime "8.7.0" - chalk "^4.0.0" - fs-extra "^9.0.0" + builder-util "22.8.0" + builder-util-runtime "8.7.2" + chalk "^4.1.0" + fs-extra "^9.0.1" lazy-val "^1.0.4" - mime "^2.4.5" + mime "^2.4.6" electron-to-chromium@^1.3.488: version "1.3.496" @@ -12535,7 +12550,7 @@ fs-extra@^7.0.0, fs-extra@^7.0.1: jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^9.0.0, fs-extra@^9.0.1: +fs-extra@^9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== @@ -13797,7 +13812,7 @@ hosted-git-info@^2.1.4, hosted-git-info@^2.7.1: resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== -hosted-git-info@^3.0.4: +hosted-git-info@^3.0.5: version "3.0.5" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-3.0.5.tgz#bea87905ef7317442e8df3087faa3c842397df03" integrity sha512-i4dpK6xj9BIpVOTboXIlKG9+8HMKggcrMX7WA24xZtKwX0TPelq/rbaS5rCKeNX8sJXZJGdSxpnEGtta+wismQ== @@ -14081,13 +14096,6 @@ iconv-lite@0.5.0: dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@^0.5.1: - version "0.5.2" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.5.2.tgz#af6d628dccfb463b7364d97f715e4b74b8c8c2b8" - integrity sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag== - dependencies: - safer-buffer ">= 2.1.2 < 3" - iconv-lite@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.2.tgz#ce13d1875b0c3a674bd6a04b7f76b01b1b6ded01" @@ -15707,7 +15715,7 @@ js-yaml@3.13.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@3.14.0, js-yaml@3.x, js-yaml@^3.13.1, js-yaml@^3.7.0: +js-yaml@3.14.0, js-yaml@3.x, js-yaml@^3.13.1, js-yaml@^3.14.0, js-yaml@^3.7.0: version "3.14.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== @@ -17389,7 +17397,7 @@ mime@2.4.4: resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5" integrity sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA== -mime@^2.0.3, mime@^2.4.5: +mime@^2.0.3, mime@^2.4.6: version "2.4.6" resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1" integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA== From 3a16e23bc68887c85a89972a2c8cac88067aa825 Mon Sep 17 00:00:00 2001 From: Chris Breiding Date: Wed, 12 Aug 2020 14:45:39 -0400 Subject: [PATCH 37/42] fix: Fix crashing due to rerunning on initial bundle of specs (#8264) * ignore sendFile EPIPE errors * upgrade @cypress/webpack-preprocessor to 5.4.4 --- packages/server/lib/controllers/spec.js | 37 +++++++++++++++++-------- packages/server/package.json | 2 +- packages/server/test/unit/spec_spec.js | 23 +++++++++++++-- yarn.lock | 8 +++--- 4 files changed, 51 insertions(+), 19 deletions(-) diff --git a/packages/server/lib/controllers/spec.js b/packages/server/lib/controllers/spec.js index b227cc9aa9b4..1cd4a4ce5606 100644 --- a/packages/server/lib/controllers/spec.js +++ b/packages/server/lib/controllers/spec.js @@ -3,6 +3,26 @@ const Promise = require('bluebird') const errors = require('../errors') const preprocessor = require('../plugins/preprocessor') +const ignoreECONNABORTED = () => { + // https://github.com/cypress-io/cypress/issues/1877 + // now that we are properly catching errors from + // res.sendFile, sendFile will reject if the browser aborts + // its internal requests (as it shuts down) with + // ECONNABORTED. This happens because if a previous spec + // file is unable to be transpiled correctly, we immediately + // shut down the run, which closes the browser, triggering + // the browser to abort the request which would end up here + // and display two different errors. +} + +const ignoreEPIPE = () => { + // 'write EPIPE' errors can occur if a spec is served and then rerun + // quickly because it's trying to send the spec file to a socket that + // has already been closed. this can be ignored because it means + // another version of the file has already been sent and will + // be loaded by the browser instead +} + module.exports = { handle (spec, req, res, config, next, onError) { debug('request for %o', { spec }) @@ -22,17 +42,12 @@ module.exports = { const sendFile = Promise.promisify(res.sendFile.bind(res)) return sendFile(filePath) - }).catch({ code: 'ECONNABORTED' }, (err) => { - // https://github.com/cypress-io/cypress/issues/1877 - // now that we are properly catching errors from - // res.sendFile, sendFile will reject if the browser aborts - // its internal requests (as it shuts down) with - // ECONNABORTED. This happens because if a previous spec - // file is unable to be transpiled correctly, we immediately - // shut down the run, which closes the browser, triggering - // the browser to abort the request which would end up here - // and display two different errors. - }).catch((err) => { + }) + .catch({ code: 'ECONNABORTED' }, ignoreECONNABORTED) + .catch({ code: 'EPIPE' }, ignoreEPIPE) + .catch((err) => { + debug(`preprocessor error for spec '%s': %s`, spec, err.stack) + if (!config.isTextTerminal) { return res.send(preprocessor.clientSideError(err)) } diff --git a/packages/server/package.json b/packages/server/package.json index 2b23e600ea82..4c70f24d7e8c 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -28,7 +28,7 @@ "@cypress/request": "2.88.5", "@cypress/request-promise": "4.2.6", "@cypress/webpack-batteries-included-preprocessor": "2.0.0", - "@cypress/webpack-preprocessor": "5.4.3", + "@cypress/webpack-preprocessor": "5.4.4", "@ffmpeg-installer/ffmpeg": "1.0.20", "ansi_up": "4.0.4", "black-hole-stream": "0.0.1", diff --git a/packages/server/test/unit/spec_spec.js b/packages/server/test/unit/spec_spec.js index 4a62a6d2a06a..cb26a4630437 100644 --- a/packages/server/test/unit/spec_spec.js +++ b/packages/server/test/unit/spec_spec.js @@ -49,7 +49,6 @@ describe('lib/controllers/spec', () => { return this.handle(specName).then(() => { expect(this.res.send).to.be.called expect(this.res.send.firstCall.args[0]).to.include('(function') - expect(this.res.send.firstCall.args[0]).to.include('Reason request failed') }) }) @@ -60,7 +59,6 @@ describe('lib/controllers/spec', () => { return this.handle(specName, { isTextTerminal: true }).then(() => { expect(this.onError).to.be.called expect(this.onError.lastCall.args[0].message).to.include('Oops...we found an error preparing this test file') - expect(this.onError.lastCall.args[0].message).to.include('Reason request failed') }) }) @@ -72,8 +70,27 @@ describe('lib/controllers/spec', () => { return this.handle(specName).then(() => { expect(this.res.send.firstCall.args[0]).to.include('(function') - expect(this.res.send.firstCall.args[0]).to.include('ENOENT') }) }) + + it('ignores ECONNABORTED errors', function () { + const sendFileErr = new Error('ECONNABORTED') + + sendFileErr.code = 'ECONNABORTED' + + this.res.sendFile.yields(sendFileErr) + + return this.handle(specName) // should resolve, not error + }) + + it('ignores EPIPE errors', function () { + const sendFileErr = new Error('EPIPE') + + sendFileErr.code = 'EPIPE' + + this.res.sendFile.yields(sendFileErr) + + return this.handle(specName) // should resolve, not error + }) }) diff --git a/yarn.lock b/yarn.lock index 2361f8981fd1..17eed3ce185c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1823,10 +1823,10 @@ debug "4.1.1" lodash "4.17.19" -"@cypress/webpack-preprocessor@5.4.3": - version "5.4.3" - resolved "https://registry.yarnpkg.com/@cypress/webpack-preprocessor/-/webpack-preprocessor-5.4.3.tgz#16cd450f15a7e95523a4e4e48b7ced3e318c4957" - integrity sha512-WomyLH7mtCkRpN6unHUfZcX/tl8lfL3MqSniSvlg6wTtQLmlYM3VPDUQjZ4mywQ+TOE9TkSEQyVeJT7SRksxUg== +"@cypress/webpack-preprocessor@5.4.4": + version "5.4.4" + resolved "https://registry.yarnpkg.com/@cypress/webpack-preprocessor/-/webpack-preprocessor-5.4.4.tgz#20adaa338799485aeb67227a9784991c420acd31" + integrity sha512-aNOS4J7vilVO6CgzYUnMCraZTCF+SvQtGTlG0HKdqbpI4+dzCWGRFpwpqiSsCb83m+WAP2gfuGQVaFTCM43JcA== dependencies: bluebird "3.7.1" debug "4.1.1" From 3a2fa26d27954fab37293369c2b9e68388e67771 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Wed, 12 Aug 2020 15:12:09 -0400 Subject: [PATCH 38/42] remove .node watchify files from code signing --- electron-builder.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/electron-builder.json b/electron-builder.json index d9685b1ef238..646f1e830313 100644 --- a/electron-builder.json +++ b/electron-builder.json @@ -12,8 +12,6 @@ "type": "distribution", "binaries": [ "./build/mac/Cypress.app/Contents/Resources/app/packages/server/node_modules/@ffmpeg-installer/darwin-x64/ffmpeg", - "./build/mac/Cypress.app/Contents/Resources/app/packages/server/node_modules/watchify/node_modules/fsevents/build/Release/.node", - "./build/mac/Cypress.app/Contents/Resources/app/packages/server/node_modules/watchify/node_modules/fsevents/build/Release/fse.node", "./build/mac/Cypress.app/Contents/Resources/app/packages/server/node_modules/registry-js/build/Release/registry.node", "./build/mac/Cypress.app/Contents/Resources/app/packages/server/node_modules/term-size/vendor/macos/term-size", "./build/mac/Cypress.app/Contents/Resources/app/packages/server/node_modules/trash/lib/macos-trash", From 467c5157a74b6d1814ad031c430a8b27e76770ca Mon Sep 17 00:00:00 2001 From: Ben Kucera <14625260+Bkucera@users.noreply.github.com> Date: Wed, 12 Aug 2020 15:23:34 -0400 Subject: [PATCH 39/42] upload binary + comment on v5.0-release branch --- circle.yml | 142 ++++++++--------------------------------------------- 1 file changed, 20 insertions(+), 122 deletions(-) diff --git a/circle.yml b/circle.yml index bf2a857d04f2..9afecd9e4f90 100644 --- a/circle.yml +++ b/circle.yml @@ -9,7 +9,6 @@ macBuildFilters: &macBuildFilters only: - develop - v5.0-release - - install-node-on-circleci-mac defaults: &defaults parallelism: 1 @@ -61,37 +60,12 @@ executors: # https://circleci.com/docs/2.0/testing-ios/#supported-xcode-versions mac: macos: - # Executor should have Node >= required version + ## Node 12.12.0 (yarn 1.19.1) xcode: "11.2.1" environment: PLATFORM: mac commands: - install-required-node: - # https://discuss.circleci.com/t/switch-nodejs-version-on-machine-executor-solved/26675/2 - description: Install Node version matching .node-version - steps: - - run: - name: Install NVM - # TODO: determine why we get the missing .nvmrc file error - command: | - export NODE_VERSION=$(cat .node-version) - echo "Installing Node $NODE_VERSION" - cp .node-version .nvmrc - curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.35.3/install.sh | bash - - run: - # https://github.com/nvm-sh/nvm#nvmrc - name: Install Node - command: | - . ./scripts/load-nvm.sh - echo "before nvm install" - nvm install - echo "before nvm use" - nvm use - echo "before nvm alias default" - nvm alias default - node --version - install-latest-chrome: description: Install latest Google Chrome (stable) parameters: @@ -280,10 +254,6 @@ commands: description: Whether to use wait-on to wait on a server to be booted type: string default: "" - server-start-command: - description: Server start command for repo - type: string - default: "npm start --if-present" steps: - attach_workspace: at: ~/ @@ -320,7 +290,7 @@ commands: command: npm run build --if-present - run: working_directory: /tmp/<> - command: <> + command: npm start --if-present background: true - run: condition: <> @@ -393,7 +363,6 @@ jobs: <<: *defaults steps: - checkout - - install-required-node - run: name: Print working folder command: echo $PWD @@ -402,36 +371,20 @@ jobs: command: echo $(yarn global bin) - run: name: print Node version - command: | - . ./scripts/load-nvm.sh - echo "nvm use default" - nvm use default - node -v + command: node -v - run: name: print yarn version command: yarn -v - - run: - name: check Node version - command: | - . ./scripts/load-nvm.sh - yarn check-node-version + - run: yarn check-node-version ## make sure the TERM is set to 'xterm' in node (Linux only) ## else colors (and tests) will fail ## See the following information ## * http://andykdocs.de/development/Docker/Fixing+the+Docker+TERM+variable+issue ## * https://unix.stackexchange.com/questions/43945/whats-the-difference-between-various-term-variables - - run: - name: Check terminal - command: | - . ./scripts/load-nvm.sh - yarn check-terminal + - run: yarn check-terminal - - run: - name: Stop .only - command: | - . ./scripts/load-nvm.sh - yarn stop-only-all + - run: yarn stop-only-all - restore_cache: name: Restore yarn cache @@ -442,11 +395,7 @@ jobs: - run: ls $(yarn global bin)/../lib/node_modules # try several times, because flaky NPM installs ... - - run: - name: install and build - command: | - . ./scripts/load-nvm.sh - yarn --frozen-lockfile || yarn --frozen-lockfile + - run: yarn --frozen-lockfile || yarn --frozen-lockfile - run: name: Top level packages command: yarn list --depth=0 || true @@ -464,13 +413,8 @@ jobs: steps: - attach_workspace: at: ~/ - - install-required-node - ## this will catch ".only"s in js/coffee as well - - run: - name: Linting 🧹 - command: | - . ./scripts/load-nvm.sh - yarn lint + ## this will catch .only's in js/coffee as well + - run: yarn lint - run: name: cypress info (dev) command: node cli/bin/cypress info --dev @@ -582,18 +526,10 @@ jobs: steps: - attach_workspace: at: ~/ - - install-required-node - - run: - name: Mocha tests - command: | - . ./scripts/load-nvm.sh - yarn test-mocha + # make sure mocha runs + - run: yarn test-mocha # test binary build code - - run: - name: Test scripts - command: | - . ./scripts/load-nvm.sh - yarn test-scripts + - run: yarn test-scripts server-unit-tests: <<: *defaults @@ -1029,7 +965,6 @@ jobs: - attach_workspace: at: ~/ - run: $(yarn bin)/print-arch - - install-required-node - run: environment: DEBUG: electron-builder,electron-osx-sign* @@ -1038,15 +973,8 @@ jobs: # if this is a forked pull request, the NEXT_DEV_VERSION environment variable # won't be set and we will use default version, since we are not going to # upload the dev binary build anywhere - command: | - . ./scripts/load-nvm.sh - node --version - yarn binary-build --platform $PLATFORM --version ${NEXT_DEV_VERSION:-0.0.0-development} - - run: - name: Zip the binary - command: | - . ./scripts/load-nvm.sh - yarn binary-zip --platform $PLATFORM + command: yarn binary-build --platform $PLATFORM --version ${NEXT_DEV_VERSION:-0.0.0-development} + - run: yarn binary-zip --platform $PLATFORM # Cypress binary file should be zipped to cypress.zip - run: ls -l *.zip - store-npm-logs @@ -1079,7 +1007,6 @@ jobs: steps: - clone-repo-and-checkout-release-branch: repo: cypress-example-kitchensink - - install-required-node - run: name: Install prod dependencies command: yarn --production @@ -1091,9 +1018,7 @@ jobs: background: true - run: name: Run Kitchensink example project - command: | - . ./scripts/load-nvm.sh - yarn cypress:run --project /tmp/cypress-example-kitchensink + command: yarn cypress:run --project /tmp/cypress-example-kitchensink - store_artifacts: path: /tmp/cypress-example-kitchensink/cypress/screenshots - store_artifacts: @@ -1143,20 +1068,13 @@ jobs: steps: - attach_workspace: at: ~/ - - install-required-node - - run: - name: Check next dev version - command: | - . ./scripts/load-nvm.sh - yarn check-next-dev-version + - run: yarn check-next-dev-version - run: name: bump NPM version command: yarn version --no-git-tag-version --new-version ${NEXT_DEV_VERSION:-0.0.0-development} - run: name: build NPM package - command: | - . ./scripts/load-nvm.sh - yarn build --scope cypress + command: yarn build --scope cypress - run: command: ls -la types working_directory: cli/build @@ -1570,16 +1488,6 @@ jobs: browser: firefox command: "npm run cypress:run" - "test-binary-against-cypress-realworld-app": - <<: *defaults - steps: - - test-binary-against-repo: - repo: cypress-realworld-app - browser: chrome - server-start-command: "npm run start:ci" - command: "npm run cypress:run" - wait-on: http://localhost:3000 - test-binary-as-specific-user: <<: *defaults steps: @@ -1807,7 +1715,7 @@ linux-workflow: &linux-workflow branches: only: - develop - - test-retries + - v5.0-release requires: - build-npm-package - build-binary: @@ -1819,7 +1727,7 @@ linux-workflow: &linux-workflow branches: only: - develop - - test-retries + - v5.0-release requires: - build-binary - test-npm-module-on-minimum-node-version: @@ -1876,7 +1784,7 @@ linux-workflow: &linux-workflow branches: only: - develop - - test-retries + - v5.0-release requires: - upload-npm-package - upload-binary @@ -1923,16 +1831,6 @@ linux-workflow: &linux-workflow <<: *testBinaryFirefox - test-binary-against-piechopper-firefox: <<: *testBinaryFirefox - - test-binary-against-cypress-realworld-app: - filters: - branches: - only: - - develop - - kevin-v5.0-release-rwa - - v5.0-release - requires: - - build-npm-package - - build-binary - test-binary-as-specific-user: name: "test binary as a non-root user" From ab953b2675543039bc7cfe1ac4d46e2f5137176a Mon Sep 17 00:00:00 2001 From: Ben Kucera <14625260+Bkucera@users.noreply.github.com> Date: Wed, 12 Aug 2020 15:40:16 -0400 Subject: [PATCH 40/42] fix circle.yml overwrite --- circle.yml | 136 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 119 insertions(+), 17 deletions(-) diff --git a/circle.yml b/circle.yml index 9afecd9e4f90..83722ddfad3f 100644 --- a/circle.yml +++ b/circle.yml @@ -9,6 +9,7 @@ macBuildFilters: &macBuildFilters only: - develop - v5.0-release + - install-node-on-circleci-mac defaults: &defaults parallelism: 1 @@ -60,12 +61,37 @@ executors: # https://circleci.com/docs/2.0/testing-ios/#supported-xcode-versions mac: macos: - ## Node 12.12.0 (yarn 1.19.1) + # Executor should have Node >= required version xcode: "11.2.1" environment: PLATFORM: mac commands: + install-required-node: + # https://discuss.circleci.com/t/switch-nodejs-version-on-machine-executor-solved/26675/2 + description: Install Node version matching .node-version + steps: + - run: + name: Install NVM + # TODO: determine why we get the missing .nvmrc file error + command: | + export NODE_VERSION=$(cat .node-version) + echo "Installing Node $NODE_VERSION" + cp .node-version .nvmrc + curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.35.3/install.sh | bash + - run: + # https://github.com/nvm-sh/nvm#nvmrc + name: Install Node + command: | + . ./scripts/load-nvm.sh + echo "before nvm install" + nvm install + echo "before nvm use" + nvm use + echo "before nvm alias default" + nvm alias default + node --version + install-latest-chrome: description: Install latest Google Chrome (stable) parameters: @@ -254,6 +280,10 @@ commands: description: Whether to use wait-on to wait on a server to be booted type: string default: "" + server-start-command: + description: Server start command for repo + type: string + default: "npm start --if-present" steps: - attach_workspace: at: ~/ @@ -290,7 +320,7 @@ commands: command: npm run build --if-present - run: working_directory: /tmp/<> - command: npm start --if-present + command: <> background: true - run: condition: <> @@ -363,6 +393,7 @@ jobs: <<: *defaults steps: - checkout + - install-required-node - run: name: Print working folder command: echo $PWD @@ -371,20 +402,36 @@ jobs: command: echo $(yarn global bin) - run: name: print Node version - command: node -v + command: | + . ./scripts/load-nvm.sh + echo "nvm use default" + nvm use default + node -v - run: name: print yarn version command: yarn -v - - run: yarn check-node-version + - run: + name: check Node version + command: | + . ./scripts/load-nvm.sh + yarn check-node-version ## make sure the TERM is set to 'xterm' in node (Linux only) ## else colors (and tests) will fail ## See the following information ## * http://andykdocs.de/development/Docker/Fixing+the+Docker+TERM+variable+issue ## * https://unix.stackexchange.com/questions/43945/whats-the-difference-between-various-term-variables - - run: yarn check-terminal + - run: + name: Check terminal + command: | + . ./scripts/load-nvm.sh + yarn check-terminal - - run: yarn stop-only-all + - run: + name: Stop .only + command: | + . ./scripts/load-nvm.sh + yarn stop-only-all - restore_cache: name: Restore yarn cache @@ -395,7 +442,11 @@ jobs: - run: ls $(yarn global bin)/../lib/node_modules # try several times, because flaky NPM installs ... - - run: yarn --frozen-lockfile || yarn --frozen-lockfile + - run: + name: install and build + command: | + . ./scripts/load-nvm.sh + yarn --frozen-lockfile || yarn --frozen-lockfile - run: name: Top level packages command: yarn list --depth=0 || true @@ -413,8 +464,13 @@ jobs: steps: - attach_workspace: at: ~/ - ## this will catch .only's in js/coffee as well - - run: yarn lint + - install-required-node + ## this will catch ".only"s in js/coffee as well + - run: + name: Linting 🧹 + command: | + . ./scripts/load-nvm.sh + yarn lint - run: name: cypress info (dev) command: node cli/bin/cypress info --dev @@ -526,10 +582,18 @@ jobs: steps: - attach_workspace: at: ~/ - # make sure mocha runs - - run: yarn test-mocha + - install-required-node + - run: + name: Mocha tests + command: | + . ./scripts/load-nvm.sh + yarn test-mocha # test binary build code - - run: yarn test-scripts + - run: + name: Test scripts + command: | + . ./scripts/load-nvm.sh + yarn test-scripts server-unit-tests: <<: *defaults @@ -965,6 +1029,7 @@ jobs: - attach_workspace: at: ~/ - run: $(yarn bin)/print-arch + - install-required-node - run: environment: DEBUG: electron-builder,electron-osx-sign* @@ -973,8 +1038,15 @@ jobs: # if this is a forked pull request, the NEXT_DEV_VERSION environment variable # won't be set and we will use default version, since we are not going to # upload the dev binary build anywhere - command: yarn binary-build --platform $PLATFORM --version ${NEXT_DEV_VERSION:-0.0.0-development} - - run: yarn binary-zip --platform $PLATFORM + command: | + . ./scripts/load-nvm.sh + node --version + yarn binary-build --platform $PLATFORM --version ${NEXT_DEV_VERSION:-0.0.0-development} + - run: + name: Zip the binary + command: | + . ./scripts/load-nvm.sh + yarn binary-zip --platform $PLATFORM # Cypress binary file should be zipped to cypress.zip - run: ls -l *.zip - store-npm-logs @@ -1007,6 +1079,7 @@ jobs: steps: - clone-repo-and-checkout-release-branch: repo: cypress-example-kitchensink + - install-required-node - run: name: Install prod dependencies command: yarn --production @@ -1018,7 +1091,9 @@ jobs: background: true - run: name: Run Kitchensink example project - command: yarn cypress:run --project /tmp/cypress-example-kitchensink + command: | + . ./scripts/load-nvm.sh + yarn cypress:run --project /tmp/cypress-example-kitchensink - store_artifacts: path: /tmp/cypress-example-kitchensink/cypress/screenshots - store_artifacts: @@ -1068,13 +1143,20 @@ jobs: steps: - attach_workspace: at: ~/ - - run: yarn check-next-dev-version + - install-required-node + - run: + name: Check next dev version + command: | + . ./scripts/load-nvm.sh + yarn check-next-dev-version - run: name: bump NPM version command: yarn version --no-git-tag-version --new-version ${NEXT_DEV_VERSION:-0.0.0-development} - run: name: build NPM package - command: yarn build --scope cypress + command: | + . ./scripts/load-nvm.sh + yarn build --scope cypress - run: command: ls -la types working_directory: cli/build @@ -1488,6 +1570,16 @@ jobs: browser: firefox command: "npm run cypress:run" + "test-binary-against-cypress-realworld-app": + <<: *defaults + steps: + - test-binary-against-repo: + repo: cypress-realworld-app + browser: chrome + server-start-command: "npm run start:ci" + command: "npm run cypress:run" + wait-on: http://localhost:3000 + test-binary-as-specific-user: <<: *defaults steps: @@ -1831,6 +1923,16 @@ linux-workflow: &linux-workflow <<: *testBinaryFirefox - test-binary-against-piechopper-firefox: <<: *testBinaryFirefox + - test-binary-against-cypress-realworld-app: + filters: + branches: + only: + - develop + - kevin-v5.0-release-rwa + - v5.0-release + requires: + - build-npm-package + - build-binary - test-binary-as-specific-user: name: "test binary as a non-root user" From 2d2c653461f1b4d4c3d411ef6b9cc0eb71b2475e Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Wed, 12 Aug 2020 16:37:29 -0400 Subject: [PATCH 41/42] two more binary files to codesign --- electron-builder.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/electron-builder.json b/electron-builder.json index 646f1e830313..7f57dd04e93a 100644 --- a/electron-builder.json +++ b/electron-builder.json @@ -11,6 +11,8 @@ "entitlementsInherit": "./scripts/entitlements.mac.inherit.plist", "type": "distribution", "binaries": [ + "./build/mac/Cypress.app/Contents/Resources/app/packages/server/node_modules/watchpack-chokidar2/node_modules/fsevents/build/Release/.node", + "./build/mac/Cypress.app/Contents/Resources/app/packages/server/node_modules/watchpack-chokidar2/node_modules/fsevents/build/Release/fse.node", "./build/mac/Cypress.app/Contents/Resources/app/packages/server/node_modules/@ffmpeg-installer/darwin-x64/ffmpeg", "./build/mac/Cypress.app/Contents/Resources/app/packages/server/node_modules/registry-js/build/Release/registry.node", "./build/mac/Cypress.app/Contents/Resources/app/packages/server/node_modules/term-size/vendor/macos/term-size", From 4aedd98b5d45a171e9ea3980c54a5bc0750e76b2 Mon Sep 17 00:00:00 2001 From: Jessica Sachs Date: Thu, 13 Aug 2020 09:55:36 -0400 Subject: [PATCH 42/42] fix: retries validation (#8268) * fix: validation logic issues --- packages/server/lib/util/validation.js | 19 ++++++++-------- packages/server/test/unit/config_spec.js | 28 ++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/packages/server/lib/util/validation.js b/packages/server/lib/util/validation.js index 72f25304797d..1e4e2736b0da 100644 --- a/packages/server/lib/util/validation.js +++ b/packages/server/lib/util/validation.js @@ -104,18 +104,19 @@ const isValidBrowserList = (key, browsers) => { } const isValidRetriesConfig = (key, value) => { - const isNullOrNumber = isOneOf([_.isNumber, _.isNull]) - - if ( - isNullOrNumber(value) - || (_.isEqual(_.keys(value), ['runMode', 'openMode'])) - && isNullOrNumber(value.runMode) - && isNullOrNumber(value.openMode) - ) { + const optionalKeys = ['runMode', 'openMode'] + const isValidRetryValue = (val) => _.isNull(val) || (Number.isInteger(val) && val >= 0) + const optionalKeysAreValid = (val, k) => optionalKeys.includes(k) && isValidRetryValue(val) + + if (isValidRetryValue(value)) { + return true + } + + if (_.isObject(value) && _.every(value, optionalKeysAreValid)) { return true } - return errMsg(key, value, 'a number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls') + return errMsg(key, value, 'a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls') } const isValidFirefoxGcInterval = (key, value) => { diff --git a/packages/server/test/unit/config_spec.js b/packages/server/test/unit/config_spec.js index 941bbdf75417..58bf50345bbb 100644 --- a/packages/server/test/unit/config_spec.js +++ b/packages/server/test/unit/config_spec.js @@ -690,6 +690,34 @@ describe('lib/config', () => { }) }) + context('retries', () => { + const retriesError = 'a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls' + + // need to keep the const here or it'll get stripped by the build + // eslint-disable-next-line no-unused-vars + const cases = [ + [{ retries: null }, 'with null', true], + [{ retries: 3 }, 'when a number', true], + [{ retries: 3.2 }, 'when a float', false], + [{ retries: -1 }, 'with a negative number', false], + [{ retries: true }, 'when true', false], + [{ retries: false }, 'when false', false], + [{ retries: {} }, 'with an empty object', true], + [{ retries: { runMode: 3 } }, 'when runMode is a positive number', true], + [{ retries: { runMode: -1 } }, 'when runMode is a negative number', false], + [{ retries: { openMode: 3 } }, 'when openMode is a positive number', true], + [{ retries: { openMode: -1 } }, 'when openMode is a negative number', false], + [{ retries: { openMode: 3, TypoRunMode: 3 } }, 'when there is an additional unknown key', false], + [{ retries: { openMode: 3, runMode: 3 } }, 'when both runMode and openMode are positive numbers', true], + ].forEach(([config, expectation, shouldPass]) => { + it(`${shouldPass ? 'passes' : 'fails'} ${expectation}`, function () { + this.setup(config) + + return shouldPass ? this.expectValidationPasses() : this.expectValidationFails(retriesError) + }) + }) + }) + context('firefoxGcInterval', () => { it('passes if a number', function () { this.setup({ firefoxGcInterval: 1 })