From f5eae48c7723be5cd8e31ff54f30c55b9e236b61 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Tue, 23 Jun 2020 15:32:18 -0400 Subject: [PATCH 01/13] chore(deps): electron@9.0.5 BREAKING CHANGE: libgbm is a requirement --- packages/electron/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/electron/package.json b/packages/electron/package.json index 1f3d82074ae3..a359834160ea 100644 --- a/packages/electron/package.json +++ b/packages/electron/package.json @@ -24,7 +24,7 @@ "minimist": "1.2.5" }, "devDependencies": { - "electron": "8.3.1", + "electron": "9.0.5", "mocha": "3.5.3" }, "files": [ From 6dfd0156740a4a57713e90cbcfee7da18fb84b2c Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Wed, 24 Jun 2020 11:28:53 -0400 Subject: [PATCH 02/13] update node, xcode, docker images --- .node-version | 2 +- appveyor.yml | 2 +- circle.yml | 8 ++++---- package.json | 2 +- scripts/run-docker-local.sh | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.node-version b/.node-version index e56b2006c570..5c088ddb94af 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -12.8.1 +12.14.1 diff --git a/appveyor.yml b/appveyor.yml index 462b9f83ee2a..14abce2131d2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,7 +7,7 @@ branches: # https://www.appveyor.com/docs/lang/nodejs-iojs/ environment: # use matching version of Node.js - nodejs_version: "12.8.1" + nodejs_version: "12.14.1" # encode secure variables which will NOT be used # in pull requests # https://www.appveyor.com/docs/build-configuration/#secure-variables diff --git a/circle.yml b/circle.yml index d8a49378a86e..78bdc0822f41 100644 --- a/circle.yml +++ b/circle.yml @@ -43,14 +43,14 @@ executors: # the Docker image with Cypress dependencies and Chrome browser cy-doc: docker: - - image: cypress/browsers:node12.13.0-chrome80-ff74 + - image: cypress/browsers:node12.14.1-chrome83-ff77 environment: PLATFORM: linux # Docker image with non-root "node" user non-root-docker-user: docker: - - image: cypress/browsers:node12.13.0-chrome80-ff74 + - image: cypress/browsers:node12.14.1-chrome83-ff77 user: node environment: PLATFORM: linux @@ -60,8 +60,8 @@ executors: # https://circleci.com/docs/2.0/testing-ios/#supported-xcode-versions mac: macos: - ## Node 12.10.0 (yarn 1.17.3) - xcode: "11.0.0" + ## Node 12.12.0 (yarn 1.19.1) + xcode: "11.2.1" environment: PLATFORM: mac diff --git a/package.json b/package.json index c7dca292f365..9fa12febfa34 100644 --- a/package.json +++ b/package.json @@ -187,7 +187,7 @@ "typescript": "3.7.4" }, "engines": { - "node": ">=12.8.1", + "node": ">=12.14.1", "yarn": ">=1.17.3" }, "productName": "Cypress", diff --git a/scripts/run-docker-local.sh b/scripts/run-docker-local.sh index d8785a770e09..11cb7cf8ede4 100755 --- a/scripts/run-docker-local.sh +++ b/scripts/run-docker-local.sh @@ -2,7 +2,7 @@ set e+x echo "This script should be run from cypress's root" -name=cypress/browsers:node12.13.0-chrome80-ff74 +name=cypress/browsers:node12.14.1-chrome83-ff77 echo "Pulling CI container $name" docker pull $name From b692cdf6dc135ec4b587fa91d14e2d68d5a34a40 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Wed, 24 Jun 2020 11:29:02 -0400 Subject: [PATCH 03/13] lockfile --- yarn.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index c6327991c5c9..8b51e1480ed4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10452,10 +10452,10 @@ electron-to-chromium@^1.3.413: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.441.tgz#094f71b992dca5bc96b798cfbaf37dc76302015a" integrity sha512-leBfJwLuyGs1jEei2QioI+PjVMavmUIvPYidE8dCCYWLAq0uefhN3NYgDNb8WxD3uiUNnJ3ScMXg0upSlwySzQ== -electron@8.3.1: - version "8.3.1" - resolved "https://registry.yarnpkg.com/electron/-/electron-8.3.1.tgz#79e98c4d5b8e7c09a8a811f1aa78903f0c692721" - integrity sha512-VZpgLVFyD2SwFDkO9rwUcNgrAMah+g38FEtALGxli8bRVTbcHl8bt21szfa0YUWpc6hWcaf6JdZjqDS5q73Bsg== +electron@9.0.5: + version "9.0.5" + resolved "https://registry.yarnpkg.com/electron/-/electron-9.0.5.tgz#189ee117cc2a2777cccf40fae0766acec5faae57" + integrity sha512-bnL9H48LuQ250DML8xUscsKiuSu+xv5umXbpBXYJ0BfvYVmFfNbG3jCfhrsH7aP6UcQKVxOG1R/oQExd0EFneQ== dependencies: "@electron/get" "^1.0.1" "@types/node" "^12.0.12" From f34576a99e22f75c2053f7260aa99134f16d1533 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Wed, 24 Jun 2020 12:16:38 -0400 Subject: [PATCH 04/13] chore(types): tsify lib/gui/windows and spec --- packages/server/lib/gui/windows.js | 327 ----------------- packages/server/lib/gui/windows.ts | 328 ++++++++++++++++++ .../gui/{windows_spec.js => windows_spec.ts} | 55 ++- 3 files changed, 353 insertions(+), 357 deletions(-) delete mode 100644 packages/server/lib/gui/windows.js create mode 100644 packages/server/lib/gui/windows.ts rename packages/server/test/unit/gui/{windows_spec.js => windows_spec.ts} (80%) diff --git a/packages/server/lib/gui/windows.js b/packages/server/lib/gui/windows.js deleted file mode 100644 index db5aff55c18f..000000000000 --- a/packages/server/lib/gui/windows.js +++ /dev/null @@ -1,327 +0,0 @@ -const _ = require('lodash') -const Promise = require('bluebird') -const cyDesktop = require('@packages/desktop-gui') -const contextMenu = require('electron-context-menu') -const { BrowserWindow } = require('electron') -const debug = require('debug')('cypress:server:windows') -const cwd = require('../cwd') -const savedState = require('../saved_state') - -let windows = {} -let recentlyCreatedWindow = false - -const getUrl = function (type) { - switch (type) { - case 'INDEX': - return cyDesktop.getPathToIndex() - default: - throw new Error(`No acceptable window type found for: '${type}'`) - } -} - -const getByType = (type) => { - return windows[type] -} - -const setWindowProxy = function (win) { - if (!process.env.HTTP_PROXY) { - return - } - - return win.webContents.session.setProxy({ - proxyRules: process.env.HTTP_PROXY, - proxyBypassRules: process.env.NO_PROXY, - }) -} - -module.exports = { - installExtension (path) { - // extensions can only be installed for all BrowserWindows - const name = BrowserWindow.addExtension(path) - - debug('electron extension installed %o', { success: !!name, name, path }) - - if (!name) { - throw new Error('Extension could not be installed.') - } - }, - - removeAllExtensions () { - const extensions = _.keys(BrowserWindow.getExtensions()) - - debug('removing all electron extensions %o', extensions) - - return extensions.forEach(BrowserWindow.removeExtension) - }, - - reset () { - windows = {} - }, - - destroy (type) { - let win - - if (type && (win = getByType(type))) { - return win.destroy() - } - }, - - get (type) { - return getByType(type) || (() => { - throw new Error(`No window exists for: '${type}'`) - })() - }, - - showAll () { - return _.invoke(windows, 'showInactive') - }, - - hideAllUnlessAnotherWindowIsFocused () { - // bail if we have another focused window - // or we are in the middle of creating a new one - if (BrowserWindow.getFocusedWindow() || recentlyCreatedWindow) { - return - } - - // else hide all windows - return _.invoke(windows, 'hide') - }, - - focusMainWindow () { - return getByType('INDEX').show() - }, - - getByWebContents (webContents) { - return BrowserWindow.fromWebContents(webContents) - }, - - _newBrowserWindow (options) { - return new BrowserWindow(options) - }, - - defaults (options = {}) { - return _.defaultsDeep(options, { - x: null, - y: null, - show: true, - frame: true, - width: null, - height: null, - minWidth: null, - minHeight: null, - devTools: false, - trackState: false, - contextMenu: false, - recordFrameRate: null, - // extension: null ## TODO add these once we update electron - // devToolsExtension: null ## since these API's were added in 1.7.6 - onFocus () {}, - onBlur () {}, - onClose () {}, - onCrashed () {}, - onNewWindow () {}, - webPreferences: { - partition: null, - webSecurity: true, - nodeIntegration: false, - backgroundThrottling: false, - }, - }) - }, - - create (projectRoot, options = {}) { - let ts - - options = this.defaults(options) - - if (options.show === false) { - options.frame = false - options.webPreferences.offscreen = true - } - - options.webPreferences.webSecurity = !!options.chromeWebSecurity - - if (options.partition) { - options.webPreferences.partition = options.partition - } - - const win = this._newBrowserWindow(options) - - win.on('blur', function (...args) { - return options.onBlur.apply(win, args) - }) - - win.on('focus', function (...args) { - return options.onFocus.apply(win, args) - }) - - win.once('closed', function (...args) { - win.removeAllListeners() - - return options.onClose.apply(win, args) - }) - - // the webview loses focus on navigation, so we - // have to refocus it everytime top navigates in headless mode - // https://github.com/cypress-io/cypress/issues/2190 - if (options.show === false) { - win.webContents.on('did-start-loading', () => { - if (!win.isDestroyed()) { - return win.focusOnWebView() - } - }) - } - - win.webContents.on('crashed', function (...args) { - return options.onCrashed.apply(win, args) - }) - - win.webContents.on('new-window', function (...args) { - return options.onNewWindow.apply(win, args) - }) - - ts = options.trackState - - if (ts) { - this.trackState(projectRoot, options.isTextTerminal, win, ts) - } - - // open dev tools if they're true - if (options.devTools) { - // and possibly detach dev tools if true - win.webContents.openDevTools() - } - - if (options.contextMenu) { - // adds context menu with copy, paste, inspect element, etc - contextMenu({ - showInspectElement: true, - window: win, - }) - } - - return win - }, - - open (projectRoot, options = {}) { - // if we already have a window open based - // on that type then just show + focus it! - let win - - win = getByType(options.type) - - if (win) { - win.show() - - return Promise.resolve(win) - } - - recentlyCreatedWindow = true - - _.defaults(options, { - width: 600, - height: 500, - show: true, - webPreferences: { - preload: cwd('lib', 'ipc', 'ipc.js'), - }, - }) - - if (!options.url) { - options.url = getUrl(options.type) - } - - win = this.create(projectRoot, options) - - debug('creating electron window with options %o', options) - - windows[options.type] = win - - win.webContents.id = _.uniqueId('webContents') - - win.once('closed', () => { - delete windows[options.type] - }) - - // enable our url to be a promise - // and wait for this to be resolved - return Promise.join( - options.url, - setWindowProxy(win), - ) - .spread((url) => { - // navigate the window here! - win.loadURL(url) - - recentlyCreatedWindow = false - }).thenReturn(win) - }, - - trackState (projectRoot, isTextTerminal, win, keys) { - const isDestroyed = () => { - return win.isDestroyed() - } - - win.on('resize', _.debounce(() => { - if (isDestroyed()) { - return - } - - const [width, height] = win.getSize() - const [x, y] = win.getPosition() - const newState = {} - - newState[keys.width] = width - newState[keys.height] = height - newState[keys.x] = x - newState[keys.y] = y - - return savedState.create(projectRoot, isTextTerminal) - .then((state) => { - return state.set(newState) - }) - } - , 500)) - - win.on('moved', _.debounce(() => { - if (isDestroyed()) { - return - } - - const [x, y] = win.getPosition() - const newState = {} - - newState[keys.x] = x - newState[keys.y] = y - - return savedState.create(projectRoot, isTextTerminal) - .then((state) => { - return state.set(newState) - }) - } - , 500)) - - win.webContents.on('devtools-opened', () => { - const newState = {} - - newState[keys.devTools] = true - - return savedState.create(projectRoot, isTextTerminal) - .then((state) => { - return state.set(newState) - }) - }) - - return win.webContents.on('devtools-closed', () => { - const newState = {} - - newState[keys.devTools] = false - - return savedState.create(projectRoot, isTextTerminal) - .then((state) => { - return state.set(newState) - }) - }) - }, - -} diff --git a/packages/server/lib/gui/windows.ts b/packages/server/lib/gui/windows.ts new file mode 100644 index 000000000000..3bb19f9677ed --- /dev/null +++ b/packages/server/lib/gui/windows.ts @@ -0,0 +1,328 @@ +import _ from 'lodash' +import Promise from 'bluebird' +import contextMenu from 'electron-context-menu' +import { BrowserWindow } from 'electron' +import Debug from 'debug' +import cwd from '../cwd' +import savedState from '../saved_state' +const cyDesktop = require('@packages/desktop-gui') + +const debug = Debug('cypress:server:windows') + +export type WindowOptions = Electron.BrowserWindowConstructorOptions & { + type?: 'INDEX' + url?: string + devTools?: boolean +} + +let windows = {} +let recentlyCreatedWindow = false + +const getUrl = function (type) { + switch (type) { + case 'INDEX': + return cyDesktop.getPathToIndex() + default: + throw new Error(`No acceptable window type found for: '${type}'`) + } +} + +const getByType = (type) => { + return windows[type] +} + +const setWindowProxy = function (win) { + if (!process.env.HTTP_PROXY) { + return + } + + return win.webContents.session.setProxy({ + proxyRules: process.env.HTTP_PROXY, + proxyBypassRules: process.env.NO_PROXY, + }) +} + +export function installExtension (path) { + // extensions can only be installed for all BrowserWindows + const name = BrowserWindow.addExtension(path) + + debug('electron extension installed %o', { success: !!name, name, path }) + + if (!name) { + throw new Error('Extension could not be installed.') + } +} + +export function removeAllExtensions () { + const extensions = _.keys(BrowserWindow.getExtensions()) + + debug('removing all electron extensions %o', extensions) + + return extensions.forEach(BrowserWindow.removeExtension) +} + +export function reset () { + windows = {} +} + +export function destroy (type) { + let win + + if (type && (win = getByType(type))) { + return win.destroy() + } +} + +export function get (type) { + return getByType(type) || (() => { + throw new Error(`No window exists for: '${type}'`) + })() +} + +export function showAll () { + return _.invoke(windows, 'showInactive') +} + +export function hideAllUnlessAnotherWindowIsFocused () { + // bail if we have another focused window + // or we are in the middle of creating a new one + if (BrowserWindow.getFocusedWindow() || recentlyCreatedWindow) { + return + } + + // else hide all windows + return _.invoke(windows, 'hide') +} + +export function focusMainWindow () { + return getByType('INDEX').show() +} + +export function getByWebContents (webContents) { + return BrowserWindow.fromWebContents(webContents) +} + +export function _newBrowserWindow (options) { + return new BrowserWindow(options) +} + +export function defaults (options = {}) { + return _.defaultsDeep(options, { + x: null, + y: null, + show: true, + frame: true, + width: null, + height: null, + minWidth: null, + minHeight: null, + devTools: false, + trackState: false, + contextMenu: false, + recordFrameRate: null, + onFocus () {}, + onBlur () {}, + onClose () {}, + onCrashed () {}, + onNewWindow () {}, + webPreferences: { + partition: null, + webSecurity: true, + nodeIntegration: false, + backgroundThrottling: false, + }, + }) +} + +export function create (projectRoot, _options: WindowOptions = {}, newBrowserWindow = _newBrowserWindow) { + const options = defaults(_options) + + if (options.show === false) { + options.frame = false + options.webPreferences.offscreen = true + } + + options.webPreferences.webSecurity = !!options.chromeWebSecurity + + if (options.partition) { + options.webPreferences.partition = options.partition + } + + const win = newBrowserWindow(options) + + win.on('blur', function (...args) { + return options.onBlur.apply(win, args) + }) + + win.on('focus', function (...args) { + return options.onFocus.apply(win, args) + }) + + win.once('closed', function (...args) { + win.removeAllListeners() + + return options.onClose.apply(win, args) + }) + + // the webview loses focus on navigation, so we + // have to refocus it everytime top navigates in headless mode + // https://github.com/cypress-io/cypress/issues/2190 + if (options.show === false) { + win.webContents.on('did-start-loading', () => { + if (!win.isDestroyed()) { + return win.focusOnWebView() + } + }) + } + + win.webContents.on('crashed', function (...args) { + return options.onCrashed.apply(win, args) + }) + + win.webContents.on('new-window', function (...args) { + return options.onNewWindow.apply(win, args) + }) + + if (options.trackState) { + trackState(projectRoot, options.isTextTerminal, win, options.trackState) + } + + // open dev tools if they're true + if (options.devTools) { + // and possibly detach dev tools if true + win.webContents.openDevTools() + } + + if (options.contextMenu) { + // adds context menu with copy, paste, inspect element, etc + contextMenu({ + showInspectElement: true, + window: win, + }) + } + + return win +} + +export function open (projectRoot, options: WindowOptions = {}, newBrowserWindow = _newBrowserWindow) { + // if we already have a window open based + // on that type then just show + focus it! + let win + + win = getByType(options.type) + + if (win) { + win.show() + + return Promise.resolve(win) + } + + recentlyCreatedWindow = true + + _.defaults(options, { + width: 600, + height: 500, + show: true, + webPreferences: { + preload: cwd('lib', 'ipc', 'ipc.js'), + }, + }) + + if (!options.url) { + options.url = getUrl(options.type) + } + + win = create(projectRoot, options, newBrowserWindow) + + debug('creating electron window with options %o', options) + + if (options.type) { + windows[options.type] = win + + win.once('closed', () => { + delete windows[options.type!] + }) + } + + win.webContents.id = _.uniqueId('webContents') + + // enable our url to be a promise + // and wait for this to be resolved + return Promise.join( + options.url, + setWindowProxy(win), + ) + .spread((url) => { + // navigate the window here! + win.loadURL(url) + + recentlyCreatedWindow = false + }).thenReturn(win) +} + +export function trackState (projectRoot, isTextTerminal, win, keys) { + const isDestroyed = () => { + return win.isDestroyed() + } + + win.on('resize', _.debounce(() => { + if (isDestroyed()) { + return + } + + const [width, height] = win.getSize() + const [x, y] = win.getPosition() + const newState = {} + + newState[keys.width] = width + newState[keys.height] = height + newState[keys.x] = x + newState[keys.y] = y + + return savedState.create(projectRoot, isTextTerminal) + .then((state) => { + return state.set(newState) + }) + } + , 500)) + + win.on('moved', _.debounce(() => { + if (isDestroyed()) { + return + } + + const [x, y] = win.getPosition() + const newState = {} + + newState[keys.x] = x + newState[keys.y] = y + + return savedState.create(projectRoot, isTextTerminal) + .then((state) => { + return state.set(newState) + }) + } + , 500)) + + win.webContents.on('devtools-opened', () => { + const newState = {} + + newState[keys.devTools] = true + + return savedState.create(projectRoot, isTextTerminal) + .then((state) => { + return state.set(newState) + }) + }) + + return win.webContents.on('devtools-closed', () => { + const newState = {} + + newState[keys.devTools] = false + + return savedState.create(projectRoot, isTextTerminal) + .then((state) => { + return state.set(newState) + }) + }) +} diff --git a/packages/server/test/unit/gui/windows_spec.js b/packages/server/test/unit/gui/windows_spec.ts similarity index 80% rename from packages/server/test/unit/gui/windows_spec.js rename to packages/server/test/unit/gui/windows_spec.ts index abfac43b39cd..952f0e4f9895 100644 --- a/packages/server/test/unit/gui/windows_spec.js +++ b/packages/server/test/unit/gui/windows_spec.ts @@ -1,13 +1,17 @@ -require('../../spec_helper') +import '../../spec_helper' + +import { expect } from 'chai' +import 'sinon-chai' + +import _ from 'lodash' +import path from 'path' +import Promise from 'bluebird' +import { EventEmitter } from 'events' +import { BrowserWindow } from 'electron' +import * as Windows from '../../../lib/gui/windows' +import savedState from '../../../lib/saved_state' -const _ = require('lodash') -const path = require('path') -const Promise = require('bluebird') -const EE = require('events').EventEmitter -const { BrowserWindow } = require('electron') const cyDesktop = require('@packages/desktop-gui') -const Windows = require(`${root}../lib/gui/windows`) -const savedState = require(`${root}../lib/saved_state`) const DEFAULT_USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Cypress/0.0.0 Chrome/59.0.3071.115 Electron/1.8.2 Safari/537.36' @@ -15,17 +19,15 @@ describe('lib/gui/windows', () => { beforeEach(function () { Windows.reset() - this.win = new EE() + this.win = new EventEmitter() this.win.loadURL = sinon.stub() this.win.destroy = sinon.stub() this.win.getSize = sinon.stub().returns([1, 2]) this.win.getPosition = sinon.stub().returns([3, 4]) - this.win.webContents = new EE() + this.win.webContents = new EventEmitter() this.win.webContents.openDevTools = sinon.stub() this.win.webContents.userAgent = DEFAULT_USER_AGENT this.win.isDestroyed = sinon.stub().returns(false) - - return sinon.stub(Windows, '_newBrowserWindow').returns(this.win) }) afterEach(() => { @@ -33,38 +35,31 @@ describe('lib/gui/windows', () => { }) context('.getByWebContents', () => { - beforeEach(() => { - return sinon.stub(BrowserWindow, 'fromWebContents') - }) - it('calls BrowserWindow.fromWebContents', () => { - BrowserWindow.fromWebContents.withArgs('foo').returns('bar') + sinon.stub(BrowserWindow, 'fromWebContents').withArgs('foo' as any).returns('bar' as any) expect(Windows.getByWebContents('foo')).to.eq('bar') }) }) context('.open', () => { - beforeEach(function () { - return sinon.stub(Windows, 'create').returns(this.win) - }) - - it('sets default options', () => { - const options = { + it('sets default options', function () { + const options: Windows.WindowOptions = { type: 'INDEX', } - return Windows.open('/path/to/project', options) + return Windows.open('/path/to/project', options, () => this.win) .then((win) => { - expect(options).to.deep.eq({ + expect(options).to.include({ height: 500, width: 600, type: 'INDEX', show: true, url: cyDesktop.getPathToIndex(), - webPreferences: { - preload: path.resolve('lib', 'ipc', 'ipc.js'), - }, + }) + + expect(options.webPreferences).to.include({ + preload: path.resolve('lib', 'ipc', 'ipc.js'), }) expect(win.loadURL).to.be.calledWith(cyDesktop.getPathToIndex()) @@ -74,10 +69,10 @@ describe('lib/gui/windows', () => { context('.create', () => { it('opens dev tools if saved state is open', function () { - Windows.create('/foo/', { devTools: true }) + Windows.create('/foo/', { devTools: true }, () => this.win) expect(this.win.webContents.openDevTools).to.be.called - Windows.create('/foo/', {}) + Windows.create('/foo/', {}, () => this.win) expect(this.win.webContents.openDevTools).not.to.be.calledTwice }) From c8af0487a8800dce31bc5d02b9be79ba0d2f8b24 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Wed, 24 Jun 2020 12:31:03 -0400 Subject: [PATCH 05/13] fix Electron extension loading global extension loading was deprecated in 9, now has to be per-session --- packages/server/lib/browsers/electron.js | 16 ++++---- packages/server/lib/gui/windows.ts | 38 +++++++++++-------- .../cypress/integration/spec.js | 6 --- .../cypress/plugins/index.js | 11 +----- 4 files changed, 31 insertions(+), 40 deletions(-) diff --git a/packages/server/lib/browsers/electron.js b/packages/server/lib/browsers/electron.js index 51eb4f1283fd..84d83e4c5061 100644 --- a/packages/server/lib/browsers/electron.js +++ b/packages/server/lib/browsers/electron.js @@ -66,12 +66,12 @@ const _getAutomation = function (win, options) { return automation } -const _installExtensions = function (extensionPaths = [], options) { - Windows.removeAllExtensions() +const _installExtensions = function (win, extensionPaths = [], options) { + Windows.removeAllExtensions(win) - return extensionPaths.forEach((path) => { + return Bluebird.map(extensionPaths, (path) => { try { - return Windows.installExtension(path) + return Windows.installExtension(win, path) } catch (error) { return options.onWarning(errors.get('EXTENSION_NOT_LOADED', 'Electron', path)) } @@ -349,10 +349,10 @@ module.exports = { debug('launching browser window to url: %s', url) - _installExtensions(launchOptions.extensions, options) - return this._render(url, projectRoot, automation, preferences) - .then((win) => { + .then(async (win) => { + await _installExtensions(win, launchOptions.extensions, options) + // cause the webview to receive focus so that // native browser focus + blur events fire correctly // https://github.com/cypress-io/cypress/issues/1939 @@ -363,7 +363,7 @@ module.exports = { win.once('closed', () => { debug('closed event fired') - Windows.removeAllExtensions() + Windows.removeAllExtensions(win) return events.emit('exit') }) diff --git a/packages/server/lib/gui/windows.ts b/packages/server/lib/gui/windows.ts index 3bb19f9677ed..52d217a01ff5 100644 --- a/packages/server/lib/gui/windows.ts +++ b/packages/server/lib/gui/windows.ts @@ -1,5 +1,5 @@ import _ from 'lodash' -import Promise from 'bluebird' +import Bluebird from 'bluebird' import contextMenu from 'electron-context-menu' import { BrowserWindow } from 'electron' import Debug from 'debug' @@ -42,23 +42,29 @@ const setWindowProxy = function (win) { }) } -export function installExtension (path) { - // extensions can only be installed for all BrowserWindows - const name = BrowserWindow.addExtension(path) - - debug('electron extension installed %o', { success: !!name, name, path }) - - if (!name) { - throw new Error('Extension could not be installed.') - } +export function installExtension (win: BrowserWindow, path) { + return win.webContents.session.loadExtension(path) + .then((data) => { + debug('electron extension installed %o', { data, path }) + }) + .catch((err) => { + debug('error installing electron extension %o', { err, path }) + throw err + }) } -export function removeAllExtensions () { - const extensions = _.keys(BrowserWindow.getExtensions()) +export function removeAllExtensions (win: BrowserWindow) { + let extensions - debug('removing all electron extensions %o', extensions) + try { + extensions = win.webContents.session.getAllExtensions() - return extensions.forEach(BrowserWindow.removeExtension) + extensions.forEach(({ id }) => { + win.webContents.session.removeExtension(id) + }) + } catch (err) { + debug('error removing all extensions %o', { err, extensions }) + } } export function reset () { @@ -214,7 +220,7 @@ export function open (projectRoot, options: WindowOptions = {}, newBrowserWindow if (win) { win.show() - return Promise.resolve(win) + return Bluebird.resolve(win) } recentlyCreatedWindow = true @@ -248,7 +254,7 @@ export function open (projectRoot, options: WindowOptions = {}, newBrowserWindow // enable our url to be a promise // and wait for this to be resolved - return Promise.join( + return Bluebird.join( options.url, setWindowProxy(win), ) diff --git a/packages/server/test/support/fixtures/projects/browser-extensions/cypress/integration/spec.js b/packages/server/test/support/fixtures/projects/browser-extensions/cypress/integration/spec.js index d9cafa56b83b..0804337984fb 100644 --- a/packages/server/test/support/fixtures/projects/browser-extensions/cypress/integration/spec.js +++ b/packages/server/test/support/fixtures/projects/browser-extensions/cypress/integration/spec.js @@ -1,11 +1,5 @@ context('before:browser:launch extension e2e', () => { it('has the expected extension', () => { - if (Cypress.browser.name === 'electron') { - cy.wrap(window.top).its('theExtensionLoaded').should('be.true') - - return - } - cy.visit('/index.html') .get('#extension') .should('contain', 'inserted from extension!') diff --git a/packages/server/test/support/fixtures/projects/browser-extensions/cypress/plugins/index.js b/packages/server/test/support/fixtures/projects/browser-extensions/cypress/plugins/index.js index e708af7d7f9d..1b9a005ab36c 100644 --- a/packages/server/test/support/fixtures/projects/browser-extensions/cypress/plugins/index.js +++ b/packages/server/test/support/fixtures/projects/browser-extensions/cypress/plugins/index.js @@ -2,16 +2,7 @@ const path = require('path') module.exports = (on) => { on('before:browser:launch', (browser, options) => { - const { extensions } = options - - if (browser.name === 'electron') { - // electron doesn't support background pages yet, so load a devtools extension - // instead which will work - extensions.push(path.join(__dirname, '../../devtools-ext')) - } else { - extensions.push(path.join(__dirname, '../../../plugin-extension/ext')) - } - + options.extensions.push(path.join(__dirname, '../../../plugin-extension/ext')) options.preferences.devTools = true return options From 75823ac58c86c9443d4c5274b66ca7a3e91ee268 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Thu, 25 Jun 2020 09:55:39 -0400 Subject: [PATCH 06/13] make windows fns stubbable --- packages/server/lib/gui/events.js | 9 +++++-- packages/server/test/unit/gui/events_spec.js | 28 ++++++++++++++------ 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/packages/server/lib/gui/events.js b/packages/server/lib/gui/events.js index e120ead901b9..1ec7d203bc6f 100644 --- a/packages/server/lib/gui/events.js +++ b/packages/server/lib/gui/events.js @@ -35,6 +35,11 @@ const nullifyUnserializableValues = (obj) => { const handleEvent = function (options, bus, event, id, type, arg) { debug('got request for event: %s, %o', type, arg) + _.defaults(options, { + windowOpenFn: Windows.open, + getWindowByWebContentsFn: Windows.getByWebContents, + }) + const sendResponse = function (originalData = {}) { try { const data = nullifyUnserializableValues(originalData) @@ -160,12 +165,12 @@ const handleEvent = function (options, bus, event, id, type, arg) { .catch(sendErr) case 'window:open': - return Windows.open(options.projectRoot, arg) + return options.windowOpenFn(options.projectRoot, arg) .then(send) .catch(sendErr) case 'window:close': - return Windows.getByWebContents(event.sender).destroy() + return options.getWindowByWebContentsFn(event.sender).destroy() case 'open:finder': return open.opn(arg) diff --git a/packages/server/test/unit/gui/events_spec.js b/packages/server/test/unit/gui/events_spec.js index 8c21facd7ce5..db9c2096149f 100644 --- a/packages/server/test/unit/gui/events_spec.js +++ b/packages/server/test/unit/gui/events_spec.js @@ -17,9 +17,8 @@ const openProject = require(`${root}../lib/open_project`) const open = require(`${root}../lib/util/open`) const auth = require(`${root}../lib/gui/auth`) const logs = require(`${root}../lib/gui/logs`) -const events = require(`${root}../lib/gui/events`) +const events = require(`../../../lib/gui/events`) const dialog = require(`${root}../lib/gui/dialog`) -const Windows = require(`${root}../lib/gui/windows`) const ensureUrl = require(`${root}../lib/util/ensure-url`) const konfig = require(`${root}../lib/konfig`) @@ -196,6 +195,8 @@ describe('lib/gui/events', () => { }) context('window', () => { + let fakeWindows + describe('window:open', () => { beforeEach(function () { this.options.projectRoot = '/path/to/my/project' @@ -207,10 +208,18 @@ describe('lib/gui/events', () => { webContents: {}, }) - return sinon.stub(Windows, 'create').withArgs(this.options.projectRoot).returns(this.win) + this.options.windowOpenFn = + + fakeWindows = { + create: sinon.stub(), + } + + fakeWindows.create.withArgs(this.options.projectRoot).returns(this.win) }) - it('calls Windows#open with args and resolves with return of Windows.open', function () { + it('calls windowOpenFn with args and resolves with return', function () { + this.options.windowOpenFn = sinon.stub().rejects().withArgs({ type: 'INDEX ' }).resolves(this.win) + return this.handleEvent('window:open', { type: 'INDEX' }) .then((assert) => { return assert.sendCalledWith(events.nullifyUnserializableValues(this.win)) @@ -220,7 +229,7 @@ describe('lib/gui/events', () => { it('catches errors', function () { const err = new Error('foo') - sinon.stub(Windows, 'open').withArgs(this.options.projectRoot, { foo: 'bar' }).rejects(err) + this.options.windowOpenFn = sinon.stub().withArgs(this.options.projectRoot, { foo: 'bar' }).rejects(err) return this.handleEvent('window:open', { foo: 'bar' }).then((assert) => { return assert.sendErrCalledWith(err) @@ -230,11 +239,14 @@ describe('lib/gui/events', () => { describe('window:close', () => { it('calls destroy on Windows#getByWebContents', function () { - this.destroy = sinon.stub() - sinon.stub(Windows, 'getByWebContents').withArgs(this.event.sender).returns({ destroy: this.destroy }) + const win = { + destroy: sinon.stub(), + } + + this.options.getWindowByWebContentsFn = sinon.stub().withArgs(this.event.sender).returns(win) this.handleEvent('window:close') - expect(this.destroy).to.be.calledOnce + expect(win.destroy).to.be.calledOnce }) }) }) From 83d6ea54bd50cb0fc82bfdbb7caa56690b12483d Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Thu, 25 Jun 2020 10:14:29 -0400 Subject: [PATCH 07/13] update electron_spec --- packages/server/test/unit/browsers/electron_spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/server/test/unit/browsers/electron_spec.js b/packages/server/test/unit/browsers/electron_spec.js index 85701d28ac64..b6eea551eac5 100644 --- a/packages/server/test/unit/browsers/electron_spec.js +++ b/packages/server/test/unit/browsers/electron_spec.js @@ -135,15 +135,15 @@ describe('lib/browsers/electron', () => { plugins.has.returns(true) plugins.execute.resolves({ extensions: ['foo', 'bar'] }) - Windows.installExtension.withArgs('bar').throws() + Windows.installExtension.withArgs(sinon.match.any, 'bar').throws() return electron.open('electron', this.url, this.options, this.automation) .then(() => { expect(Windows.removeAllExtensions).to.be.calledOnce expect(Windows.installExtension).to.be.calledTwice - expect(Windows.installExtension).to.be.calledWith('foo') - expect(Windows.installExtension).to.be.calledWith('bar') + expect(Windows.installExtension).to.be.calledWith(sinon.match.any, 'foo') + expect(Windows.installExtension).to.be.calledWith(sinon.match.any, 'bar') expect(this.options.onWarning).to.be.calledOnce From d4be2055f5f0b80d2b17ccee595db82b0b4ec992 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Thu, 25 Jun 2020 11:26:00 -0400 Subject: [PATCH 08/13] tsify issue_173_spec --- .../test/e2e/{3_issue_173_spec.js => 3_issue_173_spec.ts} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename packages/server/test/e2e/{3_issue_173_spec.js => 3_issue_173_spec.ts} (79%) diff --git a/packages/server/test/e2e/3_issue_173_spec.js b/packages/server/test/e2e/3_issue_173_spec.ts similarity index 79% rename from packages/server/test/e2e/3_issue_173_spec.js rename to packages/server/test/e2e/3_issue_173_spec.ts index d73090d33af0..dc11a1c4cf25 100644 --- a/packages/server/test/e2e/3_issue_173_spec.js +++ b/packages/server/test/e2e/3_issue_173_spec.ts @@ -1,4 +1,4 @@ -const e2e = require('../support/helpers/e2e').default +import e2e from '../support/helpers/e2e' describe('e2e issue 173', () => { e2e.setup() From 1687f577fd97034dee5e0b417526fb9582e8e79b Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Thu, 25 Jun 2020 11:57:20 -0400 Subject: [PATCH 09/13] use upstream foxdriver to fix FF >= 75 see https://github.com/benmalka/foxdriver/issues/7 --- ...sue_173_spec.js => 3_issue_173_spec.ts.js} | 0 packages/server/lib/browsers/firefox-util.ts | 2 +- packages/server/package.json | 2 +- .../server/test/unit/browsers/firefox_spec.ts | 2 +- yarn.lock | 24 +++++++++---------- 5 files changed, 15 insertions(+), 15 deletions(-) rename packages/server/__snapshots__/{3_issue_173_spec.js => 3_issue_173_spec.ts.js} (100%) diff --git a/packages/server/__snapshots__/3_issue_173_spec.js b/packages/server/__snapshots__/3_issue_173_spec.ts.js similarity index 100% rename from packages/server/__snapshots__/3_issue_173_spec.js rename to packages/server/__snapshots__/3_issue_173_spec.ts.js diff --git a/packages/server/lib/browsers/firefox-util.ts b/packages/server/lib/browsers/firefox-util.ts index 3c4078d264ac..84908987c5dd 100644 --- a/packages/server/lib/browsers/firefox-util.ts +++ b/packages/server/lib/browsers/firefox-util.ts @@ -4,7 +4,7 @@ import _ from 'lodash' import Marionette from 'marionette-client' import { Command } from 'marionette-client/lib/marionette/message.js' import util from 'util' -import Foxdriver from '@benmalka/foxdriver' +import Foxdriver from 'foxdriver' import * as protocol from './protocol' const errors = require('../errors') diff --git a/packages/server/package.json b/packages/server/package.json index 8c392934785d..153c7121bcd2 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -18,7 +18,6 @@ "test-watch": "./test/support/watch test" }, "dependencies": { - "@benmalka/foxdriver": "0.4.0", "@cypress/browserify-preprocessor": "2.2.4", "@cypress/commit-info": "2.2.0", "@cypress/get-windows-proxy": "1.6.1", @@ -57,6 +56,7 @@ "firefox-profile": "1.3.1", "fix-path": "2.1.0", "fluent-ffmpeg": "2.1.2", + "foxdriver": "0.1.1", "fs-extra": "8.1.0", "get-port": "5.1.1", "getos": "3.2.1", diff --git a/packages/server/test/unit/browsers/firefox_spec.ts b/packages/server/test/unit/browsers/firefox_spec.ts index 6ee69a500218..28b553686792 100644 --- a/packages/server/test/unit/browsers/firefox_spec.ts +++ b/packages/server/test/unit/browsers/firefox_spec.ts @@ -7,7 +7,7 @@ import firefoxUtil from '../../../lib/browsers/firefox-util' import * as firefox from '../../../lib/browsers/firefox' import { EventEmitter } from 'events' import Marionette from 'marionette-client' -import Foxdriver from '@benmalka/foxdriver' +import Foxdriver from 'foxdriver' const mockfs = require('mock-fs') const FirefoxProfile = require('firefox-profile') const utils = require('../../../lib/browsers/utils') diff --git a/yarn.lock b/yarn.lock index 8b51e1480ed4..dee038ae1e33 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1268,18 +1268,6 @@ jsonpointer "^4.0.0" xtend "^4.0.0" -"@benmalka/foxdriver@0.4.0": - version "0.4.0" - resolved "https://registry.yarnpkg.com/@benmalka/foxdriver/-/foxdriver-0.4.0.tgz#aecd158a785a09e104da036efe7611a13af4ed11" - integrity sha512-Uc9n5TTRV2AhfYaqkaDc5k72pfKxyd6LDVrQJyNLZ7uY4ThgEcQMjpVRkB8C7v16gKhubH4gmD0lad5pGvDUBg== - dependencies: - fs-extra "^4.0.1" - get-port "^3.2.0" - npmlog "^4.1.2" - safe-buffer "^5.1.1" - tcp-port-used "^1.0.1" - tmp "0.0.33" - "@chromaui/localtunnel@1.10.1": version "1.10.1" resolved "https://registry.yarnpkg.com/@chromaui/localtunnel/-/localtunnel-1.10.1.tgz#34da7dab7055a16b1b9034a9eb7e3054ebec4b98" @@ -12254,6 +12242,18 @@ forwarded@~0.1.2: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= +foxdriver@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/foxdriver/-/foxdriver-0.1.1.tgz#b66e0fb1f495b0139c7a0a5a5f31541b2adf0b23" + integrity sha512-ZJ6u9kzCkmDo7OVIzqF+KlMRLc+W53GCzglaLhFG1ixMliEC7o6TeucYd1XpSacpmZnPRVQE+byyR7LXC84htA== + dependencies: + fs-extra "^4.0.1" + get-port "^3.2.0" + npmlog "^4.1.2" + safe-buffer "^5.1.1" + tcp-port-used "^1.0.1" + tmp "0.0.33" + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" From aea21595be21127464cdcee7029a80b0ac706bd4 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Thu, 25 Jun 2020 12:00:24 -0400 Subject: [PATCH 10/13] update test --- packages/server/test/unit/gui/events_spec.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/packages/server/test/unit/gui/events_spec.js b/packages/server/test/unit/gui/events_spec.js index db9c2096149f..8389551f38c0 100644 --- a/packages/server/test/unit/gui/events_spec.js +++ b/packages/server/test/unit/gui/events_spec.js @@ -195,8 +195,6 @@ describe('lib/gui/events', () => { }) context('window', () => { - let fakeWindows - describe('window:open', () => { beforeEach(function () { this.options.projectRoot = '/path/to/my/project' @@ -207,14 +205,6 @@ describe('lib/gui/events', () => { loadURL () {}, webContents: {}, }) - - this.options.windowOpenFn = - - fakeWindows = { - create: sinon.stub(), - } - - fakeWindows.create.withArgs(this.options.projectRoot).returns(this.win) }) it('calls windowOpenFn with args and resolves with return', function () { From 97cc6e0f97d5b12ef41803df3c9f9ff8ef08c39d Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Thu, 25 Jun 2020 16:23:43 -0400 Subject: [PATCH 11/13] for now, install libgbm-dev at ci time see https://github.com/cypress-io/cypress-docker-images/pull/332 --- circle.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/circle.yml b/circle.yml index 78bdc0822f41..e9bda62a99ab 100644 --- a/circle.yml +++ b/circle.yml @@ -1102,6 +1102,9 @@ jobs: - run: mkdir test-binary - run: node --version - run: npm --version + # TODO: this can be removed as soon as the minimum Node version is bumped to 10 + # see https://github.com/cypress-io/cypress-docker-images/pull/332 + - run: apt-get update && apt-get install -y libgbm-dev - run: name: Create new NPM package working_directory: test-binary From fa1cdb69551596ee6358c26b58b972da1b7ee5f7 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Fri, 26 Jun 2020 09:11:39 -0400 Subject: [PATCH 12/13] fix open mode --- packages/server/lib/gui/windows.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/server/lib/gui/windows.ts b/packages/server/lib/gui/windows.ts index 52d217a01ff5..f44a7bb6ab24 100644 --- a/packages/server/lib/gui/windows.ts +++ b/packages/server/lib/gui/windows.ts @@ -250,8 +250,6 @@ export function open (projectRoot, options: WindowOptions = {}, newBrowserWindow }) } - win.webContents.id = _.uniqueId('webContents') - // enable our url to be a promise // and wait for this to be resolved return Bluebird.join( From af4e3e4fc50e76ddb107c86fed57ca7aeb9f9e63 Mon Sep 17 00:00:00 2001 From: Zach Bloomquist Date: Mon, 29 Jun 2020 12:10:03 -0400 Subject: [PATCH 13/13] remove devtools-ext dir --- .../browser-extensions/devtools-ext/devtools.html | 15 --------------- .../browser-extensions/devtools-ext/manifest.json | 13 ------------- 2 files changed, 28 deletions(-) delete mode 100644 packages/server/test/support/fixtures/projects/browser-extensions/devtools-ext/devtools.html delete mode 100644 packages/server/test/support/fixtures/projects/browser-extensions/devtools-ext/manifest.json diff --git a/packages/server/test/support/fixtures/projects/browser-extensions/devtools-ext/devtools.html b/packages/server/test/support/fixtures/projects/browser-extensions/devtools-ext/devtools.html deleted file mode 100644 index 46e7f0645178..000000000000 --- a/packages/server/test/support/fixtures/projects/browser-extensions/devtools-ext/devtools.html +++ /dev/null @@ -1,15 +0,0 @@ - diff --git a/packages/server/test/support/fixtures/projects/browser-extensions/devtools-ext/manifest.json b/packages/server/test/support/fixtures/projects/browser-extensions/devtools-ext/manifest.json deleted file mode 100644 index 0b9d8264c1c7..000000000000 --- a/packages/server/test/support/fixtures/projects/browser-extensions/devtools-ext/manifest.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "e2e devtools ext", - "version": "0", - "description": "tests adding devtools extension into Cypress", - "permissions": [ - "tabs", - "webNavigation", - "" - ], - "content_scripts": [], - "devtools_page": "devtools.html", - "manifest_version": 2 -}