This commit is contained in:
Josh at WLTechBlog
2025-09-30 14:11:27 -05:00
parent 86d1db55cd
commit a3c782eb24
11 changed files with 4904 additions and 6 deletions

112
README.md
View File

@@ -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:

View File

@@ -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

Binary file not shown.

File diff suppressed because it is too large Load Diff

293
main.go
View File

@@ -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:")

View File

@@ -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.

View File

@@ -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

View File

@@ -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 {

View 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
View 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
View 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/"