Skip to content

Go Client SDK

The Go client SDK provides programmatic browser control with full feature parity to the JavaScript and Python clients.

Installation

go get github.com/plexusone/w3pilot

Basic Usage

package main

import (
    "context"
    "log"

    w3pilot "github.com/plexusone/w3pilot"
)

func main() {
    ctx := context.Background()

    // Launch browser
    pilot, err := w3pilot.Launch(ctx)
    if err != nil {
        log.Fatal(err)
    }
    defer pilot.Quit(ctx)

    // Navigate and interact
    pilot.Go(ctx, "https://example.com")
    elem, _ := pilot.Find(ctx, "a", nil)
    elem.Click(ctx, nil)
}

Browser Control

Launching

// Default launch (visible browser)
pilot, err := w3pilot.Launch(ctx)

// Headless launch
pilot, err := w3pilot.LaunchHeadless(ctx)

// Custom options
pilot, err := w3pilot.Browser.Launch(ctx, &w3pilot.LaunchOptions{
    Headless:       true,
    Port:           9515,
    ExecutablePath: "/path/to/clicker",
})

Cleanup

// Close browser
err := pilot.Quit(ctx)

// Check if closed
if pilot.IsClosed() {
    // ...
}
// Navigate to URL
err := pilot.Go(ctx, "https://example.com")

// Get current URL
url, err := pilot.URL(ctx)

// Get page title
title, err := pilot.Title(ctx)

// History navigation
err := pilot.Back(ctx)
err := pilot.Forward(ctx)
err := pilot.Reload(ctx)

// Wait for navigation
err := pilot.WaitForNavigation(ctx, 30*time.Second)

// Wait for URL pattern
err := pilot.WaitForURL(ctx, "/dashboard", nil)

// Wait for load state
err := pilot.WaitForLoad(ctx, "networkidle", nil)

Finding Elements

By CSS Selector

// Find single element
elem, err := pilot.Find(ctx, "button.submit", nil)

// Find with timeout
elem, err := pilot.Find(ctx, "button.submit", &w3pilot.FindOptions{
    Timeout: 10 * time.Second,
})

// Find all matching elements
elements, err := pilot.FindAll(ctx, "li.item", nil)
for _, elem := range elements {
    text, _ := elem.Text(ctx)
    fmt.Println(text)
}

// Must find (panics if not found)
elem := pilot.MustFind(ctx, "button.submit")

By Semantic Selectors

Semantic selectors find elements by accessibility attributes instead of brittle CSS selectors. This is especially useful when:

  • Page structure may change but semantics remain stable
  • Working with AI assistants that describe elements by their purpose
  • Writing more maintainable tests
// Find by ARIA role and text
elem, err := pilot.Find(ctx, "", &w3pilot.FindOptions{
    Role: "button",
    Text: "Submit",
})

// Find by associated label (great for form inputs)
elem, err := pilot.Find(ctx, "", &w3pilot.FindOptions{
    Label: "Email address",
})

// Find by placeholder text
elem, err := pilot.Find(ctx, "", &w3pilot.FindOptions{
    Placeholder: "Enter your email",
})

// Find by data-testid (recommended for testing)
elem, err := pilot.Find(ctx, "", &w3pilot.FindOptions{
    TestID: "login-button",
})

// Find by image alt text
elem, err := pilot.Find(ctx, "", &w3pilot.FindOptions{
    Alt: "Company logo",
})

// Find by title attribute
elem, err := pilot.Find(ctx, "", &w3pilot.FindOptions{
    Title: "Close dialog",
})

// Find by XPath
elem, err := pilot.Find(ctx, "", &w3pilot.FindOptions{
    XPath: "//button[@type='submit']",
})

// Find element near another element
elem, err := pilot.Find(ctx, "", &w3pilot.FindOptions{
    Role: "button",
    Near: "#username-input",
})

Combining Selectors

You can combine CSS selectors with semantic filtering:

// Find within a form, then filter by role and label
elem, err := pilot.Find(ctx, "form.login", &w3pilot.FindOptions{
    Role:  "textbox",
    Label: "Password",
})

// Find all buttons within a specific container
buttons, err := pilot.FindAll(ctx, ".dialog-footer", &w3pilot.FindOptions{
    Role: "button",
})

Semantic Selector Reference

