Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: re-initialize DOM subscriptions from a full snapshot on cdp reconnect #29210

Merged
merged 8 commits into from
Mar 29, 2024
4 changes: 4 additions & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@

_Released 3/26/2024 (PENDING)_

**Bugfixes:**

- Fixed a hang in the test runner where Cypress would run indefinitely in record mode when CDP disconnects during the middle of a test. Fixes [#29209].
ryanthemanuel marked this conversation as resolved.
Show resolved Hide resolved

**Dependency Updates:**

- Updated jose from `4.11.2` to `4.15.5`. Addressed in [#29086](/~https://github.com/cypress-io/cypress/pull/29086).
Expand Down
5 changes: 5 additions & 0 deletions packages/server/lib/browsers/cri-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,11 @@ export const create = async ({
if (onReconnect) {
onReconnect(client)
}

// When CDP disconnects, it will automatically reconnect and re-apply various subscriptions
// (e.g. DOM.enable, Network.enable, etc.). However, we need to restart tracking DOM mutations
// from scratch. We do this by capturing a brand new full snapshot of the DOM.
await protocolManager?.cdpReconnect()
}

const retryReconnect = async () => {
Expand Down
4 changes: 4 additions & 0 deletions packages/server/lib/cloud/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,10 @@ export class ProtocolManager implements ProtocolManagerShape {
}
}

async cdpReconnect () {
await this.invokeAsync('cdpReconnect', { isEssential: true })
}

async reportNonFatalErrors (context?: {
osName: string
projectSlug: string
Expand Down
18 changes: 13 additions & 5 deletions packages/server/test/unit/browsers/cri-client_spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import EventEmitter from 'events'
import { create } from '../../../lib/browsers/cri-client'
import { ProtocolManagerShape } from '@packages/types'

const { expect, proxyquire, sinon } = require('../../spec_helper')

Expand All @@ -23,7 +24,7 @@ describe('lib/browsers/cri-client', function () {
_notifier: EventEmitter
}
let onError: sinon.SinonStub
let getClient: (options?: { host?: string, fullyManageTabs?: boolean }) => ReturnType<typeof create>
let getClient: (options?: { host?: string, fullyManageTabs?: boolean, protocolManager?: ProtocolManagerShape }) => ReturnType<typeof create>

beforeEach(function () {
send = sinon.stub()
Expand All @@ -49,8 +50,8 @@ describe('lib/browsers/cri-client', function () {
'chrome-remote-interface': criImport,
})

getClient = ({ host, fullyManageTabs } = {}) => {
return criClient.create({ target: DEBUGGER_URL, host, onAsynchronousError: onError, fullyManageTabs })
getClient = ({ host, fullyManageTabs, protocolManager } = {}) => {
return criClient.create({ target: DEBUGGER_URL, host, onAsynchronousError: onError, fullyManageTabs, protocolManager })
}
})

Expand Down Expand Up @@ -134,10 +135,16 @@ describe('lib/browsers/cri-client', function () {
})

describe('on reconnect', () => {
it('resends *.enable commands', async () => {
it('resends *.enable commands and notifies protocol manager', async () => {
criStub._notifier.on = sinon.stub()

const client = await getClient()
const protocolManager = {
cdpReconnect: sinon.stub(),
} as ProtocolManagerShape

const client = await getClient({
protocolManager,
})

client.send('Page.enable')
// @ts-ignore
Expand All @@ -157,6 +164,7 @@ describe('lib/browsers/cri-client', function () {
expect(criStub.send).to.be.calledTwice
expect(criStub.send).to.be.calledWith('Page.enable')
expect(criStub.send).to.be.calledWith('Network.enable')
expect(protocolManager.cdpReconnect).to.be.called
})

it('errors if reconnecting fails', async () => {
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface CDPClient {
// TODO(protocol): This is basic for now but will evolve as we progress with the protocol work

export interface AppCaptureProtocolCommon {
cdpReconnect (): Promise<void>
addRunnables (runnables: any): void
commandLogAdded (log: any): void
commandLogChanged (log: any): void
Expand Down
Loading