mirror of
				https://github.com/microsoft/playwright-mcp.git
				synced 2025-10-12 00:25:14 +03:00 
			
		
		
		
	chore: introduce form filling tool (#935)
This commit is contained in:
		@@ -494,6 +494,15 @@ http.createServer(async (req, res) => {
 | 
			
		||||
 | 
			
		||||
<!-- NOTE: This has been generated via update-readme.js -->
 | 
			
		||||
 | 
			
		||||
- **browser_fill_form**
 | 
			
		||||
  - Title: Fill form
 | 
			
		||||
  - Description: Fill multiple form fields
 | 
			
		||||
  - Parameters:
 | 
			
		||||
    - `fields` (array): Fields to fill in
 | 
			
		||||
  - Read-only: **false**
 | 
			
		||||
 | 
			
		||||
<!-- NOTE: This has been generated via update-readme.js -->
 | 
			
		||||
 | 
			
		||||
- **browser_handle_dialog**
 | 
			
		||||
  - Title: Handle a dialog
 | 
			
		||||
  - Description: Handle a dialog
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ import console from './tools/console.js';
 | 
			
		||||
import dialogs from './tools/dialogs.js';
 | 
			
		||||
import evaluate from './tools/evaluate.js';
 | 
			
		||||
import files from './tools/files.js';
 | 
			
		||||
import form from './tools/form.js';
 | 
			
		||||
import install from './tools/install.js';
 | 
			
		||||
import keyboard from './tools/keyboard.js';
 | 
			
		||||
import navigate from './tools/navigate.js';
 | 
			
		||||
@@ -39,6 +40,7 @@ export const allTools: Tool<any>[] = [
 | 
			
		||||
  ...dialogs,
 | 
			
		||||
  ...evaluate,
 | 
			
		||||
  ...files,
 | 
			
		||||
  ...form,
 | 
			
		||||
  ...install,
 | 
			
		||||
  ...keyboard,
 | 
			
		||||
  ...navigate,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										61
									
								
								src/tools/form.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/tools/form.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright (c) Microsoft Corporation.
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import { z } from 'zod';
 | 
			
		||||
 | 
			
		||||
import { defineTabTool } from './tool.js';
 | 
			
		||||
import { generateLocator } from './utils.js';
 | 
			
		||||
import * as javascript from '../utils/codegen.js';
 | 
			
		||||
 | 
			
		||||
const fillForm = defineTabTool({
 | 
			
		||||
  capability: 'core',
 | 
			
		||||
 | 
			
		||||
  schema: {
 | 
			
		||||
    name: 'browser_fill_form',
 | 
			
		||||
    title: 'Fill form',
 | 
			
		||||
    description: 'Fill multiple form fields',
 | 
			
		||||
    inputSchema: z.object({
 | 
			
		||||
      fields: z.array(z.object({
 | 
			
		||||
        name: z.string().describe('Human-readable field name'),
 | 
			
		||||
        type: z.enum(['textbox', 'checkbox', 'radio', 'combobox', 'slider']).describe('Type of the field'),
 | 
			
		||||
        ref: z.string().describe('Exact target field reference from the page snapshot'),
 | 
			
		||||
        value: z.string().describe('Value to fill in the field. If the field is a checkbox, the value should be `true` or `false`. If the field is a combobox, the value should be the text of the option.'),
 | 
			
		||||
      })).describe('Fields to fill in'),
 | 
			
		||||
    }),
 | 
			
		||||
    type: 'destructive',
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  handle: async (tab, params, response) => {
 | 
			
		||||
    for (const field of params.fields) {
 | 
			
		||||
      const locator = await tab.refLocator({ element: field.name, ref: field.ref });
 | 
			
		||||
      const locatorSource = `await page.${await generateLocator(locator)}`;
 | 
			
		||||
      if (field.type === 'textbox' || field.type === 'slider') {
 | 
			
		||||
        await locator.fill(field.value);
 | 
			
		||||
        response.addCode(`${locatorSource}.fill(${javascript.quote(field.value)});`);
 | 
			
		||||
      } else if (field.type === 'checkbox' || field.type === 'radio') {
 | 
			
		||||
        await locator.setChecked(field.value === 'true');
 | 
			
		||||
        response.addCode(`${locatorSource}.setChecked(${javascript.quote(field.value)});`);
 | 
			
		||||
      } else if (field.type === 'combobox') {
 | 
			
		||||
        await locator.selectOption({ label: field.value });
 | 
			
		||||
        response.addCode(`${locatorSource}.selectOption(${javascript.quote(field.value)});`);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default [
 | 
			
		||||
  fillForm,
 | 
			
		||||
];
 | 
			
		||||
@@ -24,6 +24,7 @@ test('test snapshot tool list', async ({ client }) => {
 | 
			
		||||
    'browser_drag',
 | 
			
		||||
    'browser_evaluate',
 | 
			
		||||
    'browser_file_upload',
 | 
			
		||||
    'browser_fill_form',
 | 
			
		||||
    'browser_handle_dialog',
 | 
			
		||||
    'browser_hover',
 | 
			
		||||
    'browser_select_option',
 | 
			
		||||
@@ -54,6 +55,7 @@ test('test tool list proxy mode', async ({ startClient }) => {
 | 
			
		||||
    'browser_drag',
 | 
			
		||||
    'browser_evaluate',
 | 
			
		||||
    'browser_file_upload',
 | 
			
		||||
    'browser_fill_form',
 | 
			
		||||
    'browser_handle_dialog',
 | 
			
		||||
    'browser_hover',
 | 
			
		||||
    'browser_select_option',
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										123
									
								
								tests/form.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								tests/form.spec.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,123 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright (c) Microsoft Corporation.
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 * http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import { test, expect } from './fixtures.js';
 | 
			
		||||
 | 
			
		||||
test('browser_fill_form (textbox)', async ({ client, server }) => {
 | 
			
		||||
  server.setContent('/', `
 | 
			
		||||
    <!DOCTYPE html>
 | 
			
		||||
    <html>
 | 
			
		||||
      <body>
 | 
			
		||||
        <form>
 | 
			
		||||
          <label>
 | 
			
		||||
            <input type="text" id="name" name="name" />
 | 
			
		||||
            Name
 | 
			
		||||
          </label>
 | 
			
		||||
          <label>
 | 
			
		||||
            <input type="email" id="email" name="email" />
 | 
			
		||||
            Email
 | 
			
		||||
          </label>
 | 
			
		||||
          <label>
 | 
			
		||||
            <input type="range" id="age" name="age" min="18" max="100" />
 | 
			
		||||
            Age
 | 
			
		||||
          </label>
 | 
			
		||||
          <label>
 | 
			
		||||
            <select id="country" name="country">
 | 
			
		||||
              <option value="">Choose a country</option>
 | 
			
		||||
              <option value="us">United States</option>
 | 
			
		||||
              <option value="uk">United Kingdom</option>
 | 
			
		||||
            </select>
 | 
			
		||||
            Country
 | 
			
		||||
          </label>
 | 
			
		||||
          <label>
 | 
			
		||||
            <input type="checkbox" name="subscribe" value="newsletter" />
 | 
			
		||||
            Subscribe to newsletter
 | 
			
		||||
          </label>
 | 
			
		||||
        </form>
 | 
			
		||||
      </body>
 | 
			
		||||
    </html>
 | 
			
		||||
  `, 'text/html');
 | 
			
		||||
 | 
			
		||||
  await client.callTool({
 | 
			
		||||
    name: 'browser_navigate',
 | 
			
		||||
    arguments: { url: server.PREFIX },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  expect(await client.callTool({
 | 
			
		||||
    name: 'browser_fill_form',
 | 
			
		||||
    arguments: {
 | 
			
		||||
      fields: [
 | 
			
		||||
        {
 | 
			
		||||
          name: 'Name textbox',
 | 
			
		||||
          type: 'textbox',
 | 
			
		||||
          ref: 'e4',
 | 
			
		||||
          value: 'John Doe'
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          name: 'Email textbox',
 | 
			
		||||
          type: 'textbox',
 | 
			
		||||
          ref: 'e6',
 | 
			
		||||
          value: 'john.doe@example.com'
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          name: 'Age textbox',
 | 
			
		||||
          type: 'slider',
 | 
			
		||||
          ref: 'e8',
 | 
			
		||||
          value: '25'
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          name: 'Country select',
 | 
			
		||||
          type: 'combobox',
 | 
			
		||||
          ref: 'e10',
 | 
			
		||||
          value: 'United States'
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          name: 'Subscribe checkbox',
 | 
			
		||||
          type: 'checkbox',
 | 
			
		||||
          ref: 'e12',
 | 
			
		||||
          value: 'true'
 | 
			
		||||
        },
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
  })).toHaveResponse({
 | 
			
		||||
    code: `await page.getByRole('textbox', { name: 'Name' }).fill('John Doe');
 | 
			
		||||
await page.getByRole('textbox', { name: 'Email' }).fill('john.doe@example.com');
 | 
			
		||||
await page.getByRole('slider', { name: 'Age' }).fill('25');
 | 
			
		||||
await page.getByLabel('Choose a country United').selectOption('United States');
 | 
			
		||||
await page.getByRole('checkbox', { name: 'Subscribe to newsletter' }).setChecked('true');`,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const response = await client.callTool({
 | 
			
		||||
    name: 'browser_snapshot',
 | 
			
		||||
    arguments: {
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
  expect.soft(response).toHaveResponse({
 | 
			
		||||
    pageState: expect.stringMatching(/textbox "Name".*John Doe/),
 | 
			
		||||
  });
 | 
			
		||||
  expect.soft(response).toHaveResponse({
 | 
			
		||||
    pageState: expect.stringMatching(/textbox "Email".*john.doe@example.com/),
 | 
			
		||||
  });
 | 
			
		||||
  expect.soft(response).toHaveResponse({
 | 
			
		||||
    pageState: expect.stringMatching(/slider "Age".*"25"/),
 | 
			
		||||
  });
 | 
			
		||||
  expect.soft(response).toHaveResponse({
 | 
			
		||||
    pageState: expect.stringContaining('option \"United States\" [selected]'),
 | 
			
		||||
  });
 | 
			
		||||
  expect.soft(response).toHaveResponse({
 | 
			
		||||
    pageState: expect.stringContaining('checkbox \"Subscribe to newsletter\" [checked]'),
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
		Reference in New Issue
	
	Block a user