Selector Description Example Values
Role ARIA role "button", "textbox", "link", "checkbox", "menuitem"
Text Visible text content "Submit", "Learn more", "Cancel"
Label Associated label text "Email address", "Password", "Remember me"
Placeholder Input placeholder "Enter email", "Search..."
TestID data-testid attribute "login-btn", "user-avatar"
Alt Image alt text "Company logo", "Profile picture"
Title Element title attribute "Close", "More options"
XPath XPath expression "//button[@type='submit']"
Near CSS selector of nearby element "#username", ".form-group"

Find elements within a parent element:

// Find a container first
form, err := pilot.Find(ctx, "form.signup", nil)

// Then find within it
emailInput, err := form.Find(ctx, "", &w3pilot.FindOptions{
    Label: "Email",
})

// Find all checkboxes within the form
checkboxes, err := form.FindAll(ctx, "", &w3pilot.FindOptions{
    Role: "checkbox",
})

Element Interactions

Clicking

// Click (waits for actionability)
err := elem.Click(ctx, nil)

// Click with timeout
err := elem.Click(ctx, &w3pilot.ActionOptions{
    Timeout: 5 * time.Second,
})

// Double-click
err := elem.DblClick(ctx, nil)

Text Input

// Type text (appends)
err := elem.Type(ctx, "hello", nil)

// Fill text (clears first)
err := elem.Fill(ctx, "hello", nil)

// Clear input
err := elem.Clear(ctx, nil)

// Press key
err := elem.Press(ctx, "Enter", nil)

Form Controls

// Checkbox
err := elem.Check(ctx, nil)
err := elem.Uncheck(ctx, nil)

// Select dropdown
err := elem.SelectOption(ctx, w3pilot.SelectOptionValues{
    Values: []string{"option1"},
}, nil)

// File input
err := elem.SetFiles(ctx, []string{"/path/to/file.pdf"}, nil)

Other Interactions

// Hover
err := elem.Hover(ctx, nil)

// Focus
err := elem.Focus(ctx, nil)

// Scroll into view
err := elem.ScrollIntoView(ctx, nil)

// Drag and drop
err := source.DragTo(ctx, target, nil)

// Tap (touch)
err := elem.Tap(ctx, nil)

Element State

// Get text content
text, err := elem.Text(ctx)

// Get input value
value, err := elem.Value(ctx)

// Get innerHTML
html, err := elem.InnerHTML(ctx)

// Get attribute
href, err := elem.GetAttribute(ctx, "href")

// Get bounding box
box, err := elem.BoundingBox(ctx)
// box.X, box.Y, box.Width, box.Height

// State checks
visible, err := elem.IsVisible(ctx)
hidden, err := elem.IsHidden(ctx)
enabled, err := elem.IsEnabled(ctx)
checked, err := elem.IsChecked(ctx)
editable, err := elem.IsEditable(ctx)

// Accessibility
role, err := elem.Role(ctx)
label, err := elem.Label(ctx)

// Wait for state
err := elem.WaitUntil(ctx, "visible", nil)

Input Controllers

Keyboard

keyboard := pilot.Keyboard()

// Press key
err := keyboard.Press(ctx, "Enter")

// Key down/up
err := keyboard.Down(ctx, "Shift")
err := keyboard.Up(ctx, "Shift")

// Type text
err := keyboard.Type(ctx, "hello world")

Mouse

mouse := pilot.Mouse()

// Click at coordinates
err := mouse.Click(ctx, 100, 200)

// Move mouse
err := mouse.Move(ctx, 100, 200)

// Mouse button
err := mouse.Down(ctx)
err := mouse.Up(ctx)

// Scroll
err := mouse.Wheel(ctx, 0, 100)

Touch

touch := pilot.Touch()

// Tap at coordinates
err := touch.Tap(ctx, 100, 200)

Screenshots and PDF

// Screenshot
data, err := pilot.Screenshot(ctx)
os.WriteFile("page.png", data, 0644)

// Element screenshot
data, err := elem.Screenshot(ctx)

// PDF
data, err := pilot.PDF(ctx, nil)

JavaScript

// Evaluate script
result, err := pilot.Evaluate(ctx, "document.title")

// Evaluate with element
result, err := elem.Eval(ctx, "el => el.textContent")

// Add script tag
err := pilot.AddScript(ctx, "console.log('injected')", nil)

// Add stylesheet
err := pilot.AddStyle(ctx, "body { background: red }", nil)

Page Management

// Create new page
newVibe, err := pilot.NewPage(ctx)

// Get all pages
pages, err := pilot.Pages(ctx)

// Close current page
err := pilot.Close(ctx)

// Bring to front
err := pilot.BringToFront(ctx)

