From 8a4848837aea88f79446c77eeac097b713a0d0a6 Mon Sep 17 00:00:00 2001 From: Ben Kucera <14625260+Bkucera@users.noreply.github.com> Date: Mon, 19 Jul 2021 12:18:52 -0400 Subject: [PATCH] fix(breaking_change): default to headless and 1280x720 for cypress run in all browsers (#17309) Co-authored-by: Jennifer Shehane --- cli/__snapshots__/cli_spec.js | 4 +-- cli/lib/cli.js | 4 +-- .../integration/issues/1939_1940_2190_spec.js | 25 +++++++++-------- packages/driver/cypress/support/defaults.js | 13 +++++---- packages/driver/src/cypress.js | 13 +++++++++ .../server/__snapshots__/3_config_spec.js | 2 +- packages/server/lib/browsers/chrome.ts | 4 +-- packages/server/lib/browsers/firefox-util.ts | 27 +++++++++++++++---- packages/server/lib/browsers/firefox.ts | 6 ++--- packages/server/lib/modes/run.js | 9 ++----- packages/server/lib/socket-base.ts | 2 ++ .../server/test/integration/cypress_spec.js | 4 +-- .../cypress/integration/default_size.spec.js | 14 +++++----- .../server/test/unit/browsers/chrome_spec.js | 2 +- packages/server/test/unit/modes/run_spec.js | 4 +-- scripts/ensure-dependencies.sh | 4 +-- yarn.lock | 10 +++---- 17 files changed, 90 insertions(+), 57 deletions(-) diff --git a/cli/__snapshots__/cli_spec.js b/cli/__snapshots__/cli_spec.js index 475d78d3e2..2bf70683b4 100644 --- a/cli/__snapshots__/cli_spec.js +++ b/cli/__snapshots__/cli_spec.js @@ -69,8 +69,8 @@ exports['shows help for run --foo 1'] = ` -e, --env sets environment variables. separate multiple values with a comma. overrides any value in cypress.json or cypress.env.json --group a named group for recorded runs in the Cypress Dashboard -k, --key your secret Record Key. you can omit this if you set a CYPRESS_RECORD_KEY environment variable. - --headed displays the browser instead of running headlessly (defaults to true for Firefox and Chromium-family browsers) - --headless hide the browser instead of running headed (defaults to true for Electron) + --headed displays the browser instead of running headlessly + --headless hide the browser instead of running headed (default for cypress run) --no-exit keep the browser open after tests finish --parallel enables concurrent runs and automatic load balancing of specs across multiple machines or processes -p, --port runs Cypress on a specific port. overrides any value in cypress.json. diff --git a/cli/lib/cli.js b/cli/lib/cli.js index 5c9a404203..a0237380c5 100644 --- a/cli/lib/cli.js +++ b/cli/lib/cli.js @@ -115,8 +115,8 @@ const descriptions = { forceInstall: 'force install the Cypress binary', global: 'force Cypress into global mode as if its globally installed', group: 'a named group for recorded runs in the Cypress Dashboard', - headed: 'displays the browser instead of running headlessly (defaults to true for Firefox and Chromium-family browsers)', - headless: 'hide the browser instead of running headed (defaults to true for Electron)', + headed: 'displays the browser instead of running headlessly', + headless: 'hide the browser instead of running headed (default for cypress run)', key: 'your secret Record Key. you can omit this if you set a CYPRESS_RECORD_KEY environment variable.', parallel: 'enables concurrent runs and automatic load balancing of specs across multiple machines or processes', port: 'runs Cypress on a specific port. overrides any value in cypress.json.', diff --git a/packages/driver/cypress/integration/issues/1939_1940_2190_spec.js b/packages/driver/cypress/integration/issues/1939_1940_2190_spec.js index f946173ca5..f8d99a121d 100644 --- a/packages/driver/cypress/integration/issues/1939_1940_2190_spec.js +++ b/packages/driver/cypress/integration/issues/1939_1940_2190_spec.js @@ -1,15 +1,17 @@ +beforeEach(() => { + Cypress.config('retries', 0) +}) + +const autIframeHasFocus = () => Object.getOwnPropertyDescriptor(top.Document.prototype, 'hasFocus').value.call(top.frames[1].document) + // https://github.com/cypress-io/cypress/issues/1939 -it('has focus when running headlessly in electron', (done) => { +it('has focus when running headlessly', () => { if (Cypress.browser.isHeadless) { // top (aka Cypress frame) should always be in focus // when running headlessly. if we aren't running headlessly // it may not be in focus if the user has clicked away. // we don't want this test to potentially fail in that case expect(top.document.hasFocus()).to.be.true - done() - } else { - // else done and making sure only 2 path options are here - done() } }) @@ -19,6 +21,10 @@ it('sets the AUT document.hasFocus to top.document.hasFocus', () => { // the top does. cy.visit('/timeout') .then(() => { + if (Cypress.browser.isHeadless) { + return cy.document().invoke('hasFocus').should('be.true') + } + if (top.document.hasFocus()) { return cy.document().invoke('hasFocus').should('be.true') } @@ -27,7 +33,7 @@ it('sets the AUT document.hasFocus to top.document.hasFocus', () => { }) }) -it('continues to have focus through top navigations', (done) => { +it('continues to have focus through top navigation', () => { cy .visit('http://localhost:3501/fixtures/generic.html') .then(() => { @@ -36,11 +42,8 @@ it('continues to have focus through top navigations', (done) => { // when running headlessly. if we aren't running headlessly // it may not be in focus if the user has clicked away. // we don't want this test to potentially fail in that case - expect(top.document.hasFocus()).to.be.true - done() - } else { - // else done and making sure only 2 path options are here - done() + // it's OK if the autIframe has focus too, since that means the window still has focus + expect(top.document.hasFocus() || autIframeHasFocus()).to.be.true } }) }) diff --git a/packages/driver/cypress/support/defaults.js b/packages/driver/cypress/support/defaults.js index 0a6d725d77..7fdac3420d 100644 --- a/packages/driver/cypress/support/defaults.js +++ b/packages/driver/cypress/support/defaults.js @@ -2,6 +2,14 @@ const { $ } = Cypress let isActuallyInteractive +isActuallyInteractive = Cypress.config('isInteractive') +if (!isActuallyInteractive) { + // we want to only enable retries in runMode + // and because we set `isInteractive` above + // we have to set retries here + Cypress.config('retries', 2) +} + beforeEach(() => { isActuallyInteractive = Cypress.config('isInteractive') @@ -14,11 +22,6 @@ beforeEach(() => { // necessary or else snapshots will not be taken // and we can't test them Cypress.config('numTestsKeptInMemory', 1) - - // we want to only enable retries in runMode - // and because we set `isInteractive` above - // we have to set retries here - Cypress.config('retries', 2) } // remove all event listeners diff --git a/packages/driver/src/cypress.js b/packages/driver/src/cypress.js index 68d57fa7b4..c3b479083c 100644 --- a/packages/driver/src/cypress.js +++ b/packages/driver/src/cypress.js @@ -226,6 +226,19 @@ class $Cypress { } })) }) + .then(() => { + // in order to utilize focusmanager.testingmode and trick browser into being in focus even when not focused + // this is critical for headless mode since otherwise the browser never gains focus + if (this.browser.isHeadless && this.isBrowser({ family: 'firefox' })) { + window.addEventListener('blur', () => { + this.backend('firefox:window:focus') + }) + + if (!document.hasFocus()) { + return this.backend('firefox:window:focus') + } + } + }) .then(() => { this.cy.initialize(this.$autIframe) diff --git a/packages/server/__snapshots__/3_config_spec.js b/packages/server/__snapshots__/3_config_spec.js index 1f26019678..9b4b71c670 100644 --- a/packages/server/__snapshots__/3_config_spec.js +++ b/packages/server/__snapshots__/3_config_spec.js @@ -115,7 +115,7 @@ exports['e2e config applies defaultCommandTimeout globally 1'] = ` (Screenshots) - - /XXX/XXX/XXX/cypress/screenshots/dom_times_out_spec.js/short defaultCommandTimeo (1920x1080) + - /XXX/XXX/XXX/cypress/screenshots/dom_times_out_spec.js/short defaultCommandTimeo (1280x720) ut -- times out looking for a missing element (failed).png diff --git a/packages/server/lib/browsers/chrome.ts b/packages/server/lib/browsers/chrome.ts index 9bb29a4f41..0571acb957 100644 --- a/packages/server/lib/browsers/chrome.ts +++ b/packages/server/lib/browsers/chrome.ts @@ -426,9 +426,9 @@ export = { if (isHeadless) { args.push('--headless') - // set default headless size to 1920x1080 + // set default headless size to 1280x720 // https://github.com/cypress-io/cypress/issues/6210 - args.push('--window-size=1920,1080') + args.push('--window-size=1280,720') // set default headless DPR to 1 // https://github.com/cypress-io/cypress/issues/17375 diff --git a/packages/server/lib/browsers/firefox-util.ts b/packages/server/lib/browsers/firefox-util.ts index 62d9d97507..3a86846148 100644 --- a/packages/server/lib/browsers/firefox-util.ts +++ b/packages/server/lib/browsers/firefox-util.ts @@ -21,6 +21,12 @@ let timings = { collections: [] as any[], } +let driver + +const sendMarionette = (data) => { + return driver.send(new Command(data)) +} + const getTabId = (tab) => { return _.get(tab, 'browsingContextID') } @@ -254,15 +260,11 @@ export default { getDelayMsForRetry, }) - const driver = new Marionette.Drivers.Promises({ + driver = new Marionette.Drivers.Promises({ port, tries: 1, // marionette-client has its own retry logic which we want to avoid }) - const sendMarionette = (data) => { - return driver.send(new Command(data)) - } - debug('firefox: navigating page with webdriver') const onError = (from, reject?) => { @@ -315,4 +317,19 @@ export default { // even though Marionette is not used past this point, we have to keep the session open // or else `acceptInsecureCerts` will cease to apply and SSL validation prompts will appear. }, + + async windowFocus () { + // in order to utilize focusmanager.testingmode and trick browser into being in focus even when not focused + // this is critical for headless mode since otherwise the browser never gains focus + return sendMarionette({ + name: 'WebDriver:ExecuteScript', + parameters: { + 'args': [], + 'script': `return (() => { + top.focus() + }).apply(null, arguments)\ + `, + }, + }) + }, } diff --git a/packages/server/lib/browsers/firefox.ts b/packages/server/lib/browsers/firefox.ts index 7d97645d19..95a4358fef 100644 --- a/packages/server/lib/browsers/firefox.ts +++ b/packages/server/lib/browsers/firefox.ts @@ -503,10 +503,10 @@ export async function open (browser: Browser, url, options: any = {}, automation debug('launch in firefox', { url, args: launchOptions.args }) const browserInstance = await launch(browser, 'about:blank', launchOptions.args, { - // sets headless resolution to 1920x1080 by default + // sets headless resolution to 1280x720 by default // user can overwrite this default with these env vars or --height, --width arguments - MOZ_HEADLESS_WIDTH: '1920', - MOZ_HEADLESS_HEIGHT: '1081', + MOZ_HEADLESS_WIDTH: '1280', + MOZ_HEADLESS_HEIGHT: '721', }) try { diff --git a/packages/server/lib/modes/run.js b/packages/server/lib/modes/run.js index ce71bfe1db..8f69f99cfa 100644 --- a/packages/server/lib/modes/run.js +++ b/packages/server/lib/modes/run.js @@ -543,8 +543,8 @@ const getChromeProps = (writeVideoFrame) => { const getElectronProps = (isHeaded, writeVideoFrame, onError) => { return _ .chain({ - width: 1920, - height: 1080, + width: 1280, + height: 720, show: isHeaded, onCrashed () { const err = errors.get('RENDERER_CRASHED') @@ -1259,11 +1259,6 @@ module.exports = { }, runSpecs (options = {}) { - _.defaults(options, { - // only non-Electron browsers run headed by default - headed: options.browser.name !== 'electron', - }) - const { config, browser, sys, headed, outputPath, specs, specPattern, beforeSpecRun, afterSpecRun, runUrl, parallel, group, tag, testingType } = options const isHeadless = !headed diff --git a/packages/server/lib/socket-base.ts b/packages/server/lib/socket-base.ts index 20d41fa42a..79ab5f2a5d 100644 --- a/packages/server/lib/socket-base.ts +++ b/packages/server/lib/socket-base.ts @@ -365,6 +365,8 @@ export class SocketBase { return firefoxUtil.log() case 'firefox:force:gc': return firefoxUtil.collectGarbage() + case 'firefox:window:focus': + return firefoxUtil.windowFocus() case 'get:fixture': return getFixture(args[0], args[1]) case 'read:file': diff --git a/packages/server/test/integration/cypress_spec.js b/packages/server/test/integration/cypress_spec.js index 6cd955fdbe..c8ed17c1c7 100644 --- a/packages/server/test/integration/cypress_spec.js +++ b/packages/server/test/integration/cypress_spec.js @@ -1048,8 +1048,8 @@ describe('lib/cypress', () => { // when we work with the browsers we set a few extra flags const chrome = _.find(TYPICAL_BROWSERS, { name: 'chrome' }) const launchedChrome = R.merge(chrome, { - isHeadless: false, - isHeaded: true, + isHeadless: true, + isHeaded: false, }) expect(args[0], 'found and used Chrome').to.deep.eq(launchedChrome) diff --git a/packages/server/test/support/fixtures/projects/screen-size/cypress/integration/default_size.spec.js b/packages/server/test/support/fixtures/projects/screen-size/cypress/integration/default_size.spec.js index 4a0f893fa8..968669d3cb 100644 --- a/packages/server/test/support/fixtures/projects/screen-size/cypress/integration/default_size.spec.js +++ b/packages/server/test/support/fixtures/projects/screen-size/cypress/integration/default_size.spec.js @@ -1,6 +1,6 @@ describe('windowSize', () => { it('spawns with correct default size', () => { - // assert the browser was spawned at 1920x1080 and is full size + // assert the browser was spawned at 1280x720 and is full size // normally e2e tests spawn at fixed size, but this spec should be spawned without passing any width/height arguments in plugins file. // TODO: look into fixing screen/available height and width expect({ @@ -11,12 +11,12 @@ describe('windowSize', () => { // availWidth: top.screen.availWidth, // availHeight: top.screen.availHeight, }).deep.eq({ - innerWidth: 1920, - innerHeight: 1080, - // screenWidth: 1920, - // screenHeight: 1080, - // availWidth: 1920, - // availHeight: 1080, + innerWidth: 1280, + innerHeight: 720, + // screenWidth: 1280, + // screenHeight: 720, + // availWidth: 1280, + // availHeight: 720, }) }) }) diff --git a/packages/server/test/unit/browsers/chrome_spec.js b/packages/server/test/unit/browsers/chrome_spec.js index 27f6a18edd..da7398848c 100644 --- a/packages/server/test/unit/browsers/chrome_spec.js +++ b/packages/server/test/unit/browsers/chrome_spec.js @@ -108,7 +108,7 @@ describe('lib/browsers/chrome', () => { expect(args).to.include.members([ '--headless', - '--window-size=1920,1080', + '--window-size=1280,720', '--force-device-scale-factor=1', ]) }) diff --git a/packages/server/test/unit/modes/run_spec.js b/packages/server/test/unit/modes/run_spec.js index 11b0e4d398..ce3a505d6e 100644 --- a/packages/server/test/unit/modes/run_spec.js +++ b/packages/server/test/unit/modes/run_spec.js @@ -100,9 +100,9 @@ describe('lib/modes/run', () => { it('sets width and height', () => { const props = runMode.getElectronProps() - expect(props.width).to.eq(1920) + expect(props.width).to.eq(1280) - expect(props.height).to.eq(1080) + expect(props.height).to.eq(720) }) it('sets show to boolean', () => { diff --git a/scripts/ensure-dependencies.sh b/scripts/ensure-dependencies.sh index c5f217dd71..aa0440fcba 100755 --- a/scripts/ensure-dependencies.sh +++ b/scripts/ensure-dependencies.sh @@ -1,9 +1,9 @@ #!/bin/bash +if [ $SKIP_DEPCHECK ]; then exit 0; fi yarn check --integrity -if [ $? -ne 0 ]; -then +if [ $? -ne 0 ]; then echo 'Your dependencies are out of date; installing the correct dependencies...' yarn fi diff --git a/yarn.lock b/yarn.lock index 795beeba2b..0f3d2b6f83 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3431,7 +3431,7 @@ "@jest/types@^26.3.0", "@jest/types@^26.6.2": version "26.6.2" - resolved "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== dependencies: "@types/istanbul-lib-coverage" "^2.0.0" @@ -7614,7 +7614,7 @@ "@types/cheerio@*", "@types/cheerio@0.22.21": version "0.22.21" - resolved "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.21.tgz#5e37887de309ba11b2e19a6e14cad7874b31a8a3" + resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.21.tgz#5e37887de309ba11b2e19a6e14cad7874b31a8a3" integrity sha512-aGI3DfswwqgKPiEOTaiHV2ZPC9KEhprpgEbJnv0fZl3SGX0cGgEva1126dGrMC6AJM6v/aihlUgJn9M5DbDZ/Q== dependencies: "@types/node" "*" @@ -7709,7 +7709,7 @@ "@types/enzyme@*", "@types/enzyme@3.10.5": version "3.10.5" - resolved "https://registry.npmjs.org/@types/enzyme/-/enzyme-3.10.5.tgz#fe7eeba3550369eed20e7fb565bfb74eec44f1f0" + resolved "https://registry.yarnpkg.com/@types/enzyme/-/enzyme-3.10.5.tgz#fe7eeba3550369eed20e7fb565bfb74eec44f1f0" integrity sha512-R+phe509UuUYy9Tk0YlSbipRpfVtIzb/9BHn5pTEtjJTF5LXvUjrIQcZvNyANNEyFrd2YGs196PniNT1fgvOQA== dependencies: "@types/cheerio" "*" @@ -31042,7 +31042,7 @@ pretty-error@^2.0.2, pretty-error@^2.1.1: pretty-format@26.4.0, pretty-format@^24.9.0, pretty-format@^26.6.2: version "26.4.0" - resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-26.4.0.tgz#c08073f531429e9e5024049446f42ecc9f933a3b" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.4.0.tgz#c08073f531429e9e5024049446f42ecc9f933a3b" integrity sha512-mEEwwpCseqrUtuMbrJG4b824877pM5xald3AkilJ47Po2YLr97/siejYQHqj2oDQBeJNbu+Q0qUuekJ8F0NAPg== dependencies: "@jest/types" "^26.3.0" @@ -35057,7 +35057,7 @@ socket.io-client@4.0.1: socket.io-parser@4.0.4, socket.io-parser@~3.3.0, socket.io-parser@~3.4.0, socket.io-parser@~4.0.3, socket.io-parser@~4.0.4: version "4.0.4" - resolved "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz#9ea21b0d61508d18196ef04a2c6b9ab630f4c2b0" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.0.4.tgz#9ea21b0d61508d18196ef04a2c6b9ab630f4c2b0" integrity sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g== dependencies: "@types/component-emitter" "^1.2.10"