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:
Provi
2026-02-23 09:44:08 -06:00
committed by GitHub
co-authored by Claude Opus 4.6
parent f10f3f6425
commit ad6e206a90
9 changed files with 186 additions and 5 deletions
+32
View File
@@ -263,6 +263,38 @@ pub fn parse_command(args: &[String], flags: &Flags) -> Result<Value, ParseError
})?;
Ok(json!({ "id": id, "action": "keyup", "key": key }))
}
"keyboard" => {
let sub = rest.first().ok_or_else(|| ParseError::MissingArguments {
context: "keyboard".to_string(),
usage: "keyboard <type|inserttext> <text>",
})?;
match *sub {
"type" => {
let text: String = rest[1..].join(" ");
if text.is_empty() {
return Err(ParseError::MissingArguments {
context: "keyboard type".to_string(),
usage: "keyboard type <text>",
});
}
Ok(json!({ "id": id, "action": "keyboard", "subaction": "type", "text": text }))
}
"inserttext" | "insertText" => {
let text: String = rest[1..].join(" ");
if text.is_empty() {
return Err(ParseError::MissingArguments {
context: "keyboard inserttext".to_string(),
usage: "keyboard inserttext <text>",
});
}
Ok(json!({ "id": id, "action": "keyboard", "subaction": "insertText", "text": text }))
}
_ => Err(ParseError::UnknownSubcommand {
subcommand: sub.to_string(),
valid_options: &["type", "inserttext"],
}),
}
}
// === Scroll ===
"scroll" => {