@@ -16,6 +16,7 @@ import {
1616 type RawAXNode ,
1717 snapshotAria ,
1818 snapshotDom ,
19+ snapshotRoleViaCdp ,
1920} from "./cdp.js" ;
2021
2122/**
@@ -77,6 +78,16 @@ async function startMockWsServer(handle: CdpReplyHandler) {
7778 params ?: Record < string , unknown > ;
7879 } ;
7980 handle ( msg , socket ) ;
81+ if (
82+ msg . method === "Page.enable" ||
83+ msg . method === "Runtime.enable" ||
84+ msg . method === "Network.enable" ||
85+ msg . method === "DOM.enable" ||
86+ msg . method === "Accessibility.enable" ||
87+ msg . method === "Runtime.runIfWaitingForDebugger"
88+ ) {
89+ socket . send ( JSON . stringify ( { id : msg . id , result : { } } ) ) ;
90+ }
8091 } ) ;
8192 } ) ;
8293 return {
@@ -475,6 +486,204 @@ describe("cdp internal", () => {
475486 } ) ;
476487 } ) ;
477488
489+ describe ( "snapshotRoleViaCdp" , ( ) => {
490+ it ( "builds role refs, promotes cursor-interactive nodes, and appends link urls" , async ( ) => {
491+ const server = await startMockWsServer ( ( msg , socket ) => {
492+ if ( msg . method === "Accessibility.enable" || msg . method === "Page.enable" ) {
493+ socket . send ( JSON . stringify ( { id : msg . id , result : { } } ) ) ;
494+ return ;
495+ }
496+ if ( msg . method === "Accessibility.getFullAXTree" ) {
497+ socket . send (
498+ JSON . stringify ( {
499+ id : msg . id ,
500+ result : {
501+ nodes : [
502+ {
503+ nodeId : "1" ,
504+ role : { value : "RootWebArea" } ,
505+ name : { value : "" } ,
506+ childIds : [ "2" , "3" , "4" ] ,
507+ } ,
508+ {
509+ nodeId : "2" ,
510+ role : { value : "button" } ,
511+ name : { value : "Save" } ,
512+ backendDOMNodeId : 22 ,
513+ childIds : [ ] ,
514+ } ,
515+ {
516+ nodeId : "3" ,
517+ role : { value : "link" } ,
518+ name : { value : "Docs" } ,
519+ backendDOMNodeId : 33 ,
520+ childIds : [ ] ,
521+ } ,
522+ {
523+ nodeId : "4" ,
524+ role : { value : "generic" } ,
525+ name : { value : "" } ,
526+ backendDOMNodeId : 44 ,
527+ childIds : [ ] ,
528+ } ,
529+ ] ,
530+ } ,
531+ } ) ,
532+ ) ;
533+ return ;
534+ }
535+ if ( msg . method === "Runtime.evaluate" ) {
536+ const expression =
537+ typeof msg . params ?. expression === "string" ? msg . params . expression : "" ;
538+ if ( expression . includes ( 'querySelectorAll("*"' ) ) {
539+ socket . send (
540+ JSON . stringify ( {
541+ id : msg . id ,
542+ result : {
543+ result : {
544+ value : [
545+ {
546+ text : "Clickable Card" ,
547+ tagName : "div" ,
548+ hasCursorPointer : true ,
549+ hasOnClick : true ,
550+ } ,
551+ ] ,
552+ } ,
553+ } ,
554+ } ) ,
555+ ) ;
556+ return ;
557+ }
558+ socket . send ( JSON . stringify ( { id : msg . id , result : { result : { value : true } } } ) ) ;
559+ return ;
560+ }
561+ if ( msg . method === "DOM.getDocument" ) {
562+ socket . send ( JSON . stringify ( { id : msg . id , result : { root : { nodeId : 1 } } } ) ) ;
563+ return ;
564+ }
565+ if ( msg . method === "DOM.querySelectorAll" ) {
566+ socket . send ( JSON . stringify ( { id : msg . id , result : { nodeIds : [ 44 ] } } ) ) ;
567+ return ;
568+ }
569+ if ( msg . method === "DOM.describeNode" ) {
570+ socket . send (
571+ JSON . stringify ( {
572+ id : msg . id ,
573+ result : { node : { backendNodeId : 44 , attributes : [ "data-openclaw-cdp-ci" , "0" ] } } ,
574+ } ) ,
575+ ) ;
576+ return ;
577+ }
578+ if ( msg . method === "DOM.resolveNode" ) {
579+ socket . send ( JSON . stringify ( { id : msg . id , result : { object : { objectId : "link1" } } } ) ) ;
580+ return ;
581+ }
582+ if ( msg . method === "Runtime.callFunctionOn" ) {
583+ socket . send (
584+ JSON . stringify ( {
585+ id : msg . id ,
586+ result : { result : { value : "https://docs.openclaw.ai/" } } ,
587+ } ) ,
588+ ) ;
589+ }
590+ } ) ;
591+ wss = server . wss ;
592+
593+ const snap = await snapshotRoleViaCdp ( {
594+ wsUrl : server . wsUrl ,
595+ urls : true ,
596+ options : { interactive : true } ,
597+ } ) ;
598+
599+ expect ( snap . snapshot ) . toContain ( '- button "Save" [ref=e1]' ) ;
600+ expect ( snap . snapshot ) . toContain ( '- link "Docs" [ref=e2] [url=https://docs.openclaw.ai/]' ) ;
601+ expect ( snap . snapshot ) . toContain (
602+ '- generic "Clickable Card" [ref=e3] [cursor:pointer, onclick]' ,
603+ ) ;
604+ expect ( snap . refs . e3 ?. backendDOMNodeId ) . toBe ( 44 ) ;
605+ } ) ;
606+
607+ it ( "expands one level of iframe snapshots with frame metadata" , async ( ) => {
608+ const server = await startMockWsServer ( ( msg , socket ) => {
609+ if (
610+ msg . method === "Accessibility.enable" ||
611+ msg . method === "Page.enable" ||
612+ msg . method === "Runtime.evaluate"
613+ ) {
614+ socket . send (
615+ JSON . stringify ( {
616+ id : msg . id ,
617+ result : msg . method === "Runtime.evaluate" ? { result : { value : [ ] } } : { } ,
618+ } ) ,
619+ ) ;
620+ return ;
621+ }
622+ if ( msg . method === "Accessibility.getFullAXTree" ) {
623+ const frameId = msg . params ?. frameId ;
624+ socket . send (
625+ JSON . stringify ( {
626+ id : msg . id ,
627+ result : {
628+ nodes : frameId
629+ ? [
630+ {
631+ nodeId : "c1" ,
632+ role : { value : "RootWebArea" } ,
633+ name : { value : "" } ,
634+ childIds : [ "c2" ] ,
635+ } ,
636+ {
637+ nodeId : "c2" ,
638+ role : { value : "button" } ,
639+ name : { value : "Inside" } ,
640+ backendDOMNodeId : 55 ,
641+ childIds : [ ] ,
642+ } ,
643+ ]
644+ : [
645+ {
646+ nodeId : "1" ,
647+ role : { value : "RootWebArea" } ,
648+ name : { value : "" } ,
649+ childIds : [ "2" ] ,
650+ } ,
651+ {
652+ nodeId : "2" ,
653+ role : { value : "Iframe" } ,
654+ name : { value : "Child" } ,
655+ backendDOMNodeId : 44 ,
656+ childIds : [ ] ,
657+ } ,
658+ ] ,
659+ } ,
660+ } ) ,
661+ ) ;
662+ return ;
663+ }
664+ if ( msg . method === "DOM.describeNode" ) {
665+ socket . send (
666+ JSON . stringify ( {
667+ id : msg . id ,
668+ result : { node : { contentDocument : { frameId : "FRAME_1" } } } ,
669+ } ) ,
670+ ) ;
671+ }
672+ } ) ;
673+ wss = server . wss ;
674+
675+ const snap = await snapshotRoleViaCdp ( {
676+ wsUrl : server . wsUrl ,
677+ options : { interactive : true } ,
678+ } ) ;
679+
680+ expect ( snap . snapshot ) . toContain ( '- Iframe "Child" [ref=e1]' ) ;
681+ expect ( snap . snapshot ) . toContain ( ' - button "Inside" [ref=e2]' ) ;
682+ expect ( snap . refs . e1 ?. frameId ) . toBe ( "FRAME_1" ) ;
683+ expect ( snap . refs . e2 ?. frameId ) . toBe ( "FRAME_1" ) ;
684+ } ) ;
685+ } ) ;
686+
478687 describe ( "snapshotDom" , ( ) => {
479688 it ( "returns the nodes array from the evaluated expression" , async ( ) => {
480689 const server = await startMockWsServer ( ( msg , socket ) => {
0 commit comments