From fd51f406f371c4847d3672b45db6484f30ce7b64 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 26 Jun 2024 07:32:49 -0400 Subject: [PATCH 1/6] chore: Update v8 snapshot cache (#29750) * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache --------- Co-authored-by: cypress-bot[bot] <+cypress-bot[bot]@users.noreply.github.com> --- tooling/v8-snapshot/cache/darwin/snapshot-meta.json | 5 +++-- tooling/v8-snapshot/cache/linux/snapshot-meta.json | 5 +++-- tooling/v8-snapshot/cache/win32/snapshot-meta.json | 5 +++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/tooling/v8-snapshot/cache/darwin/snapshot-meta.json b/tooling/v8-snapshot/cache/darwin/snapshot-meta.json index 5d1958bf9e2d..b134dcb291d6 100644 --- a/tooling/v8-snapshot/cache/darwin/snapshot-meta.json +++ b/tooling/v8-snapshot/cache/darwin/snapshot-meta.json @@ -604,7 +604,7 @@ "./node_modules/ws/lib/validation.js", "./node_modules/ws/lib/websocket-server.js", "./node_modules/ws/lib/websocket.js", - "./node_modules/xdg-trashdir/node_modules/xdg-basedir/index.js", + "./node_modules/xdg-basedir/index.js", "./node_modules/xml2js/lib/xml2js.js", "./packages/config/index.js", "./packages/config/src/options.ts", @@ -2210,6 +2210,7 @@ "./node_modules/ipaddr.js/lib/ipaddr.js", "./node_modules/is-binary-path/index.js", "./node_modules/is-buffer/index.js", + "./node_modules/is-ci/index.js", "./node_modules/is-core-module/core.json", "./node_modules/is-core-module/index.js", "./node_modules/is-dotfile/index.js", @@ -4351,5 +4352,5 @@ "./tooling/v8-snapshot/cache/darwin/snapshot-entry.js" ], "deferredHashFile": "yarn.lock", - "deferredHash": "00b32aef25f2e5f2eda62643aaba67c7d81fa2ce758520b18681f7323cb0d582" + "deferredHash": "f7ad8b998e773f23e154b5486d7bc410d7b49da55b20e00c6d43baeff83f4bef" } \ No newline at end of file diff --git a/tooling/v8-snapshot/cache/linux/snapshot-meta.json b/tooling/v8-snapshot/cache/linux/snapshot-meta.json index ccc686c9bae5..07a26b19f970 100644 --- a/tooling/v8-snapshot/cache/linux/snapshot-meta.json +++ b/tooling/v8-snapshot/cache/linux/snapshot-meta.json @@ -603,7 +603,7 @@ "./node_modules/ws/lib/validation.js", "./node_modules/ws/lib/websocket-server.js", "./node_modules/ws/lib/websocket.js", - "./node_modules/xdg-trashdir/node_modules/xdg-basedir/index.js", + "./node_modules/xdg-basedir/index.js", "./node_modules/xml2js/lib/xml2js.js", "./packages/config/index.js", "./packages/config/src/options.ts", @@ -2211,6 +2211,7 @@ "./node_modules/ipaddr.js/lib/ipaddr.js", "./node_modules/is-binary-path/index.js", "./node_modules/is-buffer/index.js", + "./node_modules/is-ci/index.js", "./node_modules/is-core-module/core.json", "./node_modules/is-core-module/index.js", "./node_modules/is-dotfile/index.js", @@ -4354,5 +4355,5 @@ "./tooling/v8-snapshot/cache/linux/snapshot-entry.js" ], "deferredHashFile": "yarn.lock", - "deferredHash": "00b32aef25f2e5f2eda62643aaba67c7d81fa2ce758520b18681f7323cb0d582" + "deferredHash": "f7ad8b998e773f23e154b5486d7bc410d7b49da55b20e00c6d43baeff83f4bef" } \ No newline at end of file diff --git a/tooling/v8-snapshot/cache/win32/snapshot-meta.json b/tooling/v8-snapshot/cache/win32/snapshot-meta.json index aea86b70bc0d..b4ec8581229c 100644 --- a/tooling/v8-snapshot/cache/win32/snapshot-meta.json +++ b/tooling/v8-snapshot/cache/win32/snapshot-meta.json @@ -605,7 +605,7 @@ "./node_modules/ws/lib/validation.js", "./node_modules/ws/lib/websocket-server.js", "./node_modules/ws/lib/websocket.js", - "./node_modules/xdg-trashdir/node_modules/xdg-basedir/index.js", + "./node_modules/xdg-basedir/index.js", "./node_modules/xml2js/lib/xml2js.js", "./packages/config/index.js", "./packages/config/src/options.ts", @@ -2213,6 +2213,7 @@ "./node_modules/ipaddr.js/lib/ipaddr.js", "./node_modules/is-binary-path/index.js", "./node_modules/is-buffer/index.js", + "./node_modules/is-ci/index.js", "./node_modules/is-core-module/core.json", "./node_modules/is-core-module/index.js", "./node_modules/is-dotfile/index.js", @@ -4354,5 +4355,5 @@ "./tooling/v8-snapshot/cache/win32/snapshot-entry.js" ], "deferredHashFile": "yarn.lock", - "deferredHash": "2f252e6b0ea332582f29452a3a6f44bcb3eb2356ea37fb81f4af320fb1111025" + "deferredHash": "6875cde904573913ba35e281d8ac7e6d94ca1e04f9b3cadef0ac6965ca2506f2" } \ No newline at end of file From e795d12cec15ffd648bf30715ed0f735670fc3c5 Mon Sep 17 00:00:00 2001 From: Jennifer Shehane Date: Wed, 26 Jun 2024 08:34:19 -0400 Subject: [PATCH 2/6] chore: replace deprecated color-adjust with print-color-adjust (#29743) * chore: replace deprecated color-adjust with print-color-adjust * empty commit * Update packages/frontend-shared/src/styles/normalize.scss Co-authored-by: Matt Schile * Update packages/frontend-shared/src/styles/normalize.scss Co-authored-by: Matt Schile * Update packages/frontend-shared/src/styles/normalize.scss Co-authored-by: Matt Schile --------- Co-authored-by: Matt Schile --- .../src/public/shiki/languages/css.tmLanguage.json | 2 +- packages/frontend-shared/src/styles/normalize.scss | 6 +++--- packages/frontend-shared/src/styles/shared.scss | 6 +++--- yarn.lock | 7 +------ 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/packages/frontend-shared/src/public/shiki/languages/css.tmLanguage.json b/packages/frontend-shared/src/public/shiki/languages/css.tmLanguage.json index 8fa551e9595a..894e40c5af13 100644 --- a/packages/frontend-shared/src/public/shiki/languages/css.tmLanguage.json +++ b/packages/frontend-shared/src/public/shiki/languages/css.tmLanguage.json @@ -1421,7 +1421,7 @@ "property-names": { "patterns": [ { - "match": "(?xi) (? Date: Wed, 26 Jun 2024 10:22:07 -0400 Subject: [PATCH 3/6] dependency: update dependency memfs to v3.5.3 (#29746) * fix(deps): update dependency memfs to v3.5.3 * changelog entry --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Jennifer Shehane --- cli/CHANGELOG.md | 1 + packages/server/package.json | 2 +- yarn.lock | 14 +++++++++++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index bd663275de75..177ef2dbc49f 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -14,6 +14,7 @@ _Released 7/02/2024 (PENDING)_ **Dependency Updates:** +- Updated `memfs` from `3.4.12` to `3.5.3`. Addressed in [#29746](https://github.com/cypress-io/cypress/pull/29746). - Updated `tmp` from `0.2.1` to `0.2.3`. Addresses [#29693](https://github.com/cypress-io/cypress/issues/29693). - Updated `ws` from `5.2.3` to `5.2.4`. Addressed in [#29698](https://github.com/cypress-io/cypress/pull/29698). diff --git a/packages/server/package.json b/packages/server/package.json index 0b1e61bf1de3..c6885e0bc691 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -91,7 +91,7 @@ "log-symbols": "2.2.0", "marionette-client": "https://github.com/cypress-io/marionette-client.git#5fc10cdf6c02627e9a2add98ca52de4d0c2fe74d", "md5": "2.3.0", - "memfs": "3.4.12", + "memfs": "3.5.3", "mime": "2.4.4", "mime-db": "1.45.0", "minimatch": "3.0.5", diff --git a/yarn.lock b/yarn.lock index f8d0fd961367..1687fbea60e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16839,6 +16839,11 @@ fs-monkey@^1.0.3: resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3" integrity sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q== +fs-monkey@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.6.tgz#8ead082953e88d992cf3ff844faa907b26756da2" + integrity sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg== + fs-readdir-recursive@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" @@ -21646,7 +21651,14 @@ mem@^4.0.0: mimic-fn "^2.0.0" p-is-promise "^2.0.0" -memfs@3.4.12, memfs@^3.4.1: +memfs@3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.5.3.tgz#d9b40fe4f8d5788c5f895bda804cd0d9eeee9f3b" + integrity sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw== + dependencies: + fs-monkey "^1.0.4" + +memfs@^3.4.1: version "3.4.12" resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.4.12.tgz#d00f8ad8dab132dc277c659dc85bfd14b07d03bd" integrity sha512-BcjuQn6vfqP+k100e0E9m61Hyqa//Brp+I3f0OBmN0ATHlFA8vx3Lt8z57R3u2bPqe3WGDBC+nF72fTH7isyEw== From 606a43bef86a66faf98eeeb02c6041f5dbda694b Mon Sep 17 00:00:00 2001 From: Cacie Prins Date: Wed, 26 Jun 2024 11:31:57 -0400 Subject: [PATCH 4/6] refactor: CriClient -> traditional class + async factory (#29676) * refactor: CriClient -> traditional class + async factory * Update packages/server/test/unit/browsers/cri-client_spec.ts * member fns of CriClient must be bound to context * rethrow debug-inspected ws send error * make cri client constructor private to force factory method; ref cri connection via closure * add todo for potential memory leak in CriClient --- .../server/lib/browsers/browser-cri-client.ts | 8 +- .../server/lib/browsers/cdp_automation.ts | 2 +- packages/server/lib/browsers/cri-client.ts | 447 ++++++++++-------- packages/server/test/integration/cdp_spec.ts | 26 +- .../unit/browsers/browser-cri-client_spec.ts | 2 +- .../test/unit/browsers/cri-client_spec.ts | 12 +- .../server/test/unit/browsers/firefox_spec.ts | 4 +- 7 files changed, 262 insertions(+), 239 deletions(-) diff --git a/packages/server/lib/browsers/browser-cri-client.ts b/packages/server/lib/browsers/browser-cri-client.ts index 96c60234e8fa..55fc70739ef4 100644 --- a/packages/server/lib/browsers/browser-cri-client.ts +++ b/packages/server/lib/browsers/browser-cri-client.ts @@ -5,7 +5,7 @@ import Debug from 'debug' import type { Protocol } from 'devtools-protocol' import { _connectAsync, _getDelayMsForRetry } from './protocol' import * as errors from '../errors' -import { create, CriClient, DEFAULT_NETWORK_ENABLE_OPTIONS } from './cri-client' +import { CriClient, DEFAULT_NETWORK_ENABLE_OPTIONS } from './cri-client' import { serviceWorkerClientEventHandler, serviceWorkerClientEventHandlerName } from '@packages/proxy/lib/http/util/service-worker-manager' import type { ProtocolManagerShape } from '@packages/types' import type { ServiceWorkerEventHandler } from '@packages/proxy/lib/http/util/service-worker-manager' @@ -241,7 +241,7 @@ export class BrowserCriClient { return retryWithIncreasingDelay(async () => { const versionInfo = await CRI.Version({ host, port, useHostName: true }) - const browserClient = await create({ + const browserClient = await CriClient.create({ target: versionInfo.webSocketDebuggerUrl, onAsynchronousError, onReconnect, @@ -543,7 +543,7 @@ export class BrowserCriClient { throw new Error(`Could not find url target in browser ${url}. Targets were ${JSON.stringify(targets)}`) } - this.currentlyAttachedTarget = await create({ + this.currentlyAttachedTarget = await CriClient.create({ target: target.targetId, onAsynchronousError: this.onAsynchronousError, host: this.host, @@ -603,7 +603,7 @@ export class BrowserCriClient { }) if (target) { - this.currentlyAttachedTarget = await create({ + this.currentlyAttachedTarget = await CriClient.create({ target: target.targetId, onAsynchronousError: this.onAsynchronousError, host: this.host, diff --git a/packages/server/lib/browsers/cdp_automation.ts b/packages/server/lib/browsers/cdp_automation.ts index 29ca5ec411be..853a14ad1a2e 100644 --- a/packages/server/lib/browsers/cdp_automation.ts +++ b/packages/server/lib/browsers/cdp_automation.ts @@ -144,7 +144,7 @@ export type SendDebuggerCommand = (message: T, data?: Prot export type OnFn = (eventName: T, cb: (data: ProtocolMapping.Events[T][0], sessionId?: string) => void) => void -export type OffFn = (eventName: string, cb: (data: any) => void) => void +export type OffFn = (eventName: T, cb: (data: any) => void) => void type SendCloseCommand = (shouldKeepTabOpen: boolean) => Promise | void interface HasFrame { diff --git a/packages/server/lib/browsers/cri-client.ts b/packages/server/lib/browsers/cri-client.ts index e75e661e8f61..21c22bac2a04 100644 --- a/packages/server/lib/browsers/cri-client.ts +++ b/packages/server/lib/browsers/cri-client.ts @@ -1,13 +1,12 @@ -import CRI from 'chrome-remote-interface' +import CDP from 'chrome-remote-interface' import debugModule from 'debug' import _ from 'lodash' import * as errors from '../errors' - +import type ProtocolMapping from 'devtools-protocol/types/protocol-mapping' import type EventEmitter from 'events' import type WebSocket from 'ws' -import type CDP from 'chrome-remote-interface' -import type { SendDebuggerCommand, OnFn, CdpCommand, CdpEvent } from './cdp_automation' +import type { SendDebuggerCommand, OnFn, OffFn, CdpCommand, CdpEvent } from './cdp_automation' import type { ProtocolManagerShape } from '@packages/types' const debug = debugModule('cypress:server:browsers:cri-client') @@ -53,6 +52,8 @@ type Subscription = { cb: Function } +type CmdParams = ProtocolMapping.Commands[TCmd]['paramsType'][0] + interface CDPClient extends CDP.Client { off: EventEmitter['off'] _ws: WebSocket @@ -64,7 +65,7 @@ export const DEFAULT_NETWORK_ENABLE_OPTIONS = { maxPostDataSize: 0, } -export interface CriClient { +export interface ICriClient { /** * The target id attached to by this client */ @@ -88,8 +89,6 @@ export interface CriClient { */ close (): Promise - onReconnectAttempt? (retryIndex: number): void - /** * The internal queue of replayable messages that run after a disconnect */ @@ -105,7 +104,7 @@ export interface CriClient { /** * Unregisters callback for particular event. */ - off (eventName: string, cb: (event: any) => void): void + off: OffFn } const maybeDebugCdpMessages = (cri: CDPClient) => { @@ -144,7 +143,12 @@ const maybeDebugCdpMessages = (cri: CDPClient) => { cri._ws.send = (data, callback) => { debugVerboseSend('sending CDP command %o', JSON.parse(data)) - return send.call(cri._ws, data, callback) + try { + return send.call(cri._ws, data, callback) + } catch (e: any) { + debugVerboseSend('Error sending CDP command %o %O', JSON.parse(data), e) + throw e + } } } @@ -172,120 +176,161 @@ type CreateParams = { onReconnect?: (client: CriClient) => void protocolManager?: ProtocolManagerShape fullyManageTabs?: boolean - browserClient?: CriClient + browserClient?: ICriClient + onReconnectAttempt?: (retryIndex: number) => void } -export const create = async ({ - target, - onAsynchronousError, - host, - port, - onReconnect, - protocolManager, - fullyManageTabs, - browserClient, -}: CreateParams): Promise => { - const subscriptions: Subscription[] = [] - const enableCommands: EnableCommand[] = [] - let enqueuedCommands: EnqueuedCommand[] = [] - - let closed = false // has the user called .close on this? - let connected = false // is this currently connected to CDP? - let crashed = false // has this crashed? - let reconnection: Promise | undefined = undefined - - let cri: CDPClient - let client: CriClient - - const reconnect = async (retryIndex) => { - connected = false - - if (closed) { - debug('disconnected, not reconnecting because client is closed %o', { closed, target }) - enqueuedCommands = [] +export class CriClient implements ICriClient { + private subscriptions: Subscription[] = [] + private enableCommands: EnableCommand[] = [] + private enqueuedCommands: EnqueuedCommand[] = [] + + private _closed = false + private _connected = false + private crashed = false + private reconnection: Promise | undefined = undefined + + private cri: CDPClient | undefined + + private constructor ( + public targetId: string, + private onAsynchronousError: Function, + private host?: string, + private port?: number, + private onReconnect?: (client: CriClient) => void, + private protocolManager?: ProtocolManagerShape, + private fullyManageTabs?: boolean, + private browserClient?: ICriClient, + private onReconnectAttempt?: (retryIndex: number) => void, + ) {} + + get ws () { + return this.cri!._ws + } + + get queue () { + return { + enableCommands: this.enableCommands, + enqueuedCommands: this.enqueuedCommands, + subscriptions: this.subscriptions, + } + } + + get closed () { + return this._closed + } + + get connected () { + return this._connected + } + + static async create ({ + target, + onAsynchronousError, + host, + port, + onReconnect, + protocolManager, + fullyManageTabs, + browserClient, + onReconnectAttempt, + }: CreateParams): Promise { + const newClient = new CriClient(target, onAsynchronousError, host, port, onReconnect, protocolManager, fullyManageTabs, browserClient, onReconnectAttempt) + + await newClient.connect() + + return newClient + } + + private async reconnect (retryIndex: number = 0) { + this._connected = false + + if (this.closed) { + debug('disconnected, not reconnecting because client is closed %o', { closed: this.closed, target: this.targetId }) + this.enqueuedCommands = [] return } - client.onReconnectAttempt?.(retryIndex) + this.onReconnectAttempt?.(retryIndex) - debug('disconnected, attempting to reconnect... %o', { retryIndex, closed, target }) + debug('disconnected, attempting to reconnect... %o', { retryIndex, closed: this.closed, target: this.targetId }) - await connect() + await this.connect() - debug('restoring subscriptions + running *.enable and queued commands... %o', { subscriptions, enableCommands, enqueuedCommands, target }) + debug('restoring subscriptions + running *.enable and queued commands... %o', { subscriptions: this.subscriptions, enableCommands: this.enableCommands, enqueuedCommands: this.enqueuedCommands, target: this.targetId }) - subscriptions.forEach((sub) => { - cri.on(sub.eventName, sub.cb as any) + this.subscriptions.forEach((sub) => { + this.cri?.on(sub.eventName, sub.cb as any) }) // '*.enable' commands need to be resent on reconnect or any events in // that namespace will no longer be received - await Promise.all(enableCommands.map(async ({ command, params, sessionId }) => { + await Promise.all(this.enableCommands.map(async ({ command, params, sessionId }) => { // these commands may have been enqueued, so we need to resolve those promises and remove // them from the queue when we send here const isInFlightCommand = (candidate: EnqueuedCommand) => { return candidate.command === command && candidate.params === params && candidate.sessionId === sessionId } - const enqueued = enqueuedCommands.find(isInFlightCommand) + const enqueued = this.enqueuedCommands.find(isInFlightCommand) try { - const response = await cri.send(command, params, sessionId) + const response = await this.cri?.send(command, params, sessionId) enqueued?.p.resolve(response) } catch (e) { enqueued?.p.reject(e) } finally { - enqueuedCommands = enqueuedCommands.filter((candidate) => { + this.enqueuedCommands = this.enqueuedCommands.filter((candidate) => { return !isInFlightCommand(candidate) }) } })) - enqueuedCommands.forEach((cmd) => { - cri.send(cmd.command, cmd.params, cmd.sessionId).then(cmd.p.resolve as any, cmd.p.reject as any) + this.enqueuedCommands.forEach((cmd) => { + this.cri!.send(cmd.command, cmd.params, cmd.sessionId).then(cmd.p.resolve as any, cmd.p.reject as any) }) - enqueuedCommands = [] + this.enqueuedCommands = [] - if (onReconnect) { - onReconnect(client) + if (this.onReconnect) { + this.onReconnect(this) } // 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() + await this.protocolManager?.cdpReconnect() } - const retryReconnect = async () => { - if (reconnection) { + private retryReconnect = async () => { + if (this.reconnection) { debug('reconnection in progress; not starting new process, returning promise for in-flight reconnection attempt') - return reconnection + return this.reconnection } - debug('disconnected, starting retries to reconnect... %o', { closed, target }) + debug('disconnected, starting retries to reconnect... %o', { closed: this.closed, target: this.targetId }) const retry = async (retryIndex = 0) => { retryIndex++ try { - const attempt = await reconnect(retryIndex) + const attempt = await this.reconnect(retryIndex) - reconnection = undefined + this.reconnection = undefined return attempt } catch (err) { - if (closed) { - debug('could not reconnect because client is closed %o', { closed, target }) + if (this.closed) { + debug('could not reconnect because client is closed %o', { closed: this.closed, target: this.targetId }) - enqueuedCommands = [] + this.enqueuedCommands = [] return } - debug('could not reconnect, retrying... %o', { closed, target, err }) + debug('could not reconnect, retrying... %o', { closed: this.closed, target: this.targetId, err }) if (retryIndex < 20) { await new Promise((resolve) => setTimeout(resolve, 100)) @@ -297,38 +342,67 @@ export const create = async ({ // If we cannot reconnect to CDP, we will be unable to move to the next set of specs since we use CDP to clean up and close tabs. Marking this as fatal cdpError.isFatalApiErr = true - reconnection = undefined - onAsynchronousError(cdpError) + this.reconnection = undefined + this.onAsynchronousError(cdpError) } } - reconnection = retry() + this.reconnection = retry() - return reconnection + return this.reconnection } - const connect = async () => { - await cri?.close() + private enqueueCommand ( + command: TCmd, + params: ProtocolMapping.Commands[TCmd]['paramsType'][0], + sessionId?: string, + ): Promise { + return new Promise((resolve, reject) => { + const obj: EnqueuedCommand = { + command, + p: { resolve, reject }, + } + + if (params) { + obj.params = params + } + + if (sessionId) { + obj.sessionId = sessionId + } + + this.enqueuedCommands.push(obj) + }) + } - debug('connecting %o', { connected, target }) + public connect = async () => { + await this.cri?.close() - cri = await CRI({ - host, - port, - target, + debug('connecting %o', { connected: this._connected, target: this.targetId }) + + /** + * TODO: https://github.com/cypress-io/cypress/issues/29744 + * this (`cri` / `this.cri`) symbol is referenced via closure in event listeners added in this method; this + * may prevent old instances of `CDPClient` from being garbage collected. + */ + + const cri = this.cri = await CDP({ + host: this.host, + port: this.port, + target: this.targetId, local: true, useHostName: true, }) as CDPClient - connected = true + this._connected = true - debug('connected %o', { connected, target }) + debug('connected %o', { connected: this._connected, target: this.targetId, ws: this.cri._ws }) - maybeDebugCdpMessages(cri) + maybeDebugCdpMessages(this.cri) // Having a host set indicates that this is the child cri target, a.k.a. // the main Cypress tab (as opposed to the root browser cri target) - const isChildTarget = !!host + const isChildTarget = !!this.host // don't reconnect in these circumstances if ( @@ -338,7 +412,7 @@ export const create = async ({ // that we don't want to reconnect on && !process.env.CYPRESS_INTERNAL_E2E_TESTING_SELF ) { - cri.on('disconnect', retryReconnect) + this.cri.on('disconnect', this.retryReconnect) } // We're only interested in child target traffic. Browser cri traffic is @@ -346,22 +420,22 @@ export const create = async ({ // to targets and enable network traffic. We must attach in a paused state // so that we can enable network traffic before the target starts running. if (isChildTarget) { - cri.on('Target.targetCrashed', async (event) => { - if (event.targetId !== target) { + this.cri.on('Target.targetCrashed', async (event) => { + if (event.targetId !== this.targetId) { return } debug('crash detected') - crashed = true + this.crashed = true }) - if (fullyManageTabs) { + if (this.fullyManageTabs) { cri.on('Target.attachedToTarget', async (event) => { try { // Service workers get attached at the page and browser level. We only want to handle them at the browser level // We don't track child tabs/page network traffic. 'other' targets can't have network enabled if (event.targetInfo.type !== 'service_worker' && event.targetInfo.type !== 'page' && event.targetInfo.type !== 'other') { - await cri.send('Network.enable', protocolManager?.networkEnableOptions ?? DEFAULT_NETWORK_ENABLE_OPTIONS, event.sessionId) + await cri.send('Network.enable', this.protocolManager?.networkEnableOptions ?? DEFAULT_NETWORK_ENABLE_OPTIONS, event.sessionId) } if (event.waitingForDebugger) { @@ -374,158 +448,115 @@ export const create = async ({ }) // Ideally we could use filter rather than checking the type above, but that was added relatively recently - await cri.send('Target.setAutoAttach', { autoAttach: true, waitForDebuggerOnStart: true, flatten: true }) - await cri.send('Target.setDiscoverTargets', { discover: true }) + await this.cri.send('Target.setAutoAttach', { autoAttach: true, waitForDebuggerOnStart: true, flatten: true }) + await this.cri.send('Target.setDiscoverTargets', { discover: true }) } } } - await connect() - - client = { - targetId: target, + public send = async ( + command: TCmd, + params?: CmdParams, + sessionId?: string, + ): Promise => { + if (this.crashed) { + return Promise.reject(new Error(`${command} will not run as the target browser or tab CRI connection has crashed`)) + } - async send (command: CdpCommand, params?: object, sessionId?: string) { - if (crashed) { - return Promise.reject(new Error(`${command} will not run as the target browser or tab CRI connection has crashed`)) + // Keep track of '*.enable' commands so they can be resent when + // reconnecting + if (command.endsWith('.enable') || ['Runtime.addBinding', 'Target.setDiscoverTargets'].includes(command)) { + debug('registering enable command', command) + const obj: EnableCommand = { + command, } - const enqueue = () => { - debug('enqueueing command', { command, params }) - - return new Promise((resolve, reject) => { - const obj: EnqueuedCommand = { - command, - p: { resolve, reject } as DeferredPromise, - } - - if (params) { - obj.params = params - } - - if (sessionId) { - obj.sessionId = sessionId - } - - enqueuedCommands.push(obj) - }) + if (params) { + obj.params = params } - // Keep track of '*.enable' commands so they can be resent when - // reconnecting - if (command.endsWith('.enable') || ['Runtime.addBinding', 'Target.setDiscoverTargets'].includes(command)) { - debug('registering enable command', command) - const obj: EnableCommand = { - command, - } - - if (params) { - obj.params = params - } - - if (sessionId) { - obj.sessionId = sessionId - } - - enableCommands.push(obj) + if (sessionId) { + obj.sessionId = sessionId } - if (connected) { - try { - return await cri.send(command, params, sessionId) - } catch (err) { - debug('Encountered error on send %o', { command, params, sessionId, 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)) { - debug('error classified as not WEBSOCKET_NOT_OPEN_RE, rethrowing') - throw err - } + this.enableCommands.push(obj) + } - debug('error classified as WEBSOCKET_NOT_OPEN_RE; enqueuing and attempting to reconnect') + if (this._connected) { + try { + return await this.cri!.send(command, params, sessionId) + } catch (err) { + debug('Encountered error on send %o', { command, params, sessionId, 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)) { + debug('error classified as not WEBSOCKET_NOT_OPEN_RE, rethrowing') + throw err + } - const p = enqueue() as Promise + debug('error classified as WEBSOCKET_NOT_OPEN_RE; enqueuing and attempting to reconnect') - await retryReconnect() + const p = this.enqueueCommand(command, params, sessionId) - // if enqueued commands were wiped out from the reconnect and the socket is already closed, reject the command as it will never be run - if (enqueuedCommands.length === 0 && closed) { - debug('connection was closed was trying to reconnect') + await this.retryReconnect() - return Promise.reject(new Error(`${command} will not run as browser CRI connection was reset`)) - } + // if enqueued commands were wiped out from the reconnect and the socket is already closed, reject the command as it will never be run + if (this.enqueuedCommands.length === 0 && this.closed) { + debug('connection was closed was trying to reconnect') - return p + return Promise.reject(new Error(`${command} will not run as browser CRI connection was reset`)) } - } - return enqueue() - }, + return p + } + } - on (eventName, cb) { - subscriptions.push({ eventName, cb }) - debug('registering CDP on event %o', { eventName }) + return this.enqueueCommand(command, params, sessionId) + } - cri.on(eventName, cb) - // This ensures that we are notified about the browser's network events that have been registered (e.g. service workers) - // Long term we should use flat mode entirely across all of chrome remote interface - if (eventName.startsWith('Network.')) { - browserClient?.on(eventName, cb) - } - }, - - off (eventName, cb) { - subscriptions.splice(subscriptions.findIndex((sub) => { - return sub.eventName === eventName && sub.cb === cb - }), 1) - - cri.off(eventName, cb) - // This ensures that we are notified about the browser's network events that have been registered (e.g. service workers) - // Long term we should use flat mode entirely across all of chrome remote interface - if (eventName.startsWith('Network.')) { - browserClient?.off(eventName, cb) - } - }, + public on = (eventName: T, cb: (data: ProtocolMapping.Events[T][0], sessionId?: string) => void) => { + this.subscriptions.push({ eventName, cb }) + debug('registering CDP on event %o', { eventName }) - get ws () { - return cri._ws - }, + this.cri!.on(eventName, cb) - get queue () { - return { - enableCommands, - enqueuedCommands, - subscriptions, - } - }, + if (eventName.startsWith('Network.')) { + this.browserClient?.on(eventName, cb) + } + } - get closed () { - return closed - }, + public off = (eventName: T, cb: (data: ProtocolMapping.Events[T][0], sessionId?: string) => void) => { + this.subscriptions.splice(this.subscriptions.findIndex((sub) => { + return sub.eventName === eventName && sub.cb === cb + }), 1) - get connected () { - return connected - }, + this.cri!.off(eventName, cb) + // This ensures that we are notified about the browser's network events that have been registered (e.g. service workers) + // Long term we should use flat mode entirely across all of chrome remote interface + if (eventName.startsWith('Network.')) { + this.browserClient?.off(eventName, cb) + } + } - async close () { - if (closed) { - debug('not closing, cri client is already closed %o', { closed, target }) + public close = async () => { + if (this._closed) { + debug('not closing, cri client is already closed %o', { closed: this._closed, target: this.targetId }) - return - } + return + } - debug('closing cri client %o', { closed, target }) + debug('closing cri client %o', { closed: this._closed, target: this.targetId }) - closed = true + this._closed = true - return cri.close() - .finally(() => { - debug('closed cri client %o', { closed, target }) - }) - }, + try { + await this.cri?.close() + } catch (e) { + debug('error closing cri client targeting %s: %o', this.targetId, e) + } finally { + debug('closed cri client %o', { closed: this._closed, target: this.targetId }) + } } - - return client } diff --git a/packages/server/test/integration/cdp_spec.ts b/packages/server/test/integration/cdp_spec.ts index b42f0c71f5f2..2210da30cad7 100644 --- a/packages/server/test/integration/cdp_spec.ts +++ b/packages/server/test/integration/cdp_spec.ts @@ -4,7 +4,7 @@ import Debug from 'debug' import _ from 'lodash' import WebSocket from 'ws' import { CdpCommand, CdpEvent } from '../../lib/browsers/cdp_automation' -import * as CriClient from '../../lib/browsers/cri-client' +import { CriClient } from '../../lib/browsers/cri-client' import { expect, nock } from '../spec_helper' import sinon from 'sinon' @@ -31,7 +31,7 @@ describe('CDP Clients', () => { require('mocha-banner').register() let wsSrv: WebSocket.Server - let criClient: CriClient.CriClient + let criClient: CriClient let messages: object[] let onMessage: sinon.SinonStub @@ -119,10 +119,9 @@ describe('CDP Clients', () => { target: `ws://127.0.0.1:${wsServerPort}`, onAsynchronousError, onReconnect, + onReconnectAttempt: stub, }) - criClient.onReconnectAttempt = stub - await Promise.all([ clientDisconnected(), closeWsServer(), @@ -149,10 +148,9 @@ describe('CDP Clients', () => { target: `ws://127.0.0.1:${wsServerPort}`, onAsynchronousError, onReconnect, + onReconnectAttempt: stub, }) - criClient.onReconnectAttempt = stub - await Promise.all([ clientDisconnected(), closeWsServer(), @@ -204,10 +202,9 @@ describe('CDP Clients', () => { target: `ws://127.0.0.1:${wsServerPort}`, onAsynchronousError, onReconnect, + onReconnectAttempt: stub, }) - criClient.onReconnectAttempt = stub - const send = (commands: CDPCommands[]) => { commands.forEach(({ command, params }) => { criClient.send(command, params) @@ -298,12 +295,6 @@ describe('CDP Clients', () => { const onAsynchronousError = reject const onReconnect = reject - criClient = await CriClient.create({ - target: `ws://127.0.0.1:${wsServerPort}`, - onAsynchronousError, - onReconnect, - }) - const stub = sinon.stub().onThirdCall().callsFake(async () => { criClient.close() .finally(() => { @@ -311,7 +302,12 @@ describe('CDP Clients', () => { }) }) - criClient.onReconnectAttempt = stub + criClient = await CriClient.create({ + target: `ws://127.0.0.1:${wsServerPort}`, + onAsynchronousError, + onReconnect, + onReconnectAttempt: stub, + }) await Promise.all([ clientDisconnected(), diff --git a/packages/server/test/unit/browsers/browser-cri-client_spec.ts b/packages/server/test/unit/browsers/browser-cri-client_spec.ts index 854ca3c9704b..510e553dfb02 100644 --- a/packages/server/test/unit/browsers/browser-cri-client_spec.ts +++ b/packages/server/test/unit/browsers/browser-cri-client_spec.ts @@ -1,5 +1,5 @@ import { BrowserCriClient } from '../../../lib/browsers/browser-cri-client' -import * as CriClient from '../../../lib/browsers/cri-client' +import { CriClient } from '../../../lib/browsers/cri-client' import { expect, proxyquire, sinon } from '../../spec_helper' import * as protocol from '../../../lib/browsers/protocol' import { stripAnsi } from '@packages/errors' diff --git a/packages/server/test/unit/browsers/cri-client_spec.ts b/packages/server/test/unit/browsers/cri-client_spec.ts index b9958b773f83..861a3dd351a2 100644 --- a/packages/server/test/unit/browsers/cri-client_spec.ts +++ b/packages/server/test/unit/browsers/cri-client_spec.ts @@ -1,7 +1,6 @@ import EventEmitter from 'events' -import { create } from '../../../lib/browsers/cri-client' +import type { CriClient } from '../../../lib/browsers/cri-client' import { ProtocolManagerShape } from '@packages/types' - const { expect, proxyquire, sinon } = require('../../spec_helper') const DEBUGGER_URL = 'http://foo' @@ -9,9 +8,6 @@ const HOST = '127.0.0.1' const PORT = 50505 describe('lib/browsers/cri-client', function () { - let criClient: { - create: typeof create - } let send: sinon.SinonStub let on: sinon.SinonStub let criImport: sinon.SinonStub & { @@ -46,12 +42,12 @@ describe('lib/browsers/cri-client', function () { criImport.New = sinon.stub().withArgs({ host: HOST, port: PORT, url: 'about:blank' }).resolves({ webSocketDebuggerUrl: 'http://web/socket/url' }) - criClient = proxyquire('../lib/browsers/cri-client', { + const { CriClient } = proxyquire('../lib/browsers/cri-client', { 'chrome-remote-interface': criImport, }) - getClient = ({ host, fullyManageTabs, protocolManager } = {}) => { - return criClient.create({ target: DEBUGGER_URL, host, onAsynchronousError: onError, fullyManageTabs, protocolManager }) + getClient = ({ host, fullyManageTabs, protocolManager } = {}): Promise => { + return CriClient.create({ target: DEBUGGER_URL, host, onAsynchronousError: onError, fullyManageTabs, protocolManager }) } }) diff --git a/packages/server/test/unit/browsers/firefox_spec.ts b/packages/server/test/unit/browsers/firefox_spec.ts index 40ea67b85ed4..cbca36a5748f 100644 --- a/packages/server/test/unit/browsers/firefox_spec.ts +++ b/packages/server/test/unit/browsers/firefox_spec.ts @@ -12,7 +12,7 @@ import * as firefox from '../../../lib/browsers/firefox' import firefoxUtil from '../../../lib/browsers/firefox-util' import { CdpAutomation } from '../../../lib/browsers/cdp_automation' import { BrowserCriClient } from '../../../lib/browsers/browser-cri-client' -import { CriClient } from '../../../lib/browsers/cri-client' +import { ICriClient } from '../../../lib/browsers/cri-client' const path = require('path') const _ = require('lodash') @@ -574,7 +574,7 @@ describe('lib/browsers/firefox', () => { context('#setupRemote', function () { it('correctly sets up the remote agent', async function () { - const criClientStub: CriClient = { + const criClientStub: ICriClient = { targetId: '', send: sinon.stub(), on: sinon.stub(), From a3f7a3c36bfe267983e0aef4de53300802929e81 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 27 Jun 2024 10:33:57 -0400 Subject: [PATCH 5/6] chore: Update Chrome (beta) to 127.0.6533.26 (#29760) Co-authored-by: cypress-bot[bot] <41898282+cypress-bot[bot]@users.noreply.github.com> --- browser-versions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser-versions.json b/browser-versions.json index c5fb50532208..00025b6f6423 100644 --- a/browser-versions.json +++ b/browser-versions.json @@ -1,5 +1,5 @@ { - "chrome:beta": "127.0.6533.17", + "chrome:beta": "127.0.6533.26", "chrome:stable": "126.0.6478.126", "chrome:minimum": "64.0.3282.0" } From ed85a2ce0fba0e8364246aa39ec16a6e599f151f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 27 Jun 2024 10:34:17 -0400 Subject: [PATCH 6/6] chore: Update v8 snapshot cache (#29758) * chore: updating v8 snapshot cache * chore: updating v8 snapshot cache --------- Co-authored-by: cypress-bot[bot] <+cypress-bot[bot]@users.noreply.github.com> --- tooling/v8-snapshot/cache/darwin/snapshot-meta.json | 2 +- tooling/v8-snapshot/cache/linux/snapshot-meta.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tooling/v8-snapshot/cache/darwin/snapshot-meta.json b/tooling/v8-snapshot/cache/darwin/snapshot-meta.json index b134dcb291d6..b3da56f950d0 100644 --- a/tooling/v8-snapshot/cache/darwin/snapshot-meta.json +++ b/tooling/v8-snapshot/cache/darwin/snapshot-meta.json @@ -4352,5 +4352,5 @@ "./tooling/v8-snapshot/cache/darwin/snapshot-entry.js" ], "deferredHashFile": "yarn.lock", - "deferredHash": "f7ad8b998e773f23e154b5486d7bc410d7b49da55b20e00c6d43baeff83f4bef" + "deferredHash": "bdd01b58446448ba4a513a11e903d13995b58a74ea32586ed0aa570469ba236d" } \ No newline at end of file diff --git a/tooling/v8-snapshot/cache/linux/snapshot-meta.json b/tooling/v8-snapshot/cache/linux/snapshot-meta.json index 07a26b19f970..d69c86ee9e61 100644 --- a/tooling/v8-snapshot/cache/linux/snapshot-meta.json +++ b/tooling/v8-snapshot/cache/linux/snapshot-meta.json @@ -4355,5 +4355,5 @@ "./tooling/v8-snapshot/cache/linux/snapshot-entry.js" ], "deferredHashFile": "yarn.lock", - "deferredHash": "f7ad8b998e773f23e154b5486d7bc410d7b49da55b20e00c6d43baeff83f4bef" + "deferredHash": "bdd01b58446448ba4a513a11e903d13995b58a74ea32586ed0aa570469ba236d" } \ No newline at end of file