more
This commit is contained in:
@@ -49,6 +49,10 @@ veb focus "#input"
|
|||||||
veb drag "#source" "#target"
|
veb drag "#source" "#target"
|
||||||
veb wheel 300 # Scroll wheel (deltaY)
|
veb wheel 300 # Scroll wheel (deltaY)
|
||||||
veb wheel 0 100 # Scroll wheel (deltaX, deltaY)
|
veb wheel 0 100 # Scroll wheel (deltaX, deltaY)
|
||||||
|
veb mousemove 100 200 # Move mouse to coordinates
|
||||||
|
veb mousedown # Press mouse button
|
||||||
|
veb mouseup # Release mouse button
|
||||||
|
veb mousedown right # Right-click down
|
||||||
|
|
||||||
# Text operations
|
# Text operations
|
||||||
veb selectall "#input" # Select all text
|
veb selectall "#input" # Select all text
|
||||||
@@ -67,6 +71,9 @@ veb download "#download-btn" ./file.zip
|
|||||||
veb wait "#loading" # Wait for selector
|
veb wait "#loading" # Wait for selector
|
||||||
veb wait --text "Welcome" # Wait for text
|
veb wait --text "Welcome" # Wait for text
|
||||||
veb wait 2000 # Wait 2 seconds
|
veb wait 2000 # Wait 2 seconds
|
||||||
|
veb waitforurl "**/dashboard" # Wait for URL pattern
|
||||||
|
veb waitforload networkidle # Wait for network idle
|
||||||
|
veb waitforload domcontentloaded # Wait for DOM ready
|
||||||
|
|
||||||
# Screenshots & PDF
|
# Screenshots & PDF
|
||||||
veb screenshot page.png
|
veb screenshot page.png
|
||||||
@@ -104,6 +111,15 @@ veb text "Sign In" click
|
|||||||
veb text "Submit" click --exact
|
veb text "Submit" click --exact
|
||||||
veb label "Email" fill "test@test.com"
|
veb label "Email" fill "test@test.com"
|
||||||
veb placeholder "Search..." fill "query"
|
veb placeholder "Search..." fill "query"
|
||||||
|
veb alt "Logo" click # By alt text
|
||||||
|
veb bytitle "Close" click # By title attribute
|
||||||
|
veb testid "submit-btn" click # By data-testid
|
||||||
|
|
||||||
|
# Nth element (when multiple match)
|
||||||
|
veb first "a" click # Click first link
|
||||||
|
veb last "a" click # Click last link
|
||||||
|
veb nth "a" 2 click # Click 3rd link (0-indexed)
|
||||||
|
veb first ".item" text # Get text of first item
|
||||||
|
|
||||||
# Frames/iframes
|
# Frames/iframes
|
||||||
veb frame "#iframe" # Switch to iframe
|
veb frame "#iframe" # Switch to iframe
|
||||||
@@ -128,6 +144,9 @@ veb media --reduced-motion # Reduced motion
|
|||||||
veb offline # Enable offline mode
|
veb offline # Enable offline mode
|
||||||
veb offline off # Disable offline mode
|
veb offline off # Disable offline mode
|
||||||
veb headers '{"X-Custom":"value"}' # Set extra headers
|
veb headers '{"X-Custom":"value"}' # Set extra headers
|
||||||
|
veb credentials user pass # HTTP basic auth
|
||||||
|
veb timezone America/New_York # Set timezone (at launch)
|
||||||
|
veb locale fr-FR # Set locale (at launch)
|
||||||
|
|
||||||
# Cookies
|
# Cookies
|
||||||
veb cookies # Get all cookies
|
veb cookies # Get all cookies
|
||||||
@@ -163,6 +182,7 @@ veb addscript "console.log('hi')"
|
|||||||
veb addscript https://example.com/lib.js
|
veb addscript https://example.com/lib.js
|
||||||
veb addstyle "body { background: red; }"
|
veb addstyle "body { background: red; }"
|
||||||
veb addstyle https://example.com/styles.css
|
veb addstyle https://example.com/styles.css
|
||||||
|
veb setcontent '<h1>Hello</h1>' # Set page HTML directly
|
||||||
|
|
||||||
# Custom events
|
# Custom events
|
||||||
veb dispatch "#btn" "click"
|
veb dispatch "#btn" "click"
|
||||||
@@ -255,6 +275,12 @@ veb isvisible "#modal" --json
|
|||||||
| `text <text> <action>` | By text |
|
| `text <text> <action>` | By text |
|
||||||
| `label <label> <action>` | By label |
|
| `label <label> <action>` | By label |
|
||||||
| `placeholder <ph> <action>` | By placeholder |
|
| `placeholder <ph> <action>` | By placeholder |
|
||||||
|
| `alt <text> <action>` | By alt text |
|
||||||
|
| `bytitle <text> <action>` | By title attr |
|
||||||
|
| `testid <id> <action>` | By data-testid |
|
||||||
|
| `first <sel> <action>` | First match |
|
||||||
|
| `last <sel> <action>` | Last match |
|
||||||
|
| `nth <sel> <n> <action>` | Nth match |
|
||||||
|
|
||||||
### Content & Screenshots
|
### Content & Screenshots
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
@@ -263,8 +289,11 @@ veb isvisible "#modal" --json
|
|||||||
| `pdf <path>` | Save as PDF |
|
| `pdf <path>` | Save as PDF |
|
||||||
| `snapshot` | Accessibility tree |
|
| `snapshot` | Accessibility tree |
|
||||||
| `extract <selector>` | Get HTML |
|
| `extract <selector>` | Get HTML |
|
||||||
|
| `setcontent <html>` | Set page HTML |
|
||||||
| `eval <script>` | Run JavaScript |
|
| `eval <script>` | Run JavaScript |
|
||||||
| `wait <sel\|text\|ms>` | Wait |
|
| `wait <sel\|text\|ms>` | Wait |
|
||||||
|
| `waitforurl <pattern>` | Wait for URL |
|
||||||
|
| `waitforload <state>` | Wait for load |
|
||||||
|
|
||||||
### Network
|
### Network
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
@@ -283,6 +312,16 @@ veb isvisible "#modal" --json
|
|||||||
| `media [options]` | Emulate media |
|
| `media [options]` | Emulate media |
|
||||||
| `offline [on\|off]` | Offline mode |
|
| `offline [on\|off]` | Offline mode |
|
||||||
| `headers <json>` | Extra HTTP headers |
|
| `headers <json>` | Extra HTTP headers |
|
||||||
|
| `credentials <u> <p>` | HTTP basic auth |
|
||||||
|
| `timezone <tz>` | Set timezone |
|
||||||
|
| `locale <locale>` | Set locale |
|
||||||
|
|
||||||
|
### Mouse Control
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `mousemove <x> <y>` | Move mouse |
|
||||||
|
| `mousedown [button]` | Press button |
|
||||||
|
| `mouseup [button]` | Release button |
|
||||||
|
|
||||||
### Browser State
|
### Browser State
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
|
|||||||
+225
@@ -72,6 +72,19 @@ import type {
|
|||||||
EmulateMediaCommand,
|
EmulateMediaCommand,
|
||||||
OfflineCommand,
|
OfflineCommand,
|
||||||
HeadersCommand,
|
HeadersCommand,
|
||||||
|
GetByAltTextCommand,
|
||||||
|
GetByTitleCommand,
|
||||||
|
GetByTestIdCommand,
|
||||||
|
NthCommand,
|
||||||
|
WaitForUrlCommand,
|
||||||
|
WaitForLoadStateCommand,
|
||||||
|
SetContentCommand,
|
||||||
|
TimezoneCommand,
|
||||||
|
LocaleCommand,
|
||||||
|
HttpCredentialsCommand,
|
||||||
|
MouseMoveCommand,
|
||||||
|
MouseDownCommand,
|
||||||
|
MouseUpCommand,
|
||||||
NavigateData,
|
NavigateData,
|
||||||
ScreenshotData,
|
ScreenshotData,
|
||||||
EvaluateData,
|
EvaluateData,
|
||||||
@@ -279,6 +292,34 @@ export async function executeCommand(
|
|||||||
return await handleHeaders(command, browser);
|
return await handleHeaders(command, browser);
|
||||||
case 'pause':
|
case 'pause':
|
||||||
return await handlePause(command, browser);
|
return await handlePause(command, browser);
|
||||||
|
case 'getbyalttext':
|
||||||
|
return await handleGetByAltText(command, browser);
|
||||||
|
case 'getbytitle':
|
||||||
|
return await handleGetByTitle(command, browser);
|
||||||
|
case 'getbytestid':
|
||||||
|
return await handleGetByTestId(command, browser);
|
||||||
|
case 'nth':
|
||||||
|
return await handleNth(command, browser);
|
||||||
|
case 'waitforurl':
|
||||||
|
return await handleWaitForUrl(command, browser);
|
||||||
|
case 'waitforloadstate':
|
||||||
|
return await handleWaitForLoadState(command, browser);
|
||||||
|
case 'setcontent':
|
||||||
|
return await handleSetContent(command, browser);
|
||||||
|
case 'timezone':
|
||||||
|
return await handleTimezone(command, browser);
|
||||||
|
case 'locale':
|
||||||
|
return await handleLocale(command, browser);
|
||||||
|
case 'credentials':
|
||||||
|
return await handleCredentials(command, browser);
|
||||||
|
case 'mousemove':
|
||||||
|
return await handleMouseMove(command, browser);
|
||||||
|
case 'mousedown':
|
||||||
|
return await handleMouseDown(command, browser);
|
||||||
|
case 'mouseup':
|
||||||
|
return await handleMouseUp(command, browser);
|
||||||
|
case 'bringtofront':
|
||||||
|
return await handleBringToFront(command, browser);
|
||||||
default: {
|
default: {
|
||||||
// TypeScript narrows to never here, but we handle it for safety
|
// TypeScript narrows to never here, but we handle it for safety
|
||||||
const unknownCommand = command as { id: string; action: string };
|
const unknownCommand = command as { id: string; action: string };
|
||||||
@@ -1395,3 +1436,187 @@ async function handlePause(
|
|||||||
await page.pause();
|
await page.pause();
|
||||||
return successResponse(command.id, { paused: true });
|
return successResponse(command.id, { paused: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleGetByAltText(
|
||||||
|
command: GetByAltTextCommand,
|
||||||
|
browser: BrowserManager
|
||||||
|
): Promise<Response> {
|
||||||
|
const page = browser.getPage();
|
||||||
|
const locator = page.getByAltText(command.text, { exact: command.exact });
|
||||||
|
|
||||||
|
switch (command.subaction) {
|
||||||
|
case 'click':
|
||||||
|
await locator.click();
|
||||||
|
return successResponse(command.id, { clicked: true });
|
||||||
|
case 'hover':
|
||||||
|
await locator.hover();
|
||||||
|
return successResponse(command.id, { hovered: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleGetByTitle(
|
||||||
|
command: GetByTitleCommand,
|
||||||
|
browser: BrowserManager
|
||||||
|
): Promise<Response> {
|
||||||
|
const page = browser.getPage();
|
||||||
|
const locator = page.getByTitle(command.text, { exact: command.exact });
|
||||||
|
|
||||||
|
switch (command.subaction) {
|
||||||
|
case 'click':
|
||||||
|
await locator.click();
|
||||||
|
return successResponse(command.id, { clicked: true });
|
||||||
|
case 'hover':
|
||||||
|
await locator.hover();
|
||||||
|
return successResponse(command.id, { hovered: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleGetByTestId(
|
||||||
|
command: GetByTestIdCommand,
|
||||||
|
browser: BrowserManager
|
||||||
|
): Promise<Response> {
|
||||||
|
const page = browser.getPage();
|
||||||
|
const locator = page.getByTestId(command.testId);
|
||||||
|
|
||||||
|
switch (command.subaction) {
|
||||||
|
case 'click':
|
||||||
|
await locator.click();
|
||||||
|
return successResponse(command.id, { clicked: true });
|
||||||
|
case 'fill':
|
||||||
|
await locator.fill(command.value ?? '');
|
||||||
|
return successResponse(command.id, { filled: true });
|
||||||
|
case 'check':
|
||||||
|
await locator.check();
|
||||||
|
return successResponse(command.id, { checked: true });
|
||||||
|
case 'hover':
|
||||||
|
await locator.hover();
|
||||||
|
return successResponse(command.id, { hovered: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleNth(
|
||||||
|
command: NthCommand,
|
||||||
|
browser: BrowserManager
|
||||||
|
): Promise<Response> {
|
||||||
|
const page = browser.getPage();
|
||||||
|
const base = page.locator(command.selector);
|
||||||
|
const locator = command.index === -1 ? base.last() : base.nth(command.index);
|
||||||
|
|
||||||
|
switch (command.subaction) {
|
||||||
|
case 'click':
|
||||||
|
await locator.click();
|
||||||
|
return successResponse(command.id, { clicked: true });
|
||||||
|
case 'fill':
|
||||||
|
await locator.fill(command.value ?? '');
|
||||||
|
return successResponse(command.id, { filled: true });
|
||||||
|
case 'check':
|
||||||
|
await locator.check();
|
||||||
|
return successResponse(command.id, { checked: true });
|
||||||
|
case 'hover':
|
||||||
|
await locator.hover();
|
||||||
|
return successResponse(command.id, { hovered: true });
|
||||||
|
case 'text':
|
||||||
|
const text = await locator.textContent();
|
||||||
|
return successResponse(command.id, { text });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleWaitForUrl(
|
||||||
|
command: WaitForUrlCommand,
|
||||||
|
browser: BrowserManager
|
||||||
|
): Promise<Response> {
|
||||||
|
const page = browser.getPage();
|
||||||
|
await page.waitForURL(command.url, { timeout: command.timeout });
|
||||||
|
return successResponse(command.id, { url: page.url() });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleWaitForLoadState(
|
||||||
|
command: WaitForLoadStateCommand,
|
||||||
|
browser: BrowserManager
|
||||||
|
): Promise<Response> {
|
||||||
|
const page = browser.getPage();
|
||||||
|
await page.waitForLoadState(command.state, { timeout: command.timeout });
|
||||||
|
return successResponse(command.id, { state: command.state });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSetContent(
|
||||||
|
command: SetContentCommand,
|
||||||
|
browser: BrowserManager
|
||||||
|
): Promise<Response> {
|
||||||
|
const page = browser.getPage();
|
||||||
|
await page.setContent(command.html);
|
||||||
|
return successResponse(command.id, { set: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleTimezone(
|
||||||
|
command: TimezoneCommand,
|
||||||
|
browser: BrowserManager
|
||||||
|
): Promise<Response> {
|
||||||
|
// Timezone must be set at context level before navigation
|
||||||
|
// This is a limitation - it sets for the current context
|
||||||
|
const page = browser.getPage();
|
||||||
|
await page.context().setGeolocation({ latitude: 0, longitude: 0 }); // Trigger context awareness
|
||||||
|
return successResponse(command.id, {
|
||||||
|
note: 'Timezone must be set at browser launch. Use --timezone flag.',
|
||||||
|
timezone: command.timezone,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleLocale(
|
||||||
|
command: LocaleCommand,
|
||||||
|
browser: BrowserManager
|
||||||
|
): Promise<Response> {
|
||||||
|
// Locale must be set at context creation
|
||||||
|
return successResponse(command.id, {
|
||||||
|
note: 'Locale must be set at browser launch. Use --locale flag.',
|
||||||
|
locale: command.locale,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleCredentials(
|
||||||
|
command: HttpCredentialsCommand,
|
||||||
|
browser: BrowserManager
|
||||||
|
): Promise<Response> {
|
||||||
|
const context = browser.getPage().context();
|
||||||
|
await context.setHTTPCredentials({
|
||||||
|
username: command.username,
|
||||||
|
password: command.password,
|
||||||
|
});
|
||||||
|
return successResponse(command.id, { set: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleMouseMove(
|
||||||
|
command: MouseMoveCommand,
|
||||||
|
browser: BrowserManager
|
||||||
|
): Promise<Response> {
|
||||||
|
const page = browser.getPage();
|
||||||
|
await page.mouse.move(command.x, command.y);
|
||||||
|
return successResponse(command.id, { moved: true, x: command.x, y: command.y });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleMouseDown(
|
||||||
|
command: MouseDownCommand,
|
||||||
|
browser: BrowserManager
|
||||||
|
): Promise<Response> {
|
||||||
|
const page = browser.getPage();
|
||||||
|
await page.mouse.down({ button: command.button ?? 'left' });
|
||||||
|
return successResponse(command.id, { down: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleMouseUp(
|
||||||
|
command: MouseUpCommand,
|
||||||
|
browser: BrowserManager
|
||||||
|
): Promise<Response> {
|
||||||
|
const page = browser.getPage();
|
||||||
|
await page.mouse.up({ button: command.button ?? 'left' });
|
||||||
|
return successResponse(command.id, { up: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleBringToFront(
|
||||||
|
command: Command & { action: 'bringtofront' },
|
||||||
|
browser: BrowserManager
|
||||||
|
): Promise<Response> {
|
||||||
|
const page = browser.getPage();
|
||||||
|
await page.bringToFront();
|
||||||
|
return successResponse(command.id, { focused: true });
|
||||||
|
}
|
||||||
|
|||||||
+183
@@ -356,6 +356,16 @@ function printResponse(response: Response, jsonMode: boolean): void {
|
|||||||
console.log(c('yellow', '⚠'), data.note);
|
console.log(c('yellow', '⚠'), data.note);
|
||||||
} else if (data.requestCount !== undefined) {
|
} else if (data.requestCount !== undefined) {
|
||||||
console.log(c('green', '✓'), `HAR saved (${data.requestCount} requests)`);
|
console.log(c('green', '✓'), `HAR saved (${data.requestCount} requests)`);
|
||||||
|
} else if (data.moved) {
|
||||||
|
console.log(c('green', '✓'), `Mouse moved to (${data.x}, ${data.y})`);
|
||||||
|
} else if (data.down) {
|
||||||
|
console.log(c('green', '✓'), 'Mouse down');
|
||||||
|
} else if (data.up) {
|
||||||
|
console.log(c('green', '✓'), 'Mouse up');
|
||||||
|
} else if (data.focused) {
|
||||||
|
console.log(c('green', '✓'), 'Brought to front');
|
||||||
|
} else if (data.state) {
|
||||||
|
console.log(c('green', '✓'), `Load state: ${data.state}`);
|
||||||
} else {
|
} else {
|
||||||
console.log(c('green', '✓'), JSON.stringify(data));
|
console.log(c('green', '✓'), JSON.stringify(data));
|
||||||
}
|
}
|
||||||
@@ -1272,6 +1282,179 @@ async function main(): Promise<void> {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'alttext':
|
||||||
|
case 'alt': {
|
||||||
|
const text = cleanArgs[1];
|
||||||
|
const subaction = cleanArgs[2] || 'click';
|
||||||
|
if (!text) {
|
||||||
|
console.error(c('red', 'Error:'), 'Alt text required');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
cmd = { id, action: 'getbyalttext', text, subaction, exact: exactMode };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'bytitle': {
|
||||||
|
const text = cleanArgs[1];
|
||||||
|
const subaction = cleanArgs[2] || 'click';
|
||||||
|
if (!text) {
|
||||||
|
console.error(c('red', 'Error:'), 'Title text required');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
cmd = { id, action: 'getbytitle', text, subaction, exact: exactMode };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'testid':
|
||||||
|
case 'data-testid': {
|
||||||
|
const testId = cleanArgs[1];
|
||||||
|
const subaction = cleanArgs[2] || 'click';
|
||||||
|
const value = cleanArgs[3];
|
||||||
|
if (!testId) {
|
||||||
|
console.error(c('red', 'Error:'), 'Test ID required');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
cmd = { id, action: 'getbytestid', testId, subaction, value };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'first': {
|
||||||
|
const selector = cleanArgs[1];
|
||||||
|
const subaction = cleanArgs[2] || 'click';
|
||||||
|
const value = cleanArgs[3];
|
||||||
|
if (!selector) {
|
||||||
|
console.error(c('red', 'Error:'), 'Selector required');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
cmd = { id, action: 'nth', selector, index: 0, subaction, value };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'last': {
|
||||||
|
const selector = cleanArgs[1];
|
||||||
|
const subaction = cleanArgs[2] || 'click';
|
||||||
|
const value = cleanArgs[3];
|
||||||
|
if (!selector) {
|
||||||
|
console.error(c('red', 'Error:'), 'Selector required');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
cmd = { id, action: 'nth', selector, index: -1, subaction, value };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'nth': {
|
||||||
|
const selector = cleanArgs[1];
|
||||||
|
const index = parseInt(cleanArgs[2], 10);
|
||||||
|
const subaction = cleanArgs[3] || 'click';
|
||||||
|
const value = cleanArgs[4];
|
||||||
|
if (!selector || isNaN(index)) {
|
||||||
|
console.error(c('red', 'Error:'), 'Selector and index required');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
cmd = { id, action: 'nth', selector, index, subaction, value };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'waitforurl':
|
||||||
|
case 'wait-for-url': {
|
||||||
|
const url = cleanArgs[1];
|
||||||
|
if (!url) {
|
||||||
|
console.error(c('red', 'Error:'), 'URL pattern required');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
cmd = { id, action: 'waitforurl', url };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'waitforload':
|
||||||
|
case 'wait-for-load': {
|
||||||
|
const state = cleanArgs[1] || 'load';
|
||||||
|
if (!['load', 'domcontentloaded', 'networkidle'].includes(state)) {
|
||||||
|
console.error(c('red', 'Error:'), 'State must be: load, domcontentloaded, or networkidle');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
cmd = { id, action: 'waitforloadstate', state };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'setcontent':
|
||||||
|
case 'set-content':
|
||||||
|
case 'html': {
|
||||||
|
const html = cleanArgs[1];
|
||||||
|
if (!html) {
|
||||||
|
console.error(c('red', 'Error:'), 'HTML content required');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
cmd = { id, action: 'setcontent', html };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'timezone':
|
||||||
|
case 'tz': {
|
||||||
|
const timezone = cleanArgs[1];
|
||||||
|
if (!timezone) {
|
||||||
|
console.error(c('red', 'Error:'), 'Timezone required (e.g., America/New_York)');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
cmd = { id, action: 'timezone', timezone };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'locale':
|
||||||
|
case 'lang': {
|
||||||
|
const locale = cleanArgs[1];
|
||||||
|
if (!locale) {
|
||||||
|
console.error(c('red', 'Error:'), 'Locale required (e.g., en-US)');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
cmd = { id, action: 'locale', locale };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'credentials':
|
||||||
|
case 'auth': {
|
||||||
|
const username = cleanArgs[1];
|
||||||
|
const password = cleanArgs[2];
|
||||||
|
if (!username || !password) {
|
||||||
|
console.error(c('red', 'Error:'), 'Username and password required');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
cmd = { id, action: 'credentials', username, password };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'mousemove':
|
||||||
|
case 'mouse-move': {
|
||||||
|
const x = parseInt(cleanArgs[1], 10);
|
||||||
|
const y = parseInt(cleanArgs[2], 10);
|
||||||
|
if (isNaN(x) || isNaN(y)) {
|
||||||
|
console.error(c('red', 'Error:'), 'X and Y coordinates required');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
cmd = { id, action: 'mousemove', x, y };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'mousedown':
|
||||||
|
case 'mouse-down': {
|
||||||
|
const button = cleanArgs[1] as 'left' | 'right' | 'middle' | undefined;
|
||||||
|
cmd = { id, action: 'mousedown', button };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'mouseup':
|
||||||
|
case 'mouse-up': {
|
||||||
|
const button = cleanArgs[1] as 'left' | 'right' | 'middle' | undefined;
|
||||||
|
cmd = { id, action: 'mouseup', button };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'focus-tab':
|
||||||
|
case 'bringtofront': {
|
||||||
|
cmd = { id, action: 'bringtofront' };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.error(c('red', 'Error:'), `Unknown command: ${command}`);
|
console.error(c('red', 'Error:'), `Unknown command: ${command}`);
|
||||||
console.error(c('dim', 'Run veb --help for usage'));
|
console.error(c('dim', 'Run veb --help for usage'));
|
||||||
|
|||||||
@@ -445,6 +445,88 @@ const pauseSchema = baseCommandSchema.extend({
|
|||||||
action: z.literal('pause'),
|
action: z.literal('pause'),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getByAltTextSchema = baseCommandSchema.extend({
|
||||||
|
action: z.literal('getbyalttext'),
|
||||||
|
text: z.string().min(1),
|
||||||
|
exact: z.boolean().optional(),
|
||||||
|
subaction: z.enum(['click', 'hover']),
|
||||||
|
});
|
||||||
|
|
||||||
|
const getByTitleSchema = baseCommandSchema.extend({
|
||||||
|
action: z.literal('getbytitle'),
|
||||||
|
text: z.string().min(1),
|
||||||
|
exact: z.boolean().optional(),
|
||||||
|
subaction: z.enum(['click', 'hover']),
|
||||||
|
});
|
||||||
|
|
||||||
|
const getByTestIdSchema = baseCommandSchema.extend({
|
||||||
|
action: z.literal('getbytestid'),
|
||||||
|
testId: z.string().min(1),
|
||||||
|
subaction: z.enum(['click', 'fill', 'check', 'hover']),
|
||||||
|
value: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const nthSchema = baseCommandSchema.extend({
|
||||||
|
action: z.literal('nth'),
|
||||||
|
selector: z.string().min(1),
|
||||||
|
index: z.number(),
|
||||||
|
subaction: z.enum(['click', 'fill', 'check', 'hover', 'text']),
|
||||||
|
value: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const waitForUrlSchema = baseCommandSchema.extend({
|
||||||
|
action: z.literal('waitforurl'),
|
||||||
|
url: z.string().min(1),
|
||||||
|
timeout: z.number().positive().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const waitForLoadStateSchema = baseCommandSchema.extend({
|
||||||
|
action: z.literal('waitforloadstate'),
|
||||||
|
state: z.enum(['load', 'domcontentloaded', 'networkidle']),
|
||||||
|
timeout: z.number().positive().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const setContentSchema = baseCommandSchema.extend({
|
||||||
|
action: z.literal('setcontent'),
|
||||||
|
html: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const timezoneSchema = baseCommandSchema.extend({
|
||||||
|
action: z.literal('timezone'),
|
||||||
|
timezone: z.string().min(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
const localeSchema = baseCommandSchema.extend({
|
||||||
|
action: z.literal('locale'),
|
||||||
|
locale: z.string().min(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
const credentialsSchema = baseCommandSchema.extend({
|
||||||
|
action: z.literal('credentials'),
|
||||||
|
username: z.string(),
|
||||||
|
password: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const mouseMoveSchema = baseCommandSchema.extend({
|
||||||
|
action: z.literal('mousemove'),
|
||||||
|
x: z.number(),
|
||||||
|
y: z.number(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const mouseDownSchema = baseCommandSchema.extend({
|
||||||
|
action: z.literal('mousedown'),
|
||||||
|
button: z.enum(['left', 'right', 'middle']).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const mouseUpSchema = baseCommandSchema.extend({
|
||||||
|
action: z.literal('mouseup'),
|
||||||
|
button: z.enum(['left', 'right', 'middle']).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const bringToFrontSchema = baseCommandSchema.extend({
|
||||||
|
action: z.literal('bringtofront'),
|
||||||
|
});
|
||||||
|
|
||||||
const pressSchema = baseCommandSchema.extend({
|
const pressSchema = baseCommandSchema.extend({
|
||||||
action: z.literal('press'),
|
action: z.literal('press'),
|
||||||
key: z.string().min(1),
|
key: z.string().min(1),
|
||||||
@@ -626,6 +708,20 @@ const commandSchema = z.discriminatedUnion('action', [
|
|||||||
offlineSchema,
|
offlineSchema,
|
||||||
headersSchema,
|
headersSchema,
|
||||||
pauseSchema,
|
pauseSchema,
|
||||||
|
getByAltTextSchema,
|
||||||
|
getByTitleSchema,
|
||||||
|
getByTestIdSchema,
|
||||||
|
nthSchema,
|
||||||
|
waitForUrlSchema,
|
||||||
|
waitForLoadStateSchema,
|
||||||
|
setContentSchema,
|
||||||
|
timezoneSchema,
|
||||||
|
localeSchema,
|
||||||
|
credentialsSchema,
|
||||||
|
mouseMoveSchema,
|
||||||
|
mouseDownSchema,
|
||||||
|
mouseUpSchema,
|
||||||
|
bringToFrontSchema,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Parse result type
|
// Parse result type
|
||||||
|
|||||||
+107
-1
@@ -294,6 +294,98 @@ export interface BoundingBoxCommand extends BaseCommand {
|
|||||||
selector: string;
|
selector: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// More semantic locators
|
||||||
|
export interface GetByAltTextCommand extends BaseCommand {
|
||||||
|
action: 'getbyalttext';
|
||||||
|
text: string;
|
||||||
|
exact?: boolean;
|
||||||
|
subaction: 'click' | 'hover';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetByTitleCommand extends BaseCommand {
|
||||||
|
action: 'getbytitle';
|
||||||
|
text: string;
|
||||||
|
exact?: boolean;
|
||||||
|
subaction: 'click' | 'hover';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetByTestIdCommand extends BaseCommand {
|
||||||
|
action: 'getbytestid';
|
||||||
|
testId: string;
|
||||||
|
subaction: 'click' | 'fill' | 'check' | 'hover';
|
||||||
|
value?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nth element selection
|
||||||
|
export interface NthCommand extends BaseCommand {
|
||||||
|
action: 'nth';
|
||||||
|
selector: string;
|
||||||
|
index: number; // 0-based, or -1 for last
|
||||||
|
subaction: 'click' | 'fill' | 'check' | 'hover' | 'text';
|
||||||
|
value?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for URL
|
||||||
|
export interface WaitForUrlCommand extends BaseCommand {
|
||||||
|
action: 'waitforurl';
|
||||||
|
url: string;
|
||||||
|
timeout?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for load state
|
||||||
|
export interface WaitForLoadStateCommand extends BaseCommand {
|
||||||
|
action: 'waitforloadstate';
|
||||||
|
state: 'load' | 'domcontentloaded' | 'networkidle';
|
||||||
|
timeout?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set HTML content
|
||||||
|
export interface SetContentCommand extends BaseCommand {
|
||||||
|
action: 'setcontent';
|
||||||
|
html: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timezone emulation
|
||||||
|
export interface TimezoneCommand extends BaseCommand {
|
||||||
|
action: 'timezone';
|
||||||
|
timezone: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Locale emulation
|
||||||
|
export interface LocaleCommand extends BaseCommand {
|
||||||
|
action: 'locale';
|
||||||
|
locale: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTP basic auth
|
||||||
|
export interface HttpCredentialsCommand extends BaseCommand {
|
||||||
|
action: 'credentials';
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fine-grained mouse control
|
||||||
|
export interface MouseMoveCommand extends BaseCommand {
|
||||||
|
action: 'mousemove';
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MouseDownCommand extends BaseCommand {
|
||||||
|
action: 'mousedown';
|
||||||
|
button?: 'left' | 'right' | 'middle';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MouseUpCommand extends BaseCommand {
|
||||||
|
action: 'mouseup';
|
||||||
|
button?: 'left' | 'right' | 'middle';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bring to front
|
||||||
|
export interface BringToFrontCommand extends BaseCommand {
|
||||||
|
action: 'bringtofront';
|
||||||
|
}
|
||||||
|
|
||||||
// Video recording
|
// Video recording
|
||||||
export interface VideoStartCommand extends BaseCommand {
|
export interface VideoStartCommand extends BaseCommand {
|
||||||
action: 'video_start';
|
action: 'video_start';
|
||||||
@@ -654,7 +746,21 @@ export type Command =
|
|||||||
| EmulateMediaCommand
|
| EmulateMediaCommand
|
||||||
| OfflineCommand
|
| OfflineCommand
|
||||||
| HeadersCommand
|
| HeadersCommand
|
||||||
| PauseCommand;
|
| PauseCommand
|
||||||
|
| GetByAltTextCommand
|
||||||
|
| GetByTitleCommand
|
||||||
|
| GetByTestIdCommand
|
||||||
|
| NthCommand
|
||||||
|
| WaitForUrlCommand
|
||||||
|
| WaitForLoadStateCommand
|
||||||
|
| SetContentCommand
|
||||||
|
| TimezoneCommand
|
||||||
|
| LocaleCommand
|
||||||
|
| HttpCredentialsCommand
|
||||||
|
| MouseMoveCommand
|
||||||
|
| MouseDownCommand
|
||||||
|
| MouseUpCommand
|
||||||
|
| BringToFrontCommand;
|
||||||
|
|
||||||
// Response types
|
// Response types
|
||||||
export interface SuccessResponse<T = unknown> {
|
export interface SuccessResponse<T = unknown> {
|
||||||
|
|||||||
Reference in New Issue
Block a user