mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-01 06:24:41 +01:00
G5: browse type --selector <sel> <text> (#2936)
--selector focuses the element (sel or @ref) and types into it. The bare form still types its text literally into the focused element; text that starts with -- goes after a -- separator. When the first word looks like a CSS selector, the result adds a hint to use --selector without echoing the typed text, and never refuses based on page contents. Help and command usage updated. Co-authored-by: Thomas Reuser <thomasreuser@gmail.com>
This commit is contained in:
1 parent
616773158b
commit
5015a939a9
4 files changed
+56
-5
No files matched your search
+1
-1
@@ -1647,7 +1647,7 @@ Usage: browse <command> [args...]
|
||||
Navigation: goto <url> | back | forward | reload | url
|
||||
Content: text | html [sel] | links | forms | accessibility
|
||||
Interaction: click <sel> | fill <sel> <val> | select <sel> <val>
|
||||
hover <sel> | type <text> | press <key>
|
||||
hover <sel> | type [--selector <sel>] <text> | press <key>
|
||||
scroll [sel] | wait <sel|--networkidle|--load> | viewport <WxH>
|
||||
upload <sel> <file1> [file2...]
|
||||
cookie-import <json-file>
|
||||
|
||||
@@ -122,7 +122,7 @@ export const COMMAND_DESCRIPTIONS: Record<string, { category: string; descriptio
|
||||
'fill': { category: 'Interaction', description: 'Fill input', usage: 'fill <sel> <val>' },
|
||||
'select': { category: 'Interaction', description: 'Select dropdown option by value, label, or visible text', usage: 'select <sel> <val>' },
|
||||
'hover': { category: 'Interaction', description: 'Hover element', usage: 'hover <sel>' },
|
||||
'type': { category: 'Interaction', description: 'Type into focused element', usage: 'type <text>' },
|
||||
'type': { category: 'Interaction', description: 'Send keystrokes to the focused element, or to <sel> with --selector (focuses it first). Text that starts with -- goes after a -- separator.', usage: 'type [--selector <sel>] [--] <text>' },
|
||||
'press': { category: 'Interaction', description: 'Press a Playwright keyboard key against the focused element. Names are case-sensitive: Enter, Tab, Escape, ArrowUp/Down/Left/Right, Backspace, Delete, Home, End, PageUp, PageDown. Modifiers combine with +: Shift+Enter, Control+A, Meta+K. Single printable chars (a, A, 1) work too. Full key list: https://playwright.dev/docs/api/class-keyboard#keyboard-press', usage: 'press <key>' },
|
||||
'scroll': { category: 'Interaction', description: 'With a selector, smooth-scrolls the element into view. Without a selector, jumps to page bottom. No --by/--to amount option; for pixel-precise scrolling use `js window.scrollTo(0, N)`.', usage: 'scroll [sel|@ref]' },
|
||||
'wait': { category: 'Interaction', description: 'Wait for element, network idle, or page load (timeout: 15s)', usage: 'wait <sel|--networkidle|--load>' },
|
||||
|
||||
@@ -420,10 +420,32 @@ export async function handleWriteCommand(
|
||||
}
|
||||
|
||||
case 'type': {
|
||||
const text = args.join(' ');
|
||||
if (!text) throw new Error('Usage: browse type <text>');
|
||||
// #2936: bare `type` sends keystrokes to whatever has focus, so a selector
|
||||
// given as the first word was typed as text. --selector targets an
|
||||
// element; `--` ends flags so literal text may start with "--".
|
||||
const usage = 'Usage: browse type [--selector <sel>] [--] <text>';
|
||||
let rest = args;
|
||||
let selector: string | undefined;
|
||||
if (rest[0] === '--selector') {
|
||||
selector = rest[1];
|
||||
if (!selector) throw new Error(usage);
|
||||
rest = rest.slice(2);
|
||||
}
|
||||
if (rest[0] === '--') rest = rest.slice(1);
|
||||
const text = rest.join(' ');
|
||||
if (!text) throw new Error(usage);
|
||||
if (selector) {
|
||||
const resolved = await session.resolveRef(selector);
|
||||
const locator = 'locator' in resolved ? resolved.locator : target.locator(resolved.selector);
|
||||
await locator.pressSequentially(text, { timeout: 5000 });
|
||||
return `Typed ${text.length} characters into ${selector}`;
|
||||
}
|
||||
await page.keyboard.type(text);
|
||||
return `Typed ${text.length} characters`;
|
||||
const first = rest[0];
|
||||
const selectorLike = /^([#.][A-Za-z_-]|\[[^\]]+\]$|[A-Za-z][\w-]*[#.[][A-Za-z_-])/.test(first) || rest.includes('>');
|
||||
return selectorLike
|
||||
? `Typed ${text.length} characters into the focused element\nhint: "${first}" looks like a CSS selector. To type into that element: browse type --selector '${first}' <text>`
|
||||
: `Typed ${text.length} characters`;
|
||||
}
|
||||
|
||||
case 'press': {
|
||||
|
||||
@@ -559,6 +559,35 @@ describe('Interaction', () => {
|
||||
const val = await handleReadCommand('js', ['document.querySelector("#name").value'], bm);
|
||||
expect(val).toBe('John Doe');
|
||||
});
|
||||
|
||||
test('type --selector types into that element, not the focused one (#2936)', async () => {
|
||||
await handleWriteCommand('goto', [baseUrl + '/forms.html'], bm);
|
||||
await handleWriteCommand('click', ['#email'], bm);
|
||||
const result = await handleWriteCommand('type', ['--selector', '#name', 'Jane', 'Roe'], bm);
|
||||
expect(result).toBe('Typed 8 characters into #name');
|
||||
expect(await handleReadCommand('js', ['document.querySelector("#name").value'], bm)).toBe('Jane Roe');
|
||||
expect(await handleReadCommand('js', ['document.querySelector("#email").value'], bm)).toBe('');
|
||||
});
|
||||
|
||||
test('bare type keeps typing selector-looking text literally, with a hint and no refusal', async () => {
|
||||
await handleWriteCommand('goto', [baseUrl + '/forms.html'], bm);
|
||||
await handleWriteCommand('click', ['#bio'], bm);
|
||||
const result = await handleWriteCommand('type', ['#name', 'secret-value'], bm);
|
||||
expect(result).toContain('Typed 18 characters into the focused element');
|
||||
expect(result).toContain("hint: \"#name\" looks like a CSS selector. To type into that element: browse type --selector '#name' <text>");
|
||||
expect(result).not.toContain('secret-value');
|
||||
expect(await handleReadCommand('js', ['document.querySelector("#bio").value'], bm)).toBe('#name secret-value');
|
||||
expect(await handleReadCommand('js', ['document.querySelector("#name").value'], bm)).toBe('');
|
||||
});
|
||||
|
||||
test('type -- types text that starts with -- literally; plain text gets no hint', async () => {
|
||||
await handleWriteCommand('goto', [baseUrl + '/forms.html'], bm);
|
||||
await handleWriteCommand('click', ['#bio'], bm);
|
||||
expect(await handleWriteCommand('type', ['--', '--selector', 'x'], bm)).toBe('Typed 12 characters');
|
||||
expect(await handleReadCommand('js', ['document.querySelector("#bio").value'], bm)).toBe('--selector x');
|
||||
await expect(handleWriteCommand('type', ['--selector'], bm)).rejects.toThrow('Usage: browse type [--selector <sel>] [--] <text>');
|
||||
await expect(handleWriteCommand('type', ['--selector', '#name'], bm)).rejects.toThrow('Usage');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── SPA / Console / Network ───────────────────────────────────
|
||||
|
||||
Reference in new issue
Block a user