@@ -189,6 +189,181 @@ describe('SessionCapturer', () => {
189189 } )
190190 } )
191191
192+ describe ( 'console log capture' , ( ) => {
193+ /**
194+ * Test: All console methods (log, info, warn, error) are properly captured
195+ * Validates: method type, arguments, source attribution, and timestamp
196+ */
197+ it ( 'should capture all console methods from test code' , ( ) => {
198+ const capturer = new SessionCapturer ( )
199+ const initialLength = capturer . consoleLogs . length
200+
201+ // Execute all console methods with different argument patterns
202+ console . log ( 'Log message' )
203+ console . info ( 'Info message' , 'with multiple' , 'arguments' )
204+ console . warn ( 'Warning message' )
205+ console . error ( 'Error message' )
206+
207+ // Verify all 4 logs were captured
208+ expect ( capturer . consoleLogs ) . toHaveLength ( initialLength + 4 )
209+
210+ // Validate console.log capture
211+ const logEntry = capturer . consoleLogs [ initialLength ]
212+ expect ( logEntry . type ) . toBe ( 'log' )
213+ expect ( logEntry . args ) . toEqual ( [ 'Log message' ] )
214+ expect ( logEntry . source ) . toBe ( 'test' )
215+ expect ( logEntry . timestamp ) . toBeDefined ( )
216+
217+ // Validate console.info capture with multiple arguments
218+ const infoEntry = capturer . consoleLogs [ initialLength + 1 ]
219+ expect ( infoEntry . type ) . toBe ( 'info' )
220+ expect ( infoEntry . args ) . toEqual ( [
221+ 'Info message' ,
222+ 'with multiple' ,
223+ 'arguments'
224+ ] )
225+ expect ( infoEntry . source ) . toBe ( 'test' )
226+
227+ // Validate console.warn capture
228+ const warnEntry = capturer . consoleLogs [ initialLength + 2 ]
229+ expect ( warnEntry . type ) . toBe ( 'warn' )
230+ expect ( warnEntry . args ) . toEqual ( [ 'Warning message' ] )
231+ expect ( warnEntry . source ) . toBe ( 'test' )
232+
233+ // Validate console.error capture
234+ const errorEntry = capturer . consoleLogs [ initialLength + 3 ]
235+ expect ( errorEntry . type ) . toBe ( 'error' )
236+ expect ( errorEntry . args ) . toEqual ( [ 'Error message' ] )
237+ expect ( errorEntry . source ) . toBe ( 'test' )
238+ } )
239+
240+ /**
241+ * Test: Complex argument types are handled correctly
242+ * Validates: object serialization, circular reference handling, null/undefined conversion
243+ */
244+ it ( 'should handle various argument types' , ( ) => {
245+ const capturer = new SessionCapturer ( )
246+ const initialLength = capturer . consoleLogs . length
247+
248+ // Create test data: object, circular reference
249+ const testObject = { foo : 'bar' , nested : { value : 42 } }
250+ const circular : any = { a : 1 }
251+ circular . self = circular
252+
253+ // Log various argument types in sequence
254+ console . log ( 'Object:' , testObject )
255+ console . log ( 'Circular:' , circular )
256+ console . log ( 'Values:' , null , undefined )
257+
258+ expect ( capturer . consoleLogs ) . toHaveLength ( initialLength + 3 )
259+
260+ // Verify object is stringified to JSON
261+ const objLog = capturer . consoleLogs [ initialLength ]
262+ expect ( objLog . args [ 0 ] ) . toBe ( 'Object:' )
263+ expect ( objLog . args [ 1 ] ) . toBe ( JSON . stringify ( testObject ) )
264+
265+ // Verify circular references don't crash and fallback to [object Object]
266+ const circularLog = capturer . consoleLogs [ initialLength + 1 ]
267+ expect ( circularLog . args [ 1 ] ) . toBe ( '[object Object]' )
268+
269+ // Verify null and undefined are converted to strings
270+ const nullLog = capturer . consoleLogs [ initialLength + 2 ]
271+ expect ( nullLog . args ) . toEqual ( [ 'Values:' , 'null' , 'undefined' ] )
272+ } )
273+
274+ /**
275+ * Test: Integration scenarios - cleanup, WebSocket transmission, browser source attribution
276+ * Validates: console restoration on cleanup, upstream WebSocket communication, source distinction
277+ */
278+ it ( 'should handle cleanup, upstream transmission, and browser source attribution' , async ( ) => {
279+ // Part 1: Test console restoration on cleanup
280+ const originalLog = console . log
281+ const originalInfo = console . info
282+ const originalWarn = console . warn
283+ const originalError = console . error
284+
285+ let capturer = new SessionCapturer ( )
286+ expect ( console . log ) . not . toBe ( originalLog )
287+
288+ capturer . cleanup ( )
289+ expect ( console . log ) . toBe ( originalLog )
290+ expect ( console . info ) . toBe ( originalInfo )
291+ expect ( console . warn ) . toBe ( originalWarn )
292+ expect ( console . error ) . toBe ( originalError )
293+
294+ // Part 2: Test WebSocket upstream transmission
295+ const mockWs = {
296+ readyState : WebSocket . OPEN ,
297+ send : vi . fn ( ) ,
298+ on : vi . fn ( ) ,
299+ close : vi . fn ( )
300+ }
301+
302+ vi . mocked ( WebSocket ) . mockImplementation ( function ( this : any ) {
303+ return mockWs
304+ } as any )
305+
306+ capturer = new SessionCapturer ( {
307+ hostname : 'localhost' ,
308+ port : 3000
309+ } )
310+
311+ console . log ( 'Test message' )
312+
313+ expect ( mockWs . send ) . toHaveBeenCalled ( )
314+ const sentData = JSON . parse (
315+ mockWs . send . mock . calls [ mockWs . send . mock . calls . length - 1 ] [ 0 ]
316+ )
317+ expect ( sentData . scope ) . toBe ( 'consoleLogs' )
318+ expect ( sentData . data ) . toHaveLength ( 1 )
319+ expect ( sentData . data [ 0 ] . args ) . toEqual ( [ 'Test message' ] )
320+ expect ( sentData . data [ 0 ] . source ) . toBe ( 'test' )
321+
322+ capturer . cleanup ( )
323+
324+ // Part 3: Test browser console logs source attribution
325+ capturer = new SessionCapturer ( )
326+ const mockBrowserLogs = [
327+ {
328+ timestamp : Date . now ( ) ,
329+ type : 'log' as const ,
330+ args : [ 'Browser log 1' ]
331+ } ,
332+ {
333+ timestamp : Date . now ( ) ,
334+ type : 'warn' as const ,
335+ args : [ 'Browser warning' ]
336+ }
337+ ]
338+
339+ const mockExecuteResult = {
340+ mutations : [ ] ,
341+ traceLogs : [ ] ,
342+ consoleLogs : mockBrowserLogs ,
343+ metadata : { url : 'http://test.com' , viewport : { } as VisualViewport }
344+ }
345+
346+ mockBrowser . execute = vi . fn ( ) . mockResolvedValue ( mockExecuteResult )
347+ mockBrowser . scriptAddPreloadScript = vi . fn ( ) . mockResolvedValue ( undefined )
348+ mockBrowser . isBidi = true
349+ await capturer . injectScript ( mockBrowser )
350+ await capturer . afterCommand (
351+ mockBrowser ,
352+ 'url' as any ,
353+ [ 'http://test.com' ] ,
354+ undefined ,
355+ undefined
356+ )
357+
358+ const browserLogs = capturer . consoleLogs . filter (
359+ ( log ) => log . source === 'browser'
360+ )
361+ expect ( browserLogs ) . toHaveLength ( 2 )
362+ expect ( browserLogs [ 0 ] . source ) . toBe ( 'browser' )
363+ expect ( browserLogs [ 1 ] . source ) . toBe ( 'browser' )
364+ } )
365+ } )
366+
192367 describe ( 'integration' , ( ) => {
193368 it ( 'should handle complete session capture workflow' , async ( ) => {
194369 const capturer = new SessionCapturer ( )
0 commit comments