From 1b28a3474ad1b5b0ab9dfb0348c68fe52c97eb18 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Mon, 1 Jul 2019 15:13:00 -0400 Subject: [PATCH 01/91] try connecting to chrome remote interface --- packages/server/lib/browsers/chrome.coffee | 12 ++++- packages/server/lib/browsers/protocol.js | 59 ++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 packages/server/lib/browsers/protocol.js diff --git a/packages/server/lib/browsers/chrome.coffee b/packages/server/lib/browsers/chrome.coffee index 61a1e82fc9cb..33d1b76c25ec 100644 --- a/packages/server/lib/browsers/chrome.coffee +++ b/packages/server/lib/browsers/chrome.coffee @@ -10,10 +10,12 @@ plugins = require("../plugins") fs = require("../util/fs") appData = require("../util/app_data") utils = require("./utils") +protocol = require("./protocol") LOAD_EXTENSION = "--load-extension=" CHROME_VERSIONS_WITH_BUGGY_ROOT_LAYER_SCROLLING = "66 67".split(" ") CHROME_VERSION_INTRODUCING_PROXY_BYPASS_ON_LOOPBACK = 72 +CHRONE_REMOTE_INTERFACE_PORT = 9222 pathToExtension = extension.getPathToExtension() pathToTheme = extension.getPathToTheme() @@ -171,7 +173,7 @@ module.exports = { ## https://github.com/cypress-io/cypress/issues/2223 { majorVersion } = options.browser if majorVersion in CHROME_VERSIONS_WITH_BUGGY_ROOT_LAYER_SCROLLING - args.push("--disable-blink-features=RootLayerScrolling") + args.push("--disable-blink-features=RootLayerScrolling") ## https://chromium.googlesource.com/chromium/src/+/da790f920bbc169a6805a4fb83b4c2ab09532d91 ## https://github.com/cypress-io/cypress/issues/1872 @@ -216,8 +218,16 @@ module.exports = { ## by being the last one args.push("--user-data-dir=#{userDir}") args.push("--disk-cache-dir=#{cacheDir}") + ## TODO: make remote debugging port dynamic + args.push("--remote-debugging-port=#{CHRONE_REMOTE_INTERFACE_PORT}") debug("launch in chrome: %s, %s", url, args) utils.launch(browser, url, args) + + .tap -> + protocol.getWsTargetFor(CHRONE_REMOTE_INTERFACE_PORT) + .then (wsUrl) -> + debug("received wsUrl %s for port %d", wsUrl, CHRONE_REMOTE_INTERFACE_PORT) + global.wsUrl = wsUrl } diff --git a/packages/server/lib/browsers/protocol.js b/packages/server/lib/browsers/protocol.js new file mode 100644 index 000000000000..b72691470943 --- /dev/null +++ b/packages/server/lib/browsers/protocol.js @@ -0,0 +1,59 @@ +const _ = require('lodash') +const CRI = require('chrome-remote-interface') +const promiseRetry = require('promise-retry') +const Promise = require('bluebird') +const net = require('net') +const la = require('lazy-ass') +const is = require('check-more-types') +const debug = require('debug')('cypress:server:protocol') + +function connectAsync (opts) { + return new Promise(function (resolve, reject) { + let socket = net.connect(opts) + + socket.once('connect', function () { + socket.removeListener('error', reject) + resolve(socket) + }) + socket.once('error', function (err) { + socket.removeListener('connection', resolve) + reject(err) + }) + }) +} + +/** + * Waits for the port to respond with connection to Chrome Remote Interface + * @param port Port number to connect to + */ +const getWsTargetFor = port => { + la(is.port(port), 'expected port number', port) + + return promiseRetry( + retry => { + return connectAsync({ port }).catch(retry) + }, + { retries: 10 } + ) + .catch(() => { + debug('retry connecting to debugging port %d', port) + }) + .then(() => { + return CRI.List() + }) + .then(targets => { + // activate the first available id + + // find the first target page that's a real tab + // and not the dev tools + const target = _.find(targets, t => { + return t.type === 'page' && t.url.startsWith('http') + }) + + return target.webSocketDebuggerUrl + }) +} + +module.exports = { + getWsTargetFor +} From 8b74d1e37448a12b1a4d0165f9de9a03f543fa8b Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Mon, 1 Jul 2019 15:13:10 -0400 Subject: [PATCH 02/91] linting --- packages/server/lib/browsers/protocol.js | 36 ++++++++++++------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/server/lib/browsers/protocol.js b/packages/server/lib/browsers/protocol.js index b72691470943..2484dd5ee59a 100644 --- a/packages/server/lib/browsers/protocol.js +++ b/packages/server/lib/browsers/protocol.js @@ -26,34 +26,34 @@ function connectAsync (opts) { * Waits for the port to respond with connection to Chrome Remote Interface * @param port Port number to connect to */ -const getWsTargetFor = port => { +const getWsTargetFor = (port) => { la(is.port(port), 'expected port number', port) return promiseRetry( - retry => { + (retry) => { return connectAsync({ port }).catch(retry) }, { retries: 10 } ) - .catch(() => { - debug('retry connecting to debugging port %d', port) - }) - .then(() => { - return CRI.List() - }) - .then(targets => { - // activate the first available id - - // find the first target page that's a real tab - // and not the dev tools - const target = _.find(targets, t => { - return t.type === 'page' && t.url.startsWith('http') - }) + .catch(() => { + debug('retry connecting to debugging port %d', port) + }) + .then(() => { + return CRI.List() + }) + .then((targets) => { + // activate the first available id - return target.webSocketDebuggerUrl + // find the first target page that's a real tab + // and not the dev tools + const target = _.find(targets, (t) => { + return t.type === 'page' && t.url.startsWith('http') }) + + return target.webSocketDebuggerUrl + }) } module.exports = { - getWsTargetFor + getWsTargetFor, } From ea24c39054d887e02fabcca00bdd463b94dd3c65 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Mon, 1 Jul 2019 15:24:49 -0400 Subject: [PATCH 03/91] print CRI targets for better debugging --- packages/server/lib/browsers/protocol.js | 2 ++ packages/server/package.json | 1 + 2 files changed, 3 insertions(+) diff --git a/packages/server/lib/browsers/protocol.js b/packages/server/lib/browsers/protocol.js index 2484dd5ee59a..ee34d7fa9701 100644 --- a/packages/server/lib/browsers/protocol.js +++ b/packages/server/lib/browsers/protocol.js @@ -42,6 +42,7 @@ const getWsTargetFor = (port) => { return CRI.List() }) .then((targets) => { + debug('CRI list %o', targets) // activate the first available id // find the first target page that's a real tab @@ -50,6 +51,7 @@ const getWsTargetFor = (port) => { return t.type === 'page' && t.url.startsWith('http') }) + debug('found CRI target %o', target) return target.webSocketDebuggerUrl }) } diff --git a/packages/server/package.json b/packages/server/package.json index 311c0fbe75e5..3cfcf92ad074 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -165,6 +165,7 @@ "p-queue": "1.2.0", "parse-domain": "2.0.0", "pluralize": "8.0.0", + "promise-retry": "1.1.1", "pumpify": "1.5.1", "ramda": "0.24.1", "randomstring": "1.1.5", From 3e78b8f529625e3780104647625d7bb2cbc48697 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Mon, 1 Jul 2019 15:25:01 -0400 Subject: [PATCH 04/91] linting --- packages/server/lib/browsers/protocol.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/server/lib/browsers/protocol.js b/packages/server/lib/browsers/protocol.js index ee34d7fa9701..9e37e9a2a433 100644 --- a/packages/server/lib/browsers/protocol.js +++ b/packages/server/lib/browsers/protocol.js @@ -52,6 +52,7 @@ const getWsTargetFor = (port) => { }) debug('found CRI target %o', target) + return target.webSocketDebuggerUrl }) } From fb8c0ff63d37c7f9fe9d18de21387eee457f598f Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Mon, 1 Jul 2019 16:13:56 -0400 Subject: [PATCH 05/91] load empty tab first when connecting to CRI --- packages/server/lib/browsers/chrome.coffee | 15 ++++++++++----- packages/server/lib/browsers/protocol.js | 10 +++++++--- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/packages/server/lib/browsers/chrome.coffee b/packages/server/lib/browsers/chrome.coffee index 33d1b76c25ec..d2af118e0bbe 100644 --- a/packages/server/lib/browsers/chrome.coffee +++ b/packages/server/lib/browsers/chrome.coffee @@ -15,7 +15,7 @@ protocol = require("./protocol") LOAD_EXTENSION = "--load-extension=" CHROME_VERSIONS_WITH_BUGGY_ROOT_LAYER_SCROLLING = "66 67".split(" ") CHROME_VERSION_INTRODUCING_PROXY_BYPASS_ON_LOOPBACK = 72 -CHRONE_REMOTE_INTERFACE_PORT = 9222 +CHROME_REMOTE_INTERFACE_PORT = 9222 pathToExtension = extension.getPathToExtension() pathToTheme = extension.getPathToTheme() @@ -219,15 +219,20 @@ module.exports = { args.push("--user-data-dir=#{userDir}") args.push("--disk-cache-dir=#{cacheDir}") ## TODO: make remote debugging port dynamic - args.push("--remote-debugging-port=#{CHRONE_REMOTE_INTERFACE_PORT}") + args.push("--remote-debugging-port=#{CHROME_REMOTE_INTERFACE_PORT}") debug("launch in chrome: %s, %s", url, args) - utils.launch(browser, url, args) + # FIRST load the blank page + # first allows us to connect the remote interface, + # start video recording and then + # we will load the actual page + utils.launch(browser, null, args) .tap -> - protocol.getWsTargetFor(CHRONE_REMOTE_INTERFACE_PORT) + # at first the tab will be blank "new tab" + protocol.getWsTargetFor(CHROME_REMOTE_INTERFACE_PORT, 'New Tab') .then (wsUrl) -> - debug("received wsUrl %s for port %d", wsUrl, CHRONE_REMOTE_INTERFACE_PORT) + debug("received wsUrl %s for port %d", wsUrl, CHROME_REMOTE_INTERFACE_PORT) global.wsUrl = wsUrl } diff --git a/packages/server/lib/browsers/protocol.js b/packages/server/lib/browsers/protocol.js index 9e37e9a2a433..bbc46a6fa86f 100644 --- a/packages/server/lib/browsers/protocol.js +++ b/packages/server/lib/browsers/protocol.js @@ -24,10 +24,12 @@ function connectAsync (opts) { /** * Waits for the port to respond with connection to Chrome Remote Interface - * @param port Port number to connect to + * @param {number} port Port number to connect to + * @param {string} title Expected page title */ -const getWsTargetFor = (port) => { +const getWsTargetFor = (port, title) => { la(is.port(port), 'expected port number', port) + la(is.unemptyString(title), 'invalid page title', title) return promiseRetry( (retry) => { @@ -48,10 +50,12 @@ const getWsTargetFor = (port) => { // find the first target page that's a real tab // and not the dev tools const target = _.find(targets, (t) => { - return t.type === 'page' && t.url.startsWith('http') + // return t.type === 'page' && t.url.startsWith('http') + return t.type === 'page' && t.title === title }) debug('found CRI target %o', target) + la(target, 'could not find CRI target') return target.webSocketDebuggerUrl }) From 7ba253f9f2a396c6ac76d02fa62ea0f436d33113 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Mon, 1 Jul 2019 16:29:15 -0400 Subject: [PATCH 06/91] first load blank page, then navigate --- packages/server/lib/browsers/chrome.coffee | 14 ++++++++++++++ packages/server/lib/browsers/protocol.js | 5 +++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/server/lib/browsers/chrome.coffee b/packages/server/lib/browsers/chrome.coffee index d2af118e0bbe..43bfea8d46e1 100644 --- a/packages/server/lib/browsers/chrome.coffee +++ b/packages/server/lib/browsers/chrome.coffee @@ -4,6 +4,8 @@ _ = require("lodash") os = require("os") path = require("path") Promise = require("bluebird") +CRI = require('chrome-remote-interface') +la = require('lazy-ass') extension = require("@packages/extension") debug = require("debug")("cypress:server:browsers") plugins = require("../plugins") @@ -235,4 +237,16 @@ module.exports = { .then (wsUrl) -> debug("received wsUrl %s for port %d", wsUrl, CHROME_REMOTE_INTERFACE_PORT) global.wsUrl = wsUrl + + # SECOND use RPI to initialize connection to the browser + CRI({ + target: wsUrl, + local: true + }) + .then (client) -> + la(client, "could not get CRI client") + debug("received CRI client") + + # THIRD send visit command to load the actual page + client.send('Page.navigate', { url }) } diff --git a/packages/server/lib/browsers/protocol.js b/packages/server/lib/browsers/protocol.js index bbc46a6fa86f..bf018bd5821a 100644 --- a/packages/server/lib/browsers/protocol.js +++ b/packages/server/lib/browsers/protocol.js @@ -51,11 +51,12 @@ const getWsTargetFor = (port, title) => { // and not the dev tools const target = _.find(targets, (t) => { // return t.type === 'page' && t.url.startsWith('http') - return t.type === 'page' && t.title === title + // return t.type === 'page' && t.title === title + return t.type === 'page' && t.url === 'chrome://newtab/' }) - debug('found CRI target %o', target) la(target, 'could not find CRI target') + debug('found CRI target %o', target) return target.webSocketDebuggerUrl }) From ec06d65062232ce00d21b74a98ecb991495dc199 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Wed, 3 Jul 2019 11:22:18 -0400 Subject: [PATCH 07/91] Page.navigate is working --- packages/server/lib/browsers/chrome.coffee | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/server/lib/browsers/chrome.coffee b/packages/server/lib/browsers/chrome.coffee index 43bfea8d46e1..8171c07b4bb1 100644 --- a/packages/server/lib/browsers/chrome.coffee +++ b/packages/server/lib/browsers/chrome.coffee @@ -243,10 +243,17 @@ module.exports = { target: wsUrl, local: true }) + # ? should we keep the CRI client reference around + global.criClient = client .then (client) -> la(client, "could not get CRI client") debug("received CRI client") # THIRD send visit command to load the actual page - client.send('Page.navigate', { url }) + console.log('navigating to page %s', url) + + # client.send('Page.navigate', { url }) + # equivalent + { Page } = client + Page.navigate({ url }) } From ff02745eb40fc2861b60a858e25f24a200ecf2c4 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 16 Jul 2019 13:52:35 -0400 Subject: [PATCH 08/91] linting --- packages/server/lib/browsers/chrome.coffee | 3 +-- packages/server/lib/browsers/protocol.js | 16 +++++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/server/lib/browsers/chrome.coffee b/packages/server/lib/browsers/chrome.coffee index 8171c07b4bb1..dabf4b6c0067 100644 --- a/packages/server/lib/browsers/chrome.coffee +++ b/packages/server/lib/browsers/chrome.coffee @@ -239,7 +239,7 @@ module.exports = { global.wsUrl = wsUrl # SECOND use RPI to initialize connection to the browser - CRI({ + client = CRI({ target: wsUrl, local: true }) @@ -251,7 +251,6 @@ module.exports = { # THIRD send visit command to load the actual page console.log('navigating to page %s', url) - # client.send('Page.navigate', { url }) # equivalent { Page } = client diff --git a/packages/server/lib/browsers/protocol.js b/packages/server/lib/browsers/protocol.js index bf018bd5821a..73655027e181 100644 --- a/packages/server/lib/browsers/protocol.js +++ b/packages/server/lib/browsers/protocol.js @@ -5,6 +5,7 @@ const Promise = require('bluebird') const net = require('net') const la = require('lazy-ass') const is = require('check-more-types') +const pluralize = require('pluralize') const debug = require('debug')('cypress:server:protocol') function connectAsync (opts) { @@ -15,6 +16,7 @@ function connectAsync (opts) { socket.removeListener('error', reject) resolve(socket) }) + socket.once('error', function (err) { socket.removeListener('connection', resolve) reject(err) @@ -44,16 +46,16 @@ const getWsTargetFor = (port, title) => { return CRI.List() }) .then((targets) => { - debug('CRI list %o', targets) + debug('CRI list has %s %o', pluralize('targets', targets.length, true), targets) // activate the first available id // find the first target page that's a real tab - // and not the dev tools - const target = _.find(targets, (t) => { - // return t.type === 'page' && t.url.startsWith('http') - // return t.type === 'page' && t.title === title - return t.type === 'page' && t.url === 'chrome://newtab/' - }) + // and not the dev tools or background page. + // typically there are two targets found like + // { title: 'Cypress', type: 'background_page', url: 'chrome-extension://...', ... } + // { title: 'New Tab', type: 'page', url: 'chrome://newtab/', ...} + const newTabTargetFields = { type: 'page', url: 'chrome://newtab/' } + const target = _.find(targets, newTabTargetFields) la(target, 'could not find CRI target') debug('found CRI target %o', target) From ab11757bd7d7ef211b2374344a44d652ed034d76 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 16 Jul 2019 14:38:22 -0400 Subject: [PATCH 09/91] remove title --- packages/server/lib/browsers/chrome.coffee | 11 ++++------- packages/server/lib/browsers/protocol.js | 4 +--- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/packages/server/lib/browsers/chrome.coffee b/packages/server/lib/browsers/chrome.coffee index dabf4b6c0067..22bf2f645796 100644 --- a/packages/server/lib/browsers/chrome.coffee +++ b/packages/server/lib/browsers/chrome.coffee @@ -233,7 +233,7 @@ module.exports = { .tap -> # at first the tab will be blank "new tab" - protocol.getWsTargetFor(CHROME_REMOTE_INTERFACE_PORT, 'New Tab') + protocol.getWsTargetFor(CHROME_REMOTE_INTERFACE_PORT) .then (wsUrl) -> debug("received wsUrl %s for port %d", wsUrl, CHROME_REMOTE_INTERFACE_PORT) global.wsUrl = wsUrl @@ -249,10 +249,7 @@ module.exports = { la(client, "could not get CRI client") debug("received CRI client") - # THIRD send visit command to load the actual page - console.log('navigating to page %s', url) - # client.send('Page.navigate', { url }) - # equivalent - { Page } = client - Page.navigate({ url }) + # THIRD send the visit command to load the actual page + debug('navigating to page %s', url) + client.Page.navigate({ url }) } diff --git a/packages/server/lib/browsers/protocol.js b/packages/server/lib/browsers/protocol.js index 73655027e181..33f8cdb2cb72 100644 --- a/packages/server/lib/browsers/protocol.js +++ b/packages/server/lib/browsers/protocol.js @@ -27,11 +27,9 @@ function connectAsync (opts) { /** * Waits for the port to respond with connection to Chrome Remote Interface * @param {number} port Port number to connect to - * @param {string} title Expected page title */ -const getWsTargetFor = (port, title) => { +const getWsTargetFor = (port) => { la(is.port(port), 'expected port number', port) - la(is.unemptyString(title), 'invalid page title', title) return promiseRetry( (retry) => { From 3f7df0456be9af3b07341d8d8800c3d1525e221a Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 16 Jul 2019 14:42:00 -0400 Subject: [PATCH 10/91] add mocha banner --- packages/server/test/integration/http_requests_spec.coffee | 2 ++ packages/server/test/integration/server_spec.coffee | 2 ++ 2 files changed, 4 insertions(+) diff --git a/packages/server/test/integration/http_requests_spec.coffee b/packages/server/test/integration/http_requests_spec.coffee index 5ec54f446daf..fe2e4f6ad70d 100644 --- a/packages/server/test/integration/http_requests_spec.coffee +++ b/packages/server/test/integration/http_requests_spec.coffee @@ -53,6 +53,8 @@ browserifyFile = (filePath) -> ) describe "Routes", -> + require("mocha-banner").register() + beforeEach -> process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0" diff --git a/packages/server/test/integration/server_spec.coffee b/packages/server/test/integration/server_spec.coffee index 54c96396e10b..eec5d2933df1 100644 --- a/packages/server/test/integration/server_spec.coffee +++ b/packages/server/test/integration/server_spec.coffee @@ -19,6 +19,8 @@ expectToEqDetails = (actual, expected) -> expect(actual).to.deep.eq(expected) describe "Server", -> + require("mocha-banner").register() + beforeEach -> sinon.stub(Server.prototype, "reset") From b8fc8cf85a203f953157dfa2666256a30c5b6c54 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 16 Jul 2019 14:51:51 -0400 Subject: [PATCH 11/91] more banners --- packages/server/test/integration/websockets_spec.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/server/test/integration/websockets_spec.coffee b/packages/server/test/integration/websockets_spec.coffee index aceac43d7a84..accf46512223 100644 --- a/packages/server/test/integration/websockets_spec.coffee +++ b/packages/server/test/integration/websockets_spec.coffee @@ -19,6 +19,8 @@ wsPort = 20000 wssPort = 8443 describe "Web Sockets", -> + require("mocha-banner").register() + beforeEach -> Fixtures.scaffold() From 9bc5333a97910670de94fcb8779cfa5c7ce9ff54 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 16 Jul 2019 15:33:45 -0400 Subject: [PATCH 12/91] update some server unit tests --- packages/server/lib/browsers/chrome.coffee | 38 ++++++++++++------- packages/server/lib/browsers/protocol.js | 1 + .../test/unit/browsers/chrome_spec.coffee | 23 +++++++++-- 3 files changed, 45 insertions(+), 17 deletions(-) diff --git a/packages/server/lib/browsers/chrome.coffee b/packages/server/lib/browsers/chrome.coffee index 22bf2f645796..adff6bb8a577 100644 --- a/packages/server/lib/browsers/chrome.coffee +++ b/packages/server/lib/browsers/chrome.coffee @@ -129,11 +129,33 @@ _disableRestorePagesPrompt = (userDir) -> fs.writeJson(prefsPath, preferences) .catch -> +# After the browser has been opened, we can connect to +# its remote interface via a websocket. +_connectToChromeRemoteInterface = -> + # at first the tab will be blank "new tab" + protocol.getWsTargetFor(CHROME_REMOTE_INTERFACE_PORT) + .then (wsUrl) -> + debug("received wsUrl %s for port %d", wsUrl, CHROME_REMOTE_INTERFACE_PORT) + global.wsUrl = wsUrl + + # SECOND use RPI to initialize connection to the browser + client = CRI({ + target: wsUrl, + local: true + }) + # ? should we keep the CRI client reference around + global.criClient = client + module.exports = { + # by adding functions as methods here + # we can easyly stub them from our unit tests + _normalizeArgExtensions _removeRootExtension + _connectToChromeRemoteInterface + _writeExtension: (browser, isTextTerminal, proxyUrl, socketIoRoute) -> ## get the string bytes for the final extension file extension.setHostAndPath(proxyUrl, socketIoRoute) @@ -231,20 +253,8 @@ module.exports = { # we will load the actual page utils.launch(browser, null, args) - .tap -> - # at first the tab will be blank "new tab" - protocol.getWsTargetFor(CHROME_REMOTE_INTERFACE_PORT) - .then (wsUrl) -> - debug("received wsUrl %s for port %d", wsUrl, CHROME_REMOTE_INTERFACE_PORT) - global.wsUrl = wsUrl - - # SECOND use RPI to initialize connection to the browser - client = CRI({ - target: wsUrl, - local: true - }) - # ? should we keep the CRI client reference around - global.criClient = client + .tap => + @_connectToChromeRemoteInterface() .then (client) -> la(client, "could not get CRI client") debug("received CRI client") diff --git a/packages/server/lib/browsers/protocol.js b/packages/server/lib/browsers/protocol.js index 33f8cdb2cb72..a23b258937f4 100644 --- a/packages/server/lib/browsers/protocol.js +++ b/packages/server/lib/browsers/protocol.js @@ -29,6 +29,7 @@ function connectAsync (opts) { * @param {number} port Port number to connect to */ const getWsTargetFor = (port) => { + debug('Getting WS connection to CRI on port %d', port) la(is.port(port), 'expected port number', port) return promiseRetry( diff --git a/packages/server/test/unit/browsers/chrome_spec.coffee b/packages/server/test/unit/browsers/chrome_spec.coffee index 233a95eb6f63..bca030428393 100644 --- a/packages/server/test/unit/browsers/chrome_spec.coffee +++ b/packages/server/test/unit/browsers/chrome_spec.coffee @@ -12,15 +12,28 @@ describe "lib/browsers/chrome", -> context "#open", -> beforeEach -> @args = [] + # mock CRI client during testing + @criClient = { + Page: { + navigate: sinon.stub().resolves() + } + } sinon.stub(chrome, "_getArgs").returns(@args) sinon.stub(chrome, "_writeExtension").resolves("/path/to/ext") + sinon.stub(chrome, "_connectToChromeRemoteInterface").resolves(@criClient) sinon.stub(plugins, "has") sinon.stub(plugins, "execute") sinon.stub(utils, "launch") sinon.stub(utils, "getProfileDir").returns("/profile/dir") sinon.stub(utils, "ensureCleanCache").resolves("/profile/dir/CypressCache") + # NOTE: confirm CRI client behavior before merging PR 4628 + it.skip "calls CRI Page.visit", -> + chrome.open("chrome", "http://", {}, {}) + .then => + # check if @criClient.Page.navigate has been called + it "is noop without before:browser:launch", -> plugins.has.returns(false) @@ -34,7 +47,9 @@ describe "lib/browsers/chrome", -> chrome.open("chrome", "http://", {}, {}) .then => - expect(utils.launch).to.be.calledWith("chrome", "http://", @args) + # to initialize remote interface client and prepare for true tests + # we load the browser with blank page first + expect(utils.launch).to.be.calledWith("chrome", null, @args) it "normalizes --load-extension if provided in plugin", -> plugins.has.returns(true) @@ -55,7 +70,8 @@ describe "lib/browsers/chrome", -> "--foo=bar" "--load-extension=/foo/bar/baz.js,/path/to/ext,#{pathToTheme}" "--user-data-dir=/profile/dir" - "--disk-cache-dir=/profile/dir/CypressCache" + "--disk-cache-dir=/profile/dir/CypressCache", + "--remote-debugging-port=9222" ]) it "normalizes multiple extensions from plugins", -> @@ -77,7 +93,8 @@ describe "lib/browsers/chrome", -> "--foo=bar" "--load-extension=/foo/bar/baz.js,/quux.js,/path/to/ext,#{pathToTheme}" "--user-data-dir=/profile/dir" - "--disk-cache-dir=/profile/dir/CypressCache" + "--disk-cache-dir=/profile/dir/CypressCache", + "--remote-debugging-port=9222" ]) it "cleans up an unclean browser profile exit status", -> From b999dfc0f895ff6bf342cdd67fb1d2aa0428a3c7 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 16 Jul 2019 17:16:19 -0400 Subject: [PATCH 13/91] update integration test --- packages/server/lib/browsers/chrome.coffee | 34 +++++++++++++------ .../test/integration/cypress_spec.coffee | 14 +++++++- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/packages/server/lib/browsers/chrome.coffee b/packages/server/lib/browsers/chrome.coffee index adff6bb8a577..b406d4f30f5c 100644 --- a/packages/server/lib/browsers/chrome.coffee +++ b/packages/server/lib/browsers/chrome.coffee @@ -6,6 +6,7 @@ path = require("path") Promise = require("bluebird") CRI = require('chrome-remote-interface') la = require('lazy-ass') +check = require('check-more-types') extension = require("@packages/extension") debug = require("debug")("cypress:server:browsers") plugins = require("../plugins") @@ -138,7 +139,7 @@ _connectToChromeRemoteInterface = -> debug("received wsUrl %s for port %d", wsUrl, CHROME_REMOTE_INTERFACE_PORT) global.wsUrl = wsUrl - # SECOND use RPI to initialize connection to the browser + # use the websocket connection to create Chrome remote interface client client = CRI({ target: wsUrl, local: true @@ -146,9 +147,23 @@ _connectToChromeRemoteInterface = -> # ? should we keep the CRI client reference around global.criClient = client +# a utility function that navigates to the given URL +# once Chrome remote interface client is passed to it. +_navigateUsingCRI = (url) -> + la(check.url(url), "missing url to navigate to", url) + + (client) -> + la(client, "could not get CRI client") + debug("received CRI client") + debug('navigating to page %s', url) + client.Page.navigate({ url }) + module.exports = { - # by adding functions as methods here - # we can easyly stub them from our unit tests + # + # tip: + # by adding utility functions that start with "_" + # as methods here we can easily stub them from our unit tests + # _normalizeArgExtensions @@ -156,6 +171,8 @@ module.exports = { _connectToChromeRemoteInterface + _navigateUsingCRI + _writeExtension: (browser, isTextTerminal, proxyUrl, socketIoRoute) -> ## get the string bytes for the final extension file extension.setHostAndPath(proxyUrl, socketIoRoute) @@ -254,12 +271,9 @@ module.exports = { utils.launch(browser, null, args) .tap => + # SECOND connect to the Chrome remote interface + # and when the connection is ready + # navigate to the actual url @_connectToChromeRemoteInterface() - .then (client) -> - la(client, "could not get CRI client") - debug("received CRI client") - - # THIRD send the visit command to load the actual page - debug('navigating to page %s', url) - client.Page.navigate({ url }) + .then @_navigateUsingCRI(url) } diff --git a/packages/server/test/integration/cypress_spec.coffee b/packages/server/test/integration/cypress_spec.coffee index 5488ed7c873e..e8013504dd02 100644 --- a/packages/server/test/integration/cypress_spec.coffee +++ b/packages/server/test/integration/cypress_spec.coffee @@ -38,6 +38,7 @@ Watchers = require("#{root}lib/watchers") browsers = require("#{root}lib/browsers") videoCapture = require("#{root}lib/video_capture") browserUtils = require("#{root}lib/browsers/utils") +chromeBrowser = require("#{root}lib/browsers/chrome") openProject = require("#{root}lib/open_project") env = require("#{root}lib/util/env") system = require("#{root}lib/util/system") @@ -785,6 +786,15 @@ describe "lib/cypress", -> context "before:browser:launch", -> it "chrome", -> + # during testing, do not try to connect to the remote interface or + # use the Chrome remote interface client + sinon.stub(chromeBrowser, "_connectToChromeRemoteInterface").resolves() + # the "returns(resolves)" stub is due to curried method + # it accepts URL to visit and then waits for actual CRI client reference + # and only then navigates to that URL + visitPage = sinon.stub().resolves() + sinon.stub(chromeBrowser, "_navigateUsingCRI").returns(visitPage) + cypress.start([ "--run-project=#{@pluginBrowser}" "--browser=chrome" @@ -796,7 +806,7 @@ describe "lib/cypress", -> browserArgs = args[2] - expect(browserArgs).to.have.length(7) + expect(browserArgs).to.have.length(8) expect(browserArgs.slice(0, 4)).to.deep.eq([ "chrome", "foo", "bar", "baz" @@ -804,6 +814,8 @@ describe "lib/cypress", -> @expectExitWith(0) + expect(visitPage).to.have.been.calledOnce + it "electron", -> write = sinon.stub() videoCapture.start.returns({ write }) From 054f589ff66b26e7ba9094c03e3bb28dc46846a7 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Wed, 17 Jul 2019 16:19:10 -0400 Subject: [PATCH 14/91] document how to run single driver spec file --- packages/driver/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/driver/README.md b/packages/driver/README.md index 09147e96f68f..3832928a5e94 100644 --- a/packages/driver/README.md +++ b/packages/driver/README.md @@ -55,6 +55,18 @@ cd packages/driver npm start ``` +For working with a single spec file, use `testFiles` configuration option. For example, to only show the `e2e/focus_blur_spec.js` spec file when Cypress is opened use (path is relative to `test/cypress/integration` folder) + +```bash +npm run cypress:open -- --config testFiles=e2e/focus_blur_spec.js +``` + +Or to run that single spec headlessly + +```bash +npm run cypress:run -- --config testFiles=e2e/focus_blur_spec.js +``` + ## Debugging In the browser From 1cab13069732f79dcd43526c27df6e17f14c7719 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Wed, 17 Jul 2019 16:47:00 -0400 Subject: [PATCH 15/91] set the focus back on the page before navigating from blank chrome tab --- packages/driver/README.md | 12 ++++++++++++ packages/server/lib/browsers/chrome.coffee | 7 ++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/driver/README.md b/packages/driver/README.md index 3832928a5e94..a85cd07cd78c 100644 --- a/packages/driver/README.md +++ b/packages/driver/README.md @@ -67,6 +67,18 @@ Or to run that single spec headlessly npm run cypress:run -- --config testFiles=e2e/focus_blur_spec.js ``` +Alternative: use `--spec`, but pass the path from the current folder, for example + +```bash +npm run cypress:run -- --spec test/cypress/integration/issues/1939_1940_2190_spec.js --browser chrome +``` + +If you want to run tests in Chrome and keep it open after the spec finishes, you can do + +```bash +npm run cypress:run -- --config testFiles=e2e/focus_blur_spec.js --browser chrome --no-exit +``` + ## Debugging In the browser diff --git a/packages/server/lib/browsers/chrome.coffee b/packages/server/lib/browsers/chrome.coffee index b406d4f30f5c..80916ef81db5 100644 --- a/packages/server/lib/browsers/chrome.coffee +++ b/packages/server/lib/browsers/chrome.coffee @@ -156,7 +156,11 @@ _navigateUsingCRI = (url) -> la(client, "could not get CRI client") debug("received CRI client") debug('navigating to page %s', url) - client.Page.navigate({ url }) + # when opening the blank page and trying to navigate + # the focus gets lost. Restore it first + client.Page.bringToFront() + .then -> + client.Page.navigate({ url }) module.exports = { # @@ -269,6 +273,7 @@ module.exports = { # start video recording and then # we will load the actual page utils.launch(browser, null, args) + # utils.launch(browser, url, args) .tap => # SECOND connect to the Chrome remote interface From b842f23ffbf0d30eeb11a89784c05142999a3dfb Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Wed, 17 Jul 2019 17:06:05 -0400 Subject: [PATCH 16/91] update server unit test --- packages/server/test/unit/browsers/chrome_spec.coffee | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/server/test/unit/browsers/chrome_spec.coffee b/packages/server/test/unit/browsers/chrome_spec.coffee index bca030428393..4777e040b3b5 100644 --- a/packages/server/test/unit/browsers/chrome_spec.coffee +++ b/packages/server/test/unit/browsers/chrome_spec.coffee @@ -15,6 +15,7 @@ describe "lib/browsers/chrome", -> # mock CRI client during testing @criClient = { Page: { + bringToFront: sinon.stub().resolves() navigate: sinon.stub().resolves() } } @@ -28,11 +29,11 @@ describe "lib/browsers/chrome", -> sinon.stub(utils, "getProfileDir").returns("/profile/dir") sinon.stub(utils, "ensureCleanCache").resolves("/profile/dir/CypressCache") - # NOTE: confirm CRI client behavior before merging PR 4628 - it.skip "calls CRI Page.visit", -> + it "focuses on the page and calls CRI Page.visit", -> chrome.open("chrome", "http://", {}, {}) .then => - # check if @criClient.Page.navigate has been called + expect(@criClient.Page.bringToFront).to.have.been.calledOnce + expect(@criClient.Page.navigate).to.have.been.calledOnce it "is noop without before:browser:launch", -> plugins.has.returns(false) From 6d20ab52e91032e7547b49532b9ae4bd6d773314 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Thu, 18 Jul 2019 12:54:25 -0400 Subject: [PATCH 17/91] do not store Chrome remote interface reference for now --- packages/server/lib/browsers/chrome.coffee | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/server/lib/browsers/chrome.coffee b/packages/server/lib/browsers/chrome.coffee index 80916ef81db5..1da993f6a54a 100644 --- a/packages/server/lib/browsers/chrome.coffee +++ b/packages/server/lib/browsers/chrome.coffee @@ -137,15 +137,12 @@ _connectToChromeRemoteInterface = -> protocol.getWsTargetFor(CHROME_REMOTE_INTERFACE_PORT) .then (wsUrl) -> debug("received wsUrl %s for port %d", wsUrl, CHROME_REMOTE_INTERFACE_PORT) - global.wsUrl = wsUrl - + # TODO decide later how and where to keep the wsUrl and the CRI client # use the websocket connection to create Chrome remote interface client client = CRI({ target: wsUrl, local: true }) - # ? should we keep the CRI client reference around - global.criClient = client # a utility function that navigates to the given URL # once Chrome remote interface client is passed to it. From 4aaeb5f4bd598570f6de9629d46aeeb517507406 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Thu, 18 Jul 2019 14:21:12 -0400 Subject: [PATCH 18/91] record video of the Chrome tab using screencast API --- packages/server/lib/browsers/chrome.coffee | 23 ++++++++++++++++++++++ packages/server/lib/modes/run.coffee | 11 ++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/packages/server/lib/browsers/chrome.coffee b/packages/server/lib/browsers/chrome.coffee index 1da993f6a54a..85f8cf512dd2 100644 --- a/packages/server/lib/browsers/chrome.coffee +++ b/packages/server/lib/browsers/chrome.coffee @@ -144,6 +144,18 @@ _connectToChromeRemoteInterface = -> local: true }) +_maybeRecordVideo = (options) -> + (client) -> + if options.screencastFrame + debug('starting screencast') + debug('screencast meta info %o', client.Page.ScreencastFrameMetadata) + + client.Page.screencastFrame(options.screencastFrame) + client.Page.startScreencast({ + format: 'jpeg' + }) + client + # a utility function that navigates to the given URL # once Chrome remote interface client is passed to it. _navigateUsingCRI = (url) -> @@ -156,6 +168,14 @@ _navigateUsingCRI = (url) -> # when opening the blank page and trying to navigate # the focus gets lost. Restore it first client.Page.bringToFront() + # .then -> + # debug('starting screencast') + # debug('screencast meta info %o', client.Page.ScreencastFrameMetadata) + # client.Page.screencastFrame (e) -> + # debug('received screencastFrame %d %o', e.sessionId, e.metadata) + # client.Page.startScreencast({ + # format: 'jpeg' + # }) .then -> client.Page.navigate({ url }) @@ -172,6 +192,8 @@ module.exports = { _connectToChromeRemoteInterface + _maybeRecordVideo + _navigateUsingCRI _writeExtension: (browser, isTextTerminal, proxyUrl, socketIoRoute) -> @@ -277,5 +299,6 @@ module.exports = { # and when the connection is ready # navigate to the actual url @_connectToChromeRemoteInterface() + .then @_maybeRecordVideo(options) .then @_navigateUsingCRI(url) } diff --git a/packages/server/lib/modes/run.coffee b/packages/server/lib/modes/run.coffee index cbf894dfc172..b94666f27307 100644 --- a/packages/server/lib/modes/run.coffee +++ b/packages/server/lib/modes/run.coffee @@ -575,6 +575,14 @@ module.exports = { browserOpts = switch browser.name when "electron" @getElectronProps(browser.isHeaded, project, write) + when "chrome" + if write + { + screencastFrame: (e) -> + # https://chromedevtools.github.io/devtools-protocol/tot/Page#event-screencastFrame + debug('received screencastFrame %d %o', e.sessionId, e.metadata) + write(new Buffer(e.data, 'base64')) + } else {} @@ -841,7 +849,8 @@ module.exports = { ## if we've been told to record and we're not spawning a headed browser browserCanBeRecorded = (browser) -> - browser.name is "electron" and isHeadless + true + # browser.name is "electron" and isHeadless if video if browserCanBeRecorded(browser) From a7ec58c7a65b6ccaa0b77b98cd7bccd165b3e11e Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 23 Jul 2019 17:26:18 -0400 Subject: [PATCH 19/91] use dynamic port to connect to Chrome remote interface --- packages/server/lib/browsers/chrome.coffee | 22 +++++++++++++--------- packages/server/lib/browsers/protocol.js | 15 +++++++++++++-- packages/server/lib/browsers/utils.coffee | 3 +++ packages/server/package.json | 1 + 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/packages/server/lib/browsers/chrome.coffee b/packages/server/lib/browsers/chrome.coffee index 1da993f6a54a..c77041449612 100644 --- a/packages/server/lib/browsers/chrome.coffee +++ b/packages/server/lib/browsers/chrome.coffee @@ -18,7 +18,6 @@ protocol = require("./protocol") LOAD_EXTENSION = "--load-extension=" CHROME_VERSIONS_WITH_BUGGY_ROOT_LAYER_SCROLLING = "66 67".split(" ") CHROME_VERSION_INTRODUCING_PROXY_BYPASS_ON_LOOPBACK = 72 -CHROME_REMOTE_INTERFACE_PORT = 9222 pathToExtension = extension.getPathToExtension() pathToTheme = extension.getPathToTheme() @@ -132,11 +131,14 @@ _disableRestorePagesPrompt = (userDir) -> # After the browser has been opened, we can connect to # its remote interface via a websocket. -_connectToChromeRemoteInterface = -> +_connectToChromeRemoteInterface = (port) -> + la(check.userPort(port), "expected port number to connect CRI to", port) + + debug("connecting to Chrome remote interface at random port %d", port) # at first the tab will be blank "new tab" - protocol.getWsTargetFor(CHROME_REMOTE_INTERFACE_PORT) + protocol.getWsTargetFor(port) .then (wsUrl) -> - debug("received wsUrl %s for port %d", wsUrl, CHROME_REMOTE_INTERFACE_PORT) + debug("received wsUrl %s for port %d", wsUrl, port) # TODO decide later how and where to keep the wsUrl and the CRI client # use the websocket connection to create Chrome remote interface client client = CRI({ @@ -238,9 +240,11 @@ module.exports = { ## before launching the browser every time utils.ensureCleanCache(browser, isTextTerminal), - pluginsBeforeBrowserLaunch(options.browser, args) + pluginsBeforeBrowserLaunch(options.browser, args), + + utils.getPort() ]) - .spread (cacheDir, args) => + .spread (cacheDir, args, port) => Promise.all([ @_writeExtension( browser, @@ -260,8 +264,8 @@ module.exports = { ## by being the last one args.push("--user-data-dir=#{userDir}") args.push("--disk-cache-dir=#{cacheDir}") - ## TODO: make remote debugging port dynamic - args.push("--remote-debugging-port=#{CHROME_REMOTE_INTERFACE_PORT}") + debug("get random port %d for remote debugging", port) + args.push("--remote-debugging-port=#{port}") debug("launch in chrome: %s, %s", url, args) @@ -276,6 +280,6 @@ module.exports = { # SECOND connect to the Chrome remote interface # and when the connection is ready # navigate to the actual url - @_connectToChromeRemoteInterface() + @_connectToChromeRemoteInterface(port) .then @_navigateUsingCRI(url) } diff --git a/packages/server/lib/browsers/protocol.js b/packages/server/lib/browsers/protocol.js index a23b258937f4..0151df61d2c4 100644 --- a/packages/server/lib/browsers/protocol.js +++ b/packages/server/lib/browsers/protocol.js @@ -10,14 +10,17 @@ const debug = require('debug')('cypress:server:protocol') function connectAsync (opts) { return new Promise(function (resolve, reject) { + debug('connectAsync with options %o', opts) let socket = net.connect(opts) socket.once('connect', function () { socket.removeListener('error', reject) + debug('successfully connected with options %o', opts) resolve(socket) }) socket.once('error', function (err) { + debug('error connecting with options %o', opts, err) socket.removeListener('connection', resolve) reject(err) }) @@ -42,10 +45,18 @@ const getWsTargetFor = (port) => { debug('retry connecting to debugging port %d', port) }) .then(() => { - return CRI.List() + debug('CRI.List on port %d', port) + + // what happens if the next call throws an error? + // it seems to leave the browser instance open + return CRI.List({ port }) }) .then((targets) => { - debug('CRI list has %s %o', pluralize('targets', targets.length, true), targets) + debug( + 'CRI list has %s %o', + pluralize('targets', targets.length, true), + targets + ) // activate the first available id // find the first target page that's a real tab diff --git a/packages/server/lib/browsers/utils.coffee b/packages/server/lib/browsers/utils.coffee index fb0404c6874c..13115290987f 100644 --- a/packages/server/lib/browsers/utils.coffee +++ b/packages/server/lib/browsers/utils.coffee @@ -1,5 +1,6 @@ path = require("path") Promise = require("bluebird") +getPort = require("get-port") launcher = require("@packages/launcher") fs = require("../util/fs") appData = require("../util/app_data") @@ -65,6 +66,8 @@ removeOldProfiles = -> ]) module.exports = { + getPort + copyExtension getProfileDir diff --git a/packages/server/package.json b/packages/server/package.json index 5a44c03a15fc..925306612789 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -75,6 +75,7 @@ "find-process": "1.4.1", "fluent-ffmpeg": "2.1.2", "fs-extra": "8.1.0", + "get-port": "5.0.0", "getos": "3.1.1", "glob": "7.1.3", "graceful-fs": "4.1.15", From 489990240fd6430a5c3261328ed0caa97bd5afc4 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 23 Jul 2019 17:38:28 -0400 Subject: [PATCH 20/91] update unit tests --- packages/server/test/unit/browsers/chrome_spec.coffee | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/server/test/unit/browsers/chrome_spec.coffee b/packages/server/test/unit/browsers/chrome_spec.coffee index 4777e040b3b5..07e06a8835d4 100644 --- a/packages/server/test/unit/browsers/chrome_spec.coffee +++ b/packages/server/test/unit/browsers/chrome_spec.coffee @@ -28,12 +28,15 @@ describe "lib/browsers/chrome", -> sinon.stub(utils, "launch") sinon.stub(utils, "getProfileDir").returns("/profile/dir") sinon.stub(utils, "ensureCleanCache").resolves("/profile/dir/CypressCache") + # port for Chrome remote interface communication + sinon.stub(utils, "getPort").resolves(50505) it "focuses on the page and calls CRI Page.visit", -> chrome.open("chrome", "http://", {}, {}) .then => expect(@criClient.Page.bringToFront).to.have.been.calledOnce expect(@criClient.Page.navigate).to.have.been.calledOnce + expect(utils.getPort).to.have.been.calledOnce # to get remote interface port it "is noop without before:browser:launch", -> plugins.has.returns(false) @@ -72,7 +75,7 @@ describe "lib/browsers/chrome", -> "--load-extension=/foo/bar/baz.js,/path/to/ext,#{pathToTheme}" "--user-data-dir=/profile/dir" "--disk-cache-dir=/profile/dir/CypressCache", - "--remote-debugging-port=9222" + "--remote-debugging-port=50505" ]) it "normalizes multiple extensions from plugins", -> @@ -95,7 +98,7 @@ describe "lib/browsers/chrome", -> "--load-extension=/foo/bar/baz.js,/quux.js,/path/to/ext,#{pathToTheme}" "--user-data-dir=/profile/dir" "--disk-cache-dir=/profile/dir/CypressCache", - "--remote-debugging-port=9222" + "--remote-debugging-port=50505" ]) it "cleans up an unclean browser profile exit status", -> From 901272e8129c4afd84c7b35665d54f5a1fc83c71 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Wed, 24 Jul 2019 16:36:28 -0400 Subject: [PATCH 21/91] refactoring --- packages/server/lib/browsers/chrome.coffee | 11 +----- packages/server/lib/modes/run.coffee | 42 ++++++++++++++++------ packages/server/lib/video_capture.coffee | 25 ++++++------- 3 files changed, 46 insertions(+), 32 deletions(-) diff --git a/packages/server/lib/browsers/chrome.coffee b/packages/server/lib/browsers/chrome.coffee index e2082376b96d..34fc51013279 100644 --- a/packages/server/lib/browsers/chrome.coffee +++ b/packages/server/lib/browsers/chrome.coffee @@ -168,16 +168,8 @@ _navigateUsingCRI = (url) -> debug("received CRI client") debug('navigating to page %s', url) # when opening the blank page and trying to navigate - # the focus gets lost. Restore it first + # the focus gets lost. Restore it and then navigate. client.Page.bringToFront() - # .then -> - # debug('starting screencast') - # debug('screencast meta info %o', client.Page.ScreencastFrameMetadata) - # client.Page.screencastFrame (e) -> - # debug('received screencastFrame %d %o', e.sessionId, e.metadata) - # client.Page.startScreencast({ - # format: 'jpeg' - # }) .then -> client.Page.navigate({ url }) @@ -296,7 +288,6 @@ module.exports = { # start video recording and then # we will load the actual page utils.launch(browser, null, args) - # utils.launch(browser, url, args) .tap => # SECOND connect to the Chrome remote interface diff --git a/packages/server/lib/modes/run.coffee b/packages/server/lib/modes/run.coffee index b94666f27307..6c7e6e779c14 100644 --- a/packages/server/lib/modes/run.coffee +++ b/packages/server/lib/modes/run.coffee @@ -3,6 +3,9 @@ pkg = require("@packages/root") path = require("path") chalk = require("chalk") human = require("human-interval") +# because this file also interacts with video recording +# some debug logs should have ":video" namespace +debugVideo = require("debug")("cypress:server:video") debug = require("debug")("cypress:server:run") Promise = require("bluebird") logSymbols = require("log-symbols") @@ -428,6 +431,25 @@ module.exports = { errors.warning("VIDEO_RECORDING_FAILED", err.stack) }) + getChromeProps: (isHeaded, project, writeVideoFrame) -> + chromeProps = {} + + # for observing difference in wallclock and frame timestamps + # UTC time in seconds + # start = new Date() / 1000 + if isHeaded && writeVideoFrame + chromeProps.screencastFrame = (e) -> + # https://chromedevtools.github.io/devtools-protocol/tot/Page#event-screencastFrame + # now = new Date() / 1000 + # clockElapsed = now - start + # videoTimestampElapsed = e.metadata.timestamp - start + # debugVideo('%d frame %d received screencastFrame', clockElapsed, videoTimestampElapsed) + # now if only there was a way to pass the exact frame timestamp + # from e.metadata.timestamp to FFMPEG stream to generate better video! + writeVideoFrame(new Buffer(e.data, 'base64')) + + chromeProps + getElectronProps: (isHeaded, project, write) -> obj = { width: 1280 @@ -576,13 +598,7 @@ module.exports = { when "electron" @getElectronProps(browser.isHeaded, project, write) when "chrome" - if write - { - screencastFrame: (e) -> - # https://chromedevtools.github.io/devtools-protocol/tot/Page#event-screencastFrame - debug('received screencastFrame %d %o', e.sessionId, e.metadata) - write(new Buffer(e.data, 'base64')) - } + @getChromeProps(browser.isHeaded, project, write) else {} @@ -689,6 +705,11 @@ module.exports = { { project, screenshots, started, end, name, cname, videoCompression, videoUploadOnPasses, exit, spec, estimated } = options @listenForProjectEnd(project, exit) + .tap -> + # HACK wait for video to finish + # better implementation in https://github.com/cypress-io/cypress/pull/4804 + return unless end + Promise.delay(1000) .then (obj) => _.defaults(obj, { error: null @@ -847,10 +868,11 @@ module.exports = { ## we're using an event emitter interface ## to gracefully handle this in promise land - ## if we've been told to record and we're not spawning a headed browser + # video can be recorded only for specific browser modes browserCanBeRecorded = (browser) -> - true - # browser.name is "electron" and isHeadless + if browser.name is "electron" and isHeadless then return true + if browser.name is "chrome" and not isHeadless then return true + false if video if browserCanBeRecorded(browser) diff --git a/packages/server/lib/video_capture.coffee b/packages/server/lib/video_capture.coffee index cfe06c7c0456..064cbb2dc60e 100644 --- a/packages/server/lib/video_capture.coffee +++ b/packages/server/lib/video_capture.coffee @@ -65,6 +65,7 @@ module.exports = { skipped += 1 # console.log("skipping frame. total is", skipped) + # see ffmpeg options at https://ffmpeg.org/ffmpeg-formats.html cmd = ffmpeg({ source: pt priority: 20 @@ -77,13 +78,13 @@ module.exports = { debug("capture started %o", { command }) started.resolve(new Date) - + .on "codecData", (data) -> debug("capture codec data: %o", data) - + .on "stderr", (stderr) -> debug("capture stderr log %o", { message: stderr }) - + .on "error", (err, stdout, stderr) -> debug("capture errored: %o", { error: err.message, stdout, stderr }) @@ -123,33 +124,33 @@ module.exports = { ]) .on "start", (command) -> debug("compression started %o", { command }) - + .on "codecData", (data) -> debug("compression codec data: %o", data) - + total = utils.timemarkToSeconds(data.duration) - + .on "stderr", (stderr) -> debug("compression stderr log %o", { message: stderr }) - + .on "progress", (progress) -> ## bail if we dont have total yet return if not total - + debug("compression progress: %o", progress) progressed = utils.timemarkToSeconds(progress.timemark) onProgress(progressed / total) - + .on "error", (err, stdout, stderr) -> debug("compression errored: %o", { error: err.message, stdout, stderr }) - + reject(err) - + .on "end", -> debug("compression ended") - + ## we are done progressing onProgress(1) From 97e0f0d4378b6b1963f8ccfaf99863a3088bd5b1 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Wed, 24 Jul 2019 17:26:59 -0400 Subject: [PATCH 22/91] wrap chrome remote interface in our interface, limit access to send --- packages/server/.gitignore | 1 + packages/server/lib/browsers/chrome.coffee | 17 ++---- packages/server/lib/browsers/cri-client.ts | 60 ++++++++++++++++++++++ 3 files changed, 66 insertions(+), 12 deletions(-) create mode 100644 packages/server/lib/browsers/cri-client.ts diff --git a/packages/server/.gitignore b/packages/server/.gitignore index 47fb47fd690c..51e86d0a9d58 100644 --- a/packages/server/.gitignore +++ b/packages/server/.gitignore @@ -1,3 +1,4 @@ +lib/browsers/cri-client.js lib/util/ensure-url.js lib/util/proxy.js .http-mitm-proxy diff --git a/packages/server/lib/browsers/chrome.coffee b/packages/server/lib/browsers/chrome.coffee index 34fc51013279..c46a6bf89169 100644 --- a/packages/server/lib/browsers/chrome.coffee +++ b/packages/server/lib/browsers/chrome.coffee @@ -4,7 +4,6 @@ _ = require("lodash") os = require("os") path = require("path") Promise = require("bluebird") -CRI = require('chrome-remote-interface') la = require('lazy-ass') check = require('check-more-types') extension = require("@packages/extension") @@ -14,6 +13,7 @@ fs = require("../util/fs") appData = require("../util/app_data") utils = require("./utils") protocol = require("./protocol") +{initCriClient} = require("./cri-client") LOAD_EXTENSION = "--load-extension=" CHROME_VERSIONS_WITH_BUGGY_ROOT_LAYER_SCROLLING = "66 67".split(" ") @@ -139,21 +139,14 @@ _connectToChromeRemoteInterface = (port) -> protocol.getWsTargetFor(port) .then (wsUrl) -> debug("received wsUrl %s for port %d", wsUrl, port) - # TODO decide later how and where to keep the wsUrl and the CRI client - # use the websocket connection to create Chrome remote interface client - client = CRI({ - target: wsUrl, - local: true - }) + initCriClient(wsUrl) _maybeRecordVideo = (options) -> (client) -> if options.screencastFrame debug('starting screencast') - debug('screencast meta info %o', client.Page.ScreencastFrameMetadata) - client.Page.screencastFrame(options.screencastFrame) - client.Page.startScreencast({ + client.send('Page.startScreencast', { format: 'jpeg' }) client @@ -169,9 +162,9 @@ _navigateUsingCRI = (url) -> debug('navigating to page %s', url) # when opening the blank page and trying to navigate # the focus gets lost. Restore it and then navigate. - client.Page.bringToFront() + client.send("Page.bringToFront") .then -> - client.Page.navigate({ url }) + client.send("Page.navigate", { url }) module.exports = { # diff --git a/packages/server/lib/browsers/cri-client.ts b/packages/server/lib/browsers/cri-client.ts new file mode 100644 index 000000000000..a26c92b5979e --- /dev/null +++ b/packages/server/lib/browsers/cri-client.ts @@ -0,0 +1,60 @@ +const chromeRemoteInterface = require('chrome-remote-interface') + +/** + * Url returned by the Chrome Remote Interface +*/ +type websocketUrl = string + +namespace CRI { + export enum Command { + 'Page.bringToFront', + 'Page.navigate', + 'Page.startScreencast' + } + + export interface Page { + screencastFrame(cb) + } +} + +/** + * Wrapper for Chrome Remote Interface client. Only allows "send" method. + * @see https://github.com/cyrus-and/chrome-remote-interface#clientsendmethod-params-callback +*/ +interface CRIWrapper { + /** + * Sends a command to the Chrome remote interface. + * @example client.send('Page.navigate', { url }) + */ + send (command: CRI.Command, params: object):Promise + /** + * Exposes Chrome remote interface Page domain, + * buton only for certain actions that are hard to do using "send" + * + * @example client.Page.screencastFrame(cb) + */ + Page: CRI.Page +} + +/** + * Creates a wrapper for Chrome remote interface client + * that only allows to use low-level "send" method + * and not via domain objects and commands. + * + * @example initCriClient('ws://localhost:...').send('Page.bringToFront') + */ +export const initCriClient = async (debuggerUrl: websocketUrl): Promise => { + const cri = await chromeRemoteInterface({ + target: debuggerUrl, + local: true, + }) + + const client: CRIWrapper = { + send: (command: CRI.Command, params: object):Promise => { + return cri.send(command, params) + }, + Page: cri.Page, + } + + return client +} From 0e1ff8b9cfc73ccea1ececdda28dc311a91494be Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 30 Jul 2019 12:03:26 -0400 Subject: [PATCH 23/91] resolved merge --- packages/server/lib/modes/run.coffee | 84 +++++----------------------- 1 file changed, 14 insertions(+), 70 deletions(-) diff --git a/packages/server/lib/modes/run.coffee b/packages/server/lib/modes/run.coffee index 972c6da2687e..083c7856431b 100644 --- a/packages/server/lib/modes/run.coffee +++ b/packages/server/lib/modes/run.coffee @@ -411,10 +411,12 @@ trashAssets = (config = {}) -> ## dont make trashing assets fail the build errors.warning("CANNOT_TRASH_ASSETS", err.stack) -## if we've been told to record and we're not spawning a headed browser +## if we've been told to record and we're not spawning a compatible browser browserCanBeRecorded = (browser) -> - browser.name is "electron" and browser.isHeadless - + if browser.name is "electron" and isHeadless then return true + if browser.name is "chrome" and not isHeadless then return true + false + createVideoRecording = (videoName) -> outputDir = path.dirname(videoName) @@ -434,7 +436,7 @@ getVideoRecordingDelay = (startedVideoCapture) -> return DELAY_TO_LET_VIDEO_FINISH_MS return 0 - + maybeStartVideoRecording = Promise.method (options = {}) -> { spec, browser, video, videosFolder } = options @@ -442,7 +444,7 @@ maybeStartVideoRecording = Promise.method (options = {}) -> ## a video recording if not video return - + ## handle if this browser cannot actually ## be recorded if not browserCanBeRecorded(browser) @@ -474,7 +476,7 @@ maybeStartVideoRecording = Promise.method (options = {}) -> writeVideoFrame: props.writeVideoFrame, startedVideoCapture: props.startedVideoCapture, } - + module.exports = { collectTestResults @@ -485,7 +487,7 @@ module.exports = { openProjectCreate createVideoRecording - + getVideoRecordingDelay maybeStartVideoRecording @@ -929,64 +931,6 @@ module.exports = { ## we're using an event emitter interface ## to gracefully handle this in promise land - # video can be recorded only for specific browser modes - browserCanBeRecorded = (browser) -> - if browser.name is "electron" and isHeadless then return true - if browser.name is "chrome" and not isHeadless then return true - false - - if video - if browserCanBeRecorded(browser) - if not videosFolder - throw new Error("Missing videoFolder for recording") - - name = path.join(videosFolder, spec.name + ".mp4") - cname = path.join(videosFolder, spec.name + "-compressed.mp4") - - recording = @createRecording(name) - else - console.log("") - - if browser.name is "electron" and isHeaded - errors.warning("CANNOT_RECORD_VIDEO_HEADED") - else - errors.warning("CANNOT_RECORD_VIDEO_FOR_THIS_BROWSER", browser.name) - - Promise.resolve(recording) - .then (props = {}) => - ## extract the started + ended promises from recording - {start, end, write} = props - - ## make sure we start the recording first - ## before doing anything - Promise.resolve(start) - .then (started) => - Promise.props({ - results: @waitForTestsToFinishRunning({ - end - name - spec - cname - started - project - estimated - screenshots - exit: options.exit - videoCompression: options.videoCompression - videoUploadOnPasses: options.videoUploadOnPasses - }), - - connection: @waitForBrowserToConnect({ - spec - write - project - browser - screenshots - socketId: options.socketId - webSecurity: options.webSecurity - projectRoot: options.projectRoot - }) - @maybeStartVideoRecording({ spec, browser, @@ -1000,7 +944,7 @@ module.exports = { project estimated screenshots - videoName: videoRecordProps.videoName + videoName: videoRecordProps.videoName compressedVideoName: videoRecordProps.compressedVideoName endVideoCapture: videoRecordProps.endVideoCapture startedVideoCapture: videoRecordProps.startedVideoCapture @@ -1014,10 +958,10 @@ module.exports = { project browser screenshots - writeVideoFrame: videoRecordProps.writeVideoFrame - socketId: options.socketId - webSecurity: options.webSecurity - projectRoot: options.projectRoot + writeVideoFrame: videoRecordProps.writeVideoFrame + socketId: options.socketId + webSecurity: options.webSecurity + projectRoot: options.projectRoot }) }) From 34dc56c679ed9405f5b05e38c21a003bcbbeeaee Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 30 Jul 2019 12:09:13 -0400 Subject: [PATCH 24/91] fix reference --- packages/server/lib/modes/run.coffee | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/server/lib/modes/run.coffee b/packages/server/lib/modes/run.coffee index 083c7856431b..86b89aaac871 100644 --- a/packages/server/lib/modes/run.coffee +++ b/packages/server/lib/modes/run.coffee @@ -413,8 +413,8 @@ trashAssets = (config = {}) -> ## if we've been told to record and we're not spawning a compatible browser browserCanBeRecorded = (browser) -> - if browser.name is "electron" and isHeadless then return true - if browser.name is "chrome" and not isHeadless then return true + if browser.name is "electron" and browser.isHeadless then return true + if browser.name is "chrome" and browser.isHeaded then return true false createVideoRecording = (videoName) -> @@ -450,6 +450,7 @@ maybeStartVideoRecording = Promise.method (options = {}) -> if not browserCanBeRecorded(browser) console.log("") + # TODO update error messages and included browser name and headed mode if browser.name is "electron" and browser.isHeaded errors.warning("CANNOT_RECORD_VIDEO_HEADED") else From 78e42e24df3f51ba5a17f8b8a8739b9da6dc6278 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 30 Jul 2019 12:20:28 -0400 Subject: [PATCH 25/91] passing run unit spec --- packages/server/lib/modes/run.coffee | 2 +- packages/server/test/unit/modes/run_spec.coffee | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/server/lib/modes/run.coffee b/packages/server/lib/modes/run.coffee index 86b89aaac871..88c2cf6b2b5a 100644 --- a/packages/server/lib/modes/run.coffee +++ b/packages/server/lib/modes/run.coffee @@ -513,7 +513,7 @@ module.exports = { chromeProps getElectronProps: (isHeaded, project, writeVideoFrame) -> - obj = { + electronProps = { width: 1280 height: 720 show: isHeaded diff --git a/packages/server/test/unit/modes/run_spec.coffee b/packages/server/test/unit/modes/run_spec.coffee index 584d8535a3a4..69125d4c5c70 100644 --- a/packages/server/test/unit/modes/run_spec.coffee +++ b/packages/server/test/unit/modes/run_spec.coffee @@ -527,11 +527,16 @@ describe "lib/modes/run", -> .then -> expect(errors.warning).to.be.calledWith("CANNOT_RECORD_VIDEO_HEADED") - it "disables video recording for non-electron browser", -> - runMode.run({browser: "chrome"}) + it "disables video recording for non-electron non-chrome browser", -> + runMode.run({browser: "canary"}) .then -> expect(errors.warning).to.be.calledWith("CANNOT_RECORD_VIDEO_FOR_THIS_BROWSER") + it "shows no warnings for chrome browser", -> + runMode.run({browser: "chrome"}) + .then -> + expect(errors.warning).to.not.be.called + it "names video file with spec name", -> runMode.run() .then => From bb51a0df79f202a652c2e2f08e5ae4bc864c5db8 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 30 Jul 2019 12:36:13 -0400 Subject: [PATCH 26/91] stub canary search for CI to pass --- packages/server/test/unit/modes/run_spec.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/server/test/unit/modes/run_spec.coffee b/packages/server/test/unit/modes/run_spec.coffee index 69125d4c5c70..6130b9a8d750 100644 --- a/packages/server/test/unit/modes/run_spec.coffee +++ b/packages/server/test/unit/modes/run_spec.coffee @@ -528,6 +528,10 @@ describe "lib/modes/run", -> expect(errors.warning).to.be.calledWith("CANNOT_RECORD_VIDEO_HEADED") it "disables video recording for non-electron non-chrome browser", -> + browser = { name: "canary" } + + sinon.stub(browsers, "ensureAndGetByNameOrPath").resolves(browser) + runMode.run({browser: "canary"}) .then -> expect(errors.warning).to.be.calledWith("CANNOT_RECORD_VIDEO_FOR_THIS_BROWSER") From 59fb1b40d640de3e664e86942c5c64eac3675580 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 30 Jul 2019 12:54:04 -0400 Subject: [PATCH 27/91] add build step to packages/server --- packages/server/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/server/package.json b/packages/server/package.json index 79153a0758f8..33ed36cccdfe 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -4,6 +4,7 @@ "private": true, "main": "index.js", "scripts": { + "build": "tsc", "build-js": "tsc", "check-deps": "node ../../scripts/check-deps.js --verbose", "check-deps-pre": "npm run check-deps -- --prescript", From ed46cf6808b8793dbc6415e1ff17aab8a9f8562c Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 30 Jul 2019 15:21:19 -0400 Subject: [PATCH 28/91] update chrome spec --- packages/server/test/unit/browsers/chrome_spec.coffee | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/server/test/unit/browsers/chrome_spec.coffee b/packages/server/test/unit/browsers/chrome_spec.coffee index 07e06a8835d4..a1d567b4ffb8 100644 --- a/packages/server/test/unit/browsers/chrome_spec.coffee +++ b/packages/server/test/unit/browsers/chrome_spec.coffee @@ -14,9 +14,9 @@ describe "lib/browsers/chrome", -> @args = [] # mock CRI client during testing @criClient = { + send: sinon.stub().resolves() Page: { - bringToFront: sinon.stub().resolves() - navigate: sinon.stub().resolves() + screencastFrame: sinon.stub().returns() } } @@ -34,9 +34,10 @@ describe "lib/browsers/chrome", -> it "focuses on the page and calls CRI Page.visit", -> chrome.open("chrome", "http://", {}, {}) .then => - expect(@criClient.Page.bringToFront).to.have.been.calledOnce - expect(@criClient.Page.navigate).to.have.been.calledOnce expect(utils.getPort).to.have.been.calledOnce # to get remote interface port + expect(@criClient.send).to.have.been.calledTwice + expect(@criClient.send).to.have.been.calledWith("Page.bringToFront") + expect(@criClient.send).to.have.been.calledWith("Page.navigate") it "is noop without before:browser:launch", -> plugins.has.returns(false) From eaf0aa35c25330538e99b2041a63a32d0f500750 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 30 Jul 2019 15:22:43 -0400 Subject: [PATCH 29/91] do not build js on install for server --- packages/server/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/server/package.json b/packages/server/package.json index 33ed36cccdfe..79153a0758f8 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -4,7 +4,6 @@ "private": true, "main": "index.js", "scripts": { - "build": "tsc", "build-js": "tsc", "check-deps": "node ../../scripts/check-deps.js --verbose", "check-deps-pre": "npm run check-deps -- --prescript", From 8a2e24ece27930e5b6555ccf249d654e83a1edee Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 30 Jul 2019 16:28:40 -0400 Subject: [PATCH 30/91] updated spec snapshots --- .../1_commands_outside_of_test_spec.coffee.js | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/server/__snapshots__/1_commands_outside_of_test_spec.coffee.js b/packages/server/__snapshots__/1_commands_outside_of_test_spec.coffee.js index b236eeef1dec..d0c8d981f943 100644 --- a/packages/server/__snapshots__/1_commands_outside_of_test_spec.coffee.js +++ b/packages/server/__snapshots__/1_commands_outside_of_test_spec.coffee.js @@ -73,12 +73,6 @@ exports['e2e commands outside of test [chrome] fails on cy commands 1'] = ` Running: commands_outside_of_test_spec.coffee... (1 of 1) -Warning: Cypress can only record videos when using the built in 'electron' browser. - -You have set the browser to: 'chrome' - -A video will not be recorded when using this browser. - 1) An uncaught error was detected outside of a test @@ -123,7 +117,7 @@ We dynamically generated a new test to display this failure. │ Pending: 0 │ │ Skipped: 0 │ │ Screenshots: 1 │ - │ Video: false │ + │ Video: true │ │ Duration: X seconds │ │ Spec Ran: commands_outside_of_test_spec.coffee │ └────────────────────────────────────────────────────┘ @@ -134,6 +128,12 @@ We dynamically generated a new test to display this failure. - /foo/bar/.projects/e2e/cypress/screenshots/commands_outside_of_test_spec.coffee/An uncaught error was detected outside of a test (failed).png (YYYYxZZZZ) + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds) + + ==================================================================================================== (Run Finished) @@ -166,12 +166,6 @@ exports['e2e commands outside of test [chrome] fails on failing assertions 1'] = Running: assertions_failing_outside_of_test_spec.coffee... (1 of 1) -Warning: Cypress can only record videos when using the built in 'electron' browser. - -You have set the browser to: 'chrome' - -A video will not be recorded when using this browser. - 1) An uncaught error was detected outside of a test @@ -202,7 +196,7 @@ We dynamically generated a new test to display this failure. │ Pending: 0 │ │ Skipped: 0 │ │ Screenshots: 1 │ - │ Video: false │ + │ Video: true │ │ Duration: X seconds │ │ Spec Ran: assertions_failing_outside_of_test_spec.coffee │ └──────────────────────────────────────────────────────────────┘ @@ -213,6 +207,12 @@ We dynamically generated a new test to display this failure. - /foo/bar/.projects/e2e/cypress/screenshots/assertions_failing_outside_of_test_spec.coffee/An uncaught error was detected outside of a test (failed).png (YYYYxZZZZ) + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds) + + ==================================================================================================== (Run Finished) From 11805c9983078325ce571f6da6ef02c35cc875fd Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 30 Jul 2019 16:33:25 -0400 Subject: [PATCH 31/91] update 6_visit_spec snapshot --- .../server/__snapshots__/6_visit_spec.coffee.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/server/__snapshots__/6_visit_spec.coffee.js b/packages/server/__snapshots__/6_visit_spec.coffee.js index 59ffd4eb0f9d..57e7b1423806 100644 --- a/packages/server/__snapshots__/6_visit_spec.coffee.js +++ b/packages/server/__snapshots__/6_visit_spec.coffee.js @@ -838,12 +838,6 @@ exports['e2e visit resolves visits quickly in chrome (headed) 1'] = ` Running: fast_visit_spec.coffee... (1 of 1) -Warning: Cypress can only record videos when using the built in 'electron' browser. - -You have set the browser to: 'chrome' - -A video will not be recorded when using this browser. - on localhost 95% of visits are faster than XX:XX, 80% are faster than XX:XX histogram line @@ -892,12 +886,18 @@ histogram line │ Pending: 0 │ │ Skipped: 0 │ │ Screenshots: 0 │ - │ Video: false │ + │ Video: true │ │ Duration: X seconds │ │ Spec Ran: fast_visit_spec.coffee │ └──────────────────────────────────────┘ + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds) + + ==================================================================================================== (Run Finished) From 6cf87c1b229952383386410c19ab9b716219cf97 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 30 Jul 2019 16:34:38 -0400 Subject: [PATCH 32/91] update snapshot for 6_web_security_spec --- .../__snapshots__/6_web_security_spec.coffee.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/server/__snapshots__/6_web_security_spec.coffee.js b/packages/server/__snapshots__/6_web_security_spec.coffee.js index 925119bbe4c6..79726b72caa8 100644 --- a/packages/server/__snapshots__/6_web_security_spec.coffee.js +++ b/packages/server/__snapshots__/6_web_security_spec.coffee.js @@ -185,12 +185,6 @@ exports['e2e web security when disabled passes 1'] = ` Running: web_security_spec.coffee... (1 of 1) -Warning: Cypress can only record videos when using the built in 'electron' browser. - -You have set the browser to: 'chrome' - -A video will not be recorded when using this browser. - web security ✓ fails when clicking to another origin @@ -210,12 +204,18 @@ A video will not be recorded when using this browser. │ Pending: 0 │ │ Skipped: 0 │ │ Screenshots: 0 │ - │ Video: false │ + │ Video: true │ │ Duration: X seconds │ │ Spec Ran: web_security_spec.coffee │ └────────────────────────────────────────┘ + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds) + + ==================================================================================================== (Run Finished) From 4594fba162224d93846753c4a97abbb87b9339bc Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 30 Jul 2019 16:36:46 -0400 Subject: [PATCH 33/91] update snapshot for 3_plugins_spec --- .../server/__snapshots__/3_plugins_spec.coffee.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/server/__snapshots__/3_plugins_spec.coffee.js b/packages/server/__snapshots__/3_plugins_spec.coffee.js index 163ee4f41137..fcc446489d82 100644 --- a/packages/server/__snapshots__/3_plugins_spec.coffee.js +++ b/packages/server/__snapshots__/3_plugins_spec.coffee.js @@ -198,12 +198,6 @@ exports['e2e plugins works with user extensions 1'] = ` Running: app_spec.coffee... (1 of 1) -Warning: Cypress can only record videos when using the built in 'electron' browser. - -You have set the browser to: 'chrome' - -A video will not be recorded when using this browser. - ✓ can inject text from an extension @@ -219,12 +213,18 @@ A video will not be recorded when using this browser. │ Pending: 0 │ │ Skipped: 0 │ │ Screenshots: 0 │ - │ Video: false │ + │ Video: true │ │ Duration: X seconds │ │ Spec Ran: app_spec.coffee │ └───────────────────────────────┘ + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /foo/bar/.projects/plugin-extension/cypress/videos/abc123.mp4 (X seconds) + + ==================================================================================================== (Run Finished) From 35aeb614ae9b7e0509e5224bddaf5497f7fcc07f Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 30 Jul 2019 16:37:43 -0400 Subject: [PATCH 34/91] update snapshot for 3_user_agent_spec --- .../__snapshots__/3_user_agent_spec.coffee.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/server/__snapshots__/3_user_agent_spec.coffee.js b/packages/server/__snapshots__/3_user_agent_spec.coffee.js index 94e6b822c56a..683c1c390102 100644 --- a/packages/server/__snapshots__/3_user_agent_spec.coffee.js +++ b/packages/server/__snapshots__/3_user_agent_spec.coffee.js @@ -16,12 +16,6 @@ exports['e2e user agent passes on chrome 1'] = ` Running: user_agent_spec.coffee... (1 of 1) -Warning: Cypress can only record videos when using the built in 'electron' browser. - -You have set the browser to: 'chrome' - -A video will not be recorded when using this browser. - user agent ✓ is set on visits @@ -40,12 +34,18 @@ A video will not be recorded when using this browser. │ Pending: 0 │ │ Skipped: 0 │ │ Screenshots: 0 │ - │ Video: false │ + │ Video: true │ │ Duration: X seconds │ │ Spec Ran: user_agent_spec.coffee │ └──────────────────────────────────────┘ + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds) + + ==================================================================================================== (Run Finished) From bbf6d4fd8e716f2341211932dcf03a8f8d3e252e Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 30 Jul 2019 16:39:44 -0400 Subject: [PATCH 35/91] update snapshot for 5_stdout_spec --- .../server/__snapshots__/5_stdout_spec.coffee.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/server/__snapshots__/5_stdout_spec.coffee.js b/packages/server/__snapshots__/5_stdout_spec.coffee.js index 3cbaab1866ac..47376ad0ee56 100644 --- a/packages/server/__snapshots__/5_stdout_spec.coffee.js +++ b/packages/server/__snapshots__/5_stdout_spec.coffee.js @@ -357,12 +357,6 @@ exports['e2e stdout logs that chrome cannot be recorded 1'] = ` Running: simple_spec.coffee... (1 of 1) -Warning: Cypress can only record videos when using the built in 'electron' browser. - -You have set the browser to: 'chrome' - -A video will not be recorded when using this browser. - ✓ is true @@ -378,12 +372,18 @@ A video will not be recorded when using this browser. │ Pending: 0 │ │ Skipped: 0 │ │ Screenshots: 0 │ - │ Video: false │ + │ Video: true │ │ Duration: X seconds │ │ Spec Ran: simple_spec.coffee │ └──────────────────────────────────┘ + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds) + + ==================================================================================================== (Run Finished) From 77144e069385f86b0261cab6d3630738ddec649a Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 30 Jul 2019 16:50:22 -0400 Subject: [PATCH 36/91] update snapshot for 2_browser_path_spec --- .../__snapshots__/2_browser_path_spec.coffee.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/server/__snapshots__/2_browser_path_spec.coffee.js b/packages/server/__snapshots__/2_browser_path_spec.coffee.js index e755aa8ccef9..d8bb1de7366c 100644 --- a/packages/server/__snapshots__/2_browser_path_spec.coffee.js +++ b/packages/server/__snapshots__/2_browser_path_spec.coffee.js @@ -16,12 +16,6 @@ exports['e2e launching browsers by path works with an installed browser path 1'] Running: simple_spec.coffee... (1 of 1) -Warning: Cypress can only record videos when using the built in 'electron' browser. - -You have set the browser to: 'chrome' - -A video will not be recorded when using this browser. - ✓ is true @@ -37,12 +31,18 @@ A video will not be recorded when using this browser. │ Pending: 0 │ │ Skipped: 0 │ │ Screenshots: 0 │ - │ Video: false │ + │ Video: true │ │ Duration: X seconds │ │ Spec Ran: simple_spec.coffee │ └──────────────────────────────────┘ + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds) + + ==================================================================================================== (Run Finished) From b00f75316e60ef1d111d5ba0b1c9ec3f193174a3 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Fri, 2 Aug 2019 09:46:58 -0400 Subject: [PATCH 37/91] do not git ignore js files, add note why --- packages/server/.gitignore | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/server/.gitignore b/packages/server/.gitignore index 51e86d0a9d58..5e1717fe3e47 100644 --- a/packages/server/.gitignore +++ b/packages/server/.gitignore @@ -1,4 +1,5 @@ -lib/browsers/cri-client.js -lib/util/ensure-url.js -lib/util/proxy.js +# we do not explicitly ignore JavaScript files in "lib/browsers" folder +# because when we add TS files we do not transpile them as a build step +# instead always use require hooks to transpile TS files on the fly + .http-mitm-proxy From 5f5d4b07fb0e2ef44aea3694978c6ea055138a43 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 10 Sep 2019 15:16:37 -0400 Subject: [PATCH 38/91] update several snapshots with video on Chrome --- .../1_commands_outside_of_test_spec.coffee.js | 12 ------------ .../__snapshots__/2_browser_path_spec.coffee.js | 6 ------ .../server/__snapshots__/3_plugins_spec.coffee.js | 6 ------ .../server/__snapshots__/3_user_agent_spec.coffee.js | 6 ------ .../server/__snapshots__/5_stdout_spec.coffee.js | 6 ------ .../__snapshots__/6_web_security_spec.coffee.js | 9 +++------ 6 files changed, 3 insertions(+), 42 deletions(-) diff --git a/packages/server/__snapshots__/1_commands_outside_of_test_spec.coffee.js b/packages/server/__snapshots__/1_commands_outside_of_test_spec.coffee.js index d0c8d981f943..76c496a55aa6 100644 --- a/packages/server/__snapshots__/1_commands_outside_of_test_spec.coffee.js +++ b/packages/server/__snapshots__/1_commands_outside_of_test_spec.coffee.js @@ -128,12 +128,6 @@ We dynamically generated a new test to display this failure. - /foo/bar/.projects/e2e/cypress/screenshots/commands_outside_of_test_spec.coffee/An uncaught error was detected outside of a test (failed).png (YYYYxZZZZ) - (Video) - - - Started processing: Compressing to 32 CRF - - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds) - - ==================================================================================================== (Run Finished) @@ -207,12 +201,6 @@ We dynamically generated a new test to display this failure. - /foo/bar/.projects/e2e/cypress/screenshots/assertions_failing_outside_of_test_spec.coffee/An uncaught error was detected outside of a test (failed).png (YYYYxZZZZ) - (Video) - - - Started processing: Compressing to 32 CRF - - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds) - - ==================================================================================================== (Run Finished) diff --git a/packages/server/__snapshots__/2_browser_path_spec.coffee.js b/packages/server/__snapshots__/2_browser_path_spec.coffee.js index d8bb1de7366c..9f33e6ac1e85 100644 --- a/packages/server/__snapshots__/2_browser_path_spec.coffee.js +++ b/packages/server/__snapshots__/2_browser_path_spec.coffee.js @@ -37,12 +37,6 @@ exports['e2e launching browsers by path works with an installed browser path 1'] └──────────────────────────────────┘ - (Video) - - - Started processing: Compressing to 32 CRF - - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds) - - ==================================================================================================== (Run Finished) diff --git a/packages/server/__snapshots__/3_plugins_spec.coffee.js b/packages/server/__snapshots__/3_plugins_spec.coffee.js index fcc446489d82..09e430be41ee 100644 --- a/packages/server/__snapshots__/3_plugins_spec.coffee.js +++ b/packages/server/__snapshots__/3_plugins_spec.coffee.js @@ -219,12 +219,6 @@ exports['e2e plugins works with user extensions 1'] = ` └───────────────────────────────┘ - (Video) - - - Started processing: Compressing to 32 CRF - - Finished processing: /foo/bar/.projects/plugin-extension/cypress/videos/abc123.mp4 (X seconds) - - ==================================================================================================== (Run Finished) diff --git a/packages/server/__snapshots__/3_user_agent_spec.coffee.js b/packages/server/__snapshots__/3_user_agent_spec.coffee.js index 683c1c390102..19d37115cb7c 100644 --- a/packages/server/__snapshots__/3_user_agent_spec.coffee.js +++ b/packages/server/__snapshots__/3_user_agent_spec.coffee.js @@ -40,12 +40,6 @@ exports['e2e user agent passes on chrome 1'] = ` └──────────────────────────────────────┘ - (Video) - - - Started processing: Compressing to 32 CRF - - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds) - - ==================================================================================================== (Run Finished) diff --git a/packages/server/__snapshots__/5_stdout_spec.coffee.js b/packages/server/__snapshots__/5_stdout_spec.coffee.js index 47376ad0ee56..28471fa391a9 100644 --- a/packages/server/__snapshots__/5_stdout_spec.coffee.js +++ b/packages/server/__snapshots__/5_stdout_spec.coffee.js @@ -378,12 +378,6 @@ exports['e2e stdout logs that chrome cannot be recorded 1'] = ` └──────────────────────────────────┘ - (Video) - - - Started processing: Compressing to 32 CRF - - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds) - - ==================================================================================================== (Run Finished) diff --git a/packages/server/__snapshots__/6_web_security_spec.coffee.js b/packages/server/__snapshots__/6_web_security_spec.coffee.js index 79726b72caa8..40c6e62277b6 100644 --- a/packages/server/__snapshots__/6_web_security_spec.coffee.js +++ b/packages/server/__snapshots__/6_web_security_spec.coffee.js @@ -185,6 +185,9 @@ exports['e2e web security when disabled passes 1'] = ` Running: web_security_spec.coffee... (1 of 1) +Timed out waiting for the browser to connect. Retrying... +The automation client disconnected. Cannot continue running tests. + web security ✓ fails when clicking to another origin @@ -210,12 +213,6 @@ exports['e2e web security when disabled passes 1'] = ` └────────────────────────────────────────┘ - (Video) - - - Started processing: Compressing to 32 CRF - - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds) - - ==================================================================================================== (Run Finished) From 787c2363765aaf26d7b8e7755825076875feb01a Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 10 Sep 2019 15:51:49 -0400 Subject: [PATCH 39/91] update visit performance snapshot --- .../server/__snapshots__/cy_visit_performance_spec.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/server/__snapshots__/cy_visit_performance_spec.js b/packages/server/__snapshots__/cy_visit_performance_spec.js index 9e26e951b923..904c364f9458 100644 --- a/packages/server/__snapshots__/cy_visit_performance_spec.js +++ b/packages/server/__snapshots__/cy_visit_performance_spec.js @@ -16,13 +16,6 @@ exports['cy.visit performance tests pass in chrome 1'] = ` Running: fast_visit_spec.coffee... (1 of 1) -Warning: Cypress can only record videos when using the built in 'electron' browser. - -You have set the browser to: 'chrome' - -A video will not be recorded when using this browser. - - on localhost 95% of visits are faster than XX:XX, 80% are faster than XX:XX histogram line histogram line @@ -70,7 +63,7 @@ histogram line │ Pending: 0 │ │ Skipped: 0 │ │ Screenshots: 0 │ - │ Video: false │ + │ Video: true │ │ Duration: X seconds │ │ Spec Ran: fast_visit_spec.coffee │ └──────────────────────────────────────┘ From 53ac19022c205ce064967faf18647db81d2f0278 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Wed, 11 Sep 2019 15:43:09 -0400 Subject: [PATCH 40/91] add chrome-remote-interface dependency --- packages/server/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/server/package.json b/packages/server/package.json index 7756574e5f6e..ed57d9be1404 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -53,6 +53,7 @@ "charset": "1.0.1", "check-more-types": "2.24.0", "chokidar": "3.0.1", + "chrome-remote-interface": "0.28.0", "cjsxify": "0.3.0", "cli-table3": "0.5.1", "color-string": "1.5.3", From 7a56d43099d4031da17f2d0d22953920e62eceeb Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Sun, 29 Sep 2019 00:50:59 -0400 Subject: [PATCH 41/91] cleanup coffeescript conversion to JS, fix some type errors, make parallel override clearer --- packages/server/lib/modes/record.coffee | 11 +- packages/server/lib/modes/run.js | 180 ++++++++++++------------ 2 files changed, 98 insertions(+), 93 deletions(-) diff --git a/packages/server/lib/modes/record.coffee b/packages/server/lib/modes/record.coffee index f616b996d7c9..03d5ccfb02e9 100644 --- a/packages/server/lib/modes/record.coffee +++ b/packages/server/lib/modes/record.coffee @@ -544,7 +544,9 @@ createRunAndRecordSpecs = (options = {}) -> if not resp ## if a forked run, can't record and can't be parallel ## because the necessary env variables aren't present - runAllSpecs({}, false) + runAllSpecs({ + parallel: false + }) else { runUrl, runId, machineId, groupId } = resp @@ -625,7 +627,12 @@ createRunAndRecordSpecs = (options = {}) -> instanceId }) - runAllSpecs({ beforeSpecRun, afterSpecRun, runUrl }) + runAllSpecs({ + runUrl, + parallel, + beforeSpecRun, + afterSpecRun, + }) module.exports = { createRun diff --git a/packages/server/lib/modes/run.js b/packages/server/lib/modes/run.js index 1c37aefc3e08..d64cadb235eb 100644 --- a/packages/server/lib/modes/run.js +++ b/packages/server/lib/modes/run.js @@ -736,14 +736,13 @@ module.exports = { chalk.cyan(`Compressing to ${videoCompression} CRF`) ) - const started = new Date() + const started = Date.now() let progress = Date.now() - const throttle = - env.get('VIDEO_COMPRESSION_THROTTLE') || human('10 seconds') + const throttle = env.get('VIDEO_COMPRESSION_THROTTLE') || human('10 seconds') const onProgress = function (float) { if (float === 1) { - const finished = new Date() - started + const finished = Date.now() - started const dur = `(${humanTime.long(finished)})` console.log( @@ -755,7 +754,7 @@ module.exports = { return console.log('') } - if (new Date() - progress > throttle) { + if (Date.now() - progress > throttle) { // bump up the progress so we dont // continuously get notifications progress += throttle @@ -863,12 +862,11 @@ module.exports = { }, waitForBrowserToConnect (options = {}) { - let waitForBrowserToConnect const { project, socketId, timeout } = options let attempts = 0 - return (waitForBrowserToConnect = () => { + const wait = () => { return Promise.join( this.waitForSocketConnection(project, socketId), this.launchBrowser(options) @@ -881,14 +879,15 @@ module.exports = { // always first close the open browsers // before retrying or dieing - return openProject.closeBrowser().then(() => { + return openProject.closeBrowser() + .then(() => { if (attempts === 1 || attempts === 2) { // try again up to 3 attempts const word = attempts === 1 ? 'Retrying...' : 'Retrying again...' errors.warning('TESTS_DID_NOT_START_RETRYING', word) - return waitForBrowserToConnect() + return wait() } err = errors.get('TESTS_DID_NOT_START_FAILED') @@ -897,7 +896,9 @@ module.exports = { return project.emit('exitEarlyWithErr', err.message) }) }) - })() + } + + return wait() }, waitForSocketConnection (project, id) { @@ -1096,7 +1097,8 @@ module.exports = { runEachSpec, afterSpecRun, beforeSpecRun, - }).then((runs = []) => { + }) + .then((runs = []) => { results.startedTestsAt = getRun(_.first(runs), 'stats.wallClockStartedAt') results.endedTestsAt = getRun(_.last(runs), 'stats.wallClockEndedAt') results.totalDuration = reduceRuns(runs, 'stats.wallClockDuration') @@ -1138,7 +1140,8 @@ module.exports = { browser, video: options.video, videosFolder: options.videosFolder, - }).then((videoRecordProps = {}) => { + }) + .then((videoRecordProps = {}) => { return Promise.props({ results: this.waitForTestsToFinishRunning({ spec, @@ -1219,95 +1222,90 @@ module.exports = { recordMode.throwIfIndeterminateCiBuildId(ciBuildId, parallel, group) } - if (record) { - recordMode.throwIfNoProjectId(projectId) - recordMode.throwIfIncorrectCiBuildIdUsage(ciBuildId, parallel, group) - recordMode.throwIfIndeterminateCiBuildId(ciBuildId, parallel, group) + return Promise.all([ + system.info(), + browsers.ensureAndGetByNameOrPath(browserName), + this.findSpecs(config, specPattern), + trashAssets(config), + removeOldProfiles(), + ]) + .spread((sys = {}, browser = {}, specs = []) => { + // return only what is return to the specPattern + if (specPattern) { + specPattern = specsUtil.getPatternRelativeToProjectRoot( + specPattern, + projectRoot + ) } - return Promise.all([ - system.info(), - browsers.ensureAndGetByNameOrPath(browserName), - this.findSpecs(config, specPattern), - trashAssets(config), - removeOldProfiles(), - ]).spread((sys = {}, browser = {}, specs = []) => { - // return only what is return to the specPattern - if (specPattern) { - specPattern = specsUtil.getPatternRelativeToProjectRoot( - specPattern, - projectRoot - ) - } - - if (!specs.length) { - errors.throw( - 'NO_SPECS_FOUND', - config.integrationFolder, - specPattern - ) - } + if (!specs.length) { + errors.throw( + 'NO_SPECS_FOUND', + config.integrationFolder, + specPattern + ) + } - if (browser.family === 'chrome') { - chromePolicyCheck.run(onWarning) - } + if (browser.family === 'chrome') { + chromePolicyCheck.run(onWarning) + } - const runAllSpecs = ( - { beforeSpecRun, afterSpecRun, runUrl }, - parallelOverride = parallel - ) => { - return this.runSpecs({ - beforeSpecRun, - afterSpecRun, - projectRoot, - specPattern, - socketId, - parallel: parallelOverride, - browser, - project, - runUrl, - group, - config, - specs, - sys, - videosFolder: config.videosFolder, - video: config.video, - videoCompression: config.videoCompression, - videoUploadOnPasses: config.videoUploadOnPasses, - exit: options.exit, - headed: options.headed, - outputPath: options.outputPath, - }).tap(renderSummaryTable(runUrl)) - } + const runAllSpecs = ({ beforeSpecRun, afterSpecRun, runUrl, parallel }) => { + return this.runSpecs({ + beforeSpecRun, + afterSpecRun, + projectRoot, + specPattern, + socketId, + parallel, + browser, + project, + runUrl, + group, + config, + specs, + sys, + videosFolder: config.videosFolder, + video: config.video, + videoCompression: config.videoCompression, + videoUploadOnPasses: config.videoUploadOnPasses, + exit: options.exit, + headed: options.headed, + outputPath: options.outputPath, + }) + .tap(renderSummaryTable(runUrl)) + } - if (record) { - const { projectName } = config - - return recordMode.createRunAndRecordSpecs({ - key, - sys, - specs, - group, - browser, - parallel, - ciBuildId, - projectId, - projectRoot, - projectName, - specPattern, - runAllSpecs, - }) - } + if (record) { + const { projectName } = config + + return recordMode.createRunAndRecordSpecs({ + key, + sys, + specs, + group, + browser, + parallel, + ciBuildId, + projectId, + projectRoot, + projectName, + specPattern, + runAllSpecs, + }) + } - // not recording, can't be parallel - return runAllSpecs({}, false) + // not recording, can't be parallel + return runAllSpecs({ + parallel: false, }) - } - ) + }) + }) }, run (options) { - return electronApp.ready().then(() => { + return electronApp.ready() + .then(() => { return this.ready(options) }) }, From 96b56d606129168484abaad5ba82f0efe1d0a6b0 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Sun, 29 Sep 2019 01:17:30 -0400 Subject: [PATCH 42/91] fix failing tests --- packages/server/test/unit/modes/record_spec.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/test/unit/modes/record_spec.coffee b/packages/server/test/unit/modes/record_spec.coffee index 0d8de98714c9..8e66ad5cf85d 100644 --- a/packages/server/test/unit/modes/record_spec.coffee +++ b/packages/server/test/unit/modes/record_spec.coffee @@ -126,7 +126,7 @@ describe "lib/modes/record", -> runAllSpecs }) .then -> - expect(runAllSpecs).to.have.been.calledWith({}, false) + expect(runAllSpecs).to.have.been.calledWith({ parallel: false }) expect(createRun).to.have.been.calledOnce expect(createRun.firstCall.args).to.have.length(1) { commit } = createRun.firstCall.args[0] @@ -177,7 +177,7 @@ describe "lib/modes/record", -> runAllSpecs }) .then -> - expect(runAllSpecs).to.have.been.calledWith({}, false) + expect(runAllSpecs).to.have.been.calledWith({ parallel: false }) expect(createRun).to.have.been.calledOnce expect(createRun.firstCall.args).to.have.length(1) { commit } = createRun.firstCall.args[0] From 5f6b9a735d5b53ceef7912fc45b48c41e2d7d926 Mon Sep 17 00:00:00 2001 From: Jennifer Shehane Date: Tue, 1 Oct 2019 14:50:50 -0400 Subject: [PATCH 43/91] Fix snapshot - now we do record in Chrome, so warning message is no longer there. --- packages/server/__snapshots__/5_subdomain_spec.coffee.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/server/__snapshots__/5_subdomain_spec.coffee.js b/packages/server/__snapshots__/5_subdomain_spec.coffee.js index d6b9e0fd261e..43cc088c7b1d 100644 --- a/packages/server/__snapshots__/5_subdomain_spec.coffee.js +++ b/packages/server/__snapshots__/5_subdomain_spec.coffee.js @@ -86,12 +86,6 @@ exports['e2e subdomain passes in chrome 1'] = ` Running: subdomain_spec.coffee... (1 of 1) -Warning: Cypress can only record videos when using the built in 'electron' browser. - -You have set the browser to: 'chrome' - -A video will not be recorded when using this browser. - subdomains ✓ can swap to help.foobar.com:2292 @@ -118,7 +112,7 @@ A video will not be recorded when using this browser. │ Pending: 2 │ │ Skipped: 0 │ │ Screenshots: 0 │ - │ Video: false │ + │ Video: true │ │ Duration: X seconds │ │ Spec Ran: subdomain_spec.coffee │ └─────────────────────────────────────┘ From 506589bc9e56f4015c0511bf3d42510323610af8 Mon Sep 17 00:00:00 2001 From: Jennifer Shehane Date: Tue, 1 Oct 2019 15:33:21 -0400 Subject: [PATCH 44/91] remove chrome warnings about not recording from snapshot --- packages/server/__snapshots__/2_cookies_spec.coffee.js | 8 +------- packages/server/__snapshots__/4_request_spec.coffee.js | 8 +------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/packages/server/__snapshots__/2_cookies_spec.coffee.js b/packages/server/__snapshots__/2_cookies_spec.coffee.js index 7c8873d24898..fda5aa48f1e2 100644 --- a/packages/server/__snapshots__/2_cookies_spec.coffee.js +++ b/packages/server/__snapshots__/2_cookies_spec.coffee.js @@ -16,12 +16,6 @@ exports['e2e cookies passes in chrome 1'] = ` Running: cookies_spec.coffee... (1 of 1) -Warning: Cypress can only record videos when using the built in 'electron' browser. - -You have set the browser to: 'chrome' - -A video will not be recorded when using this browser. - cookies with whitelist @@ -49,7 +43,7 @@ A video will not be recorded when using this browser. │ Pending: 0 │ │ Skipped: 0 │ │ Screenshots: 0 │ - │ Video: false │ + │ Video: true │ │ Duration: X seconds │ │ Spec Ran: cookies_spec.coffee │ └───────────────────────────────────┘ diff --git a/packages/server/__snapshots__/4_request_spec.coffee.js b/packages/server/__snapshots__/4_request_spec.coffee.js index d4938f054877..7590d03993b8 100644 --- a/packages/server/__snapshots__/4_request_spec.coffee.js +++ b/packages/server/__snapshots__/4_request_spec.coffee.js @@ -88,12 +88,6 @@ exports['e2e requests passes in chrome 1'] = ` Running: request_spec.coffee... (1 of 1) -Warning: Cypress can only record videos when using the built in 'electron' browser. - -You have set the browser to: 'chrome' - -A video will not be recorded when using this browser. - redirects + requests ✓ gets and sets cookies from cy.request @@ -122,7 +116,7 @@ A video will not be recorded when using this browser. │ Pending: 0 │ │ Skipped: 0 │ │ Screenshots: 0 │ - │ Video: false │ + │ Video: true │ │ Duration: X seconds │ │ Spec Ran: request_spec.coffee │ └───────────────────────────────────┘ From e3a93c7267495a7a26a3501f90bcac6e4e578484 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Wed, 2 Oct 2019 10:05:21 -0400 Subject: [PATCH 45/91] Remove performance tests from 6_visit_spec snapshot --- .../__snapshots__/6_visit_spec.coffee.js | 181 +----------------- 1 file changed, 1 insertion(+), 180 deletions(-) diff --git a/packages/server/__snapshots__/6_visit_spec.coffee.js b/packages/server/__snapshots__/6_visit_spec.coffee.js index addcd23658d2..5a3c52e5927d 100644 --- a/packages/server/__snapshots__/6_visit_spec.coffee.js +++ b/packages/server/__snapshots__/6_visit_spec.coffee.js @@ -798,183 +798,4 @@ Error: ESOCKETTIMEDOUT 1 of 1 failed (100%) XX:XX 3 - 3 - - - - (Run Starting) - - ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ Cypress: 1.2.3 │ - │ Browser: FooBrowser 88 │ - │ Specs: 1 found (fast_visit_spec.coffee) │ - │ Searched: cypress/integration/fast_visit_spec.coffee │ - └────────────────────────────────────────────────────────────────────────────────────────────────┘ - - -──────────────────────────────────────────────────────────────────────────────────────────────────── - - Running: fast_visit_spec.coffee... (1 of 1) - - - on localhost 95% of visits are faster than XX:XX, 80% are faster than XX:XX -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line - ✓ with connection: close -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line - ✓ with connection: keep-alive - - - 2 passing - - - (Results) - - ┌──────────────────────────────────────┐ - │ Tests: 2 │ - │ Passing: 2 │ - │ Failing: 0 │ - │ Pending: 0 │ - │ Skipped: 0 │ - │ Screenshots: 0 │ - │ Video: true │ - │ Duration: X seconds │ - │ Spec Ran: fast_visit_spec.coffee │ - └──────────────────────────────────────┘ - - - (Video) - - - Started processing: Compressing to 32 CRF - - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds) - - -==================================================================================================== - - (Run Finished) - - - Spec Tests Passing Failing Pending Skipped - ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ ✔ fast_visit_spec.coffee XX:XX 2 2 - - - │ - └────────────────────────────────────────────────────────────────────────────────────────────────┘ - All specs passed! XX:XX 2 2 - - - - - -` - -exports['e2e visit resolves visits quickly in electron (headless) 1'] = ` - -==================================================================================================== - - (Run Starting) - - ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ Cypress: 1.2.3 │ - │ Browser: FooBrowser 88 │ - │ Specs: 1 found (fast_visit_spec.coffee) │ - │ Searched: cypress/integration/fast_visit_spec.coffee │ - └────────────────────────────────────────────────────────────────────────────────────────────────┘ - - -──────────────────────────────────────────────────────────────────────────────────────────────────── - - Running: fast_visit_spec.coffee... (1 of 1) - - - on localhost 95% of visits are faster than XX:XX, 80% are faster than XX:XX -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line - ✓ with connection: close -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line -histogram line - ✓ with connection: keep-alive - - - 2 passing - - - (Results) - - ┌──────────────────────────────────────┐ - │ Tests: 2 │ - │ Passing: 2 │ - │ Failing: 0 │ - │ Pending: 0 │ - │ Skipped: 0 │ - │ Screenshots: 0 │ - │ Video: true │ - │ Duration: X seconds │ - │ Spec Ran: fast_visit_spec.coffee │ - └──────────────────────────────────────┘ - - - (Video) - - - Started processing: Compressing to 32 CRF - - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds) - - -==================================================================================================== - - (Run Finished) - - - Spec Tests Passing Failing Pending Skipped - ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ ✔ fast_visit_spec.coffee XX:XX 2 2 - - - │ - └────────────────────────────────────────────────────────────────────────────────────────────────┘ - All specs passed! XX:XX 2 2 - - - - - -` +` \ No newline at end of file From 629da2ec36e847430b79590b01343f1aeb6426cc Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Wed, 2 Oct 2019 10:14:28 -0400 Subject: [PATCH 46/91] Remove error from snapshot --- packages/server/__snapshots__/6_web_security_spec.coffee.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/server/__snapshots__/6_web_security_spec.coffee.js b/packages/server/__snapshots__/6_web_security_spec.coffee.js index e6399419eccb..66302241005b 100644 --- a/packages/server/__snapshots__/6_web_security_spec.coffee.js +++ b/packages/server/__snapshots__/6_web_security_spec.coffee.js @@ -185,9 +185,6 @@ exports['e2e web security when disabled passes 1'] = ` Running: web_security_spec.coffee... (1 of 1) -Timed out waiting for the browser to connect. Retrying... -The automation client disconnected. Cannot continue running tests. - web security ✓ fails when clicking to another origin From 12195bc15ba3caaa94afdc9f395203a569e1e99a Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Wed, 2 Oct 2019 10:17:02 -0400 Subject: [PATCH 47/91] Add newline back to cy_visit_performance_spec snapshot --- packages/server/__snapshots__/cy_visit_performance_spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/server/__snapshots__/cy_visit_performance_spec.js b/packages/server/__snapshots__/cy_visit_performance_spec.js index 904c364f9458..a51caedb0bd0 100644 --- a/packages/server/__snapshots__/cy_visit_performance_spec.js +++ b/packages/server/__snapshots__/cy_visit_performance_spec.js @@ -16,6 +16,7 @@ exports['cy.visit performance tests pass in chrome 1'] = ` Running: fast_visit_spec.coffee... (1 of 1) + on localhost 95% of visits are faster than XX:XX, 80% are faster than XX:XX histogram line histogram line From 66d28ae8325abe22f6ba43ea81ae74620f812f07 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Mon, 7 Oct 2019 11:52:53 -0400 Subject: [PATCH 48/91] Navigate to about:blank --- packages/server/lib/browsers/chrome.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/lib/browsers/chrome.coffee b/packages/server/lib/browsers/chrome.coffee index c46a6bf89169..da7c144c0e4c 100644 --- a/packages/server/lib/browsers/chrome.coffee +++ b/packages/server/lib/browsers/chrome.coffee @@ -280,7 +280,7 @@ module.exports = { # first allows us to connect the remote interface, # start video recording and then # we will load the actual page - utils.launch(browser, null, args) + utils.launch(browser, "about:blank", args) .tap => # SECOND connect to the Chrome remote interface From 1100b5aaa5cb86631725cc9f7ff902527001766c Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Mon, 7 Oct 2019 15:45:09 -0400 Subject: [PATCH 49/91] look for blank page url --- packages/server/lib/browsers/protocol.js | 9 +++++---- packages/server/test/unit/browsers/chrome_spec.coffee | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/server/lib/browsers/protocol.js b/packages/server/lib/browsers/protocol.js index 0151df61d2c4..924eece1cf2d 100644 --- a/packages/server/lib/browsers/protocol.js +++ b/packages/server/lib/browsers/protocol.js @@ -61,10 +61,11 @@ const getWsTargetFor = (port) => { // find the first target page that's a real tab // and not the dev tools or background page. - // typically there are two targets found like - // { title: 'Cypress', type: 'background_page', url: 'chrome-extension://...', ... } - // { title: 'New Tab', type: 'page', url: 'chrome://newtab/', ...} - const newTabTargetFields = { type: 'page', url: 'chrome://newtab/' } + // since we open a blank page first, it has a special url + const newTabTargetFields = { + type: 'page', + url: 'about:blank', + } const target = _.find(targets, newTabTargetFields) la(target, 'could not find CRI target') diff --git a/packages/server/test/unit/browsers/chrome_spec.coffee b/packages/server/test/unit/browsers/chrome_spec.coffee index a1d567b4ffb8..f43170e27bb9 100644 --- a/packages/server/test/unit/browsers/chrome_spec.coffee +++ b/packages/server/test/unit/browsers/chrome_spec.coffee @@ -54,7 +54,7 @@ describe "lib/browsers/chrome", -> .then => # to initialize remote interface client and prepare for true tests # we load the browser with blank page first - expect(utils.launch).to.be.calledWith("chrome", null, @args) + expect(utils.launch).to.be.calledWith("chrome", "about:blank", @args) it "normalizes --load-extension if provided in plugin", -> plugins.has.returns(true) From 9755bc61c8c3dce59bbadf6ede5c4c2103d07e6e Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Mon, 7 Oct 2019 17:04:13 -0400 Subject: [PATCH 50/91] add note about avoiding Prettier --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1199b7f07ab2..e22b9558dfc0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -365,6 +365,8 @@ npm run lint-changed-fix When committing files, we run a Git pre-commit hook to lint the staged JS files. See the [`lint-staged` project](https://github.com/okonet/lint-staged). If this command fails, you may need to run `npm run lint-changed-fix` and commit those changes. +We **DO NOT** use Prettier to format code. You can find [.prettierignore](.prettierignore) file that ignores all files in this repository. To ensure this file is loaded, please always open _the root repository folder_ in your text editor, otherwise your code formatter might execute, reformatting lots of source files. + ### Tests For most packages there are typically unit and some integration tests. From cbe32b8773c071c5c3619c1f343d803e94cd1d08 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Mon, 7 Oct 2019 17:07:48 -0400 Subject: [PATCH 51/91] disable prettier a little more --- .vscode/settings.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index ae8c20a1d0d1..3f6006f21b08 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -28,4 +28,14 @@ }, ], "eslint.enable": true, + // this project does not use Prettier + // thus set all settings to disable accidentally running Prettier + "prettier.requireConfig": true, + "prettier.disableLanguages": [ + "javascript", + "javascriptreact", + "typescript", + "typescriptreact", + "json" + ] } From 06d3f813143b1d8c77fb6db594899981cb4de245 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Mon, 7 Oct 2019 17:57:57 -0400 Subject: [PATCH 52/91] call chrome remote interface close after each spec --- packages/server/lib/browsers/chrome.coffee | 17 ++++++++++++++-- packages/server/lib/browsers/cri-client.ts | 8 ++++++++ packages/server/lib/browsers/index.coffee | 23 +++++++++++++--------- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/packages/server/lib/browsers/chrome.coffee b/packages/server/lib/browsers/chrome.coffee index da7c144c0e4c..123eb85431de 100644 --- a/packages/server/lib/browsers/chrome.coffee +++ b/packages/server/lib/browsers/chrome.coffee @@ -135,7 +135,7 @@ _connectToChromeRemoteInterface = (port) -> la(check.userPort(port), "expected port number to connect CRI to", port) debug("connecting to Chrome remote interface at random port %d", port) - # at first the tab will be blank "new tab" + protocol.getWsTargetFor(port) .then (wsUrl) -> debug("received wsUrl %s for port %d", wsUrl, port) @@ -282,11 +282,24 @@ module.exports = { # we will load the actual page utils.launch(browser, "about:blank", args) - .tap => + .then (launchedBrowser) => # SECOND connect to the Chrome remote interface # and when the connection is ready # navigate to the actual url @_connectToChromeRemoteInterface(port) + .then (criClient) => + la(criClient, "expected Chrome remote interface reference", criClient) + + debug("adding method to close the remote interface client") + launchedBrowser.close = () -> + debug("closing remote interface client") + criClient.close() + + return criClient .then @_maybeRecordVideo(options) .then @_navigateUsingCRI(url) + .then -> + # return the launched browser process + # with additional method to close the remote connection + return launchedBrowser } diff --git a/packages/server/lib/browsers/cri-client.ts b/packages/server/lib/browsers/cri-client.ts index a26c92b5979e..a7c76c78df10 100644 --- a/packages/server/lib/browsers/cri-client.ts +++ b/packages/server/lib/browsers/cri-client.ts @@ -34,6 +34,11 @@ interface CRIWrapper { * @example client.Page.screencastFrame(cb) */ Page: CRI.Page + + /** + * Calls underlying remote interface clien't close + */ + close ():Promise } /** @@ -54,6 +59,9 @@ export const initCriClient = async (debuggerUrl: websocketUrl): Promise { + return cri.close() + }, } return client diff --git a/packages/server/lib/browsers/index.coffee b/packages/server/lib/browsers/index.coffee index 9151a2039cca..bf7f56c0777f 100644 --- a/packages/server/lib/browsers/index.coffee +++ b/packages/server/lib/browsers/index.coffee @@ -13,19 +13,24 @@ kill = (unbind) -> ## instance return Promise.resolve() if not instance - new Promise (resolve) -> - if unbind - instance.removeAllListeners() + # if this browser needs to close its own connection, call the close + closeRemoteClient = if instance.close then instance.close() else Promise.resolve() - instance.once "exit", (code, sigint) -> - debug("browser process killed") + closeRemoteClient.then -> + new Promise (resolve) -> + if unbind + debug("removing all listeners") + instance.removeAllListeners() - resolve.apply(null, arguments) + instance.once "exit", (code, sigint) -> + debug("browser process killed") - debug("killing browser process") + resolve.apply(null, arguments) - instance.kill() - cleanup() + debug("killing browser process") + + instance.kill() + cleanup() cleanup = -> instance = null From 15ecc5c3dafa7c85ab8076ab8407c8a2d9c021c5 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Mon, 7 Oct 2019 18:01:52 -0400 Subject: [PATCH 53/91] return promise when starting screencast --- packages/server/lib/browsers/chrome.coffee | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/server/lib/browsers/chrome.coffee b/packages/server/lib/browsers/chrome.coffee index 123eb85431de..16a19edecd1f 100644 --- a/packages/server/lib/browsers/chrome.coffee +++ b/packages/server/lib/browsers/chrome.coffee @@ -143,13 +143,16 @@ _connectToChromeRemoteInterface = (port) -> _maybeRecordVideo = (options) -> (client) -> - if options.screencastFrame - debug('starting screencast') - client.Page.screencastFrame(options.screencastFrame) - client.send('Page.startScreencast', { - format: 'jpeg' - }) - client + return client unless options.screencastFrame + + debug('starting screencast') + client.Page.screencastFrame(options.screencastFrame) + + client.send('Page.startScreencast', { + format: 'jpeg' + }) + .then -> + return client # a utility function that navigates to the given URL # once Chrome remote interface client is passed to it. From af4b07256defca33ff1b66e247336fe76a47987e Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 8 Oct 2019 08:43:42 -0400 Subject: [PATCH 54/91] update failing unit tests, add cri client close test --- packages/server/lib/browsers/chrome.coffee | 2 ++ .../test/unit/browsers/browsers_spec.coffee | 2 ++ .../server/test/unit/browsers/chrome_spec.coffee | 15 +++++++++++++-- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/server/lib/browsers/chrome.coffee b/packages/server/lib/browsers/chrome.coffee index 16a19edecd1f..d0e25985fee2 100644 --- a/packages/server/lib/browsers/chrome.coffee +++ b/packages/server/lib/browsers/chrome.coffee @@ -286,6 +286,8 @@ module.exports = { utils.launch(browser, "about:blank", args) .then (launchedBrowser) => + la(launchedBrowser, "did not get launched browser instance") + # SECOND connect to the Chrome remote interface # and when the connection is ready # navigate to the actual url diff --git a/packages/server/test/unit/browsers/browsers_spec.coffee b/packages/server/test/unit/browsers/browsers_spec.coffee index 308bf12bbc8a..606742928161 100644 --- a/packages/server/test/unit/browsers/browsers_spec.coffee +++ b/packages/server/test/unit/browsers/browsers_spec.coffee @@ -38,6 +38,8 @@ describe "lib/browsers/index", -> expect(err.type).to.eq("BROWSER_NOT_FOUND_BY_NAME") expect(err.message).to.contain("'foo-bad-bang' was not found on your system") + # Ooo, browser clean up tests are disabled?!! + # it "calls onBrowserClose callback on close", -> # onBrowserClose = sinon.stub() # browsers.launch("electron", @url, {onBrowserClose}).then -> diff --git a/packages/server/test/unit/browsers/chrome_spec.coffee b/packages/server/test/unit/browsers/chrome_spec.coffee index f43170e27bb9..da89b274d7b2 100644 --- a/packages/server/test/unit/browsers/chrome_spec.coffee +++ b/packages/server/test/unit/browsers/chrome_spec.coffee @@ -17,15 +17,18 @@ describe "lib/browsers/chrome", -> send: sinon.stub().resolves() Page: { screencastFrame: sinon.stub().returns() - } + }, + close: sinon.stub().resolves() } + # mock launched browser child process object + @launchedBrowser = {} sinon.stub(chrome, "_getArgs").returns(@args) sinon.stub(chrome, "_writeExtension").resolves("/path/to/ext") sinon.stub(chrome, "_connectToChromeRemoteInterface").resolves(@criClient) sinon.stub(plugins, "has") sinon.stub(plugins, "execute") - sinon.stub(utils, "launch") + sinon.stub(utils, "launch").resolves(@launchedBrowser) sinon.stub(utils, "getProfileDir").returns("/profile/dir") sinon.stub(utils, "ensureCleanCache").resolves("/profile/dir/CypressCache") # port for Chrome remote interface communication @@ -120,6 +123,14 @@ describe "lib/browsers/chrome", -> } }) + it "calls cri client close", -> + chrome.open("chrome", "http://", {}, {}) + .then => + expect(@launchedBrowser.close, "Chrome browser process gets close method").to.be.a("function") + @launchedBrowser.close() + .then => + expect(@criClient.close).to.have.been.calledOnce + context "#_getArgs", -> it "disables gpu when linux", -> sinon.stub(os, "platform").returns("linux") From 1c35680c6e39d145bd345d8b85a62b99d73bb6bc Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 8 Oct 2019 08:53:47 -0400 Subject: [PATCH 55/91] update integration test --- packages/server/test/integration/cypress_spec.coffee | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/server/test/integration/cypress_spec.coffee b/packages/server/test/integration/cypress_spec.coffee index bfadebe31f3c..969bd0761fdd 100644 --- a/packages/server/test/integration/cypress_spec.coffee +++ b/packages/server/test/integration/cypress_spec.coffee @@ -765,6 +765,10 @@ describe "lib/cypress", -> ee = new EE() ee.kill = -> + # ughh, would be nice to test logic inside the launcher + # that cleans up after the browser exit + # like calling client.close() if available to let the + # browser free any resources ee.emit("exit") ee.close = -> ee.emit("closed") @@ -792,7 +796,10 @@ describe "lib/cypress", -> it "chrome", -> # during testing, do not try to connect to the remote interface or # use the Chrome remote interface client - sinon.stub(chromeBrowser, "_connectToChromeRemoteInterface").resolves() + criClient = { + close: sinon.stub().resolves() + } + sinon.stub(chromeBrowser, "_connectToChromeRemoteInterface").resolves(criClient) # the "returns(resolves)" stub is due to curried method # it accepts URL to visit and then waits for actual CRI client reference # and only then navigates to that URL From 17e0421ff23c946a1d786c01e2a987df4b49f590 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Tue, 8 Oct 2019 09:56:51 -0400 Subject: [PATCH 56/91] Add verbose debug statements to cri-client --- packages/server/lib/browsers/cri-client.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/server/lib/browsers/cri-client.ts b/packages/server/lib/browsers/cri-client.ts index a26c92b5979e..e002df58a426 100644 --- a/packages/server/lib/browsers/cri-client.ts +++ b/packages/server/lib/browsers/cri-client.ts @@ -1,5 +1,9 @@ +import debugModule from 'debug' + const chromeRemoteInterface = require('chrome-remote-interface') +const debugVerbose = debugModule('cy-verbose:server:browsers:cri-client') + /** * Url returned by the Chrome Remote Interface */ @@ -51,7 +55,18 @@ export const initCriClient = async (debuggerUrl: websocketUrl): Promise => { + debugVerbose('sending %s %o', command, params) + return cri.send(command, params) + .then((result) => { + debugVerbose('received response for %s: %o', command, { result }) + + return result + }) + .catch((err) => { + debugVerbose('received error for %s: %o', command, { err }) + throw err + }) }, Page: cri.Page, } From 5f56b639058c6060c1aae5770d19f65de1531deb Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Tue, 8 Oct 2019 12:41:33 -0400 Subject: [PATCH 57/91] Use connect.createRetryingSocket for CDP connection --- packages/server/lib/browsers/protocol.js | 43 +++++++++++------------- packages/server/package.json | 1 - 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/packages/server/lib/browsers/protocol.js b/packages/server/lib/browsers/protocol.js index 924eece1cf2d..138620243e80 100644 --- a/packages/server/lib/browsers/protocol.js +++ b/packages/server/lib/browsers/protocol.js @@ -1,29 +1,28 @@ const _ = require('lodash') const CRI = require('chrome-remote-interface') -const promiseRetry = require('promise-retry') +const { connect } = require('@packages/network') const Promise = require('bluebird') -const net = require('net') const la = require('lazy-ass') const is = require('check-more-types') const pluralize = require('pluralize') const debug = require('debug')('cypress:server:protocol') -function connectAsync (opts) { - return new Promise(function (resolve, reject) { - debug('connectAsync with options %o', opts) - let socket = net.connect(opts) +function getDelayMsForRetry (i) { + if (i < 8) { + return 100 + } - socket.once('connect', function () { - socket.removeListener('error', reject) - debug('successfully connected with options %o', opts) - resolve(socket) - }) + if (i < 10) { + return 500 + } +} - socket.once('error', function (err) { - debug('error connecting with options %o', opts, err) - socket.removeListener('connection', resolve) - reject(err) - }) +function connectAsync (opts) { + return Promise.fromCallback((cb) => { + connect.createRetryingSocket({ + getDelayMsForRetry, + ...opts, + }, cb) }) } @@ -35,14 +34,10 @@ const getWsTargetFor = (port) => { debug('Getting WS connection to CRI on port %d', port) la(is.port(port), 'expected port number', port) - return promiseRetry( - (retry) => { - return connectAsync({ port }).catch(retry) - }, - { retries: 10 } - ) - .catch(() => { - debug('retry connecting to debugging port %d', port) + return connectAsync({ port }) + .catch((err) => { + debug('failed to connect to CDP %o', { port, err }) + throw err }) .then(() => { debug('CRI.List on port %d', port) diff --git a/packages/server/package.json b/packages/server/package.json index 79a3c6cdf475..ca56bde692d3 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -112,7 +112,6 @@ "p-queue": "6.1.0", "parse-domain": "2.0.0", "pluralize": "8.0.0", - "promise-retry": "1.1.1", "pumpify": "1.5.1", "ramda": "0.24.1", "randomstring": "1.1.5", From fef98de1e98af9a8c0620712a75bfdd1e2887fe7 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 8 Oct 2019 12:59:32 -0400 Subject: [PATCH 58/91] record video from chrome browsers --- packages/server/lib/browsers/chrome.coffee | 4 +++- packages/server/lib/modes/run.js | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/server/lib/browsers/chrome.coffee b/packages/server/lib/browsers/chrome.coffee index d0e25985fee2..3bc62f7b61fa 100644 --- a/packages/server/lib/browsers/chrome.coffee +++ b/packages/server/lib/browsers/chrome.coffee @@ -143,7 +143,9 @@ _connectToChromeRemoteInterface = (port) -> _maybeRecordVideo = (options) -> (client) -> - return client unless options.screencastFrame + if not options.screencastFrame + debug("screencastFrame is false") + return client debug('starting screencast') client.Page.screencastFrame(options.screencastFrame) diff --git a/packages/server/lib/modes/run.js b/packages/server/lib/modes/run.js index c8f56ef70da2..bd0ede19d6ba 100644 --- a/packages/server/lib/modes/run.js +++ b/packages/server/lib/modes/run.js @@ -612,6 +612,9 @@ module.exports = { maybeStartVideoRecording, getChromeProps (isHeaded, project, writeVideoFrame) { + debug('setting Chrome properties, is headed? %s, should write video? %s', + isHeaded, Boolean(writeVideoFrame)) + const chromeProps = {} if (isHeaded && writeVideoFrame) { @@ -805,11 +808,11 @@ module.exports = { } = options const browserOpts = (() => { - if (browser.name === 'electron') { + if (browser.family === 'electron') { return this.getElectronProps(browser.isHeaded, project, writeVideoFrame) } - if (browser.name === 'electron') { + if (browser.family === 'chrome') { return this.getChromeProps(browser.isHeaded, project, writeVideoFrame) } From d0794c16b76a65642249332b6c76418160a6697b Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 8 Oct 2019 13:21:56 -0400 Subject: [PATCH 59/91] add method for validating browser family --- packages/server/lib/browsers/index.coffee | 8 ++++++++ packages/server/lib/modes/run.js | 3 +++ .../test/unit/browsers/browsers_spec.coffee | 6 ++++++ .../server/test/unit/modes/run_spec.coffee | 19 ++++++++++++++----- 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/packages/server/lib/browsers/index.coffee b/packages/server/lib/browsers/index.coffee index bf7f56c0777f..eea2c08a401a 100644 --- a/packages/server/lib/browsers/index.coffee +++ b/packages/server/lib/browsers/index.coffee @@ -5,6 +5,11 @@ debug = require("debug")("cypress:server:browsers") utils = require("./utils") errors = require("../errors") fs = require("../util/fs") +la = require("lazy-ass") +check = require("check-more-types") + +# returns true if the passed string is a known browser family name +isBrowserFamily = check.oneOf(["electron", "chrome"]) instance = null @@ -36,6 +41,7 @@ cleanup = -> instance = null getBrowserLauncherByFamily = (family) -> + la(isBrowserFamily(family), "unknown browser family", family) switch family when "electron" require("./electron") @@ -87,6 +93,8 @@ module.exports = { close: kill + isBrowserFamily + getAllBrowsersWith: (nameOrPath) -> if nameOrPath return ensureAndGetByNameOrPath(nameOrPath, true) diff --git a/packages/server/lib/modes/run.js b/packages/server/lib/modes/run.js index bd0ede19d6ba..dcd724d1854a 100644 --- a/packages/server/lib/modes/run.js +++ b/packages/server/lib/modes/run.js @@ -1,5 +1,6 @@ /* eslint-disable no-console */ const _ = require('lodash') +const la = require('lazy-ass') const pkg = require('@packages/root') const path = require('path') const chalk = require('chalk') @@ -808,6 +809,8 @@ module.exports = { } = options const browserOpts = (() => { + la(browsers.isBrowserFamily(browser.family), 'invalid browser family in', browser) + if (browser.family === 'electron') { return this.getElectronProps(browser.isHeaded, project, writeVideoFrame) } diff --git a/packages/server/test/unit/browsers/browsers_spec.coffee b/packages/server/test/unit/browsers/browsers_spec.coffee index 606742928161..a9befe717b1f 100644 --- a/packages/server/test/unit/browsers/browsers_spec.coffee +++ b/packages/server/test/unit/browsers/browsers_spec.coffee @@ -6,6 +6,12 @@ browsers = require("#{root}../lib/browsers") utils = require("#{root}../lib/browsers/utils") describe "lib/browsers/index", -> + context ".isBrowserFamily", -> + it "allows only known browsers", -> + expect(browsers.isBrowserFamily("chrome")).to.be.true + expect(browsers.isBrowserFamily("electron")).to.be.true + expect(browsers.isBrowserFamily("my-favorite-browser")).to.be.false + context ".ensureAndGetByNameOrPath", -> it "returns browser by name", -> sinon.stub(utils, "getBrowsers").resolves([ diff --git a/packages/server/test/unit/modes/run_spec.coffee b/packages/server/test/unit/modes/run_spec.coffee index 6130b9a8d750..113ba7059fe1 100644 --- a/packages/server/test/unit/modes/run_spec.coffee +++ b/packages/server/test/unit/modes/run_spec.coffee @@ -143,7 +143,11 @@ describe "lib/modes/run", -> absolute: "/path/to/spec" } - browser = { name: "electron", isHeaded: false } + browser = { + name: "electron", + family: "electron", + isHeaded: false + } runMode.launchBrowser({ spec @@ -173,7 +177,11 @@ describe "lib/modes/run", -> absolute: "/path/to/spec" } - browser = { name: "chrome", isHeaded: true } + browser = { + name: "chrome", + family: "chrome", + isHeaded: true + } runMode.launchBrowser({ spec @@ -528,7 +536,7 @@ describe "lib/modes/run", -> expect(errors.warning).to.be.calledWith("CANNOT_RECORD_VIDEO_HEADED") it "disables video recording for non-electron non-chrome browser", -> - browser = { name: "canary" } + browser = { name: "canary", family: "chrome" } sinon.stub(browsers, "ensureAndGetByNameOrPath").resolves(browser) @@ -563,7 +571,8 @@ describe "lib/modes/run", -> sinon.stub(browsers, "ensureAndGetByNameOrPath").resolves({ name: "fooBrowser", path: "path/to/browser" - version: "777" + version: "777", + family: "electron" }) sinon.stub(runMode, "waitForSocketConnection").resolves() sinon.stub(runMode, "waitForTestsToFinishRunning").resolves({ @@ -615,7 +624,7 @@ describe "lib/modes/run", -> }) it "passes headed to openProject.launch", -> - browser = { name: "electron" } + browser = { name: "electron", family: "electron" } browsers.ensureAndGetByNameOrPath.resolves(browser) From 22b2d6231d9e0b4b0ec3dd062009cde882e9db1b Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 8 Oct 2019 13:39:00 -0400 Subject: [PATCH 60/91] update e2e spec snapshot --- packages/server/__snapshots__/6_web_security_spec.coffee.js | 6 ++++++ packages/server/lib/modes/run.js | 2 ++ 2 files changed, 8 insertions(+) diff --git a/packages/server/__snapshots__/6_web_security_spec.coffee.js b/packages/server/__snapshots__/6_web_security_spec.coffee.js index 66302241005b..5ae3d6225304 100644 --- a/packages/server/__snapshots__/6_web_security_spec.coffee.js +++ b/packages/server/__snapshots__/6_web_security_spec.coffee.js @@ -210,6 +210,12 @@ exports['e2e web security when disabled passes 1'] = ` └────────────────────────────────────────┘ + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds) + + ==================================================================================================== (Run Finished) diff --git a/packages/server/lib/modes/run.js b/packages/server/lib/modes/run.js index dcd724d1854a..2e3f5ca4cb42 100644 --- a/packages/server/lib/modes/run.js +++ b/packages/server/lib/modes/run.js @@ -819,6 +819,8 @@ module.exports = { return this.getChromeProps(browser.isHeaded, project, writeVideoFrame) } + debug('warning - cannot determine browser props for %o', browser) + return {} })() From 87716ffeed989b13ab3f7926d9ff3c0939746b5e Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 8 Oct 2019 13:40:59 -0400 Subject: [PATCH 61/91] update 4_request_spec snapshot --- packages/server/__snapshots__/4_request_spec.coffee.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/server/__snapshots__/4_request_spec.coffee.js b/packages/server/__snapshots__/4_request_spec.coffee.js index 7590d03993b8..1c85b3a3854b 100644 --- a/packages/server/__snapshots__/4_request_spec.coffee.js +++ b/packages/server/__snapshots__/4_request_spec.coffee.js @@ -122,6 +122,12 @@ exports['e2e requests passes in chrome 1'] = ` └───────────────────────────────────┘ + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds) + + ==================================================================================================== (Run Finished) From 896eb6a2dba58d32cfa5ced51e88aee49c6f16b9 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 8 Oct 2019 13:43:00 -0400 Subject: [PATCH 62/91] update snapshot for spec 1_commands_outside_of_test_spec --- .../1_commands_outside_of_test_spec.coffee.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/server/__snapshots__/1_commands_outside_of_test_spec.coffee.js b/packages/server/__snapshots__/1_commands_outside_of_test_spec.coffee.js index f6f9a46dccbb..605b1a5e23e4 100644 --- a/packages/server/__snapshots__/1_commands_outside_of_test_spec.coffee.js +++ b/packages/server/__snapshots__/1_commands_outside_of_test_spec.coffee.js @@ -128,6 +128,12 @@ We dynamically generated a new test to display this failure. - /foo/bar/.projects/e2e/cypress/screenshots/commands_outside_of_test_spec.coffee/An uncaught error was detected outside of a test (failed).png (YYYYxZZZZ) + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds) + + ==================================================================================================== (Run Finished) @@ -201,6 +207,12 @@ We dynamically generated a new test to display this failure. - /foo/bar/.projects/e2e/cypress/screenshots/assertions_failing_outside_of_test_spec.coffee/An uncaught error was detected outside of a test (failed).png (YYYYxZZZZ) + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds) + + ==================================================================================================== (Run Finished) From edb1e7d8401854ecba42c32791c562e9197451e3 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 8 Oct 2019 13:45:07 -0400 Subject: [PATCH 63/91] update snapshot for 3_plugins_spec --- packages/server/__snapshots__/3_plugins_spec.coffee.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/server/__snapshots__/3_plugins_spec.coffee.js b/packages/server/__snapshots__/3_plugins_spec.coffee.js index b8c27d9e6900..dc967c4f1bb1 100644 --- a/packages/server/__snapshots__/3_plugins_spec.coffee.js +++ b/packages/server/__snapshots__/3_plugins_spec.coffee.js @@ -218,6 +218,12 @@ exports['e2e plugins works with user extensions 1'] = ` └───────────────────────────────┘ + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /foo/bar/.projects/plugin-extension/cypress/videos/abc123.mp4 (X seconds) + + ==================================================================================================== (Run Finished) From 0530c8e1fa41bd60f7bf792ef9ec6c696a31f8e6 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 8 Oct 2019 13:46:01 -0400 Subject: [PATCH 64/91] update snapshot for spec 3_user_agent_spec --- packages/server/__snapshots__/3_user_agent_spec.coffee.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/server/__snapshots__/3_user_agent_spec.coffee.js b/packages/server/__snapshots__/3_user_agent_spec.coffee.js index 19d37115cb7c..683c1c390102 100644 --- a/packages/server/__snapshots__/3_user_agent_spec.coffee.js +++ b/packages/server/__snapshots__/3_user_agent_spec.coffee.js @@ -40,6 +40,12 @@ exports['e2e user agent passes on chrome 1'] = ` └──────────────────────────────────────┘ + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds) + + ==================================================================================================== (Run Finished) From 70bdc7d89984bff08487c280b6bc23d92458c134 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Tue, 8 Oct 2019 14:01:32 -0400 Subject: [PATCH 65/91] try: Always log video capturing errors --- packages/server/lib/modes/run.js | 4 ---- packages/server/lib/video_capture.coffee | 10 ++++------ 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/server/lib/modes/run.js b/packages/server/lib/modes/run.js index 2e3f5ca4cb42..02510e203449 100644 --- a/packages/server/lib/modes/run.js +++ b/packages/server/lib/modes/run.js @@ -787,10 +787,6 @@ module.exports = { return videoCapture.process(name, cname, videoCompression, onProgress) }) - .catch({ recordingVideoFailed: true }, () => { - // dont do anything if this error occured because - // recording the video had already failed - }) .catch((err) => { // log that post processing was attempted // but failed and dont let this change the run exit code diff --git a/packages/server/lib/video_capture.coffee b/packages/server/lib/video_capture.coffee index 3d3a9f7dfbed..fec0605ceb90 100644 --- a/packages/server/lib/video_capture.coffee +++ b/packages/server/lib/video_capture.coffee @@ -66,7 +66,7 @@ module.exports = { if not wantsWrite = pt.write(data) pt.once "drain", -> debugFrames("video stream drained") - + wantsWrite = true else skipped += 1 @@ -105,8 +105,6 @@ module.exports = { if logErrors options.onError(err, stdout, stderr) - err.recordingVideoFailed = true - ## reject the ended promise ended.reject(err) @@ -115,14 +113,14 @@ module.exports = { ended.resolve() .save(name) - + startCapturing() .then ({ cmd, startedVideoCapture }) -> return { - cmd, + cmd, endVideoCapture, writeVideoFrame, - startedVideoCapture, + startedVideoCapture, } process: (name, cname, videoCompression, onProgress = ->) -> From 0ef525b9e1f743685b03be2f8b1f95a7115aa920 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 8 Oct 2019 14:05:49 -0400 Subject: [PATCH 66/91] update snapshot for 2_browser_path_spec --- packages/server/__snapshots__/2_browser_path_spec.coffee.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/server/__snapshots__/2_browser_path_spec.coffee.js b/packages/server/__snapshots__/2_browser_path_spec.coffee.js index 9f33e6ac1e85..d8bb1de7366c 100644 --- a/packages/server/__snapshots__/2_browser_path_spec.coffee.js +++ b/packages/server/__snapshots__/2_browser_path_spec.coffee.js @@ -37,6 +37,12 @@ exports['e2e launching browsers by path works with an installed browser path 1'] └──────────────────────────────────┘ + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds) + + ==================================================================================================== (Run Finished) From 07024b9a8d525800f5dbec6a91cc805e3a6c5348 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 8 Oct 2019 14:07:01 -0400 Subject: [PATCH 67/91] update snapshot for 2_cookies_spec --- packages/server/__snapshots__/2_cookies_spec.coffee.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/server/__snapshots__/2_cookies_spec.coffee.js b/packages/server/__snapshots__/2_cookies_spec.coffee.js index fda5aa48f1e2..6a4546431d17 100644 --- a/packages/server/__snapshots__/2_cookies_spec.coffee.js +++ b/packages/server/__snapshots__/2_cookies_spec.coffee.js @@ -49,6 +49,12 @@ exports['e2e cookies passes in chrome 1'] = ` └───────────────────────────────────┘ + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds) + + ==================================================================================================== (Run Finished) From cdacbd0a4a21366a9b6389691b3e92bdb3676409 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 8 Oct 2019 14:30:11 -0400 Subject: [PATCH 68/91] better browser family test --- packages/server/lib/browsers/index.coffee | 4 +++- .../server/test/unit/browsers/browsers_spec.coffee | 12 ++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/server/lib/browsers/index.coffee b/packages/server/lib/browsers/index.coffee index eea2c08a401a..952b564db44e 100644 --- a/packages/server/lib/browsers/index.coffee +++ b/packages/server/lib/browsers/index.coffee @@ -41,7 +41,9 @@ cleanup = -> instance = null getBrowserLauncherByFamily = (family) -> - la(isBrowserFamily(family), "unknown browser family", family) + if not isBrowserFamily(family) + debug("unknown browser family", family) + switch family when "electron" require("./electron") diff --git a/packages/server/test/unit/browsers/browsers_spec.coffee b/packages/server/test/unit/browsers/browsers_spec.coffee index a9befe717b1f..295a2e07e84b 100644 --- a/packages/server/test/unit/browsers/browsers_spec.coffee +++ b/packages/server/test/unit/browsers/browsers_spec.coffee @@ -38,11 +38,15 @@ describe "lib/browsers/index", -> family: 'foo-bad' }, { browsers: [] - }).then -> + }) + .then (e) -> + console.error(e) throw new Error("should've failed") - .catch (err) -> - expect(err.type).to.eq("BROWSER_NOT_FOUND_BY_NAME") - expect(err.message).to.contain("'foo-bad-bang' was not found on your system") + , (err) -> + # by being explicit with assertions, if something is unexpected + # we will get good error message that includes the "err" object + expect(err).to.have.property("type").to.eq("BROWSER_NOT_FOUND_BY_NAME") + expect(err).to.have.property("message").to.contain("'foo-bad-bang' was not found on your system") # Ooo, browser clean up tests are disabled?!! From c1b00e93793f3b044284f3facd59f4b97e130f35 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 8 Oct 2019 14:32:03 -0400 Subject: [PATCH 69/91] update snapshot for 5_stdout_spec --- packages/server/__snapshots__/5_stdout_spec.coffee.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/server/__snapshots__/5_stdout_spec.coffee.js b/packages/server/__snapshots__/5_stdout_spec.coffee.js index 0d25ae1f48fa..5e47b0976cd7 100644 --- a/packages/server/__snapshots__/5_stdout_spec.coffee.js +++ b/packages/server/__snapshots__/5_stdout_spec.coffee.js @@ -376,6 +376,12 @@ exports['e2e stdout logs that chrome cannot be recorded 1'] = ` └──────────────────────────────────┘ + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds) + + ==================================================================================================== (Run Finished) From 7a66da41cac7453348cf0977c4aaf1d652d2c5c6 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 8 Oct 2019 14:33:26 -0400 Subject: [PATCH 70/91] update snapshot for 5_subdomain_spec --- packages/server/__snapshots__/5_subdomain_spec.coffee.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/server/__snapshots__/5_subdomain_spec.coffee.js b/packages/server/__snapshots__/5_subdomain_spec.coffee.js index 43cc088c7b1d..5818ed1b3a39 100644 --- a/packages/server/__snapshots__/5_subdomain_spec.coffee.js +++ b/packages/server/__snapshots__/5_subdomain_spec.coffee.js @@ -118,6 +118,12 @@ exports['e2e subdomain passes in chrome 1'] = ` └─────────────────────────────────────┘ + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds) + + ==================================================================================================== (Run Finished) From ddb168d2ad48ebbec546c01a3feea77351fab745 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Tue, 8 Oct 2019 15:18:30 -0400 Subject: [PATCH 71/91] Add protocol_spec tests --- .../server/__snapshots__/protocol_spec.ts.js | 12 ++++ packages/server/lib/browsers/protocol.js | 5 +- packages/server/package.json | 1 + packages/server/test/spec_helper.coffee | 1 + .../test/unit/browsers/protocol_spec.ts | 55 +++++++++++++++++++ 5 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 packages/server/__snapshots__/protocol_spec.ts.js create mode 100644 packages/server/test/unit/browsers/protocol_spec.ts diff --git a/packages/server/__snapshots__/protocol_spec.ts.js b/packages/server/__snapshots__/protocol_spec.ts.js new file mode 100644 index 000000000000..e1333e6bef07 --- /dev/null +++ b/packages/server/__snapshots__/protocol_spec.ts.js @@ -0,0 +1,12 @@ +exports['lib/browsers/protocol ._getDelayMsForRetry retries as expected 1'] = [ + 100, + 100, + 100, + 100, + 100, + 100, + 100, + 100, + 500, + 500 +] diff --git a/packages/server/lib/browsers/protocol.js b/packages/server/lib/browsers/protocol.js index 138620243e80..159fe6bb01d9 100644 --- a/packages/server/lib/browsers/protocol.js +++ b/packages/server/lib/browsers/protocol.js @@ -7,7 +7,7 @@ const is = require('check-more-types') const pluralize = require('pluralize') const debug = require('debug')('cypress:server:protocol') -function getDelayMsForRetry (i) { +function _getDelayMsForRetry (i) { if (i < 8) { return 100 } @@ -20,7 +20,7 @@ function getDelayMsForRetry (i) { function connectAsync (opts) { return Promise.fromCallback((cb) => { connect.createRetryingSocket({ - getDelayMsForRetry, + getDelayMsForRetry: _getDelayMsForRetry, ...opts, }, cb) }) @@ -71,5 +71,6 @@ const getWsTargetFor = (port) => { } module.exports = { + _getDelayMsForRetry, getWsTargetFor, } diff --git a/packages/server/package.json b/packages/server/package.json index ca56bde692d3..c2cc5a9eb268 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -154,6 +154,7 @@ "babelify": "10.0.0", "bin-up": "1.2.0", "body-parser": "1.19.0", + "chai-as-promised": "7.1.1", "chai-uuid": "1.0.6", "chokidar-cli": "1.2.2", "chrome-har-capturer": "0.13.4", diff --git a/packages/server/test/spec_helper.coffee b/packages/server/test/spec_helper.coffee index da4f4fa5d6c5..355aa89257c5 100644 --- a/packages/server/test/spec_helper.coffee +++ b/packages/server/test/spec_helper.coffee @@ -16,6 +16,7 @@ appData = require("../lib/util/app_data") require("chai") .use(require("@cypress/sinon-chai")) .use(require("chai-uuid")) +.use(require("chai-as-promised")) if process.env.UPDATE throw new Error("You're using UPDATE=1 which is the old way of updating snapshots.\n\nThe correct environment variable is SNAPSHOT_UPDATE=1") diff --git a/packages/server/test/unit/browsers/protocol_spec.ts b/packages/server/test/unit/browsers/protocol_spec.ts new file mode 100644 index 000000000000..131483fde3f1 --- /dev/null +++ b/packages/server/test/unit/browsers/protocol_spec.ts @@ -0,0 +1,55 @@ +import '../../spec_helper' +import { connect } from '@packages/network' +import CRI from 'chrome-remote-interface' +import { expect } from 'chai' +import protocol from '../../../lib/browsers/protocol' +import sinon from 'sinon' +import snapshot from 'snap-shot-it' + +describe('lib/browsers/protocol', function () { + context('._getDelayMsForRetry', function () { + it('retries as expected', function () { + let delays = [] + let delay : number + let i = 0 + + while ((delay = protocol._getDelayMsForRetry(i))) { + delays.push(delay) + i++ + } + + snapshot(delays) + }) + }) + + context('.getWsTargetFor', function () { + it('rejects if CDP connection fails', function () { + sinon.stub(connect, 'createRetryingSocket').callsArgWith(1, new Error('cdp connection failure')) + const p = protocol.getWsTargetFor(12345) + + return expect(p).to.eventually.be.rejected + }) + + it('returns the debugger URL of the first about:blank tab', function () { + const targets = [ + { + type: 'page', + url: 'chrome://newtab', + webSocketDebuggerUrl: 'foo', + }, + { + type: 'page', + url: 'about:blank', + webSocketDebuggerUrl: 'bar', + }, + ] + + sinon.stub(CRI, 'List').withArgs({ port: 12345 }).resolves(targets) + sinon.stub(connect, 'createRetryingSocket').callsArg(1) + + const p = protocol.getWsTargetFor(12345) + + return expect(p).to.eventually.equal('bar') + }) + }) +}) From dd0b7270178f2b6e2f1cee8529f1715cc7e255ee Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Tue, 8 Oct 2019 15:46:50 -0400 Subject: [PATCH 72/91] do not capture video during performance test --- .../server/__snapshots__/cy_visit_performance_spec.js | 10 ++-------- .../test/performance/cy_visit_performance_spec.js | 2 ++ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/server/__snapshots__/cy_visit_performance_spec.js b/packages/server/__snapshots__/cy_visit_performance_spec.js index a51caedb0bd0..0482df8f2f56 100644 --- a/packages/server/__snapshots__/cy_visit_performance_spec.js +++ b/packages/server/__snapshots__/cy_visit_performance_spec.js @@ -64,7 +64,7 @@ histogram line │ Pending: 0 │ │ Skipped: 0 │ │ Screenshots: 0 │ - │ Video: true │ + │ Video: false │ │ Duration: X seconds │ │ Spec Ran: fast_visit_spec.coffee │ └──────────────────────────────────────┘ @@ -150,18 +150,12 @@ histogram line │ Pending: 0 │ │ Skipped: 0 │ │ Screenshots: 0 │ - │ Video: true │ + │ Video: false │ │ Duration: X seconds │ │ Spec Ran: fast_visit_spec.coffee │ └──────────────────────────────────────┘ - (Video) - - - Started processing: Compressing to 32 CRF - - Finished processing: /foo/bar/.projects/e2e/cypress/videos/abc123.mp4 (X seconds) - - ==================================================================================================== (Run Finished) diff --git a/packages/server/test/performance/cy_visit_performance_spec.js b/packages/server/test/performance/cy_visit_performance_spec.js index 30e19fe61798..40049f676a2c 100644 --- a/packages/server/test/performance/cy_visit_performance_spec.js +++ b/packages/server/test/performance/cy_visit_performance_spec.js @@ -19,6 +19,7 @@ context('cy.visit performance tests', function () { }, settings: { baseUrl: 'http://localhost:3434', + video: false, }, }) @@ -37,6 +38,7 @@ context('cy.visit performance tests', function () { snapshot: true, expectedExitCode: 0, config: { + video: false, env: { currentRetry: this.test._currentRetry, }, From 9c64847e2b47ac6856a356e3e71f142a35cbd5df Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Tue, 8 Oct 2019 16:39:49 -0400 Subject: [PATCH 73/91] Add test for VIDEO_POST_PROCESSING_FAILED warning --- packages/server/lib/modes/run.js | 2 +- packages/server/test/unit/modes/run_spec.coffee | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/server/lib/modes/run.js b/packages/server/lib/modes/run.js index 02510e203449..26cfa7dbeda1 100644 --- a/packages/server/lib/modes/run.js +++ b/packages/server/lib/modes/run.js @@ -790,7 +790,7 @@ module.exports = { .catch((err) => { // log that post processing was attempted // but failed and dont let this change the run exit code - return errors.warning('VIDEO_POST_PROCESSING_FAILED', err.stack) + errors.warning('VIDEO_POST_PROCESSING_FAILED', err.stack) }) }, diff --git a/packages/server/test/unit/modes/run_spec.coffee b/packages/server/test/unit/modes/run_spec.coffee index 113ba7059fe1..80c07d55ba64 100644 --- a/packages/server/test/unit/modes/run_spec.coffee +++ b/packages/server/test/unit/modes/run_spec.coffee @@ -224,6 +224,15 @@ describe "lib/modes/run", -> .then -> expect(videoCapture.process).not.to.be.called + it "logs a warning on failure and resolves", -> + sinon.stub(errors, 'warning') + end = sinon.stub().rejects() + + runMode.postProcessRecording(end) + .then -> + expect(end).to.be.calledOnce + expect(errors.warning).to.be.calledWith('VIDEO_POST_PROCESSING_FAILED') + context ".waitForBrowserToConnect", -> it "throws TESTS_DID_NOT_START_FAILED after 3 connection attempts", -> sinon.spy(errors, "warning") From 9eb37c1e6bb552d2ca646bbe0cbca6f76678a763 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Wed, 9 Oct 2019 15:02:21 -0400 Subject: [PATCH 74/91] use client.on to register screencast callback --- packages/server/lib/browsers/chrome.coffee | 2 +- packages/server/lib/browsers/cri-client.ts | 35 +++++++++++++++++----- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/packages/server/lib/browsers/chrome.coffee b/packages/server/lib/browsers/chrome.coffee index 3bc62f7b61fa..61da2102489a 100644 --- a/packages/server/lib/browsers/chrome.coffee +++ b/packages/server/lib/browsers/chrome.coffee @@ -148,7 +148,7 @@ _maybeRecordVideo = (options) -> return client debug('starting screencast') - client.Page.screencastFrame(options.screencastFrame) + client.on('Page.screencastFrame', options.screencastFrame) client.send('Page.startScreencast', { format: 'jpeg' diff --git a/packages/server/lib/browsers/cri-client.ts b/packages/server/lib/browsers/cri-client.ts index c28eda601c30..d1f28ce8a909 100644 --- a/packages/server/lib/browsers/cri-client.ts +++ b/packages/server/lib/browsers/cri-client.ts @@ -9,6 +9,10 @@ const debugVerbose = debugModule('cy-verbose:server:browsers:cri-client') */ type websocketUrl = string +/** + * Enumerations to make programming CDP slightly simpler - provides + * IntelliSense whenever you use named types. + */ namespace CRI { export enum Command { 'Page.bringToFront', @@ -16,8 +20,8 @@ namespace CRI { 'Page.startScreencast' } - export interface Page { - screencastFrame(cb) + export enum EventNames { + 'Page.screencastFrame' } } @@ -37,12 +41,17 @@ interface CRIWrapper { * * @example client.Page.screencastFrame(cb) */ - Page: CRI.Page /** - * Calls underlying remote interface clien't close + * Registers callback for particular event. + * @see https://github.com/cyrus-and/chrome-remote-interface#class-cdp + */ + on (eventName: CRI.EventNames, cb: Function): void + + /** + * Calls underlying remote interface client close */ - close ():Promise + close ():Promise } /** @@ -58,8 +67,12 @@ export const initCriClient = async (debuggerUrl: websocketUrl): Promise => { + send (command: CRI.Command, params: object):Promise { debugVerbose('sending %s %o', command, params) return cri.send(command, params) @@ -73,8 +86,14 @@ export const initCriClient = async (debuggerUrl: websocketUrl): Promise { + + on (eventName: CRI.EventNames, cb: Function) { + debugVerbose('registering CDP event %s', eventName) + + return cri.on(eventName, cb) + }, + + close ():Promise { return cri.close() }, } From a8e6266f5f975d0bdb3356493490ac8c52bfa5bb Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 10 Oct 2019 14:14:59 -0400 Subject: [PATCH 75/91] cleanup prettier, extract some functions, switch to browser.family --- packages/server/lib/modes/run.js | 270 ++++++++++++------------------- 1 file changed, 106 insertions(+), 164 deletions(-) diff --git a/packages/server/lib/modes/run.js b/packages/server/lib/modes/run.js index 26cfa7dbeda1..aeaacd103f4c 100644 --- a/packages/server/lib/modes/run.js +++ b/packages/server/lib/modes/run.js @@ -105,7 +105,11 @@ const formatNodeVersion = ({ resolvedNodeVersion, resolvedNodePath }) => { } const formatSpecSummary = (name, failures) => { - return [getSymbol(failures), color(name, 'reset')].join(' ') + return [ + getSymbol(failures), + color(name, 'reset'), + ] + .join(' ') } const formatRecordParams = function (runUrl, parallel, group) { @@ -133,7 +137,8 @@ const formatSpecs = function (specs) { gray('('), gray(names.join(', ')), gray(')'), - ].join('') + ] + .join('') } const displayRunStarting = function (options = {}) { @@ -159,7 +164,8 @@ const displayRunStarting = function (options = {}) { type: 'outsideBorder', }) - const data = _.chain([ + const data = _ + .chain([ [gray('Cypress:'), pkg.version], [gray('Browser:'), formatBrowser(browser)], [gray('Node Version:'), formatNodeVersion(config)], @@ -193,7 +199,10 @@ const displaySpecHeader = function (name, curr, total, estimated) { }) table.push(['', '']) - table.push([`Running: ${gray(`${name}...`)}`, gray(`(${curr} of ${total})`)]) + table.push([ + `Running: ${gray(`${name}...`)}`, + gray(`(${curr} of ${total})`), + ]) console.log(table.toString()) @@ -234,24 +243,8 @@ const renderSummaryTable = (runUrl) => { }) if (runs && runs.length) { - const head = [ - ' Spec', - '', - 'Tests', - 'Passing', - 'Failing', - 'Pending', - 'Skipped', - ] - const colAligns = [ - 'left', - 'right', - 'right', - 'right', - 'right', - 'right', - 'right', - ] + const head = [' Spec', '', 'Tests', 'Passing', 'Failing', 'Pending', 'Skipped'] + const colAligns = ['left', 'right', 'right', 'right', 'right', 'right', 'right'] const colWidths = [39, 11, 10, 10, 10, 10, 10] const table1 = terminal.table({ @@ -321,21 +314,15 @@ const renderSummaryTable = (runUrl) => { } const iterateThroughSpecs = function (options = {}) { - const { - specs, - runEachSpec, - parallel, - beforeSpecRun, - afterSpecRun, - config, - } = options + const { specs, runEachSpec, parallel, beforeSpecRun, afterSpecRun, config } = options const serial = () => { return Promise.mapSeries(specs, runEachSpec) } const serialWithRecord = () => { - return Promise.mapSeries(specs, (spec, index, length) => { + return Promise + .mapSeries(specs, (spec, index, length) => { return beforeSpecRun(spec) .then(({ estimated }) => { return runEachSpec(spec, index, length, estimated) @@ -347,36 +334,35 @@ const iterateThroughSpecs = function (options = {}) { } const parallelWithRecord = (runs) => { - return beforeSpecRun().then( - ({ spec, claimedInstances, totalInstances, estimated }) => { - // no more specs to run? - if (!spec) { - // then we're done! - return runs - } + return beforeSpecRun() + .then(({ spec, claimedInstances, totalInstances, estimated }) => { + // no more specs to run? + if (!spec) { + // then we're done! + return runs + } - // find the actual spec object amongst - // our specs array since the API sends us - // the relative name - spec = _.find(specs, { relative: spec }) + // find the actual spec object amongst + // our specs array since the API sends us + // the relative name + spec = _.find(specs, { relative: spec }) - return runEachSpec( - spec, - claimedInstances - 1, - totalInstances, - estimated - ) - .tap((results) => { - runs.push(results) + return runEachSpec( + spec, + claimedInstances - 1, + totalInstances, + estimated + ) + .tap((results) => { + runs.push(results) - return afterSpecRun(spec, results, config) - }) - .then(() => { - // recurse - return parallelWithRecord(runs) - }) - } - ) + return afterSpecRun(spec, results, config) + }) + .then(() => { + // recurse + return parallelWithRecord(runs) + }) + }) } if (parallel) { @@ -405,20 +391,29 @@ const getProjectId = Promise.method((project, id) => { return id } - return project.getProjectId().catch(() => { + return project.getProjectId() + .catch(() => { // no id no problem return null }) }) -const reduceRuns = (runs, prop) => { - return _.reduce( - runs, - (memo, run) => { - return (memo += _.get(run, prop)) - }, - 0 - ) +const getDefaultBrowserOptsByFamily = (browser, project, writeVideoFrame) => { + la(browsers.isBrowserFamily(browser.family), 'invalid browser family in', browser) + + if (browser.family === 'electron') { + return this.getElectronProps(browser.isHeaded, project, writeVideoFrame) + } + + if (browser.family === 'chrome') { + return this.getChromeProps(browser.isHeaded, project, writeVideoFrame) + } + + return {} +} + +const sumByProp = (runs, prop) => { + return _.sumBy(runs, prop) || 0 } const getRun = (run, prop) => { @@ -481,10 +476,9 @@ const createAndOpenProject = function (socketId, options) { .then(() => { // open this project without // adding it to the global cache - return openProjectCreate(projectRoot, socketId, options).call( - 'getProject' - ) + return openProjectCreate(projectRoot, socketId, options) }) + .call('getProject') .then((project) => { return Promise.props({ project, @@ -495,33 +489,35 @@ const createAndOpenProject = function (socketId, options) { } const removeOldProfiles = () => { - return browsers.removeOldProfiles().catch((err) => { + return browsers.removeOldProfiles() + .catch((err) => { // dont make removing old browsers profiles break the build return errors.warning('CANNOT_REMOVE_OLD_BROWSER_PROFILES', err.stack) }) } -const trashAssets = function (config = {}) { +const trashAssets = Promise.method((config = {}) => { if (config.trashAssetsBeforeRuns !== true) { - return Promise.resolve() + return } return Promise.join( trash.folder(config.videosFolder), trash.folder(config.screenshotsFolder) - ).catch((err) => { + ) + .catch((err) => { // dont make trashing assets fail the build return errors.warning('CANNOT_TRASH_ASSETS', err.stack) }) -} +}) // if we've been told to record and we're not spawning a headed browser const browserCanBeRecorded = (browser) => { - if (browser.name === 'electron' && browser.isHeadless) { + if (browser.family === 'electron' && browser.isHeadless) { return true } - if (browser.name === 'chrome' && browser.isHeaded) { + if (browser.family === 'chrome' && browser.isHeaded) { return true } @@ -531,8 +527,11 @@ const browserCanBeRecorded = (browser) => { const createVideoRecording = function (videoName) { const outputDir = path.dirname(videoName) - return fs.ensureDirAsync(outputDir).then(() => { - return videoCapture.start(videoName, { + return fs + .ensureDirAsync(outputDir) + .then(() => { + return videoCapture + .start(videoName, { onError (err) { // catch video recording failures and log them out // but don't let this affect the run at all @@ -565,7 +564,7 @@ const maybeStartVideoRecording = Promise.method(function (options = {}) { console.log('') // TODO update error messages and included browser name and headed mode - if (browser.name === 'electron' && browser.isHeaded) { + if (browser.family === 'electron' && browser.isHeaded) { errors.warning('CANNOT_RECORD_VIDEO_HEADED') } else { errors.warning('CANNOT_RECORD_VIDEO_FOR_THIS_BROWSER', browser.name) @@ -586,7 +585,8 @@ const maybeStartVideoRecording = Promise.method(function (options = {}) { const videoName = videoPath('.mp4') const compressedVideoName = videoPath('-compressed.mp4') - return this.createVideoRecording(videoName).then((props = {}) => { + return this.createVideoRecording(videoName) + .then((props = {}) => { return { videoName, compressedVideoName, @@ -723,11 +723,7 @@ module.exports = { }, postProcessRecording (end, name, cname, videoCompression, shouldUploadVideo) { - debug('ending the video recording %o', { - name, - videoCompression, - shouldUploadVideo, - }) + debug('ending the video recording %o', { name, videoCompression, shouldUploadVideo }) // once this ended promises resolves // then begin processing the file @@ -776,10 +772,7 @@ module.exports = { progress += throttle const percentage = `${Math.ceil(float * 100)}%` - return console.log( - ' - Compression progress: ', - chalk.cyan(percentage) - ) + return console.log(' - Compression progress: ', chalk.cyan(percentage)) } } @@ -795,30 +788,9 @@ module.exports = { }, launchBrowser (options = {}) { - const { - browser, - spec, - writeVideoFrame, - project, - screenshots, - projectRoot, - } = options + const { browser, spec, writeVideoFrame, project, screenshots, projectRoot } = options - const browserOpts = (() => { - la(browsers.isBrowserFamily(browser.family), 'invalid browser family in', browser) - - if (browser.family === 'electron') { - return this.getElectronProps(browser.isHeaded, project, writeVideoFrame) - } - - if (browser.family === 'chrome') { - return this.getChromeProps(browser.isHeaded, project, writeVideoFrame) - } - - debug('warning - cannot determine browser props for %o', browser) - - return {} - })() + const browserOpts = getDefaultBrowserOptsByFamily(browser, project, writeVideoFrame) browserOpts.automationMiddleware = { onAfterResponse: (message, data, resp) => { @@ -940,19 +912,7 @@ module.exports = { }, waitForTestsToFinishRunning (options = {}) { - const { - project, - screenshots, - startedVideoCapture, - endVideoCapture, - videoName, - compressedVideoName, - videoCompression, - videoUploadOnPasses, - exit, - spec, - estimated, - } = options + const { project, screenshots, startedVideoCapture, endVideoCapture, videoName, compressedVideoName, videoCompression, videoUploadOnPasses, exit, spec, estimated } = options // https://github.com/cypress-io/cypress/issues/2370 // delay 1 second if we're recording a video to give @@ -1004,10 +964,7 @@ module.exports = { // we should upload the video if we upload on passes (by default) // or if we have any failures and have started the video - const suv = Boolean( - videoUploadOnPasses === true || - (startedVideoCapture && hasFailingTests) - ) + const suv = Boolean(videoUploadOnPasses === true || (startedVideoCapture && hasFailingTests)) obj.shouldUploadVideo = suv @@ -1016,7 +973,9 @@ module.exports = { // always close the browser now as opposed to letting // it exit naturally with the parent process due to // electron bug in windows - return openProject.closeBrowser().then(() => { + return openProject + .closeBrowser() + .then(() => { if (endVideoCapture) { return this.postProcessRecording( endVideoCapture, @@ -1046,22 +1005,9 @@ module.exports = { }, runSpecs (options = {}) { - const { - config, - browser, - sys, - headed, - outputPath, - specs, - specPattern, - beforeSpecRun, - afterSpecRun, - runUrl, - parallel, - group, - } = options + const { config, browser, sys, headed, outputPath, specs, specPattern, beforeSpecRun, afterSpecRun, runUrl, parallel, group } = options - const isHeadless = browser.name === 'electron' && !headed + const isHeadless = browser.family === 'electron' && !headed browser.isHeadless = isHeadless browser.isHeaded = !isHeadless @@ -1118,13 +1064,13 @@ module.exports = { .then((runs = []) => { results.startedTestsAt = getRun(_.first(runs), 'stats.wallClockStartedAt') results.endedTestsAt = getRun(_.last(runs), 'stats.wallClockEndedAt') - results.totalDuration = reduceRuns(runs, 'stats.wallClockDuration') - results.totalSuites = reduceRuns(runs, 'stats.suites') - results.totalTests = reduceRuns(runs, 'stats.tests') - results.totalPassed = reduceRuns(runs, 'stats.passes') - results.totalPending = reduceRuns(runs, 'stats.pending') - results.totalFailed = reduceRuns(runs, 'stats.failures') - results.totalSkipped = reduceRuns(runs, 'stats.skipped') + results.totalDuration = sumByProp(runs, 'stats.wallClockDuration') + results.totalSuites = sumByProp(runs, 'stats.suites') + results.totalTests = sumByProp(runs, 'stats.tests') + results.totalPassed = sumByProp(runs, 'stats.passes') + results.totalPending = sumByProp(runs, 'stats.pending') + results.totalFailed = sumByProp(runs, 'stats.failures') + results.totalSkipped = sumByProp(runs, 'stats.skipped') results.runs = runs debug('final results of all runs: %o', results) @@ -1189,7 +1135,9 @@ module.exports = { }, findSpecs (config, specPattern) { - return specsUtil.find(config, specPattern).tap((specs = []) => { + return specsUtil + .find(config, specPattern) + .tap((specs = []) => { if (debug.enabled) { const names = _.map(specs, 'name') @@ -1249,18 +1197,11 @@ module.exports = { .spread((sys = {}, browser = {}, specs = []) => { // return only what is return to the specPattern if (specPattern) { - specPattern = specsUtil.getPatternRelativeToProjectRoot( - specPattern, - projectRoot - ) + specPattern = specsUtil.getPatternRelativeToProjectRoot(specPattern, projectRoot) } if (!specs.length) { - errors.throw( - 'NO_SPECS_FOUND', - config.integrationFolder, - specPattern - ) + errors.throw('NO_SPECS_FOUND', config.integrationFolder, specPattern) } if (browser.family === 'chrome') { @@ -1321,7 +1262,8 @@ module.exports = { }, run (options) { - return electronApp.ready() + return electronApp + .ready() .then(() => { return this.ready(options) }) From 92c50d81ed94714a1bbc2e6b297096ad8c214c93 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 10 Oct 2019 14:51:56 -0400 Subject: [PATCH 76/91] moar cleanup and fixes --- packages/server/lib/browsers/chrome.coffee | 58 ++++++------ packages/server/lib/modes/run.js | 103 +++++++++++---------- 2 files changed, 84 insertions(+), 77 deletions(-) diff --git a/packages/server/lib/browsers/chrome.coffee b/packages/server/lib/browsers/chrome.coffee index 61da2102489a..5033ebfcc2a1 100644 --- a/packages/server/lib/browsers/chrome.coffee +++ b/packages/server/lib/browsers/chrome.coffee @@ -13,7 +13,7 @@ fs = require("../util/fs") appData = require("../util/app_data") utils = require("./utils") protocol = require("./protocol") -{initCriClient} = require("./cri-client") +CriClient = require("./cri-client") LOAD_EXTENSION = "--load-extension=" CHROME_VERSIONS_WITH_BUGGY_ROOT_LAYER_SCROLLING = "66 67".split(" ") @@ -129,8 +129,8 @@ _disableRestorePagesPrompt = (userDir) -> fs.writeJson(prefsPath, preferences) .catch -> -# After the browser has been opened, we can connect to -# its remote interface via a websocket. +## After the browser has been opened, we can connect to +## its remote interface via a websocket. _connectToChromeRemoteInterface = (port) -> la(check.userPort(port), "expected port number to connect CRI to", port) @@ -139,10 +139,11 @@ _connectToChromeRemoteInterface = (port) -> protocol.getWsTargetFor(port) .then (wsUrl) -> debug("received wsUrl %s for port %d", wsUrl, port) - initCriClient(wsUrl) + + CriClient.create(wsUrl) _maybeRecordVideo = (options) -> - (client) -> + return (client) -> if not options.screencastFrame debug("screencastFrame is false") return client @@ -156,27 +157,28 @@ _maybeRecordVideo = (options) -> .then -> return client -# a utility function that navigates to the given URL -# once Chrome remote interface client is passed to it. +## a utility function that navigates to the given URL +## once Chrome remote interface client is passed to it. _navigateUsingCRI = (url) -> la(check.url(url), "missing url to navigate to", url) - (client) -> + return (client) -> la(client, "could not get CRI client") debug("received CRI client") debug('navigating to page %s', url) - # when opening the blank page and trying to navigate - # the focus gets lost. Restore it and then navigate. + + ## when opening the blank page and trying to navigate + ## the focus gets lost. Restore it and then navigate. client.send("Page.bringToFront") .then -> client.send("Page.navigate", { url }) module.exports = { - # - # tip: - # by adding utility functions that start with "_" - # as methods here we can easily stub them from our unit tests - # + ## + ## tip: + ## by adding utility functions that start with "_" + ## as methods here we can easily stub them from our unit tests + ## _normalizeArgExtensions @@ -276,23 +278,22 @@ module.exports = { ## by being the last one args.push("--user-data-dir=#{userDir}") args.push("--disk-cache-dir=#{cacheDir}") - debug("get random port %d for remote debugging", port) args.push("--remote-debugging-port=#{port}") + + debug("launching in chrome with debugging port", { url, args, port }) - debug("launch in chrome: %s, %s", url, args) - - # FIRST load the blank page - # first allows us to connect the remote interface, - # start video recording and then - # we will load the actual page + ## FIRST load the blank page + ## first allows us to connect the remote interface, + ## start video recording and then + ## we will load the actual page utils.launch(browser, "about:blank", args) .then (launchedBrowser) => la(launchedBrowser, "did not get launched browser instance") - # SECOND connect to the Chrome remote interface - # and when the connection is ready - # navigate to the actual url + ## SECOND connect to the Chrome remote interface + ## and when the connection is ready + ## navigate to the actual url @_connectToChromeRemoteInterface(port) .then (criClient) => la(criClient, "expected Chrome remote interface reference", criClient) @@ -305,8 +306,7 @@ module.exports = { return criClient .then @_maybeRecordVideo(options) .then @_navigateUsingCRI(url) - .then -> - # return the launched browser process - # with additional method to close the remote connection - return launchedBrowser + ## return the launched browser process + ## with additional method to close the remote connection + .return(launchedBrowser) } diff --git a/packages/server/lib/modes/run.js b/packages/server/lib/modes/run.js index aeaacd103f4c..e6257b14d02e 100644 --- a/packages/server/lib/modes/run.js +++ b/packages/server/lib/modes/run.js @@ -402,16 +402,66 @@ const getDefaultBrowserOptsByFamily = (browser, project, writeVideoFrame) => { la(browsers.isBrowserFamily(browser.family), 'invalid browser family in', browser) if (browser.family === 'electron') { - return this.getElectronProps(browser.isHeaded, project, writeVideoFrame) + return getElectronProps(browser.isHeaded, project, writeVideoFrame) } if (browser.family === 'chrome') { - return this.getChromeProps(browser.isHeaded, project, writeVideoFrame) + return getChromeProps(browser.isHeaded, project, writeVideoFrame) } return {} } +const getChromeProps = (isHeaded, project, writeVideoFrame) => { + const shouldWriteVideo = Boolean(writeVideoFrame) + + debug('setting Chrome properties %o', { isHeaded, shouldWriteVideo }) + + return _ + .chain({}) + .tap((props) => { + if (isHeaded && writeVideoFrame) { + props.screencastFrame = (e) => { + // https://chromedevtools.github.io/devtools-protocol/tot/Page#event-screencastFrame + writeVideoFrame(new Buffer(e.data, 'base64')) + } + } + }) + .value() +} + +const getElectronProps = (isHeaded, project, writeVideoFrame) => { + return _ + .chain({ + width: 1280, + height: 720, + show: isHeaded, + onCrashed () { + const err = errors.get('RENDERER_CRASHED') + + errors.log(err) + + return project.emit('exitEarlyWithErr', err.message) + }, + onNewWindow (e, url, frameName, disposition, options) { + // force new windows to automatically open with show: false + // this prevents window.open inside of javascript client code + // to cause a new BrowserWindow instance to open + // https://github.com/cypress-io/cypress/issues/123 + options.show = false + }, + }) + .tap((props) => { + if (writeVideoFrame) { + props.recordFrameRate = 20 + props.onPaint = (event, dirty, image) => { + return writeVideoFrame(image.toJPEG(100)) + } + } + }) + .value() +} + const sumByProp = (runs, prop) => { return _.sumBy(runs, prop) || 0 } @@ -426,7 +476,7 @@ const writeOutput = (outputPath, results) => { return } - debug('saving output results as %s', outputPath) + debug('saving output results %o', { outputPath }) return fs.outputJsonAsync(outputPath, results) }) @@ -612,52 +662,9 @@ module.exports = { maybeStartVideoRecording, - getChromeProps (isHeaded, project, writeVideoFrame) { - debug('setting Chrome properties, is headed? %s, should write video? %s', - isHeaded, Boolean(writeVideoFrame)) - - const chromeProps = {} - - if (isHeaded && writeVideoFrame) { - chromeProps.screencastFrame = (e) => { - // https://chromedevtools.github.io/devtools-protocol/tot/Page#event-screencastFrame - writeVideoFrame(new Buffer(e.data, 'base64')) - } - } - - return chromeProps - }, - - getElectronProps (isHeaded, project, writeVideoFrame) { - const electronProps = { - width: 1280, - height: 720, - show: isHeaded, - onCrashed () { - const err = errors.get('RENDERER_CRASHED') + getChromeProps, - errors.log(err) - - return project.emit('exitEarlyWithErr', err.message) - }, - onNewWindow (e, url, frameName, disposition, options) { - // force new windows to automatically open with show: false - // this prevents window.open inside of javascript client code - // to cause a new BrowserWindow instance to open - // https://github.com/cypress-io/cypress/issues/123 - options.show = false - }, - } - - if (writeVideoFrame) { - electronProps.recordFrameRate = 20 - electronProps.onPaint = (event, dirty, image) => { - return writeVideoFrame(image.toJPEG(100)) - } - } - - return electronProps - }, + getElectronProps, displayResults (obj = {}, estimated) { const results = collectTestResults(obj, estimated) From 0632635915e86ef6a8611dd31c9df3063fb6cdb2 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 10 Oct 2019 15:44:17 -0400 Subject: [PATCH 77/91] add logging to the cri-client so we can see every message sent + received to the cdp protocol --- packages/server/lib/browsers/cri-client.ts | 64 ++++++++++++++++------ packages/server/lib/browsers/protocol.js | 13 ++--- 2 files changed, 52 insertions(+), 25 deletions(-) diff --git a/packages/server/lib/browsers/cri-client.ts b/packages/server/lib/browsers/cri-client.ts index d1f28ce8a909..a9a2635970df 100644 --- a/packages/server/lib/browsers/cri-client.ts +++ b/packages/server/lib/browsers/cri-client.ts @@ -1,8 +1,10 @@ import debugModule from 'debug' +import _ from 'lodash' const chromeRemoteInterface = require('chrome-remote-interface') - -const debugVerbose = debugModule('cy-verbose:server:browsers:cri-client') +const debugVerbose = debugModule('cypress-verbose:server:browsers:cri-client') +const debugVerboseSend = debugModule('cypress-verbose:server:browsers:cri-client:[-->]') +const debugVerboseReceive = debugModule('cypress-verbose:server:browsers:cri-client:[<--]') /** * Url returned by the Chrome Remote Interface @@ -54,41 +56,71 @@ interface CRIWrapper { close ():Promise } +const maybeDebugCdpMessages = (cri) => { + if (debugVerboseReceive.enabled) { + cri._ws.on('message', (data) => { + data = _ + .chain(JSON.parse(data)) + .tap((data) => { + const str = _.get(data, 'params.data') + + if (!_.isString(str)) { + return + } + + data.params.data = _.truncate(str, { + length: 100, + omission: `... [truncated string of total bytes: ${str.length}]`, + }) + + return data + }) + .value() + + debugVerboseReceive('received CDP message %o', data) + }) + + } + + if (debugVerboseSend.enabled) { + const send = cri._ws.send + + cri._ws.send = (data, callback) => { + debugVerboseSend('sending CDP command %o', JSON.parse(data)) + + return send.call(cri._ws, data, callback) + } + } +} + /** * Creates a wrapper for Chrome remote interface client * that only allows to use low-level "send" method * and not via domain objects and commands. * - * @example initCriClient('ws://localhost:...').send('Page.bringToFront') + * @example create('ws://localhost:...').send('Page.bringToFront') */ -export const initCriClient = async (debuggerUrl: websocketUrl): Promise => { +export { chromeRemoteInterface } + +export const create = async (debuggerUrl: websocketUrl): Promise => { const cri = await chromeRemoteInterface({ target: debuggerUrl, local: true, }) + maybeDebugCdpMessages(cri) + /** * Wrapper around Chrome remote interface client * that logs every command sent. */ const client: CRIWrapper = { send (command: CRI.Command, params: object):Promise { - debugVerbose('sending %s %o', command, params) - return cri.send(command, params) - .then((result) => { - debugVerbose('received response for %s: %o', command, { result }) - - return result - }) - .catch((err) => { - debugVerbose('received error for %s: %o', command, { err }) - throw err - }) }, on (eventName: CRI.EventNames, cb: Function) { - debugVerbose('registering CDP event %s', eventName) + debugVerbose('registering CDP on event %o', { eventName }) return cri.on(eventName, cb) }, diff --git a/packages/server/lib/browsers/protocol.js b/packages/server/lib/browsers/protocol.js index 159fe6bb01d9..6f19e83520b5 100644 --- a/packages/server/lib/browsers/protocol.js +++ b/packages/server/lib/browsers/protocol.js @@ -4,7 +4,6 @@ const { connect } = require('@packages/network') const Promise = require('bluebird') const la = require('lazy-ass') const is = require('check-more-types') -const pluralize = require('pluralize') const debug = require('debug')('cypress:server:protocol') function _getDelayMsForRetry (i) { @@ -35,9 +34,8 @@ const getWsTargetFor = (port) => { la(is.port(port), 'expected port number', port) return connectAsync({ port }) - .catch((err) => { + .tapCatch((err) => { debug('failed to connect to CDP %o', { port, err }) - throw err }) .then(() => { debug('CRI.List on port %d', port) @@ -47,13 +45,8 @@ const getWsTargetFor = (port) => { return CRI.List({ port }) }) .then((targets) => { - debug( - 'CRI list has %s %o', - pluralize('targets', targets.length, true), - targets - ) + debug('CRI List %o', { numTargets: targets.length, targets }) // activate the first available id - // find the first target page that's a real tab // and not the dev tools or background page. // since we open a blank page first, it has a special url @@ -61,9 +54,11 @@ const getWsTargetFor = (port) => { type: 'page', url: 'about:blank', } + const target = _.find(targets, newTabTargetFields) la(target, 'could not find CRI target') + debug('found CRI target %o', target) return target.webSocketDebuggerUrl From 0e0013bb04d5d7822d224313bd96b17617a9fe46 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 10 Oct 2019 16:05:38 -0400 Subject: [PATCH 78/91] bump bluebird to 3.7.0 for .tapCatch addition --- packages/server/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/package.json b/packages/server/package.json index c2cc5a9eb268..c9cfc74b6084 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -46,7 +46,7 @@ "@cypress/icons": "0.7.0", "@ffmpeg-installer/ffmpeg": "1.0.19", "ansi_up": "1.3.0", - "bluebird": "3.4.7", + "bluebird": "3.7.0", "browserify": "16.3.0", "chai": "1.10.0", "chalk": "2.4.2", From e741d1d38646456d318560e5c3f362c904ad9fe7 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Thu, 10 Oct 2019 16:35:21 -0400 Subject: [PATCH 79/91] Fix unit tests --- packages/server/test/unit/modes/run_spec.coffee | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/packages/server/test/unit/modes/run_spec.coffee b/packages/server/test/unit/modes/run_spec.coffee index 80c07d55ba64..aebc3df774ad 100644 --- a/packages/server/test/unit/modes/run_spec.coffee +++ b/packages/server/test/unit/modes/run_spec.coffee @@ -133,7 +133,6 @@ describe "lib/modes/run", -> context ".launchBrowser", -> beforeEach -> @launch = sinon.stub(openProject, "launch") - sinon.stub(runMode, "getElectronProps").returns({foo: "bar"}) sinon.stub(runMode, "screenshotMetadata").returns({a: "a"}) it "can launch electron", -> @@ -157,9 +156,7 @@ describe "lib/modes/run", -> screenshots: screenshots }) - expect(runMode.getElectronProps).to.be.calledWith(false, @projectInstance, "write") - - expect(@launch).to.be.calledWithMatch(browser, spec, { foo: "bar" }) + expect(@launch).to.be.calledWithMatch(browser, spec) browserOpts = @launch.firstCall.args[2] @@ -188,8 +185,6 @@ describe "lib/modes/run", -> browser }) - expect(runMode.getElectronProps).not.to.be.called - expect(@launch).to.be.calledWithMatch(browser, spec, {}) context ".postProcessRecording", -> @@ -544,14 +539,13 @@ describe "lib/modes/run", -> .then -> expect(errors.warning).to.be.calledWith("CANNOT_RECORD_VIDEO_HEADED") - it "disables video recording for non-electron non-chrome browser", -> - browser = { name: "canary", family: "chrome" } + it "throws an error if invalid browser family supplied", -> + browser = { name: "opera", family: "opera - btw when is Opera support coming?" } sinon.stub(browsers, "ensureAndGetByNameOrPath").resolves(browser) - runMode.run({browser: "canary"}) - .then -> - expect(errors.warning).to.be.calledWith("CANNOT_RECORD_VIDEO_FOR_THIS_BROWSER") + expect(runMode.run({browser: "opera"})) + .to.be.rejectedWith(/invalid browser family in/) it "shows no warnings for chrome browser", -> runMode.run({browser: "chrome"}) From 0bdf678fdbccff406a290b9aedffb31bfc337618 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 10 Oct 2019 16:46:33 -0400 Subject: [PATCH 80/91] WIP: update e2e test to ensure that duration of the video matches what we expect --- packages/server/lib/video_capture.coffee | 29 +++++++++++++++++++ .../test/e2e/6_video_compression_spec.coffee | 15 ++++++++++ 2 files changed, 44 insertions(+) diff --git a/packages/server/lib/video_capture.coffee b/packages/server/lib/video_capture.coffee index fec0605ceb90..5f00206aac0d 100644 --- a/packages/server/lib/video_capture.coffee +++ b/packages/server/lib/video_capture.coffee @@ -1,6 +1,9 @@ _ = require("lodash") +os = require("os") +path = require("path") utils = require("fluent-ffmpeg/lib/utils") debug = require("debug")("cypress:server:video") + # extra verbose logs for logging individual frames debugFrames = require("debug")("cypress:server:video:frames") ffmpeg = require("fluent-ffmpeg") @@ -13,6 +16,32 @@ debug("using ffmpeg from %s", ffmpegPath) ffmpeg.setFfmpegPath(ffmpegPath) module.exports = { + getCodecData: (src) -> + # Promise.fromCallback (cb) -> + # ffmpeg(src) + # .ffprobe(cb) + + ## TODO: figure out if we can get codecData + ## without doing a mutation + output = path.join(os.tmpdir(), 'tmp-output.mp4') + + new Promise (resolve, reject) -> + ffmpeg() + .on 'codecData', (data) -> + debug('codecData %o', { + src, + data, + }) + + resolve(data) + .on "error", (err) -> + debug("getting codecData failed", { err }) + + reject(err) + .input(src) + .output(output) + .run() + copy: (src, dest) -> debug("copying from %s to %s", src, dest) fs diff --git a/packages/server/test/e2e/6_video_compression_spec.coffee b/packages/server/test/e2e/6_video_compression_spec.coffee index 8955fda34d44..704469bcb64b 100644 --- a/packages/server/test/e2e/6_video_compression_spec.coffee +++ b/packages/server/test/e2e/6_video_compression_spec.coffee @@ -1,4 +1,8 @@ +path = require("path") e2e = require("../support/helpers/e2e") +glob = require("../../lib/util/glob") +videoCapture = require("../../lib/video_capture") +Fixtures = require("../support/helpers/fixtures") describe "e2e video compression", -> e2e.setup() @@ -11,6 +15,17 @@ describe "e2e video compression", -> snapshot: false expectedExitCode: 0 }) + .tap -> + videosPath = Fixtures.projectPath("e2e/cypress/videos/*") + + glob(videosPath) + .then (files) -> + expect(files).to.have.length(1, "globbed for videos and found: #{files.length}. Expected to find 1 video. Search in videosPath: #{videosPath}.") + + videoCapture.getCodecData(files[0]) + .then (props) -> + console.log(props) + .get("stdout") .then (stdout) -> expect(stdout).to.match(/Compression progress:\s+\d{1,3}%/) From ed25ffa7708debc313ebf9433d4a6d81217f44fc Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Thu, 10 Oct 2019 17:49:32 -0400 Subject: [PATCH 81/91] Test duration of recorded video --- packages/server/lib/video_capture.coffee | 59 +++++++++++++------ packages/server/package.json | 1 + .../test/e2e/6_video_compression_spec.coffee | 16 ++++- .../integration/video_compression_spec.coffee | 4 +- 4 files changed, 57 insertions(+), 23 deletions(-) diff --git a/packages/server/lib/video_capture.coffee b/packages/server/lib/video_capture.coffee index 5f00206aac0d..eca624f8907f 100644 --- a/packages/server/lib/video_capture.coffee +++ b/packages/server/lib/video_capture.coffee @@ -1,4 +1,6 @@ _ = require("lodash") +BlackHoleStream = require("black-hole-stream") +la = require("lazy-ass") os = require("os") path = require("path") utils = require("fluent-ffmpeg/lib/utils") @@ -15,33 +17,54 @@ fs = require("./util/fs") debug("using ffmpeg from %s", ffmpegPath) ffmpeg.setFfmpegPath(ffmpegPath) +ffmpegLoggerObj = { + debug, + info: debug, + warn: debug, + error: debug +} + +durationRe = /(\d{2}):(\d{2}):(\d{2})\.(\d{2})/ + module.exports = { + getMsFromDuration: (durationString) -> + matches = durationRe.exec(durationString) + la(matches, 'durationString should be a string of the format: HH:MM:SS.MS') + [hrs, mins, secs, ms] = matches.slice(1).map(Number) + mins += hrs * 60 + secs += mins * 60 + ms += secs * 1000 + ms + getCodecData: (src) -> - # Promise.fromCallback (cb) -> - # ffmpeg(src) - # .ffprobe(cb) - - ## TODO: figure out if we can get codecData - ## without doing a mutation - output = path.join(os.tmpdir(), 'tmp-output.mp4') - new Promise (resolve, reject) -> - ffmpeg() + onError = (err) -> + debug("getting codecData failed", { err }) + cleanup() + reject(err) + + cleanup = => + ff?.removeListener("error", onError) + ff?.kill() + ff = null + + ff = ffmpeg(src) .on 'codecData', (data) -> debug('codecData %o', { src, - data, + data, }) - + cleanup() resolve(data) - .on "error", (err) -> - debug("getting codecData failed", { err }) - - reject(err) - .input(src) - .output(output) + .on "error", onError + ## an outputformat must be set, or ffmpeg will fail to read the input + ## this is not enough for ffmpeg to properly transcode, so it will + ## exit with an error - which is fine since we cleanup error listeners + ## on success anyways, the error will get ignored + .format('mp4') + .output(new BlackHoleStream) .run() - + copy: (src, dest) -> debug("copying from %s to %s", src, dest) fs diff --git a/packages/server/package.json b/packages/server/package.json index c9cfc74b6084..0d4de8b9d1aa 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -46,6 +46,7 @@ "@cypress/icons": "0.7.0", "@ffmpeg-installer/ffmpeg": "1.0.19", "ansi_up": "1.3.0", + "black-hole-stream": "0.0.1", "bluebird": "3.7.0", "browserify": "16.3.0", "chai": "1.10.0", diff --git a/packages/server/test/e2e/6_video_compression_spec.coffee b/packages/server/test/e2e/6_video_compression_spec.coffee index 704469bcb64b..805872a064b3 100644 --- a/packages/server/test/e2e/6_video_compression_spec.coffee +++ b/packages/server/test/e2e/6_video_compression_spec.coffee @@ -4,6 +4,10 @@ glob = require("../../lib/util/glob") videoCapture = require("../../lib/video_capture") Fixtures = require("../support/helpers/fixtures") +NUM_TESTS = 40 +MS_PER_TEST = 500 +EXPECTED_DURATION_MS = NUM_TESTS * MS_PER_TEST + describe "e2e video compression", -> e2e.setup() @@ -13,18 +17,24 @@ describe "e2e video compression", -> e2e.exec(@, { spec: "video_compression_spec.coffee" snapshot: false + config: { + env: { + NUM_TESTS: 40 + MS_PER_TEST: 500 + } + } expectedExitCode: 0 }) .tap -> videosPath = Fixtures.projectPath("e2e/cypress/videos/*") - glob(videosPath) .then (files) -> expect(files).to.have.length(1, "globbed for videos and found: #{files.length}. Expected to find 1 video. Search in videosPath: #{videosPath}.") videoCapture.getCodecData(files[0]) - .then (props) -> - console.log(props) + .then ({ duration }) -> + durationMs = videoCapture.getMsFromDuration(duration) + expect(durationMs).to.be.closeTo(EXPECTED_DURATION_MS, 10 * 1000) .get("stdout") .then (stdout) -> diff --git a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/video_compression_spec.coffee b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/video_compression_spec.coffee index ec36529dc8bb..37261de69448 100644 --- a/packages/server/test/support/fixtures/projects/e2e/cypress/integration/video_compression_spec.coffee +++ b/packages/server/test/support/fixtures/projects/e2e/cypress/integration/video_compression_spec.coffee @@ -1,3 +1,3 @@ -Cypress._.times 40, (i) -> +Cypress._.times Cypress.env('NUM_TESTS'), (i) -> it "num: #{i+1} makes some long tests", -> - cy.wait(500) + cy.wait(Cypress.env('MS_PER_TEST')) From 6fa7492dccaf9844b6902ea75cf759aa727430e5 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Thu, 10 Oct 2019 17:52:07 -0400 Subject: [PATCH 82/91] Run 6_video_compression in chrome + electron --- .../test/e2e/6_video_compression_spec.coffee | 52 ++++++++++--------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/packages/server/test/e2e/6_video_compression_spec.coffee b/packages/server/test/e2e/6_video_compression_spec.coffee index 805872a064b3..6055a6d46139 100644 --- a/packages/server/test/e2e/6_video_compression_spec.coffee +++ b/packages/server/test/e2e/6_video_compression_spec.coffee @@ -11,31 +11,35 @@ EXPECTED_DURATION_MS = NUM_TESTS * MS_PER_TEST describe "e2e video compression", -> e2e.setup() - it "passes", -> - process.env.VIDEO_COMPRESSION_THROTTLE = 10 + [ + 'chrome', + 'electron' + ].map (browser) -> + it "passes in #{browser}", -> + process.env.VIDEO_COMPRESSION_THROTTLE = 10 - e2e.exec(@, { - spec: "video_compression_spec.coffee" - snapshot: false - config: { - env: { - NUM_TESTS: 40 - MS_PER_TEST: 500 + e2e.exec(@, { + spec: "video_compression_spec.coffee" + snapshot: false + config: { + env: { + NUM_TESTS: 40 + MS_PER_TEST: 500 + } } - } - expectedExitCode: 0 - }) - .tap -> - videosPath = Fixtures.projectPath("e2e/cypress/videos/*") - glob(videosPath) - .then (files) -> - expect(files).to.have.length(1, "globbed for videos and found: #{files.length}. Expected to find 1 video. Search in videosPath: #{videosPath}.") + expectedExitCode: 0 + }) + .tap -> + videosPath = Fixtures.projectPath("e2e/cypress/videos/*") + glob(videosPath) + .then (files) -> + expect(files).to.have.length(1, "globbed for videos and found: #{files.length}. Expected to find 1 video. Search in videosPath: #{videosPath}.") - videoCapture.getCodecData(files[0]) - .then ({ duration }) -> - durationMs = videoCapture.getMsFromDuration(duration) - expect(durationMs).to.be.closeTo(EXPECTED_DURATION_MS, 10 * 1000) + videoCapture.getCodecData(files[0]) + .then ({ duration }) -> + durationMs = videoCapture.getMsFromDuration(duration) + expect(durationMs).to.be.closeTo(EXPECTED_DURATION_MS, 10 * 1000) - .get("stdout") - .then (stdout) -> - expect(stdout).to.match(/Compression progress:\s+\d{1,3}%/) + .get("stdout") + .then (stdout) -> + expect(stdout).to.match(/Compression progress:\s+\d{1,3}%/) From 324e7eb942ead8d25f00c2c6133b4b6384450f6c Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Thu, 10 Oct 2019 17:52:49 -0400 Subject: [PATCH 83/91] Cleanup --- packages/server/lib/video_capture.coffee | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/server/lib/video_capture.coffee b/packages/server/lib/video_capture.coffee index eca624f8907f..7674da6ff888 100644 --- a/packages/server/lib/video_capture.coffee +++ b/packages/server/lib/video_capture.coffee @@ -17,13 +17,6 @@ fs = require("./util/fs") debug("using ffmpeg from %s", ffmpegPath) ffmpeg.setFfmpegPath(ffmpegPath) -ffmpegLoggerObj = { - debug, - info: debug, - warn: debug, - error: debug -} - durationRe = /(\d{2}):(\d{2}):(\d{2})\.(\d{2})/ module.exports = { From 3783a4924b1933f5c5cd93801a259f954475cf50 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 10 Oct 2019 19:33:28 -0400 Subject: [PATCH 84/91] finish ffmpeg duration verification --- packages/server/lib/video_capture.coffee | 60 +++++++------------ .../test/e2e/6_video_compression_spec.coffee | 9 ++- 2 files changed, 27 insertions(+), 42 deletions(-) diff --git a/packages/server/lib/video_capture.coffee b/packages/server/lib/video_capture.coffee index 7674da6ff888..9773a51ee5e8 100644 --- a/packages/server/lib/video_capture.coffee +++ b/packages/server/lib/video_capture.coffee @@ -1,62 +1,44 @@ _ = require("lodash") -BlackHoleStream = require("black-hole-stream") la = require("lazy-ass") os = require("os") path = require("path") utils = require("fluent-ffmpeg/lib/utils") debug = require("debug")("cypress:server:video") - -# extra verbose logs for logging individual frames -debugFrames = require("debug")("cypress:server:video:frames") ffmpeg = require("fluent-ffmpeg") stream = require("stream") Promise = require("bluebird") ffmpegPath = require("@ffmpeg-installer/ffmpeg").path +BlackHoleStream = require("black-hole-stream") fs = require("./util/fs") +## extra verbose logs for logging individual frames +debugFrames = require("debug")("cypress:server:video:frames") + debug("using ffmpeg from %s", ffmpegPath) -ffmpeg.setFfmpegPath(ffmpegPath) -durationRe = /(\d{2}):(\d{2}):(\d{2})\.(\d{2})/ +ffmpeg.setFfmpegPath(ffmpegPath) module.exports = { - getMsFromDuration: (durationString) -> - matches = durationRe.exec(durationString) - la(matches, 'durationString should be a string of the format: HH:MM:SS.MS') - [hrs, mins, secs, ms] = matches.slice(1).map(Number) - mins += hrs * 60 - secs += mins * 60 - ms += secs * 1000 - ms + getMsFromDuration: (duration) -> + utils.timemarkToSeconds(duration) * 1000 getCodecData: (src) -> new Promise (resolve, reject) -> - onError = (err) -> - debug("getting codecData failed", { err }) - cleanup() - reject(err) - - cleanup = => - ff?.removeListener("error", onError) - ff?.kill() - ff = null - - ff = ffmpeg(src) - .on 'codecData', (data) -> - debug('codecData %o', { - src, - data, - }) - cleanup() - resolve(data) - .on "error", onError - ## an outputformat must be set, or ffmpeg will fail to read the input - ## this is not enough for ffmpeg to properly transcode, so it will - ## exit with an error - which is fine since we cleanup error listeners - ## on success anyways, the error will get ignored - .format('mp4') - .output(new BlackHoleStream) + ffmpeg() + .on "stderr", (stderr) -> + debug("get codecData stderr log %o", { message: stderr }) + .on("codecData", resolve) + .input(src) + .format("null") + .output(new BlackHoleStream()) .run() + .tap (data) -> + debug('codecData %o', { + src, + data, + }) + .tapCatch (err) -> + debug("getting codecData failed", { err }) copy: (src, dest) -> debug("copying from %s to %s", src, dest) diff --git a/packages/server/test/e2e/6_video_compression_spec.coffee b/packages/server/test/e2e/6_video_compression_spec.coffee index 6055a6d46139..55ae8d2e3243 100644 --- a/packages/server/test/e2e/6_video_compression_spec.coffee +++ b/packages/server/test/e2e/6_video_compression_spec.coffee @@ -1,4 +1,5 @@ path = require("path") +humanInterval = require("human-interval") e2e = require("../support/helpers/e2e") glob = require("../../lib/util/glob") videoCapture = require("../../lib/video_capture") @@ -23,14 +24,15 @@ describe "e2e video compression", -> snapshot: false config: { env: { - NUM_TESTS: 40 - MS_PER_TEST: 500 + NUM_TESTS + MS_PER_TEST } } expectedExitCode: 0 }) .tap -> videosPath = Fixtures.projectPath("e2e/cypress/videos/*") + glob(videosPath) .then (files) -> expect(files).to.have.length(1, "globbed for videos and found: #{files.length}. Expected to find 1 video. Search in videosPath: #{videosPath}.") @@ -38,7 +40,8 @@ describe "e2e video compression", -> videoCapture.getCodecData(files[0]) .then ({ duration }) -> durationMs = videoCapture.getMsFromDuration(duration) - expect(durationMs).to.be.closeTo(EXPECTED_DURATION_MS, 10 * 1000) + expect(durationMs).to.be.ok + expect(durationMs).to.be.closeTo(EXPECTED_DURATION_MS, humanInterval('10 seconds')) .get("stdout") .then (stdout) -> From 5df252dcad8619933915d08024f61cbcb61f4411 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Fri, 11 Oct 2019 11:14:19 -0400 Subject: [PATCH 85/91] Update 8_reporters_spec snapshot --- packages/server/__snapshots__/8_reporters_spec.coffee.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/server/__snapshots__/8_reporters_spec.coffee.js b/packages/server/__snapshots__/8_reporters_spec.coffee.js index 65b84dfe8b43..129f4bd9b4bd 100644 --- a/packages/server/__snapshots__/8_reporters_spec.coffee.js +++ b/packages/server/__snapshots__/8_reporters_spec.coffee.js @@ -698,6 +698,7 @@ Error: this reporter threw an error at stack trace line at stack trace line at stack trace line + at stack trace line Learn more at https://on.cypress.io/reporters From dcb2a488538299386f80d2299fffff2125316512 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Fri, 11 Oct 2019 12:19:28 -0400 Subject: [PATCH 86/91] Update CRI close logic to monkey-patch browser.kill --- packages/server/lib/browsers/chrome.coffee | 15 ++++++--- packages/server/lib/browsers/index.coffee | 33 +++++-------------- .../test/unit/browsers/chrome_spec.coffee | 17 ++++++---- 3 files changed, 30 insertions(+), 35 deletions(-) diff --git a/packages/server/lib/browsers/chrome.coffee b/packages/server/lib/browsers/chrome.coffee index 01b53cfd1e94..4c8cc54629ff 100644 --- a/packages/server/lib/browsers/chrome.coffee +++ b/packages/server/lib/browsers/chrome.coffee @@ -151,7 +151,7 @@ _connectToChromeRemoteInterface = (port) -> protocol.getWsTargetFor(port) .then (wsUrl) -> debug("received wsUrl %s for port %d", wsUrl, port) - + CriClient.create(wsUrl) _maybeRecordVideo = (options) -> @@ -291,7 +291,7 @@ module.exports = { args.push("--user-data-dir=#{userDir}") args.push("--disk-cache-dir=#{cacheDir}") args.push("--remote-debugging-port=#{port}") - + debug("launching in chrome with debugging port", { url, args, port }) ## FIRST load the blank page @@ -299,7 +299,6 @@ module.exports = { ## start video recording and then ## we will load the actual page utils.launch(browser, "about:blank", args) - .then (launchedBrowser) => la(launchedBrowser, "did not get launched browser instance") @@ -310,10 +309,16 @@ module.exports = { .then (criClient) => la(criClient, "expected Chrome remote interface reference", criClient) - debug("adding method to close the remote interface client") - launchedBrowser.close = () -> + ## monkey-patch the .kill method to that the CDP connection is closed + originalBrowserKill = launchedBrowser.kill + + launchedBrowser.kill = (args...) => debug("closing remote interface client") + criClient.close() + .then => + debug("closing chrome") + originalBrowserKill.call(launchedBrowser, args...) return criClient .then @_maybeRecordVideo(options) diff --git a/packages/server/lib/browsers/index.coffee b/packages/server/lib/browsers/index.coffee index 952b564db44e..9151a2039cca 100644 --- a/packages/server/lib/browsers/index.coffee +++ b/packages/server/lib/browsers/index.coffee @@ -5,11 +5,6 @@ debug = require("debug")("cypress:server:browsers") utils = require("./utils") errors = require("../errors") fs = require("../util/fs") -la = require("lazy-ass") -check = require("check-more-types") - -# returns true if the passed string is a known browser family name -isBrowserFamily = check.oneOf(["electron", "chrome"]) instance = null @@ -18,32 +13,24 @@ kill = (unbind) -> ## instance return Promise.resolve() if not instance - # if this browser needs to close its own connection, call the close - closeRemoteClient = if instance.close then instance.close() else Promise.resolve() - - closeRemoteClient.then -> - new Promise (resolve) -> - if unbind - debug("removing all listeners") - instance.removeAllListeners() + new Promise (resolve) -> + if unbind + instance.removeAllListeners() - instance.once "exit", (code, sigint) -> - debug("browser process killed") + instance.once "exit", (code, sigint) -> + debug("browser process killed") - resolve.apply(null, arguments) + resolve.apply(null, arguments) - debug("killing browser process") + debug("killing browser process") - instance.kill() - cleanup() + instance.kill() + cleanup() cleanup = -> instance = null getBrowserLauncherByFamily = (family) -> - if not isBrowserFamily(family) - debug("unknown browser family", family) - switch family when "electron" require("./electron") @@ -95,8 +82,6 @@ module.exports = { close: kill - isBrowserFamily - getAllBrowsersWith: (nameOrPath) -> if nameOrPath return ensureAndGetByNameOrPath(nameOrPath, true) diff --git a/packages/server/test/unit/browsers/chrome_spec.coffee b/packages/server/test/unit/browsers/chrome_spec.coffee index da89b274d7b2..5491611c19f9 100644 --- a/packages/server/test/unit/browsers/chrome_spec.coffee +++ b/packages/server/test/unit/browsers/chrome_spec.coffee @@ -21,7 +21,9 @@ describe "lib/browsers/chrome", -> close: sinon.stub().resolves() } # mock launched browser child process object - @launchedBrowser = {} + @launchedBrowser = { + kill: sinon.stub().returns() + } sinon.stub(chrome, "_getArgs").returns(@args) sinon.stub(chrome, "_writeExtension").resolves("/path/to/ext") @@ -123,13 +125,17 @@ describe "lib/browsers/chrome", -> } }) - it "calls cri client close", -> + it "calls cri client close on kill", -> + ## need a reference here since the stub will be monkey-patched + kill = @launchedBrowser.kill + chrome.open("chrome", "http://", {}, {}) .then => - expect(@launchedBrowser.close, "Chrome browser process gets close method").to.be.a("function") - @launchedBrowser.close() + expect(@launchedBrowser.kill).to.be.a("function") + @launchedBrowser.kill() .then => - expect(@criClient.close).to.have.been.calledOnce + expect(@criClient.close).to.be.calledOnce + expect(kill).to.be.calledOnce context "#_getArgs", -> it "disables gpu when linux", -> @@ -211,4 +217,3 @@ describe "lib/browsers/chrome", -> chromeVersionHasLoopback("71", false) chromeVersionHasLoopback("72", true) chromeVersionHasLoopback("73", true) - From 4d04eb89a40215ba94e9f28215ca30c0ec6e4816 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Fri, 11 Oct 2019 12:21:29 -0400 Subject: [PATCH 87/91] add isBrowserFamily back --- packages/server/lib/browsers/index.coffee | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/server/lib/browsers/index.coffee b/packages/server/lib/browsers/index.coffee index 9151a2039cca..9c6dead6b81c 100644 --- a/packages/server/lib/browsers/index.coffee +++ b/packages/server/lib/browsers/index.coffee @@ -5,6 +5,11 @@ debug = require("debug")("cypress:server:browsers") utils = require("./utils") errors = require("../errors") fs = require("../util/fs") +la = require("lazy-ass") +check = require("check-more-types") + +# returns true if the passed string is a known browser family name +isBrowserFamily = check.oneOf(["electron", "chrome"]) instance = null @@ -31,6 +36,9 @@ cleanup = -> instance = null getBrowserLauncherByFamily = (family) -> + if not isBrowserFamily(family) + debug("unknown browser family", family) + switch family when "electron" require("./electron") @@ -74,6 +82,8 @@ process.once "exit", kill module.exports = { ensureAndGetByNameOrPath + isBrowserFamily + removeOldProfiles: utils.removeOldProfiles get: utils.getBrowsers From 0631585123835161098e2c04604216c638eacbfd Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Fri, 11 Oct 2019 16:30:56 -0400 Subject: [PATCH 88/91] make it possible for remote-debugging-port to get overridden --- packages/server/lib/browsers/chrome.coffee | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/server/lib/browsers/chrome.coffee b/packages/server/lib/browsers/chrome.coffee index 4c8cc54629ff..034435006531 100644 --- a/packages/server/lib/browsers/chrome.coffee +++ b/packages/server/lib/browsers/chrome.coffee @@ -261,15 +261,17 @@ module.exports = { .try => args = @_getArgs(options) - Promise.all([ - ## ensure that we have a clean cache dir - ## before launching the browser every time - utils.ensureCleanCache(browser, isTextTerminal), - - pluginsBeforeBrowserLaunch(options.browser, args), + utils.getPort() + .then (port) -> + args.push("--remote-debugging-port=#{port}") - utils.getPort() - ]) + Promise.all([ + ## ensure that we have a clean cache dir + ## before launching the browser every time + utils.ensureCleanCache(browser, isTextTerminal), + pluginsBeforeBrowserLaunch(options.browser, args), + port + ]) .spread (cacheDir, args, port) => Promise.all([ @_writeExtension( @@ -290,7 +292,6 @@ module.exports = { ## by being the last one args.push("--user-data-dir=#{userDir}") args.push("--disk-cache-dir=#{cacheDir}") - args.push("--remote-debugging-port=#{port}") debug("launching in chrome with debugging port", { url, args, port }) From bda0e4f6038c26d11135c2106993253b022d4245 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Fri, 11 Oct 2019 16:37:02 -0400 Subject: [PATCH 89/91] Make CDP timeout 5s; add unit, e2e tests for CDP failure; add user-friendly CDP failure error --- .../server/__snapshots__/2_cdp_spec.ts.js | 30 +++++++++++++++++++ .../server/__snapshots__/protocol_spec.ts.js | 10 ++++++- packages/server/lib/browsers/protocol.js | 8 +++-- packages/server/lib/errors.coffee | 12 ++++++++ packages/server/package.json | 1 + packages/server/test/e2e/2_cdp_spec.ts | 21 +++++++++++++ .../cypress.json | 1 + .../cypress/integration/spec.ts | 3 ++ .../cypress/plugins.js | 14 +++++++++ .../test/unit/browsers/protocol_spec.ts | 25 ++++++++++++++-- 10 files changed, 120 insertions(+), 5 deletions(-) create mode 100644 packages/server/__snapshots__/2_cdp_spec.ts.js create mode 100644 packages/server/test/e2e/2_cdp_spec.ts create mode 100644 packages/server/test/support/fixtures/projects/remote-debugging-port-removed/cypress.json create mode 100644 packages/server/test/support/fixtures/projects/remote-debugging-port-removed/cypress/integration/spec.ts create mode 100644 packages/server/test/support/fixtures/projects/remote-debugging-port-removed/cypress/plugins.js diff --git a/packages/server/__snapshots__/2_cdp_spec.ts.js b/packages/server/__snapshots__/2_cdp_spec.ts.js new file mode 100644 index 000000000000..a7dad81df24c --- /dev/null +++ b/packages/server/__snapshots__/2_cdp_spec.ts.js @@ -0,0 +1,30 @@ +exports['e2e cdp fails when remote debugging port cannot be connected to 1'] = ` + +==================================================================================================== + + (Run Starting) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Cypress: 1.2.3 │ + │ Browser: FooBrowser 88 │ + │ Specs: 1 found (spec.ts) │ + │ Searched: cypress/integration/spec.ts │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: spec.ts... (1 of 1) +Cypress could not make a connection to the Chrome DevTools Protocol after 5 seconds. + +Without this, your tests can not run. Please check your Chrome installation and try again. + +The CDP port requested was 777. + +Error details: + +Error: connect ECONNREFUSED 127.0.0.1:777 + at stack trace line + + +` diff --git a/packages/server/__snapshots__/protocol_spec.ts.js b/packages/server/__snapshots__/protocol_spec.ts.js index e1333e6bef07..c970d204ca9f 100644 --- a/packages/server/__snapshots__/protocol_spec.ts.js +++ b/packages/server/__snapshots__/protocol_spec.ts.js @@ -1,4 +1,4 @@ -exports['lib/browsers/protocol ._getDelayMsForRetry retries as expected 1'] = [ +exports['lib/browsers/protocol ._getDelayMsForRetry retries as expected for up to 5 seconds 1'] = [ 100, 100, 100, @@ -7,6 +7,14 @@ exports['lib/browsers/protocol ._getDelayMsForRetry retries as expected 1'] = [ 100, 100, 100, + 100, + 100, + 500, + 500, + 500, + 500, + 500, + 500, 500, 500 ] diff --git a/packages/server/lib/browsers/protocol.js b/packages/server/lib/browsers/protocol.js index 6f19e83520b5..08ced634e491 100644 --- a/packages/server/lib/browsers/protocol.js +++ b/packages/server/lib/browsers/protocol.js @@ -1,17 +1,18 @@ const _ = require('lodash') const CRI = require('chrome-remote-interface') const { connect } = require('@packages/network') +const errors = require('../errors') const Promise = require('bluebird') const la = require('lazy-ass') const is = require('check-more-types') const debug = require('debug')('cypress:server:protocol') function _getDelayMsForRetry (i) { - if (i < 8) { + if (i < 10) { return 100 } - if (i < 10) { + if (i < 18) { return 500 } } @@ -23,6 +24,9 @@ function connectAsync (opts) { ...opts, }, cb) }) + .catch((err) => { + errors.throw('CDP_COULD_NOT_CONNECT', opts.port, err) + }) } /** diff --git a/packages/server/lib/errors.coffee b/packages/server/lib/errors.coffee index 53bc818a72fa..19199f16a676 100644 --- a/packages/server/lib/errors.coffee +++ b/packages/server/lib/errors.coffee @@ -842,6 +842,18 @@ getMsgByType = (type, arg1 = {}, arg2) -> Please do not modify CYPRESS_ENV value. """ + when "CDP_COULD_NOT_CONNECT" + """ + Cypress could not make a connection to the Chrome DevTools Protocol after 5 seconds. + + Without this, your tests can not run. Please check your Chrome installation and try again. + + The CDP port requested was #{chalk.yellow(arg1)}. + + Error details: + + #{arg2.stack} + """ get = (type, arg1, arg2) -> msg = getMsgByType(type, arg1, arg2) diff --git a/packages/server/package.json b/packages/server/package.json index 3c469812a397..e65a68a97f15 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -151,6 +151,7 @@ "@cypress/debugging-proxy": "2.0.1", "@cypress/json-schemas": "5.32.2", "@cypress/sinon-chai": "1.1.0", + "@types/chai-as-promised": "7.1.2", "babel-plugin-add-module-exports": "1.0.2", "babelify": "10.0.0", "bin-up": "1.2.2", diff --git a/packages/server/test/e2e/2_cdp_spec.ts b/packages/server/test/e2e/2_cdp_spec.ts new file mode 100644 index 000000000000..786e087757da --- /dev/null +++ b/packages/server/test/e2e/2_cdp_spec.ts @@ -0,0 +1,21 @@ +const e2e = require('../support/helpers/e2e') +const Fixtures = require('../support/helpers/fixtures') + +describe('e2e cdp', function () { + e2e.setup() + + it('fails when remote debugging port cannot be connected to', function () { + return e2e.exec(this, { + project: Fixtures.projectPath('remote-debugging-port-removed'), + spec: 'spec.ts', + browser: 'chrome', + expectedExitCode: 1, + snapshot: true, + onStdout: (stdout: string) => { + return stdout + .replace(/(port requested was )(\d+)/, '$1777') + .replace(/(127\.0\.0\.1:)(\d+)/, '$1777') + }, + }) + }) +}) diff --git a/packages/server/test/support/fixtures/projects/remote-debugging-port-removed/cypress.json b/packages/server/test/support/fixtures/projects/remote-debugging-port-removed/cypress.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/packages/server/test/support/fixtures/projects/remote-debugging-port-removed/cypress.json @@ -0,0 +1 @@ +{} diff --git a/packages/server/test/support/fixtures/projects/remote-debugging-port-removed/cypress/integration/spec.ts b/packages/server/test/support/fixtures/projects/remote-debugging-port-removed/cypress/integration/spec.ts new file mode 100644 index 000000000000..60bc2f960218 --- /dev/null +++ b/packages/server/test/support/fixtures/projects/remote-debugging-port-removed/cypress/integration/spec.ts @@ -0,0 +1,3 @@ +describe('passes', () => { + it('passes', () => {}) +}) diff --git a/packages/server/test/support/fixtures/projects/remote-debugging-port-removed/cypress/plugins.js b/packages/server/test/support/fixtures/projects/remote-debugging-port-removed/cypress/plugins.js new file mode 100644 index 000000000000..60dd08b421ac --- /dev/null +++ b/packages/server/test/support/fixtures/projects/remote-debugging-port-removed/cypress/plugins.js @@ -0,0 +1,14 @@ +const la = require('lazy-ass') + +module.exports = (on) => { + on('before:browser:launch', (browser = {}, args) => { + la(browser.family === 'chrome', 'this test can only be run with a chrome-family browser') + + // remove debugging port so that the browser connection fails + const newArgs = args.filter((arg) => !arg.startsWith('--remote-debugging-port=')) + + la(newArgs.length === args.length - 1, 'exactly one argument should have been removed') + + return newArgs + }) +} diff --git a/packages/server/test/unit/browsers/protocol_spec.ts b/packages/server/test/unit/browsers/protocol_spec.ts index 131483fde3f1..0c95aa5b17a1 100644 --- a/packages/server/test/unit/browsers/protocol_spec.ts +++ b/packages/server/test/unit/browsers/protocol_spec.ts @@ -1,14 +1,19 @@ import '../../spec_helper' +import _ from 'lodash' +import 'chai-as-promised' // for the types! +import chalk from 'chalk' import { connect } from '@packages/network' import CRI from 'chrome-remote-interface' import { expect } from 'chai' +import humanInterval from 'human-interval' import protocol from '../../../lib/browsers/protocol' import sinon from 'sinon' import snapshot from 'snap-shot-it' +import { stripIndents } from 'common-tags' describe('lib/browsers/protocol', function () { context('._getDelayMsForRetry', function () { - it('retries as expected', function () { + it('retries as expected for up to 5 seconds', function () { let delays = [] let delay : number let i = 0 @@ -18,16 +23,32 @@ describe('lib/browsers/protocol', function () { i++ } + expect(_.sum(delays)).to.eq(humanInterval('5 seconds')) + snapshot(delays) }) }) context('.getWsTargetFor', function () { it('rejects if CDP connection fails', function () { - sinon.stub(connect, 'createRetryingSocket').callsArgWith(1, new Error('cdp connection failure')) + const innerErr = new Error('cdp connection failure') + + sinon.stub(connect, 'createRetryingSocket').callsArgWith(1, innerErr) const p = protocol.getWsTargetFor(12345) + const expectedError = stripIndents` + Cypress could not make a connection to the Chrome DevTools Protocol after 5 seconds. + + Without this, your tests can not run. Please check your Chrome installation and try again. + + The CDP port requested was ${chalk.yellow('12345')}. + + Error details: + ` + return expect(p).to.eventually.be.rejected + .and.property('message').include(expectedError) + .and.include(innerErr.message) }) it('returns the debugger URL of the first about:blank tab', function () { From 63d2474417438e8c148b647bec4871c19b0d8bac Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Fri, 11 Oct 2019 17:00:08 -0400 Subject: [PATCH 90/91] Update tests --- packages/server/test/integration/cypress_spec.coffee | 2 +- packages/server/test/unit/browsers/chrome_spec.coffee | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/server/test/integration/cypress_spec.coffee b/packages/server/test/integration/cypress_spec.coffee index 969bd0761fdd..5b80a594caf2 100644 --- a/packages/server/test/integration/cypress_spec.coffee +++ b/packages/server/test/integration/cypress_spec.coffee @@ -817,7 +817,7 @@ describe "lib/cypress", -> browserArgs = args[2] - expect(browserArgs).to.have.length(8) + expect(browserArgs).to.have.length(7) expect(browserArgs.slice(0, 4)).to.deep.eq([ "chrome", "foo", "bar", "baz" diff --git a/packages/server/test/unit/browsers/chrome_spec.coffee b/packages/server/test/unit/browsers/chrome_spec.coffee index 5491611c19f9..76a957fa8468 100644 --- a/packages/server/test/unit/browsers/chrome_spec.coffee +++ b/packages/server/test/unit/browsers/chrome_spec.coffee @@ -80,8 +80,7 @@ describe "lib/browsers/chrome", -> "--foo=bar" "--load-extension=/foo/bar/baz.js,/path/to/ext,#{pathToTheme}" "--user-data-dir=/profile/dir" - "--disk-cache-dir=/profile/dir/CypressCache", - "--remote-debugging-port=50505" + "--disk-cache-dir=/profile/dir/CypressCache" ]) it "normalizes multiple extensions from plugins", -> @@ -103,8 +102,7 @@ describe "lib/browsers/chrome", -> "--foo=bar" "--load-extension=/foo/bar/baz.js,/quux.js,/path/to/ext,#{pathToTheme}" "--user-data-dir=/profile/dir" - "--disk-cache-dir=/profile/dir/CypressCache", - "--remote-debugging-port=50505" + "--disk-cache-dir=/profile/dir/CypressCache" ]) it "cleans up an unclean browser profile exit status", -> From 4e6bd31e9b6b912f3f8e842f97bbf04749e08c78 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Fri, 11 Oct 2019 18:09:28 -0400 Subject: [PATCH 91/91] Use CYPRESS_REMOTE_DEBUGGING_PORT to set CDP port; update CDP error message --- packages/server/__snapshots__/2_cdp_spec.ts.js | 8 ++++---- packages/server/lib/browsers/chrome.coffee | 7 ++++++- packages/server/lib/errors.coffee | 4 ++-- packages/server/test/e2e/2_cdp_spec.ts | 18 +++++++++++++----- .../server/test/unit/browsers/protocol_spec.ts | 4 ++-- 5 files changed, 27 insertions(+), 14 deletions(-) diff --git a/packages/server/__snapshots__/2_cdp_spec.ts.js b/packages/server/__snapshots__/2_cdp_spec.ts.js index a7dad81df24c..9d4d4ccb30c1 100644 --- a/packages/server/__snapshots__/2_cdp_spec.ts.js +++ b/packages/server/__snapshots__/2_cdp_spec.ts.js @@ -15,15 +15,15 @@ exports['e2e cdp fails when remote debugging port cannot be connected to 1'] = ` ──────────────────────────────────────────────────────────────────────────────────────────────────── Running: spec.ts... (1 of 1) -Cypress could not make a connection to the Chrome DevTools Protocol after 5 seconds. +Cypress failed to make a connection to the Chrome DevTools Protocol after retrying for 5 seconds. -Without this, your tests can not run. Please check your Chrome installation and try again. +This usually indicates there was a problem opening the Chrome browser. -The CDP port requested was 777. +The CDP port requested was 7777. Error details: -Error: connect ECONNREFUSED 127.0.0.1:777 +Error: connect ECONNREFUSED 127.0.0.1:7777 at stack trace line diff --git a/packages/server/lib/browsers/chrome.coffee b/packages/server/lib/browsers/chrome.coffee index 034435006531..f3f3e57f5cce 100644 --- a/packages/server/lib/browsers/chrome.coffee +++ b/packages/server/lib/browsers/chrome.coffee @@ -92,6 +92,11 @@ defaultArgs = [ "--use-mock-keychain" ] +getRemoteDebuggingPort = Promise.method () -> + if port = Number(process.env.CYPRESS_REMOTE_DEBUGGING_PORT) + return port + utils.getPort() + pluginsBeforeBrowserLaunch = (browser, args) -> ## bail if we're not registered to this event return args if not plugins.has("before:browser:launch") @@ -261,7 +266,7 @@ module.exports = { .try => args = @_getArgs(options) - utils.getPort() + getRemoteDebuggingPort() .then (port) -> args.push("--remote-debugging-port=#{port}") diff --git a/packages/server/lib/errors.coffee b/packages/server/lib/errors.coffee index 19199f16a676..a91aa94a9950 100644 --- a/packages/server/lib/errors.coffee +++ b/packages/server/lib/errors.coffee @@ -844,9 +844,9 @@ getMsgByType = (type, arg1 = {}, arg2) -> """ when "CDP_COULD_NOT_CONNECT" """ - Cypress could not make a connection to the Chrome DevTools Protocol after 5 seconds. + Cypress failed to make a connection to the Chrome DevTools Protocol after retrying for 5 seconds. - Without this, your tests can not run. Please check your Chrome installation and try again. + This usually indicates there was a problem opening the Chrome browser. The CDP port requested was #{chalk.yellow(arg1)}. diff --git a/packages/server/test/e2e/2_cdp_spec.ts b/packages/server/test/e2e/2_cdp_spec.ts index 786e087757da..2c5c5f0a54fd 100644 --- a/packages/server/test/e2e/2_cdp_spec.ts +++ b/packages/server/test/e2e/2_cdp_spec.ts @@ -1,8 +1,21 @@ +import mockedEnv from 'mocked-env' + const e2e = require('../support/helpers/e2e') const Fixtures = require('../support/helpers/fixtures') describe('e2e cdp', function () { e2e.setup() + let restoreEnv : Function + + beforeEach(() => { + restoreEnv = mockedEnv({ + CYPRESS_REMOTE_DEBUGGING_PORT: '7777', + }) + }) + + afterEach(() => { + restoreEnv() + }) it('fails when remote debugging port cannot be connected to', function () { return e2e.exec(this, { @@ -11,11 +24,6 @@ describe('e2e cdp', function () { browser: 'chrome', expectedExitCode: 1, snapshot: true, - onStdout: (stdout: string) => { - return stdout - .replace(/(port requested was )(\d+)/, '$1777') - .replace(/(127\.0\.0\.1:)(\d+)/, '$1777') - }, }) }) }) diff --git a/packages/server/test/unit/browsers/protocol_spec.ts b/packages/server/test/unit/browsers/protocol_spec.ts index 0c95aa5b17a1..c9e29fae09f0 100644 --- a/packages/server/test/unit/browsers/protocol_spec.ts +++ b/packages/server/test/unit/browsers/protocol_spec.ts @@ -37,9 +37,9 @@ describe('lib/browsers/protocol', function () { const p = protocol.getWsTargetFor(12345) const expectedError = stripIndents` - Cypress could not make a connection to the Chrome DevTools Protocol after 5 seconds. + Cypress failed to make a connection to the Chrome DevTools Protocol after retrying for 5 seconds. - Without this, your tests can not run. Please check your Chrome installation and try again. + This usually indicates there was a problem opening the Chrome browser. The CDP port requested was ${chalk.yellow('12345')}.