3 DOM-Queries
Virgil edited this page 2026-02-19 16:55:15 +00:00

DOM Queries

This page covers how to find and inspect DOM elements using CSS selectors.

See also: Home | Getting-Started | Actions | Console-Monitoring | Angular-Testing

QuerySelector

QuerySelector finds the first element matching a CSS selector and returns an *ElementInfo struct containing the node ID, tag name, attributes, and bounding box.

elem, err := wv.QuerySelector("#main-heading")
if err != nil {
    log.Printf("element not found: %v", err)
    return
}

log.Printf("Tag: %s", elem.TagName)
log.Printf("ID: %s", elem.Attributes["id"])
log.Printf("Class: %s", elem.Attributes["class"])

if elem.BoundingBox != nil {
    log.Printf("Position: (%.0f, %.0f) Size: %.0f x %.0f",
        elem.BoundingBox.X, elem.BoundingBox.Y,
        elem.BoundingBox.Width, elem.BoundingBox.Height)
}

Under the hood, QuerySelector uses the CDP DOM.getDocument method to obtain the root node, then DOM.querySelector to find the matching element. It returns an error if no element matches.

QuerySelectorAll

QuerySelectorAll returns a slice of all elements matching the selector:

links, err := wv.QuerySelectorAll("a[href]")
if err != nil {
    log.Fatal(err)
}

for _, link := range links {
    href := link.Attributes["href"]
    text := link.Attributes["title"]
    log.Printf("Link: %s -> %s", text, href)
}

This uses DOM.querySelectorAll to retrieve all matching node IDs, then calls DOM.describeNode and DOM.getBoxModel for each to populate the ElementInfo structs.

ElementInfo

The ElementInfo struct provides everything you need to know about a DOM element:

type ElementInfo struct {
    NodeID      int               // CDP node identifier
    TagName     string            // Element tag name (e.g., "DIV", "INPUT")
    Attributes  map[string]string // All HTML attributes (id, class, href, etc.)
    InnerHTML   string            // Inner HTML content (when populated)
    InnerText   string            // Text content (when populated)
    BoundingBox *BoundingBox      // Visual position and size (may be nil)
}

type BoundingBox struct {
    X      float64 // Left edge in pixels
    Y      float64 // Top edge in pixels
    Width  float64 // Width in pixels
    Height float64 // Height in pixels
}

The BoundingBox is derived from the element's content box model via DOM.getBoxModel. It may be nil for elements that are not rendered (e.g., hidden elements or elements with display: none).

WaitForSelector

WaitForSelector polls for an element to appear in the DOM. This is useful when waiting for dynamically loaded content:

// Wait for a loading spinner to disappear and content to appear
if err := wv.WaitForSelector(".content-loaded"); err != nil {
    log.Fatal("content never appeared:", err)
}

// Now safe to query
elem, _ := wv.QuerySelector(".content-loaded h1")

The method polls every 100ms until the element is found or the timeout expires. The timeout is controlled by WithTimeout (default 30 seconds).

Getting Page Information

Several convenience methods retrieve page-level information without needing to write raw JavaScript:

// Current URL
url, err := wv.GetURL()

// Page title
title, err := wv.GetTitle()

// Outer HTML of an element (or full document if selector is empty)
html, err := wv.GetHTML(".article-body")
fullPage, err := wv.GetHTML("")

JavaScript Evaluation for Custom Queries

For queries beyond what CSS selectors can express, use Evaluate to run JavaScript directly:

// Count elements
result, err := wv.Evaluate("document.querySelectorAll('li.item').length")
if err == nil {
    count := result.(float64)
    log.Printf("Found %.0f items", count)
}

// Get computed style
result, err = wv.Evaluate(`
    getComputedStyle(document.querySelector('.hero')).backgroundColor
`)

// Check visibility
result, err = wv.Evaluate(`
    (function() {
        const el = document.querySelector('#modal');
        if (!el) return false;
        const style = getComputedStyle(el);
        return style.display !== 'none' && style.visibility !== 'hidden';
    })()
`)

Evaluate uses the CDP Runtime.evaluate method with returnByValue: true, so the result is marshalled back as a Go value. JavaScript errors are caught and returned as Go errors.

Practical Examples

Extracting a Table

rows, err := wv.QuerySelectorAll("table#results tbody tr")
if err != nil {
    log.Fatal(err)
}

for i, row := range rows {
    // Use JavaScript to get cell text for each row
    script := fmt.Sprintf(
        "document.querySelectorAll('table#results tbody tr')[%d].innerText", i)
    text, _ := wv.Evaluate(script)
    log.Printf("Row %d: %v", i, text)
}

Checking if an Element Exists

elem, err := wv.QuerySelector(".error-banner")
if err != nil {
    // Element does not exist -- no error banner is showing
    log.Println("No errors on page")
} else {
    log.Printf("Error banner found: %s", elem.Attributes["class"])
}

Waiting for Dynamic Content

wv.Navigate("https://example.com/dashboard")

// Wait for the chart to render
if err := wv.WaitForSelector("canvas.chart-rendered"); err != nil {
    log.Fatal("chart did not render")
}

// Now capture a screenshot of the rendered page
data, _ := wv.Screenshot()
os.WriteFile("dashboard.png", data, 0644)

Next Steps