package client import ( "bytes" "encoding/json" "fmt" "io" "mime/multipart" "net/http" "os" "strconv" "time" ) // Client is the client for communicating with the daemon type Client struct { serverURL string httpClient *http.Client } // Command represents a command sent from the client to the daemon type Command struct { Action string `json:"action"` Params map[string]string `json:"params"` } // Response represents a response from the daemon to the client type Response struct { Success bool `json:"success"` Data interface{} `json:"data,omitempty"` Error string `json:"error,omitempty"` } // NewClient creates a new client 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 := c.httpClient.Get(c.serverURL + "/status") if err != nil { return false, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode) } var response Response err = json.NewDecoder(resp.Body).Decode(&response) if err != nil { return false, err } return response.Success, nil } // GetVersion gets the daemon version func (c *Client) GetVersion() (string, error) { response, err := c.SendCommand("version", map[string]string{}) if err != nil { return "", err } if !response.Success { return "", fmt.Errorf("failed to get version: %s", response.Error) } // The version should be in the Data field as a string if version, ok := response.Data.(string); ok { return version, nil } return "", fmt.Errorf("unexpected version response format") } // TabInfo contains information about a tab type TabInfo struct { ID string `json:"id"` URL string `json:"url"` IsCurrent bool `json:"is_current"` HistoryIndex int `json:"history_index"` // Position in tab history (higher = more recent) } // ListTabs returns a list of all open tabs func (c *Client) ListTabs() ([]TabInfo, error) { resp, err := http.Get(c.serverURL + "/status") if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) } var response Response err = json.NewDecoder(resp.Body).Decode(&response) if err != nil { return nil, fmt.Errorf("failed to parse response: %w", err) } if !response.Success { return nil, fmt.Errorf("daemon returned error: %s", response.Error) } // Extract the data data, ok := response.Data.(map[string]interface{}) if !ok { return nil, fmt.Errorf("unexpected response format") } // Get the tabs tabsData, ok := data["tabs"].(map[string]interface{}) if !ok { return nil, fmt.Errorf("unexpected tabs format") } // Get the current tab currentTab, _ := data["current_tab"].(string) // Get the tab history tabHistoryData, ok := data["tab_history"].([]interface{}) if !ok { tabHistoryData = []interface{}{} } // Create a map of tab history indices tabHistoryIndices := make(map[string]int) for i, idInterface := range tabHistoryData { id, ok := idInterface.(string) if ok { tabHistoryIndices[id] = i } } // Convert to TabInfo tabs := make([]TabInfo, 0, len(tabsData)) for id, urlInterface := range tabsData { url, ok := urlInterface.(string) if !ok { url = "" } // Get the history index (default to -1 if not in history) historyIndex, inHistory := tabHistoryIndices[id] if !inHistory { historyIndex = -1 } tabs = append(tabs, TabInfo{ ID: id, URL: url, IsCurrent: id == currentTab, HistoryIndex: historyIndex, }) } return tabs, nil } // SendCommand sends a command to the daemon func (c *Client) SendCommand(action string, params map[string]string) (*Response, error) { cmd := Command{ Action: action, Params: params, } jsonData, err := json.Marshal(cmd) if err != nil { return nil, err } resp, err := c.httpClient.Post(c.serverURL+"/command", "application/json", bytes.NewBuffer(jsonData)) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return nil, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, body) } var response Response err = json.NewDecoder(resp.Body).Decode(&response) if err != nil { return nil, err } return &response, nil } // OpenTab opens a new tab // timeout is in seconds, 0 means no timeout func (c *Client) OpenTab(timeout int) (string, error) { params := map[string]string{} // Add timeout if specified if timeout > 0 { params["timeout"] = strconv.Itoa(timeout) } resp, err := c.SendCommand("open-tab", params) if err != nil { return "", err } if !resp.Success { return "", fmt.Errorf("failed to open tab: %s", resp.Error) } tabID, ok := resp.Data.(string) if !ok { return "", fmt.Errorf("unexpected response data type") } return tabID, nil } // LoadURL loads a URL in a tab // If tabID is empty, the current tab will be used // timeout is in seconds, 0 means no timeout func (c *Client) LoadURL(tabID, url string, timeout int) error { params := map[string]string{ "url": url, } // 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("load-url", params) if err != nil { return err } if !resp.Success { return fmt.Errorf("failed to load URL: %s", resp.Error) } return nil } // FillFormField fills a form field with a value // If tabID is empty, the current tab will be used // timeout is in seconds, 0 means no timeout func (c *Client) FillFormField(tabID, selector, value string, timeout int) error { params := map[string]string{ "selector": selector, "value": value, } // 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("fill-form", params) if err != nil { return err } if !resp.Success { return fmt.Errorf("failed to fill form field: %s", resp.Error) } return nil } // UploadFile uploads a file to a file input // If tabID is empty, the current tab will be used // timeout is in seconds, 0 means no timeout func (c *Client) UploadFile(tabID, selector, filePath string, timeout int) error { params := map[string]string{ "selector": selector, "file": filePath, } // 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("upload-file", params) if err != nil { return err } if !resp.Success { return fmt.Errorf("failed to upload file: %s", resp.Error) } return nil } // SelectElement selects an option in a select dropdown // If tabID is empty, the current tab will be used // timeout is in seconds, 0 means no timeout func (c *Client) SelectElement(tabID, selector, value string, timeout int) error { params := map[string]string{ "selector": selector, "value": value, } // 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-element", params) if err != nil { return err } if !resp.Success { return fmt.Errorf("failed to select element: %s", resp.Error) } return nil } // SubmitForm submits a form // If tabID is empty, the current tab will be used // timeout is in seconds, 0 means no timeout func (c *Client) SubmitForm(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("submit-form", params) if err != nil { return err } if !resp.Success { return fmt.Errorf("failed to submit form: %s", resp.Error) } return nil } // GetPageSource gets the source code of a page // If tabID is empty, the current tab will be used // timeout is in seconds, 0 means no timeout func (c *Client) GetPageSource(tabID string, timeout int) (string, error) { params := map[string]string{} // 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("get-source", params) if err != nil { return "", err } if !resp.Success { return "", fmt.Errorf("failed to get page source: %s", resp.Error) } source, ok := resp.Data.(string) if !ok { return "", fmt.Errorf("unexpected response data type") } return source, nil } // GetElementHTML gets the HTML of an element // If tabID is empty, the current tab will be used // timeout is in seconds, 0 means no timeout func (c *Client) GetElementHTML(tabID, selector string, timeout int) (string, 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("get-element", params) if err != nil { return "", err } if !resp.Success { return "", fmt.Errorf("failed to get element HTML: %s", resp.Error) } html, ok := resp.Data.(string) if !ok { return "", fmt.Errorf("unexpected response data type") } return html, nil } // CloseTab closes a tab // If tabID is empty, the current tab will be used // timeout is in seconds, 0 means no timeout func (c *Client) CloseTab(tabID string, timeout int) error { params := map[string]string{} // 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("close-tab", params) if err != nil { return err } if !resp.Success { return fmt.Errorf("failed to close tab: %s", resp.Error) } return nil } // WaitNavigation waits for a navigation event // If tabID is empty, the current tab will be used func (c *Client) WaitNavigation(tabID string, timeout int) error { params := map[string]string{ "timeout": fmt.Sprintf("%d", timeout), } // Only include tab ID if it's provided if tabID != "" { params["tab"] = tabID } resp, err := c.SendCommand("wait-navigation", params) if err != nil { return err } if !resp.Success { return fmt.Errorf("failed to wait for navigation: %s", resp.Error) } return nil } // EvalJS executes JavaScript code in a tab and returns the result // If tabID is empty, the current tab will be used // timeout is in seconds, 0 means no timeout func (c *Client) EvalJS(tabID, jsCode string, timeout int) (string, error) { params := map[string]string{ "code": jsCode, } // 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("eval-js", params) if err != nil { return "", err } if !resp.Success { return "", fmt.Errorf("failed to execute JavaScript: %s", resp.Error) } // Convert result to string if it exists if resp.Data != nil { if result, ok := resp.Data.(string); ok { return result, nil } // If it's not a string, convert it to string representation return fmt.Sprintf("%v", resp.Data), nil } return "", nil } // TakeScreenshot takes a screenshot of a tab and saves it to a file // If tabID is empty, the current tab will be used // timeout is in seconds, 0 means no timeout func (c *Client) TakeScreenshot(tabID, outputPath string, fullPage bool, timeout int) error { params := map[string]string{ "output": outputPath, "full-page": strconv.FormatBool(fullPage), } // 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("screenshot", params) if err != nil { return err } if !resp.Success { return fmt.Errorf("failed to take screenshot: %s", resp.Error) } return nil } // SwitchToIframe switches the context to an iframe for subsequent commands // If tabID is empty, the current tab will be used // timeout is in seconds, 0 means no timeout func (c *Client) SwitchToIframe(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("switch-iframe", params) if err != nil { return err } if !resp.Success { return fmt.Errorf("failed to switch to iframe: %s", resp.Error) } return nil } // SwitchToMain switches the context back to the main page // If tabID is empty, the current tab will be used // timeout is in seconds, 0 means no timeout func (c *Client) SwitchToMain(tabID string, timeout int) error { params := map[string]string{} // 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("switch-main", params) if err != nil { return err } if !resp.Success { return fmt.Errorf("failed to switch to main context: %s", resp.Error) } return nil } // ClickElement clicks on an element // If tabID is empty, the current tab will be used // timeout is in seconds, 0 means no timeout func (c *Client) ClickElement(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("click-element", params) if err != nil { return err } if !resp.Success { return fmt.Errorf("failed to click element: %s", resp.Error) } return nil } // UploadFileToContainer uploads a file from the client to the container // localPath: path to the file on the client machine // containerPath: optional path where to store the file in the container (defaults to /tmp/filename) func (c *Client) UploadFileToContainer(localPath, containerPath string) (string, error) { // Open the local file file, err := os.Open(localPath) if err != nil { return "", fmt.Errorf("failed to open local file: %w", err) } defer file.Close() // Get file info fileInfo, err := file.Stat() if err != nil { return "", fmt.Errorf("failed to get file info: %w", err) } // Create multipart form var body bytes.Buffer writer := multipart.NewWriter(&body) // Add the file field fileWriter, err := writer.CreateFormFile("file", fileInfo.Name()) if err != nil { return "", fmt.Errorf("failed to create form file: %w", err) } _, err = io.Copy(fileWriter, file) if err != nil { return "", fmt.Errorf("failed to copy file data: %w", err) } // Add the path field if specified if containerPath != "" { err = writer.WriteField("path", containerPath) if err != nil { return "", fmt.Errorf("failed to write path field: %w", err) } } err = writer.Close() if err != nil { return "", fmt.Errorf("failed to close multipart writer: %w", err) } // Send the request req, err := http.NewRequest("POST", c.serverURL+"/upload", &body) if err != nil { return "", fmt.Errorf("failed to create request: %w", err) } req.Header.Set("Content-Type", writer.FormDataContentType()) client := &http.Client{} resp, err := client.Do(req) if err != nil { return "", fmt.Errorf("failed to send request: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return "", fmt.Errorf("upload failed with status %d: %s", resp.StatusCode, body) } // Parse the response var response Response err = json.NewDecoder(resp.Body).Decode(&response) if err != nil { return "", fmt.Errorf("failed to decode response: %w", err) } if !response.Success { return "", fmt.Errorf("upload failed: %s", response.Error) } // Extract the target path from response data, ok := response.Data.(map[string]interface{}) if !ok { return "", fmt.Errorf("invalid response data format") } targetPath, ok := data["target_path"].(string) if !ok { return "", fmt.Errorf("target_path not found in response") } return targetPath, nil } // DownloadFileFromContainer downloads a file from the container to the client // containerPath: path to the file in the container // localPath: path where to save the file on the client machine func (c *Client) DownloadFileFromContainer(containerPath, localPath string) error { // Create the request url := fmt.Sprintf("%s/download?path=%s", c.serverURL, containerPath) resp, err := http.Get(url) if err != nil { return fmt.Errorf("failed to send download request: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("download failed with status %d: %s", resp.StatusCode, body) } // Create the local file localFile, err := os.Create(localPath) if err != nil { return fmt.Errorf("failed to create local file: %w", err) } defer localFile.Close() // Copy the response body to the local file _, err = io.Copy(localFile, resp.Body) if err != nil { return fmt.Errorf("failed to save file: %w", err) } return nil } // GetConsoleLogs retrieves console logs from a tab // If tabID is empty, the current tab will be used // If clear is true, the logs will be cleared after retrieval func (c *Client) GetConsoleLogs(tabID string, clear bool) ([]map[string]interface{}, error) { params := map[string]string{} // Only include tab ID if it's provided if tabID != "" { params["tab"] = tabID } // Add clear flag if specified if clear { params["clear"] = "true" } resp, err := c.SendCommand("console-logs", params) if err != nil { return nil, err } if !resp.Success { return nil, fmt.Errorf("failed to get console logs: %s", resp.Error) } // Convert response data to slice of console logs logs, ok := resp.Data.([]interface{}) if !ok { return nil, fmt.Errorf("unexpected response data type") } result := make([]map[string]interface{}, len(logs)) for i, log := range logs { logMap, ok := log.(map[string]interface{}) if !ok { return nil, fmt.Errorf("unexpected log entry format") } result[i] = logMap } return result, nil } // ExecuteConsoleCommand executes a command in the browser console // If tabID is empty, the current tab will be used // timeout is in seconds, 0 means no timeout func (c *Client) ExecuteConsoleCommand(tabID, command string, timeout int) (string, error) { params := map[string]string{ "command": command, } // 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("console-command", params) if err != nil { return "", err } if !resp.Success { return "", fmt.Errorf("failed to execute console command: %s", resp.Error) } result, ok := resp.Data.(string) if !ok { return "", fmt.Errorf("unexpected response data type") } return result, nil } // ElementCheckResult represents the result of an element check type ElementCheckResult struct { Exists bool `json:"exists"` Visible bool `json:"visible,omitempty"` Enabled bool `json:"enabled,omitempty"` Focused bool `json:"focused,omitempty"` Selected bool `json:"selected,omitempty"` Count int `json:"count,omitempty"` } // MultipleExtractionResult represents the result of extracting from multiple selectors type MultipleExtractionResult struct { Results map[string]interface{} `json:"results"` Errors map[string]string `json:"errors,omitempty"` } // LinkInfo represents information about a link type LinkInfo struct { Href string `json:"href"` Text string `json:"text"` Title string `json:"title,omitempty"` Target string `json:"target,omitempty"` } // LinksExtractionResult represents the result of extracting links type LinksExtractionResult struct { Links []LinkInfo `json:"links"` Count int `json:"count"` } // TableExtractionResult represents the result of extracting table data type TableExtractionResult struct { Headers []string `json:"headers,omitempty"` Rows [][]string `json:"rows"` Data []map[string]string `json:"data,omitempty"` // Only if headers are included Count int `json:"count"` } // TextExtractionResult represents the result of extracting text type TextExtractionResult struct { Text string `json:"text"` Matches []string `json:"matches,omitempty"` // If pattern was used Count int `json:"count"` // Number of elements matched } // FormField represents a form field with its properties type FormField struct { Name string `json:"name"` Type string `json:"type"` Value string `json:"value"` Placeholder string `json:"placeholder,omitempty"` Required bool `json:"required"` Disabled bool `json:"disabled"` ReadOnly bool `json:"readonly"` Selector string `json:"selector"` Label string `json:"label,omitempty"` Options []FormFieldOption `json:"options,omitempty"` // For select/radio/checkbox } // FormFieldOption represents an option in a select, radio, or checkbox group type FormFieldOption struct { Value string `json:"value"` Text string `json:"text"` Selected bool `json:"selected"` } // FormAnalysisResult represents the result of analyzing a form type FormAnalysisResult struct { Action string `json:"action,omitempty"` Method string `json:"method,omitempty"` Fields []FormField `json:"fields"` FieldCount int `json:"field_count"` CanSubmit bool `json:"can_submit"` SubmitText string `json:"submit_text,omitempty"` } // InteractionItem represents a single interaction to perform type InteractionItem struct { Selector string `json:"selector"` Action string `json:"action"` // click, fill, select, check, uncheck Value string `json:"value,omitempty"` } // InteractionResult represents the result of a single interaction type InteractionResult struct { Selector string `json:"selector"` Action string `json:"action"` Success bool `json:"success"` Error string `json:"error,omitempty"` } // MultipleInteractionResult represents the result of multiple interactions type MultipleInteractionResult struct { Results []InteractionResult `json:"results"` SuccessCount int `json:"success_count"` ErrorCount int `json:"error_count"` TotalCount int `json:"total_count"` } // FormBulkFillResult represents the result of bulk form filling type FormBulkFillResult struct { FilledFields []InteractionResult `json:"filled_fields"` SuccessCount int `json:"success_count"` ErrorCount int `json:"error_count"` TotalCount int `json:"total_count"` } // PageInfo represents page metadata and state information type PageInfo struct { Title string `json:"title"` URL string `json:"url"` LoadingState string `json:"loading_state"` ReadyState string `json:"ready_state"` Referrer string `json:"referrer"` Domain string `json:"domain"` Protocol string `json:"protocol"` Charset string `json:"charset"` ContentType string `json:"content_type"` LastModified string `json:"last_modified"` CookieEnabled bool `json:"cookie_enabled"` OnlineStatus bool `json:"online_status"` } // ViewportInfo represents viewport and scroll information type ViewportInfo struct { Width int `json:"width"` Height int `json:"height"` ScrollX int `json:"scroll_x"` ScrollY int `json:"scroll_y"` ScrollWidth int `json:"scroll_width"` ScrollHeight int `json:"scroll_height"` ClientWidth int `json:"client_width"` ClientHeight int `json:"client_height"` DevicePixelRatio float64 `json:"device_pixel_ratio"` Orientation string `json:"orientation"` } // PerformanceMetrics represents page performance data type PerformanceMetrics struct { NavigationStart int64 `json:"navigation_start"` LoadEventEnd int64 `json:"load_event_end"` DOMContentLoaded int64 `json:"dom_content_loaded"` FirstPaint int64 `json:"first_paint"` FirstContentfulPaint int64 `json:"first_contentful_paint"` LoadTime int64 `json:"load_time"` DOMLoadTime int64 `json:"dom_load_time"` ResourceCount int `json:"resource_count"` JSHeapSizeLimit int64 `json:"js_heap_size_limit"` JSHeapSizeTotal int64 `json:"js_heap_size_total"` JSHeapSizeUsed int64 `json:"js_heap_size_used"` } // ContentCheck represents content verification results type ContentCheck struct { Type string `json:"type"` ImagesLoaded int `json:"images_loaded,omitempty"` ImagesTotal int `json:"images_total,omitempty"` ScriptsLoaded int `json:"scripts_loaded,omitempty"` ScriptsTotal int `json:"scripts_total,omitempty"` StylesLoaded int `json:"styles_loaded,omitempty"` StylesTotal int `json:"styles_total,omitempty"` FormsPresent int `json:"forms_present,omitempty"` LinksPresent int `json:"links_present,omitempty"` IframesPresent int `json:"iframes_present,omitempty"` HasErrors bool `json:"has_errors,omitempty"` ErrorCount int `json:"error_count,omitempty"` ErrorMessages []string `json:"error_messages,omitempty"` } // ScreenshotMetadata represents metadata for enhanced screenshots type ScreenshotMetadata struct { Timestamp string `json:"timestamp"` URL string `json:"url"` Title string `json:"title"` ViewportSize struct { Width int `json:"width"` Height int `json:"height"` } `json:"viewport_size"` FullPage bool `json:"full_page"` FilePath string `json:"file_path"` FileSize int64 `json:"file_size"` Resolution struct { Width int `json:"width"` Height int `json:"height"` } `json:"resolution"` } // FileOperation represents a single file operation type FileOperation struct { LocalPath string `json:"local_path"` ContainerPath string `json:"container_path"` Operation string `json:"operation"` // "upload" or "download" } // BulkFileResult represents the result of bulk file operations type BulkFileResult struct { Successful []FileOperationResult `json:"successful"` Failed []FileOperationError `json:"failed"` Summary struct { Total int `json:"total"` Successful int `json:"successful"` Failed int `json:"failed"` } `json:"summary"` } // FileOperationResult represents a successful file operation type FileOperationResult struct { LocalPath string `json:"local_path"` ContainerPath string `json:"container_path"` Operation string `json:"operation"` Size int64 `json:"size"` } // FileOperationError represents a failed file operation type FileOperationError struct { LocalPath string `json:"local_path"` ContainerPath string `json:"container_path"` Operation string `json:"operation"` Error string `json:"error"` } // FileManagementResult represents the result of file management operations type FileManagementResult struct { Operation string `json:"operation"` Files []FileInfo `json:"files,omitempty"` Cleaned []string `json:"cleaned,omitempty"` Summary map[string]interface{} `json:"summary"` } // FileInfo represents information about a file type FileInfo struct { Path string `json:"path"` Size int64 `json:"size"` ModTime time.Time `json:"mod_time"` IsDir bool `json:"is_dir"` Permissions string `json:"permissions"` } // CheckElement checks various states of an element // checkType can be: "exists", "visible", "enabled", "focused", "selected", "all" // timeout is in seconds, 0 means no timeout func (c *Client) CheckElement(tabID, selector, checkType string, timeout int) (*ElementCheckResult, error) { params := map[string]string{ "selector": selector, "type": checkType, } // 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("check-element", params) if err != nil { return nil, err } if !resp.Success { return nil, fmt.Errorf("failed to check element: %s", resp.Error) } // Parse the response data data, ok := resp.Data.(map[string]interface{}) if !ok { return nil, fmt.Errorf("unexpected response data type") } result := &ElementCheckResult{} if exists, ok := data["exists"].(bool); ok { result.Exists = exists } if visible, ok := data["visible"].(bool); ok { result.Visible = visible } if enabled, ok := data["enabled"].(bool); ok { result.Enabled = enabled } if focused, ok := data["focused"].(bool); ok { result.Focused = focused } if selected, ok := data["selected"].(bool); ok { result.Selected = selected } if count, ok := data["count"].(float64); ok { result.Count = int(count) } return result, nil } // GetElementAttributes gets attributes, properties, and computed styles of an element // attributes can be a comma-separated list of attribute names or "all" for common attributes // Use prefixes: "style_" for computed styles, "prop_" for JavaScript properties // timeout is in seconds, 0 means no timeout func (c *Client) GetElementAttributes(tabID, selector, attributes string, timeout int) (map[string]interface{}, error) { params := map[string]string{ "selector": selector, "attributes": attributes, } // 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("get-element-attributes", params) if err != nil { return nil, err } if !resp.Success { return nil, fmt.Errorf("failed to get element attributes: %s", resp.Error) } // Parse the response data result, ok := resp.Data.(map[string]interface{}) if !ok { return nil, fmt.Errorf("unexpected response data type") } return result, nil } // CountElements counts the number of elements matching a selector // timeout is in seconds, 0 means no timeout func (c *Client) CountElements(tabID, selector string, timeout int) (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("count-elements", params) if err != nil { return 0, err } if !resp.Success { return 0, fmt.Errorf("failed to count elements: %s", resp.Error) } // Parse the response data count, ok := resp.Data.(float64) if !ok { return 0, fmt.Errorf("unexpected response data type") } return int(count), nil } // ExtractMultiple extracts data from multiple selectors in a single call // selectors should be a map[string]string where keys are labels and values are CSS selectors // timeout is in seconds, 0 means no timeout func (c *Client) ExtractMultiple(tabID string, selectors map[string]string, timeout int) (*MultipleExtractionResult, error) { // Convert selectors map to JSON selectorsJSON, err := json.Marshal(selectors) if err != nil { return nil, fmt.Errorf("failed to marshal selectors: %w", err) } params := map[string]string{ "selectors": string(selectorsJSON), } // 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("extract-multiple", params) if err != nil { return nil, err } if !resp.Success { return nil, fmt.Errorf("failed to extract multiple: %s", resp.Error) } // Parse the response data data, ok := resp.Data.(map[string]interface{}) if !ok { return nil, fmt.Errorf("unexpected response data type") } result := &MultipleExtractionResult{ Results: make(map[string]interface{}), Errors: make(map[string]string), } if results, ok := data["results"].(map[string]interface{}); ok { result.Results = results } if errors, ok := data["errors"].(map[string]interface{}); ok { for key, value := range errors { if errorStr, ok := value.(string); ok { result.Errors[key] = errorStr } } } return result, nil } // ExtractLinks extracts all links from the page with optional filtering // containerSelector: optional CSS selector to limit search to a container (empty for entire page) // hrefPattern: optional regex pattern to filter links by href (empty for no filtering) // textPattern: optional regex pattern to filter links by text content (empty for no filtering) // timeout is in seconds, 0 means no timeout func (c *Client) ExtractLinks(tabID, containerSelector, hrefPattern, textPattern string, timeout int) (*LinksExtractionResult, error) { params := map[string]string{} // Add optional parameters if containerSelector != "" { params["selector"] = containerSelector } if hrefPattern != "" { params["href-pattern"] = hrefPattern } if textPattern != "" { params["text-pattern"] = textPattern } // 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("extract-links", params) if err != nil { return nil, err } if !resp.Success { return nil, fmt.Errorf("failed to extract links: %s", resp.Error) } // Parse the response data data, ok := resp.Data.(map[string]interface{}) if !ok { return nil, fmt.Errorf("unexpected response data type") } result := &LinksExtractionResult{ Links: make([]LinkInfo, 0), Count: 0, } if count, ok := data["count"].(float64); ok { result.Count = int(count) } if linksData, ok := data["links"].([]interface{}); ok { for _, linkInterface := range linksData { if linkMap, ok := linkInterface.(map[string]interface{}); ok { linkInfo := LinkInfo{} if href, ok := linkMap["href"].(string); ok { linkInfo.Href = href } if text, ok := linkMap["text"].(string); ok { linkInfo.Text = text } if title, ok := linkMap["title"].(string); ok { linkInfo.Title = title } if target, ok := linkMap["target"].(string); ok { linkInfo.Target = target } result.Links = append(result.Links, linkInfo) } } } return result, nil } // ExtractTable extracts table data as structured JSON // selector: CSS selector for the table element // includeHeaders: whether to extract and use headers for structured data // timeout is in seconds, 0 means no timeout func (c *Client) ExtractTable(tabID, selector string, includeHeaders bool, timeout int) (*TableExtractionResult, error) { params := map[string]string{ "selector": selector, "include-headers": strconv.FormatBool(includeHeaders), } // 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("extract-table", params) if err != nil { return nil, err } if !resp.Success { return nil, fmt.Errorf("failed to extract table: %s", resp.Error) } // Parse the response data data, ok := resp.Data.(map[string]interface{}) if !ok { return nil, fmt.Errorf("unexpected response data type") } result := &TableExtractionResult{ Rows: make([][]string, 0), } if count, ok := data["count"].(float64); ok { result.Count = int(count) } // Parse headers if present if headersData, ok := data["headers"].([]interface{}); ok { headers := make([]string, 0) for _, headerInterface := range headersData { if header, ok := headerInterface.(string); ok { headers = append(headers, header) } } result.Headers = headers } // Parse rows if rowsData, ok := data["rows"].([]interface{}); ok { for _, rowInterface := range rowsData { if rowArray, ok := rowInterface.([]interface{}); ok { row := make([]string, 0) for _, cellInterface := range rowArray { if cell, ok := cellInterface.(string); ok { row = append(row, cell) } } result.Rows = append(result.Rows, row) } } } // Parse structured data if present if dataArray, ok := data["data"].([]interface{}); ok { structuredData := make([]map[string]string, 0) for _, dataInterface := range dataArray { if dataMap, ok := dataInterface.(map[string]interface{}); ok { rowMap := make(map[string]string) for key, value := range dataMap { if valueStr, ok := value.(string); ok { rowMap[key] = valueStr } } structuredData = append(structuredData, rowMap) } } result.Data = structuredData } return result, nil } // ExtractText extracts text content with optional pattern matching // selector: CSS selector for elements to extract text from // pattern: optional regex pattern to match within the extracted text (empty for no pattern matching) // extractType: type of text extraction - "text", "innerText", "textContent" (default: "textContent") // timeout is in seconds, 0 means no timeout func (c *Client) ExtractText(tabID, selector, pattern, extractType string, timeout int) (*TextExtractionResult, error) { params := map[string]string{ "selector": selector, } // Add optional parameters if pattern != "" { params["pattern"] = pattern } if extractType != "" { params["type"] = extractType } // 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("extract-text", params) if err != nil { return nil, err } if !resp.Success { return nil, fmt.Errorf("failed to extract text: %s", resp.Error) } // Parse the response data data, ok := resp.Data.(map[string]interface{}) if !ok { return nil, fmt.Errorf("unexpected response data type") } result := &TextExtractionResult{} if text, ok := data["text"].(string); ok { result.Text = text } if count, ok := data["count"].(float64); ok { result.Count = int(count) } // Parse matches if present if matchesData, ok := data["matches"].([]interface{}); ok { matches := make([]string, 0) for _, matchInterface := range matchesData { if match, ok := matchInterface.(string); ok { matches = append(matches, match) } } result.Matches = matches } return result, nil } // AnalyzeForm analyzes a form and returns detailed information about its fields // selector: CSS selector for the form element // timeout is in seconds, 0 means no timeout func (c *Client) AnalyzeForm(tabID, selector string, timeout int) (*FormAnalysisResult, 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("analyze-form", params) if err != nil { return nil, err } if !resp.Success { return nil, fmt.Errorf("failed to analyze form: %s", resp.Error) } // Parse the response data data, ok := resp.Data.(map[string]interface{}) if !ok { return nil, fmt.Errorf("unexpected response data type") } result := &FormAnalysisResult{ Fields: make([]FormField, 0), } if action, ok := data["action"].(string); ok { result.Action = action } if method, ok := data["method"].(string); ok { result.Method = method } if fieldCount, ok := data["field_count"].(float64); ok { result.FieldCount = int(fieldCount) } if canSubmit, ok := data["can_submit"].(bool); ok { result.CanSubmit = canSubmit } if submitText, ok := data["submit_text"].(string); ok { result.SubmitText = submitText } // Parse fields if fieldsData, ok := data["fields"].([]interface{}); ok { for _, fieldInterface := range fieldsData { if fieldMap, ok := fieldInterface.(map[string]interface{}); ok { field := FormField{} if name, ok := fieldMap["name"].(string); ok { field.Name = name } if fieldType, ok := fieldMap["type"].(string); ok { field.Type = fieldType } if value, ok := fieldMap["value"].(string); ok { field.Value = value } if placeholder, ok := fieldMap["placeholder"].(string); ok { field.Placeholder = placeholder } if required, ok := fieldMap["required"].(bool); ok { field.Required = required } if disabled, ok := fieldMap["disabled"].(bool); ok { field.Disabled = disabled } if readonly, ok := fieldMap["readonly"].(bool); ok { field.ReadOnly = readonly } if selector, ok := fieldMap["selector"].(string); ok { field.Selector = selector } if label, ok := fieldMap["label"].(string); ok { field.Label = label } // Parse options if present if optionsData, ok := fieldMap["options"].([]interface{}); ok { options := make([]FormFieldOption, 0) for _, optionInterface := range optionsData { if optionMap, ok := optionInterface.(map[string]interface{}); ok { option := FormFieldOption{} if value, ok := optionMap["value"].(string); ok { option.Value = value } if text, ok := optionMap["text"].(string); ok { option.Text = text } if selected, ok := optionMap["selected"].(bool); ok { option.Selected = selected } options = append(options, option) } } field.Options = options } result.Fields = append(result.Fields, field) } } } return result, nil } // InteractMultiple performs multiple interactions in sequence // interactions: slice of InteractionItem specifying what actions to perform // timeout is in seconds, 0 means no timeout func (c *Client) InteractMultiple(tabID string, interactions []InteractionItem, timeout int) (*MultipleInteractionResult, error) { // Convert interactions to JSON interactionsJSON, err := json.Marshal(interactions) if err != nil { return nil, fmt.Errorf("failed to marshal interactions: %w", err) } params := map[string]string{ "interactions": string(interactionsJSON), } // 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("interact-multiple", params) if err != nil { return nil, err } if !resp.Success { return nil, fmt.Errorf("failed to perform multiple interactions: %s", resp.Error) } // Parse the response data data, ok := resp.Data.(map[string]interface{}) if !ok { return nil, fmt.Errorf("unexpected response data type") } result := &MultipleInteractionResult{ Results: make([]InteractionResult, 0), } if successCount, ok := data["success_count"].(float64); ok { result.SuccessCount = int(successCount) } if errorCount, ok := data["error_count"].(float64); ok { result.ErrorCount = int(errorCount) } if totalCount, ok := data["total_count"].(float64); ok { result.TotalCount = int(totalCount) } // Parse results if resultsData, ok := data["results"].([]interface{}); ok { for _, resultInterface := range resultsData { if resultMap, ok := resultInterface.(map[string]interface{}); ok { interactionResult := InteractionResult{} if selector, ok := resultMap["selector"].(string); ok { interactionResult.Selector = selector } if action, ok := resultMap["action"].(string); ok { interactionResult.Action = action } if success, ok := resultMap["success"].(bool); ok { interactionResult.Success = success } if errorMsg, ok := resultMap["error"].(string); ok { interactionResult.Error = errorMsg } result.Results = append(result.Results, interactionResult) } } } return result, nil } // FillFormBulk fills multiple form fields in a single operation // formSelector: CSS selector for the form element (optional, can be empty to search entire page) // fields: map of field names/selectors to values // timeout is in seconds, 0 means no timeout func (c *Client) FillFormBulk(tabID, formSelector string, fields map[string]string, timeout int) (*FormBulkFillResult, error) { // Convert fields to JSON fieldsJSON, err := json.Marshal(fields) if err != nil { return nil, fmt.Errorf("failed to marshal fields: %w", err) } params := map[string]string{ "fields": string(fieldsJSON), } // Add form selector if provided if formSelector != "" { params["form-selector"] = formSelector } // 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("fill-form-bulk", params) if err != nil { return nil, err } if !resp.Success { return nil, fmt.Errorf("failed to fill form bulk: %s", resp.Error) } // Parse the response data data, ok := resp.Data.(map[string]interface{}) if !ok { return nil, fmt.Errorf("unexpected response data type") } result := &FormBulkFillResult{ FilledFields: make([]InteractionResult, 0), } if successCount, ok := data["success_count"].(float64); ok { result.SuccessCount = int(successCount) } if errorCount, ok := data["error_count"].(float64); ok { result.ErrorCount = int(errorCount) } if totalCount, ok := data["total_count"].(float64); ok { result.TotalCount = int(totalCount) } // Parse filled fields if fieldsData, ok := data["filled_fields"].([]interface{}); ok { for _, fieldInterface := range fieldsData { if fieldMap, ok := fieldInterface.(map[string]interface{}); ok { fieldResult := InteractionResult{} if selector, ok := fieldMap["selector"].(string); ok { fieldResult.Selector = selector } if action, ok := fieldMap["action"].(string); ok { fieldResult.Action = action } if success, ok := fieldMap["success"].(bool); ok { fieldResult.Success = success } if errorMsg, ok := fieldMap["error"].(string); ok { fieldResult.Error = errorMsg } result.FilledFields = append(result.FilledFields, fieldResult) } } } return result, nil } // GetPageInfo retrieves comprehensive page metadata and state information func (c *Client) GetPageInfo(tabID string, timeout int) (*PageInfo, error) { params := map[string]string{} // 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("get-page-info", params) if err != nil { return nil, err } if !resp.Success { return nil, fmt.Errorf("failed to get page info: %s", resp.Error) } // Parse the response data data, ok := resp.Data.(map[string]interface{}) if !ok { return nil, fmt.Errorf("unexpected response data type") } result := &PageInfo{} if title, ok := data["title"].(string); ok { result.Title = title } if url, ok := data["url"].(string); ok { result.URL = url } if loadingState, ok := data["loading_state"].(string); ok { result.LoadingState = loadingState } if readyState, ok := data["ready_state"].(string); ok { result.ReadyState = readyState } if referrer, ok := data["referrer"].(string); ok { result.Referrer = referrer } if domain, ok := data["domain"].(string); ok { result.Domain = domain } if protocol, ok := data["protocol"].(string); ok { result.Protocol = protocol } if charset, ok := data["charset"].(string); ok { result.Charset = charset } if contentType, ok := data["content_type"].(string); ok { result.ContentType = contentType } if lastModified, ok := data["last_modified"].(string); ok { result.LastModified = lastModified } if cookieEnabled, ok := data["cookie_enabled"].(bool); ok { result.CookieEnabled = cookieEnabled } if onlineStatus, ok := data["online_status"].(bool); ok { result.OnlineStatus = onlineStatus } return result, nil } // GetViewportInfo retrieves viewport and scroll information func (c *Client) GetViewportInfo(tabID string, timeout int) (*ViewportInfo, error) { params := map[string]string{} // 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("get-viewport-info", params) if err != nil { return nil, err } if !resp.Success { return nil, fmt.Errorf("failed to get viewport info: %s", resp.Error) } // Parse the response data data, ok := resp.Data.(map[string]interface{}) if !ok { return nil, fmt.Errorf("unexpected response data type") } result := &ViewportInfo{} if width, ok := data["width"].(float64); ok { result.Width = int(width) } if height, ok := data["height"].(float64); ok { result.Height = int(height) } if scrollX, ok := data["scroll_x"].(float64); ok { result.ScrollX = int(scrollX) } if scrollY, ok := data["scroll_y"].(float64); ok { result.ScrollY = int(scrollY) } if scrollWidth, ok := data["scroll_width"].(float64); ok { result.ScrollWidth = int(scrollWidth) } if scrollHeight, ok := data["scroll_height"].(float64); ok { result.ScrollHeight = int(scrollHeight) } if clientWidth, ok := data["client_width"].(float64); ok { result.ClientWidth = int(clientWidth) } if clientHeight, ok := data["client_height"].(float64); ok { result.ClientHeight = int(clientHeight) } if devicePixelRatio, ok := data["device_pixel_ratio"].(float64); ok { result.DevicePixelRatio = devicePixelRatio } if orientation, ok := data["orientation"].(string); ok { result.Orientation = orientation } return result, nil } // GetPerformance retrieves page performance metrics func (c *Client) GetPerformance(tabID string, timeout int) (*PerformanceMetrics, error) { params := map[string]string{} // 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("get-performance", params) if err != nil { return nil, err } if !resp.Success { return nil, fmt.Errorf("failed to get performance metrics: %s", resp.Error) } // Parse the response data data, ok := resp.Data.(map[string]interface{}) if !ok { return nil, fmt.Errorf("unexpected response data type") } result := &PerformanceMetrics{} if navigationStart, ok := data["navigation_start"].(float64); ok { result.NavigationStart = int64(navigationStart) } if loadEventEnd, ok := data["load_event_end"].(float64); ok { result.LoadEventEnd = int64(loadEventEnd) } if domContentLoaded, ok := data["dom_content_loaded"].(float64); ok { result.DOMContentLoaded = int64(domContentLoaded) } if firstPaint, ok := data["first_paint"].(float64); ok { result.FirstPaint = int64(firstPaint) } if firstContentfulPaint, ok := data["first_contentful_paint"].(float64); ok { result.FirstContentfulPaint = int64(firstContentfulPaint) } if loadTime, ok := data["load_time"].(float64); ok { result.LoadTime = int64(loadTime) } if domLoadTime, ok := data["dom_load_time"].(float64); ok { result.DOMLoadTime = int64(domLoadTime) } if resourceCount, ok := data["resource_count"].(float64); ok { result.ResourceCount = int(resourceCount) } if jsHeapSizeLimit, ok := data["js_heap_size_limit"].(float64); ok { result.JSHeapSizeLimit = int64(jsHeapSizeLimit) } if jsHeapSizeTotal, ok := data["js_heap_size_total"].(float64); ok { result.JSHeapSizeTotal = int64(jsHeapSizeTotal) } if jsHeapSizeUsed, ok := data["js_heap_size_used"].(float64); ok { result.JSHeapSizeUsed = int64(jsHeapSizeUsed) } return result, nil } // CheckContent verifies specific content types and loading states // contentType can be: "images", "scripts", "styles", "forms", "links", "iframes", "errors" func (c *Client) CheckContent(tabID string, contentType string, timeout int) (*ContentCheck, error) { params := map[string]string{ "type": contentType, } // 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("check-content", params) if err != nil { return nil, err } if !resp.Success { return nil, fmt.Errorf("failed to check content: %s", resp.Error) } // Parse the response data data, ok := resp.Data.(map[string]interface{}) if !ok { return nil, fmt.Errorf("unexpected response data type") } result := &ContentCheck{} if contentTypeResult, ok := data["type"].(string); ok { result.Type = contentTypeResult } if imagesLoaded, ok := data["images_loaded"].(float64); ok { result.ImagesLoaded = int(imagesLoaded) } if imagesTotal, ok := data["images_total"].(float64); ok { result.ImagesTotal = int(imagesTotal) } if scriptsLoaded, ok := data["scripts_loaded"].(float64); ok { result.ScriptsLoaded = int(scriptsLoaded) } if scriptsTotal, ok := data["scripts_total"].(float64); ok { result.ScriptsTotal = int(scriptsTotal) } if stylesLoaded, ok := data["styles_loaded"].(float64); ok { result.StylesLoaded = int(stylesLoaded) } if stylesTotal, ok := data["styles_total"].(float64); ok { result.StylesTotal = int(stylesTotal) } if formsPresent, ok := data["forms_present"].(float64); ok { result.FormsPresent = int(formsPresent) } if linksPresent, ok := data["links_present"].(float64); ok { result.LinksPresent = int(linksPresent) } if iframesPresent, ok := data["iframes_present"].(float64); ok { result.IframesPresent = int(iframesPresent) } if hasErrors, ok := data["has_errors"].(bool); ok { result.HasErrors = hasErrors } if errorCount, ok := data["error_count"].(float64); ok { result.ErrorCount = int(errorCount) } if errorMessages, ok := data["error_messages"].([]interface{}); ok { for _, msg := range errorMessages { if msgStr, ok := msg.(string); ok { result.ErrorMessages = append(result.ErrorMessages, msgStr) } } } return result, nil } // ScreenshotElement takes a screenshot of a specific element // If tabID is empty, the current tab will be used // timeout is in seconds, 0 means no timeout func (c *Client) ScreenshotElement(tabID, selector, outputPath string, timeout int) error { params := map[string]string{ "selector": selector, "output": outputPath, } // 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("screenshot-element", params) if err != nil { return err } if !resp.Success { return fmt.Errorf("failed to take element screenshot: %s", resp.Error) } return nil } // Accessibility tree data structures (matching daemon types) // AXNode represents a node in the accessibility tree type AXNode struct { NodeID string `json:"nodeId"` Ignored bool `json:"ignored"` IgnoredReasons []AXProperty `json:"ignoredReasons,omitempty"` Role *AXValue `json:"role,omitempty"` ChromeRole *AXValue `json:"chromeRole,omitempty"` Name *AXValue `json:"name,omitempty"` Description *AXValue `json:"description,omitempty"` Value *AXValue `json:"value,omitempty"` Properties []AXProperty `json:"properties,omitempty"` ParentID string `json:"parentId,omitempty"` ChildIDs []string `json:"childIds,omitempty"` BackendDOMNodeID int `json:"backendDOMNodeId,omitempty"` FrameID string `json:"frameId,omitempty"` } // AXProperty represents a property of an accessibility node type AXProperty struct { Name string `json:"name"` Value *AXValue `json:"value"` } // AXValue represents a computed accessibility value type AXValue struct { Type string `json:"type"` Value interface{} `json:"value,omitempty"` RelatedNodes []AXRelatedNode `json:"relatedNodes,omitempty"` Sources []AXValueSource `json:"sources,omitempty"` } // AXRelatedNode represents a related node in the accessibility tree type AXRelatedNode struct { BackendDOMNodeID int `json:"backendDOMNodeId"` IDRef string `json:"idref,omitempty"` Text string `json:"text,omitempty"` } // AXValueSource represents a source for a computed accessibility value type AXValueSource struct { Type string `json:"type"` Value *AXValue `json:"value,omitempty"` Attribute string `json:"attribute,omitempty"` AttributeValue *AXValue `json:"attributeValue,omitempty"` Superseded bool `json:"superseded,omitempty"` NativeSource string `json:"nativeSource,omitempty"` NativeSourceValue *AXValue `json:"nativeSourceValue,omitempty"` Invalid bool `json:"invalid,omitempty"` InvalidReason string `json:"invalidReason,omitempty"` } // AccessibilityTreeResult represents the result of accessibility tree operations type AccessibilityTreeResult struct { Nodes []AXNode `json:"nodes"` } // AccessibilityQueryResult represents the result of accessibility queries type AccessibilityQueryResult struct { Nodes []AXNode `json:"nodes"` } // GetAccessibilityTree retrieves the full accessibility tree for a tab // If tabID is empty, the current tab will be used // depth limits the tree depth (optional, nil for full tree) // timeout is in seconds, 0 means no timeout func (c *Client) GetAccessibilityTree(tabID string, depth *int, timeout int) (*AccessibilityTreeResult, error) { params := map[string]string{} // Only include tab ID if it's provided if tabID != "" { params["tab"] = tabID } // Add depth if specified if depth != nil { params["depth"] = strconv.Itoa(*depth) } // Add timeout if specified if timeout > 0 { params["timeout"] = strconv.Itoa(timeout) } resp, err := c.SendCommand("get-accessibility-tree", params) if err != nil { return nil, err } if !resp.Success { return nil, fmt.Errorf("failed to get accessibility tree: %s", resp.Error) } // Parse the response data var result AccessibilityTreeResult dataBytes, err := json.Marshal(resp.Data) if err != nil { return nil, fmt.Errorf("failed to marshal response data: %w", err) } err = json.Unmarshal(dataBytes, &result) if err != nil { return nil, fmt.Errorf("failed to unmarshal accessibility tree result: %w", err) } return &result, nil } // GetPartialAccessibilityTree retrieves a partial accessibility tree for a specific element // If tabID is empty, the current tab will be used // selector is the CSS selector for the element to get the tree for // fetchRelatives determines whether to include ancestors, siblings, and children // timeout is in seconds, 0 means no timeout func (c *Client) GetPartialAccessibilityTree(tabID, selector string, fetchRelatives bool, timeout int) (*AccessibilityTreeResult, error) { params := map[string]string{ "selector": selector, "fetch-relatives": strconv.FormatBool(fetchRelatives), } // 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("get-partial-accessibility-tree", params) if err != nil { return nil, err } if !resp.Success { return nil, fmt.Errorf("failed to get partial accessibility tree: %s", resp.Error) } // Parse the response data var result AccessibilityTreeResult dataBytes, err := json.Marshal(resp.Data) if err != nil { return nil, fmt.Errorf("failed to marshal response data: %w", err) } err = json.Unmarshal(dataBytes, &result) if err != nil { return nil, fmt.Errorf("failed to unmarshal accessibility tree result: %w", err) } return &result, nil } // QueryAccessibilityTree queries the accessibility tree for nodes matching specific criteria // If tabID is empty, the current tab will be used // selector is optional CSS selector to limit the search scope // accessibleName is optional accessible name to match // role is optional role to match // timeout is in seconds, 0 means no timeout func (c *Client) QueryAccessibilityTree(tabID, selector, accessibleName, role string, timeout int) (*AccessibilityQueryResult, error) { params := map[string]string{} // Only include tab ID if it's provided if tabID != "" { params["tab"] = tabID } // Add optional parameters if selector != "" { params["selector"] = selector } if accessibleName != "" { params["accessible-name"] = accessibleName } if role != "" { params["role"] = role } // Add timeout if specified if timeout > 0 { params["timeout"] = strconv.Itoa(timeout) } resp, err := c.SendCommand("query-accessibility-tree", params) if err != nil { return nil, err } if !resp.Success { return nil, fmt.Errorf("failed to query accessibility tree: %s", resp.Error) } // Parse the response data var result AccessibilityQueryResult dataBytes, err := json.Marshal(resp.Data) if err != nil { return nil, fmt.Errorf("failed to marshal response data: %w", err) } err = json.Unmarshal(dataBytes, &result) if err != nil { return nil, fmt.Errorf("failed to unmarshal accessibility query result: %w", err) } return &result, nil } // ScreenshotEnhanced takes a screenshot with metadata // If tabID is empty, the current tab will be used // timeout is in seconds, 0 means no timeout func (c *Client) ScreenshotEnhanced(tabID, outputPath string, fullPage bool, timeout int) (*ScreenshotMetadata, error) { params := map[string]string{ "output": outputPath, "full-page": strconv.FormatBool(fullPage), } // 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("screenshot-enhanced", params) if err != nil { return nil, err } if !resp.Success { return nil, fmt.Errorf("failed to take enhanced screenshot: %s", resp.Error) } // Parse the response data var metadata ScreenshotMetadata dataBytes, err := json.Marshal(resp.Data) if err != nil { return nil, fmt.Errorf("failed to marshal response data: %w", err) } err = json.Unmarshal(dataBytes, &metadata) if err != nil { return nil, fmt.Errorf("failed to parse screenshot metadata: %w", err) } return &metadata, nil } // BulkFiles performs bulk file operations (upload/download) // operationType: "upload" or "download" // operations: slice of FileOperation structs // timeout is in seconds, 0 means no timeout (default 30s for bulk operations) func (c *Client) BulkFiles(operationType string, operations []FileOperation, timeout int) (*BulkFileResult, error) { // Convert operations to JSON operationsJSON, err := json.Marshal(operations) if err != nil { return nil, fmt.Errorf("failed to marshal operations: %w", err) } params := map[string]string{ "operation": operationType, "files": string(operationsJSON), } // Add timeout if specified (default to 30 seconds for bulk operations) if timeout > 0 { params["timeout"] = strconv.Itoa(timeout) } resp, err := c.SendCommand("bulk-files", params) if err != nil { return nil, err } if !resp.Success { return nil, fmt.Errorf("failed to perform bulk file operations: %s", resp.Error) } // Parse the response data var result BulkFileResult dataBytes, err := json.Marshal(resp.Data) if err != nil { return nil, fmt.Errorf("failed to marshal response data: %w", err) } err = json.Unmarshal(dataBytes, &result) if err != nil { return nil, fmt.Errorf("failed to parse bulk file result: %w", err) } return &result, nil } // ManageFiles performs file management operations // operation: "cleanup", "list", or "info" // pattern: file pattern for cleanup/list operations, or file path for info // maxAge: max age in hours for cleanup operations (optional) func (c *Client) ManageFiles(operation, pattern, maxAge string) (*FileManagementResult, error) { params := map[string]string{ "operation": operation, } // Add optional parameters if pattern != "" { params["pattern"] = pattern } if maxAge != "" { params["max-age"] = maxAge } resp, err := c.SendCommand("manage-files", params) if err != nil { return nil, err } if !resp.Success { return nil, fmt.Errorf("failed to manage files: %s", resp.Error) } // Parse the response data var result FileManagementResult dataBytes, err := json.Marshal(resp.Data) if err != nil { return nil, fmt.Errorf("failed to marshal response data: %w", err) } err = json.Unmarshal(dataBytes, &result) if err != nil { return nil, fmt.Errorf("failed to parse file management result: %w", err) } return &result, nil }