77import type { ElementHandle } from 'puppeteer-core' ;
88import z from 'zod' ;
99
10+ import type { McpContext , TextSnapshotNode } from '../McpContext.js' ;
11+
1012import { ToolCategories } from './categories.js' ;
1113import { defineTool } from './ToolDefinition.js' ;
1214
@@ -78,6 +80,61 @@ export const hover = defineTool({
7880 } ,
7981} ) ;
8082
83+ // The AXNode for an option doesn't contain its `value`. We set text content of the option as value.
84+ // If the form is a combobox, we need to find the correct option by its text value.
85+ // To do that, loop through the children while checking which child's text matches the requested value (requested value is actually the text content).
86+ // When the correct option is found, use the element handle to get the real value.
87+ async function selectOption (
88+ handle : ElementHandle ,
89+ aXNode : TextSnapshotNode ,
90+ value : string ,
91+ ) {
92+ let optionFound = false ;
93+ for ( const child of aXNode . children ) {
94+ if ( child . role === 'option' && child . name === value && child . value ) {
95+ optionFound = true ;
96+ const childHandle = await child . elementHandle ( ) ;
97+ if ( childHandle ) {
98+ try {
99+ const childValueHandle = await childHandle . getProperty ( 'value' ) ;
100+ try {
101+ const childValue = await childValueHandle . jsonValue ( ) ;
102+ if ( childValue ) {
103+ await handle . asLocator ( ) . fill ( childValue . toString ( ) ) ;
104+ }
105+ } finally {
106+ void childValueHandle . dispose ( ) ;
107+ }
108+ break ;
109+ } finally {
110+ void childHandle . dispose ( ) ;
111+ }
112+ }
113+ }
114+ }
115+ if ( ! optionFound ) {
116+ throw new Error ( `Could not find option with text "${ value } "` ) ;
117+ }
118+ }
119+
120+ async function fillFormElement (
121+ uid : string ,
122+ value : string ,
123+ context : McpContext ,
124+ ) {
125+ const handle = await context . getElementByUid ( uid ) ;
126+ try {
127+ const aXNode = context . getAXNodeByUid ( uid ) ;
128+ if ( aXNode && aXNode . role === 'combobox' ) {
129+ await selectOption ( handle , aXNode , value ) ;
130+ } else {
131+ await handle . asLocator ( ) . fill ( value ) ;
132+ }
133+ } finally {
134+ void handle . dispose ( ) ;
135+ }
136+ }
137+
81138export const fill = defineTool ( {
82139 name : 'fill' ,
83140 description : `Type text into a input, text area or select an option from a <select> element.` ,
@@ -94,16 +151,15 @@ export const fill = defineTool({
94151 value : z . string ( ) . describe ( 'The value to fill in' ) ,
95152 } ,
96153 handler : async ( request , response , context ) => {
97- const handle = await context . getElementByUid ( request . params . uid ) ;
98- try {
99- await context . waitForEventsAfterAction ( async ( ) => {
100- await handle . asLocator ( ) . fill ( request . params . value ) ;
101- } ) ;
102- response . appendResponseLine ( `Successfully filled out the element` ) ;
103- response . setIncludeSnapshot ( true ) ;
104- } finally {
105- void handle . dispose ( ) ;
106- }
154+ await context . waitForEventsAfterAction ( async ( ) => {
155+ await fillFormElement (
156+ request . params . uid ,
157+ request . params . value ,
158+ context as McpContext ,
159+ ) ;
160+ } ) ;
161+ response . appendResponseLine ( `Successfully filled out the element` ) ;
162+ response . setIncludeSnapshot ( true ) ;
107163 } ,
108164} ) ;
109165
@@ -155,14 +211,13 @@ export const fillForm = defineTool({
155211 } ,
156212 handler : async ( request , response , context ) => {
157213 for ( const element of request . params . elements ) {
158- const handle = await context . getElementByUid ( element . uid ) ;
159- try {
160- await context . waitForEventsAfterAction ( async ( ) => {
161- await handle . asLocator ( ) . fill ( element . value ) ;
162- } ) ;
163- } finally {
164- void handle . dispose ( ) ;
165- }
214+ await context . waitForEventsAfterAction ( async ( ) => {
215+ await fillFormElement (
216+ element . uid ,
217+ element . value ,
218+ context as McpContext ,
219+ ) ;
220+ } ) ;
166221 }
167222 response . appendResponseLine ( `Successfully filled out the form` ) ;
168223 response . setIncludeSnapshot ( true ) ;
0 commit comments