@@ -139,6 +139,44 @@ describe( 'Studio AI MCP tools', () => {
139139 tool : ReturnType < typeof resolveStudioToolDefinitions > [ number ] ,
140140 args : Record < string , unknown >
141141 ) => tool . execute ( 'tool-call-1' , args as never , new AbortController ( ) . signal , ( ) => { } ) ;
142+ const createMockPage = ( {
143+ buffer,
144+ documentHeight,
145+ } : {
146+ buffer : Buffer ;
147+ documentHeight ?: number ;
148+ } ) => ( {
149+ emulateMedia : vi . fn ( ) ,
150+ goto : vi . fn ( ) ,
151+ waitForLoadState : vi . fn ( ) . mockResolvedValue ( undefined ) ,
152+ evaluate : vi . fn ( ) . mockResolvedValue ( documentHeight ) ,
153+ addStyleTag : vi . fn ( ) ,
154+ screenshot : vi . fn ( ) . mockResolvedValue ( buffer ) ,
155+ close : vi . fn ( ) ,
156+ } ) ;
157+ const mockScreenshotBrowser = ( ...pages : Array < ReturnType < typeof createMockPage > > ) => {
158+ const newPage = vi . fn ( ) ;
159+ for ( const page of pages ) {
160+ newPage . mockResolvedValueOnce ( page ) ;
161+ }
162+ vi . mocked ( getSharedBrowser ) . mockResolvedValue ( { newPage } as never ) ;
163+ return { newPage } ;
164+ } ;
165+ type ScreenshotArtifact = {
166+ widgetProps : { alt : string ; source : { path : string ; name : string } } ;
167+ } ;
168+ const getScreenshotArtifacts = ( result : {
169+ studioArtifacts ?: Array < { widgetProps : Record < string , unknown > } > ;
170+ } ) : ScreenshotArtifact [ ] => {
171+ expect ( result . studioArtifacts ?. length ) . toBeGreaterThan ( 0 ) ;
172+ return result . studioArtifacts as unknown as ScreenshotArtifact [ ] ;
173+ } ;
174+ const cleanUpScreenshotArtifacts = ( artifacts : ScreenshotArtifact [ ] ) =>
175+ Promise . all (
176+ artifacts . map ( ( artifact ) =>
177+ rm ( path . dirname ( artifact . widgetProps . source . path ) , { recursive : true , force : true } )
178+ )
179+ ) ;
142180 const mockWpCliResponse = ( {
143181 stdout = '' ,
144182 stderr = '' ,
@@ -315,41 +353,27 @@ describe( 'Studio AI MCP tools', () => {
315353 const studioPresent = resolveStudioToolDefinitions ( {
316354 emitChatArtifacts : true ,
317355 } ) . find ( ( tool ) => tool . name === 'studio_present' ) ;
318- expect ( takeScreenshot ?. description ) . toContain ( 'ready-to-use media widget payload' ) ;
356+ expect ( takeScreenshot ?. description ) . not . toContain ( 'ready-to-use media widget payload' ) ;
319357 expect ( takeScreenshot ?. description ) . not . toContain (
320358 'This does not automatically show the screenshot to the user'
321359 ) ;
322360 expect ( takeScreenshot ?. description ) . not . toContain (
323361 'Do not use a site-preview widget as a substitute for the screenshot'
324362 ) ;
325- expect ( studioPresent ?. description ) . toContain (
326- 'Do not substitute a site-preview widget for a screenshot'
327- ) ;
363+ expect ( studioPresent ?. description ) . toContain ( 'Never call studio_present for a screenshot' ) ;
328364 } ) ;
329365
330- it ( 'keeps take_screenshot output compact while returning a media payload ' , async ( ) => {
366+ it ( 'keeps take_screenshot output compact while returning artifacts structurally ' , async ( ) => {
331367 const screenshotBuffer = Buffer . from ( 'fake-jpeg' ) ;
332- const page = {
333- emulateMedia : vi . fn ( ) ,
334- goto : vi . fn ( ) ,
335- waitForLoadState : vi . fn ( ) . mockResolvedValue ( undefined ) ,
336- evaluate : vi . fn ( ) . mockResolvedValue ( 2400 ) ,
337- addStyleTag : vi . fn ( ) ,
338- screenshot : vi . fn ( ) . mockResolvedValue ( screenshotBuffer ) ,
339- close : vi . fn ( ) ,
340- } ;
341- const browser = {
342- newPage : vi . fn ( ) . mockResolvedValue ( page ) ,
343- } ;
344- vi . mocked ( getSharedBrowser ) . mockResolvedValue ( browser as never ) ;
368+ mockScreenshotBrowser ( createMockPage ( { buffer : screenshotBuffer , documentHeight : 2400 } ) ) ;
345369
346370 const result = await getTool ( 'take_screenshot' ) . rawHandler ( {
347371 url : 'http://localhost:8903/story-time' ,
348372 } as never ) ;
349373 const text = getTextContent ( result ) ;
350374 expect ( text ) . toContain ( 'Screenshot captured' ) ;
351375 expect ( text ) . toContain ( 'desktop: captured full page (2400px tall)' ) ;
352- expect ( text ) . toContain ( 'mediaWidgetPayload= ' ) ;
376+ expect ( text ) . not . toContain ( 'mediaWidgetPayload' ) ;
353377 expect ( text ) . not . toContain ( 'When this screenshot is useful to show the user' ) ;
354378 expect ( text ) . not . toContain ( 'Path:' ) ;
355379 expect ( text ) . not . toContain ( 'File URL:' ) ;
@@ -359,28 +383,14 @@ describe( 'Studio AI MCP tools', () => {
359383 mimeType : 'image/jpeg' ,
360384 } ) ;
361385
362- const payload = JSON . parse ( text ! . split ( 'mediaWidgetPayload=' ) [ 1 ] ) as {
363- widgetProps : { source : { path : string } } ;
364- } ;
365- expect ( result . studioArtifacts ) . toEqual ( [ payload ] ) ;
366- await rm ( path . dirname ( payload . widgetProps . source . path ) , { recursive : true , force : true } ) ;
386+ const artifacts = getScreenshotArtifacts ( result ) ;
387+ expect ( artifacts [ 0 ] . widgetProps . source . name ) . toBe ( 'screenshot-desktop.jpg' ) ;
388+ await cleanUpScreenshotArtifacts ( artifacts ) ;
367389 } ) ;
368390
369391 it ( 'can force dark mode for take_screenshot captures' , async ( ) => {
370- const screenshotBuffer = Buffer . from ( 'dark-jpeg' ) ;
371- const page = {
372- emulateMedia : vi . fn ( ) ,
373- goto : vi . fn ( ) ,
374- waitForLoadState : vi . fn ( ) . mockResolvedValue ( undefined ) ,
375- evaluate : vi . fn ( ) . mockResolvedValue ( 1800 ) ,
376- addStyleTag : vi . fn ( ) ,
377- screenshot : vi . fn ( ) . mockResolvedValue ( screenshotBuffer ) ,
378- close : vi . fn ( ) ,
379- } ;
380- const browser = {
381- newPage : vi . fn ( ) . mockResolvedValue ( page ) ,
382- } ;
383- vi . mocked ( getSharedBrowser ) . mockResolvedValue ( browser as never ) ;
392+ const page = createMockPage ( { buffer : Buffer . from ( 'dark-jpeg' ) , documentHeight : 1800 } ) ;
393+ mockScreenshotBrowser ( page ) ;
384394
385395 const result = await getTool ( 'take_screenshot' ) . rawHandler ( {
386396 url : 'http://localhost:8903/story-time' ,
@@ -394,37 +404,21 @@ describe( 'Studio AI MCP tools', () => {
394404 } ) ;
395405 expect ( text ) . toContain ( 'desktop dark: captured full page (1800px tall)' ) ;
396406
397- const payload = JSON . parse ( text ! . split ( 'mediaWidgetPayload=' ) [ 1 ] ) as {
398- widgetProps : { alt : string ; source : { path : string ; name : string } } ;
399- } ;
407+ const artifacts = getScreenshotArtifacts ( result ) ;
400408 try {
401- expect ( payload . widgetProps . alt ) . toBe (
409+ expect ( artifacts [ 0 ] . widgetProps . alt ) . toBe (
402410 'Screenshot of http://localhost:8903/story-time (desktop dark)'
403411 ) ;
404- expect ( payload . widgetProps . source . name ) . toBe ( 'screenshot-desktop-dark.jpg' ) ;
412+ expect ( artifacts [ 0 ] . widgetProps . source . name ) . toBe ( 'screenshot-desktop-dark.jpg' ) ;
405413 } finally {
406- await rm ( path . dirname ( payload . widgetProps . source . path ) , {
407- recursive : true ,
408- force : true ,
409- } ) ;
414+ await cleanUpScreenshotArtifacts ( artifacts ) ;
410415 }
411416 } ) ;
412417
413418 it ( 'emits a media artifact when take_screenshot runs with chat artifacts enabled' , async ( ) => {
414- const screenshotBuffer = Buffer . from ( 'artifact-jpeg' ) ;
415- const page = {
416- emulateMedia : vi . fn ( ) ,
417- goto : vi . fn ( ) ,
418- waitForLoadState : vi . fn ( ) . mockResolvedValue ( undefined ) ,
419- evaluate : vi . fn ( ) . mockResolvedValue ( 1200 ) ,
420- addStyleTag : vi . fn ( ) ,
421- screenshot : vi . fn ( ) . mockResolvedValue ( screenshotBuffer ) ,
422- close : vi . fn ( ) ,
423- } ;
424- const browser = {
425- newPage : vi . fn ( ) . mockResolvedValue ( page ) ,
426- } ;
427- vi . mocked ( getSharedBrowser ) . mockResolvedValue ( browser as never ) ;
419+ mockScreenshotBrowser (
420+ createMockPage ( { buffer : Buffer . from ( 'artifact-jpeg' ) , documentHeight : 1200 } )
421+ ) ;
428422 const tool = resolveStudioToolDefinitions ( {
429423 emitChatArtifacts : true ,
430424 } ) . find ( ( definition ) => definition . name === 'take_screenshot' ) ;
@@ -455,31 +449,19 @@ describe( 'Studio AI MCP tools', () => {
455449 } ) ,
456450 } )
457451 ) ;
452+ expect ( getTextContent ( result ) ) . not . toContain ( 'mediaWidgetPayload' ) ;
458453
459- const payload = JSON . parse ( getTextContent ( result ) ! . split ( 'mediaWidgetPayload=' ) [ 1 ] ) as {
460- widgetProps : { source : { path : string } } ;
461- } ;
462- await rm ( path . dirname ( payload . widgetProps . source . path ) , { recursive : true , force : true } ) ;
454+ const details = result . details as { studioArtifacts : ScreenshotArtifact [ ] } ;
455+ await cleanUpScreenshotArtifacts ( details . studioArtifacts ) ;
463456 } ) ;
464457
465458 it ( 'can capture desktop and mobile screenshots in one take_screenshot call' , async ( ) => {
466459 const desktopBuffer = Buffer . from ( 'desktop-jpeg' ) ;
467460 const mobileBuffer = Buffer . from ( 'mobile-jpeg' ) ;
468- const createPage = ( buffer : Buffer ) => ( {
469- emulateMedia : vi . fn ( ) ,
470- goto : vi . fn ( ) ,
471- waitForLoadState : vi . fn ( ) . mockResolvedValue ( undefined ) ,
472- evaluate : vi . fn ( ) . mockResolvedValue ( 2400 ) ,
473- addStyleTag : vi . fn ( ) ,
474- screenshot : vi . fn ( ) . mockResolvedValue ( buffer ) ,
475- close : vi . fn ( ) ,
476- } ) ;
477- const desktopPage = createPage ( desktopBuffer ) ;
478- const mobilePage = createPage ( mobileBuffer ) ;
479- const browser = {
480- newPage : vi . fn ( ) . mockResolvedValueOnce ( desktopPage ) . mockResolvedValueOnce ( mobilePage ) ,
481- } ;
482- vi . mocked ( getSharedBrowser ) . mockResolvedValue ( browser as never ) ;
461+ const { newPage } = mockScreenshotBrowser (
462+ createMockPage ( { buffer : desktopBuffer , documentHeight : 2400 } ) ,
463+ createMockPage ( { buffer : mobileBuffer , documentHeight : 2400 } )
464+ ) ;
483465
484466 const result = await getTool ( 'take_screenshot' ) . rawHandler ( {
485467 url : 'http://localhost:8903/story-time' ,
@@ -490,8 +472,8 @@ describe( 'Studio AI MCP tools', () => {
490472 expect ( text ) . toContain ( 'Screenshots captured:' ) ;
491473 expect ( text ) . toContain ( '- desktop: captured full page (2400px tall)' ) ;
492474 expect ( text ) . toContain ( '- mobile: captured full page (2400px tall)' ) ;
493- expect ( text ) . toContain ( 'mediaWidgetPayloads= ' ) ;
494- expect ( browser . newPage ) . toHaveBeenCalledTimes ( 2 ) ;
475+ expect ( text ) . not . toContain ( 'mediaWidgetPayload ' ) ;
476+ expect ( newPage ) . toHaveBeenCalledTimes ( 2 ) ;
495477 expect ( result . content . slice ( 1 ) ) . toEqual ( [
496478 {
497479 type : 'image' ,
@@ -505,44 +487,23 @@ describe( 'Studio AI MCP tools', () => {
505487 } ,
506488 ] ) ;
507489
508- const payloads = JSON . parse ( text ! . split ( 'mediaWidgetPayloads=' ) [ 1 ] ) as Array < {
509- widgetProps : { source : { path : string ; name : string } } ;
510- } > ;
490+ const artifacts = getScreenshotArtifacts ( result ) ;
511491 try {
512- expect ( payloads . map ( ( payload ) => payload . widgetProps . source . name ) ) . toEqual ( [
492+ expect ( artifacts . map ( ( artifact ) => artifact . widgetProps . source . name ) ) . toEqual ( [
513493 'screenshot-desktop.jpg' ,
514494 'screenshot-mobile.jpg' ,
515495 ] ) ;
516- expect (
517- result . studioArtifacts ?. map ( ( payload ) => payload . widgetProps . source )
518- ) . toMatchObject ( [ { name : 'screenshot-desktop.jpg' } , { name : 'screenshot-mobile.jpg' } ] ) ;
519496 } finally {
520- await Promise . all (
521- payloads . map ( ( payload ) =>
522- rm ( path . dirname ( payload . widgetProps . source . path ) , { recursive : true , force : true } )
523- )
524- ) ;
497+ await cleanUpScreenshotArtifacts ( artifacts ) ;
525498 }
526499 } ) ;
527500
528501 it ( 'can capture light and dark screenshots in one take_screenshot call' , async ( ) => {
529502 const lightBuffer = Buffer . from ( 'light-jpeg' ) ;
530503 const darkBuffer = Buffer . from ( 'dark-jpeg' ) ;
531- const createPage = ( buffer : Buffer ) => ( {
532- emulateMedia : vi . fn ( ) ,
533- goto : vi . fn ( ) ,
534- waitForLoadState : vi . fn ( ) . mockResolvedValue ( undefined ) ,
535- evaluate : vi . fn ( ) . mockResolvedValue ( 1600 ) ,
536- addStyleTag : vi . fn ( ) ,
537- screenshot : vi . fn ( ) . mockResolvedValue ( buffer ) ,
538- close : vi . fn ( ) ,
539- } ) ;
540- const lightPage = createPage ( lightBuffer ) ;
541- const darkPage = createPage ( darkBuffer ) ;
542- const browser = {
543- newPage : vi . fn ( ) . mockResolvedValueOnce ( lightPage ) . mockResolvedValueOnce ( darkPage ) ,
544- } ;
545- vi . mocked ( getSharedBrowser ) . mockResolvedValue ( browser as never ) ;
504+ const lightPage = createMockPage ( { buffer : lightBuffer , documentHeight : 1600 } ) ;
505+ const darkPage = createMockPage ( { buffer : darkBuffer , documentHeight : 1600 } ) ;
506+ mockScreenshotBrowser ( lightPage , darkPage ) ;
546507
547508 const result = await getTool ( 'take_screenshot' ) . rawHandler ( {
548509 url : 'http://localhost:8903/story-time' ,
@@ -573,23 +534,14 @@ describe( 'Studio AI MCP tools', () => {
573534 } ,
574535 ] ) ;
575536
576- const payloads = JSON . parse ( text ! . split ( 'mediaWidgetPayloads=' ) [ 1 ] ) as Array < {
577- widgetProps : { source : { path : string ; name : string } } ;
578- } > ;
537+ const artifacts = getScreenshotArtifacts ( result ) ;
579538 try {
580- expect ( payloads . map ( ( payload ) => payload . widgetProps . source . name ) ) . toEqual ( [
539+ expect ( artifacts . map ( ( artifact ) => artifact . widgetProps . source . name ) ) . toEqual ( [
581540 'screenshot-desktop-light.jpg' ,
582541 'screenshot-desktop-dark.jpg' ,
583542 ] ) ;
584543 } finally {
585- await Promise . all (
586- payloads . map ( ( payload ) =>
587- rm ( path . dirname ( payload . widgetProps . source . path ) , {
588- recursive : true ,
589- force : true ,
590- } )
591- )
592- ) ;
544+ await cleanUpScreenshotArtifacts ( artifacts ) ;
593545 }
594546 } ) ;
595547
@@ -946,17 +898,8 @@ describe( 'Studio AI MCP tools', () => {
946898
947899 it ( 'can force dark mode when sharing a screenshot' , async ( ) => {
948900 const screenshotBuffer = Buffer . from ( 'shared-png' ) ;
949- const page = {
950- emulateMedia : vi . fn ( ) ,
951- goto : vi . fn ( ) ,
952- waitForLoadState : vi . fn ( ) . mockResolvedValue ( undefined ) ,
953- evaluate : vi . fn ( ) . mockResolvedValue ( undefined ) ,
954- addStyleTag : vi . fn ( ) ,
955- screenshot : vi . fn ( ) . mockResolvedValue ( screenshotBuffer ) ,
956- close : vi . fn ( ) ,
957- } ;
958- const browser = { newPage : vi . fn ( ) . mockResolvedValue ( page ) } ;
959- vi . mocked ( getSharedBrowser ) . mockResolvedValue ( browser as never ) ;
901+ const page = createMockPage ( { buffer : screenshotBuffer } ) ;
902+ mockScreenshotBrowser ( page ) ;
960903
961904 const result = await getTool ( 'share_screenshot' ) . rawHandler ( {
962905 url : 'http://localhost:8903/' ,
0 commit comments