@@ -387,16 +387,125 @@ describe('performance', () => {
387387 { performanceCrux : false } ,
388388 ) ;
389389 } ) ;
390+
391+ it ( 'fetches CrUX data for desktop and includes it in the summary' , async ( ) => {
392+ const rawData = loadTraceAsBuffer ( 'web-dev-with-commit.json.gz' ) ;
393+ await withMcpContext ( async ( response , context ) => {
394+ context . setIsRunningPerformanceTrace ( true ) ;
395+ const selectedPage = context . getSelectedPptrPage ( ) ;
396+ sinon . stub ( selectedPage . tracing , 'stop' ) . resolves ( rawData ) ;
397+
398+ const fetchStub = globalThis . fetch as sinon . SinonStub ;
399+ fetchStub . resetHistory ( ) ;
400+ fetchStub . callsFake ( async ( url , options ) => {
401+ const body = options ?. body ? JSON . parse ( options . body as string ) : { } ;
402+ const requestedUrl = body . url || body . origin || 'https://web.dev/' ;
403+ const lcp = body . formFactor === 'DESKTOP' ? 1000 : 2595 ;
404+ return new Response (
405+ JSON . stringify ( cruxResponseFixture ( requestedUrl , lcp ) ) ,
406+ {
407+ status : 200 ,
408+ headers : { 'Content-Type' : 'application/json' } ,
409+ } ,
410+ ) ;
411+ } ) ;
412+
413+ await stopTrace . handler (
414+ { params : { } , page : context . getSelectedMcpPage ( ) } ,
415+ response ,
416+ context ,
417+ ) ;
418+
419+ const result = await response . handle ( 'performance_stop_trace' , context ) ;
420+ const fullOutput = result . content
421+ . map ( c => ( c . type === 'text' ? c . text : '' ) )
422+ . join ( '\n' ) ;
423+
424+ assert . ok ( fetchStub . called , 'CrUX fetch should have been called' ) ;
425+ assert . ok (
426+ fullOutput . includes ( 'Metrics (field / real users)' ) ,
427+ 'Summary should include field data' ,
428+ ) ;
429+ assert . ok (
430+ fullOutput . includes ( 'LCP: 1000 ms' ) ,
431+ 'Summary should include desktop LCP value' ,
432+ ) ;
433+ } ) ;
434+ } ) ;
435+
436+ it ( 'fetches CrUX data for mobile and includes it in the summary' , async ( ) => {
437+ const rawData = loadTraceAsBuffer ( 'web-dev-with-commit.json.gz' ) ;
438+ // Use a unique URL to avoid cache issues
439+ const jsonString = new TextDecoder ( ) . decode ( rawData ) ;
440+ const modifiedJsonString = jsonString . replaceAll (
441+ 'https://web.dev/' ,
442+ 'https://mobile.web.dev/' ,
443+ ) ;
444+ const modifiedData = new TextEncoder ( ) . encode ( modifiedJsonString ) ;
445+
446+ await withMcpContext ( async ( response , context ) => {
447+ context . setIsRunningPerformanceTrace ( true ) ;
448+ const selectedPage = context . getSelectedPptrPage ( ) ;
449+ sinon . stub ( selectedPage . tracing , 'stop' ) . resolves ( modifiedData ) ;
450+
451+ // Emulate mobile
452+ await context . emulate ( {
453+ viewport : {
454+ width : 375 ,
455+ height : 667 ,
456+ isMobile : true ,
457+ hasTouch : true ,
458+ deviceScaleFactor : 2 ,
459+ } ,
460+ } ) ;
461+
462+ const fetchStub = globalThis . fetch as sinon . SinonStub ;
463+ fetchStub . resetHistory ( ) ;
464+ fetchStub . callsFake ( async ( url , options ) => {
465+ const body = options ?. body ? JSON . parse ( options . body as string ) : { } ;
466+ const requestedUrl = body . url || body . origin || 'https://web.dev/' ;
467+ const lcp = body . formFactor === 'PHONE' ? 2000 : 2595 ;
468+ return new Response (
469+ JSON . stringify ( cruxResponseFixture ( requestedUrl , lcp ) ) ,
470+ {
471+ status : 200 ,
472+ headers : { 'Content-Type' : 'application/json' } ,
473+ } ,
474+ ) ;
475+ } ) ;
476+
477+ await stopTrace . handler (
478+ { params : { } , page : context . getSelectedMcpPage ( ) } ,
479+ response ,
480+ context ,
481+ ) ;
482+
483+ const result = await response . handle ( 'performance_stop_trace' , context ) ;
484+ const fullOutput = result . content
485+ . map ( c => ( c . type === 'text' ? c . text : '' ) )
486+ . join ( '\n' ) ;
487+
488+ assert . ok ( fetchStub . called , 'CrUX fetch should have been called' ) ;
489+ assert . ok (
490+ fullOutput . includes ( 'Metrics (field / real users)' ) ,
491+ 'Summary should include field data' ,
492+ ) ;
493+ assert . ok (
494+ fullOutput . includes ( 'LCP: 2000 ms' ) ,
495+ 'Summary should include mobile LCP value' ,
496+ ) ;
497+ } ) ;
498+ } ) ;
390499 } ) ;
391500} ) ;
392501
393- function cruxResponseFixture ( ) {
502+ function cruxResponseFixture ( url = 'https://web.dev/' , lcp = 2595 ) {
394503 // Ideally we could use `mockResponse` from 'chrome-devtools-frontend/front_end/models/crux-manager/CrUXManager.test.ts'
395504 // But test files are not published in the cdtf npm package.
396505 return {
397506 record : {
398507 key : {
399- url : 'https://web.dev/' ,
508+ url,
400509 } ,
401510 metrics : {
402511 form_factors : {
@@ -408,7 +517,7 @@ function cruxResponseFixture() {
408517 { start : 2500 , end : 4000 , density : 0.163 } ,
409518 { start : 4000 , density : 0.1061 } ,
410519 ] ,
411- percentiles : { p75 : 2595 } ,
520+ percentiles : { p75 : lcp } ,
412521 } ,
413522 largest_contentful_paint_image_element_render_delay : {
414523 percentiles : { p75 : 786 } ,
0 commit comments