// Get frames
frames, err := pilot.Frames(ctx)

// Get frame by name/URL
frame, err := pilot.Frame(ctx, "iframe-name")

Browser Context

// Create new context (isolated session)
browserCtx, err := pilot.NewContext(ctx)

// Cookies
cookies, err := browserCtx.Cookies(ctx)
err := browserCtx.SetCookies(ctx, cookies)
err := browserCtx.ClearCookies(ctx)

// Storage state (cookies + localStorage only)
state, err := browserCtx.StorageState(ctx)

Storage State

Full storage state management including cookies, localStorage, and sessionStorage:

// Get complete storage state (cookies + localStorage + sessionStorage)
state, err := pilot.StorageState(ctx)

// Save to file for later restoration
jsonBytes, _ := json.Marshal(state)
os.WriteFile("storage.json", jsonBytes, 0600)

// Restore storage state from JSON
var savedState w3pilot.StorageState
json.Unmarshal(jsonBytes, &savedState)
err := pilot.SetStorageState(ctx, &savedState)

// Clear all storage (cookies, localStorage, sessionStorage)
err := pilot.ClearStorage(ctx)

The StorageState type contains:

type StorageState struct {
    Cookies []Cookie             `json:"cookies"`
    Origins []StorageStateOrigin `json:"origins"`
}

type StorageStateOrigin struct {
    Origin         string            `json:"origin"`
    LocalStorage   map[string]string `json:"localStorage"`
    SessionStorage map[string]string `json:"sessionStorage,omitempty"`
}

Init Scripts

Inject JavaScript that runs before any page scripts on every navigation:

// Add script that runs before page loads
err := pilot.AddInitScript(ctx, `window.testMode = true;`)

// Mock APIs before page scripts run
err := pilot.AddInitScript(ctx, `
    const originalFetch = window.fetch;
    window.fetch = async (url, opts) => {
        if (url.includes('/api/user')) {
            return {
                ok: true,
                json: () => Promise.resolve({ id: 1, name: 'Test User' })
            };
        }
        return originalFetch(url, opts);
    };
`)

// Disable analytics
err := pilot.AddInitScript(ctx, `
    window.gtag = () => {};
    window.analytics = { track: () => {} };
`)

// Via BrowserContext for isolated contexts
browserCtx, _ := pilot.NewContext(ctx)
err := browserCtx.AddInitScript(ctx, `window.contextId = 'isolated';`)

Tracing

Record browser actions with screenshots and DOM snapshots:

// Get tracing controller
tracing := pilot.Tracing()

// Start with options
err := tracing.Start(ctx, &w3pilot.TracingStartOptions{
    Name:        "login-test",
    Screenshots: true,
    Snapshots:   true,
    Sources:     true,
    Title:       "Login Flow Test",
})

// Perform actions to record
pilot.Go(ctx, "https://example.com")
elem, _ := pilot.Find(ctx, "button", nil)
elem.Click(ctx, nil)

// Stop and get trace data
data, err := tracing.Stop(ctx, nil)
os.WriteFile("trace.zip", data, 0600)

// Use chunks for segmented recording
err := tracing.StartChunk(ctx, &w3pilot.TracingChunkOptions{
    Name: "step-1",
})
// ... actions ...
chunkData, err := tracing.StopChunk(ctx, nil)

// Use groups for logical organization
err := tracing.StartGroup(ctx, "Login Flow", nil)
// ... login actions ...
err := tracing.StopGroup(ctx)

Emulation

// Viewport
err := pilot.SetViewport(ctx, w3pilot.Viewport{
    Width:  1920,
    Height: 1080,
})

// Media emulation
err := pilot.EmulateMedia(ctx, &w3pilot.EmulateMediaOptions{
    Media:       "print",
    ColorScheme: "dark",
})

// Geolocation
err := pilot.SetGeolocation(ctx, &w3pilot.Geolocation{
    Latitude:  37.7749,
    Longitude: -122.4194,
})

Error Handling

import "errors"

elem, err := pilot.Find(ctx, "#missing", nil)
if err != nil {
    if errors.Is(err, w3pilot.ErrElementNotFound) {
        // Element not found
    }
    if errors.Is(err, w3pilot.ErrTimeout) {
        // Timeout
    }
}

Debug Logging

W3PILOT_DEBUG=1 go run main.go
// Check debug mode
if w3pilot.Debug() {
    // ...
}

// Custom logger
logger := w3pilot.NewDebugLogger()
ctx = w3pilot.ContextWithLogger(ctx, logger)