Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions packages/playwright-core/src/tools/cli-daemon/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ const numberArg = z.preprocess((val, ctx) => {
return number;
}, z.number());

// Minimist returns a string for a single occurrence and an array for repeated.
const stringArrayArg = z.union([z.string(), z.array(z.string())]).transform(v => Array.isArray(v) ? v : [v]);

// Navigation commands

const open = declareCommand({
Expand Down Expand Up @@ -235,7 +238,7 @@ const click = declareCommand({
button: z.string().optional().describe('Button to click, defaults to left'),
}),
options: z.object({
modifiers: z.array(z.string()).optional().describe('Modifier keys to press'),
modifiers: stringArrayArg.optional().describe('Modifier key to press (repeatable)'),
}),
toolName: 'browser_click',
toolParams: ({ target, button, modifiers }) => ({ target, button, modifiers }),
Expand All @@ -250,7 +253,7 @@ const doubleClick = declareCommand({
button: z.string().optional().describe('Button to click, defaults to left'),
}),
options: z.object({
modifiers: z.array(z.string()).optional().describe('Modifier keys to press'),
modifiers: stringArrayArg.optional().describe('Modifier key to press (repeatable)'),
}),
toolName: 'browser_click',
toolParams: ({ target, button, modifiers }) => ({ target, button, modifiers, doubleClick: true }),
Expand All @@ -276,8 +279,8 @@ const drop = declareCommand({
target: z.string().describe(elementTargetDescription),
}),
options: z.object({
path: z.union([z.string(), z.array(z.string())]).optional().transform(v => v ? (Array.isArray(v) ? v : [v]) : undefined).describe('Absolute path to a file to drop onto the element (repeatable)'),
data: z.union([z.string(), z.array(z.string())]).optional().transform(v => v ? (Array.isArray(v) ? v : [v]) : undefined).describe('Data to drop in "mime/type=value" format, e.g. --data "text/plain=hello" (repeatable)'),
path: stringArrayArg.optional().describe('Absolute path to a file to drop onto the element (repeatable)'),
data: stringArrayArg.optional().describe('Data to drop in "mime/type=value" format, e.g. --data "text/plain=hello" (repeatable)'),
}),
toolName: 'browser_drop',
toolParams: ({ target, path, data }) => {
Expand Down Expand Up @@ -730,7 +733,7 @@ const routeMock = declareCommand({
status: numberArg.optional().describe('HTTP status code (default: 200)'),
body: z.string().optional().describe('Response body (text or JSON string)'),
['content-type']: z.string().optional().describe('Content-Type header'),
header: z.union([z.string(), z.array(z.string())]).optional().transform(v => v ? (Array.isArray(v) ? v : [v]) : undefined).describe('Header to add in "Name: Value" format (repeatable)'),
header: stringArrayArg.optional().describe('Header to add in "Name: Value" format (repeatable)'),
['remove-header']: z.string().optional().describe('Comma-separated header names to remove'),
}),
toolName: 'browser_route',
Expand Down
11 changes: 11 additions & 0 deletions tests/mcp/cli-core.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,17 @@ test('dblclick', async ({ cli, server }) => {
expect(snapshot).toContain('dblclick 0');
});

test('click with --modifiers', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright-cli/issues/406' } }, async ({ cli, server }) => {
server.setContent('/', `<button>Submit</button>`, 'text/html');
await cli('open', server.PREFIX);

const single = await cli('click', 'e2', '--modifiers', 'Control');
expect(single.output).toContain(`await page.getByRole('button', { name: 'Submit' }).click({\n modifiers: ['Control']\n});`);

const repeated = await cli('click', 'e2', '--modifiers', 'Control', '--modifiers', 'Shift');
expect(repeated.output).toContain(`await page.getByRole('button', { name: 'Submit' }).click({\n modifiers: ['Control', 'Shift']\n});`);
});

test('type', async ({ cli, server }) => {
server.setContent('/', `<input type=text>`, 'text/html');
const { snapshot } = await cli('open', server.PREFIX);
Expand Down
Loading