@@ -30,9 +30,10 @@ import { helper } from '../helper';
3030import * as network from '../network' ;
3131import { nullProgress } from '../progress' ;
3232
33+ import { Page } from '../page' ;
34+
3335import type { RegisteredListener } from '@utils/eventsHelper' ;
3436import type { APIRequestEvent , APIRequestFinishedEvent } from '../fetch' ;
35- import type { Page } from '../page' ;
3637import type { Worker } from '../page' ;
3738import type { HeadersArray , LifecycleEvent } from '../types' ;
3839import type * as har from '@trace/har' ;
@@ -70,6 +71,7 @@ export class HarTracer {
7071 private _eventListeners : RegisteredListener [ ] = [ ] ;
7172 private _started = false ;
7273 private _entrySymbol : symbol ;
74+ private _webSocketEntries = new Map < /* requestId */ string , har . Entry > ( ) ;
7375 private _baseURL : string | undefined ;
7476 private _page : Page | null ;
7577
@@ -102,7 +104,10 @@ export class HarTracer {
102104 ] ;
103105 if ( this . _context instanceof BrowserContext ) {
104106 this . _eventListeners . push (
105- eventsHelper . addEventListener ( this . _context , BrowserContext . Events . Page , ( page : Page ) => this . _createPageEntryIfNeeded ( page ) ) ,
107+ eventsHelper . addEventListener ( this . _context , BrowserContext . Events . Page , ( page : Page ) => {
108+ this . _addPageEventListeners ( page ) ;
109+ this . _createPageEntryIfNeeded ( page ) ;
110+ } ) ,
106111 eventsHelper . addEventListener ( this . _context , BrowserContext . Events . Request , ( request : network . Request ) => this . _onRequest ( request ) ) ,
107112 eventsHelper . addEventListener ( this . _context , BrowserContext . Events . RequestFinished , ( { request, response } ) => this . _onRequestFinished ( request , response ) . catch ( ( ) => { } ) ) ,
108113 eventsHelper . addEventListener ( this . _context , BrowserContext . Events . RequestFailed , request => this . _onRequestFailed ( request ) ) ,
@@ -111,11 +116,21 @@ export class HarTracer {
111116 eventsHelper . addEventListener ( this . _context , BrowserContext . Events . RequestFulfilled , request => this . _onRequestFulfilled ( request ) ) ,
112117 eventsHelper . addEventListener ( this . _context , BrowserContext . Events . RequestContinued , request => this . _onRequestContinued ( request ) ) ,
113118 ) ;
114- for ( const page of this . _context . pages ( ) )
119+ for ( const page of this . _context . pages ( ) ) {
120+ this . _addPageEventListeners ( page ) ;
115121 this . _createPageEntryIfNeeded ( page ) ;
122+ }
116123 }
117124 }
118125
126+ private _addPageEventListeners ( page : Page ) {
127+ if ( this . _page && page !== this . _page )
128+ return ;
129+ this . _eventListeners . push (
130+ eventsHelper . addEventListener ( page , Page . Events . WebSocket , ( webSocket : network . WebSocket ) => this . _onWebSocket ( page , webSocket ) ) ,
131+ ) ;
132+ }
133+
119134 private _shouldIncludeEntryWithUrl ( urlString : string ) {
120135 return ! this . _options . urlFilter || urlMatches ( this . _baseURL , urlString , this . _options . urlFilter ) ;
121136 }
@@ -281,6 +296,10 @@ export class HarTracer {
281296 fromEntry . response . redirectURL = request . url ( ) ;
282297 }
283298 ( request as any ) [ this . _entrySymbol ] = harEntry ;
299+ // In Firefox, WebSockets have additional events once opened.
300+ // In Chromium and WebKit, WebSockets have an entirely different lifecycle and won't reach this.
301+ if ( request . resourceType ( ) === 'websocket' )
302+ this . _webSocketEntries . set ( request . requestId ( ) , harEntry ) ;
284303 assert ( this . _started ) ;
285304 this . _delegate . onEntryStarted ( harEntry ) ;
286305 }
@@ -362,6 +381,11 @@ export class HarTracer {
362381 } ) . catch ( ( ) => {
363382 compressionCalculationBarrier ?. setDecodedBodySize ( 0 ) ;
364383 } ) . then ( ( ) => {
384+ // In Firefox, WebSockets have additional events once opened.
385+ // In Chromium and WebKit, WebSockets have an entirely different lifecycle and won't reach this.
386+ if ( request . resourceType ( ) === 'websocket' )
387+ return ;
388+
365389 if ( this . _started )
366390 this . _delegate . onEntryFinished ( harEntry ) ;
367391 } ) ;
@@ -418,6 +442,74 @@ export class HarTracer {
418442 harEntry . _wasContinued = true ;
419443 }
420444
445+ private _onWebSocket ( page : Page , webSocket : network . WebSocket ) {
446+ if ( ! this . _shouldIncludeEntryWithUrl ( webSocket . url ( ) ) )
447+ return ;
448+ const url = network . parseURL ( webSocket . url ( ) ) ;
449+ if ( ! url )
450+ return ;
451+
452+ let harEntry : har . Entry | undefined = undefined ;
453+ let matchingRequestId : string | undefined = undefined ;
454+
455+ // Only listen for the rest of the WebSocket lifecycle once it's been opened.
456+ const addRemainingListeners = ( ) => {
457+ this . _eventListeners . push (
458+ eventsHelper . addEventListener ( webSocket , network . WebSocket . Events . Response , ( { status, statusText, headers } : { status : number , statusText : string , headers : HeadersArray } ) => {
459+ harEntry ! . response . status = status ;
460+ harEntry ! . response . statusText = statusText ;
461+ this . _recordResponseHeaders ( harEntry ! , headers ) ;
462+ } ) ,
463+ eventsHelper . addEventListener ( webSocket , network . WebSocket . Events . FrameSent , ( { opcode, data, timestamp } : { opcode : number , data : string , timestamp : number } ) => {
464+ harEntry ! . _webSocketMessages ! . push ( { type : 'send' , time : timestamp , opcode, data } ) ;
465+ } ) ,
466+ eventsHelper . addEventListener ( webSocket , network . WebSocket . Events . FrameReceived , ( { opcode, data, timestamp } : { opcode : number , data : string , timestamp : number } ) => {
467+ harEntry ! . _webSocketMessages ! . push ( { type : 'receive' , time : timestamp , opcode, data } ) ;
468+ } ) ,
469+ eventsHelper . addEventListener ( webSocket , network . WebSocket . Events . SocketError , ( errorMessage : string ) => {
470+ harEntry ! . response . _failureText = errorMessage ;
471+ } ) ,
472+ eventsHelper . addEventListener ( webSocket , network . WebSocket . Events . Close , ( ) => {
473+ if ( matchingRequestId !== undefined )
474+ this . _webSocketEntries . delete ( matchingRequestId ) ;
475+
476+ if ( this . _started )
477+ this . _delegate . onEntryFinished ( harEntry ! ) ;
478+ } ) ,
479+ ) ;
480+ } ;
481+
482+ this . _eventListeners . push (
483+ // In Firefox, WebSocket lifecycle events actually come through the normal Network events.
484+ eventsHelper . addEventListener ( webSocket , network . WebSocket . Events . Open , ( { requestId } : { requestId : string } ) => {
485+ matchingRequestId = requestId ;
486+
487+ harEntry = this . _webSocketEntries . get ( matchingRequestId ) ;
488+ if ( ! harEntry )
489+ return ;
490+
491+ harEntry . request . url = webSocket . url ( ) ;
492+ harEntry . _resourceType = 'websocket' ;
493+ harEntry . _webSocketMessages = [ ] ;
494+
495+ addRemainingListeners ( ) ;
496+ } ) ,
497+ // In Chromium and WebKit, WebSocket lifecycle events are entirely separate from normal Network events.
498+ eventsHelper . addEventListener ( webSocket , network . WebSocket . Events . Request , ( { headers } : { headers : HeadersArray } ) => {
499+ const pageEntry = this . _createPageEntryIfNeeded ( page ) ;
500+ harEntry = createHarEntry ( pageEntry ?. id , 'GET' , url , page . mainFrame ( ) . guid , this . _options ) ;
501+ harEntry . _resourceType = 'websocket' ;
502+ harEntry . _webSocketMessages = [ ] ;
503+ this . _recordRequestHeadersAndCookies ( harEntry , headers ) ;
504+
505+ if ( this . _started )
506+ this . _delegate . onEntryStarted ( harEntry ) ;
507+
508+ addRemainingListeners ( ) ;
509+ } ) ,
510+ ) ;
511+ }
512+
421513 private _storeResponseContent ( buffer : Buffer | undefined , content : har . Content , resourceType : string ) {
422514 if ( ! buffer ) {
423515 content . size = 0 ;
0 commit comments