@@ -444,20 +444,28 @@ describe('browser routing', () => {
444444 ) . rejects . toThrow ( / u n s u p p o r t e d H T T P m e t h o d / i) ;
445445 } ) ;
446446
447- test ( 'defaults browser routing subresources to curl and telemetry/stream when env is unset' , async ( ) => {
447+ test ( 'defaults browser routing subresources when env is unset' , async ( ) => {
448448 await withBrowserRoutingEnv ( undefined , async ( ) => {
449- expect ( browserRoutingSubresourcesFromEnv ( ) ) . toEqual ( [ 'curl' , 'telemetry/stream' ] ) ;
449+ expect ( browserRoutingSubresourcesFromEnv ( ) ) . toEqual ( [
450+ 'curl' ,
451+ 'telemetry/stream' ,
452+ 'computer' ,
453+ 'playwright' ,
454+ ] ) ;
450455 } ) ;
451456 } ) ;
452457
453458 test ( 'allowlist matching is segment-boundary aware (telemetry/events stays on the control plane)' , ( ) => {
454- const prefixes = [ 'curl' , 'telemetry/stream' ] ;
459+ const prefixes = [ 'curl' , 'telemetry/stream' , 'computer' , 'playwright' ] ;
455460 expect ( matchesDirectVMPrefix ( 'telemetry/stream' , prefixes ) ) . toBe ( true ) ;
456461 expect ( matchesDirectVMPrefix ( 'telemetry/stream/x' , prefixes ) ) . toBe ( true ) ;
457462 expect ( matchesDirectVMPrefix ( 'telemetry/events' , prefixes ) ) . toBe ( false ) ;
458463 expect ( matchesDirectVMPrefix ( 'telemetry/streaming-config' , prefixes ) ) . toBe ( false ) ;
459464 expect ( matchesDirectVMPrefix ( 'telemetry' , prefixes ) ) . toBe ( false ) ;
460465 expect ( matchesDirectVMPrefix ( 'curl/raw' , prefixes ) ) . toBe ( true ) ;
466+ expect ( matchesDirectVMPrefix ( 'computer/screenshot' , prefixes ) ) . toBe ( true ) ;
467+ expect ( matchesDirectVMPrefix ( 'playwright/execute' , prefixes ) ) . toBe ( true ) ;
468+ expect ( matchesDirectVMPrefix ( 'process/exec' , prefixes ) ) . toBe ( false ) ;
461469 expect ( matchesDirectVMPrefix ( 'fs/read' , prefixes ) ) . toBe ( false ) ;
462470 } ) ;
463471
@@ -498,4 +506,88 @@ describe('browser routing', () => {
498506 expect ( browserRoutingSubresourcesFromEnv ( ) ) . toEqual ( [ ] ) ;
499507 } ) ;
500508 } ) ;
509+
510+ test ( 'routes computer screenshot and playwright execute to the VM by default' , async ( ) => {
511+ await withBrowserRoutingEnv ( undefined , async ( ) => {
512+ const calls : Array < { url : string ; headers : Headers } > = [ ] ;
513+ const kernel = new Kernel ( {
514+ apiKey : 'k' ,
515+ baseURL : 'https://api.example/' ,
516+ fetch : async ( input , init ?: RequestInit ) => {
517+ const url = normalizeURL ( input ) ;
518+ const headers = input instanceof Request ? new Headers ( input . headers ) : new Headers ( init ?. headers ) ;
519+ calls . push ( { url, headers } ) ;
520+ if ( url === 'https://api.example/browsers' ) {
521+ return Response . json ( {
522+ session_id : 'sess-1' ,
523+ base_url : 'http://browser-session.test/browser/kernel' ,
524+ cdp_ws_url : 'wss://browser-session.test/browser/cdp?jwt=token-abc' ,
525+ } ) ;
526+ }
527+ if ( url . includes ( '/computer/screenshot' ) ) {
528+ return new Response ( new Uint8Array ( [ 1 , 2 , 3 ] ) , {
529+ status : 200 ,
530+ headers : { 'content-type' : 'image/png' } ,
531+ } ) ;
532+ }
533+ return Response . json ( { success : true } ) ;
534+ } ,
535+ } ) ;
536+
537+ await kernel . browsers . create ( ) ;
538+ await kernel . browsers . computer . captureScreenshot ( 'sess-1' ) ;
539+ await kernel . browsers . playwright . execute ( 'sess-1' , { code : 'return 1' } ) ;
540+
541+ expect ( calls [ 1 ] ?. url ) . toBe (
542+ 'http://browser-session.test/browser/kernel/computer/screenshot?jwt=token-abc' ,
543+ ) ;
544+ expect ( calls [ 1 ] ?. headers . get ( 'authorization' ) ) . toBeNull ( ) ;
545+ expect ( calls [ 2 ] ?. url ) . toBe (
546+ 'http://browser-session.test/browser/kernel/playwright/execute?jwt=token-abc' ,
547+ ) ;
548+ expect ( calls [ 2 ] ?. headers . get ( 'authorization' ) ) . toBeNull ( ) ;
549+ } ) ;
550+ } ) ;
551+
552+ test ( 'keeps process, fs, and telemetry/events on the API origin by default' , async ( ) => {
553+ await withBrowserRoutingEnv ( undefined , async ( ) => {
554+ const calls : string [ ] = [ ] ;
555+ const kernel = new Kernel ( {
556+ apiKey : 'k' ,
557+ baseURL : 'https://api.example/' ,
558+ fetch : async ( input ) => {
559+ const url = normalizeURL ( input ) ;
560+ calls . push ( url ) ;
561+ if ( url === 'https://api.example/browsers' ) {
562+ return Response . json ( {
563+ session_id : 'sess-1' ,
564+ base_url : 'http://browser-session.test/browser/kernel' ,
565+ cdp_ws_url : 'wss://browser-session.test/browser/cdp?jwt=token-abc' ,
566+ } ) ;
567+ }
568+ if ( url . includes ( '/telemetry/events' ) ) {
569+ return Response . json ( [ ] ) ;
570+ }
571+ if ( url . includes ( '/fs/read_file' ) ) {
572+ return new Response ( new Uint8Array ( [ 1 ] ) , {
573+ status : 200 ,
574+ headers : { 'content-type' : 'application/octet-stream' } ,
575+ } ) ;
576+ }
577+ return Response . json ( { exit_code : 0 , stdout_b64 : '' , stderr_b64 : '' } ) ;
578+ } ,
579+ } ) ;
580+
581+ await kernel . browsers . create ( ) ;
582+ await kernel . browsers . process . exec ( 'sess-1' , { command : 'echo' } ) ;
583+ await kernel . browsers . fs . readFile ( 'sess-1' , { path : '/tmp/x' } ) ;
584+ await kernel . browsers . telemetry . events ( 'sess-1' ) ;
585+
586+ expect ( calls . slice ( 1 ) ) . toEqual ( [
587+ 'https://api.example/browsers/sess-1/process/exec' ,
588+ 'https://api.example/browsers/sess-1/fs/read_file?path=%2Ftmp%2Fx' ,
589+ 'https://api.example/browsers/sess-1/telemetry/events' ,
590+ ] ) ;
591+ } ) ;
592+ } ) ;
501593} ) ;
0 commit comments