Merge pull request #16831 from cypress-io/8.0-release

BREAKING CHANGE: 8.0 release
This commit is contained in:
Zach Bloomquist
2021-07-19 16:54:40 -04:00
committed by GitHub
64 changed files with 446 additions and 1050 deletions

View File

@@ -69,8 +69,8 @@ exports['shows help for run --foo 1'] = `
-e, --env <env> sets environment variables. separate multiple values with a comma. overrides any value in cypress.json or cypress.env.json
--group <name> a named group for recorded runs in the Cypress Dashboard
-k, --key <record-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 <port> runs Cypress on a specific port. overrides any value in cypress.json.

View File

@@ -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.',

View File

@@ -138,7 +138,7 @@ declare namespace CypressCommandLine {
/**
* Specify configuration
*/
config: Partial<Cypress.ResolvedConfigOptions>
config: Cypress.ConfigOptions
/**
* Path to the config file to be used.
*

100
cli/types/cypress.d.ts vendored
View File

@@ -64,6 +64,22 @@ declare namespace Cypress {
path: string
isHeaded: boolean
isHeadless: boolean
/**
* Informational text to accompany this browser. Shown in desktop-gui.
*/
info?: string
/**
* Warning text to accompany this browser. Shown in desktop-gui.
*/
warning?: string
/**
* The minimum majorVersion of this browser supported by Cypress.
*/
minSupportedVersion?: number
/**
* If `true`, this browser is too old to be supported by Cypress.
*/
unsupportedVersion?: boolean
}
interface LocalStorage {
@@ -341,15 +357,6 @@ declare namespace Cypress {
*/
env(object: ObjectLike): void
/**
* Firefox only: Get the current number of tests that will run between forced garbage collections.
*
* Returns undefined if not in Firefox, returns a null or 0 if forced GC is disabled.
*
* @see https://on.cypress.io/firefox-gc-issue
*/
getFirefoxGcInterval(): number | null | undefined
/**
* @returns the number of test retries currently enabled for the run
*/
@@ -1833,6 +1840,12 @@ declare namespace Cypress {
* @see https://on.cypress.io/then
*/
then<S>(options: Partial<Timeoutable>, fn: (this: ObjectLike, currentSubject: Subject) => PromiseLike<S>): Chainable<S>
/**
* Enables you to work with the subject yielded from the previous command / promise.
*
* @see https://on.cypress.io/then
*/
then<S extends string | number | boolean>(fn: (this: ObjectLike, currentSubject: Subject) => S): Chainable<S>
/**
* Enables you to work with the subject yielded from the previous command / promise.
*
@@ -1850,7 +1863,7 @@ declare namespace Cypress {
*
* @see https://on.cypress.io/then
*/
then<S extends object | any[] | string | number | boolean>(fn: (this: ObjectLike, currentSubject: Subject) => S): Chainable<S>
then<S extends any[] | object>(fn: (this: ObjectLike, currentSubject: Subject) => S): Chainable<S>
/**
* Enables you to work with the subject yielded from the previous command / promise.
*
@@ -2640,13 +2653,6 @@ declare namespace Cypress {
* @default 'top'
*/
scrollBehavior: scrollBehaviorOptions
/**
* Firefox version 79 and below only: The number of tests that will run between forced garbage collections.
* If a number is supplied, it will apply to `run` mode and `open` mode.
* Set the interval to `null` or 0 to disable forced garbage collections.
* @default { runMode: 1, openMode: null }
*/
firefoxGcInterval: Nullable<number | { runMode: Nullable<number>, openMode: Nullable<number> }>
/**
* Allows listening to the `before:run`, `after:run`, `before:spec`, and `after:spec` events in the plugins file during interactive mode.
* @default false
@@ -2678,17 +2684,46 @@ declare namespace Cypress {
*/
includeShadowDom: boolean
/**
* The list of hosts to be blocked
*/
blockHosts: null | string | string[]
/**
* Path to folder containing component test files.
*/
componentFolder: false | string
/**
* A unique ID for the project used for recording
*/
projectId: null | string
/**
* Path to the support folder.
*/
supportFolder: string
/**
* Glob pattern to determine what test files to load.
*/
testFiles: string | string[]
/**
* The user agent the browser sends in all request headers.
*/
userAgent: null | string
/**
* Polyfills `window.fetch` to enable Network spying and stubbing
*/
experimentalFetchPolyfill: boolean
/**
* Override default config options for Component Testing runner.
* @default {}
*/
component: ResolvedConfigOptions
component: Omit<ResolvedConfigOptions, 'e2e' | 'component'>
/**
* Override default config options for E2E Testing runner.
* @default {}
*/
e2e: ResolvedConfigOptions
e2e: Omit<ResolvedConfigOptions, 'e2e' | 'component'>
}
/**
@@ -2701,10 +2736,6 @@ declare namespace Cypress {
* @see https://nodejs.org/api/os.html#os_os_arch
*/
arch: string
/**
* The list of hosts to be blocked
*/
blockHosts: null | string | string[]
/**
* The browser Cypress is running on.
*/
@@ -2713,10 +2744,6 @@ declare namespace Cypress {
* Available browsers found on your system.
*/
browsers: Browser[]
/**
* Path to folder containing component test files.
*/
componentFolder: string
/**
* Hosts mappings to IP addresses.
*/
@@ -2736,22 +2763,6 @@ declare namespace Cypress {
* The platform Cypress is running on.
*/
platform: 'linux' | 'darwin' | 'win32'
/**
* A unique ID for the project used for recording
*/
projectId: null | string
/**
* Path to the support folder.
*/
supportFolder: string
/**
* Glob pattern to determine what test files to load.
*/
testFiles: string
/**
* The user agent the browser sends in all request headers.
*/
userAgent: null | string
/**
* The Cypress version being used.
*/
@@ -2798,7 +2809,8 @@ declare namespace Cypress {
/**
* All configuration items are optional.
*/
type ConfigOptions = Partial<ResolvedConfigOptions>
type CoreConfigOptions = Partial<Omit<ResolvedConfigOptions, 'e2e' | 'component'>>
type ConfigOptions = CoreConfigOptions & {e2e?: CoreConfigOptions, component?: CoreConfigOptions }
interface PluginConfigOptions extends ResolvedConfigOptions {
/**

View File

@@ -257,6 +257,15 @@ describe('then', () => {
$p // $ExpectType JQuery<HTMLParagraphElement>
})
})
// https://github.com/cypress-io/cypress/issues/16669
it('any as default', () => {
cy.get('body')
.then(() => ({} as any))
.then(v => {
v // $ExpectType any
})
})
})
cy.wait(['@foo', '@bar'])

View File

@@ -83,7 +83,8 @@
"channel": "stable",
"version": "69.0.1",
"path": "/Applications/Firefox/Contents/MacOS/Firefox",
"majorVersion": "69"
"majorVersion": "69",
"unsupportedVersion": true
},
{
"name": "firefox",

View File

@@ -204,6 +204,20 @@ describe('Project Nav', function () {
})
})
it('has unsupportedVersions styled and unselectable', function () {
cy.get('.browsers-list .dropdown-chosen').click()
cy.get('.browsers-list').find('.dropdown-menu')
.find('li').should('have.length', this.config.browsers.length - 1)
.contains('span', 'Firefox 69')
.should('have.class', 'unsupported-version')
.click()
cy.get('.browsers-list .dropdown-menu').should('be.visible')
cy.get('.browsers-list .dropdown-chosen').contains('Chromium')
})
it('saves chosen browser in local storage', () => {
expect(localStorage.getItem('chosenBrowser')).to.eq(JSON.stringify({ name: 'chromium', channel: 'stable' }))
})
@@ -255,7 +269,7 @@ describe('Project Nav', function () {
const browserArg = this.ipc.launchBrowser.getCall(0).args[0].browser
expect(browserArg).to.have.keys([
'family', 'name', 'path', 'profilePath', 'version', 'majorVersion', 'displayName', 'info', 'isChosen', 'custom', 'warning', 'channel',
'family', 'name', 'path', 'profilePath', 'version', 'majorVersion', 'displayName', 'info', 'isChosen', 'custom', 'warning', 'channel', 'unsupportedVersion',
])
expect(browserArg.path).to.include('/')
@@ -364,6 +378,22 @@ describe('Project Nav', function () {
})
})
describe('when browser saved in local storage has an unsupported version', function () {
beforeEach(function () {
localStorage.setItem('chosenBrowser', JSON.stringify({ name: 'firefox', channel: 'stable' }))
// sanity check: saved browser should be found in the config
expect(this.config.browsers.find((b) => b.name === 'firefox' && b.channel === 'stable' && b.unsupportedVersion)).to.exist
this.openProject.resolve(this.config)
})
it('defaults to first browser', () => {
cy.get('.browsers-list .dropdown-chosen')
.should('contain', 'Chrome')
})
})
describe('only one browser available', function () {
beforeEach(function () {
this.config.browsers = [{

View File

@@ -236,6 +236,13 @@
li {
padding: 9px 15px;
white-space: nowrap;
.unsupported-version {
color: #888;
img {
filter: opacity(0.5);
}
}
}
.dropdown-chosen {

View File

@@ -12,6 +12,7 @@ export default class Browser {
@observable info
@observable custom
@observable warning
@observable unsupportedVersion
@observable isChosen = false
constructor (browser) {
@@ -26,5 +27,6 @@ export default class Browser {
this.info = browser.info
this.custom = browser.custom
this.warning = browser.warning
this.unsupportedVersion = browser.unsupportedVersion
}
}

View File

@@ -1,5 +1,6 @@
import React, { Component } from 'react'
import { observer } from 'mobx-react'
import cs from 'classnames'
import Tooltip from '@cypress/react-tooltip'
import { BrowserIcon, Dropdown } from '@packages/ui-components'
@@ -49,6 +50,8 @@ export default class Browsers extends Component {
}
_onSelect = (browser) => {
if (browser.unsupportedVersion) return true
this.props.project.setChosenBrowser(browser)
}
@@ -69,14 +72,14 @@ export default class Browsers extends Component {
}
return (
<>
<span className={cs({ 'unsupported-version': browser.unsupportedVersion })}>
{icon}{' '}
{prefixText}{' '}
{browser.displayName}{' '}
{browser.majorVersion}
{this._info(browser)}
{this._warn(browser)}
</>
</span>
)
}

View File

@@ -119,12 +119,16 @@ export default class Project {
return _.filter(this.browsers, { isChosen: false })
}
@computed get supportedBrowsers () {
return _.filter(this.browsers, (browser) => !browser.unsupportedVersion)
}
@computed get chosenBrowser () {
return _.find(this.browsers, { isChosen: true })
}
@computed get defaultBrowser () {
return this.browsers[0]
return this.supportedBrowsers[0]
}
@computed get warnings () {
@@ -173,7 +177,7 @@ export default class Project {
// use a custom browser if one is supplied. or, if they already have
// a browser chosen that's been saved in localStorage, then select that
// otherwise just do the default.
const customBrowser = _.find(this.browsers, { custom: true })
const customBrowser = _.find(this.supportedBrowsers, { custom: true })
if (customBrowser) {
return this.setChosenBrowser(customBrowser, { save: false })
@@ -260,7 +264,7 @@ export default class Project {
filter.name = ls
}
const browser = _.find(this.browsers, filter) || this.defaultBrowser
const browser = _.find(this.supportedBrowsers, filter) || this.defaultBrowser
this.setChosenBrowser(browser)
}

View File

@@ -4007,15 +4007,15 @@ describe('mouse state', () => {
// TODO: add back assertion on Y values
const coordsFirefox = {
clientX: 494,
// clientY: 10,
clientY: 10,
// layerX: 492,
// layerY: 215,
pageX: 494,
pageY: 226,
screenX: 494,
// screenY: 10,
screenY: 10,
x: 494,
// y: 10,
y: 10,
}
let coords
@@ -4030,8 +4030,7 @@ describe('mouse state', () => {
}
const mouseout = cy.stub().callsFake((e) => {
expect(_.toPlainObject(e)).to.containSubset({
...coords,
const exp = {
altKey: false,
bubbles: true,
button: 0,
@@ -4059,13 +4058,16 @@ describe('mouse state', () => {
type: 'mouseout',
view: cy.state('window'),
// which: 0,
})
}
expect(_.pick(e, _.keys(exp))).to.containSubset(exp)
_.each(coords, (v, key) => expect(e[key], key).closeTo(v, 1))
e.target.removeEventListener('mouseout', mouseout)
}).as('mouseout')
const mouseleave = cy.stub().callsFake((e) => {
expect(_.toPlainObject(e)).to.containSubset({
...coords,
const exp = {
altKey: false,
bubbles: false,
button: 0,
@@ -4094,13 +4096,16 @@ describe('mouse state', () => {
type: 'mouseleave',
view: cy.state('window'),
// which: 0,
})
}
expect(_.pick(e, _.keys(exp))).to.containSubset(exp)
_.each(coords, (v, key) => expect(e[key], key).closeTo(v, 1))
e.target.removeEventListener('mouseleave', mouseleave)
}).as('mouseleave')
const pointerout = cy.stub().callsFake((e) => {
expect(_.toPlainObject(e)).to.containSubset({
...coords,
const exp = {
altKey: false,
bubbles: true,
button: -1,
@@ -4129,13 +4134,15 @@ describe('mouse state', () => {
type: 'pointerout',
view: cy.state('window'),
// which: 0,
})
}
expect(_.pick(e, _.keys(exp))).to.containSubset(exp)
_.each(coords, (v, key) => expect(e[key], key).closeTo(v, 1))
e.target.removeEventListener('pointerout', pointerout)
}).as('pointerout')
const pointerleave = cy.stub().callsFake((e) => {
expect(_.toPlainObject(e)).to.containSubset({
...coords,
const exp = {
altKey: false,
bubbles: false,
button: -1,
@@ -4164,13 +4171,15 @@ describe('mouse state', () => {
type: 'pointerleave',
view: cy.state('window'),
// which: 0,
})
}
expect(_.pick(e, _.keys(exp))).to.containSubset(exp)
_.each(coords, (v, key) => expect(e[key], key).closeTo(v, 1))
e.target.removeEventListener('pointerleave', pointerleave)
}).as('pointerleave')
const mouseover = cy.stub().callsFake((e) => {
expect(_.toPlainObject(e)).to.containSubset({
...coords,
const exp = {
altKey: false,
bubbles: true,
button: 0,
@@ -4199,13 +4208,15 @@ describe('mouse state', () => {
type: 'mouseover',
view: cy.state('window'),
// which: 0,
})
}
expect(_.pick(e, _.keys(exp))).to.containSubset(exp)
_.each(coords, (v, key) => expect(e[key], key).closeTo(v, 1))
e.target.removeEventListener('mouseover', mouseover)
}).as('mouseover')
const mouseenter = cy.stub().callsFake((e) => {
expect(_.toPlainObject(e)).to.containSubset({
...coords,
const exp = {
altKey: false,
bubbles: false,
button: 0,
@@ -4234,13 +4245,15 @@ describe('mouse state', () => {
type: 'mouseenter',
view: cy.state('window'),
// which: 0,
})
}
expect(_.pick(e, _.keys(exp))).to.containSubset(exp)
_.each(coords, (v, key) => expect(e[key], key).closeTo(v, 1))
e.target.removeEventListener('mouseenter', mouseenter)
}).as('mouseenter')
const pointerover = cy.stub().callsFake((e) => {
expect(_.toPlainObject(e)).to.containSubset({
...coords,
const exp = {
altKey: false,
bubbles: true,
button: -1,
@@ -4269,13 +4282,15 @@ describe('mouse state', () => {
type: 'pointerover',
view: cy.state('window'),
// which: 0,
})
}
expect(_.pick(e, _.keys(exp))).to.containSubset(exp)
_.each(coords, (v, key) => expect(e[key], key).closeTo(v, 1))
e.target.removeEventListener('pointerover', pointerover)
}).as('pointerover')
const pointerenter = cy.stub().callsFake((e) => {
expect(_.toPlainObject(e)).to.containSubset({
...coords,
const exp = {
altKey: false,
bubbles: false,
button: -1,
@@ -4304,7 +4319,10 @@ describe('mouse state', () => {
type: 'pointerenter',
view: cy.state('window'),
// which: 0,
})
}
expect(_.pick(e, _.keys(exp))).to.containSubset(exp)
_.each(coords, (v, key) => expect(e[key], key).closeTo(v, 1))
e.target.removeEventListener('pointerenter', pointerenter)
}).as('pointerenter')

View File

@@ -312,8 +312,8 @@ describe('src/cypress/dom/visibility', () => {
`)
this.$parentPointerEventsNone = add(`\
<div style="pointer-events: none;">
<span style="position: fixed; top: 20px;">parent pointer-events: none</span>
<div style="pointer-events: none">
<span style="position: fixed; left: 0; top: 50%;">parent pointer-events: none</span>
</div>\
`)

