77import type { ElementHandle } from 'puppeteer-core' ;
88import z from 'zod' ;
99
10+ import type { McpContext } from '../McpContext.js' ;
11+
1012import { ToolCategories } from './categories.js' ;
1113import { defineTool } from './ToolDefinition.js' ;
1214
@@ -78,6 +80,40 @@ export const hover = defineTool({
7880 } ,
7981} ) ;
8082
83+ async function fillFormElement (
84+ uid : string ,
85+ value : string ,
86+ context : McpContext ,
87+ ) {
88+ const handle = await context . getElementByUid ( uid ) ;
89+ try {
90+ // The AXNode for an option doesn't contain its `value`. We set text content of the option as value.
91+ // If the form is a combobox, we need to find the correct option by its text value.
92+ // To do that, loop through the children while checking which child's text matches the requested value (requested value is actually the text content).
93+ // When the correct option is found, use the element handle to get the real value.
94+ const aXNode = context . getAXNodeByUid ( uid ) ;
95+ if ( aXNode && aXNode . role === 'combobox' && aXNode . children ) {
96+ for ( const child of aXNode . children ) {
97+ if ( child . role === 'option' && child . name === value && child . value ) {
98+ const childHandle = await child . elementHandle ( ) ;
99+ if ( childHandle ) {
100+ const childValueHandle = await childHandle . getProperty ( 'value' ) ;
101+ const childValue = await childValueHandle . jsonValue ( ) ;
102+ if ( childValue ) {
103+ await handle . asLocator ( ) . fill ( childValue . toString ( ) ) ;
104+ }
105+ break ;
106+ }
107+ }
108+ }
109+ } else {
110+ await handle . asLocator ( ) . fill ( value ) ;
111+ }
112+ } finally {
113+ void handle . dispose ( ) ;
114+ }
115+ }
116+
81117export const fill = defineTool ( {
82118 name : 'fill' ,
83119 description : `Type text into a input, text area or select an option from a <select> element.` ,
@@ -94,35 +130,15 @@ export const fill = defineTool({
94130 value : z . string ( ) . describe ( 'The value to fill in' ) ,
95131 } ,
96132 handler : async ( request , response , context ) => {
97- const handle = await context . getElementByUid ( request . params . uid ) ;
98- try {
99- await context . waitForEventsAfterAction ( async ( ) => {
100- // The AXNode for an option doesn't contain its `value`. We set text content of the option as value.
101- // To fill the form, get the correct option by its text value.
102- const isSelectElement = await handle . evaluate (
103- el => el . tagName === 'SELECT' ,
104- ) ;
105- if ( isSelectElement ) {
106- const value = await handle . evaluate ( ( el , text ) => {
107- const option = Array . from ( ( el as HTMLSelectElement ) . options ) . find (
108- option => option . text === text ,
109- ) ;
110- if ( option ) {
111- return option . value ;
112- }
113- return request . params . value ;
114- } , request . params . value ) ;
115-
116- await handle . asLocator ( ) . fill ( value ) ;
117- } else {
118- await handle . asLocator ( ) . fill ( request . params . value ) ;
119- }
120- } ) ;
121- response . appendResponseLine ( `Successfully filled out the element` ) ;
122- response . setIncludeSnapshot ( true ) ;
123- } finally {
124- void handle . dispose ( ) ;
125- }
133+ await context . waitForEventsAfterAction ( async ( ) => {
134+ await fillFormElement (
135+ request . params . uid ,
136+ request . params . value ,
137+ context as McpContext ,
138+ ) ;
139+ } ) ;
140+ response . appendResponseLine ( `Successfully filled out the element` ) ;
141+ response . setIncludeSnapshot ( true ) ;
126142 } ,
127143} ) ;
128144
@@ -174,14 +190,13 @@ export const fillForm = defineTool({
174190 } ,
175191 handler : async ( request , response , context ) => {
176192 for ( const element of request . params . elements ) {
177- const handle = await context . getElementByUid ( element . uid ) ;
178- try {
179- await context . waitForEventsAfterAction ( async ( ) => {
180- await handle . asLocator ( ) . fill ( element . value ) ;
181- } ) ;
182- } finally {
183- void handle . dispose ( ) ;
184- }
193+ await context . waitForEventsAfterAction ( async ( ) => {
194+ await fillFormElement (
195+ element . uid ,
196+ element . value ,
197+ context as McpContext ,
198+ ) ;
199+ } ) ;
185200 }
186201 response . appendResponseLine ( `Successfully filled out the form` ) ;
187202 response . setIncludeSnapshot ( true ) ;
0 commit comments