accessibility

This commit is contained in:
Josh at WLTechBlog
2025-08-29 12:11:54 -05:00
parent 6bad614f9e
commit 7f4d8b8e84
12 changed files with 2708 additions and 1577 deletions

View File

@@ -257,8 +257,8 @@ func (c *Client) LoadURL(tabID, url string, timeout int) error {
// FillFormField fills a form field with a value
// If tabID is empty, the current tab will be used
// selectionTimeout and actionTimeout are in seconds, 0 means no timeout
func (c *Client) FillFormField(tabID, selector, value string, selectionTimeout, actionTimeout int) error {
// 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,
@@ -269,13 +269,9 @@ func (c *Client) FillFormField(tabID, selector, value string, selectionTimeout,
params["tab"] = tabID
}
// Add timeouts if specified
if selectionTimeout > 0 {
params["selection-timeout"] = strconv.Itoa(selectionTimeout)
}
if actionTimeout > 0 {
params["action-timeout"] = strconv.Itoa(actionTimeout)
// Add timeout if specified
if timeout > 0 {
params["timeout"] = strconv.Itoa(timeout)
}
resp, err := c.SendCommand("fill-form", params)
@@ -292,8 +288,8 @@ func (c *Client) FillFormField(tabID, selector, value string, selectionTimeout,
// UploadFile uploads a file to a file input
// If tabID is empty, the current tab will be used
// selectionTimeout and actionTimeout are in seconds, 0 means no timeout
func (c *Client) UploadFile(tabID, selector, filePath string, selectionTimeout, actionTimeout int) error {
// 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,
@@ -304,13 +300,9 @@ func (c *Client) UploadFile(tabID, selector, filePath string, selectionTimeout,
params["tab"] = tabID
}
// Add timeouts if specified
if selectionTimeout > 0 {
params["selection-timeout"] = strconv.Itoa(selectionTimeout)
}
if actionTimeout > 0 {
params["action-timeout"] = strconv.Itoa(actionTimeout)
// Add timeout if specified
if timeout > 0 {
params["timeout"] = strconv.Itoa(timeout)
}
resp, err := c.SendCommand("upload-file", params)
@@ -327,8 +319,8 @@ func (c *Client) UploadFile(tabID, selector, filePath string, selectionTimeout,
// SelectElement selects an option in a select dropdown
// If tabID is empty, the current tab will be used
// selectionTimeout and actionTimeout are in seconds, 0 means no timeout
func (c *Client) SelectElement(tabID, selector, value string, selectionTimeout, actionTimeout int) error {
// 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,
@@ -339,13 +331,9 @@ func (c *Client) SelectElement(tabID, selector, value string, selectionTimeout,
params["tab"] = tabID
}
// Add timeouts if specified
if selectionTimeout > 0 {
params["selection-timeout"] = strconv.Itoa(selectionTimeout)
}
if actionTimeout > 0 {
params["action-timeout"] = strconv.Itoa(actionTimeout)
// Add timeout if specified
if timeout > 0 {
params["timeout"] = strconv.Itoa(timeout)
}
resp, err := c.SendCommand("select-element", params)
@@ -362,8 +350,8 @@ func (c *Client) SelectElement(tabID, selector, value string, selectionTimeout,
// SubmitForm submits a form
// If tabID is empty, the current tab will be used
// selectionTimeout and actionTimeout are in seconds, 0 means no timeout
func (c *Client) SubmitForm(tabID, selector string, selectionTimeout, actionTimeout int) error {
// timeout is in seconds, 0 means no timeout
func (c *Client) SubmitForm(tabID, selector string, timeout int) error {
params := map[string]string{
"selector": selector,
}
@@ -373,13 +361,9 @@ func (c *Client) SubmitForm(tabID, selector string, selectionTimeout, actionTime
params["tab"] = tabID
}
// Add timeouts if specified
if selectionTimeout > 0 {
params["selection-timeout"] = strconv.Itoa(selectionTimeout)
}
if actionTimeout > 0 {
params["action-timeout"] = strconv.Itoa(actionTimeout)
// Add timeout if specified
if timeout > 0 {
params["timeout"] = strconv.Itoa(timeout)
}
resp, err := c.SendCommand("submit-form", params)
@@ -429,8 +413,8 @@ func (c *Client) GetPageSource(tabID string, timeout int) (string, error) {
// GetElementHTML gets the HTML of an element
// If tabID is empty, the current tab will be used
// selectionTimeout is in seconds, 0 means no timeout
func (c *Client) GetElementHTML(tabID, selector string, selectionTimeout int) (string, error) {
// 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,
}
@@ -441,8 +425,8 @@ func (c *Client) GetElementHTML(tabID, selector string, selectionTimeout int) (s
}
// Add timeout if specified
if selectionTimeout > 0 {
params["selection-timeout"] = strconv.Itoa(selectionTimeout)
if timeout > 0 {
params["timeout"] = strconv.Itoa(timeout)
}
resp, err := c.SendCommand("get-element", params)
@@ -644,8 +628,8 @@ func (c *Client) SwitchToMain(tabID string, timeout int) error {
// ClickElement clicks on an element
// If tabID is empty, the current tab will be used
// selectionTimeout and actionTimeout are in seconds, 0 means no timeout
func (c *Client) ClickElement(tabID, selector string, selectionTimeout, actionTimeout int) error {
// timeout is in seconds, 0 means no timeout
func (c *Client) ClickElement(tabID, selector string, timeout int) error {
params := map[string]string{
"selector": selector,
}
@@ -655,13 +639,9 @@ func (c *Client) ClickElement(tabID, selector string, selectionTimeout, actionTi
params["tab"] = tabID
}
// Add timeouts if specified
if selectionTimeout > 0 {
params["selection-timeout"] = strconv.Itoa(selectionTimeout)
}
if actionTimeout > 0 {
params["action-timeout"] = strconv.Itoa(actionTimeout)
// Add timeout if specified
if timeout > 0 {
params["timeout"] = strconv.Itoa(timeout)
}
resp, err := c.SendCommand("click-element", params)
@@ -2137,6 +2117,214 @@ func (c *Client) ScreenshotElement(tabID, selector, outputPath string, timeout i
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