View File

@@ -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
}
})
})

View File

@@ -1,240 +0,0 @@
import { createIntervalGetter, install } from '../../../src/util/firefox_forced_gc'
describe('driver/src/util/firefox_forced_gc', () => {
describe('#createIntervalGetter returns a function that', () => {
const run = (configObj) => {
const fakeCypress = {
config: (key) => {
return key ? configObj[key] : configObj
},
browser: configObj.browser,
}
// @ts-ignore
return createIntervalGetter(fakeCypress)()
}
it('returns undefined if not in Firefox', () => {
expect(run({
browser: {
family: 'chrome',
},
})).to.be.undefined
})
it('returns a number if firefoxGcInterval is a plain number', () => {
expect(run({
browser: {
family: 'firefox',
majorVersion: 79,
},
firefoxGcInterval: 99,
})).to.eq(99)
})
it('returns null if firefoxGcInterval is null', () => {
expect(run({
browser: {
family: 'firefox',
majorVersion: 79,
},
firefoxGcInterval: null,
})).to.eq(null)
})
it('returns the appropriate interval for open mode', () => {
expect(run({
browser: {
family: 'firefox',
majorVersion: 79,
},
firefoxGcInterval: {
runMode: 10,
openMode: 20,
},
isInteractive: true,
})).to.eq(20)
})
it('returns the appropriate interval for run mode', () => {
expect(run({
browser: {
family: 'firefox',
majorVersion: 79,
},
firefoxGcInterval: {
runMode: 10,
openMode: 20,
},
isInteractive: false,
})).to.eq(10)
})
it('has been correctly mounted at Cypress.getFirefoxGcInterval', {
// @ts-ignore
firefoxGcInterval: 5,
}, () => {
const real = Cypress.getFirefoxGcInterval
const fake = createIntervalGetter(Cypress)
// conditional, so it can pass in non-ff browsers
expect(real()).to.eq(fake()).and.eq(Cypress.isBrowser('firefox') && Cypress.browser.majorVersion < 80 ? 5 : undefined)
})
})
describe('#install', () => {
let MockCypress: any
let commandStartFn: any
let testBeforeRunAsyncFn: any
beforeEach(() => {
MockCypress = {
on: cy.stub().throws(),
emit: cy.stub().throws(),
browser: {
family: 'firefox',
majorVersion: 79,
},
getFirefoxGcInterval: cy.stub().throws(),
backend: cy.stub().throws(),
}
commandStartFn = testBeforeRunAsyncFn = undefined
MockCypress.on.withArgs('command:start').callsFake((_, fn) => {
commandStartFn = fn
})
MockCypress.on.withArgs('test:before:run:async').callsFake((_, fn) => {
testBeforeRunAsyncFn = fn
})
})
const fakeVisit = () => {
commandStartFn({ get: cy.stub().throws().withArgs('name').returns('visit') })
}
const fakeBeforeTestRun = (order) => {
return testBeforeRunAsyncFn({ order }) || Promise.resolve()
}
it('registers no event handlers if not in Firefox', () => {
MockCypress.browser.family = 'chrome'
install(MockCypress)
expect(MockCypress.on).to.not.be.called
})
// @see https://github.com/cypress-io/cypress/issues/8241
it('registers no event handlers if in Firefox >= 80', () => {
MockCypress.browser.majorVersion = 80
install(MockCypress)
expect(MockCypress.on).to.not.be.called
})
it('triggers a forced GC correctly with interval = 1', () => {
MockCypress.getFirefoxGcInterval.returns(1)
const forceGc = MockCypress.backend.withArgs('firefox:force:gc').resolves()
const emitBefore = MockCypress.emit.withArgs('before:firefox:force:gc').returns()
const emitAfter = MockCypress.emit.withArgs('after:firefox:force:gc').returns()
install(MockCypress)
return fakeBeforeTestRun(0).then(() => {
fakeVisit()
return fakeBeforeTestRun(1)
})
.then(() => {
expect(forceGc).to.be.calledOnce
expect(emitBefore).to.be.calledOnce
expect(emitAfter).to.be.calledOnce
})
.then(() => {
return fakeBeforeTestRun(2)
})
.then(() => {
return fakeBeforeTestRun(3)
})
.then(() => {
expect(forceGc).to.be.calledOnce
expect(emitBefore).to.be.calledOnce
expect(emitAfter).to.be.calledOnce
fakeVisit()
return fakeBeforeTestRun(4)
})
.then(() => {
expect(forceGc).to.be.calledTwice
expect(emitBefore).to.be.calledTwice
expect(emitAfter).to.be.calledTwice
})
})
it('triggers a forced GC correctly with interval = 3', () => {
MockCypress.getFirefoxGcInterval.returns(3)
const forceGc = MockCypress.backend.withArgs('firefox:force:gc').resolves()
const emitBefore = MockCypress.emit.withArgs('before:firefox:force:gc').returns()
const emitAfter = MockCypress.emit.withArgs('after:firefox:force:gc').returns()
install(MockCypress)
return fakeBeforeTestRun(0).then(() => {
return fakeBeforeTestRun(1)
})
.then(() => {
expect(forceGc).to.not.be.called
expect(emitBefore).to.not.be.called
expect(emitAfter).to.not.be.called
fakeVisit()
})
.then(() => {
return fakeBeforeTestRun(2)
})
.then(() => {
return fakeBeforeTestRun(3)
})
.then(() => {
expect(forceGc).to.be.calledOnce
expect(emitBefore).to.be.calledOnce
expect(emitAfter).to.be.calledOnce
})
})
it('does not trigger any forced GC with falsy interval', () => {
MockCypress.getFirefoxGcInterval.returns(false)
const forceGc = MockCypress.backend.withArgs('firefox:force:gc').resolves()
const emitBefore = MockCypress.emit.withArgs('before:firefox:force:gc').returns()
const emitAfter = MockCypress.emit.withArgs('after:firefox:force:gc').returns()
install(MockCypress)
return fakeBeforeTestRun(0).then(() => {
return fakeBeforeTestRun(1)
})
.then(() => {
expect(forceGc).to.not.be.called
expect(emitBefore).to.not.be.called
expect(emitAfter).to.not.be.called
fakeVisit()
})
.then(() => {
return fakeBeforeTestRun(2)
})
.then(() => {
expect(forceGc).to.not.be.called
expect(emitBefore).to.not.be.called
expect(emitAfter).to.not.be.called
})
})
})
})

