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 -->
|
<!-- 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**
|
- **browser_handle_dialog**
|
||||||
- Title: Handle a dialog
|
- Title: Handle a dialog
|
||||||
- Description: Handle a dialog
|
- Description: Handle a dialog
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import console from './tools/console.js';
|
|||||||
import dialogs from './tools/dialogs.js';
|
import dialogs from './tools/dialogs.js';
|
||||||
import evaluate from './tools/evaluate.js';
|
import evaluate from './tools/evaluate.js';
|
||||||
import files from './tools/files.js';
|
import files from './tools/files.js';
|
||||||
|
import form from './tools/form.js';
|
||||||
import install from './tools/install.js';
|
import install from './tools/install.js';
|
||||||
import keyboard from './tools/keyboard.js';
|
import keyboard from './tools/keyboard.js';
|
||||||
import navigate from './tools/navigate.js';
|
import navigate from './tools/navigate.js';
|
||||||
@@ -39,6 +40,7 @@ export const allTools: Tool<any>[] = [
|
|||||||
...dialogs,
|
...dialogs,
|
||||||
...evaluate,
|
...evaluate,
|
||||||
...files,
|
...files,
|
||||||
|
...form,
|
||||||
...install,
|
...install,
|
||||||
...keyboard,
|
...keyboard,
|
||||||
...navigate,
|
...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_drag',
|
||||||
'browser_evaluate',
|
'browser_evaluate',
|
||||||
'browser_file_upload',
|
'browser_file_upload',
|
||||||
|
'browser_fill_form',
|
||||||
'browser_handle_dialog',
|
'browser_handle_dialog',
|
||||||
'browser_hover',
|
'browser_hover',
|
||||||
'browser_select_option',
|
'browser_select_option',
|
||||||
@@ -54,6 +55,7 @@ test('test tool list proxy mode', async ({ startClient }) => {
|
|||||||
'browser_drag',
|
'browser_drag',
|
||||||
'browser_evaluate',
|
'browser_evaluate',
|
||||||
'browser_file_upload',
|
'browser_file_upload',
|
||||||
|
'browser_fill_form',
|
||||||
'browser_handle_dialog',
|
'browser_handle_dialog',
|
||||||
'browser_hover',
|
'browser_hover',
|
||||||
'browser_select_option',
|
'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