This commit is contained in:
Josh at WLTechBlog
2025-08-19 06:15:55 -05:00
parent 58e361ba70
commit 36adab7878
9 changed files with 506 additions and 46 deletions

View File

@@ -450,6 +450,33 @@ func (d *Daemon) handleCommand(w http.ResponseWriter, r *http.Request) {
response = Response{Success: true}
}
case "select-element":
tabID := cmd.Params["tab"]
selector := cmd.Params["selector"]
value := cmd.Params["value"]
// Parse timeouts
selectionTimeout := 5 // Default: 5 seconds
if timeoutStr, ok := cmd.Params["selection-timeout"]; ok {
if parsedTimeout, err := strconv.Atoi(timeoutStr); err == nil && parsedTimeout > 0 {
selectionTimeout = parsedTimeout
}
}
actionTimeout := 5 // Default: 5 seconds
if timeoutStr, ok := cmd.Params["action-timeout"]; ok {
if parsedTimeout, err := strconv.Atoi(timeoutStr); err == nil && parsedTimeout > 0 {
actionTimeout = parsedTimeout
}
}
err := d.selectElement(tabID, selector, value, selectionTimeout, actionTimeout)
if err != nil {
response = Response{Success: false, Error: err.Error()}
} else {
response = Response{Success: true}
}
case "eval-js":
tabID := cmd.Params["tab"]
jsCode := cmd.Params["code"]
@@ -1904,6 +1931,55 @@ func (d *Daemon) clickElement(tabID, selector string, selectionTimeout, actionTi
return nil
}
// selectElement selects an option in a select dropdown
func (d *Daemon) selectElement(tabID, selector, value string, selectionTimeout, actionTimeout int) error {
page, err := d.getTab(tabID)
if err != nil {
return err
}
// Find the element with optional timeout
var element *rod.Element
if selectionTimeout > 0 {
// Use timeout if specified
element, err = page.Timeout(time.Duration(selectionTimeout) * time.Second).Element(selector)
if err != nil {
return fmt.Errorf("failed to find element (timeout after %ds): %w", selectionTimeout, err)
}
} else {
// No timeout
element, err = page.Element(selector)
if err != nil {
return fmt.Errorf("failed to find element: %w", err)
}
}
// Make sure the element is visible and scrolled into view
err = element.ScrollIntoView()
if err != nil {
return fmt.Errorf("failed to scroll element into view: %w", err)
}
// For select elements, use rod's built-in Select method
// Try to select by text first (most common case)
err = element.Select([]string{value}, true, rod.SelectorTypeText)
if err != nil {
// If text selection failed, the value might be the actual option value
// Try to find and select by matching option value using page.Eval
script := fmt.Sprintf(`(function(){ var el = document.querySelector("%s"); if(el) { el.value = "%s"; el.dispatchEvent(new Event('change', { bubbles: true })); } })()`, selector, value)
// Execute JavaScript and ignore any rod evaluation quirks
page.Eval(script)
// Verify the selection worked by checking the value
actualValue, err := element.Attribute("value")
if err != nil || actualValue == nil || *actualValue != value {
return fmt.Errorf("failed to select option '%s' in element", value)
}
}
return nil
}
// evalJS executes JavaScript code in a tab and returns the result
func (d *Daemon) evalJS(tabID, jsCode string, timeout int) (string, error) {
page, err := d.getTab(tabID)
@@ -3605,7 +3681,7 @@ func (d *Daemon) fillFormBulk(tabID, formSelector, fieldsJSON string, timeout in
for fieldName, fieldValue := range fields {
fieldResult := InteractionResult{
Selector: fieldName,
Action: "fill",
Action: "fill", // Default action, will be updated based on element type
Success: false,
}
@@ -3626,18 +3702,9 @@ func (d *Daemon) fillFormBulk(tabID, formSelector, fieldsJSON string, timeout in
if timeout > 0 {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
element, err = form.Context(ctx).Element(selector)
// Don't cancel yet if element found - we need context for filling
if err == nil {
fieldResult.Selector = selector
// Fill the field while context is still valid
err = element.SelectAllText()
if err == nil {
err = element.Input("")
}
if err == nil {
err = element.Input(fieldValue)
}
cancel() // Now we can cancel
cancel() // Cancel context now that we found the element
break
}
cancel() // Cancel if element not found
@@ -3645,14 +3712,6 @@ func (d *Daemon) fillFormBulk(tabID, formSelector, fieldsJSON string, timeout in
element, err = form.Element(selector)
if err == nil {
fieldResult.Selector = selector
// Fill the field
err = element.SelectAllText()
if err == nil {
err = element.Input("")
}
if err == nil {
err = element.Input(fieldValue)
}
break
}
}
@@ -3675,18 +3734,9 @@ func (d *Daemon) fillFormBulk(tabID, formSelector, fieldsJSON string, timeout in
if timeout > 0 {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
element, err = page.Context(ctx).Element(selector)
// Don't cancel yet - we need the context for filling
if err == nil {
fieldResult.Selector = selector
// Fill the field while context is still valid
err = element.SelectAllText()
if err == nil {
err = element.Input("")
}
if err == nil {
err = element.Input(fieldValue)
}
cancel() // Now we can cancel
cancel() // Cancel context now that we found the element
break
}
cancel() // Cancel if element not found
@@ -3694,14 +3744,6 @@ func (d *Daemon) fillFormBulk(tabID, formSelector, fieldsJSON string, timeout in
element, err = page.Element(selector)
if err == nil {
fieldResult.Selector = selector
// Fill the field
err = element.SelectAllText()
if err == nil {
err = element.Input("")
}
if err == nil {
err = element.Input(fieldValue)
}
break
}
}
@@ -3715,12 +3757,56 @@ func (d *Daemon) fillFormBulk(tabID, formSelector, fieldsJSON string, timeout in
continue
}
// Determine the element type and use appropriate action
tagName, err := element.Eval("() => this.tagName.toLowerCase()")
if err != nil {
fieldResult.Error = fmt.Sprintf("failed to fill field: %v", err)
fieldResult.Error = fmt.Sprintf("failed to get element tag name: %v", err)
result.FilledFields = append(result.FilledFields, fieldResult)
result.ErrorCount++
continue
}
// Handle different element types
if tagName.Value.String() == "select" {
// Use select action for select elements
fieldResult.Action = "select"
err = element.Select([]string{fieldValue}, true, rod.SelectorTypeText)
if err != nil {
// If text selection failed, try by value
script := fmt.Sprintf(`(function(){ var el = document.querySelector("%s"); if(el) { el.value = "%s"; el.dispatchEvent(new Event('change', { bubbles: true })); } })()`, fieldResult.Selector, fieldValue)
page.Eval(script)
// Verify the selection worked
actualValue, err := element.Attribute("value")
if err != nil || actualValue == nil || *actualValue != fieldValue {
fieldResult.Error = fmt.Sprintf("failed to select option '%s'", fieldValue)
result.ErrorCount++
} else {
fieldResult.Success = true
result.SuccessCount++
}
} else {
fieldResult.Success = true
result.SuccessCount++
}
} else {
fieldResult.Success = true
result.SuccessCount++
// Use fill action for input, textarea, etc.
fieldResult.Action = "fill"
err = element.SelectAllText()
if err == nil {
err = element.Input("")
}
if err == nil {
err = element.Input(fieldValue)
}
if err != nil {
fieldResult.Error = fmt.Sprintf("failed to fill field: %v", err)
result.ErrorCount++
} else {
fieldResult.Success = true
result.SuccessCount++
}
}
result.FilledFields = append(result.FilledFields, fieldResult)