Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 42 additions & 3 deletions packages/playwright-core/src/server/har/harTracer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export class HarTracer {
private _pageEntrySymbol: symbol;
private _baseURL: string | undefined;
private _page: Page | null;
private _saveOpenWebSocketMessagesFunctions = new Set<() => void>();

constructor(context: BrowserContext | APIRequestContext, page: Page | null, delegate: HarTracerDelegate, options: HarTracerOptions) {
this._context = context;
Expand Down Expand Up @@ -438,7 +439,28 @@ export class HarTracer {
const pageEntry = this._createPageEntryIfNeeded(page);
const harEntry = createHarEntry(pageEntry?.id, method, url, page.mainFrame().guid, this._options, webSocket.wallTimeMs());
harEntry._resourceType = 'websocket';
harEntry._webSocketMessages = [];

const messages: har.WebSocketMessage[] = [];
if (this._options.content === 'embed')
harEntry._webSocketMessages = messages;

let saveMessages: (() => void) | undefined;
if (this._options.content === 'attach') {
saveMessages = () => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to support websockets in the trace, we'll have to do this separately in HarRecorder vs Tracing. Let's move this out to HarRecorder through HarTracerDelegate right away?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if it's OK with you, id rather handle this when i actually work on supporting WebSocket in tracing as right now i dont really know what the best approach would be (i.e. i'd hate to implement something for it now and then have to completely change it once i start working on tracing)

i plan to do that next so this shouldn't be "stale" for too long

if (!messages.length)
return;

const buffer = Buffer.from(JSON.stringify(messages));
const sha1 = calculateSha1(buffer) + '.json';
if (this._options.includeTraceInfo)
harEntry.response.content._sha1 = sha1;
else
harEntry.response.content._file = sha1;
if (this._started)
this._delegate.onContentBlob(sha1, buffer);
};
this._saveOpenWebSocketMessagesFunctions.add(saveMessages);
}

let oldestWallTimeMs = Infinity;
let newestWallTimeMs = -Infinity;
Expand Down Expand Up @@ -475,12 +497,17 @@ export class HarTracer {
}
}),
eventsHelper.addEventListener(webSocket, network.WebSocket.Events.FrameSent, ({ opcode, data, wallTimeMs }: { opcode: number, data: string, wallTimeMs: number }) => {
harEntry._webSocketMessages!.push({ type: 'send', time: this._options.omitTiming ? -1 : wallTimeMs, opcode, data });
if (this._options.content !== 'omit')
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be a separate option? I can imagine not everyone would like websocket traffic (that is sometimes very heavy) to be recorder. Perhaps overall, an option for (not) recording websockets?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we discussed offline and felt that it's probably not supper common for a developer to want the network metadata (e.g. headers, timing, etc.) without also wanting the content for a WebSocket, so in addition to the existing content settings they could easily add a urlFilter to ignore all ws:///wss:// instead

if needed we could add an environment variable, but that might be overeager

messages.push({ type: 'send', time: this._options.omitTiming ? -1 : wallTimeMs, opcode, data });

updateTime(wallTimeMs);
}),
eventsHelper.addEventListener(webSocket, network.WebSocket.Events.FrameReceived, ({ opcode, data, wallTimeMs }: { opcode: number, data: string, wallTimeMs: number }) => {
harEntry._webSocketMessages!.push({ type: 'receive', time: this._options.omitTiming ? -1 : wallTimeMs, opcode, data });
if (this._options.content !== 'omit')
messages.push({ type: 'receive', time: this._options.omitTiming ? -1 : wallTimeMs, opcode, data });

updateTime(wallTimeMs);

if (!this._options.omitSizes) {
const length = (opcode === 1) ? Buffer.byteLength(data, 'utf8') : base64ByteLength(data);

Expand All @@ -503,6 +530,11 @@ export class HarTracer {
eventsHelper.addEventListener(webSocket, network.WebSocket.Events.Close, () => {
eventsHelper.removeEventListeners(eventListeners);

if (saveMessages) {
this._saveOpenWebSocketMessagesFunctions.delete(saveMessages);
saveMessages();
}

if (this._started)
this._delegate.onEntryFinished(harEntry);
}),
Expand Down Expand Up @@ -633,6 +665,12 @@ export class HarTracer {
}

stop() {
// Unlike other requests that have a single response, a WebSocket can receive multiple frames.
// As such, we don't finish the entry until the WebSocket is closed, which delays when the captured frames are saved.
// Make sure to save at least what has been captured so far.
for (const saveOpenWebSocketMessages of this._saveOpenWebSocketMessagesFunctions)
saveOpenWebSocketMessages();

this._started = false;
eventsHelper.removeEventListeners(this._eventListeners);
this._barrierPromises.clear();
Expand Down Expand Up @@ -665,6 +703,7 @@ export class HarTracer {
}
}
this._pageEntries = [];
this._saveOpenWebSocketMessagesFunctions.clear();
return log;
}

Expand Down
Loading
Loading