mcp iframe updates
This commit is contained in:
parent
cf34368901
commit
e1f2c45c3a
|
@ -527,7 +527,8 @@ func (c *Client) TakeScreenshot(tabID, outputPath string, fullPage bool, timeout
|
|||
|
||||
// SwitchToIframe switches the context to an iframe for subsequent commands
|
||||
// If tabID is empty, the current tab will be used
|
||||
func (c *Client) SwitchToIframe(tabID, selector string) error {
|
||||
// timeout is in seconds, 0 means no timeout
|
||||
func (c *Client) SwitchToIframe(tabID, selector string, timeout int) error {
|
||||
params := map[string]string{
|
||||
"selector": selector,
|
||||
}
|
||||
|
@ -537,6 +538,11 @@ func (c *Client) SwitchToIframe(tabID, selector string) error {
|
|||
params["tab"] = tabID
|
||||
}
|
||||
|
||||
// Add timeout if specified
|
||||
if timeout > 0 {
|
||||
params["timeout"] = strconv.Itoa(timeout)
|
||||
}
|
||||
|
||||
resp, err := c.SendCommand("switch-iframe", params)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
134
daemon/daemon.go
134
daemon/daemon.go
|
@ -462,8 +462,17 @@ func (d *Daemon) handleCommand(w http.ResponseWriter, r *http.Request) {
|
|||
case "switch-iframe":
|
||||
tabID := cmd.Params["tab"]
|
||||
selector := cmd.Params["selector"]
|
||||
timeoutStr := cmd.Params["timeout"]
|
||||
|
||||
err := d.switchToIframe(tabID, selector)
|
||||
// Parse timeout (default to 5 seconds if not specified)
|
||||
timeout := 5
|
||||
if timeoutStr != "" {
|
||||
if parsedTimeout, err := strconv.Atoi(timeoutStr); err == nil && parsedTimeout > 0 {
|
||||
timeout = parsedTimeout
|
||||
}
|
||||
}
|
||||
|
||||
err := d.switchToIframe(tabID, selector, timeout)
|
||||
if err != nil {
|
||||
response = Response{Success: false, Error: err.Error()}
|
||||
} else {
|
||||
|
@ -1678,10 +1687,13 @@ func (d *Daemon) takeScreenshot(tabID, outputPath string, fullPage bool, timeout
|
|||
}
|
||||
|
||||
// switchToIframe switches the context to an iframe for subsequent commands
|
||||
func (d *Daemon) switchToIframe(tabID, selector string) error {
|
||||
func (d *Daemon) switchToIframe(tabID, selector string, timeout int) error {
|
||||
d.debugLog("Switching to iframe: selector=%s, tab=%s, timeout=%d", selector, tabID, timeout)
|
||||
|
||||
// Get the main page first (not iframe context)
|
||||
actualTabID, err := d.getTabID(tabID)
|
||||
if err != nil {
|
||||
d.debugLog("Failed to get tab ID: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -1691,49 +1703,155 @@ func (d *Daemon) switchToIframe(tabID, selector string) error {
|
|||
// Get the main page (bypass iframe context)
|
||||
mainPage, exists := d.tabs[actualTabID]
|
||||
if !exists {
|
||||
d.debugLog("Tab %s not in cache, trying to find it", actualTabID)
|
||||
// Try to find it
|
||||
mainPage, err = d.findPageByID(actualTabID)
|
||||
if err != nil {
|
||||
d.debugLog("Failed to find tab %s: %v", actualTabID, err)
|
||||
return err
|
||||
}
|
||||
if mainPage == nil {
|
||||
d.debugLog("Tab %s not found", actualTabID)
|
||||
return fmt.Errorf("tab not found: %s", actualTabID)
|
||||
}
|
||||
d.tabs[actualTabID] = mainPage
|
||||
}
|
||||
|
||||
// Find the iframe element
|
||||
iframeElement, err := mainPage.Element(selector)
|
||||
d.debugLog("Found main page for tab %s, looking for iframe element", actualTabID)
|
||||
|
||||
// Find the iframe element with timeout
|
||||
var iframeElement *rod.Element
|
||||
if timeout > 0 {
|
||||
// Use timeout context for finding the iframe element
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Create a channel to signal completion
|
||||
done := make(chan struct {
|
||||
element *rod.Element
|
||||
err error
|
||||
}, 1)
|
||||
|
||||
// Execute the element search in a goroutine
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
done <- struct {
|
||||
element *rod.Element
|
||||
err error
|
||||
}{nil, fmt.Errorf("iframe element search panicked: %v", r)}
|
||||
}
|
||||
}()
|
||||
|
||||
element, err := mainPage.Timeout(time.Duration(timeout) * time.Second).Element(selector)
|
||||
done <- struct {
|
||||
element *rod.Element
|
||||
err error
|
||||
}{element, err}
|
||||
}()
|
||||
|
||||
// Wait for either completion or timeout
|
||||
select {
|
||||
case result := <-done:
|
||||
iframeElement = result.element
|
||||
err = result.err
|
||||
case <-ctx.Done():
|
||||
d.debugLog("Iframe element search timed out after %d seconds", timeout)
|
||||
return fmt.Errorf("failed to find iframe element (timeout after %ds): %s", timeout, selector)
|
||||
}
|
||||
} else {
|
||||
// No timeout
|
||||
iframeElement, err = mainPage.Element(selector)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
d.debugLog("Failed to find iframe element: %v", err)
|
||||
return fmt.Errorf("failed to find iframe element: %w", err)
|
||||
}
|
||||
|
||||
// Get the iframe's page context
|
||||
iframePage, err := iframeElement.Frame()
|
||||
d.debugLog("Found iframe element, getting frame context")
|
||||
|
||||
// Get the iframe's page context with timeout
|
||||
var iframePage *rod.Page
|
||||
if timeout > 0 {
|
||||
// Use timeout context for getting the frame
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Create a channel to signal completion
|
||||
done := make(chan struct {
|
||||
page *rod.Page
|
||||
err error
|
||||
}, 1)
|
||||
|
||||
// Execute the frame access in a goroutine
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
done <- struct {
|
||||
page *rod.Page
|
||||
err error
|
||||
}{nil, fmt.Errorf("iframe frame access panicked: %v", r)}
|
||||
}
|
||||
}()
|
||||
|
||||
page, err := iframeElement.Frame()
|
||||
done <- struct {
|
||||
page *rod.Page
|
||||
err error
|
||||
}{page, err}
|
||||
}()
|
||||
|
||||
// Wait for either completion or timeout
|
||||
select {
|
||||
case result := <-done:
|
||||
iframePage = result.page
|
||||
err = result.err
|
||||
case <-ctx.Done():
|
||||
d.debugLog("Iframe frame access timed out after %d seconds", timeout)
|
||||
return fmt.Errorf("failed to get iframe context (timeout after %ds)", timeout)
|
||||
}
|
||||
} else {
|
||||
// No timeout
|
||||
iframePage, err = iframeElement.Frame()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
d.debugLog("Failed to get iframe context: %v", err)
|
||||
return fmt.Errorf("failed to get iframe context: %w", err)
|
||||
}
|
||||
|
||||
// Store the iframe page context
|
||||
d.iframePages[actualTabID] = iframePage
|
||||
d.debugLog("Successfully switched to iframe context for tab %s", actualTabID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// switchToMain switches back to the main page context
|
||||
func (d *Daemon) switchToMain(tabID string) error {
|
||||
d.debugLog("Switching back to main context: tab=%s", tabID)
|
||||
|
||||
// Get the tab ID to use (may be the current tab)
|
||||
actualTabID, err := d.getTabID(tabID)
|
||||
if err != nil {
|
||||
d.debugLog("Failed to get tab ID: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
// Remove the iframe context for this tab
|
||||
delete(d.iframePages, actualTabID)
|
||||
// Check if there was an iframe context to remove
|
||||
if _, exists := d.iframePages[actualTabID]; exists {
|
||||
d.debugLog("Removing iframe context for tab %s", actualTabID)
|
||||
// Remove the iframe context for this tab
|
||||
delete(d.iframePages, actualTabID)
|
||||
} else {
|
||||
d.debugLog("No iframe context found for tab %s", actualTabID)
|
||||
}
|
||||
|
||||
d.debugLog("Successfully switched back to main context for tab %s", actualTabID)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
3
main.go
3
main.go
|
@ -116,6 +116,7 @@ func main() {
|
|||
// switch-iframe flags
|
||||
switchIframeTabID := switchIframeCmd.String("tab", "", "Tab ID to switch iframe context in (optional, uses current tab if not specified)")
|
||||
switchIframeSelector := switchIframeCmd.String("selector", "", "CSS selector for the iframe element")
|
||||
switchIframeTimeout := switchIframeCmd.Int("timeout", 5, "Timeout in seconds for iframe switching")
|
||||
switchIframeHost := switchIframeCmd.String("host", "localhost", "Daemon host")
|
||||
switchIframePort := switchIframeCmd.Int("port", 8989, "Daemon port")
|
||||
|
||||
|
@ -366,7 +367,7 @@ func main() {
|
|||
c := client.NewClient(*switchIframeHost, *switchIframePort)
|
||||
|
||||
// Switch to iframe
|
||||
err := c.SwitchToIframe(*switchIframeTabID, *switchIframeSelector)
|
||||
err := c.SwitchToIframe(*switchIframeTabID, *switchIframeSelector, *switchIframeTimeout)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
|
|
|
@ -112,17 +112,21 @@ Switch iframe context for subsequent operations.
|
|||
- `action` (required): One of "enter", "exit"
|
||||
- `selector` (optional): Iframe CSS selector (required for "enter" action)
|
||||
- `tab` (optional): Specific tab ID to use
|
||||
- `timeout` (optional): Timeout in seconds (default: 5)
|
||||
|
||||
**Example Usage:**
|
||||
```
|
||||
web_iframe_cremotemcp:
|
||||
action: "enter"
|
||||
selector: "iframe#payment-form"
|
||||
timeout: 10
|
||||
|
||||
web_iframe_cremotemcp:
|
||||
action: "exit"
|
||||
```
|
||||
|
||||
**Note:** The timeout parameter is particularly important for iframe operations as they can hang if the iframe takes time to load or if the selector doesn't match any elements.
|
||||
|
||||
### 7. `file_upload_cremotemcp`
|
||||
Upload files from the client to the container for use in form uploads.
|
||||
|
||||
|
|
|
@ -750,7 +750,7 @@ func (s *MCPServer) handleIframe(params map[string]interface{}) (ToolResult, err
|
|||
if selector == "" {
|
||||
return ToolResult{}, fmt.Errorf("selector parameter is required for enter action")
|
||||
}
|
||||
err = s.client.SwitchToIframe(tab, selector)
|
||||
err = s.client.SwitchToIframe(tab, selector, 5) // Default 5 second timeout
|
||||
s.iframeMode = true
|
||||
data = map[string]string{"action": "entered", "selector": selector}
|
||||
|
||||
|
|
|
@ -636,7 +636,7 @@ func (s *MCPServer) handleIframe(params map[string]interface{}) (ToolResult, err
|
|||
if selector == "" {
|
||||
return ToolResult{}, fmt.Errorf("selector parameter is required for enter action")
|
||||
}
|
||||
err = s.client.SwitchToIframe(tab, selector)
|
||||
err = s.client.SwitchToIframe(tab, selector, 5) // Default 5 second timeout
|
||||
s.iframeMode = true
|
||||
data = map[string]string{"action": "entered", "selector": selector}
|
||||
|
||||
|
|
BIN
mcp/cremote-mcp
BIN
mcp/cremote-mcp
Binary file not shown.
BIN
mcp/cremote-mcp2
BIN
mcp/cremote-mcp2
Binary file not shown.
|
@ -538,6 +538,11 @@ func main() {
|
|||
"type": "string",
|
||||
"description": "Tab ID (optional)",
|
||||
},
|
||||
"timeout": map[string]any{
|
||||
"type": "integer",
|
||||
"description": "Timeout in seconds",
|
||||
"default": 5,
|
||||
},
|
||||
},
|
||||
Required: []string{"action"},
|
||||
},
|
||||
|
@ -551,6 +556,7 @@ func main() {
|
|||
action := getStringParam(params, "action", "")
|
||||
selector := getStringParam(params, "selector", "")
|
||||
tab := getStringParam(params, "tab", cremoteServer.currentTab)
|
||||
timeout := getIntParam(params, "timeout", 5)
|
||||
|
||||
if action == "" {
|
||||
return nil, fmt.Errorf("action parameter is required")
|
||||
|
@ -567,7 +573,7 @@ func main() {
|
|||
if selector == "" {
|
||||
return nil, fmt.Errorf("selector parameter is required for enter action")
|
||||
}
|
||||
err = cremoteServer.client.SwitchToIframe(tab, selector)
|
||||
err = cremoteServer.client.SwitchToIframe(tab, selector, timeout)
|
||||
cremoteServer.iframeMode = true
|
||||
message = fmt.Sprintf("Entered iframe: %s", selector)
|
||||
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Test script for iframe timeout functionality
|
||||
set -e
|
||||
|
||||
echo "Starting iframe timeout test..."
|
||||
|
||||
# Start the daemon in background
|
||||
echo "Starting cremotedaemon..."
|
||||
./cremotedaemon --debug &
|
||||
DAEMON_PID=$!
|
||||
|
||||
# Wait for daemon to start
|
||||
sleep 2
|
||||
|
||||
# Function to cleanup
|
||||
cleanup() {
|
||||
echo "Cleaning up..."
|
||||
kill $DAEMON_PID 2>/dev/null || true
|
||||
wait $DAEMON_PID 2>/dev/null || true
|
||||
}
|
||||
|
||||
# Set trap for cleanup
|
||||
trap cleanup EXIT
|
||||
|
||||
# Test 1: Basic iframe switching with timeout
|
||||
echo "Test 1: Basic iframe switching with timeout"
|
||||
TAB_ID=$(./cremote open-tab --timeout 5 | grep -o '"[^"]*"' | tr -d '"')
|
||||
echo "Created tab: $TAB_ID"
|
||||
|
||||
# Load test page
|
||||
./cremote load-url --tab "$TAB_ID" --url "file://$(pwd)/test-iframe.html" --timeout 10
|
||||
echo "Loaded test page"
|
||||
|
||||
# Switch to iframe with timeout
|
||||
echo "Switching to iframe with 5 second timeout..."
|
||||
./cremote switch-iframe --tab "$TAB_ID" --selector "#test-iframe" --timeout 5
|
||||
echo "Successfully switched to iframe"
|
||||
|
||||
# Try to click button in iframe
|
||||
echo "Clicking button in iframe..."
|
||||
./cremote click-element --tab "$TAB_ID" --selector "#iframe-button" --selection-timeout 5 --action-timeout 5
|
||||
echo "Successfully clicked iframe button"
|
||||
|
||||
# Switch back to main
|
||||
echo "Switching back to main context..."
|
||||
./cremote switch-main --tab "$TAB_ID"
|
||||
echo "Successfully switched back to main"
|
||||
|
||||
# Try to click main button
|
||||
echo "Clicking main page button..."
|
||||
./cremote click-element --tab "$TAB_ID" --selector "#main-button" --selection-timeout 5 --action-timeout 5
|
||||
echo "Successfully clicked main button"
|
||||
|
||||
# Test 2: Test timeout with non-existent iframe
|
||||
echo ""
|
||||
echo "Test 2: Testing timeout with non-existent iframe"
|
||||
set +e # Allow command to fail
|
||||
./cremote switch-iframe --tab "$TAB_ID" --selector "#non-existent-iframe" --timeout 2
|
||||
RESULT=$?
|
||||
set -e
|
||||
|
||||
if [ $RESULT -eq 0 ]; then
|
||||
echo "ERROR: Expected timeout failure but command succeeded"
|
||||
exit 1
|
||||
else
|
||||
echo "SUCCESS: Timeout correctly handled for non-existent iframe"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "All iframe timeout tests passed!"
|
|
@ -0,0 +1,20 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Iframe Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Main Page</h1>
|
||||
<p>This is the main page content.</p>
|
||||
|
||||
<iframe id="test-iframe" src="data:text/html,<html><body><h2>Iframe Content</h2><button id='iframe-button'>Click Me</button><script>document.getElementById('iframe-button').onclick = function() { alert('Iframe button clicked!'); }</script></body></html>" width="400" height="200"></iframe>
|
||||
|
||||
<button id="main-button">Main Page Button</button>
|
||||
|
||||
<script>
|
||||
document.getElementById('main-button').onclick = function() {
|
||||
alert('Main page button clicked!');
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue