@@ -317,21 +317,175 @@ function deselectTest(row, closeDetails) {
317
317
history . replaceState ( null , null , '#' ) ;
318
318
}
319
319
320
- function testHasClients ( testData ) {
321
- return testData . clientInfo && Object . getOwnPropertyNames ( testData . clientInfo ) . length > 0 ;
320
+ function testHasClients ( testData , suiteData ) {
321
+ if ( testData . clientInfo && Object . getOwnPropertyNames ( testData . clientInfo ) . length > 0 ) {
322
+ return true ;
323
+ }
324
+
325
+ if ( testData . summaryResult && testData . summaryResult . clientLogs &&
326
+ Object . keys ( testData . summaryResult . clientLogs ) . length > 0 ) {
327
+ return true ;
328
+ }
329
+
330
+ if ( suiteData && suiteData . sharedClients ) {
331
+ for ( let clientID in suiteData . sharedClients ) {
332
+ let clientName = suiteData . sharedClients [ clientID ] . name ;
333
+ if ( testData . name . includes ( clientName ) ) {
334
+ return true ;
335
+ }
336
+ }
337
+ }
338
+
339
+ return false ;
322
340
}
323
341
324
342
// formatClientLogsList turns the clientInfo part of a test into a list of links.
325
343
function formatClientLogsList ( suiteData , testIndex , clientInfo ) {
326
344
let links = [ ] ;
327
- for ( let instanceID in clientInfo ) {
328
- let instanceInfo = clientInfo [ instanceID ] ;
329
- let logfile = routes . resultsRoot + instanceInfo . logFile ;
330
- let url = routes . clientLog ( suiteData . suiteID , suiteData . name , testIndex , logfile ) ;
331
- let link = html . makeLink ( url , instanceInfo . name ) ;
332
- link . classList . add ( 'log-link' ) ;
333
- links . push ( link . outerHTML ) ;
345
+ let testCase = suiteData . testCases [ testIndex ] ;
346
+ let usedSharedClients = new Set ( ) ; // Track which shared clients were used in this test
347
+
348
+ // First, check if the test has specific information about which shared clients it used
349
+ if ( testCase && testCase . summaryResult && testCase . summaryResult . clientLogs ) {
350
+ for ( let clientID in testCase . summaryResult . clientLogs ) {
351
+ usedSharedClients . add ( clientID ) ;
352
+ }
353
+ }
354
+
355
+ // Handle clients listed directly in the test's clientInfo
356
+ if ( clientInfo ) {
357
+ for ( let instanceID in clientInfo ) {
358
+ let instanceInfo = clientInfo [ instanceID ] ;
359
+
360
+ // Skip if no log file
361
+ if ( ! instanceInfo . logFile ) {
362
+ continue ;
363
+ }
364
+
365
+ // If it's a shared client, mark it as used
366
+ if ( instanceInfo . isShared ) {
367
+ usedSharedClients . add ( instanceID ) ;
368
+ }
369
+
370
+ let logfile = routes . resultsRoot + instanceInfo . logFile ;
371
+ let url = routes . clientLog ( suiteData . suiteID , suiteData . name , testIndex , logfile ) ;
372
+
373
+ // Check if this is a shared client with a log segment
374
+ let hasSegment = testCase &&
375
+ testCase . summaryResult &&
376
+ testCase . summaryResult . clientLogs &&
377
+ testCase . summaryResult . clientLogs [ instanceID ] ;
378
+
379
+ if ( hasSegment ) {
380
+ // If we have a log segment, update the URL to include the line numbers
381
+ const clientLogInfo = testCase . summaryResult . clientLogs [ instanceID ] ;
382
+
383
+ // Use line numbers from the backend
384
+ url += `#L${ clientLogInfo . startLine } -${ clientLogInfo . endLine } ` ;
385
+ }
386
+
387
+ // Add "(shared)" indicator for shared clients
388
+ let clientName = instanceInfo . name ;
389
+ if ( instanceInfo . isShared || hasSegment ) {
390
+ clientName += " (shared)" ;
391
+ }
392
+
393
+ let link = html . makeLink ( url , clientName ) ;
394
+ link . classList . add ( 'log-link' ) ;
395
+ if ( instanceInfo . isShared ) {
396
+ link . classList . add ( 'shared-client-log' ) ;
397
+ }
398
+ links . push ( link . outerHTML ) ;
399
+ }
400
+ }
401
+
402
+ // For backward compatibility - if test name includes client name, add that client
403
+ // This handles the case where tests don't yet have clientInfo or clientLogs properly populated
404
+ if ( suiteData . sharedClients ) {
405
+
406
+ // First try to match by existing client logs
407
+ if ( usedSharedClients . size === 0 ) {
408
+ // Group clients by name to identify if there are multiple of the same type
409
+ let clientsByName = { } ;
410
+ for ( let instanceID in suiteData . sharedClients ) {
411
+ let sharedClient = suiteData . sharedClients [ instanceID ] ;
412
+ if ( ! sharedClient . logFile ) continue ; // Skip if no log file
413
+
414
+ // Add to the clients by name map
415
+ if ( ! clientsByName [ sharedClient . name ] ) {
416
+ clientsByName [ sharedClient . name ] = [ ] ;
417
+ }
418
+ clientsByName [ sharedClient . name ] . push ( { id : instanceID , client : sharedClient } ) ;
419
+ }
420
+
421
+ // Now check test name for client match, but only if there's exactly one client of that type
422
+ for ( let clientName in clientsByName ) {
423
+ if ( testCase . name . includes ( clientName ) && clientsByName [ clientName ] . length === 1 ) {
424
+ // If there's exactly one client of this type, it's safe to auto-register
425
+ let instanceID = clientsByName [ clientName ] [ 0 ] . id ;
426
+ usedSharedClients . add ( instanceID ) ;
427
+ }
428
+ }
429
+ }
430
+
431
+ // Now add all the used shared clients that haven't been handled yet
432
+ for ( let instanceID in suiteData . sharedClients ) {
433
+ // First check if this client is explicitly registered in the test's clientLogs
434
+ // This is the most reliable way to determine if a client was used in a test
435
+ const explicitlyRegistered = testCase &&
436
+ testCase . summaryResult &&
437
+ testCase . summaryResult . clientLogs &&
438
+ testCase . summaryResult . clientLogs [ instanceID ] ;
439
+
440
+ if ( explicitlyRegistered ) {
441
+ usedSharedClients . add ( instanceID ) ;
442
+ }
443
+
444
+ // Skip if not used by this test (based on explicit tracking or name matching)
445
+ if ( ! usedSharedClients . has ( instanceID ) ) {
446
+ continue ;
447
+ }
448
+
449
+ // Skip clients already handled in clientInfo
450
+ if ( clientInfo && instanceID in clientInfo ) {
451
+ continue ;
452
+ }
453
+
454
+ let sharedClient = suiteData . sharedClients [ instanceID ] ;
455
+
456
+ // Skip if no log file
457
+ if ( ! sharedClient . logFile ) {
458
+ continue ;
459
+ }
460
+
461
+ // Create a link to the full log file for this shared client
462
+ let logfile = routes . resultsRoot + sharedClient . logFile ;
463
+ let url = routes . clientLog ( suiteData . suiteID , suiteData . name , testIndex , logfile ) ;
464
+
465
+ // Check if we have specific log segments for this client in this test
466
+ let hasSegment = testCase &&
467
+ testCase . summaryResult &&
468
+ testCase . summaryResult . clientLogs &&
469
+ testCase . summaryResult . clientLogs [ instanceID ] ;
470
+
471
+ if ( hasSegment ) {
472
+ // If we have a log segment, update the URL to include the line numbers
473
+ const clientLogInfo = testCase . summaryResult . clientLogs [ instanceID ] ;
474
+
475
+ // Only add line range if we have valid line numbers (both > 0)
476
+ if ( clientLogInfo . startLine > 0 && clientLogInfo . endLine > 0 ) {
477
+ url += `#L${ clientLogInfo . startLine } -${ clientLogInfo . endLine } ` ;
478
+ }
479
+ }
480
+
481
+ let clientName = sharedClient . name + " (shared)" ;
482
+ let link = html . makeLink ( url , clientName ) ;
483
+
484
+ link . classList . add ( 'log-link' , 'shared-client-log' ) ;
485
+ links . push ( link . outerHTML ) ;
486
+ }
334
487
}
488
+
335
489
return links . join ( ', ' ) ;
336
490
}
337
491
@@ -360,7 +514,7 @@ function formatTestDetails(suiteData, row) {
360
514
p . innerHTML = formatTestStatus ( d . summaryResult ) ;
361
515
container . appendChild ( p ) ;
362
516
}
363
- if ( ! row . column ( 'logs:name' ) . responsiveHidden ( ) && testHasClients ( d ) ) {
517
+ if ( ! row . column ( 'logs:name' ) . responsiveHidden ( ) && testHasClients ( d , suiteData ) ) {
364
518
let p = document . createElement ( 'p' ) ;
365
519
p . innerHTML = '<b>Clients:</b> ' + formatClientLogsList ( suiteData , d . testIndex , d . clientInfo ) ;
366
520
container . appendChild ( p ) ;
0 commit comments