Merge branch 'develop' into feature-multidomain

This commit is contained in:
Matt Henkes
2022-04-05 11:40:02 -05:00
committed by GitHub
9 changed files with 133 additions and 16 deletions

View File

@@ -1,4 +1,4 @@
{
"chrome:beta": "100.0.4896.60",
"chrome:stable": "100.0.4896.60"
"chrome:beta": "101.0.4951.15",
"chrome:stable": "100.0.4896.75"
}

View File

@@ -2363,7 +2363,7 @@ declare namespace Cypress {
type Agent<T extends sinon.SinonSpy> = SinonSpyAgent<T> & T
interface CookieDefaults {
preserve: string | string[] | RegExp | ((cookie: any) => boolean)
preserve: string | string[] | RegExp | ((cookie: Cookie) => boolean)
}
interface Failable {

View File

@@ -274,6 +274,7 @@ export default function (Commands, Cypress, cy, state, config) {
const isContentEditable = $elements.isContentEditable(options.$el.get(0))
const isTextarea = $elements.isTextarea(options.$el.get(0))
const isFirefoxBefore98 = Cypress.isBrowser('firefox') && Cypress.browserMajorVersion() < 98
const fireClickEvent = (el) => {
const ctor = $dom.getDocumentFromElement(el).defaultView!.PointerEvent
@@ -334,9 +335,16 @@ export default function (Commands, Cypress, cy, state, config) {
}
if (
// Firefox sends a click event when the Space key is pressed.
// We don't want to send it twice.
!Cypress.isBrowser('firefox') &&
(
// Before Firefox 98,
// Firefox sends a click event when the Space key is pressed.
// We don't want to send it twice.
!Cypress.isBrowser('firefox') ||
// After Firefox 98,
// it sends a click event automatically if the element is a <button>
// it does not if the element is an <input>
(!isFirefoxBefore98 && $elements.isInput(event.target))
) &&
// Click event is sent after keyup event with space key.
event.type === 'keyup' && event.code === 'Space' &&
// When event is prevented, the click event should not be emitted
@@ -352,6 +360,16 @@ export default function (Commands, Cypress, cy, state, config) {
fireClickEvent(event.target)
keydownEvents = []
// After Firefox 98,
// Firefox doesn't update checkbox automatically even if the click event is sent.
if (Cypress.isBrowser('firefox')) {
if (event.target.type === 'checkbox') {
event.target.checked = !event.target.checked
} else if (event.target.type === 'radio') { // when checked is false, here cannot be reached because of the above condition
event.target.checked = true
}
}
}
},
@@ -404,8 +422,13 @@ export default function (Commands, Cypress, cy, state, config) {
// https://github.com/cypress-io/cypress/issues/19541
// Send click event on type('{enter}')
if (sendClickEvent) {
// Firefox sends a click event automatically.
if (!Cypress.isBrowser('firefox')) {
if (
// Before Firefox 98, it sends a click event automatically.
!Cypress.isBrowser('firefox') ||
// After Firefox 98,
// it sends a click event automatically if the element is a <button>
// it does not if the element is an <input>
(!isFirefoxBefore98 && $elements.isInput(el))) {
fireClickEvent(el)
}
}

View File

@@ -101,6 +101,7 @@ class $Cypress {
isCy: any
log: any
isBrowser: any
browserMajorVersion: any
emit: any
emitThen: any
emitMap: any

View File

@@ -69,5 +69,6 @@ export default (config) => {
return {
browser: config.browser,
isBrowser: _.partial(isBrowser, config),
browserMajorVersion: () => config.browser.majorVersion,
}
}

View File

@@ -23,6 +23,9 @@ type websocketUrl = string
*/
namespace CRI {
export type Command =
'Page.enable' |
'Network.enable' |
'Console.enable' |
'Browser.getVersion' |
'Page.bringToFront' |
'Page.captureScreenshot' |
@@ -138,6 +141,7 @@ type DeferredPromise = { resolve: Function, reject: Function }
export const create = async (target: websocketUrl, onAsynchronousError: Function): Promise<CRIWrapper> => {
const subscriptions: {eventName: CRI.EventName, cb: Function}[] = []
const enableCommands: CRI.Command[] = []
let enqueuedCommands: {command: CRI.Command, params: any, p: DeferredPromise }[] = []
let closed = false // has the user called .close on this?
@@ -158,7 +162,14 @@ export const create = async (target: websocketUrl, onAsynchronousError: Function
try {
await connect()
debug('restoring subscriptions + running queued commands... %o', { subscriptions, enqueuedCommands })
debug('restoring subscriptions + running *.enable and queued commands... %o', { subscriptions, enableCommands, enqueuedCommands })
// '*.enable' commands need to be resent on reconnect or any events in
// that namespace will no longer be received
await Promise.all(enableCommands.map((cmdName) => {
return cri.send(cmdName)
}))
subscriptions.forEach((sub) => {
cri.on(sub.eventName, sub.cb)
})
@@ -175,13 +186,20 @@ export const create = async (target: websocketUrl, onAsynchronousError: Function
}
const connect = async () => {
cri?.close()
await cri?.close()
debug('connecting %o', { target })
cri = await chromeRemoteInterface({
target,
local: true,
// Minor optimization. chrome-remote-interface creates a DSL based on
// this so you can call methods instead of using the event emitter
// (e.g. cri.Network.enable() instead of cri.send('Network.enable'))
// We only use the event emitter, so if we pass in an empty protcol,
// it will keep c-r-i from looping through it and needlessly creating
// the DSL
protocol: { domains: [] },
})
connected = true
@@ -226,10 +244,20 @@ export const create = async (target: websocketUrl, onAsynchronousError: Function
})
}
// Keep track of '*.enable' commands so they can be resent when
// reconnecting
if (command.endsWith('.enable')) {
enableCommands.push(command)
}
if (connected) {
try {
return await cri.send(command, params)
} catch (err) {
// This error occurs when the browser has been left open for a long
// time and/or the user's computer has been put to sleep. The
// socket disconnects and we need to recreate the socket and
// connection
if (!WEBSOCKET_NOT_OPEN_RE.test(err.message)) {
throw err
}
@@ -257,6 +285,9 @@ export const create = async (target: websocketUrl, onAsynchronousError: Function
return cri.close()
},
// @ts-ignore
reconnect,
}
return client

View File

@@ -269,6 +269,7 @@ const parseSpecArgv = (pattern) => {
if (token === ',') {
const isBreakable =
(!opens.length && !closes.length) ||
index > opens[opens.length - 1] &&
index > closes[closes.length - 1] &&
opens.length === closes.length
@@ -304,7 +305,7 @@ const parseSpecArgv = (pattern) => {
rule.comma - offset,
)
offset = offsettedBy
offset += offsettedBy
carry = mutated
return res

View File

@@ -12,23 +12,29 @@ describe('lib/browsers/cri-client', function () {
}
let send: sinon.SinonStub
let criImport: sinon.SinonStub
let criStub: {
send: typeof send
close: sinon.SinonStub
_notifier: EventEmitter
}
let onError: sinon.SinonStub
let getClient: () => ReturnType<typeof create>
beforeEach(function () {
send = sinon.stub()
onError = sinon.stub()
criStub = {
send,
close: sinon.stub(),
_notifier: new EventEmitter(),
}
criImport = sinon.stub()
.withArgs({
target: DEBUGGER_URL,
local: true,
})
.resolves({
send,
close: sinon.stub(),
_notifier: new EventEmitter(),
})
.resolves(criStub)
criClient = proxyquire('../lib/browsers/cri-client', {
'chrome-remote-interface': criImport,
@@ -120,4 +126,28 @@ describe('lib/browsers/cri-client', function () {
})
})
})
describe('on reconnect', () => {
it('resends *.enable commands', async () => {
criStub._notifier.on = sinon.stub()
const client = await getClient()
client.send('Page.enable')
client.send('Page.foo')
client.send('Page.bar')
client.send('Network.enable')
client.send('Network.baz')
// clear out previous calls before reconnect
criStub.send.reset()
// @ts-ignore
await criStub._notifier.on.withArgs('disconnect').args[0][1]()
expect(criStub.send).to.be.calledTwice
expect(criStub.send).to.be.calledWith('Page.enable')
expect(criStub.send).to.be.calledWith('Network.enable')
})
})
})

View File

@@ -189,6 +189,36 @@ describe('lib/util/args', () => {
expect(options.spec[0]).to.eq(`${cwd}/cypress/integration/{a,b,c}/*.js`)
})
// https://github.com/cypress-io/cypress/issues/20794
it('does not split at filename with glob pattern', function () {
const options = this.setup('--spec', 'cypress/integration/foo/bar/[baz]/test.ts,cypress/integration/foo1/bar/[baz]/test.ts,cypress/integration/foo2/bar/baz/test.ts,cypress/integration/foo3/bar/baz/foo4.ts')
expect(options.spec[0]).to.eq(`${cwd}/cypress/integration/foo/bar/[baz]/test.ts`)
expect(options.spec[1]).to.eq(`${cwd}/cypress/integration/foo1/bar/[baz]/test.ts`)
expect(options.spec[2]).to.eq(`${cwd}/cypress/integration/foo2/bar/baz/test.ts`)
expect(options.spec[3]).to.eq(`${cwd}/cypress/integration/foo3/bar/baz/foo4.ts`)
})
// https://github.com/cypress-io/cypress/issues/20794
it('correctly splits at comma with glob pattern', function () {
const options = this.setup('--spec', 'cypress/integration/foo/bar/baz/test.ts,cypress/integration/foo1/bar/[baz]/test.ts,cypress/integration/foo2/bar/baz/test.ts,cypress/integration/foo3/bar/baz/foo4.ts')
expect(options.spec[0]).to.eq(`${cwd}/cypress/integration/foo/bar/baz/test.ts`)
expect(options.spec[1]).to.eq(`${cwd}/cypress/integration/foo1/bar/[baz]/test.ts`)
expect(options.spec[2]).to.eq(`${cwd}/cypress/integration/foo2/bar/baz/test.ts`)
expect(options.spec[3]).to.eq(`${cwd}/cypress/integration/foo3/bar/baz/foo4.ts`)
})
// https://github.com/cypress-io/cypress/issues/20794
it('correctly splits at comma with escaped glob pattern', function () {
const options = this.setup('--spec', 'cypress/integration/foo/bar/\[baz\]/test.ts,cypress/integration/foo1/bar/\[baz1\]/test.ts,cypress/integration/foo2/bar/baz/test.ts,cypress/integration/foo3/bar/baz/foo4.ts')
expect(options.spec[0]).to.eq(`${cwd}/cypress/integration/foo/bar/\[baz\]/test.ts`)
expect(options.spec[1]).to.eq(`${cwd}/cypress/integration/foo1/bar/\[baz1\]/test.ts`)
expect(options.spec[2]).to.eq(`${cwd}/cypress/integration/foo2/bar/baz/test.ts`)
expect(options.spec[3]).to.eq(`${cwd}/cypress/integration/foo3/bar/baz/foo4.ts`)
})
})
context('--tag', () => {