mirror of
https://github.com/cypress-io/cypress.git
synced 2026-02-20 06:01:12 -06:00
Merge branch 'develop' into feature-multidomain
This commit is contained in:
@@ -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"
|
||||
}
|
||||
|
||||
2
cli/types/cypress.d.ts
vendored
2
cli/types/cypress.d.ts
vendored
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ class $Cypress {
|
||||
isCy: any
|
||||
log: any
|
||||
isBrowser: any
|
||||
browserMajorVersion: any
|
||||
emit: any
|
||||
emitThen: any
|
||||
emitMap: any
|
||||
|
||||
@@ -69,5 +69,6 @@ export default (config) => {
|
||||
return {
|
||||
browser: config.browser,
|
||||
isBrowser: _.partial(isBrowser, config),
|
||||
browserMajorVersion: () => config.browser.majorVersion,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
Reference in New Issue
Block a user