diff --git a/client/client.go b/client/client.go index 1ffe019..20a80ba 100644 --- a/client/client.go +++ b/client/client.go @@ -14,7 +14,8 @@ import ( // Client is the client for communicating with the daemon type Client struct { - serverURL string + serverURL string + httpClient *http.Client } // Command represents a command sent from the client to the daemon @@ -34,12 +35,15 @@ type Response struct { func NewClient(host string, port int) *Client { return &Client{ serverURL: fmt.Sprintf("http://%s:%d", host, port), + httpClient: &http.Client{ + Timeout: 60 * time.Second, // 60 second timeout for long operations + }, } } // CheckStatus checks if the daemon is running func (c *Client) CheckStatus() (bool, error) { - resp, err := http.Get(c.serverURL + "/status") + resp, err := c.httpClient.Get(c.serverURL + "/status") if err != nil { return false, err } @@ -174,7 +178,7 @@ func (c *Client) SendCommand(action string, params map[string]string) (*Response return nil, err } - resp, err := http.Post(c.serverURL+"/command", "application/json", bytes.NewBuffer(jsonData)) + resp, err := c.httpClient.Post(c.serverURL+"/command", "application/json", bytes.NewBuffer(jsonData)) if err != nil { return nil, err } diff --git a/daemon/daemon.go b/daemon/daemon.go index f005f94..0fe847a 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -2822,21 +2822,40 @@ func (d *Daemon) extractMultiple(tabID, selectorsJSON string, timeout int) (*Mul continue } - // Extract text content from all matching elements - var texts []string + // Extract content from all matching elements + var values []string for _, element := range elements { - text, err := element.Text() + var value string + var err error + + // Check if it's a form input element and get its value + tagName, _ := element.Eval("() => this.tagName.toLowerCase()") + + if tagName.Value.Str() == "input" || tagName.Value.Str() == "textarea" || tagName.Value.Str() == "select" { + // For form elements, get the value property + valueProp, err := element.Property("value") + if err == nil && valueProp.Str() != "" { + value = valueProp.Str() + } else { + // Fallback to text content + value, err = element.Text() + } + } else { + // For non-form elements, get text content + value, err = element.Text() + } + if err != nil { - result.Errors[key] = fmt.Sprintf("failed to get text: %v", err) + result.Errors[key] = fmt.Sprintf("failed to get content: %v", err) break } - texts = append(texts, text) + values = append(values, value) } - if len(texts) == 1 { - result.Results[key] = texts[0] + if len(values) == 1 { + result.Results[key] = values[0] } else { - result.Results[key] = texts + result.Results[key] = values } } @@ -3414,15 +3433,9 @@ func (d *Daemon) interactMultiple(tabID, interactionsJSON string, timeout int) ( Success: false, } - // Find the element + // Find the element without timeout to avoid context cancellation issues var element *rod.Element - if timeout > 0 { - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) - element, err = page.Context(ctx).Element(interaction.Selector) - cancel() - } else { - element, err = page.Element(interaction.Selector) - } + element, err = page.Element(interaction.Selector) if err != nil { interactionResult.Error = fmt.Sprintf("failed to find element: %v", err) @@ -3435,6 +3448,14 @@ func (d *Daemon) interactMultiple(tabID, interactionsJSON string, timeout int) ( switch interaction.Action { case "click": err = element.Click(proto.InputMouseButtonLeft, 1) + // Retry once if context was canceled + if err != nil && strings.Contains(err.Error(), "context canceled") { + // Try to find element again and click + element, err = page.Element(interaction.Selector) + if err == nil { + err = element.Click(proto.InputMouseButtonLeft, 1) + } + } if err != nil { interactionResult.Error = fmt.Sprintf("failed to click: %v", err) } else { @@ -3450,6 +3471,20 @@ func (d *Daemon) interactMultiple(tabID, interactionsJSON string, timeout int) ( if err == nil { err = element.Input(interaction.Value) } + // Retry once if context was canceled + if err != nil && strings.Contains(err.Error(), "context canceled") { + // Try to find element again and fill + element, err = page.Element(interaction.Selector) + if err == nil { + err = element.SelectAllText() + if err == nil { + err = element.Input("") + } + if err == nil { + err = element.Input(interaction.Value) + } + } + } if err != nil { interactionResult.Error = fmt.Sprintf("failed to fill: %v", err) } else { @@ -3457,14 +3492,34 @@ func (d *Daemon) interactMultiple(tabID, interactionsJSON string, timeout int) ( } case "select": - // For select elements, try to select by text first, then by value - err = element.Select([]string{interaction.Value}, true, rod.SelectorTypeText) - if err != nil { - // Try by value if text selection failed - err = element.Select([]string{interaction.Value}, false, rod.SelectorTypeText) - } - if err != nil { - interactionResult.Error = fmt.Sprintf("failed to select: %v", err) + // For select elements, use JavaScript to set the value + script := fmt.Sprintf(` + const element = arguments[0]; + if (element.tagName.toLowerCase() === 'select') { + // Try to select by value first + for (let option of element.options) { + if (option.value === '%s') { + element.value = '%s'; + element.dispatchEvent(new Event('change', { bubbles: true })); + return true; + } + } + // Try to select by text if value didn't work + for (let option of element.options) { + if (option.text === '%s') { + element.value = option.value; + element.dispatchEvent(new Event('change', { bubbles: true })); + return true; + } + } + return false; + } + return false; + `, interaction.Value, interaction.Value, interaction.Value) + + result, err := element.Eval(script) + if err != nil || !result.Value.Bool() { + interactionResult.Error = fmt.Sprintf("failed to select option: %s", interaction.Value) } else { interactionResult.Success = true } @@ -3476,6 +3531,14 @@ func (d *Daemon) interactMultiple(tabID, interactionsJSON string, timeout int) ( interactionResult.Success = true // Already checked } else { err = element.Click(proto.InputMouseButtonLeft, 1) + // Retry once if context was canceled + if err != nil && strings.Contains(err.Error(), "context canceled") { + // Try to find element again and click + element, err = page.Element(interaction.Selector) + if err == nil { + err = element.Click(proto.InputMouseButtonLeft, 1) + } + } if err != nil { interactionResult.Error = fmt.Sprintf("failed to check: %v", err) } else { diff --git a/mcp/cremote-mcp b/mcp/cremote-mcp index 0d74570..935c2c9 100755 Binary files a/mcp/cremote-mcp and b/mcp/cremote-mcp differ