-
Notifications
You must be signed in to change notification settings - Fork 68
fix: use own cdp implementation #1129
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
Conversation
8cc1b46 to
1223919
Compare
commit: |
9232a6d to
56b81a4
Compare
| "vite": "5.1.6", | ||
| "wait-port": "1.1.0", | ||
| "worker-farm": "1.7.0", | ||
| "ws": "8.18.3", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
using this version because we already have it as "@testplane/[email protected]" dependency
| import * as logger from "../../utils/logger"; | ||
| import { EventEmitter } from "events"; | ||
|
|
||
| export class CDPEventEmitter<Events extends { [key in keyof Events]: unknown }> extends EventEmitter { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| public readonly profiler: CDPProfiler; | ||
|
|
||
| static async create(browser: Browser): Promise<CDP | null> { | ||
| if (!browser.publicAPI.isChromium) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cdp is only available in chromium-based browsers
| import { CDPEventEmitter } from "./emitter"; | ||
| import type { CDPDebuggerLocation, CDPSessionId, CDPScriptCoverage, CDPProfile } from "./types"; | ||
|
|
||
| interface StartPreciseCoverageRequest { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
copied typings from https://chromedevtools.github.io/devtools-protocol/1-3/Profiler/
| return new Promise(resolve => setTimeout(resolve, delay).unref()); | ||
| }; | ||
|
|
||
| export const extractRequestIdFromBrokenResponse = (message: string): number | null => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as we have seen, we might get broken message, which can't be parsed with json.parse
This function extracts request "id", so we can stop waiting for response for it until timeout and just retry it
src/browser/cdp/connection.ts
Outdated
| * @description creates ws connection with retries or returns existing one | ||
| * @note Concurrent requests with same params produce same ws connection | ||
| */ | ||
| private async getWsConnection(): Promise<WebSocket> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Retries "tryToEstablishWsConnection"
src/browser/cdp/connection.ts
Outdated
| } | ||
|
|
||
| /** @description establishes ws connection, sends request with timeout and waits for response */ | ||
| private async tryToSendRequest( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes single try to send a request and resolves its response
| } | ||
|
|
||
| /** @description Performs high-level CDP request with retries and timeouts */ | ||
| async request<T>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
retries "tryToSendRequest"
| /** @description Closes websocket connection, terminating all pending requests */ | ||
| close(): void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should only be called once, when we would not need this connection in the future (for example, when deleting browser session)
src/browser/cdp/connection.ts
Outdated
| if (!this.isWebSocketActive(ws)) { | ||
| clearInterval(pingInterval); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
on each tick we have to check it first, because we might catch a frame where this callback is called when this pingInterval is no longer active because ws connection is reestablished
fc679c0 to
0805224
Compare
0e0c4ed to
641a566
Compare
| public readonly target: CDPTarget; | ||
| public readonly profiler: CDPProfiler; | ||
|
|
||
| static async create(browser: Browser): Promise<CDP | null> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This may be controversial, but IMO create methods don't bring any value and only pollute codebase. But not critical to me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its async
Since we can't make async constructor, i think its better to encapsulate "get browser ws endpoint" logic to "cdp" scope
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a common complaint, but you don't need async constructor. You can get all necessary values before calling the constructor and just pass all those values
| } | ||
|
|
||
| /** @description Creates CDPConnection without establishing it */ | ||
| static async create(browser: Browser): Promise<CDPConnection> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here as well I think there's little value in create() method and in receiving our Browser god-object if we in fact only need headers and wsEndpoint. This increases code coupling. For example when we chose to expose this API, users will be forced to create our Browser object, when they could just pass the endpoint and it would work fine
Not critical
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think i will leave it be for now, and its not critical while it is internals
We can change it when (and if) we will decide to make it public
| ws.close(); | ||
| done( | ||
| new CDPTimeoutError({ | ||
| message: `Couldn't establish CDP connection to "${endpoint}" in ${CDP_CONNECTION_TIMEOUT}ms`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be really nice if we had more actionable errors here and in all other places as well. I think a good error message states clearly "what happenned and why" and "what can be done to fix". Imagining myself as a user receiving the "Couldn't establish CDP connection to ... in 1000ms", I'd be pretty puzzled.
Not critical, but very nice to have. I don't think that all these new errors are actionable right now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would love to leave actionable errors here, explaining "why it happened" and "what to do", if i knew, why could it happen and how to resolve these issues.
My plan to these kind of errors where you dont know, why it might happen, is the following:
- Release this version with non-actionable errors
- Saturate error messages with actionability, based on user stories (user comes with this error, we check, why it happened and what to do in this case, then update error message)
|
|
||
| this._wsConnectionStatus = WsConnectionStatus.CONNECTING; | ||
|
|
||
| this._wsConnectionPromise = (async (): Promise<WebSocket> => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we move this iife to a helper/protected method with human readable name? The logic here is pretty hard, especially the part where you reassign wsConnectionPromise inside.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
especially the part where you reassign wsConnectionPromise inside
Thats why i keep it inside, so we could see all complex _wsConnectionPromise transitions scoped to just one method, instead it being reassigned in multiple ones
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would move domain implementations to a separate directory "domains". Now, they are mixed with types, utils, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its done in next PR
| await cdpTarget.activateTarget(incognitoWindowId); | ||
| } | ||
|
|
||
| if (this._session.isBidi) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We got rid of this in master, because puppeteer could do cleanup of unnecessary contexts with bidi, too. Would it remain possible with our own implementation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will take a look at the code
test/src/browser/cdp/connection.ts
Outdated
| }); | ||
|
|
||
| it("should handle connection termination and reconnect", async () => { | ||
| const result1 = await connection.request("Successfull.Method"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor typo — successful, not critical
641a566 to
de92444
Compare
de92444 to
bb74561
Compare


No description provided.