11import { readFileSync } from 'node:fs' ;
2+ import path from 'node:path' ;
23import { createYamlPlayer , launchServer } from '@/create-yaml-player' ;
34import type { MidsceneYamlScript , MidsceneYamlScriptEnv } from '@midscene/core' ;
45import { processCacheConfig } from '@midscene/core/utils' ;
@@ -47,9 +48,25 @@ vi.mock('@midscene/web/bridge-mode', () => ({
4748 AgentOverChromeBridge : vi . fn ( ) ,
4849} ) ) ;
4950
50- vi . mock ( '@midscene/web/puppeteer-agent-launcher' , ( ) => ( {
51- puppeteerAgentForTarget : vi . fn ( ) ,
52- } ) ) ;
51+ vi . mock ( '@midscene/web/puppeteer-agent-launcher' , async ( importOriginal ) => {
52+ const original =
53+ await importOriginal <
54+ typeof import ( '@midscene/web/puppeteer-agent-launcher' )
55+ > ( ) ;
56+ return {
57+ ...original ,
58+ buildDownloadBehavior : ( downloadPath : string | undefined ) =>
59+ downloadPath
60+ ? {
61+ policy : 'allow' ,
62+ downloadPath : downloadPath . startsWith ( '/' )
63+ ? downloadPath
64+ : `${ process . cwd ( ) } /${ downloadPath . replace ( / ^ \. \/ / , '' ) } ` ,
65+ }
66+ : undefined ,
67+ puppeteerAgentForTarget : vi . fn ( ) ,
68+ } ;
69+ } ) ;
5370
5471vi . mock ( '@midscene/web/puppeteer' , ( ) => ( {
5572 PuppeteerAgent : vi . fn ( ) ,
@@ -275,6 +292,42 @@ describe('create-yaml-player', () => {
275292
276293 expect ( result ) . toBe ( mockPlayer ) ;
277294 } ) ;
295+
296+ test ( 'should pass web downloadPath to puppeteer agent launcher' , async ( ) => {
297+ const mockScript : MidsceneYamlScript = {
298+ web : {
299+ url : 'http://example.com' ,
300+ downloadPath : './downloads' ,
301+ } ,
302+ tasks : [ ] ,
303+ } ;
304+ const mockAgent = { destroy : vi . fn ( ) } ;
305+ let setupFnCallback : ( ( ) => Promise < any > ) | undefined ;
306+
307+ vi . mocked ( puppeteerAgentForTarget ) . mockResolvedValue ( {
308+ agent : mockAgent as any ,
309+ freeFn : [ ] ,
310+ } ) ;
311+ vi . mocked ( ScriptPlayer ) . mockImplementation ( ( script , setupFn ) => {
312+ setupFnCallback = setupFn as ( ) => Promise < any > ;
313+ return {
314+ addCleanup : vi . fn ( ) ,
315+ } as unknown as ScriptPlayer < MidsceneYamlScriptEnv > ;
316+ } ) ;
317+
318+ await createYamlPlayer ( mockFilePath , mockScript ) ;
319+ await setupFnCallback ?.( ) ;
320+
321+ expect ( puppeteerAgentForTarget ) . toHaveBeenCalledWith (
322+ expect . objectContaining ( {
323+ url : 'http://example.com' ,
324+ downloadPath : './downloads' ,
325+ } ) ,
326+ expect . any ( Object ) ,
327+ undefined ,
328+ undefined ,
329+ ) ;
330+ } ) ;
278331 } ) ;
279332
280333 describe ( 'Cache configuration - Legacy compatibility mode' , ( ) => {
@@ -821,6 +874,44 @@ describe('create-yaml-player', () => {
821874 ) ;
822875 } ) ;
823876
877+ test ( 'should warn that downloadPath is ignored in bridge mode' , async ( ) => {
878+ const mockScript : MidsceneYamlScript = {
879+ web : {
880+ url : 'http://example.com' ,
881+ bridgeMode : 'newTabWithUrl' ,
882+ downloadPath : './downloads' ,
883+ } ,
884+ tasks : [ ] ,
885+ } ;
886+
887+ const mockAgent = {
888+ destroy : vi . fn ( ) ,
889+ connectNewTabWithUrl : vi . fn ( ) . mockResolvedValue ( undefined ) ,
890+ } ;
891+ const warnSpy = vi . spyOn ( console , 'warn' ) . mockImplementation ( ( ) => { } ) ;
892+ let setupFnCallback : ( ( ) => Promise < any > ) | undefined ;
893+
894+ vi . mocked ( readFileSync ) . mockReturnValue ( 'mock yaml content' ) ;
895+ vi . mocked ( parseYamlScript ) . mockReturnValue ( mockScript ) ;
896+ vi . mocked ( AgentOverChromeBridge ) . mockImplementation (
897+ ( ) => mockAgent as any ,
898+ ) ;
899+ vi . mocked ( ScriptPlayer ) . mockImplementation ( ( script , setupFn ) => {
900+ setupFnCallback = setupFn as ( ) => Promise < any > ;
901+ return {
902+ addCleanup : vi . fn ( ) ,
903+ } as unknown as ScriptPlayer < MidsceneYamlScriptEnv > ;
904+ } ) ;
905+
906+ await createYamlPlayer ( mockFilePath , mockScript ) ;
907+ await setupFnCallback ?.( ) ;
908+
909+ expect ( warnSpy ) . toHaveBeenCalledWith (
910+ expect . stringContaining ( 'downloadPath' ) ,
911+ ) ;
912+ warnSpy . mockRestore ( ) ;
913+ } ) ;
914+
824915 test ( 'should handle undefined aiActionContext gracefully for Android' , async ( ) => {
825916 const mockScript : MidsceneYamlScript = {
826917 android : {
@@ -1300,6 +1391,7 @@ describe('create-yaml-player', () => {
13001391 expect ( puppeteer . connect ) . toHaveBeenCalledWith ( {
13011392 browserWSEndpoint : 'ws://localhost:9222/devtools/browser/xxx' ,
13021393 defaultViewport : null ,
1394+ downloadBehavior : undefined ,
13031395 } ) ;
13041396
13051397 // Should reuse puppeteerAgentForTarget (page setup: viewport, userAgent, etc.)
@@ -1311,6 +1403,53 @@ describe('create-yaml-player', () => {
13111403 ) ;
13121404 } ) ;
13131405
1406+ test ( 'should configure download behavior via Puppeteer connect options in CDP mode' , async ( ) => {
1407+ const mockScript : MidsceneYamlScript = {
1408+ web : {
1409+ url : 'http://example.com' ,
1410+ cdpEndpoint : 'ws://localhost:9222/devtools/browser/xxx' ,
1411+ downloadPath : './downloads' ,
1412+ } ,
1413+ tasks : [ ] ,
1414+ } ;
1415+
1416+ const mockBrowser = {
1417+ disconnect : vi . fn ( ) ,
1418+ } ;
1419+ const mockAgent = { destroy : vi . fn ( ) } ;
1420+ let setupFnCallback : ( ( ) => Promise < any > ) | undefined ;
1421+
1422+ vi . mocked ( readFileSync ) . mockReturnValue ( 'mock yaml content' ) ;
1423+ vi . mocked ( parseYamlScript ) . mockReturnValue ( mockScript ) ;
1424+
1425+ const puppeteer = ( await import ( 'puppeteer' ) ) . default ;
1426+ vi . mocked ( puppeteer . connect ) . mockResolvedValue ( mockBrowser as any ) ;
1427+
1428+ vi . mocked ( puppeteerAgentForTarget ) . mockResolvedValue ( {
1429+ agent : mockAgent as any ,
1430+ freeFn : [ ] ,
1431+ } ) ;
1432+
1433+ vi . mocked ( ScriptPlayer ) . mockImplementation ( ( script , setupFn ) => {
1434+ setupFnCallback = setupFn as ( ) => Promise < any > ;
1435+ return {
1436+ addCleanup : vi . fn ( ) ,
1437+ } as unknown as ScriptPlayer < MidsceneYamlScriptEnv > ;
1438+ } ) ;
1439+
1440+ await createYamlPlayer ( mockFilePath , mockScript ) ;
1441+ await setupFnCallback ?.( ) ;
1442+
1443+ expect ( puppeteer . connect ) . toHaveBeenCalledWith ( {
1444+ browserWSEndpoint : 'ws://localhost:9222/devtools/browser/xxx' ,
1445+ defaultViewport : null ,
1446+ downloadBehavior : {
1447+ policy : 'allow' ,
1448+ downloadPath : path . resolve ( './downloads' ) ,
1449+ } ,
1450+ } ) ;
1451+ } ) ;
1452+
13141453 test ( 'should pass agent options in CDP mode via puppeteerAgentForTarget' , async ( ) => {
13151454 const mockScript : MidsceneYamlScript = {
13161455 web : {
0 commit comments