diff --git a/src/tools/input.ts b/src/tools/input.ts
index cd8b2f4d0..c309326e4 100644
--- a/src/tools/input.ts
+++ b/src/tools/input.ts
@@ -177,6 +177,10 @@ async function selectOption(
}
}
+function hasOptionChildren(aXNode: TextSnapshotNode) {
+ return aXNode.children.some(child => child.role === 'option');
+}
+
async function fillFormElement(
uid: string,
value: string,
@@ -185,7 +189,9 @@ async function fillFormElement(
const handle = await context.getElementByUid(uid);
try {
const aXNode = context.getAXNodeByUid(uid);
- if (aXNode && aXNode.role === 'combobox') {
+ // We assume that combobox needs to be handled as select if it has
+ // role='combobox' and option children.
+ if (aXNode && aXNode.role === 'combobox' && hasOptionChildren(aXNode)) {
await selectOption(handle, aXNode, value);
} else {
// Increase timeout for longer input values.
diff --git a/tests/tools/input.test.ts b/tests/tools/input.test.ts
index 06a752148..37f861add 100644
--- a/tests/tools/input.test.ts
+++ b/tests/tools/input.test.ts
@@ -352,13 +352,40 @@ describe('input', () => {
});
});
+ it('fills out a textarea marked as combobox', async () => {
+ await withMcpContext(async (response, context) => {
+ const page = context.getSelectedPage();
+ await page.setContent(html``);
+ await context.createTextSnapshot();
+ await fill.handler(
+ {
+ params: {
+ uid: '1_1',
+ value: '1',
+ },
+ },
+ response,
+ context,
+ );
+ assert.strictEqual(
+ response.responseLines[0],
+ 'Successfully filled out the element',
+ );
+ assert.ok(response.includeSnapshot);
+ assert.ok(
+ await page.evaluate(() => {
+ return document.body.querySelector('textarea')?.value === '1';
+ }),
+ );
+ });
+ });
+
it('fills out a textarea with long text', async () => {
await withMcpContext(async (response, context) => {
const page = context.getSelectedPage();
await page.setContent(html``);
- await page.focus('textarea');
await context.createTextSnapshot();
- await page.setDefaultTimeout(1000);
+ page.setDefaultTimeout(1000);
await fill.handler(
{
params: {