View File

@@ -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

View File

@@ -14,7 +14,6 @@ const $Commands = require('./cypress/commands')
const $Cookies = require('./cypress/cookies')
const $Cy = require('./cypress/cy')
const $Events = require('./cypress/events')
const $FirefoxForcedGc = require('./util/firefox_forced_gc')
const $Keyboard = require('./cy/keyboard')
const $SetterGetter = require('./cypress/setter_getter')
const $Log = require('./cypress/log')
@@ -137,7 +136,6 @@ class $Cypress {
this.originalConfig = _.cloneDeep(config)
this.config = $SetterGetter.create(config)
this.env = $SetterGetter.create(env)
this.getFirefoxGcInterval = $FirefoxForcedGc.createIntervalGetter(this)
this.getTestRetries = function () {
const testRetries = this.config('retries')
@@ -213,8 +211,6 @@ class $Cypress {
this.events.proxyTo(this.cy)
$FirefoxForcedGc.install(this)
$scriptUtils.runScripts(specWindow, scripts)
.catch((error) => {
this.runner.onSpecError('error')({ error })
@@ -230,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)

View File

@@ -1,64 +0,0 @@
import { isNumber, isNull } from 'lodash'
function usingFirefoxWithGcBug (browser: Cypress.Browser) {
// @see https://github.com/cypress-io/cypress/issues/8241
return browser.family === 'firefox' && browser.majorVersion < 80
}
export function createIntervalGetter (Cypress: Cypress.Cypress) {
return () => {
if (!usingFirefoxWithGcBug(Cypress.browser)) {
return undefined
}
const intervals = Cypress.config('firefoxGcInterval')
if (isNumber(intervals) || isNull(intervals)) {
return intervals
}
// @ts-ignore
return intervals[Cypress.config('isInteractive') ? 'openMode' : 'runMode']
}
}
export function install (Cypress: Cypress.Cypress & EventEmitter) {
if (!usingFirefoxWithGcBug(Cypress.browser)) {
return
}
let cyVisitedSinceLastGc = false
let testsSinceLastForcedGc = 0
Cypress.on('command:start', function (cmd) {
if (cmd.get('name') === 'visit') {
cyVisitedSinceLastGc = true
}
})
Cypress.on('test:before:run:async', function (testAttrs) {
const { order } = testAttrs
testsSinceLastForcedGc++
// if this is the first test, or the last test didn't run a cy.visit...
if (order === 0 || !cyVisitedSinceLastGc) {
return
}
const gcInterval = Cypress.getFirefoxGcInterval()
cyVisitedSinceLastGc = false
if (gcInterval && gcInterval > 0 && testsSinceLastForcedGc >= gcInterval) {
testsSinceLastForcedGc = 0
Cypress.emit('before:firefox:force:gc', { gcInterval })
return Cypress.backend('firefox:force:gc').then(() => {
return Cypress.emit('after:firefox:force:gc', { gcInterval })
})
}
return
})
}

View File

@@ -9,7 +9,8 @@ exports['browsers returns the expected list of browsers 1'] = [
"google-chrome",
"chrome",
"google-chrome-stable"
]
],
"minSupportedVersion": 64
},
{
"name": "chromium",
@@ -20,7 +21,8 @@ exports['browsers returns the expected list of browsers 1'] = [
"binary": [
"chromium-browser",
"chromium"
]
],
"minSupportedVersion": 64
},
{
"name": "chrome",
@@ -28,7 +30,8 @@ exports['browsers returns the expected list of browsers 1'] = [
"channel": "beta",
"displayName": "Chrome Beta",
"versionRegex": {},
"binary": "google-chrome-beta"
"binary": "google-chrome-beta",
"minSupportedVersion": 64
},
{
"name": "chrome",
@@ -36,7 +39,8 @@ exports['browsers returns the expected list of browsers 1'] = [
"channel": "canary",
"displayName": "Canary",
"versionRegex": {},
"binary": "google-chrome-canary"
"binary": "google-chrome-canary",
"minSupportedVersion": 64
},
{
"name": "firefox",
@@ -44,7 +48,8 @@ exports['browsers returns the expected list of browsers 1'] = [
"channel": "stable",
"displayName": "Firefox",
"versionRegex": {},
"binary": "firefox"
"binary": "firefox",
"minSupportedVersion": 86
},
{
"name": "firefox",
@@ -55,7 +60,8 @@ exports['browsers returns the expected list of browsers 1'] = [
"binary": [
"firefox-developer-edition",
"firefox"
]
],
"minSupportedVersion": 86
},
{
"name": "firefox",
@@ -66,7 +72,8 @@ exports['browsers returns the expected list of browsers 1'] = [
"binary": [
"firefox-nightly",
"firefox-trunk"
]
],
"minSupportedVersion": 86
},
{
"name": "edge",
@@ -77,7 +84,8 @@ exports['browsers returns the expected list of browsers 1'] = [
"binary": [
"edge",
"microsoft-edge"
]
],
"minSupportedVersion": 79
},
{
"name": "edge",
@@ -85,7 +93,8 @@ exports['browsers returns the expected list of browsers 1'] = [
"channel": "canary",
"displayName": "Edge Canary",
"versionRegex": {},
"binary": "edge-canary"
"binary": "edge-canary",
"minSupportedVersion": 79
},
{
"name": "edge",
@@ -93,7 +102,8 @@ exports['browsers returns the expected list of browsers 1'] = [
"channel": "beta",
"displayName": "Edge Beta",
"versionRegex": {},
"binary": "edge-beta"
"binary": "edge-beta",
"minSupportedVersion": 79
},
{
"name": "edge",
@@ -104,6 +114,7 @@ exports['browsers returns the expected list of browsers 1'] = [
"binary": [
"edge-dev",
"microsoft-edge-dev"
]
],
"minSupportedVersion": 79
}
]

View File

@@ -10,6 +10,7 @@ exports['darwin browser detection detects browsers as expected 1'] = [
"chrome",
"google-chrome-stable"
],
"minSupportedVersion": 64,
"path": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
"version": "someVersion",
"findAppParams": {
@@ -29,6 +30,7 @@ exports['darwin browser detection detects browsers as expected 1'] = [
"chromium-browser",
"chromium"
],
"minSupportedVersion": 64,
"path": "/Applications/Chromium.app/Contents/MacOS/Chromium",
"version": "someVersion",
"findAppParams": {
@@ -45,6 +47,7 @@ exports['darwin browser detection detects browsers as expected 1'] = [
"displayName": "Chrome Beta",
"versionRegex": {},
"binary": "google-chrome-beta",
"minSupportedVersion": 64,
"path": "/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta",
"version": "someVersion",
"findAppParams": {
@@ -61,6 +64,7 @@ exports['darwin browser detection detects browsers as expected 1'] = [
"displayName": "Canary",
"versionRegex": {},
"binary": "google-chrome-canary",
"minSupportedVersion": 64,
"path": "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
"version": "someVersion",
"findAppParams": {
@@ -77,6 +81,7 @@ exports['darwin browser detection detects browsers as expected 1'] = [
"displayName": "Firefox",
"versionRegex": {},
"binary": "firefox",
"minSupportedVersion": 86,
"path": "/Applications/Firefox.app/Contents/MacOS/firefox-bin",
"version": "someVersion",
"findAppParams": {
@@ -96,6 +101,7 @@ exports['darwin browser detection detects browsers as expected 1'] = [
"firefox-developer-edition",
"firefox"
],
"minSupportedVersion": 86,
"path": "/Applications/Firefox Developer Edition.app/Contents/MacOS/firefox-bin",
"version": "someVersion",
"findAppParams": {
@@ -115,6 +121,7 @@ exports['darwin browser detection detects browsers as expected 1'] = [
"firefox-nightly",
"firefox-trunk"
],
"minSupportedVersion": 86,
"path": "/Applications/Firefox Nightly.app/Contents/MacOS/firefox-bin",
"version": "someVersion",
"findAppParams": {
@@ -134,6 +141,7 @@ exports['darwin browser detection detects browsers as expected 1'] = [
"edge",
"microsoft-edge"
],
"minSupportedVersion": 79,
"path": "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
"version": "someVersion",
"findAppParams": {
@@ -150,6 +158,7 @@ exports['darwin browser detection detects browsers as expected 1'] = [
"displayName": "Edge Canary",
"versionRegex": {},
"binary": "edge-canary",
"minSupportedVersion": 79,
"path": "/Applications/Microsoft Edge Canary.app/Contents/MacOS/Microsoft Edge Canary",
"version": "someVersion",
"findAppParams": {
@@ -166,6 +175,7 @@ exports['darwin browser detection detects browsers as expected 1'] = [
"displayName": "Edge Beta",
"versionRegex": {},
"binary": "edge-beta",
"minSupportedVersion": 79,
"path": "/Applications/Microsoft Edge Beta.app/Contents/MacOS/Microsoft Edge Beta",
"version": "someVersion",
"findAppParams": {
@@ -185,6 +195,7 @@ exports['darwin browser detection detects browsers as expected 1'] = [
"edge-dev",
"microsoft-edge-dev"
],
"minSupportedVersion": 79,
"path": "/Applications/Microsoft Edge Dev.app/Contents/MacOS/Microsoft Edge Dev",
"version": "someVersion",
"findAppParams": {

View File

@@ -10,6 +10,7 @@ exports['windows browser detection detects browsers as expected 1'] = [
"chrome",
"google-chrome-stable"
],
"minSupportedVersion": 64,
"path": "C:/Program Files (x86)/Google/Chrome/Application/chrome.exe",
"version": "1.2.3",
"findAppParams": {
@@ -29,6 +30,7 @@ exports['windows browser detection detects browsers as expected 1'] = [
"chromium-browser",
"chromium"
],
"minSupportedVersion": 64,
"path": "C:/Program Files (x86)/Google/chrome-win32/chrome.exe",
"version": "2.3.4",
"findAppParams": {
@@ -45,6 +47,7 @@ exports['windows browser detection detects browsers as expected 1'] = [
"displayName": "Chrome Beta",
"versionRegex": {},
"binary": "google-chrome-beta",
"minSupportedVersion": 64,
"path": "C:/Program Files (x86)/Google/Chrome Beta/Application/chrome.exe",
"version": "6.7.8",
"findAppParams": {
@@ -61,6 +64,7 @@ exports['windows browser detection detects browsers as expected 1'] = [
"displayName": "Canary",
"versionRegex": {},
"binary": "google-chrome-canary",
"minSupportedVersion": 64,
"path": "C:/Users/flotwig/AppData/Local/Google/Chrome SxS/Application/chrome.exe",
"version": "3.4.5",
"findAppParams": {
@@ -77,6 +81,7 @@ exports['windows browser detection detects browsers as expected 1'] = [
"displayName": "Firefox",
"versionRegex": {},
"binary": "firefox",
"minSupportedVersion": 86,
"path": "C:/Program Files/Mozilla Firefox/firefox.exe",
"version": "72",
"findAppParams": {
@@ -96,6 +101,7 @@ exports['windows browser detection detects browsers as expected 1'] = [
"firefox-developer-edition",
"firefox"
],
"minSupportedVersion": 86,
"path": "C:/Program Files (x86)/Firefox Developer Edition/firefox.exe",
"version": "73",
"findAppParams": {
@@ -115,6 +121,7 @@ exports['windows browser detection detects browsers as expected 1'] = [
"firefox-nightly",
"firefox-trunk"
],
"minSupportedVersion": 86,
"path": "C:/Program Files/Firefox Nightly/firefox.exe",
"version": "74",
"findAppParams": {
@@ -134,6 +141,7 @@ exports['windows browser detection detects browsers as expected 1'] = [
"edge",
"microsoft-edge"
],
"minSupportedVersion": 79,
"path": "C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe",
"version": "11",
"findAppParams": {
@@ -150,6 +158,7 @@ exports['windows browser detection detects browsers as expected 1'] = [
"displayName": "Edge Canary",
"versionRegex": {},
"binary": "edge-canary",
"minSupportedVersion": 79,
"path": "C:/Users/flotwig/AppData/Local/Microsoft/Edge SxS/Application/msedge.exe",
"version": "14",
"findAppParams": {
@@ -166,6 +175,7 @@ exports['windows browser detection detects browsers as expected 1'] = [
"displayName": "Edge Beta",
"versionRegex": {},
"binary": "edge-beta",
"minSupportedVersion": 79,
"path": "C:/Program Files (x86)/Microsoft/Edge Beta/Application/msedge.exe",
"version": "12",
"findAppParams": {
@@ -185,6 +195,7 @@ exports['windows browser detection detects browsers as expected 1'] = [
"edge-dev",
"microsoft-edge-dev"
],
"minSupportedVersion": 79,
"path": "C:/Program Files (x86)/Microsoft/Edge Dev/Application/msedge.exe",
"version": "13",
"findAppParams": {
@@ -216,6 +227,7 @@ exports['windows browser detection detects local Firefox installs 1'] = [
"displayName": "Firefox",
"versionRegex": {},
"binary": "firefox",
"minSupportedVersion": 86,
"path": "C:/Users/flotwig/AppData/Local/Mozilla Firefox/firefox.exe",
"version": "100",
"findAppParams": {
@@ -235,6 +247,7 @@ exports['windows browser detection detects local Firefox installs 1'] = [
"firefox-developer-edition",
"firefox"
],
"minSupportedVersion": 86,
"path": "C:/Users/flotwig/AppData/Local/Firefox Developer Edition/firefox.exe",
"version": "300",
"findAppParams": {
@@ -254,6 +267,7 @@ exports['windows browser detection detects local Firefox installs 1'] = [
"firefox-nightly",
"firefox-trunk"
],
"minSupportedVersion": 86,
"path": "C:/Users/flotwig/AppData/Local/Firefox Nightly/firefox.exe",
"version": "200",
"findAppParams": {

View File

@@ -2,6 +2,13 @@ import { log } from './log'
import * as cp from 'child_process'
import { Browser, FoundBrowser } from './types'
// Chrome started exposing CDP 1.3 in 64
const MIN_CHROME_VERSION = 64
// Firefox started exposing CDP in 86
const MIN_FIREFOX_VERSION = 86
// Edge switched to Blink in 79
const MIN_EDGE_VERSION = 79
/** list of the browsers we can detect and use by default */
export const browsers: Browser[] = [
{
@@ -11,6 +18,7 @@ export const browsers: Browser[] = [
displayName: 'Chrome',
versionRegex: /Google Chrome (\S+)/m,
binary: ['google-chrome', 'chrome', 'google-chrome-stable'],
minSupportedVersion: MIN_CHROME_VERSION,
},
{
name: 'chromium',
@@ -20,6 +28,7 @@ export const browsers: Browser[] = [
displayName: 'Chromium',
versionRegex: /Chromium (\S+)/m,
binary: ['chromium-browser', 'chromium'],
minSupportedVersion: MIN_CHROME_VERSION,
},
{
name: 'chrome',
@@ -28,6 +37,7 @@ export const browsers: Browser[] = [
displayName: 'Chrome Beta',
versionRegex: /Google Chrome (\S+) beta/m,
binary: 'google-chrome-beta',
minSupportedVersion: MIN_CHROME_VERSION,
},
{
name: 'chrome',
@@ -36,6 +46,7 @@ export const browsers: Browser[] = [
displayName: 'Canary',
versionRegex: /Google Chrome Canary (\S+)/m,
binary: 'google-chrome-canary',
minSupportedVersion: MIN_CHROME_VERSION,
},
{
name: 'firefox',
@@ -45,6 +56,7 @@ export const browsers: Browser[] = [
// Mozilla Firefox 70.0.1
versionRegex: /^Mozilla Firefox ([^\sab]+)$/m,
binary: 'firefox',
minSupportedVersion: MIN_FIREFOX_VERSION,
},
{
name: 'firefox',
@@ -55,6 +67,7 @@ export const browsers: Browser[] = [
versionRegex: /^Mozilla Firefox (\S+b\S*)$/m,
// ubuntu PPAs install it as firefox
binary: ['firefox-developer-edition', 'firefox'],
minSupportedVersion: MIN_FIREFOX_VERSION,
},
{
name: 'firefox',
@@ -65,6 +78,7 @@ export const browsers: Browser[] = [
versionRegex: /^Mozilla Firefox (\S+a\S*)$/m,
// ubuntu PPAs install it as firefox-trunk
binary: ['firefox-nightly', 'firefox-trunk'],
minSupportedVersion: MIN_FIREFOX_VERSION,
},
{
name: 'edge',
@@ -73,6 +87,7 @@ export const browsers: Browser[] = [
displayName: 'Edge',
versionRegex: /Microsoft Edge (\S+)/m,
binary: ['edge', 'microsoft-edge'],
minSupportedVersion: MIN_EDGE_VERSION,
},
{
name: 'edge',
@@ -81,6 +96,7 @@ export const browsers: Browser[] = [
displayName: 'Edge Canary',
versionRegex: /Microsoft Edge Canary (\S+)/m,
binary: 'edge-canary',
minSupportedVersion: MIN_EDGE_VERSION,
},
{
name: 'edge',
@@ -89,6 +105,7 @@ export const browsers: Browser[] = [
displayName: 'Edge Beta',
versionRegex: /Microsoft Edge Beta (\S+)/m,
binary: 'edge-beta',
minSupportedVersion: MIN_EDGE_VERSION,
},
{
name: 'edge',
@@ -97,6 +114,7 @@ export const browsers: Browser[] = [
displayName: 'Edge Dev',
versionRegex: /Microsoft Edge Dev (\S+)/m,
binary: ['edge-dev', 'microsoft-edge-dev'],
minSupportedVersion: MIN_EDGE_VERSION,
},
]

View File

@@ -16,26 +16,32 @@ import {
} from './types'
import * as windowsHelper from './windows'
type HasVersion = {
version?: string
majorVersion?: string | number
type HasVersion = Partial<FoundBrowser> & {
version: string
name: string
}
export const setMajorVersion = <T extends HasVersion>(browser: T): T => {
let majorVersion = browser.majorVersion
const majorVersion = parseInt(browser.version.split('.')[0]) || browser.version
if (browser.version) {
majorVersion = parseInt(browser.version.split('.')[0]) || browser.version
log(
'browser %s version %s major version %s',
browser.name,
browser.version,
majorVersion,
)
const unsupportedVersion = browser.minSupportedVersion && majorVersion < browser.minSupportedVersion
log(
'browser %s version %s major version %s',
browser.name,
browser.version,
majorVersion,
unsupportedVersion,
)
const foundBrowser = extend({}, browser, { majorVersion })
if (unsupportedVersion) {
foundBrowser.unsupportedVersion = true
foundBrowser.warning = `Cypress does not support running ${browser.displayName} version ${majorVersion}. To use ${browser.displayName} with Cypress, install a version of ${browser.displayName} newer than or equal to ${browser.minSupportedVersion}.`
}
return extend({}, browser, { majorVersion })
return foundBrowser
}
type PlatformHelper = {
@@ -102,6 +108,8 @@ function checkOneBrowser (browser: Browser): Promise<boolean | FoundBrowser> {
'custom',
'warning',
'info',
'minSupportedVersion',
'unsupportedVersion',
])
const logBrowser = (props: any) => {
@@ -125,21 +133,9 @@ function checkOneBrowser (browser: Browser): Promise<boolean | FoundBrowser> {
.then(pickBrowserProps)
.then(tap(logBrowser))
.then((browser) => setMajorVersion(browser))
.then(maybeSetFirefoxWarning)
.catch(failed)
}
export const firefoxGcWarning = 'This version of Firefox has a bug that causes excessive memory consumption and will cause your tests to run slowly. It is recommended to upgrade to Firefox 80 or newer. [Learn more.](https://docs.cypress.io/guides/references/configuration.html#firefoxGcInterval)'
// @see https://github.com/cypress-io/cypress/issues/8241
const maybeSetFirefoxWarning = (browser: FoundBrowser) => {
if (browser.family === 'firefox' && Number(browser.majorVersion) < 80) {
browser.warning = firefoxGcWarning
}
return browser
}
/** returns list of detected browsers */
export const detect = (goalBrowsers?: Browser[]): Bluebird<FoundBrowser[]> => {
// we can detect same browser under different aliases
@@ -192,18 +188,16 @@ export const detectByPath = (
const setCustomBrowserData = (browser: Browser, path: string, versionStr: string): FoundBrowser => {
const version = helper.getVersionNumber(versionStr, browser)
let parsedBrowser = {
let parsedBrowser = extend({}, browser, {
name: browser.name,
displayName: `Custom ${browser.displayName}`,
info: `Loaded from ${path}`,
custom: true,
path,
version,
}
})
parsedBrowser = setMajorVersion(parsedBrowser)
return extend({}, browser, parsedBrowser)
return setMajorVersion(parsedBrowser)
}
const pathData = helper.getPathData(path)
@@ -226,7 +220,6 @@ export const detectByPath = (
return setCustomBrowserData(browser, pathData.path, version)
})
.then(maybeSetFirefoxWarning)
.catch((err: NotDetectedAtPathError) => {
if (err.notDetectedAtPath) {
throw err

View File

@@ -43,6 +43,8 @@ export type Browser = {
warning?: string
/** optional info that will be shown in the GUI */
info?: string
/** if set, the majorVersion must be >= this to be run in Cypress */
minSupportedVersion?: number
}
/**
@@ -54,6 +56,7 @@ export type FoundBrowser = Omit<Browser, 'versionRegex' | 'binary'> & {
majorVersion?: string
/** is this a user-supplied browser? */
custom?: boolean
unsupportedVersion?: boolean
}
/**

View File

@@ -1,5 +1,5 @@
require('../spec_helper')
import { firefoxGcWarning, detect, detectByPath, setMajorVersion } from '../../lib/detect'
import { detect, detectByPath, setMajorVersion } from '../../lib/detect'
import { goalBrowsers } from '../fixtures'
import { expect } from 'chai'
import { utils } from '../../lib/utils'
@@ -62,6 +62,21 @@ describe('browser detection', () => {
// @ts-ignore
expect(res.majorVersion).to.equal(foundBrowser.version)
})
it('creates warning when version is unsupported', () => {
const foundBrowser = {
displayName: 'TestBro',
name: 'test browser',
version: '9000.1',
minSupportedVersion: 9001,
}
const res = setMajorVersion(foundBrowser)
// @ts-ignore
expect(res.warning).to.contain('does not support running TestBro version 9000')
.and.contain('TestBro newer than or equal to 9001')
})
})
context('#detectByPath', () => {
@@ -143,18 +158,14 @@ describe('browser detection', () => {
})
})
// @see https://github.com/cypress-io/cypress/issues/8241
it('adds warnings to Firefox versions less than 80', async () => {
it('creates warning when version is unsupported', async () => {
execa.withArgs('/good-firefox', ['--version'])
.resolves({ stdout: 'Mozilla Firefox 80.0' })
.resolves({ stdout: 'Mozilla Firefox 85.0' })
execa.withArgs('/bad-firefox', ['--version'])
.resolves({ stdout: 'Mozilla Firefox 79.1' })
const foundBrowser = await detectByPath('/good-firefox')
expect(await detectByPath('/good-firefox')).to.not.have.property('warning')
expect(await detectByPath('/bad-firefox')).to.include({
warning: firefoxGcWarning,
})
expect(foundBrowser.warning).to.contain('does not support running Custom Firefox version 85')
.and.contain('Firefox newer than or equal to 86')
})
})
})

View File

@@ -4,8 +4,7 @@ import _ from 'lodash'
import * as linuxHelper from '../../lib/linux'
import 'chai-as-promised'
import { log } from '../log'
import { detect, firefoxGcWarning } from '../../lib/detect'
import { browsers } from '../../lib/browsers'
import { detect } from '../../lib/detect'
import { goalBrowsers } from '../fixtures'
import { expect } from 'chai'
import { utils } from '../../lib/utils'
@@ -49,7 +48,7 @@ describe('linux browser detection', () => {
// https://github.com/cypress-io/cypress/pull/7039
it('sets profilePath on snapcraft chromium', () => {
execa.withArgs('chromium', ['--version'])
.resolves({ stdout: 'Chromium 1.2.3 snap' })
.resolves({ stdout: 'Chromium 64.2.3 snap' })
sinon.stub(os, 'platform').returns('linux')
sinon.stub(os, 'homedir').returns('/home/foo')
@@ -60,10 +59,11 @@ describe('linux browser detection', () => {
name: 'chromium',
family: 'chromium',
displayName: 'Chromium',
majorVersion: 1,
majorVersion: 64,
minSupportedVersion: 64,
path: 'chromium',
profilePath: '/home/foo/snap/chromium/current',
version: '1.2.3',
version: '64.2.3',
})
}
@@ -93,18 +93,6 @@ describe('linux browser detection', () => {
return linuxHelper.detect(goal).then(checkBrowser)
})
// @see https://github.com/cypress-io/cypress/issues/8241
it('adds warnings to Firefox versions less than 80', async () => {
const goalFirefox = _.find(browsers, { binary: 'firefox' })
sinon.stub(os, 'platform').withArgs().returns('linux')
execa.withArgs('firefox', ['--version']).resolves({ stdout: 'Mozilla Firefox 79.1' })
expect((await detect([goalFirefox]))[0]).to.include({
warning: firefoxGcWarning,
})
})
// despite using detect(), this test is in linux/spec instead of detect_spec because it is
// testing side effects that occur within the Linux-specific detect function
// https://github.com/cypress-io/cypress/issues/1400

View File

@@ -1,119 +0,0 @@
import { EventEmitter } from 'events'
import { RootRunnable } from '../../src/runnables/runnables-store'
describe('forced gc', () => {
let runner: EventEmitter
let runnables: RootRunnable
beforeEach(() => {
cy.fixture('runnables').then((_runnables) => {
runnables = _runnables
})
runner = new EventEmitter()
cy.visit('/').then((win) => {
win.render({
runner,
spec: {
name: 'foo',
absolute: '/foo/bar',
relative: 'foo/bar',
},
})
})
cy.get('.reporter').then(() => {
runner.emit('runnables:ready', runnables)
runner.emit('reporter:start', {})
})
})
it('does not display the warning when interval is undefined', () => {
cy.get('.forced-gc-warning').should('not.exist')
})
describe('when interval is null or a number', () => {
beforeEach(() => {
runner.emit('before:firefox:force:gc', { gcInterval: null })
})
it('expands on click', () => {
cy.contains('GC Interval').click()
cy.contains('Garbage Collection Interval').should('be.visible')
})
it('collapses on a second click', () => {
cy.contains('GC Interval').click().click()
cy.contains('Garbage Collection Interval').should('not.be.visible')
})
it('collapses on a clicking X', () => {
cy.contains('GC Interval').click()
cy.get('.forced-gc-warning .fa-times').click()
cy.contains('Garbage Collection Interval').should('not.be.visible')
})
it('opens links externally', () => {
cy.spy(runner, 'emit')
cy.contains('GC Interval').click()
cy.get('.forced-gc-warning a').each(($link) => {
cy.wrap($link).click()
cy.wrap(runner.emit).should('be.calledWith', 'external:open', $link.attr('href'))
})
})
})
describe('when interval is null', () => {
beforeEach(() => {
runner.emit('before:firefox:force:gc', { gcInterval: null })
})
it('displays the warning with (disabled)', () => {
cy.get('.forced-gc-warning').should('be.visible')
cy.contains('GC Interval: disabled')
})
it('displays the full message with (disabled)', () => {
cy.contains('GC Interval').click()
cy.contains('Garbage Collection Interval: (disabled)').should('be.visible')
})
})
describe('when interval is 0', () => {
beforeEach(() => {
runner.emit('before:firefox:force:gc', { gcInterval: 0 })
})
it('displays the warning with (disabled)', () => {
cy.get('.forced-gc-warning').should('be.visible')
cy.contains('GC Interval: disabled')
})
it('displays the full message with (disabled)', () => {
cy.contains('GC Interval').click()
cy.contains('Garbage Collection Interval: (disabled)').should('be.visible')
})
})
describe('when interval is greater than 0', () => {
beforeEach(() => {
runner.emit('before:firefox:force:gc', { gcInterval: 15 })
})
it('displays the warning with duration and running message', () => {
cy.get('.forced-gc-warning').should('be.visible')
cy.contains('GC Duration: 0.00')
cy.contains('Running GC...')
// ensure the page is loaded before taking snapshot
cy.contains('test 4').should('be.visible')
cy.percySnapshot()
})
it('displays the full message with (enabled)', () => {
cy.contains('GC Duration').click()
cy.contains('Garbage Collection Interval: (enabled)').should('be.visible')
})
})
})

View File

@@ -141,28 +141,6 @@ describe('app state', () => {
})
})
context('#setForcingGc', () => {
it('sets forcingGc', () => {
const instance = new AppState()
instance.setForcingGc(false)
expect(instance.forcingGc).to.be.false
instance.setForcingGc(true)
expect(instance.forcingGc).to.be.true
})
})
context('#setFirefoxGcInterval', () => {
it('sets forcingGc', () => {
const instance = new AppState()
instance.setFirefoxGcInterval(111)
expect(instance.firefoxGcInterval).to.eq(111)
instance.setFirefoxGcInterval(222)
expect(instance.firefoxGcInterval).to.eq(222)
})
})
context('#setStudioActive', () => {
it('sets studioActive', () => {
const instance = new AppState()

View File

@@ -25,8 +25,6 @@ type AppStateStub = AppState & {
resume: SinonSpy
end: SinonSpy
temporarilySetAutoScrolling: SinonSpy
setFirefoxGcInterval: SinonSpy
setForcingGc: SinonSpy
setStudioActive: SinonSpy
stop: SinonSpy
}
@@ -39,8 +37,6 @@ const appStateStub = () => {
resume: sinon.spy(),
end: sinon.spy(),
temporarilySetAutoScrolling: sinon.spy(),
setFirefoxGcInterval: sinon.spy(),
setForcingGc: sinon.spy(),
setStudioActive: sinon.spy(),
stop: sinon.spy(),
} as AppStateStub
@@ -194,28 +190,11 @@ describe('events', () => {
expect(appState.temporarilySetAutoScrolling).to.have.been.calledWith(false)
})
it('sets firefoxGcInterval on the app state on reporter:start', () => {
runner.on.withArgs('reporter:start').callArgWith(1, { firefoxGcInterval: 111 })
expect(appState.setFirefoxGcInterval).to.have.been.calledWith(111)
})
it('sets studioActive on the app state on reporter:start', () => {
runner.on.withArgs('reporter:start').callArgWith(1, { studioActive: true })
expect(appState.setStudioActive).to.have.been.calledWith(true)
})
it('sets forcingGc & firefoxGcInterval on the app state on before:firefox:force:gc', () => {
runner.on.withArgs('before:firefox:force:gc').callArgWith(1, { gcInterval: 222 })
expect(appState.setFirefoxGcInterval).to.have.been.calledWith(222)
expect(appState.setForcingGc).to.have.been.calledWith(true)
})
it('sets forcingGc & firefoxGcInterval on the app state on after:firefox:force:gc', () => {
runner.on.withArgs('after:firefox:force:gc').callArgWith(1, { gcInterval: 333 })
expect(appState.setFirefoxGcInterval).to.have.been.calledWith(333)
expect(appState.setForcingGc).to.have.been.calledWith(false)
})
it('sets initial scrollTop on the scroller on reporter:start', () => {
runner.on.withArgs('reporter:start').callArgWith(1, { scrollTop: 123 })
expect(runnablesStore.setInitialScrollTop).to.have.been.calledWith(123)

View File

@@ -2,8 +2,6 @@ import _ from 'lodash'
import { observable } from 'mobx'
interface DefaultAppState {
forcingGc: boolean
firefoxGcInterval: number | null | undefined
isPaused: boolean
isRunning: boolean
nextCommandName: string | null | undefined
@@ -12,8 +10,6 @@ interface DefaultAppState {
}
const defaults: DefaultAppState = {
forcingGc: false,
firefoxGcInterval: undefined,
isPaused: false,
isRunning: false,
nextCommandName: null,
@@ -23,12 +19,10 @@ const defaults: DefaultAppState = {
class AppState {
@observable autoScrollingEnabled = true
@observable forcingGc = defaults.forcingGc
@observable isPaused = defaults.isPaused
@observable isRunning = defaults.isRunning
@observable nextCommandName = defaults.nextCommandName
@observable pinnedSnapshotId = defaults.pinnedSnapshotId
@observable firefoxGcInterval = defaults.firefoxGcInterval
@observable studioActive = defaults.studioActive
isStopped = false;
@@ -59,14 +53,6 @@ class AppState {
this._resetAutoScrolling()
}
setForcingGc (forcingGc: boolean) {
this.forcingGc = forcingGc
}
setFirefoxGcInterval (firefoxGcInterval: DefaultAppState['firefoxGcInterval']) {
this.firefoxGcInterval = firefoxGcInterval
}
temporarilySetAutoScrolling (isEnabled?: boolean | null) {
if (isEnabled != null) {
this.autoScrollingEnabled = isEnabled

View File

@@ -1,7 +1,5 @@
body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img,a img{border:none;}address,caption,cite,code,dfn,em,strong,th,var,optgroup{font-style:inherit;font-weight:inherit;}del,ins{text-decoration:none;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym{border:0;font-variant:normal;}sup{vertical-align:baseline;}sub{vertical-align:baseline;}legend{color:#000;}
@import './forced-gc-warning.scss';
.reporter {
background-color: #F6F6F6;
bottom: 0;

View File

@@ -34,7 +34,6 @@ export interface Events {
interface StartInfo extends StatsStoreStartInfo {
autoScrollingEnabled: boolean
firefoxGcInterval: number
scrollTop: number
studioActive: boolean
}
@@ -91,7 +90,6 @@ const events: Events = {
runner.on('reporter:start', action('start', (startInfo: StartInfo) => {
appState.temporarilySetAutoScrolling(startInfo.autoScrollingEnabled)
appState.setFirefoxGcInterval(startInfo.firefoxGcInterval)
runnablesStore.setInitialScrollTop(startInfo.scrollTop)
appState.setStudioActive(startInfo.studioActive)
if (runnablesStore.hasTests) {
@@ -135,16 +133,6 @@ const events: Events = {
appState.pinnedSnapshotId = null
}))
runner.on('before:firefox:force:gc', action('before:firefox:force:gc', ({ gcInterval }) => {
appState.setForcingGc(true)
appState.setFirefoxGcInterval(gcInterval)
}))
runner.on('after:firefox:force:gc', action('after:firefox:force:gc', ({ gcInterval }) => {
appState.setForcingGc(false)
appState.setFirefoxGcInterval(gcInterval)
}))
localBus.on('resume', action('resume', () => {
appState.resume()
statsStore.resume()

View File

@@ -1,79 +0,0 @@
.forced-gc-warning {
background-color: $yellow-lightest;
padding: 1em;
bottom: 0;
left: 0;
width: 100%;
box-shadow: 0 -1px 3px #ccc;
border-right: 1px solid #ddd;
p {
line-height: 1.5 !important;
}
a.code-link {
&>code {
color: #1079c3;
background: #f7f7f7;
}
&:hover, &:focus, &:active {
text-decoration: none;
&>code {
background-color: #e5f1f6;
text-decoration: none;
}
}
}
code {
background-color: #f9f2f4;
color: #c7254e;
border-radius: 2px;
letter-spacing: 0.5px;
padding: 2px 4px;
}
>.gc-expando {
display: none;
&.expanded {
display: block;
padding-bottom: 1em;
.fa-times {
float: right;
font-size: 16px;
}
}
}
.gc-status {
color: #888;
border-bottom: 1px dotted;
}
>.gc-status-bar {
.status-text {
float: right;
}
>.total-time {
>i {
margin-right: .3em;
}
}
}
.clickable {
cursor: pointer;
opacity: .7;
&:hover {
opacity: 1;
}
}
}

View File

@@ -1,159 +0,0 @@
import { isUndefined, round } from 'lodash'
import { observer } from 'mobx-react'
import React from 'react'
import { AppState } from './app-state'
import { Events } from './events'
export interface Props {
appState: Pick<AppState, 'forcingGc' | 'firefoxGcInterval'>
events: Events
}
interface State {
expanded: boolean
}
@observer
class ForcedGcWarning extends React.Component<Props> {
gcStartMs: number | null = null
gcTotalMs: number = 0
persisted = false
state: State
constructor (props: Props) {
super(props)
this.state = {
expanded: false,
}
}
_toggleExpando () {
this.setState({ expanded: !this.state.expanded })
}
_updateGcTimer () {
const { forcingGc } = this.props.appState
if (!forcingGc) {
if (this.gcStartMs) {
const duration = Date.now() - this.gcStartMs
this.gcStartMs = null
this.gcTotalMs += duration
}
}
if (forcingGc && !this.gcStartMs) {
this.gcStartMs = Date.now()
}
}
_renderDisabled () {
return (
<div className='forced-gc-warning'>
<div className={`gc-expando ${this.state.expanded ? 'expanded' : ''}`}>
<p>
<strong>
Garbage Collection Interval: (disabled)
</strong>
<i className='fas fa-times clickable' onClick={() => this._toggleExpando()}></i>
</p>
<div>
<p>
Cypress can force Firefox to run Garbage Collection (GC) between tests by enabling: <a className='code-link' onClick={this._handleLink} href='https://on.cypress.io/firefox-gc-interval'><code>firefoxGcInterval</code></a>
</p>
<p>
By default, <a className='code-link' onClick={this._handleLink} href='https://on.cypress.io/firefox-gc-interval'><code>firefoxGcInterval</code></a> is only enabled in <strong>run mode</strong>.
</p>
<p>
Running GC prevents Firefox from running out of memory during longer test runs.
</p>
<p>
<a className='code-link' onClick={this._handleLink} href='https://on.cypress.io/firefox-gc-interval'>Learn more</a>.
</p>
</div>
</div>
<div className='gc-status-bar clickable gc-not-running' onClick={() => this._toggleExpando()}>
<span className='total-time'>
<i className='fas fa-ws fa-info-circle'></i>
GC Interval: <span className='gc-status'>disabled</span>
</span>
</div>
</div>
)
}
_handleLink = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
if (!e.currentTarget || !e.currentTarget.href) {
return
}
e.preventDefault()
this.props.events.emit('external:open', e.currentTarget.href)
}
_renderForcedGcWarning () {
const { forcingGc } = this.props.appState
return (
<div className='forced-gc-warning'>
<div className={`gc-expando ${this.state.expanded ? 'expanded' : ''}`}>
<div>
<p>
<strong>
Garbage Collection Interval: (enabled)
</strong>
<i className='fas fa-times clickable' onClick={() => this._toggleExpando()}></i>
</p>
</div>
<div>
<p>
Cypress will force Firefox to run Garbage Collection (GC) between tests based on the value of: <a className='code-link' onClick={this._handleLink} href='https://on.cypress.io/firefox-gc-interval'><code>firefoxGcInterval</code></a>
</p>
<p>
Running GC prevents Firefox from running out of memory during longer test runs.
</p>
<p>
Running GC is an expensive operation that can take up to a few seconds to complete. During this time Firefox may "freeze" and become unresponsive to user input.
</p>
<p>
To improve performance, you can try setting <a className='code-link' onClick={this._handleLink} href='https://on.cypress.io/firefox-gc-interval'><code>firefoxGcInterval</code></a> to a higher value, which will result in running GC less frequently.
</p>
<p>
<a className='code-link' onClick={this._handleLink} href='https://on.cypress.io/firefox-gc-interval'>Learn more</a>.
</p>
</div>
</div>
<div className={`gc-status-bar clickable ${forcingGc ? 'gc-running' : 'gc-not-running'}`} onClick={() => this._toggleExpando()}>
<span className='total-time' title='Total time spent running GC throughout this run'>
<i className='fas fa-ws fa-info-circle'></i>
GC Duration: <span className='gc-status'>{round(this.gcTotalMs / 1000, 2).toFixed(2)}</span>
</span>
{forcingGc && <span className='status-text'>
<i className='fas fa-spinner fa-spin'></i> Running GC...
</span>}
</div>
</div>
)
}
render () {
const { firefoxGcInterval } = this.props.appState
if (isUndefined(firefoxGcInterval)) {
// we're either still loading or it is disabled
return null
}
if (firefoxGcInterval === 0 || firefoxGcInterval == null) {
return this._renderDisabled()
}
this._updateGcTimer()
return this._renderForcedGcWarning()
}
}
export default ForcedGcWarning

View File

@@ -11,7 +11,6 @@ import EQ from 'css-element-queries/src/ElementQueries'
import { RunnablesErrorModel } from './runnables/runnable-error'
import appState, { AppState } from './lib/app-state'
import events, { Runner, Events } from './lib/events'
import ForcedGcWarning from './lib/forced-gc-warning'
import runnablesStore, { RunnablesStore } from './runnables/runnables-store'
import scroller, { Scroller } from './lib/scroller'
import statsStore, { StatsStore } from './header/stats-store'
@@ -84,7 +83,6 @@ class Reporter extends Component<SingleReporterProps | MultiReporterProps> {
runnablesStore,
scroller,
error,
events,
statsStore,
experimentalStudioEnabled,
renderReporterHeader = (props: ReporterHeaderProps) => <Header {...props}/>,
@@ -115,11 +113,6 @@ class Reporter extends Component<SingleReporterProps | MultiReporterProps> {
spec={spec}
/>
))}
<ForcedGcWarning
appState={appState}
events={events}
/>
</div>
)
}

View File

@@ -39,7 +39,6 @@ export const ReporterContainer = namedObserver('ReporterContainer',
specRunId={props.state.specRunId}
allSpecs={props.state.multiSpecs}
error={errorMessages.reporterError(props.state.scriptError, props.state.spec.relative)}
firefoxGcInterval={props.config.firefoxGcInterval}
resetStatsOnSpecChange={props.state.runMode === 'single'}
renderReporterHeader={renderReporterHeader}
experimentalStudioEnabled={false}

View File

@@ -21,7 +21,7 @@ ws.on('connect', () => {
ws.emit('runner:connected')
})
const driverToReporterEvents = 'paused before:firefox:force:gc after:firefox:force:gc'.split(' ')
const driverToReporterEvents = 'paused'.split(' ')
const driverToLocalAndReporterEvents = 'run:start run:end'.split(' ')
const driverToSocketEvents = 'backend:request automation:request mocha recorder:frame'.split(' ')
const driverTestEvents = 'test:before:run:async test:after:run'.split(' ')
@@ -497,7 +497,6 @@ export const eventManager = {
})
reporterBus.emit('reporter:start', {
firefoxGcInterval: Cypress.getFirefoxGcInterval(),
startTime: Cypress.runner.getStartTime(),
numPassed: state.passed,
numFailed: state.failed,

View File

@@ -1,7 +1,7 @@
const helpers = require('../support/helpers')
const { shouldHaveTestResults, getRunState, cleanseRunStateMap } = helpers
const { runIsolatedCypress, snapshotMochaEvents, getAutCypress } = helpers.createCypress({ config: { retries: 2, isTextTerminal: true, firefoxGcInterval: null } })
const { runIsolatedCypress, snapshotMochaEvents, getAutCypress } = helpers.createCypress({ config: { retries: 2, isTextTerminal: true } })
const { sinon } = Cypress
const match = Cypress.sinon.match

View File

@@ -87,7 +87,6 @@ class App extends Component {
spec={spec}
autoScrollingEnabled={this.props.config.state.autoScrollingEnabled}
error={errorMessages.reporterError(this.props.state.scriptError, spec.relative)}
firefoxGcInterval={this.props.config.firefoxGcInterval}
experimentalStudioEnabled={this.props.config.experimentalStudio}
/>}
</div>

View File

@@ -87,15 +87,6 @@ describe('<App />', () => {
expect(component.find(Reporter)).to.have.prop('autoScrollingEnabled', true)
})
it('renders the <Reporter /> with the firefoxGcInterval flag', () => {
const props = createProps()
props.config.firefoxGcInterval = 111
const component = shallowRender(<App {...props} />)
expect(component.find(Reporter)).to.have.prop('firefoxGcInterval', 111)
})
it('renders the runner container with `left` set as the width of the reporter', () => {
const props = createProps()

View File

@@ -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

View File

@@ -426,9 +426,13 @@ 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
args.push('--force-device-scale-factor=1')
}
// force ipv4

View File

@@ -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)\
`,
},
})
},
}

View File

@@ -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 {

View File

@@ -107,13 +107,6 @@ export const options = [
defaultValue: '',
validation: v.isString,
isFolder: true,
}, {
name: 'firefoxGcInterval',
defaultValue: {
runMode: 1,
openMode: null,
},
validation: v.isValidFirefoxGcInterval,
}, {
name: 'fixturesFolder',
defaultValue: 'cypress/fixtures',
@@ -331,5 +324,9 @@ export const breakingOptions = [
name: 'experimentalShadowDomSupport',
errorKey: 'EXPERIMENTAL_SHADOW_DOM_REMOVED',
isWarning: true,
}, {
name: 'firefoxGcInterval',
errorKey: 'FIREFOX_GC_INTERVAL_REMOVED',
isWarning: true,
},
]

View File

@@ -940,6 +940,13 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) {
return stripIndent`\
The \`experimentalRunEvents\` configuration option was removed in Cypress version \`6.7.0\`. It is no longer necessary when listening to run events in the plugins file.
You can safely remove this option from your config.`
case 'FIREFOX_GC_INTERVAL_REMOVED':
return stripIndent`\
The \`firefoxGcInterval\` configuration option was removed in Cypress version \`8.0.0\`. It was introduced to work around a bug in Firefox 79 and below.
Since Cypress no longer supports Firefox 85 and below in Cypress 8, this option was removed.
You can safely remove this option from your config.`
case 'INCOMPATIBLE_PLUGIN_RETRIES':
return stripIndent`\
@@ -980,6 +987,8 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) {
https://on.cypress.io/component-testing
`
case 'UNSUPPORTED_BROWSER_VERSION':
return arg1
default:
}
}

View File

@@ -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
@@ -1552,7 +1547,7 @@ module.exports = {
trashAssets(config),
])
.spread((sys = {}, browser = {}, specs = []) => {
// return only what is return to the specPattern
// return only what is return to the specPattern
if (specPattern) {
specPattern = specsUtil.getPatternRelativeToProjectRoot(specPattern, projectRoot)
}
@@ -1567,6 +1562,10 @@ module.exports = {
}
}
if (browser.unsupportedVersion && browser.warning) {
errors.throw('UNSUPPORTED_BROWSER_VERSION', browser.warning)
}
if (browser.family === 'chromium') {
chromePolicyCheck.run(onWarning)
}

View File

@@ -38,8 +38,8 @@ import { checkSupportFile } from './project_utils'
// and are required when creating a project.
// TODO: Figure out how to type this better.
type ReceivedCypressOptions =
Partial<Pick<Cypress.RuntimeConfigOptions, 'hosts' | 'projectName' | 'clientRoute' | 'devServerPublicPathRoute' | 'supportFolder' | 'namespace' | 'report' | 'socketIoCookie' | 'configFile' | 'isTextTerminal' | 'isNewProject' | 'proxyUrl' | 'browsers'>>
& Partial<Pick<Cypress.ResolvedConfigOptions, 'experimentalSourceRewriting' | 'fixturesFolder' | 'reporter' | 'reporterOptions' | 'screenshotsFolder' | 'pluginsFile' | 'supportFile' | 'integrationFolder' | 'baseUrl' | 'viewportHeight' | 'viewportWidth' | 'port' | 'experimentalInteractiveRunEvents'>>
Partial<Pick<Cypress.RuntimeConfigOptions, 'hosts' | 'projectName' | 'clientRoute' | 'devServerPublicPathRoute' | 'namespace' | 'report' | 'socketIoCookie' | 'configFile' | 'isTextTerminal' | 'isNewProject' | 'proxyUrl' | 'browsers'>>
& Partial<Pick<Cypress.ResolvedConfigOptions, 'supportFolder' | 'experimentalSourceRewriting' | 'fixturesFolder' | 'reporter' | 'reporterOptions' | 'screenshotsFolder' | 'pluginsFile' | 'supportFile' | 'integrationFolder' | 'baseUrl' | 'viewportHeight' | 'viewportWidth' | 'port' | 'experimentalInteractiveRunEvents'>>
export interface Cfg extends ReceivedCypressOptions {
projectRoot: string
@@ -678,7 +678,7 @@ export class ProjectBase<TServer extends ServerE2E | ServerCt> extends EE {
return {
...browser,
warning: errors.getMsgByType('CHROME_WEB_SECURITY_NOT_SUPPORTED', browser.name),
warning: browser.warning || errors.getMsgByType('CHROME_WEB_SECURITY_NOT_SUPPORTED', browser.name),
}
})
}

View File

@@ -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':

View File

@@ -121,25 +121,6 @@ const isValidRetriesConfig = (key, value) => {
return errMsg(key, value, 'a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls')
}
const isValidFirefoxGcInterval = (key, value) => {
const isIntervalValue = (val) => {
if (isNumber(val)) {
return val >= 0
}
return val == null
}
if (isIntervalValue(value)
|| (_.isEqual(_.keys(value), ['runMode', 'openMode'])
&& isIntervalValue(value.runMode)
&& isIntervalValue(value.openMode))) {
return true
}
return errMsg(key, value, 'a positive number or null or an object with "openMode" and "runMode" as keys and positive numbers or nulls as values')
}
const isPlainObject = (key, value) => {
if (value == null || _.isPlainObject(value)) {
return true
@@ -283,8 +264,6 @@ module.exports = {
isValidBrowserList,
isValidFirefoxGcInterval,
isValidRetriesConfig,
isValidConfig,

View File

@@ -185,7 +185,7 @@
"supertest-session": "4.0.0",
"through2": "2.0.5",
"ts-loader": "7.0.4",
"tsconfig-paths": "3.9.0",
"tsconfig-paths": "3.10.1",
"webpack": "4.43.0",
"ws": "5.2.3",
"xvfb": "cypress-io/node-xvfb#22e3783c31d81ebe64d8c0df491ea00cdc74726a",

View File

@@ -1,9 +1,9 @@
diff --git a/node_modules/tsconfig-paths/lib/register.js b/node_modules/tsconfig-paths/lib/register.js
index c12b996..8beea8c 100644
index 311c7fd..ac9feec 100644
--- a/node_modules/tsconfig-paths/lib/register.js
+++ b/node_modules/tsconfig-paths/lib/register.js
@@ -51,7 +51,7 @@ function register(explicitParams) {
explicitParams: explicitParams
explicitParams: explicitParams,
});
if (configLoaderResult.resultType === "failed") {
- console.warn(configLoaderResult.message + ". tsconfig-paths will be skipped");

View File

@@ -84,4 +84,10 @@ describe('e2e headless', function () {
project: Fixtures.projectPath('screen-size'),
spec: 'default_size.spec.js',
})
e2e.it('launches at DPR 1x', {
headed: false,
project: Fixtures.projectPath('screen-size'),
spec: 'device_pixel_ratio.spec.js',
})
})

View File

@@ -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)

View File

@@ -19,18 +19,13 @@ function parse (obj) {
const str = JSON.stringify(obj, [
'usedJSHeapSize',
'totalJSHeapSize',
'jsHeapSizeLimit',
])
return JSON.parse(str)
}
const stats = () => {
const { firefoxGcInterval, firefoxGcInOpenMode } = Cypress.config()
cy.task('console', {
firefoxGcInterval,
firefoxGcInOpenMode,
numTests: NUM_TESTS,
})
}

View File

@@ -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,
})
})
})

View File

@@ -0,0 +1,6 @@
describe('devicePixelRatio', () => {
it('has DPR of 1', () => {
// assert the browser was spawned with DPR of 1
expect(window.devicePixelRatio).to.equal(1)
})
})

View File

@@ -4,6 +4,7 @@
module.exports = (on) => {
on('before:browser:launch', (browser, options) => {
// options.args.push('-width', '1280', '-height', '1024')
// options.args.push('--force-device-scale-factor=2')
// return options
})

View File

@@ -99,7 +99,7 @@ describe('lib/browsers/chrome', () => {
})
})
it('sets default window size in headless mode', function () {
it('sets default window size and DPR in headless mode', function () {
chrome._writeExtension.restore()
return chrome.open({ isHeadless: true, isHeaded: false }, 'http://', {}, this.automation)
@@ -108,7 +108,8 @@ describe('lib/browsers/chrome', () => {
expect(args).to.include.members([
'--headless',
'--window-size=1920,1080',
'--window-size=1280,720',
'--force-device-scale-factor=1',
])
})
})

View File

@@ -853,38 +853,6 @@ describe('lib/config', () => {
})
})
context('firefoxGcInterval', () => {
it('passes if a number', function () {
this.setup({ firefoxGcInterval: 1 })
return this.expectValidationPasses()
})
it('passes if null', function () {
this.setup({ firefoxGcInterval: null })
return this.expectValidationPasses()
})
it('passes if correctly shaped object', function () {
this.setup({ firefoxGcInterval: { runMode: 1, openMode: null } })
return this.expectValidationPasses()
})
it('fails if string', function () {
this.setup({ firefoxGcInterval: 'foo' })
return this.expectValidationFails('a positive number or null or an object')
})
it('fails if invalid object', function () {
this.setup({ firefoxGcInterval: { foo: 'bar' } })
return this.expectValidationFails('a positive number or null or an object')
})
})
function pemCertificate () {
return {
clientCertificates: [
@@ -1433,6 +1401,16 @@ describe('lib/config', () => {
expect(warning).to.be.calledWith('EXPERIMENTAL_NETWORK_STUBBING_REMOVED')
})
it('warns if firefoxGcInterval is passed', async function () {
const warning = sinon.spy(errors, 'warning')
await this.defaults('firefoxGcInterval', true, {
firefoxGcInterval: true,
})
expect(warning).to.be.calledWith('FIREFOX_GC_INTERVAL_REMOVED')
})
describe('.resolved', () => {
it('sets reporter and port to cli', () => {
const obj = {
@@ -1465,7 +1443,6 @@ describe('lib/config', () => {
experimentalSourceRewriting: { value: false, from: 'default' },
experimentalStudio: { value: false, from: 'default' },
fileServerFolder: { value: '', from: 'default' },
firefoxGcInterval: { value: { openMode: null, runMode: 1 }, from: 'default' },
fixturesFolder: { value: 'cypress/fixtures', from: 'default' },
hosts: { value: null, from: 'default' },
ignoreTestFiles: { value: '*.hot-update.js', from: 'default' },
@@ -1571,7 +1548,6 @@ describe('lib/config', () => {
},
},
fileServerFolder: { value: '', from: 'default' },
firefoxGcInterval: { value: { openMode: null, runMode: 1 }, from: 'default' },
fixturesFolder: { value: 'cypress/fixtures', from: 'default' },
hosts: { value: null, from: 'default' },
ignoreTestFiles: { value: '*.hot-update.js', from: 'default' },

View File

@@ -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', () => {
@@ -701,6 +701,15 @@ describe('lib/modes/run', () => {
.to.be.rejectedWith(/invalid browser family in/)
})
it('throws an error if unsupportedVersion', () => {
const browser = { displayName: 'SomeBrowser', warning: 'blah blah', unsupportedVersion: true }
sinon.stub(browsers, 'ensureAndGetByNameOrPath').resolves(browser)
return expect(runMode.run())
.to.be.rejectedWith('blah blah')
})
it('shows no warnings for chrome browser', () => {
return runMode.run({ browser: 'chrome' })
.then(() => {

View File

@@ -108,8 +108,10 @@ class Dropdown extends Component<Props> {
}
_onSelect (item: object) {
this.setState({ open: false })
this.props.onSelect(item)
const retval = this.props.onSelect(item)
const open = _.isBoolean(retval) ? retval : false
this.setState({ open })
}
}

View File

@@ -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

View File

@@ -7917,11 +7917,6 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad"
integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==
"@types/json5@^0.0.29":
version "0.0.29"
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
"@types/keyv@*":
version "3.1.1"
resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.1.tgz#e45a45324fca9dab716ab1230ee249c9fb52cfa7"
@@ -24264,7 +24259,7 @@ json5@^1.0.1:
dependencies:
minimist "^1.2.0"
json5@^2.1.0, json5@^2.1.2, json5@^2.1.3:
json5@^2.1.0, json5@^2.1.2, json5@^2.1.3, json5@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3"
integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==
@@ -31047,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"
@@ -37421,13 +37416,12 @@ tsconfig-paths-webpack-plugin@^3.3.0, tsconfig-paths-webpack-plugin@^3.5.1:
enhanced-resolve "^5.7.0"
tsconfig-paths "^3.9.0"
tsconfig-paths@3.9.0, tsconfig-paths@^3.9.0:
version "3.9.0"
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b"
integrity sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==
tsconfig-paths@3.10.1, tsconfig-paths@^3.9.0:
version "3.10.1"
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.10.1.tgz#79ae67a68c15289fdf5c51cb74f397522d795ed7"
integrity sha512-rETidPDgCpltxF7MjBZlAFPUHv5aHH2MymyPvh+vEyWAED4Eb/WeMbsnD/JDr4OKPOA1TssDHgIcpTN5Kh0p6Q==
dependencies:
"@types/json5" "^0.0.29"
json5 "^1.0.1"
json5 "^2.2.0"
minimist "^1.2.0"
strip-bom "^3.0.0"
@@ -38961,7 +38955,7 @@ vue-style-loader@^4.1.0, vue-style-loader@^4.1.2:
vue-template-compiler@2.6.12, vue-template-compiler@^2.6.11:
version "2.6.12"
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.12.tgz#947ed7196744c8a5285ebe1233fe960437fcc57e"
resolved "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.12.tgz#947ed7196744c8a5285ebe1233fe960437fcc57e"
integrity sha512-OzzZ52zS41YUbkCBfdXShQTe69j1gQDZ9HIX8miuC9C3rBCk9wIRjLiZZLrmX9V+Ftq/YEyv1JaVr5Y/hNtByg==
dependencies:
de-indent "^1.0.2"