From a945995cd0864d002bff524bb397d3490cf6569f Mon Sep 17 00:00:00 2001 From: Piotr Paulski Date: Tue, 28 Apr 2026 16:16:06 +0000 Subject: [PATCH 1/2] fix: Make agents choose fill_form more frequently when filling forms with checkboxes --- docs/tool-reference.md | 4 ++-- src/tools/input.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/tool-reference.md b/docs/tool-reference.md index b7f20ab37..2b1e8564a 100644 --- a/docs/tool-reference.md +++ b/docs/tool-reference.md @@ -1,6 +1,6 @@ -# Chrome DevTools MCP Tool Reference (~7005 cl100k_base tokens) +# Chrome DevTools MCP Tool Reference (~7014 cl100k_base tokens) - **[Input automation](#input-automation)** (9 tools) - [`click`](#click) @@ -85,7 +85,7 @@ ### `fill_form` -**Description:** [`Fill`](#fill) out multiple form elements at once +**Description:** [`Fill`](#fill) out multiple form elements at once. To set checkboxes [`fill`](#fill) them with 'true' **Parameters:** diff --git a/src/tools/input.ts b/src/tools/input.ts index 588f5308b..1f252a421 100644 --- a/src/tools/input.ts +++ b/src/tools/input.ts @@ -313,7 +313,7 @@ export const drag = definePageTool({ export const fillForm = definePageTool({ name: 'fill_form', - description: `Fill out multiple form elements at once`, + description: `Fill out multiple form elements at once. To set checkboxes fill them with 'true'`, annotations: { category: ToolCategory.INPUT, readOnlyHint: false, From 053365a72ebd39d4bac1075c3acb17e9938e5d55 Mon Sep 17 00:00:00 2001 From: Piotr Paulski Date: Thu, 30 Apr 2026 15:00:44 +0000 Subject: [PATCH 2/2] Modify fill and fill_form tools to handle checkboxes and radio buttons. --- src/tools/input.ts | 32 +++++-- tests/tools/input.test.ts | 174 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+), 5 deletions(-) diff --git a/src/tools/input.ts b/src/tools/input.ts index 1f252a421..ffc1af362 100644 --- a/src/tools/input.ts +++ b/src/tools/input.ts @@ -204,11 +204,33 @@ async function fillFormElement( if (aXNode && aXNode.role === 'combobox' && hasOptionChildren(aXNode)) { await selectOption(handle, aXNode, value); } else { - // Increase timeout for longer input values. - const timeoutPerChar = 10; // ms - const fillTimeout = - page.pptrPage.getDefaultTimeout() + value.length * timeoutPerChar; - await handle.asLocator().setTimeout(fillTimeout).fill(value); + const isToggle = await handle.evaluate(el => { + if (el instanceof HTMLInputElement) { + return el.type === 'checkbox' || el.type === 'radio'; + } + const role = el.getAttribute('role'); + return role === 'checkbox' || role === 'radio' || role === 'switch'; + }); + + if (isToggle) { + const shouldBeChecked = value.toLowerCase() === 'true'; + const isChecked = await handle.evaluate(el => { + if (el instanceof HTMLInputElement) { + return el.checked; + } + return el.getAttribute('aria-checked') === 'true'; + }); + + if (isChecked !== shouldBeChecked) { + await handle.asLocator().click(); + } + } else { + // Increase timeout for longer input values. + const timeoutPerChar = 10; // ms + const fillTimeout = + page.pptrPage.getDefaultTimeout() + value.length * timeoutPerChar; + await handle.asLocator().setTimeout(fillTimeout).fill(value); + } } } catch (error) { handleActionError(error, uid); diff --git a/tests/tools/input.test.ts b/tests/tools/input.test.ts index b0033ec4d..e6bf8dc10 100644 --- a/tests/tools/input.test.ts +++ b/tests/tools/input.test.ts @@ -622,6 +622,180 @@ describe('input', () => { ); }); }); + + it('toggles checkboxes', async () => { + await withMcpContext(async (response, context) => { + const page = context.getSelectedPptrPage(); + await page.setContent( + html``, + ); + context.getSelectedMcpPage().textSnapshot = await TextSnapshot.create( + context.getSelectedMcpPage(), + ); + + // Check it + await fill.handler( + { + params: { + uid: '1_1', + value: 'true', + }, + page: context.getSelectedMcpPage(), + }, + response, + context, + ); + + assert.strictEqual( + response.responseLines[0], + 'Successfully filled out the element', + ); + assert.ok(response.includeSnapshot); + let isChecked = await page.$eval( + '#cb', + el => (el as HTMLInputElement).checked, + ); + assert.strictEqual(isChecked, true); + + // Uncheck it + await fill.handler( + { + params: { + uid: '1_1', + value: 'false', + }, + page: context.getSelectedMcpPage(), + }, + new McpResponse({} as ParsedArguments), + context, + ); + + isChecked = await page.$eval( + '#cb', + el => (el as HTMLInputElement).checked, + ); + assert.strictEqual(isChecked, false); + }); + }); + + it('toggles switches', async () => { + await withMcpContext(async (response, context) => { + const page = context.getSelectedPptrPage(); + await page.setContent(html` +
+ switch +
+ `); + context.getSelectedMcpPage().textSnapshot = await TextSnapshot.create( + context.getSelectedMcpPage(), + ); + + // Turn it on + await fill.handler( + { + params: { + uid: '1_1', + value: 'true', + }, + page: context.getSelectedMcpPage(), + }, + response, + context, + ); + + let swChecked = await page.$eval( + '#sw', + el => el.getAttribute('aria-checked') === 'true', + ); + assert.strictEqual(swChecked, true); + + // Turn it off + await fill.handler( + { + params: { + uid: '1_1', + value: 'false', + }, + page: context.getSelectedMcpPage(), + }, + new McpResponse({} as ParsedArguments), + context, + ); + + swChecked = await page.$eval( + '#sw', + el => el.getAttribute('aria-checked') === 'true', + ); + assert.strictEqual(swChecked, false); + }); + }); + + it('selects radio buttons', async () => { + await withMcpContext(async (response, context) => { + const page = context.getSelectedPptrPage(); + await page.setContent(html` + + + `); + context.getSelectedMcpPage().textSnapshot = await TextSnapshot.create( + context.getSelectedMcpPage(), + ); + + // Initial state + let r1Checked = await page.$eval( + '#r1', + el => (el as HTMLInputElement).checked, + ); + let r2Checked = await page.$eval( + '#r2', + el => (el as HTMLInputElement).checked, + ); + assert.strictEqual(r1Checked, true); + assert.strictEqual(r2Checked, false); + + // Fill second radio with true + await fill.handler( + { + params: { + uid: '1_2', + value: 'true', + }, + page: context.getSelectedMcpPage(), + }, + response, + context, + ); + + r1Checked = await page.$eval( + '#r1', + el => (el as HTMLInputElement).checked, + ); + r2Checked = await page.$eval( + '#r2', + el => (el as HTMLInputElement).checked, + ); + assert.strictEqual(r1Checked, false); + assert.strictEqual(r2Checked, true); + }); + }); }); describe('drags', () => {