feat: add keyboard command for raw keyboard input (#521)
Adds `keyboard type` and `keyboard insertText` subcommands that operate on the currently focused element without requiring a selector. Essential for contenteditable editors (Lexical, ProseMirror, CodeMirror, Monaco) where `type <selector>` doesn't trigger the editor's internal event pipeline (beforeinput/DOM mutation). - `keyboard type <text>` — page.keyboard.type() with real keystrokes - `keyboard insertText <text>` — page.keyboard.insertText() Note: `keyboard press` intentionally omitted — the existing top-level `press` command already operates on current focus. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+15
-2
@@ -1894,8 +1894,21 @@ async function handleKeyboard(
|
||||
browser: BrowserManager
|
||||
): Promise<Response> {
|
||||
const page = browser.getPage();
|
||||
await page.keyboard.press(command.keys);
|
||||
return successResponse(command.id, { pressed: command.keys });
|
||||
const sub = command.subaction ?? 'press';
|
||||
|
||||
switch (sub) {
|
||||
case 'type':
|
||||
await page.keyboard.type(command.text ?? '', { delay: command.delay });
|
||||
return successResponse(command.id, { typed: true, text: command.text });
|
||||
case 'press':
|
||||
await page.keyboard.press(command.keys ?? '');
|
||||
return successResponse(command.id, { pressed: command.keys });
|
||||
case 'insertText':
|
||||
await page.keyboard.insertText(command.text ?? '');
|
||||
return successResponse(command.id, { inserted: true, text: command.text });
|
||||
default:
|
||||
return errorResponse(command.id, `Unknown keyboard subaction: ${sub}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleWheel(command: WheelCommand, browser: BrowserManager): Promise<Response> {
|
||||
|
||||
+14
-1
@@ -442,7 +442,10 @@ const errorsSchema = baseCommandSchema.extend({
|
||||
|
||||
const keyboardSchema = baseCommandSchema.extend({
|
||||
action: z.literal('keyboard'),
|
||||
keys: z.string().min(1),
|
||||
subaction: z.enum(['type', 'press', 'insertText']).optional(),
|
||||
keys: z.string().min(1).optional(),
|
||||
text: z.string().min(1).optional(),
|
||||
delay: z.number().optional(),
|
||||
});
|
||||
|
||||
const wheelSchema = baseCommandSchema.extend({
|
||||
@@ -1058,6 +1061,16 @@ export function parseCommand(input: string): ParseResult {
|
||||
};
|
||||
}
|
||||
|
||||
if (command.action === 'keyboard') {
|
||||
const sub = command.subaction ?? 'press';
|
||||
if ((sub === 'type' || sub === 'insertText') && !command.text) {
|
||||
return { success: false, error: `keyboard ${sub} requires text`, id };
|
||||
}
|
||||
if (sub === 'press' && !command.keys) {
|
||||
return { success: false, error: 'keyboard press requires keys', id };
|
||||
}
|
||||
}
|
||||
|
||||
return { success: true, command };
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -667,10 +667,13 @@ export interface ErrorsCommand extends BaseCommand {
|
||||
clear?: boolean;
|
||||
}
|
||||
|
||||
// Keyboard shortcuts
|
||||
// Raw keyboard input (no selector needed)
|
||||
export interface KeyboardCommand extends BaseCommand {
|
||||
action: 'keyboard';
|
||||
keys: string; // e.g., "Control+a", "Shift+Tab"
|
||||
subaction?: 'type' | 'press' | 'insertText'; // press kept for backward compat
|
||||
keys?: string; // for legacy press path
|
||||
text?: string; // for type/insertText
|
||||
delay?: number; // for type (ms between keystrokes)
|
||||
}
|
||||
|
||||
// Mouse wheel
|
||||
|
||||
Reference in New Issue
Block a user