bump
This commit is contained in:
112
README.md
112
README.md
@@ -95,6 +95,18 @@ cremote <command> [options]
|
||||
- `clear-all-site-data`: Clear all site data (cookies, storage, cache, etc.)
|
||||
- `clear-cookies`: Clear cookies for a tab
|
||||
- `clear-storage`: Clear web storage (localStorage, sessionStorage, etc.)
|
||||
- `drag-and-drop`: Drag and drop from source element to target element
|
||||
- `drag-and-drop-coordinates`: Drag and drop from source element to specific coordinates
|
||||
- `drag-and-drop-offset`: Drag and drop from source element by relative offset
|
||||
- `right-click`: Right-click on an element to open context menus
|
||||
- `double-click`: Double-click on an element for file operations or text selection
|
||||
- `middle-click`: Middle-click on an element (typically opens links in new tabs)
|
||||
- `hover`: Hover over an element to trigger tooltips or dropdowns
|
||||
- `mouse-move`: Move mouse to specific coordinates without clicking
|
||||
- `scroll-wheel`: Scroll with mouse wheel at specific coordinates
|
||||
- `key-combination`: Send key combinations (Ctrl+C, Alt+Tab, Shift+Enter, etc.)
|
||||
- `special-key`: Send special keys (Enter, Escape, Tab, F1-F12, Arrow keys, etc.)
|
||||
- `modifier-click`: Click with modifier keys (Ctrl+click, Shift+click for multi-selection)
|
||||
- `status`: Check if the daemon is running
|
||||
|
||||
### Current Tab Feature
|
||||
@@ -347,6 +359,106 @@ cremote clear-storage [--tab="<tab-id>"] [--timeout=5]
|
||||
|
||||
The `--timeout` parameter specifies how many seconds to wait for the operation to complete (default: 5 seconds, use longer timeouts for comprehensive data clearing).
|
||||
|
||||
#### Drag and Drop Operations
|
||||
|
||||
You can perform drag and drop operations for testing interactive web applications:
|
||||
|
||||
```bash
|
||||
# Drag and Drop Between Elements
|
||||
# Drag from source element to target element
|
||||
cremote drag-and-drop --source=".draggable-item" --target=".drop-zone" [--tab="<tab-id>"] [--timeout=5]
|
||||
|
||||
# Drag and Drop to Specific Coordinates
|
||||
# Drag from source element to specific x,y coordinates
|
||||
cremote drag-and-drop-coordinates --source=".draggable-item" --x=300 --y=200 [--tab="<tab-id>"] [--timeout=5]
|
||||
|
||||
# Drag and Drop by Relative Offset
|
||||
# Drag from source element by relative pixel offset
|
||||
cremote drag-and-drop-offset --source=".draggable-item" --offset-x=100 --offset-y=50 [--tab="<tab-id>"] [--timeout=5]
|
||||
```
|
||||
|
||||
**Use Cases:**
|
||||
- **File Upload**: Drag files to upload areas
|
||||
- **Sortable Lists**: Reorder items in sortable lists
|
||||
- **Kanban Boards**: Move cards between columns
|
||||
- **Image Galleries**: Rearrange images or media
|
||||
- **Form Builders**: Drag form elements to build layouts
|
||||
- **Dashboard Widgets**: Rearrange dashboard components
|
||||
- **Game Testing**: Test drag-based game mechanics
|
||||
- **UI Component Testing**: Test custom drag and drop components
|
||||
|
||||
**Technical Details:**
|
||||
- Uses Chrome DevTools Protocol for precise mouse event simulation
|
||||
- Performs realistic drag operations with intermediate mouse movements
|
||||
- Calculates element center points automatically for accurate targeting
|
||||
- Supports timeout handling for slow or complex drag operations
|
||||
- Works with all modern drag and drop APIs (HTML5 Drag and Drop, custom implementations)
|
||||
|
||||
The `--timeout` parameter specifies how many seconds to wait for the drag and drop operation to complete (default: 5 seconds).
|
||||
|
||||
#### Advanced Input Operations
|
||||
|
||||
Cremote provides sophisticated mouse and keyboard interactions for comprehensive testing of modern web applications:
|
||||
|
||||
##### Mouse Operations
|
||||
```bash
|
||||
# Right-click to open context menus
|
||||
cremote right-click --selector=".file-item" [--tab="<tab-id>"] [--timeout=5]
|
||||
|
||||
# Double-click for file operations or text selection
|
||||
cremote double-click --selector=".file-icon" [--tab="<tab-id>"] [--timeout=5]
|
||||
|
||||
# Middle-click to open links in new tabs
|
||||
cremote middle-click --selector="a[href='/dashboard']" [--tab="<tab-id>"] [--timeout=5]
|
||||
|
||||
# Hover to trigger tooltips or dropdowns
|
||||
cremote hover --selector=".tooltip-trigger" [--tab="<tab-id>"] [--timeout=5]
|
||||
|
||||
# Move mouse to specific coordinates without clicking
|
||||
cremote mouse-move --x=400 --y=300 [--tab="<tab-id>"] [--timeout=5]
|
||||
|
||||
# Scroll with mouse wheel at specific coordinates
|
||||
cremote scroll-wheel --x=400 --y=300 --delta-y=-120 [--delta-x=0] [--tab="<tab-id>"] [--timeout=5]
|
||||
```
|
||||
|
||||
##### Keyboard Operations
|
||||
```bash
|
||||
# Send key combinations (Ctrl+C, Alt+Tab, Shift+Enter, etc.)
|
||||
cremote key-combination --keys="Ctrl+C" [--tab="<tab-id>"] [--timeout=5]
|
||||
cremote key-combination --keys="Alt+Tab" [--tab="<tab-id>"] [--timeout=5]
|
||||
cremote key-combination --keys="Ctrl+Shift+T" [--tab="<tab-id>"] [--timeout=5]
|
||||
|
||||
# Send special keys (Enter, Escape, Tab, F1-F12, Arrow keys, etc.)
|
||||
cremote special-key --key="Enter" [--tab="<tab-id>"] [--timeout=5]
|
||||
cremote special-key --key="Escape" [--tab="<tab-id>"] [--timeout=5]
|
||||
cremote special-key --key="ArrowUp" [--tab="<tab-id>"] [--timeout=5]
|
||||
cremote special-key --key="F1" [--tab="<tab-id>"] [--timeout=5]
|
||||
|
||||
# Click with modifier keys (Ctrl+click, Shift+click for multi-selection)
|
||||
cremote modifier-click --selector=".selectable-item" --modifiers="Ctrl" [--tab="<tab-id>"] [--timeout=5]
|
||||
cremote modifier-click --selector=".list-item" --modifiers="Shift" [--tab="<tab-id>"] [--timeout=5]
|
||||
cremote modifier-click --selector=".table-row" --modifiers="Ctrl+Shift" [--tab="<tab-id>"] [--timeout=5]
|
||||
```
|
||||
|
||||
**Advanced Use Cases:**
|
||||
- **Context Menu Testing**: Right-click to test context menus and their functionality
|
||||
- **Accessibility Testing**: Full keyboard navigation support for accessibility compliance
|
||||
- **Tooltip/Dropdown Testing**: Hover interactions for UI elements that appear on mouse over
|
||||
- **Multi-Selection Testing**: Ctrl+click and Shift+click for testing selection interfaces
|
||||
- **Copy/Paste Workflows**: Test clipboard operations with Ctrl+A, Ctrl+C, Ctrl+V
|
||||
- **Precise Mouse Control**: Pixel-perfect mouse positioning and scrolling
|
||||
- **Function Key Testing**: Test application shortcuts using F1-F12 keys
|
||||
- **Arrow Key Navigation**: Test keyboard navigation in lists, tables, and forms
|
||||
|
||||
**Technical Details:**
|
||||
- Uses Chrome DevTools Protocol's Input domain for precise control
|
||||
- Supports all modifier keys: Ctrl, Alt, Shift, Meta/Cmd
|
||||
- Comprehensive key mapping for 60+ keys including letters, numbers, function keys, special keys
|
||||
- Proper modifier key sequencing (key down → action → key up)
|
||||
- Element positioning using content quads for pixel-perfect accuracy
|
||||
- Mouse button differentiation (Left, Right, Middle)
|
||||
- Realistic interaction patterns matching human behavior
|
||||
|
||||
### Connecting to a Remote Daemon
|
||||
|
||||
By default, the client connects to a daemon running on localhost. To connect to a daemon running on a different host:
|
||||
|
||||
632
client/client.go
632
client/client.go
@@ -2621,3 +2621,635 @@ func (c *Client) ClearStorage(tabID string, timeout int) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DragAndDrop performs a drag and drop operation from source to target element
|
||||
// If tabID is empty, the current tab will be used
|
||||
// timeout is in seconds, 0 means no timeout
|
||||
func (c *Client) DragAndDrop(tabID, sourceSelector, targetSelector string, timeout int) error {
|
||||
params := map[string]string{
|
||||
"source": sourceSelector,
|
||||
"target": targetSelector,
|
||||
}
|
||||
|
||||
// Only include tab ID if it's provided
|
||||
if tabID != "" {
|
||||
params["tab"] = tabID
|
||||
}
|
||||
|
||||
// Add timeout if specified
|
||||
if timeout > 0 {
|
||||
params["timeout"] = strconv.Itoa(timeout)
|
||||
}
|
||||
|
||||
resp, err := c.SendCommand("drag-and-drop", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.Success {
|
||||
return fmt.Errorf("failed to perform drag and drop: %s", resp.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DragAndDropToCoordinates performs a drag and drop operation from source element to specific coordinates
|
||||
// If tabID is empty, the current tab will be used
|
||||
// timeout is in seconds, 0 means no timeout
|
||||
func (c *Client) DragAndDropToCoordinates(tabID, sourceSelector string, targetX, targetY int, timeout int) error {
|
||||
params := map[string]string{
|
||||
"source": sourceSelector,
|
||||
"target-x": strconv.Itoa(targetX),
|
||||
"target-y": strconv.Itoa(targetY),
|
||||
}
|
||||
|
||||
// Only include tab ID if it's provided
|
||||
if tabID != "" {
|
||||
params["tab"] = tabID
|
||||
}
|
||||
|
||||
// Add timeout if specified
|
||||
if timeout > 0 {
|
||||
params["timeout"] = strconv.Itoa(timeout)
|
||||
}
|
||||
|
||||
resp, err := c.SendCommand("drag-and-drop-coordinates", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.Success {
|
||||
return fmt.Errorf("failed to perform drag and drop to coordinates: %s", resp.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DragAndDropByOffset performs a drag and drop operation from source element by a relative offset
|
||||
// If tabID is empty, the current tab will be used
|
||||
// timeout is in seconds, 0 means no timeout
|
||||
func (c *Client) DragAndDropByOffset(tabID, sourceSelector string, offsetX, offsetY int, timeout int) error {
|
||||
params := map[string]string{
|
||||
"source": sourceSelector,
|
||||
"offset-x": strconv.Itoa(offsetX),
|
||||
"offset-y": strconv.Itoa(offsetY),
|
||||
}
|
||||
|
||||
// Only include tab ID if it's provided
|
||||
if tabID != "" {
|
||||
params["tab"] = tabID
|
||||
}
|
||||
|
||||
// Add timeout if specified
|
||||
if timeout > 0 {
|
||||
params["timeout"] = strconv.Itoa(timeout)
|
||||
}
|
||||
|
||||
resp, err := c.SendCommand("drag-and-drop-offset", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.Success {
|
||||
return fmt.Errorf("failed to perform drag and drop by offset: %s", resp.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RightClick performs a right-click on an element
|
||||
// If tabID is empty, the current tab will be used
|
||||
// timeout is in seconds, 0 means no timeout
|
||||
func (c *Client) RightClick(tabID, selector string, timeout int) error {
|
||||
params := map[string]string{
|
||||
"selector": selector,
|
||||
}
|
||||
|
||||
// Only include tab ID if it's provided
|
||||
if tabID != "" {
|
||||
params["tab"] = tabID
|
||||
}
|
||||
|
||||
// Add timeout if specified
|
||||
if timeout > 0 {
|
||||
params["timeout"] = strconv.Itoa(timeout)
|
||||
}
|
||||
|
||||
resp, err := c.SendCommand("right-click", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.Success {
|
||||
return fmt.Errorf("failed to right-click element: %s", resp.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DoubleClick performs a double-click on an element
|
||||
// If tabID is empty, the current tab will be used
|
||||
// timeout is in seconds, 0 means no timeout
|
||||
func (c *Client) DoubleClick(tabID, selector string, timeout int) error {
|
||||
params := map[string]string{
|
||||
"selector": selector,
|
||||
}
|
||||
|
||||
// Only include tab ID if it's provided
|
||||
if tabID != "" {
|
||||
params["tab"] = tabID
|
||||
}
|
||||
|
||||
// Add timeout if specified
|
||||
if timeout > 0 {
|
||||
params["timeout"] = strconv.Itoa(timeout)
|
||||
}
|
||||
|
||||
resp, err := c.SendCommand("double-click", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.Success {
|
||||
return fmt.Errorf("failed to double-click element: %s", resp.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MiddleClick performs a middle-click on an element
|
||||
// If tabID is empty, the current tab will be used
|
||||
// timeout is in seconds, 0 means no timeout
|
||||
func (c *Client) MiddleClick(tabID, selector string, timeout int) error {
|
||||
params := map[string]string{
|
||||
"selector": selector,
|
||||
}
|
||||
|
||||
// Only include tab ID if it's provided
|
||||
if tabID != "" {
|
||||
params["tab"] = tabID
|
||||
}
|
||||
|
||||
// Add timeout if specified
|
||||
if timeout > 0 {
|
||||
params["timeout"] = strconv.Itoa(timeout)
|
||||
}
|
||||
|
||||
resp, err := c.SendCommand("middle-click", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.Success {
|
||||
return fmt.Errorf("failed to middle-click element: %s", resp.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Hover moves the mouse over an element without clicking
|
||||
// If tabID is empty, the current tab will be used
|
||||
// timeout is in seconds, 0 means no timeout
|
||||
func (c *Client) Hover(tabID, selector string, timeout int) error {
|
||||
params := map[string]string{
|
||||
"selector": selector,
|
||||
}
|
||||
|
||||
// Only include tab ID if it's provided
|
||||
if tabID != "" {
|
||||
params["tab"] = tabID
|
||||
}
|
||||
|
||||
// Add timeout if specified
|
||||
if timeout > 0 {
|
||||
params["timeout"] = strconv.Itoa(timeout)
|
||||
}
|
||||
|
||||
resp, err := c.SendCommand("hover", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.Success {
|
||||
return fmt.Errorf("failed to hover over element: %s", resp.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MouseMove moves the mouse to specific coordinates without clicking
|
||||
// If tabID is empty, the current tab will be used
|
||||
// timeout is in seconds, 0 means no timeout
|
||||
func (c *Client) MouseMove(tabID string, x, y int, timeout int) error {
|
||||
params := map[string]string{
|
||||
"x": strconv.Itoa(x),
|
||||
"y": strconv.Itoa(y),
|
||||
}
|
||||
|
||||
// Only include tab ID if it's provided
|
||||
if tabID != "" {
|
||||
params["tab"] = tabID
|
||||
}
|
||||
|
||||
// Add timeout if specified
|
||||
if timeout > 0 {
|
||||
params["timeout"] = strconv.Itoa(timeout)
|
||||
}
|
||||
|
||||
resp, err := c.SendCommand("mouse-move", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.Success {
|
||||
return fmt.Errorf("failed to move mouse: %s", resp.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ScrollWheel performs mouse wheel scrolling at specific coordinates
|
||||
// If tabID is empty, the current tab will be used
|
||||
// deltaX and deltaY specify scroll amounts (negative = up/left, positive = down/right)
|
||||
// timeout is in seconds, 0 means no timeout
|
||||
func (c *Client) ScrollWheel(tabID string, x, y, deltaX, deltaY int, timeout int) error {
|
||||
params := map[string]string{
|
||||
"x": strconv.Itoa(x),
|
||||
"y": strconv.Itoa(y),
|
||||
"delta-x": strconv.Itoa(deltaX),
|
||||
"delta-y": strconv.Itoa(deltaY),
|
||||
}
|
||||
|
||||
// Only include tab ID if it's provided
|
||||
if tabID != "" {
|
||||
params["tab"] = tabID
|
||||
}
|
||||
|
||||
// Add timeout if specified
|
||||
if timeout > 0 {
|
||||
params["timeout"] = strconv.Itoa(timeout)
|
||||
}
|
||||
|
||||
resp, err := c.SendCommand("scroll-wheel", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.Success {
|
||||
return fmt.Errorf("failed to scroll with mouse wheel: %s", resp.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// KeyCombination sends a key combination (e.g., "Ctrl+C", "Alt+Tab", "Shift+Enter")
|
||||
// If tabID is empty, the current tab will be used
|
||||
// timeout is in seconds, 0 means no timeout
|
||||
func (c *Client) KeyCombination(tabID, keys string, timeout int) error {
|
||||
params := map[string]string{
|
||||
"keys": keys,
|
||||
}
|
||||
|
||||
// Only include tab ID if it's provided
|
||||
if tabID != "" {
|
||||
params["tab"] = tabID
|
||||
}
|
||||
|
||||
// Add timeout if specified
|
||||
if timeout > 0 {
|
||||
params["timeout"] = strconv.Itoa(timeout)
|
||||
}
|
||||
|
||||
resp, err := c.SendCommand("key-combination", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.Success {
|
||||
return fmt.Errorf("failed to send key combination: %s", resp.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SpecialKey sends a special key (e.g., "Enter", "Escape", "Tab", "F1", "ArrowUp")
|
||||
// If tabID is empty, the current tab will be used
|
||||
// timeout is in seconds, 0 means no timeout
|
||||
func (c *Client) SpecialKey(tabID, key string, timeout int) error {
|
||||
params := map[string]string{
|
||||
"key": key,
|
||||
}
|
||||
|
||||
// Only include tab ID if it's provided
|
||||
if tabID != "" {
|
||||
params["tab"] = tabID
|
||||
}
|
||||
|
||||
// Add timeout if specified
|
||||
if timeout > 0 {
|
||||
params["timeout"] = strconv.Itoa(timeout)
|
||||
}
|
||||
|
||||
resp, err := c.SendCommand("special-key", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.Success {
|
||||
return fmt.Errorf("failed to send special key: %s", resp.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ModifierClick performs a click with modifier keys (e.g., Ctrl+click, Shift+click)
|
||||
// If tabID is empty, the current tab will be used
|
||||
// timeout is in seconds, 0 means no timeout
|
||||
func (c *Client) ModifierClick(tabID, selector, modifiers string, timeout int) error {
|
||||
params := map[string]string{
|
||||
"selector": selector,
|
||||
"modifiers": modifiers,
|
||||
}
|
||||
|
||||
// Only include tab ID if it's provided
|
||||
if tabID != "" {
|
||||
params["tab"] = tabID
|
||||
}
|
||||
|
||||
// Add timeout if specified
|
||||
if timeout > 0 {
|
||||
params["timeout"] = strconv.Itoa(timeout)
|
||||
}
|
||||
|
||||
resp, err := c.SendCommand("modifier-click", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.Success {
|
||||
return fmt.Errorf("failed to perform modifier click: %s", resp.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TouchTap performs a single finger tap at specific coordinates
|
||||
// If tabID is empty, the current tab will be used
|
||||
// timeout is in seconds, 0 means no timeout
|
||||
func (c *Client) TouchTap(tabID string, x, y int, timeout int) error {
|
||||
params := map[string]string{
|
||||
"x": strconv.Itoa(x),
|
||||
"y": strconv.Itoa(y),
|
||||
}
|
||||
|
||||
// Only include tab ID if it's provided
|
||||
if tabID != "" {
|
||||
params["tab"] = tabID
|
||||
}
|
||||
|
||||
// Add timeout if specified
|
||||
if timeout > 0 {
|
||||
params["timeout"] = strconv.Itoa(timeout)
|
||||
}
|
||||
|
||||
resp, err := c.SendCommand("touch-tap", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.Success {
|
||||
return fmt.Errorf("failed to perform touch tap: %s", resp.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TouchLongPress performs a long press at specific coordinates
|
||||
// If tabID is empty, the current tab will be used
|
||||
// duration is in milliseconds (default: 1000ms)
|
||||
// timeout is in seconds, 0 means no timeout
|
||||
func (c *Client) TouchLongPress(tabID string, x, y, duration int, timeout int) error {
|
||||
params := map[string]string{
|
||||
"x": strconv.Itoa(x),
|
||||
"y": strconv.Itoa(y),
|
||||
}
|
||||
|
||||
if duration > 0 {
|
||||
params["duration"] = strconv.Itoa(duration)
|
||||
}
|
||||
|
||||
// Only include tab ID if it's provided
|
||||
if tabID != "" {
|
||||
params["tab"] = tabID
|
||||
}
|
||||
|
||||
// Add timeout if specified
|
||||
if timeout > 0 {
|
||||
params["timeout"] = strconv.Itoa(timeout)
|
||||
}
|
||||
|
||||
resp, err := c.SendCommand("touch-long-press", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.Success {
|
||||
return fmt.Errorf("failed to perform touch long press: %s", resp.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TouchSwipe performs a swipe gesture from start to end coordinates
|
||||
// If tabID is empty, the current tab will be used
|
||||
// timeout is in seconds, 0 means no timeout
|
||||
func (c *Client) TouchSwipe(tabID string, startX, startY, endX, endY int, timeout int) error {
|
||||
params := map[string]string{
|
||||
"start-x": strconv.Itoa(startX),
|
||||
"start-y": strconv.Itoa(startY),
|
||||
"end-x": strconv.Itoa(endX),
|
||||
"end-y": strconv.Itoa(endY),
|
||||
}
|
||||
|
||||
// Only include tab ID if it's provided
|
||||
if tabID != "" {
|
||||
params["tab"] = tabID
|
||||
}
|
||||
|
||||
// Add timeout if specified
|
||||
if timeout > 0 {
|
||||
params["timeout"] = strconv.Itoa(timeout)
|
||||
}
|
||||
|
||||
resp, err := c.SendCommand("touch-swipe", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.Success {
|
||||
return fmt.Errorf("failed to perform touch swipe: %s", resp.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PinchZoom performs a pinch-to-zoom gesture
|
||||
// If tabID is empty, the current tab will be used
|
||||
// scale > 1.0 zooms in, scale < 1.0 zooms out
|
||||
// timeout is in seconds, 0 means no timeout
|
||||
func (c *Client) PinchZoom(tabID string, centerX, centerY int, scale float64, timeout int) error {
|
||||
params := map[string]string{
|
||||
"center-x": strconv.Itoa(centerX),
|
||||
"center-y": strconv.Itoa(centerY),
|
||||
"scale": fmt.Sprintf("%.2f", scale),
|
||||
}
|
||||
|
||||
// Only include tab ID if it's provided
|
||||
if tabID != "" {
|
||||
params["tab"] = tabID
|
||||
}
|
||||
|
||||
// Add timeout if specified
|
||||
if timeout > 0 {
|
||||
params["timeout"] = strconv.Itoa(timeout)
|
||||
}
|
||||
|
||||
resp, err := c.SendCommand("pinch-zoom", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.Success {
|
||||
return fmt.Errorf("failed to perform pinch zoom: %s", resp.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ScrollElement scrolls a specific element by a given amount
|
||||
// If tabID is empty, the current tab will be used
|
||||
// timeout is in seconds, 0 means no timeout
|
||||
func (c *Client) ScrollElement(tabID, selector string, deltaX, deltaY int, timeout int) error {
|
||||
params := map[string]string{
|
||||
"selector": selector,
|
||||
"delta-x": strconv.Itoa(deltaX),
|
||||
"delta-y": strconv.Itoa(deltaY),
|
||||
}
|
||||
|
||||
// Only include tab ID if it's provided
|
||||
if tabID != "" {
|
||||
params["tab"] = tabID
|
||||
}
|
||||
|
||||
// Add timeout if specified
|
||||
if timeout > 0 {
|
||||
params["timeout"] = strconv.Itoa(timeout)
|
||||
}
|
||||
|
||||
resp, err := c.SendCommand("scroll-element", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.Success {
|
||||
return fmt.Errorf("failed to scroll element: %s", resp.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ScrollToCoordinates scrolls the page to specific coordinates
|
||||
// If tabID is empty, the current tab will be used
|
||||
// timeout is in seconds, 0 means no timeout
|
||||
func (c *Client) ScrollToCoordinates(tabID string, x, y int, timeout int) error {
|
||||
params := map[string]string{
|
||||
"x": strconv.Itoa(x),
|
||||
"y": strconv.Itoa(y),
|
||||
}
|
||||
|
||||
// Only include tab ID if it's provided
|
||||
if tabID != "" {
|
||||
params["tab"] = tabID
|
||||
}
|
||||
|
||||
// Add timeout if specified
|
||||
if timeout > 0 {
|
||||
params["timeout"] = strconv.Itoa(timeout)
|
||||
}
|
||||
|
||||
resp, err := c.SendCommand("scroll-to-coordinates", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.Success {
|
||||
return fmt.Errorf("failed to scroll to coordinates: %s", resp.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SelectText selects text within an element by character range
|
||||
// If tabID is empty, the current tab will be used
|
||||
// timeout is in seconds, 0 means no timeout
|
||||
func (c *Client) SelectText(tabID, selector string, startIndex, endIndex int, timeout int) error {
|
||||
params := map[string]string{
|
||||
"selector": selector,
|
||||
"start": strconv.Itoa(startIndex),
|
||||
"end": strconv.Itoa(endIndex),
|
||||
}
|
||||
|
||||
// Only include tab ID if it's provided
|
||||
if tabID != "" {
|
||||
params["tab"] = tabID
|
||||
}
|
||||
|
||||
// Add timeout if specified
|
||||
if timeout > 0 {
|
||||
params["timeout"] = strconv.Itoa(timeout)
|
||||
}
|
||||
|
||||
resp, err := c.SendCommand("select-text", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.Success {
|
||||
return fmt.Errorf("failed to select text: %s", resp.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SelectAllText selects all text within an element or the entire page
|
||||
// If tabID is empty, the current tab will be used
|
||||
// If selector is empty, selects all text on the page
|
||||
// timeout is in seconds, 0 means no timeout
|
||||
func (c *Client) SelectAllText(tabID, selector string, timeout int) error {
|
||||
params := map[string]string{}
|
||||
|
||||
if selector != "" {
|
||||
params["selector"] = selector
|
||||
}
|
||||
|
||||
// Only include tab ID if it's provided
|
||||
if tabID != "" {
|
||||
params["tab"] = tabID
|
||||
}
|
||||
|
||||
// Add timeout if specified
|
||||
if timeout > 0 {
|
||||
params["timeout"] = strconv.Itoa(timeout)
|
||||
}
|
||||
|
||||
resp, err := c.SendCommand("select-all-text", params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.Success {
|
||||
return fmt.Errorf("failed to select all text: %s", resp.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
BIN
daemon/cremotedaemon
Executable file
BIN
daemon/cremotedaemon
Executable file
Binary file not shown.
1891
daemon/daemon.go
1891
daemon/daemon.go
File diff suppressed because it is too large
Load Diff
293
main.go
293
main.go
@@ -41,6 +41,32 @@ func main() {
|
||||
clearAllSiteDataCmd := flag.NewFlagSet("clear-all-site-data", flag.ExitOnError)
|
||||
clearCookiesCmd := flag.NewFlagSet("clear-cookies", flag.ExitOnError)
|
||||
clearStorageCmd := flag.NewFlagSet("clear-storage", flag.ExitOnError)
|
||||
dragAndDropCmd := flag.NewFlagSet("drag-and-drop", flag.ExitOnError)
|
||||
dragAndDropCoordinatesCmd := flag.NewFlagSet("drag-and-drop-coordinates", flag.ExitOnError)
|
||||
dragAndDropOffsetCmd := flag.NewFlagSet("drag-and-drop-offset", flag.ExitOnError)
|
||||
|
||||
// Advanced mouse operations
|
||||
rightClickCmd := flag.NewFlagSet("right-click", flag.ExitOnError)
|
||||
doubleClickCmd := flag.NewFlagSet("double-click", flag.ExitOnError)
|
||||
middleClickCmd := flag.NewFlagSet("middle-click", flag.ExitOnError)
|
||||
hoverCmd := flag.NewFlagSet("hover", flag.ExitOnError)
|
||||
mouseMoveCmd := flag.NewFlagSet("mouse-move", flag.ExitOnError)
|
||||
scrollWheelCmd := flag.NewFlagSet("scroll-wheel", flag.ExitOnError)
|
||||
|
||||
// Keyboard operations
|
||||
keyCombinationCmd := flag.NewFlagSet("key-combination", flag.ExitOnError)
|
||||
specialKeyCmd := flag.NewFlagSet("special-key", flag.ExitOnError)
|
||||
modifierClickCmd := flag.NewFlagSet("modifier-click", flag.ExitOnError)
|
||||
|
||||
// Note: Touch operations and advanced scrolling/text operations are planned for future implementation
|
||||
// touchTapCmd := flag.NewFlagSet("touch-tap", flag.ExitOnError)
|
||||
// touchLongPressCmd := flag.NewFlagSet("touch-long-press", flag.ExitOnError)
|
||||
// touchSwipeCmd := flag.NewFlagSet("touch-swipe", flag.ExitOnError)
|
||||
// pinchZoomCmd := flag.NewFlagSet("pinch-zoom", flag.ExitOnError)
|
||||
// scrollElementCmd := flag.NewFlagSet("scroll-element", flag.ExitOnError)
|
||||
// scrollToCoordinatesCmd := flag.NewFlagSet("scroll-to-coordinates", flag.ExitOnError)
|
||||
// selectTextCmd := flag.NewFlagSet("select-text", flag.ExitOnError)
|
||||
// selectAllTextCmd := flag.NewFlagSet("select-all-text", flag.ExitOnError)
|
||||
|
||||
// Define flags for each subcommand
|
||||
// open-tab flags
|
||||
@@ -182,6 +208,104 @@ func main() {
|
||||
clearStorageHost := clearStorageCmd.String("host", "localhost", "Daemon host")
|
||||
clearStoragePort := clearStorageCmd.Int("port", 8989, "Daemon port")
|
||||
|
||||
// drag-and-drop flags
|
||||
dragAndDropTabID := dragAndDropCmd.String("tab", "", "Tab ID to perform drag and drop on (optional, uses current tab if not specified)")
|
||||
dragAndDropSource := dragAndDropCmd.String("source", "", "CSS selector for the source element to drag")
|
||||
dragAndDropTarget := dragAndDropCmd.String("target", "", "CSS selector for the target element to drop on")
|
||||
dragAndDropTimeout := dragAndDropCmd.Int("timeout", 5, "Timeout in seconds for drag and drop operation")
|
||||
dragAndDropHost := dragAndDropCmd.String("host", "localhost", "Daemon host")
|
||||
dragAndDropPort := dragAndDropCmd.Int("port", 8989, "Daemon port")
|
||||
|
||||
// drag-and-drop-coordinates flags
|
||||
dragAndDropCoordinatesTabID := dragAndDropCoordinatesCmd.String("tab", "", "Tab ID to perform drag and drop on (optional, uses current tab if not specified)")
|
||||
dragAndDropCoordinatesSource := dragAndDropCoordinatesCmd.String("source", "", "CSS selector for the source element to drag")
|
||||
dragAndDropCoordinatesX := dragAndDropCoordinatesCmd.Int("x", 0, "Target X coordinate")
|
||||
dragAndDropCoordinatesY := dragAndDropCoordinatesCmd.Int("y", 0, "Target Y coordinate")
|
||||
dragAndDropCoordinatesTimeout := dragAndDropCoordinatesCmd.Int("timeout", 5, "Timeout in seconds for drag and drop operation")
|
||||
dragAndDropCoordinatesHost := dragAndDropCoordinatesCmd.String("host", "localhost", "Daemon host")
|
||||
dragAndDropCoordinatesPort := dragAndDropCoordinatesCmd.Int("port", 8989, "Daemon port")
|
||||
|
||||
// drag-and-drop-offset flags
|
||||
dragAndDropOffsetTabID := dragAndDropOffsetCmd.String("tab", "", "Tab ID to perform drag and drop on (optional, uses current tab if not specified)")
|
||||
dragAndDropOffsetSource := dragAndDropOffsetCmd.String("source", "", "CSS selector for the source element to drag")
|
||||
dragAndDropOffsetX := dragAndDropOffsetCmd.Int("offset-x", 0, "Horizontal offset in pixels")
|
||||
dragAndDropOffsetY := dragAndDropOffsetCmd.Int("offset-y", 0, "Vertical offset in pixels")
|
||||
dragAndDropOffsetTimeout := dragAndDropOffsetCmd.Int("timeout", 5, "Timeout in seconds for drag and drop operation")
|
||||
dragAndDropOffsetHost := dragAndDropOffsetCmd.String("host", "localhost", "Daemon host")
|
||||
dragAndDropOffsetPort := dragAndDropOffsetCmd.Int("port", 8989, "Daemon port")
|
||||
|
||||
// right-click flags
|
||||
rightClickTabID := rightClickCmd.String("tab", "", "Tab ID to perform right-click on (optional, uses current tab if not specified)")
|
||||
rightClickSelector := rightClickCmd.String("selector", "", "CSS selector for the element to right-click")
|
||||
rightClickTimeout := rightClickCmd.Int("timeout", 5, "Timeout in seconds for right-click operation")
|
||||
rightClickHost := rightClickCmd.String("host", "localhost", "Daemon host")
|
||||
rightClickPort := rightClickCmd.Int("port", 8989, "Daemon port")
|
||||
|
||||
// double-click flags
|
||||
doubleClickTabID := doubleClickCmd.String("tab", "", "Tab ID to perform double-click on (optional, uses current tab if not specified)")
|
||||
doubleClickSelector := doubleClickCmd.String("selector", "", "CSS selector for the element to double-click")
|
||||
doubleClickTimeout := doubleClickCmd.Int("timeout", 5, "Timeout in seconds for double-click operation")
|
||||
doubleClickHost := doubleClickCmd.String("host", "localhost", "Daemon host")
|
||||
doubleClickPort := doubleClickCmd.Int("port", 8989, "Daemon port")
|
||||
|
||||
// middle-click flags
|
||||
middleClickTabID := middleClickCmd.String("tab", "", "Tab ID to perform middle-click on (optional, uses current tab if not specified)")
|
||||
middleClickSelector := middleClickCmd.String("selector", "", "CSS selector for the element to middle-click")
|
||||
middleClickTimeout := middleClickCmd.Int("timeout", 5, "Timeout in seconds for middle-click operation")
|
||||
middleClickHost := middleClickCmd.String("host", "localhost", "Daemon host")
|
||||
middleClickPort := middleClickCmd.Int("port", 8989, "Daemon port")
|
||||
|
||||
// hover flags
|
||||
hoverTabID := hoverCmd.String("tab", "", "Tab ID to perform hover on (optional, uses current tab if not specified)")
|
||||
hoverSelector := hoverCmd.String("selector", "", "CSS selector for the element to hover over")
|
||||
hoverTimeout := hoverCmd.Int("timeout", 5, "Timeout in seconds for hover operation")
|
||||
hoverHost := hoverCmd.String("host", "localhost", "Daemon host")
|
||||
hoverPort := hoverCmd.Int("port", 8989, "Daemon port")
|
||||
|
||||
// mouse-move flags
|
||||
mouseMoveTabID := mouseMoveCmd.String("tab", "", "Tab ID to move mouse on (optional, uses current tab if not specified)")
|
||||
mouseMoveX := mouseMoveCmd.Int("x", 0, "X coordinate to move mouse to")
|
||||
mouseMoveY := mouseMoveCmd.Int("y", 0, "Y coordinate to move mouse to")
|
||||
mouseMoveTimeout := mouseMoveCmd.Int("timeout", 5, "Timeout in seconds for mouse move operation")
|
||||
mouseMoveHost := mouseMoveCmd.String("host", "localhost", "Daemon host")
|
||||
mouseMovePort := mouseMoveCmd.Int("port", 8989, "Daemon port")
|
||||
|
||||
// scroll-wheel flags
|
||||
scrollWheelTabID := scrollWheelCmd.String("tab", "", "Tab ID to scroll on (optional, uses current tab if not specified)")
|
||||
scrollWheelX := scrollWheelCmd.Int("x", 0, "X coordinate for scroll wheel")
|
||||
scrollWheelY := scrollWheelCmd.Int("y", 0, "Y coordinate for scroll wheel")
|
||||
scrollWheelDeltaX := scrollWheelCmd.Int("delta-x", 0, "Horizontal scroll delta (negative = left, positive = right)")
|
||||
scrollWheelDeltaY := scrollWheelCmd.Int("delta-y", 0, "Vertical scroll delta (negative = up, positive = down)")
|
||||
scrollWheelTimeout := scrollWheelCmd.Int("timeout", 5, "Timeout in seconds for scroll wheel operation")
|
||||
scrollWheelHost := scrollWheelCmd.String("host", "localhost", "Daemon host")
|
||||
scrollWheelPort := scrollWheelCmd.Int("port", 8989, "Daemon port")
|
||||
|
||||
// key-combination flags
|
||||
keyCombinationTabID := keyCombinationCmd.String("tab", "", "Tab ID to send key combination to (optional, uses current tab if not specified)")
|
||||
keyCombinationKeys := keyCombinationCmd.String("keys", "", "Key combination to send (e.g., 'Ctrl+C', 'Alt+Tab', 'Shift+Enter')")
|
||||
keyCombinationTimeout := keyCombinationCmd.Int("timeout", 5, "Timeout in seconds for key combination operation")
|
||||
keyCombinationHost := keyCombinationCmd.String("host", "localhost", "Daemon host")
|
||||
keyCombinationPort := keyCombinationCmd.Int("port", 8989, "Daemon port")
|
||||
|
||||
// special-key flags
|
||||
specialKeyTabID := specialKeyCmd.String("tab", "", "Tab ID to send special key to (optional, uses current tab if not specified)")
|
||||
specialKeyKey := specialKeyCmd.String("key", "", "Special key to send (e.g., 'Enter', 'Escape', 'Tab', 'F1', 'ArrowUp')")
|
||||
specialKeyTimeout := specialKeyCmd.Int("timeout", 5, "Timeout in seconds for special key operation")
|
||||
specialKeyHost := specialKeyCmd.String("host", "localhost", "Daemon host")
|
||||
specialKeyPort := specialKeyCmd.Int("port", 8989, "Daemon port")
|
||||
|
||||
// modifier-click flags
|
||||
modifierClickTabID := modifierClickCmd.String("tab", "", "Tab ID to perform modifier click on (optional, uses current tab if not specified)")
|
||||
modifierClickSelector := modifierClickCmd.String("selector", "", "CSS selector for the element to click")
|
||||
modifierClickModifiers := modifierClickCmd.String("modifiers", "", "Modifier keys (e.g., 'Ctrl', 'Shift', 'Alt', 'Ctrl+Shift')")
|
||||
modifierClickTimeout := modifierClickCmd.Int("timeout", 5, "Timeout in seconds for modifier click operation")
|
||||
modifierClickHost := modifierClickCmd.String("host", "localhost", "Daemon host")
|
||||
modifierClickPort := modifierClickCmd.Int("port", 8989, "Daemon port")
|
||||
|
||||
// Note: Touch operations flags will be added when the functionality is implemented
|
||||
// touchTapTabID := touchTapCmd.String("tab", "", "Tab ID to perform touch tap on (optional, uses current tab if not specified)")
|
||||
// ... (other touch operation flags)
|
||||
|
||||
// Check if a subcommand is provided
|
||||
if len(os.Args) < 2 {
|
||||
printUsage()
|
||||
@@ -622,6 +746,163 @@ func main() {
|
||||
|
||||
fmt.Println("Storage cleared successfully")
|
||||
|
||||
case "drag-and-drop":
|
||||
dragAndDropCmd.Parse(os.Args[2:])
|
||||
|
||||
// Validate required parameters
|
||||
if *dragAndDropSource == "" {
|
||||
fmt.Fprintf(os.Stderr, "Error: --source parameter is required\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
if *dragAndDropTarget == "" {
|
||||
fmt.Fprintf(os.Stderr, "Error: --target parameter is required\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Create a client
|
||||
c := client.NewClient(*dragAndDropHost, *dragAndDropPort)
|
||||
|
||||
// Perform drag and drop
|
||||
err := c.DragAndDrop(*dragAndDropTabID, *dragAndDropSource, *dragAndDropTarget, *dragAndDropTimeout)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("Drag and drop completed successfully")
|
||||
|
||||
case "drag-and-drop-coordinates":
|
||||
dragAndDropCoordinatesCmd.Parse(os.Args[2:])
|
||||
|
||||
// Validate required parameters
|
||||
if *dragAndDropCoordinatesSource == "" {
|
||||
fmt.Fprintf(os.Stderr, "Error: --source parameter is required\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Create a client
|
||||
c := client.NewClient(*dragAndDropCoordinatesHost, *dragAndDropCoordinatesPort)
|
||||
|
||||
// Perform drag and drop to coordinates
|
||||
err := c.DragAndDropToCoordinates(*dragAndDropCoordinatesTabID, *dragAndDropCoordinatesSource, *dragAndDropCoordinatesX, *dragAndDropCoordinatesY, *dragAndDropCoordinatesTimeout)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Drag and drop to coordinates (%d, %d) completed successfully\n", *dragAndDropCoordinatesX, *dragAndDropCoordinatesY)
|
||||
|
||||
case "drag-and-drop-offset":
|
||||
dragAndDropOffsetCmd.Parse(os.Args[2:])
|
||||
|
||||
// Validate required parameters
|
||||
if *dragAndDropOffsetSource == "" {
|
||||
fmt.Fprintf(os.Stderr, "Error: --source parameter is required\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Create a client
|
||||
c := client.NewClient(*dragAndDropOffsetHost, *dragAndDropOffsetPort)
|
||||
|
||||
// Perform drag and drop by offset
|
||||
err := c.DragAndDropByOffset(*dragAndDropOffsetTabID, *dragAndDropOffsetSource, *dragAndDropOffsetX, *dragAndDropOffsetY, *dragAndDropOffsetTimeout)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Drag and drop by offset (%d, %d) completed successfully\n", *dragAndDropOffsetX, *dragAndDropOffsetY)
|
||||
|
||||
case "right-click":
|
||||
rightClickCmd.Parse(os.Args[2:])
|
||||
c := client.NewClient(*rightClickHost, *rightClickPort)
|
||||
err := c.RightClick(*rightClickTabID, *rightClickSelector, *rightClickTimeout)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Right-click completed successfully\n")
|
||||
|
||||
case "double-click":
|
||||
doubleClickCmd.Parse(os.Args[2:])
|
||||
c := client.NewClient(*doubleClickHost, *doubleClickPort)
|
||||
err := c.DoubleClick(*doubleClickTabID, *doubleClickSelector, *doubleClickTimeout)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Double-click completed successfully\n")
|
||||
|
||||
case "middle-click":
|
||||
middleClickCmd.Parse(os.Args[2:])
|
||||
c := client.NewClient(*middleClickHost, *middleClickPort)
|
||||
err := c.MiddleClick(*middleClickTabID, *middleClickSelector, *middleClickTimeout)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Middle-click completed successfully\n")
|
||||
|
||||
case "hover":
|
||||
hoverCmd.Parse(os.Args[2:])
|
||||
c := client.NewClient(*hoverHost, *hoverPort)
|
||||
err := c.Hover(*hoverTabID, *hoverSelector, *hoverTimeout)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Hover completed successfully\n")
|
||||
|
||||
case "mouse-move":
|
||||
mouseMoveCmd.Parse(os.Args[2:])
|
||||
c := client.NewClient(*mouseMoveHost, *mouseMovePort)
|
||||
err := c.MouseMove(*mouseMoveTabID, *mouseMoveX, *mouseMoveY, *mouseMoveTimeout)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Mouse move to (%d, %d) completed successfully\n", *mouseMoveX, *mouseMoveY)
|
||||
|
||||
case "scroll-wheel":
|
||||
scrollWheelCmd.Parse(os.Args[2:])
|
||||
c := client.NewClient(*scrollWheelHost, *scrollWheelPort)
|
||||
err := c.ScrollWheel(*scrollWheelTabID, *scrollWheelX, *scrollWheelY, *scrollWheelDeltaX, *scrollWheelDeltaY, *scrollWheelTimeout)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Scroll wheel at (%d, %d) with delta (%d, %d) completed successfully\n", *scrollWheelX, *scrollWheelY, *scrollWheelDeltaX, *scrollWheelDeltaY)
|
||||
|
||||
case "key-combination":
|
||||
keyCombinationCmd.Parse(os.Args[2:])
|
||||
c := client.NewClient(*keyCombinationHost, *keyCombinationPort)
|
||||
err := c.KeyCombination(*keyCombinationTabID, *keyCombinationKeys, *keyCombinationTimeout)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Key combination '%s' sent successfully\n", *keyCombinationKeys)
|
||||
|
||||
case "special-key":
|
||||
specialKeyCmd.Parse(os.Args[2:])
|
||||
c := client.NewClient(*specialKeyHost, *specialKeyPort)
|
||||
err := c.SpecialKey(*specialKeyTabID, *specialKeyKey, *specialKeyTimeout)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Special key '%s' sent successfully\n", *specialKeyKey)
|
||||
|
||||
case "modifier-click":
|
||||
modifierClickCmd.Parse(os.Args[2:])
|
||||
c := client.NewClient(*modifierClickHost, *modifierClickPort)
|
||||
err := c.ModifierClick(*modifierClickTabID, *modifierClickSelector, *modifierClickModifiers, *modifierClickTimeout)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Modifier click with '%s' completed successfully\n", *modifierClickModifiers)
|
||||
|
||||
default:
|
||||
printUsage()
|
||||
os.Exit(1)
|
||||
@@ -653,6 +934,18 @@ func printUsage() {
|
||||
fmt.Println(" clear-all-site-data Clear all site data (cookies, storage, cache, etc.)")
|
||||
fmt.Println(" clear-cookies Clear cookies for a tab")
|
||||
fmt.Println(" clear-storage Clear web storage (localStorage, sessionStorage, etc.)")
|
||||
fmt.Println(" drag-and-drop Drag and drop from source element to target element")
|
||||
fmt.Println(" drag-and-drop-coordinates Drag and drop from source element to specific coordinates")
|
||||
fmt.Println(" drag-and-drop-offset Drag and drop from source element by relative offset")
|
||||
fmt.Println(" right-click Right-click on an element")
|
||||
fmt.Println(" double-click Double-click on an element")
|
||||
fmt.Println(" middle-click Middle-click on an element")
|
||||
fmt.Println(" hover Hover over an element")
|
||||
fmt.Println(" mouse-move Move mouse to specific coordinates")
|
||||
fmt.Println(" scroll-wheel Scroll with mouse wheel at specific coordinates")
|
||||
fmt.Println(" key-combination Send key combinations (Ctrl+C, Alt+Tab, etc.)")
|
||||
fmt.Println(" special-key Send special keys (Enter, Escape, Tab, F1, etc.)")
|
||||
fmt.Println(" modifier-click Click with modifier keys (Ctrl+click, Shift+click)")
|
||||
fmt.Println(" status Check if the daemon is running")
|
||||
fmt.Println("\nRun 'cremote <command> -h' for more information on a command")
|
||||
fmt.Println("\nBefore using this tool, make sure the daemon is running:")
|
||||
|
||||
@@ -1034,6 +1034,300 @@ web_clear_storage_cremotemcp:
|
||||
timeout: 5
|
||||
```
|
||||
|
||||
### 34. `web_drag_and_drop_cremotemcp` *(New in Phase 6)*
|
||||
Perform drag and drop operation from source element to target element.
|
||||
|
||||
**Parameters:**
|
||||
- `source` (required): CSS selector for the source element to drag
|
||||
- `target` (required): CSS selector for the target element to drop on
|
||||
- `tab` (optional): Tab ID (uses current tab if not specified)
|
||||
- `timeout` (optional): Timeout in seconds (default: 5)
|
||||
|
||||
**Example Usage:**
|
||||
```
|
||||
# Drag item to drop zone
|
||||
web_drag_and_drop_cremotemcp:
|
||||
source: ".draggable-item"
|
||||
target: ".drop-zone"
|
||||
timeout: 10
|
||||
|
||||
# Drag file to upload area
|
||||
web_drag_and_drop_cremotemcp:
|
||||
source: "#file-item"
|
||||
target: "#upload-area"
|
||||
tab: "tab-123"
|
||||
```
|
||||
|
||||
### 35. `web_drag_and_drop_coordinates_cremotemcp` *(New in Phase 6)*
|
||||
Perform drag and drop operation from source element to specific coordinates.
|
||||
|
||||
**Parameters:**
|
||||
- `source` (required): CSS selector for the source element to drag
|
||||
- `x` (required): Target X coordinate
|
||||
- `y` (required): Target Y coordinate
|
||||
- `tab` (optional): Tab ID (uses current tab if not specified)
|
||||
- `timeout` (optional): Timeout in seconds (default: 5)
|
||||
|
||||
**Example Usage:**
|
||||
```
|
||||
# Drag item to specific coordinates
|
||||
web_drag_and_drop_coordinates_cremotemcp:
|
||||
source: ".draggable-item"
|
||||
x: 300
|
||||
y: 200
|
||||
timeout: 10
|
||||
|
||||
# Drag widget to precise position
|
||||
web_drag_and_drop_coordinates_cremotemcp:
|
||||
source: "#dashboard-widget"
|
||||
x: 150
|
||||
y: 400
|
||||
tab: "tab-123"
|
||||
```
|
||||
|
||||
### 36. `web_drag_and_drop_offset_cremotemcp` *(New in Phase 6)*
|
||||
Perform drag and drop operation from source element by relative offset.
|
||||
|
||||
**Parameters:**
|
||||
- `source` (required): CSS selector for the source element to drag
|
||||
- `offset_x` (required): Horizontal offset in pixels
|
||||
- `offset_y` (required): Vertical offset in pixels
|
||||
- `tab` (optional): Tab ID (uses current tab if not specified)
|
||||
- `timeout` (optional): Timeout in seconds (default: 5)
|
||||
|
||||
**Example Usage:**
|
||||
```
|
||||
# Drag item by relative offset
|
||||
web_drag_and_drop_offset_cremotemcp:
|
||||
source: ".draggable-item"
|
||||
offset_x: 100
|
||||
offset_y: 50
|
||||
timeout: 10
|
||||
|
||||
# Move element slightly to the right and down
|
||||
web_drag_and_drop_offset_cremotemcp:
|
||||
source: "#moveable-element"
|
||||
offset_x: 25
|
||||
offset_y: 25
|
||||
tab: "tab-123"
|
||||
```
|
||||
|
||||
### 37. `web_right_click_cremotemcp` *(New in Phase 7)*
|
||||
Right-click on an element to open context menus.
|
||||
|
||||
**Parameters:**
|
||||
- `selector` (required): CSS selector for the element to right-click
|
||||
- `tab` (optional): Tab ID (uses current tab if not specified)
|
||||
- `timeout` (optional): Timeout in seconds (default: 5)
|
||||
|
||||
**Example Usage:**
|
||||
```
|
||||
# Right-click to open context menu
|
||||
web_right_click_cremotemcp:
|
||||
selector: ".file-item"
|
||||
timeout: 10
|
||||
|
||||
# Right-click on specific element
|
||||
web_right_click_cremotemcp:
|
||||
selector: "#document-title"
|
||||
tab: "tab-123"
|
||||
```
|
||||
|
||||
### 38. `web_double_click_cremotemcp` *(New in Phase 7)*
|
||||
Double-click on an element for file operations or text selection.
|
||||
|
||||
**Parameters:**
|
||||
- `selector` (required): CSS selector for the element to double-click
|
||||
- `tab` (optional): Tab ID (uses current tab if not specified)
|
||||
- `timeout` (optional): Timeout in seconds (default: 5)
|
||||
|
||||
**Example Usage:**
|
||||
```
|
||||
# Double-click to open file
|
||||
web_double_click_cremotemcp:
|
||||
selector: ".file-icon"
|
||||
timeout: 10
|
||||
|
||||
# Double-click to select text
|
||||
web_double_click_cremotemcp:
|
||||
selector: ".text-content"
|
||||
tab: "tab-123"
|
||||
```
|
||||
|
||||
### 39. `web_hover_cremotemcp` *(New in Phase 7)*
|
||||
Hover over an element to trigger tooltips or dropdowns.
|
||||
|
||||
**Parameters:**
|
||||
- `selector` (required): CSS selector for the element to hover over
|
||||
- `tab` (optional): Tab ID (uses current tab if not specified)
|
||||
- `timeout` (optional): Timeout in seconds (default: 5)
|
||||
|
||||
**Example Usage:**
|
||||
```
|
||||
# Hover to show tooltip
|
||||
web_hover_cremotemcp:
|
||||
selector: ".tooltip-trigger"
|
||||
timeout: 10
|
||||
|
||||
# Hover to open dropdown menu
|
||||
web_hover_cremotemcp:
|
||||
selector: ".nav-item"
|
||||
tab: "tab-123"
|
||||
```
|
||||
|
||||
### 40. `web_middle_click_cremotemcp` *(New in Phase 7)*
|
||||
Middle-click on an element (typically opens links in new tabs).
|
||||
|
||||
**Parameters:**
|
||||
- `selector` (required): CSS selector for the element to middle-click
|
||||
- `tab` (optional): Tab ID (uses current tab if not specified)
|
||||
- `timeout` (optional): Timeout in seconds (default: 5)
|
||||
|
||||
**Example Usage:**
|
||||
```
|
||||
# Middle-click to open link in new tab
|
||||
web_middle_click_cremotemcp:
|
||||
selector: "a[href='/dashboard']"
|
||||
timeout: 10
|
||||
|
||||
# Middle-click on navigation link
|
||||
web_middle_click_cremotemcp:
|
||||
selector: ".nav-link"
|
||||
tab: "tab-123"
|
||||
```
|
||||
|
||||
### 41. `web_mouse_move_cremotemcp` *(New in Phase 7)*
|
||||
Move mouse to specific coordinates without clicking.
|
||||
|
||||
**Parameters:**
|
||||
- `x` (required): X coordinate to move mouse to
|
||||
- `y` (required): Y coordinate to move mouse to
|
||||
- `tab` (optional): Tab ID (uses current tab if not specified)
|
||||
- `timeout` (optional): Timeout in seconds (default: 5)
|
||||
|
||||
**Example Usage:**
|
||||
```
|
||||
# Move mouse to specific position
|
||||
web_mouse_move_cremotemcp:
|
||||
x: 400
|
||||
y: 300
|
||||
timeout: 5
|
||||
|
||||
# Position mouse for subsequent action
|
||||
web_mouse_move_cremotemcp:
|
||||
x: 150
|
||||
y: 200
|
||||
tab: "tab-123"
|
||||
```
|
||||
|
||||
### 42. `web_scroll_wheel_cremotemcp` *(New in Phase 7)*
|
||||
Scroll with mouse wheel at specific coordinates.
|
||||
|
||||
**Parameters:**
|
||||
- `x` (required): X coordinate for scroll wheel
|
||||
- `y` (required): Y coordinate for scroll wheel
|
||||
- `delta_y` (required): Vertical scroll delta (negative = up, positive = down)
|
||||
- `delta_x` (optional): Horizontal scroll delta (negative = left, positive = right, default: 0)
|
||||
- `tab` (optional): Tab ID (uses current tab if not specified)
|
||||
- `timeout` (optional): Timeout in seconds (default: 5)
|
||||
|
||||
**Example Usage:**
|
||||
```
|
||||
# Scroll up at specific location
|
||||
web_scroll_wheel_cremotemcp:
|
||||
x: 400
|
||||
y: 300
|
||||
delta_y: -120
|
||||
timeout: 5
|
||||
|
||||
# Scroll down with horizontal component
|
||||
web_scroll_wheel_cremotemcp:
|
||||
x: 500
|
||||
y: 400
|
||||
delta_x: 50
|
||||
delta_y: 120
|
||||
tab: "tab-123"
|
||||
```
|
||||
|
||||
### 43. `web_key_combination_cremotemcp` *(New in Phase 7)*
|
||||
Send key combinations like Ctrl+C, Alt+Tab, Shift+Enter.
|
||||
|
||||
**Parameters:**
|
||||
- `keys` (required): Key combination to send (e.g., 'Ctrl+C', 'Alt+Tab', 'Shift+Enter', 'Ctrl+Shift+T')
|
||||
- `tab` (optional): Tab ID (uses current tab if not specified)
|
||||
- `timeout` (optional): Timeout in seconds (default: 5)
|
||||
|
||||
**Example Usage:**
|
||||
```
|
||||
# Copy selected text
|
||||
web_key_combination_cremotemcp:
|
||||
keys: "Ctrl+C"
|
||||
timeout: 5
|
||||
|
||||
# Open new tab
|
||||
web_key_combination_cremotemcp:
|
||||
keys: "Ctrl+T"
|
||||
tab: "tab-123"
|
||||
|
||||
# Select all text
|
||||
web_key_combination_cremotemcp:
|
||||
keys: "Ctrl+A"
|
||||
```
|
||||
|
||||
### 44. `web_special_key_cremotemcp` *(New in Phase 7)*
|
||||
Send special keys like Enter, Escape, Tab, F1-F12, Arrow keys.
|
||||
|
||||
**Parameters:**
|
||||
- `key` (required): Special key to send (e.g., 'Enter', 'Escape', 'Tab', 'F1', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End', 'PageUp', 'PageDown', 'Delete', 'Backspace')
|
||||
- `tab` (optional): Tab ID (uses current tab if not specified)
|
||||
- `timeout` (optional): Timeout in seconds (default: 5)
|
||||
|
||||
**Example Usage:**
|
||||
```
|
||||
# Press Enter key
|
||||
web_special_key_cremotemcp:
|
||||
key: "Enter"
|
||||
timeout: 5
|
||||
|
||||
# Navigate with arrow keys
|
||||
web_special_key_cremotemcp:
|
||||
key: "ArrowDown"
|
||||
tab: "tab-123"
|
||||
|
||||
# Press Escape to close dialog
|
||||
web_special_key_cremotemcp:
|
||||
key: "Escape"
|
||||
```
|
||||
|
||||
### 45. `web_modifier_click_cremotemcp` *(New in Phase 7)*
|
||||
Click on an element with modifier keys (Ctrl+click, Shift+click).
|
||||
|
||||
**Parameters:**
|
||||
- `selector` (required): CSS selector for the element to click
|
||||
- `modifiers` (required): Modifier keys to hold while clicking (e.g., 'Ctrl', 'Shift', 'Alt', 'Ctrl+Shift', 'Ctrl+Alt')
|
||||
- `tab` (optional): Tab ID (uses current tab if not specified)
|
||||
- `timeout` (optional): Timeout in seconds (default: 5)
|
||||
|
||||
**Example Usage:**
|
||||
```
|
||||
# Ctrl+click for multi-selection
|
||||
web_modifier_click_cremotemcp:
|
||||
selector: ".selectable-item"
|
||||
modifiers: "Ctrl"
|
||||
timeout: 10
|
||||
|
||||
# Shift+click for range selection
|
||||
web_modifier_click_cremotemcp:
|
||||
selector: ".list-item:nth-child(5)"
|
||||
modifiers: "Shift"
|
||||
tab: "tab-123"
|
||||
|
||||
# Ctrl+Shift+click for extended selection
|
||||
web_modifier_click_cremotemcp:
|
||||
selector: ".table-row"
|
||||
modifiers: "Ctrl+Shift"
|
||||
```
|
||||
|
||||
## Common Usage Patterns
|
||||
|
||||
### 1. Basic Web Navigation
|
||||
@@ -1737,10 +2031,110 @@ get_partial_accessibility_tree_cremotemcp:
|
||||
- Test screen reader compatibility by analyzing semantic structure
|
||||
- Build more robust automation using ARIA roles and accessible names
|
||||
|
||||
### Advanced Input Operation Testing *(Phase 7 Patterns)*
|
||||
```
|
||||
# Test context menu workflows
|
||||
web_right_click_cremotemcp:
|
||||
selector: ".file-item"
|
||||
timeout: 5
|
||||
web_interact_cremotemcp:
|
||||
action: "click"
|
||||
selector: ".context-menu .delete"
|
||||
timeout: 5
|
||||
|
||||
# Test hover-triggered dropdowns
|
||||
web_hover_cremotemcp:
|
||||
selector: ".nav-item"
|
||||
timeout: 5
|
||||
web_interact_cremotemcp:
|
||||
action: "click"
|
||||
selector: ".dropdown-item"
|
||||
timeout: 5
|
||||
|
||||
# Test keyboard navigation accessibility
|
||||
web_special_key_cremotemcp:
|
||||
key: "Tab"
|
||||
timeout: 5
|
||||
web_special_key_cremotemcp:
|
||||
key: "Enter"
|
||||
timeout: 5
|
||||
|
||||
# Test copy/paste workflows
|
||||
web_key_combination_cremotemcp:
|
||||
keys: "Ctrl+A"
|
||||
timeout: 5
|
||||
web_key_combination_cremotemcp:
|
||||
keys: "Ctrl+C"
|
||||
timeout: 5
|
||||
web_interact_cremotemcp:
|
||||
action: "click"
|
||||
selector: "#target-input"
|
||||
timeout: 5
|
||||
web_key_combination_cremotemcp:
|
||||
keys: "Ctrl+V"
|
||||
timeout: 5
|
||||
|
||||
# Test multi-selection with modifier clicks
|
||||
web_modifier_click_cremotemcp:
|
||||
selector: ".item:nth-child(1)"
|
||||
modifiers: "Ctrl"
|
||||
timeout: 5
|
||||
web_modifier_click_cremotemcp:
|
||||
selector: ".item:nth-child(3)"
|
||||
modifiers: "Ctrl"
|
||||
timeout: 5
|
||||
web_modifier_click_cremotemcp:
|
||||
selector: ".item:nth-child(5)"
|
||||
modifiers: "Ctrl"
|
||||
timeout: 5
|
||||
|
||||
# Test precise mouse wheel scrolling
|
||||
web_scroll_wheel_cremotemcp:
|
||||
x: 400
|
||||
y: 300
|
||||
delta_y: -240 # Scroll up 2 notches
|
||||
timeout: 5
|
||||
|
||||
# Test middle-click for new tabs
|
||||
web_middle_click_cremotemcp:
|
||||
selector: "a[href='/dashboard']"
|
||||
timeout: 5
|
||||
|
||||
# Test double-click for text selection
|
||||
web_double_click_cremotemcp:
|
||||
selector: ".editable-text"
|
||||
timeout: 5
|
||||
```
|
||||
|
||||
### Advanced Drag & Drop Testing *(Phase 6 Patterns)*
|
||||
```
|
||||
# Test sortable list reordering
|
||||
web_drag_and_drop_cremotemcp:
|
||||
source: ".list-item:first-child"
|
||||
target: ".list-item:last-child"
|
||||
timeout: 10
|
||||
|
||||
# Test file upload via drag and drop
|
||||
web_drag_and_drop_coordinates_cremotemcp:
|
||||
source: ".file-item"
|
||||
x: 400
|
||||
y: 300 # Drop zone coordinates
|
||||
timeout: 15
|
||||
|
||||
# Test dashboard widget repositioning
|
||||
web_drag_and_drop_offset_cremotemcp:
|
||||
source: ".widget"
|
||||
offset_x: 200
|
||||
offset_y: 100
|
||||
timeout: 10
|
||||
```
|
||||
|
||||
## 🎉 Production Ready
|
||||
|
||||
This comprehensive web automation platform provides **31 tools** across 6 enhancement phases, optimized specifically for LLM agents and production workflows. All tools include proper error handling, timeout management, and structured responses for reliable automation.
|
||||
This comprehensive web automation platform provides **45 tools** across 7 enhancement phases, optimized specifically for LLM agents and production workflows. All tools include proper error handling, timeout management, and structured responses for reliable automation.
|
||||
|
||||
**Latest Enhancement - Phase 7**: Advanced input operations including right-click context menus, hover interactions, keyboard combinations, modifier clicks, precise mouse control, and mouse wheel scrolling for comprehensive testing of modern interactive web applications.
|
||||
|
||||
---
|
||||
|
||||
**Ready for Production**: Complete web automation platform with 31 tools, designed for maximum efficiency and reliability in LLM-driven workflows.
|
||||
**Ready for Production**: Complete web automation platform with 45 tools, designed for maximum efficiency and reliability in LLM-driven workflows.
|
||||
|
||||
201
mcp/README.md
201
mcp/README.md
@@ -611,9 +611,187 @@ Clear web storage (localStorage, sessionStorage, IndexedDB, etc.) for a tab.
|
||||
}
|
||||
```
|
||||
|
||||
#### 34. `web_drag_and_drop_cremotemcp`
|
||||
Perform drag and drop operation from source element to target element.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "web_drag_and_drop_cremotemcp",
|
||||
"arguments": {
|
||||
"source": ".draggable-item",
|
||||
"target": ".drop-zone",
|
||||
"tab": "tab-123",
|
||||
"timeout": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 35. `web_drag_and_drop_coordinates_cremotemcp`
|
||||
Perform drag and drop operation from source element to specific coordinates.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "web_drag_and_drop_coordinates_cremotemcp",
|
||||
"arguments": {
|
||||
"source": ".draggable-item",
|
||||
"x": 300,
|
||||
"y": 200,
|
||||
"tab": "tab-123",
|
||||
"timeout": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 36. `web_drag_and_drop_offset_cremotemcp`
|
||||
Perform drag and drop operation from source element by relative offset.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "web_drag_and_drop_offset_cremotemcp",
|
||||
"arguments": {
|
||||
"source": ".draggable-item",
|
||||
"offset_x": 100,
|
||||
"offset_y": 50,
|
||||
"tab": "tab-123",
|
||||
"timeout": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 37. `web_right_click_cremotemcp`
|
||||
Right-click on an element to open context menus.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "web_right_click_cremotemcp",
|
||||
"arguments": {
|
||||
"selector": ".file-item",
|
||||
"tab": "tab-123",
|
||||
"timeout": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 38. `web_double_click_cremotemcp`
|
||||
Double-click on an element for file operations or text selection.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "web_double_click_cremotemcp",
|
||||
"arguments": {
|
||||
"selector": ".file-icon",
|
||||
"tab": "tab-123",
|
||||
"timeout": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 39. `web_hover_cremotemcp`
|
||||
Hover over an element to trigger tooltips or dropdowns.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "web_hover_cremotemcp",
|
||||
"arguments": {
|
||||
"selector": ".tooltip-trigger",
|
||||
"tab": "tab-123",
|
||||
"timeout": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 40. `web_middle_click_cremotemcp`
|
||||
Middle-click on an element (typically opens links in new tabs).
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "web_middle_click_cremotemcp",
|
||||
"arguments": {
|
||||
"selector": "a[href='/dashboard']",
|
||||
"tab": "tab-123",
|
||||
"timeout": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 41. `web_mouse_move_cremotemcp`
|
||||
Move mouse to specific coordinates without clicking.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "web_mouse_move_cremotemcp",
|
||||
"arguments": {
|
||||
"x": 400,
|
||||
"y": 300,
|
||||
"tab": "tab-123",
|
||||
"timeout": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 42. `web_scroll_wheel_cremotemcp`
|
||||
Scroll with mouse wheel at specific coordinates.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "web_scroll_wheel_cremotemcp",
|
||||
"arguments": {
|
||||
"x": 400,
|
||||
"y": 300,
|
||||
"delta_x": 0,
|
||||
"delta_y": -120,
|
||||
"tab": "tab-123",
|
||||
"timeout": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 43. `web_key_combination_cremotemcp`
|
||||
Send key combinations like Ctrl+C, Alt+Tab, Shift+Enter.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "web_key_combination_cremotemcp",
|
||||
"arguments": {
|
||||
"keys": "Ctrl+C",
|
||||
"tab": "tab-123",
|
||||
"timeout": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 44. `web_special_key_cremotemcp`
|
||||
Send special keys like Enter, Escape, Tab, F1-F12, Arrow keys.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "web_special_key_cremotemcp",
|
||||
"arguments": {
|
||||
"key": "Enter",
|
||||
"tab": "tab-123",
|
||||
"timeout": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 45. `web_modifier_click_cremotemcp`
|
||||
Click on an element with modifier keys (Ctrl+click, Shift+click).
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "web_modifier_click_cremotemcp",
|
||||
"arguments": {
|
||||
"selector": ".selectable-item",
|
||||
"modifiers": "Ctrl",
|
||||
"tab": "tab-123",
|
||||
"timeout": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🎉 Complete Enhancement Summary
|
||||
|
||||
All 6 phases of the MCP enhancement plan have been successfully implemented, delivering a comprehensive web automation platform with **33 tools** organized across the following capabilities:
|
||||
All phases of the MCP enhancement plan have been successfully implemented, delivering a comprehensive web automation platform with **45 tools** organized across the following capabilities:
|
||||
|
||||
### ✅ Phase 1: Element State and Checking (2 tools)
|
||||
**Enables conditional logic without timing issues**
|
||||
@@ -657,16 +835,31 @@ All 6 phases of the MCP enhancement plan have been successfully implemented, del
|
||||
|
||||
**Benefits**: Better debugging with targeted screenshots, improved file handling workflows, automatic resource management, enhanced visual debugging capabilities.
|
||||
|
||||
### ✅ Phase 6: Browser Cache and Site Data Management (6 tools)
|
||||
**Enables comprehensive cache and site data control for testing and privacy**
|
||||
### ✅ Phase 6: Browser Cache, Site Data Management, and Drag & Drop (9 tools)
|
||||
**Enables comprehensive cache/site data control and advanced drag & drop interactions**
|
||||
- `web_disable_cache_cremotemcp`: Disable browser cache for a tab
|
||||
- `web_enable_cache_cremotemcp`: Enable browser cache for a tab
|
||||
- `web_clear_cache_cremotemcp`: Clear browser cache for a tab
|
||||
- `web_clear_all_site_data_cremotemcp`: Clear all site data (cookies, storage, cache, etc.)
|
||||
- `web_clear_cookies_cremotemcp`: Clear cookies for a tab
|
||||
- `web_clear_storage_cremotemcp`: Clear web storage (localStorage, sessionStorage, IndexedDB, etc.)
|
||||
- `web_drag_and_drop_cremotemcp`: Drag and drop between elements
|
||||
- `web_drag_and_drop_coordinates_cremotemcp`: Drag and drop to specific coordinates
|
||||
- `web_drag_and_drop_offset_cremotemcp`: Drag and drop by relative offset
|
||||
|
||||
**Benefits**: Essential for testing scenarios requiring fresh page loads, performance testing without cached resources, debugging cache-related issues, ensuring consistent test environments, privacy testing, authentication testing, and complete site data cleanup.
|
||||
### ✅ Phase 7: Advanced Input Operations (9 tools)
|
||||
**Enables sophisticated mouse and keyboard interactions for comprehensive testing**
|
||||
- `web_right_click_cremotemcp`: Right-click for context menus
|
||||
- `web_double_click_cremotemcp`: Double-click for file operations and text selection
|
||||
- `web_hover_cremotemcp`: Hover for tooltips and dropdown triggers
|
||||
- `web_middle_click_cremotemcp`: Middle-click for opening links in new tabs
|
||||
- `web_mouse_move_cremotemcp`: Precise mouse positioning without clicking
|
||||
- `web_scroll_wheel_cremotemcp`: Mouse wheel scrolling with pixel-perfect control
|
||||
- `web_key_combination_cremotemcp`: Key combinations (Ctrl+C, Alt+Tab, Shift+Enter, etc.)
|
||||
- `web_special_key_cremotemcp`: Special keys (Enter, Escape, Tab, F1-F12, Arrow keys, etc.)
|
||||
- `web_modifier_click_cremotemcp`: Modifier clicks (Ctrl+click, Shift+click for multi-selection)
|
||||
|
||||
**Benefits**: Essential for testing scenarios requiring fresh page loads, performance testing without cached resources, debugging cache-related issues, ensuring consistent test environments, privacy testing, authentication testing, complete site data cleanup, **plus advanced drag & drop testing for sortable lists, file uploads, kanban boards, dashboard widgets, and interactive UI components**. **Advanced input operations enable context menu testing, keyboard navigation accessibility testing, tooltip/dropdown interactions, multi-selection workflows, copy/paste operations, and sophisticated user interaction patterns**.
|
||||
|
||||
## Key Benefits for LLM Agents
|
||||
|
||||
|
||||
863
mcp/main.go
863
mcp/main.go
@@ -2440,6 +2440,869 @@ func main() {
|
||||
}, nil
|
||||
})
|
||||
|
||||
// Drag and drop tools
|
||||
mcpServer.AddTool(mcp.Tool{
|
||||
Name: "web_drag_and_drop_cremotemcp",
|
||||
Description: "Perform drag and drop operation from source element to target element",
|
||||
InputSchema: mcp.ToolInputSchema{
|
||||
Type: "object",
|
||||
Properties: map[string]any{
|
||||
"source": map[string]any{
|
||||
"type": "string",
|
||||
"description": "CSS selector for the source element to drag",
|
||||
},
|
||||
"target": map[string]any{
|
||||
"type": "string",
|
||||
"description": "CSS selector for the target element to drop on",
|
||||
},
|
||||
"tab": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Tab ID (optional, uses current tab)",
|
||||
},
|
||||
"timeout": map[string]any{
|
||||
"type": "integer",
|
||||
"description": "Timeout in seconds (default: 5)",
|
||||
"default": 5,
|
||||
},
|
||||
},
|
||||
Required: []string{"source", "target"},
|
||||
},
|
||||
}, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
// Convert arguments to map
|
||||
params, ok := request.Params.Arguments.(map[string]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid arguments format")
|
||||
}
|
||||
|
||||
source := getStringParam(params, "source", "")
|
||||
target := getStringParam(params, "target", "")
|
||||
tab := getStringParam(params, "tab", "")
|
||||
timeout := getIntParam(params, "timeout", 5)
|
||||
|
||||
if source == "" {
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent("Error: source parameter is required"),
|
||||
},
|
||||
IsError: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
if target == "" {
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent("Error: target parameter is required"),
|
||||
},
|
||||
IsError: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
err := cremoteServer.client.DragAndDrop(tab, source, target, timeout)
|
||||
if err != nil {
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent(fmt.Sprintf("Error performing drag and drop: %v", err)),
|
||||
},
|
||||
IsError: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent(fmt.Sprintf("Drag and drop completed successfully from %s to %s", source, target)),
|
||||
},
|
||||
IsError: false,
|
||||
}, nil
|
||||
})
|
||||
|
||||
mcpServer.AddTool(mcp.Tool{
|
||||
Name: "web_drag_and_drop_coordinates_cremotemcp",
|
||||
Description: "Perform drag and drop operation from source element to specific coordinates",
|
||||
InputSchema: mcp.ToolInputSchema{
|
||||
Type: "object",
|
||||
Properties: map[string]any{
|
||||
"source": map[string]any{
|
||||
"type": "string",
|
||||
"description": "CSS selector for the source element to drag",
|
||||
},
|
||||
"x": map[string]any{
|
||||
"type": "integer",
|
||||
"description": "Target X coordinate",
|
||||
},
|
||||
"y": map[string]any{
|
||||
"type": "integer",
|
||||
"description": "Target Y coordinate",
|
||||
},
|
||||
"tab": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Tab ID (optional, uses current tab)",
|
||||
},
|
||||
"timeout": map[string]any{
|
||||
"type": "integer",
|
||||
"description": "Timeout in seconds (default: 5)",
|
||||
"default": 5,
|
||||
},
|
||||
},
|
||||
Required: []string{"source", "x", "y"},
|
||||
},
|
||||
}, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
// Convert arguments to map
|
||||
params, ok := request.Params.Arguments.(map[string]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid arguments format")
|
||||
}
|
||||
|
||||
source := getStringParam(params, "source", "")
|
||||
x := getIntParam(params, "x", 0)
|
||||
y := getIntParam(params, "y", 0)
|
||||
tab := getStringParam(params, "tab", "")
|
||||
timeout := getIntParam(params, "timeout", 5)
|
||||
|
||||
if source == "" {
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent("Error: source parameter is required"),
|
||||
},
|
||||
IsError: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
err := cremoteServer.client.DragAndDropToCoordinates(tab, source, x, y, timeout)
|
||||
if err != nil {
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent(fmt.Sprintf("Error performing drag and drop to coordinates: %v", err)),
|
||||
},
|
||||
IsError: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent(fmt.Sprintf("Drag and drop to coordinates (%d, %d) completed successfully from %s", x, y, source)),
|
||||
},
|
||||
IsError: false,
|
||||
}, nil
|
||||
})
|
||||
|
||||
mcpServer.AddTool(mcp.Tool{
|
||||
Name: "web_drag_and_drop_offset_cremotemcp",
|
||||
Description: "Perform drag and drop operation from source element by relative offset",
|
||||
InputSchema: mcp.ToolInputSchema{
|
||||
Type: "object",
|
||||
Properties: map[string]any{
|
||||
"source": map[string]any{
|
||||
"type": "string",
|
||||
"description": "CSS selector for the source element to drag",
|
||||
},
|
||||
"offset_x": map[string]any{
|
||||
"type": "integer",
|
||||
"description": "Horizontal offset in pixels",
|
||||
},
|
||||
"offset_y": map[string]any{
|
||||
"type": "integer",
|
||||
"description": "Vertical offset in pixels",
|
||||
},
|
||||
"tab": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Tab ID (optional, uses current tab)",
|
||||
},
|
||||
"timeout": map[string]any{
|
||||
"type": "integer",
|
||||
"description": "Timeout in seconds (default: 5)",
|
||||
"default": 5,
|
||||
},
|
||||
},
|
||||
Required: []string{"source", "offset_x", "offset_y"},
|
||||
},
|
||||
}, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
// Convert arguments to map
|
||||
params, ok := request.Params.Arguments.(map[string]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid arguments format")
|
||||
}
|
||||
|
||||
source := getStringParam(params, "source", "")
|
||||
offsetX := getIntParam(params, "offset_x", 0)
|
||||
offsetY := getIntParam(params, "offset_y", 0)
|
||||
tab := getStringParam(params, "tab", "")
|
||||
timeout := getIntParam(params, "timeout", 5)
|
||||
|
||||
if source == "" {
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent("Error: source parameter is required"),
|
||||
},
|
||||
IsError: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
err := cremoteServer.client.DragAndDropByOffset(tab, source, offsetX, offsetY, timeout)
|
||||
if err != nil {
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent(fmt.Sprintf("Error performing drag and drop by offset: %v", err)),
|
||||
},
|
||||
IsError: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent(fmt.Sprintf("Drag and drop by offset (%d, %d) completed successfully from %s", offsetX, offsetY, source)),
|
||||
},
|
||||
IsError: false,
|
||||
}, nil
|
||||
})
|
||||
|
||||
// Register web_right_click tool
|
||||
mcpServer.AddTool(mcp.Tool{
|
||||
Name: "web_right_click_cremotemcp",
|
||||
Description: "Right-click on an element",
|
||||
InputSchema: mcp.ToolInputSchema{
|
||||
Type: "object",
|
||||
Properties: map[string]any{
|
||||
"selector": map[string]any{
|
||||
"type": "string",
|
||||
"description": "CSS selector for the element to right-click",
|
||||
},
|
||||
"tab": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Tab ID (optional, uses current tab)",
|
||||
},
|
||||
"timeout": map[string]any{
|
||||
"type": "integer",
|
||||
"description": "Timeout in seconds (default: 5)",
|
||||
"default": 5,
|
||||
},
|
||||
},
|
||||
Required: []string{"selector"},
|
||||
},
|
||||
}, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
params, ok := request.Params.Arguments.(map[string]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid arguments format")
|
||||
}
|
||||
|
||||
selector, ok := params["selector"].(string)
|
||||
if !ok || selector == "" {
|
||||
return nil, fmt.Errorf("selector is required")
|
||||
}
|
||||
|
||||
tab := ""
|
||||
if tabParam, exists := params["tab"]; exists {
|
||||
if tabStr, ok := tabParam.(string); ok {
|
||||
tab = tabStr
|
||||
}
|
||||
}
|
||||
|
||||
timeout := 5
|
||||
if timeoutParam, exists := params["timeout"]; exists {
|
||||
if timeoutFloat, ok := timeoutParam.(float64); ok {
|
||||
timeout = int(timeoutFloat)
|
||||
}
|
||||
}
|
||||
|
||||
err := cremoteServer.client.RightClick(tab, selector, timeout)
|
||||
if err != nil {
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent(fmt.Sprintf("Failed to right-click: %v", err)),
|
||||
},
|
||||
IsError: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent("Right-click completed successfully"),
|
||||
},
|
||||
IsError: false,
|
||||
}, nil
|
||||
})
|
||||
|
||||
// Register web_double_click tool
|
||||
mcpServer.AddTool(mcp.Tool{
|
||||
Name: "web_double_click_cremotemcp",
|
||||
Description: "Double-click on an element",
|
||||
InputSchema: mcp.ToolInputSchema{
|
||||
Type: "object",
|
||||
Properties: map[string]any{
|
||||
"selector": map[string]any{
|
||||
"type": "string",
|
||||
"description": "CSS selector for the element to double-click",
|
||||
},
|
||||
"tab": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Tab ID (optional, uses current tab)",
|
||||
},
|
||||
"timeout": map[string]any{
|
||||
"type": "integer",
|
||||
"description": "Timeout in seconds (default: 5)",
|
||||
"default": 5,
|
||||
},
|
||||
},
|
||||
Required: []string{"selector"},
|
||||
},
|
||||
}, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
params, ok := request.Params.Arguments.(map[string]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid arguments format")
|
||||
}
|
||||
|
||||
selector, ok := params["selector"].(string)
|
||||
if !ok || selector == "" {
|
||||
return nil, fmt.Errorf("selector is required")
|
||||
}
|
||||
|
||||
tab := ""
|
||||
if tabParam, exists := params["tab"]; exists {
|
||||
if tabStr, ok := tabParam.(string); ok {
|
||||
tab = tabStr
|
||||
}
|
||||
}
|
||||
|
||||
timeout := 5
|
||||
if timeoutParam, exists := params["timeout"]; exists {
|
||||
if timeoutFloat, ok := timeoutParam.(float64); ok {
|
||||
timeout = int(timeoutFloat)
|
||||
}
|
||||
}
|
||||
|
||||
err := cremoteServer.client.DoubleClick(tab, selector, timeout)
|
||||
if err != nil {
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent(fmt.Sprintf("Failed to double-click: %v", err)),
|
||||
},
|
||||
IsError: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent("Double-click completed successfully"),
|
||||
},
|
||||
IsError: false,
|
||||
}, nil
|
||||
})
|
||||
|
||||
// Register web_hover tool
|
||||
mcpServer.AddTool(mcp.Tool{
|
||||
Name: "web_hover_cremotemcp",
|
||||
Description: "Hover over an element",
|
||||
InputSchema: mcp.ToolInputSchema{
|
||||
Type: "object",
|
||||
Properties: map[string]any{
|
||||
"selector": map[string]any{
|
||||
"type": "string",
|
||||
"description": "CSS selector for the element to hover over",
|
||||
},
|
||||
"tab": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Tab ID (optional, uses current tab)",
|
||||
},
|
||||
"timeout": map[string]any{
|
||||
"type": "integer",
|
||||
"description": "Timeout in seconds (default: 5)",
|
||||
"default": 5,
|
||||
},
|
||||
},
|
||||
Required: []string{"selector"},
|
||||
},
|
||||
}, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
params, ok := request.Params.Arguments.(map[string]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid arguments format")
|
||||
}
|
||||
|
||||
selector, ok := params["selector"].(string)
|
||||
if !ok || selector == "" {
|
||||
return nil, fmt.Errorf("selector is required")
|
||||
}
|
||||
|
||||
tab := ""
|
||||
if tabParam, exists := params["tab"]; exists {
|
||||
if tabStr, ok := tabParam.(string); ok {
|
||||
tab = tabStr
|
||||
}
|
||||
}
|
||||
|
||||
timeout := 5
|
||||
if timeoutParam, exists := params["timeout"]; exists {
|
||||
if timeoutFloat, ok := timeoutParam.(float64); ok {
|
||||
timeout = int(timeoutFloat)
|
||||
}
|
||||
}
|
||||
|
||||
err := cremoteServer.client.Hover(tab, selector, timeout)
|
||||
if err != nil {
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent(fmt.Sprintf("Failed to hover: %v", err)),
|
||||
},
|
||||
IsError: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent("Hover completed successfully"),
|
||||
},
|
||||
IsError: false,
|
||||
}, nil
|
||||
})
|
||||
|
||||
// Register web_middle_click tool
|
||||
mcpServer.AddTool(mcp.Tool{
|
||||
Name: "web_middle_click_cremotemcp",
|
||||
Description: "Middle-click on an element (typically opens links in new tabs)",
|
||||
InputSchema: mcp.ToolInputSchema{
|
||||
Type: "object",
|
||||
Properties: map[string]any{
|
||||
"selector": map[string]any{
|
||||
"type": "string",
|
||||
"description": "CSS selector for the element to middle-click",
|
||||
},
|
||||
"tab": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Tab ID (optional, uses current tab)",
|
||||
},
|
||||
"timeout": map[string]any{
|
||||
"type": "integer",
|
||||
"description": "Timeout in seconds (default: 5)",
|
||||
"default": 5,
|
||||
},
|
||||
},
|
||||
Required: []string{"selector"},
|
||||
},
|
||||
}, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
params, ok := request.Params.Arguments.(map[string]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid arguments format")
|
||||
}
|
||||
|
||||
selector, ok := params["selector"].(string)
|
||||
if !ok || selector == "" {
|
||||
return nil, fmt.Errorf("selector is required")
|
||||
}
|
||||
|
||||
tab := ""
|
||||
if tabParam, exists := params["tab"]; exists {
|
||||
if tabStr, ok := tabParam.(string); ok {
|
||||
tab = tabStr
|
||||
}
|
||||
}
|
||||
|
||||
timeout := 5
|
||||
if timeoutParam, exists := params["timeout"]; exists {
|
||||
if timeoutFloat, ok := timeoutParam.(float64); ok {
|
||||
timeout = int(timeoutFloat)
|
||||
}
|
||||
}
|
||||
|
||||
err := cremoteServer.client.MiddleClick(tab, selector, timeout)
|
||||
if err != nil {
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent(fmt.Sprintf("Failed to middle-click: %v", err)),
|
||||
},
|
||||
IsError: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent("Middle-click completed successfully"),
|
||||
},
|
||||
IsError: false,
|
||||
}, nil
|
||||
})
|
||||
|
||||
// Register web_mouse_move tool
|
||||
mcpServer.AddTool(mcp.Tool{
|
||||
Name: "web_mouse_move_cremotemcp",
|
||||
Description: "Move mouse to specific coordinates without clicking",
|
||||
InputSchema: mcp.ToolInputSchema{
|
||||
Type: "object",
|
||||
Properties: map[string]any{
|
||||
"x": map[string]any{
|
||||
"type": "integer",
|
||||
"description": "X coordinate to move mouse to",
|
||||
},
|
||||
"y": map[string]any{
|
||||
"type": "integer",
|
||||
"description": "Y coordinate to move mouse to",
|
||||
},
|
||||
"tab": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Tab ID (optional, uses current tab)",
|
||||
},
|
||||
"timeout": map[string]any{
|
||||
"type": "integer",
|
||||
"description": "Timeout in seconds (default: 5)",
|
||||
"default": 5,
|
||||
},
|
||||
},
|
||||
Required: []string{"x", "y"},
|
||||
},
|
||||
}, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
params, ok := request.Params.Arguments.(map[string]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid arguments format")
|
||||
}
|
||||
|
||||
xFloat, ok := params["x"].(float64)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("x coordinate is required")
|
||||
}
|
||||
x := int(xFloat)
|
||||
|
||||
yFloat, ok := params["y"].(float64)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("y coordinate is required")
|
||||
}
|
||||
y := int(yFloat)
|
||||
|
||||
tab := ""
|
||||
if tabParam, exists := params["tab"]; exists {
|
||||
if tabStr, ok := tabParam.(string); ok {
|
||||
tab = tabStr
|
||||
}
|
||||
}
|
||||
|
||||
timeout := 5
|
||||
if timeoutParam, exists := params["timeout"]; exists {
|
||||
if timeoutFloat, ok := timeoutParam.(float64); ok {
|
||||
timeout = int(timeoutFloat)
|
||||
}
|
||||
}
|
||||
|
||||
err := cremoteServer.client.MouseMove(tab, x, y, timeout)
|
||||
if err != nil {
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent(fmt.Sprintf("Failed to move mouse: %v", err)),
|
||||
},
|
||||
IsError: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent(fmt.Sprintf("Mouse moved to coordinates (%d, %d) successfully", x, y)),
|
||||
},
|
||||
IsError: false,
|
||||
}, nil
|
||||
})
|
||||
|
||||
// Register web_scroll_wheel tool
|
||||
mcpServer.AddTool(mcp.Tool{
|
||||
Name: "web_scroll_wheel_cremotemcp",
|
||||
Description: "Scroll with mouse wheel at specific coordinates",
|
||||
InputSchema: mcp.ToolInputSchema{
|
||||
Type: "object",
|
||||
Properties: map[string]any{
|
||||
"x": map[string]any{
|
||||
"type": "integer",
|
||||
"description": "X coordinate for scroll wheel",
|
||||
},
|
||||
"y": map[string]any{
|
||||
"type": "integer",
|
||||
"description": "Y coordinate for scroll wheel",
|
||||
},
|
||||
"delta_x": map[string]any{
|
||||
"type": "integer",
|
||||
"description": "Horizontal scroll delta (negative = left, positive = right)",
|
||||
"default": 0,
|
||||
},
|
||||
"delta_y": map[string]any{
|
||||
"type": "integer",
|
||||
"description": "Vertical scroll delta (negative = up, positive = down)",
|
||||
},
|
||||
"tab": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Tab ID (optional, uses current tab)",
|
||||
},
|
||||
"timeout": map[string]any{
|
||||
"type": "integer",
|
||||
"description": "Timeout in seconds (default: 5)",
|
||||
"default": 5,
|
||||
},
|
||||
},
|
||||
Required: []string{"x", "y", "delta_y"},
|
||||
},
|
||||
}, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
params, ok := request.Params.Arguments.(map[string]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid arguments format")
|
||||
}
|
||||
|
||||
xFloat, ok := params["x"].(float64)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("x coordinate is required")
|
||||
}
|
||||
x := int(xFloat)
|
||||
|
||||
yFloat, ok := params["y"].(float64)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("y coordinate is required")
|
||||
}
|
||||
y := int(yFloat)
|
||||
|
||||
deltaXFloat := float64(0)
|
||||
if deltaXParam, exists := params["delta_x"]; exists {
|
||||
if deltaXVal, ok := deltaXParam.(float64); ok {
|
||||
deltaXFloat = deltaXVal
|
||||
}
|
||||
}
|
||||
deltaX := int(deltaXFloat)
|
||||
|
||||
deltaYFloat, ok := params["delta_y"].(float64)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("delta_y is required")
|
||||
}
|
||||
deltaY := int(deltaYFloat)
|
||||
|
||||
tab := ""
|
||||
if tabParam, exists := params["tab"]; exists {
|
||||
if tabStr, ok := tabParam.(string); ok {
|
||||
tab = tabStr
|
||||
}
|
||||
}
|
||||
|
||||
timeout := 5
|
||||
if timeoutParam, exists := params["timeout"]; exists {
|
||||
if timeoutFloat, ok := timeoutParam.(float64); ok {
|
||||
timeout = int(timeoutFloat)
|
||||
}
|
||||
}
|
||||
|
||||
err := cremoteServer.client.ScrollWheel(tab, x, y, deltaX, deltaY, timeout)
|
||||
if err != nil {
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent(fmt.Sprintf("Failed to scroll with mouse wheel: %v", err)),
|
||||
},
|
||||
IsError: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent(fmt.Sprintf("Mouse wheel scroll at (%d, %d) with delta (%d, %d) completed successfully", x, y, deltaX, deltaY)),
|
||||
},
|
||||
IsError: false,
|
||||
}, nil
|
||||
})
|
||||
|
||||
// Register web_key_combination tool
|
||||
mcpServer.AddTool(mcp.Tool{
|
||||
Name: "web_key_combination_cremotemcp",
|
||||
Description: "Send key combinations like Ctrl+C, Alt+Tab, Shift+Enter, etc.",
|
||||
InputSchema: mcp.ToolInputSchema{
|
||||
Type: "object",
|
||||
Properties: map[string]any{
|
||||
"keys": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Key combination to send (e.g., 'Ctrl+C', 'Alt+Tab', 'Shift+Enter', 'Ctrl+Shift+T')",
|
||||
},
|
||||
"tab": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Tab ID (optional, uses current tab)",
|
||||
},
|
||||
"timeout": map[string]any{
|
||||
"type": "integer",
|
||||
"description": "Timeout in seconds (default: 5)",
|
||||
"default": 5,
|
||||
},
|
||||
},
|
||||
Required: []string{"keys"},
|
||||
},
|
||||
}, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
params, ok := request.Params.Arguments.(map[string]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid arguments format")
|
||||
}
|
||||
|
||||
keys, ok := params["keys"].(string)
|
||||
if !ok || keys == "" {
|
||||
return nil, fmt.Errorf("keys parameter is required")
|
||||
}
|
||||
|
||||
tab := ""
|
||||
if tabParam, exists := params["tab"]; exists {
|
||||
if tabStr, ok := tabParam.(string); ok {
|
||||
tab = tabStr
|
||||
}
|
||||
}
|
||||
|
||||
timeout := 5
|
||||
if timeoutParam, exists := params["timeout"]; exists {
|
||||
if timeoutFloat, ok := timeoutParam.(float64); ok {
|
||||
timeout = int(timeoutFloat)
|
||||
}
|
||||
}
|
||||
|
||||
err := cremoteServer.client.KeyCombination(tab, keys, timeout)
|
||||
if err != nil {
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent(fmt.Sprintf("Failed to send key combination: %v", err)),
|
||||
},
|
||||
IsError: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent(fmt.Sprintf("Key combination '%s' sent successfully", keys)),
|
||||
},
|
||||
IsError: false,
|
||||
}, nil
|
||||
})
|
||||
|
||||
// Register web_special_key tool
|
||||
mcpServer.AddTool(mcp.Tool{
|
||||
Name: "web_special_key_cremotemcp",
|
||||
Description: "Send special keys like Enter, Escape, Tab, F1-F12, Arrow keys, etc.",
|
||||
InputSchema: mcp.ToolInputSchema{
|
||||
Type: "object",
|
||||
Properties: map[string]any{
|
||||
"key": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Special key to send (e.g., 'Enter', 'Escape', 'Tab', 'F1', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End', 'PageUp', 'PageDown', 'Delete', 'Backspace')",
|
||||
},
|
||||
"tab": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Tab ID (optional, uses current tab)",
|
||||
},
|
||||
"timeout": map[string]any{
|
||||
"type": "integer",
|
||||
"description": "Timeout in seconds (default: 5)",
|
||||
"default": 5,
|
||||
},
|
||||
},
|
||||
Required: []string{"key"},
|
||||
},
|
||||
}, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
params, ok := request.Params.Arguments.(map[string]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid arguments format")
|
||||
}
|
||||
|
||||
key, ok := params["key"].(string)
|
||||
if !ok || key == "" {
|
||||
return nil, fmt.Errorf("key parameter is required")
|
||||
}
|
||||
|
||||
tab := ""
|
||||
if tabParam, exists := params["tab"]; exists {
|
||||
if tabStr, ok := tabParam.(string); ok {
|
||||
tab = tabStr
|
||||
}
|
||||
}
|
||||
|
||||
timeout := 5
|
||||
if timeoutParam, exists := params["timeout"]; exists {
|
||||
if timeoutFloat, ok := timeoutParam.(float64); ok {
|
||||
timeout = int(timeoutFloat)
|
||||
}
|
||||
}
|
||||
|
||||
err := cremoteServer.client.SpecialKey(tab, key, timeout)
|
||||
if err != nil {
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent(fmt.Sprintf("Failed to send special key: %v", err)),
|
||||
},
|
||||
IsError: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent(fmt.Sprintf("Special key '%s' sent successfully", key)),
|
||||
},
|
||||
IsError: false,
|
||||
}, nil
|
||||
})
|
||||
|
||||
// Register web_modifier_click tool
|
||||
mcpServer.AddTool(mcp.Tool{
|
||||
Name: "web_modifier_click_cremotemcp",
|
||||
Description: "Click on an element with modifier keys (Ctrl+click, Shift+click, Alt+click, etc.)",
|
||||
InputSchema: mcp.ToolInputSchema{
|
||||
Type: "object",
|
||||
Properties: map[string]any{
|
||||
"selector": map[string]any{
|
||||
"type": "string",
|
||||
"description": "CSS selector for the element to click",
|
||||
},
|
||||
"modifiers": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Modifier keys to hold while clicking (e.g., 'Ctrl', 'Shift', 'Alt', 'Ctrl+Shift', 'Ctrl+Alt')",
|
||||
},
|
||||
"tab": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Tab ID (optional, uses current tab)",
|
||||
},
|
||||
"timeout": map[string]any{
|
||||
"type": "integer",
|
||||
"description": "Timeout in seconds (default: 5)",
|
||||
"default": 5,
|
||||
},
|
||||
},
|
||||
Required: []string{"selector", "modifiers"},
|
||||
},
|
||||
}, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
params, ok := request.Params.Arguments.(map[string]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid arguments format")
|
||||
}
|
||||
|
||||
selector, ok := params["selector"].(string)
|
||||
if !ok || selector == "" {
|
||||
return nil, fmt.Errorf("selector is required")
|
||||
}
|
||||
|
||||
modifiers, ok := params["modifiers"].(string)
|
||||
if !ok || modifiers == "" {
|
||||
return nil, fmt.Errorf("modifiers parameter is required")
|
||||
}
|
||||
|
||||
tab := ""
|
||||
if tabParam, exists := params["tab"]; exists {
|
||||
if tabStr, ok := tabParam.(string); ok {
|
||||
tab = tabStr
|
||||
}
|
||||
}
|
||||
|
||||
timeout := 5
|
||||
if timeoutParam, exists := params["timeout"]; exists {
|
||||
if timeoutFloat, ok := timeoutParam.(float64); ok {
|
||||
timeout = int(timeoutFloat)
|
||||
}
|
||||
}
|
||||
|
||||
err := cremoteServer.client.ModifierClick(tab, selector, modifiers, timeout)
|
||||
if err != nil {
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent(fmt.Sprintf("Failed to perform modifier click: %v", err)),
|
||||
},
|
||||
IsError: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent(fmt.Sprintf("Modifier click with '%s' on %s completed successfully", modifiers, selector)),
|
||||
},
|
||||
IsError: false,
|
||||
}, nil
|
||||
})
|
||||
|
||||
// Start the server
|
||||
log.Printf("Cremote MCP server ready")
|
||||
if err := server.ServeStdio(mcpServer); err != nil {
|
||||
|
||||
244
mcp_drag_and_drop_examples.md
Normal file
244
mcp_drag_and_drop_examples.md
Normal file
@@ -0,0 +1,244 @@
|
||||
# MCP Drag and Drop Test Examples
|
||||
|
||||
This document provides examples of how to use the new drag and drop tools through the MCP interface.
|
||||
|
||||
## Overview
|
||||
|
||||
Cremote now supports three types of drag and drop operations:
|
||||
|
||||
1. **Element to Element**: Drag from one element to another element
|
||||
2. **Element to Coordinates**: Drag from an element to specific x,y coordinates
|
||||
3. **Element by Offset**: Drag from an element by a relative pixel offset
|
||||
|
||||
## Example 1: Basic Drag and Drop Between Elements
|
||||
|
||||
```yaml
|
||||
# Navigate to a page with draggable elements
|
||||
web_navigate_cremotemcp:
|
||||
url: "https://www.w3schools.com/html/html5_draganddrop.asp"
|
||||
timeout: 10
|
||||
|
||||
# Drag an image from one div to another
|
||||
web_drag_and_drop_cremotemcp:
|
||||
source: "#drag1"
|
||||
target: "#div2"
|
||||
timeout: 10
|
||||
|
||||
# Take a screenshot to verify the result
|
||||
web_screenshot_cremotemcp:
|
||||
output: "/tmp/drag-drop-result.png"
|
||||
```
|
||||
|
||||
## Example 2: Sortable List Testing
|
||||
|
||||
```yaml
|
||||
# Navigate to a sortable list demo
|
||||
web_navigate_cremotemcp:
|
||||
url: "https://jqueryui.com/sortable/"
|
||||
|
||||
# Switch to the demo iframe
|
||||
web_iframe_cremotemcp:
|
||||
action: "enter"
|
||||
selector: ".demo-frame"
|
||||
|
||||
# Drag the first item to the third position
|
||||
web_drag_and_drop_cremotemcp:
|
||||
source: "#sortable li:first-child"
|
||||
target: "#sortable li:nth-child(3)"
|
||||
timeout: 10
|
||||
|
||||
# Switch back to main frame
|
||||
web_iframe_cremotemcp:
|
||||
action: "exit"
|
||||
|
||||
# Take a screenshot
|
||||
web_screenshot_cremotemcp:
|
||||
output: "/tmp/sortable-test.png"
|
||||
```
|
||||
|
||||
## Example 3: Kanban Board Testing
|
||||
|
||||
```yaml
|
||||
# Navigate to a kanban board application
|
||||
web_navigate_cremotemcp:
|
||||
url: "https://example.com/kanban-board"
|
||||
|
||||
# Drag a card from "To Do" to "In Progress"
|
||||
web_drag_and_drop_cremotemcp:
|
||||
source: ".todo-column .card:first-child"
|
||||
target: ".in-progress-column"
|
||||
timeout: 15
|
||||
|
||||
# Verify the card moved by checking the target column
|
||||
web_element_check_cremotemcp:
|
||||
selector: ".in-progress-column .card"
|
||||
check_type: "exists"
|
||||
|
||||
# Take a screenshot of the updated board
|
||||
web_screenshot_cremotemcp:
|
||||
output: "/tmp/kanban-board-updated.png"
|
||||
```
|
||||
|
||||
## Example 4: Drag and Drop to Specific Coordinates
|
||||
|
||||
```yaml
|
||||
# Navigate to a drawing or design application
|
||||
web_navigate_cremotemcp:
|
||||
url: "https://example.com/design-tool"
|
||||
|
||||
# Drag an element to a specific position on the canvas
|
||||
web_drag_and_drop_coordinates_cremotemcp:
|
||||
source: ".toolbar .shape-tool"
|
||||
x: 400
|
||||
y: 300
|
||||
timeout: 10
|
||||
|
||||
# Drag another element to a different position
|
||||
web_drag_and_drop_coordinates_cremotemcp:
|
||||
source: ".toolbar .text-tool"
|
||||
x: 200
|
||||
y: 150
|
||||
timeout: 10
|
||||
|
||||
# Take a screenshot of the canvas
|
||||
web_screenshot_cremotemcp:
|
||||
output: "/tmp/design-canvas.png"
|
||||
```
|
||||
|
||||
## Example 5: File Upload via Drag and Drop
|
||||
|
||||
```yaml
|
||||
# Navigate to a file upload page
|
||||
web_navigate_cremotemcp:
|
||||
url: "https://example.com/file-upload"
|
||||
|
||||
# First, upload a file to the container (if needed)
|
||||
file_upload_cremotemcp:
|
||||
local_path: "/home/user/test-file.pdf"
|
||||
container_path: "/tmp/test-file.pdf"
|
||||
|
||||
# Simulate dragging a file to the upload area
|
||||
# Note: This simulates the drag gesture, actual file upload may require different handling
|
||||
web_drag_and_drop_cremotemcp:
|
||||
source: ".file-selector"
|
||||
target: ".upload-drop-zone"
|
||||
timeout: 15
|
||||
|
||||
# Check if upload was successful
|
||||
web_element_check_cremotemcp:
|
||||
selector: ".upload-success"
|
||||
check_type: "visible"
|
||||
```
|
||||
|
||||
## Example 6: Dashboard Widget Rearrangement
|
||||
|
||||
```yaml
|
||||
# Navigate to a dashboard
|
||||
web_navigate_cremotemcp:
|
||||
url: "https://example.com/dashboard"
|
||||
|
||||
# Drag a widget by relative offset to rearrange layout
|
||||
web_drag_and_drop_offset_cremotemcp:
|
||||
source: "#widget-sales"
|
||||
offset_x: 200
|
||||
offset_y: 0
|
||||
timeout: 10
|
||||
|
||||
# Drag another widget to a different position
|
||||
web_drag_and_drop_offset_cremotemcp:
|
||||
source: "#widget-analytics"
|
||||
offset_x: -150
|
||||
offset_y: 100
|
||||
timeout: 10
|
||||
|
||||
# Take a screenshot of the rearranged dashboard
|
||||
web_screenshot_cremotemcp:
|
||||
output: "/tmp/dashboard-rearranged.png"
|
||||
```
|
||||
|
||||
## Example 7: Form Builder Testing
|
||||
|
||||
```yaml
|
||||
# Navigate to a form builder application
|
||||
web_navigate_cremotemcp:
|
||||
url: "https://example.com/form-builder"
|
||||
|
||||
# Drag a text input from the toolbox to the form area
|
||||
web_drag_and_drop_cremotemcp:
|
||||
source: ".toolbox .text-input"
|
||||
target: ".form-canvas"
|
||||
timeout: 10
|
||||
|
||||
# Drag a button element to the form
|
||||
web_drag_and_drop_cremotemcp:
|
||||
source: ".toolbox .button-element"
|
||||
target: ".form-canvas"
|
||||
timeout: 10
|
||||
|
||||
# Rearrange elements by dragging the button below the text input
|
||||
web_drag_and_drop_cremotemcp:
|
||||
source: ".form-canvas .button-element"
|
||||
target: ".form-canvas .text-input"
|
||||
timeout: 10
|
||||
|
||||
# Take a screenshot of the built form
|
||||
web_screenshot_cremotemcp:
|
||||
output: "/tmp/form-builder-result.png"
|
||||
```
|
||||
|
||||
## Example 8: Game Testing - Puzzle Game
|
||||
|
||||
```yaml
|
||||
# Navigate to a puzzle game
|
||||
web_navigate_cremotemcp:
|
||||
url: "https://example.com/puzzle-game"
|
||||
|
||||
# Drag puzzle pieces to solve the puzzle
|
||||
web_drag_and_drop_cremotemcp:
|
||||
source: "#piece-1"
|
||||
target: "#slot-1"
|
||||
timeout: 10
|
||||
|
||||
web_drag_and_drop_cremotemcp:
|
||||
source: "#piece-2"
|
||||
target: "#slot-2"
|
||||
timeout: 10
|
||||
|
||||
# Check if puzzle is solved
|
||||
web_element_check_cremotemcp:
|
||||
selector: ".puzzle-solved"
|
||||
check_type: "visible"
|
||||
|
||||
# Take a screenshot of the completed puzzle
|
||||
web_screenshot_cremotemcp:
|
||||
output: "/tmp/puzzle-completed.png"
|
||||
```
|
||||
|
||||
## Integration with Other Tools
|
||||
|
||||
Drag and drop works seamlessly with other cremote MCP tools:
|
||||
|
||||
- Use with `web_element_check_cremotemcp` to verify drag results
|
||||
- Combine with `web_screenshot_cremotemcp` for visual verification
|
||||
- Use with `web_navigate_cremotemcp` to test different drag and drop implementations
|
||||
- Integrate with `web_form_analyze_cremotemcp` for form builder testing
|
||||
- Use with `web_iframe_cremotemcp` for testing drag and drop in embedded content
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use appropriate timeouts** - Drag and drop operations may take longer than simple clicks
|
||||
2. **Verify results** - Always check that the drag and drop had the expected effect
|
||||
3. **Take screenshots** - Visual verification is important for drag and drop testing
|
||||
4. **Test different scenarios** - Try element-to-element, coordinates, and offset methods
|
||||
5. **Handle iframes** - Switch iframe context when testing embedded drag and drop widgets
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
- **File Upload Testing**: Drag files to upload areas
|
||||
- **Sortable List Testing**: Reorder items in lists
|
||||
- **Kanban Board Testing**: Move cards between columns
|
||||
- **Dashboard Testing**: Rearrange widgets and components
|
||||
- **Form Builder Testing**: Drag form elements to build layouts
|
||||
- **Game Testing**: Test drag-based game mechanics
|
||||
- **Image Gallery Testing**: Rearrange images or media
|
||||
- **UI Component Testing**: Test custom drag and drop components
|
||||
156
test_advanced_input.sh
Executable file
156
test_advanced_input.sh
Executable file
@@ -0,0 +1,156 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Test script for advanced input operations in cremote
|
||||
# This script demonstrates the new mouse and keyboard capabilities
|
||||
|
||||
set -e
|
||||
|
||||
echo "🚀 Testing Advanced Input Operations in Cremote"
|
||||
echo "=============================================="
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to print test status
|
||||
print_test() {
|
||||
echo -e "${BLUE}[TEST]${NC} $1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
print_info() {
|
||||
echo -e "${YELLOW}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
# Check if cremote is available
|
||||
if ! command -v ./cremote &> /dev/null; then
|
||||
print_error "cremote binary not found. Please build it first with: go build -o cremote ."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test basic help functionality
|
||||
print_test "Testing help for new commands"
|
||||
./cremote right-click -h > /dev/null 2>&1 && print_success "right-click help works"
|
||||
./cremote double-click -h > /dev/null 2>&1 && print_success "double-click help works"
|
||||
./cremote hover -h > /dev/null 2>&1 && print_success "hover help works"
|
||||
./cremote middle-click -h > /dev/null 2>&1 && print_success "middle-click help works"
|
||||
./cremote mouse-move -h > /dev/null 2>&1 && print_success "mouse-move help works"
|
||||
./cremote scroll-wheel -h > /dev/null 2>&1 && print_success "scroll-wheel help works"
|
||||
./cremote key-combination -h > /dev/null 2>&1 && print_success "key-combination help works"
|
||||
./cremote special-key -h > /dev/null 2>&1 && print_success "special-key help works"
|
||||
./cremote modifier-click -h > /dev/null 2>&1 && print_success "modifier-click help works"
|
||||
|
||||
echo ""
|
||||
print_info "All help commands are working correctly!"
|
||||
|
||||
echo ""
|
||||
print_test "Testing main usage message includes new commands"
|
||||
if ./cremote 2>&1 | grep -q "right-click"; then
|
||||
print_success "right-click appears in usage"
|
||||
else
|
||||
print_error "right-click missing from usage"
|
||||
fi
|
||||
|
||||
if ./cremote 2>&1 | grep -q "key-combination"; then
|
||||
print_success "key-combination appears in usage"
|
||||
else
|
||||
print_error "key-combination missing from usage"
|
||||
fi
|
||||
|
||||
if ./cremote 2>&1 | grep -q "modifier-click"; then
|
||||
print_success "modifier-click appears in usage"
|
||||
else
|
||||
print_error "modifier-click missing from usage"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
print_info "Usage message verification complete!"
|
||||
|
||||
echo ""
|
||||
print_test "Testing command parameter validation"
|
||||
|
||||
# Test missing required parameters
|
||||
if ./cremote right-click 2>&1 | grep -q "Usage of right-click"; then
|
||||
print_success "right-click shows usage when no parameters provided"
|
||||
fi
|
||||
|
||||
if ./cremote key-combination 2>&1 | grep -q "Usage of key-combination"; then
|
||||
print_success "key-combination shows usage when no parameters provided"
|
||||
fi
|
||||
|
||||
if ./cremote modifier-click 2>&1 | grep -q "Usage of modifier-click"; then
|
||||
print_success "modifier-click shows usage when no parameters provided"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
print_info "Parameter validation working correctly!"
|
||||
|
||||
echo ""
|
||||
print_test "Testing MCP server build"
|
||||
cd mcp
|
||||
if go build -o cremote-mcp . > /dev/null 2>&1; then
|
||||
print_success "MCP server builds successfully"
|
||||
cd ..
|
||||
else
|
||||
print_error "MCP server build failed"
|
||||
cd ..
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
print_info "All basic tests passed! 🎉"
|
||||
|
||||
echo ""
|
||||
echo "📋 Advanced Input Operations Summary:"
|
||||
echo "====================================="
|
||||
echo "✅ Mouse Operations:"
|
||||
echo " • right-click - Context menus"
|
||||
echo " • double-click - File operations, text selection"
|
||||
echo " • middle-click - Open links in new tabs"
|
||||
echo " • hover - Tooltips, dropdowns"
|
||||
echo " • mouse-move - Precise positioning"
|
||||
echo " • scroll-wheel - Mouse wheel scrolling"
|
||||
echo ""
|
||||
echo "✅ Keyboard Operations:"
|
||||
echo " • key-combination - Ctrl+C, Alt+Tab, Shift+Enter, etc."
|
||||
echo " • special-key - Enter, Escape, Tab, F1-F12, Arrow keys"
|
||||
echo " • modifier-click - Ctrl+click, Shift+click for multi-selection"
|
||||
echo ""
|
||||
echo "✅ MCP Tools Available:"
|
||||
echo " • web_right_click_cremotemcp"
|
||||
echo " • web_double_click_cremotemcp"
|
||||
echo " • web_hover_cremotemcp"
|
||||
echo " • web_middle_click_cremotemcp"
|
||||
echo " • web_mouse_move_cremotemcp"
|
||||
echo " • web_scroll_wheel_cremotemcp"
|
||||
echo " • web_key_combination_cremotemcp"
|
||||
echo " • web_special_key_cremotemcp"
|
||||
echo " • web_modifier_click_cremotemcp"
|
||||
echo ""
|
||||
echo "🎯 Use Cases Enabled:"
|
||||
echo " • Context menu testing"
|
||||
echo " • Keyboard navigation accessibility"
|
||||
echo " • Tooltip/dropdown interactions"
|
||||
echo " • Multi-selection workflows"
|
||||
echo " • Copy/paste operations"
|
||||
echo " • Precise mouse control"
|
||||
echo " • Advanced user interaction patterns"
|
||||
echo ""
|
||||
echo "🚀 Ready for comprehensive web application testing!"
|
||||
|
||||
echo ""
|
||||
print_info "To test with a real browser, start the daemon and Chrome:"
|
||||
echo " 1. Start Chrome: chromium --remote-debugging-port=9222 --user-data-dir=/tmp/chromium-debug"
|
||||
echo " 2. Start daemon: ./daemon/cremotedaemon"
|
||||
echo " 3. Test commands: ./cremote open-tab"
|
||||
echo " 4. Try new features: ./cremote right-click --selector='body'"
|
||||
120
test_drag_and_drop.sh
Executable file
120
test_drag_and_drop.sh
Executable file
@@ -0,0 +1,120 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Test script for cremote drag and drop functionality
|
||||
# This script demonstrates all three drag and drop commands
|
||||
|
||||
echo "🎯 Testing Cremote Drag and Drop Functionality"
|
||||
echo "=============================================="
|
||||
echo ""
|
||||
|
||||
# Check if cremote binary exists
|
||||
if [ ! -f "./cremote" ]; then
|
||||
echo "❌ Error: cremote binary not found. Please run 'go build -o cremote .' first"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if daemon is running
|
||||
echo "🔍 Checking if cremotedaemon is running..."
|
||||
if ! ./cremote status > /dev/null 2>&1; then
|
||||
echo "❌ Error: cremotedaemon is not running"
|
||||
echo "Please start the daemon first:"
|
||||
echo " 1. Start Chrome with debugging: chromium --remote-debugging-port=9222 --user-data-dir=/tmp/chromium-debug"
|
||||
echo " 2. Start daemon: cd daemon && ./cremotedaemon"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Daemon is running"
|
||||
echo ""
|
||||
|
||||
# Test 1: Basic drag and drop between elements
|
||||
echo "🎯 Test 1: Drag and drop between elements..."
|
||||
echo "Command: ./cremote drag-and-drop --source=\".draggable\" --target=\".drop-zone\" --timeout=10"
|
||||
echo ""
|
||||
echo "Note: This test requires a page with draggable elements."
|
||||
echo "You can test this on: https://www.w3schools.com/html/html5_draganddrop.asp"
|
||||
echo ""
|
||||
echo "Press Enter to continue or Ctrl+C to skip..."
|
||||
read -r
|
||||
|
||||
./cremote drag-and-drop --source="#drag1" --target="#div2" --timeout=10
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ Drag and drop between elements completed successfully"
|
||||
else
|
||||
echo "⚠️ Drag and drop between elements failed (this is expected if no suitable page is loaded)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Test 2: Drag and drop to coordinates
|
||||
echo "🎯 Test 2: Drag and drop to specific coordinates..."
|
||||
echo "Command: ./cremote drag-and-drop-coordinates --source=\".draggable\" --x=300 --y=200 --timeout=10"
|
||||
echo ""
|
||||
|
||||
./cremote drag-and-drop-coordinates --source="#drag1" --x=300 --y=200 --timeout=10
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ Drag and drop to coordinates completed successfully"
|
||||
else
|
||||
echo "⚠️ Drag and drop to coordinates failed (this is expected if no suitable page is loaded)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Test 3: Drag and drop by offset
|
||||
echo "🎯 Test 3: Drag and drop by relative offset..."
|
||||
echo "Command: ./cremote drag-and-drop-offset --source=\".draggable\" --offset-x=100 --offset-y=50 --timeout=10"
|
||||
echo ""
|
||||
|
||||
./cremote drag-and-drop-offset --source="#drag1" --offset-x=100 --offset-y=50 --timeout=10
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ Drag and drop by offset completed successfully"
|
||||
else
|
||||
echo "⚠️ Drag and drop by offset failed (this is expected if no suitable page is loaded)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Test 4: Help documentation
|
||||
echo "🎯 Test 4: Testing help documentation..."
|
||||
echo ""
|
||||
|
||||
echo "📖 drag-and-drop help:"
|
||||
./cremote drag-and-drop -h
|
||||
echo ""
|
||||
|
||||
echo "📖 drag-and-drop-coordinates help:"
|
||||
./cremote drag-and-drop-coordinates -h
|
||||
echo ""
|
||||
|
||||
echo "📖 drag-and-drop-offset help:"
|
||||
./cremote drag-and-drop-offset -h
|
||||
echo ""
|
||||
|
||||
echo "🎉 Drag and drop functionality tests completed!"
|
||||
echo ""
|
||||
echo "📋 Summary of new features:"
|
||||
echo " • drag-and-drop: Drag from source element to target element"
|
||||
echo " • drag-and-drop-coordinates: Drag from source element to specific x,y coordinates"
|
||||
echo " • drag-and-drop-offset: Drag from source element by relative pixel offset"
|
||||
echo ""
|
||||
echo "💡 Use cases:"
|
||||
echo " • File Upload: Drag files to upload areas"
|
||||
echo " • Sortable Lists: Reorder items in sortable lists"
|
||||
echo " • Kanban Boards: Move cards between columns"
|
||||
echo " • Image Galleries: Rearrange images or media"
|
||||
echo " • Form Builders: Drag form elements to build layouts"
|
||||
echo " • Dashboard Widgets: Rearrange dashboard components"
|
||||
echo " • Game Testing: Test drag-based game mechanics"
|
||||
echo " • UI Component Testing: Test custom drag and drop components"
|
||||
echo ""
|
||||
echo "🔧 Technical features:"
|
||||
echo " • Uses Chrome DevTools Protocol for precise mouse event simulation"
|
||||
echo " • Performs realistic drag operations with intermediate mouse movements"
|
||||
echo " • Calculates element center points automatically for accurate targeting"
|
||||
echo " • Supports timeout handling for slow or complex drag operations"
|
||||
echo " • Works with all modern drag and drop APIs"
|
||||
echo ""
|
||||
echo "🌐 Test pages you can use:"
|
||||
echo " • https://www.w3schools.com/html/html5_draganddrop.asp"
|
||||
echo " • https://jqueryui.com/droppable/"
|
||||
echo " • https://sortablejs.github.io/Sortable/"
|
||||
echo " • https://dragula.js.org/"
|
||||
Reference in New Issue
Block a user