DimmKirr/gowda

Go WDA client

★ 1Forks 0GoGitHub ↗Compare

README

gowda

Go client library for Apple's WebDriverAgent (WDA). A 1:1 Go equivalent of openatx/facebook-wda (Python).

Automate iOS devices — find elements, tap, swipe, launch apps, take screenshots, handle alerts, and more.

Install

go get github.com/dimmkirr/gowda

Quick Start

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/dimmkirr/gowda"
)

func main() {
	ctx := context.Background()
	client := gowda.NewClient("http://localhost:8100")

	// Check WDA status
	status, err := client.Status(ctx)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("WDA status:", status)

	// Create a session
	sess, err := client.Session(ctx, gowda.WithBundleID("com.example.app"))
	if err != nil {
		log.Fatal(err)
	}
	defer sess.Close(ctx)

	// Tap at coordinates
	sess.Tap(ctx, 200, 400)

	// Find and click an element
	sess.Select(gowda.ByName("Sign In")).Click(ctx)

	// Find element and read its text
	el, err := sess.Select(gowda.ByClassName("StaticText"), gowda.ByLabelContains("Welcome")).Get(ctx)
	if err != nil {
		log.Fatal(err)
	}
	text, _ := el.Text(ctx)
	fmt.Println("Found:", text)
}

CLI

A command-line tool is included for quick device interaction and debugging.

# Build
go build -o bin/wda ./cmd/wda

# Or with Task
task build

Set the WDA URL via environment variable or flag:

export DEVICE_URL=http://192.168.1.50:8100
# or
wda -url http://192.168.1.50:8100 status

Commands

wda status                           # WDA server status
wda screenshot -o screen.png         # Save screenshot
wda source -format xml               # Page source
wda tap 200 400                      # Tap at coordinates
wda doubletap 200 400                # Double-tap
wda swipe 100 500 100 100            # Swipe
wda home                             # Press home
wda lock / unlock                    # Lock/unlock device
wda launch com.example.app           # Launch app
wda terminate com.example.app        # Terminate app
wda current                          # Current app info
wda applist                          # Running apps
wda appstate com.example.app         # App state
wda info                             # Device info
wda battery                          # Battery info
wda orientation                      # Get orientation
wda orientation LANDSCAPE            # Set orientation
wda window-size                      # Window dimensions
wda press home                       # Press button
wda keys "hello"                     # Send keystrokes
wda alert text                       # Read alert text
wda alert accept                     # Accept alert
wda alert dismiss                    # Dismiss alert
wda alert buttons                    # List alert buttons
wda alert click Allow                # Click alert button
wda siri "open Settings"             # Activate Siri
wda clipboard get                    # Read clipboard
wda clipboard set "hello"            # Set clipboard

API Overview

Client

client := gowda.NewClient(url,
	gowda.WithHTTPTimeout(30*time.Second),
	gowda.WithHTTPClient(customHTTPClient),
)

client.Status(ctx)              // WDA server status
client.IsReady(ctx)             // Quick readiness check
client.WaitReady(ctx, timeout)  // Wait until WDA responds
client.Home(ctx)                // Press home button
client.Locked(ctx)              // Check lock state
client.Lock(ctx)                // Lock device
client.Unlock(ctx)              // Unlock device
client.Screenshot(ctx)          // PNG screenshot bytes
client.Source(ctx, "xml")       // Page source
client.AppCurrent(ctx)          // Current app info

Session

sess, _ := client.Session(ctx,
	gowda.WithBundleID("com.example.app"),
	gowda.WithArguments([]string{"-debug"}),
	gowda.WithEnvironment(map[string]string{"KEY": "VAL"}),
)
defer sess.Close(ctx)

sess.AppLaunch(ctx, bundleID)      // Launch app
sess.AppTerminate(ctx, bundleID)   // Terminate app
sess.AppActivate(ctx, bundleID)    // Bring to foreground
sess.AppState(ctx, bundleID)       // App state
sess.AppList(ctx)                  // Running apps
sess.Tap(ctx, x, y)               // Tap
sess.DoubleTap(ctx, x, y)         // Double-tap
sess.TapHold(ctx, x, y, duration) // Long press
sess.Swipe(ctx, x1, y1, x2, y2, dur) // Swipe
sess.SwipeUp(ctx)                  // Directional swipes
sess.SendKeys(ctx, "text")        // Type text
sess.Press(ctx, "home")           // Press button
sess.Orientation(ctx)             // Get orientation
sess.SetOrientation(ctx, val)     // Set orientation
sess.WindowSize(ctx)              // Window size
sess.DeviceInfo(ctx)              // Device info
sess.BatteryInfo(ctx)             // Battery info
sess.SetClipboard(ctx, text, "")  // Set clipboard
sess.GetClipboard(ctx)            // Get clipboard
sess.SiriActivate(ctx, "open X")  // Siri
sess.Deactivate(ctx, duration)    // Background app
sess.OpenURL(ctx, url)            // Open URL
sess.Scale(ctx)                   // UIKit scale factor
sess.ImplicitlyWait(10*time.Second) // Default find timeout

Element Finding

sel := sess.Select(
	gowda.ByName("Login"),           // Exact name match
	gowda.ByClassName("Button"),     // XCUIElementType
	gowda.ByLabel("Submit"),         // Label match
	gowda.ByLabelContains("Sub"),    // Partial label
	gowda.ByNameContains("Log"),     // Partial name
	gowda.ByNameMatches("Log.*"),    // Regex name
	gowda.ByValue("on"),             // Value match
	gowda.ByValueContains("o"),      // Partial value
	gowda.ByVisible(true),           // Visibility
	gowda.ByEnabled(true),           // Enabled state
	gowda.ByID("element-id"),        // Raw identifier
	gowda.ByXPath("//XCUIElementTypeButton"), // XPath
	gowda.ByClassChain("**/Button"), // Class chain
	gowda.ByPredicate("name == 'x'"),// Predicate string
	gowda.WithTimeout(5*time.Second),// Find timeout
	gowda.AtIndex(0),                // Element index
)

el, err := sel.Get(ctx)             // Wait and return first match
elements, _ := sel.FindElements(ctx) // All matches (no wait)
count, _ := sel.Count(ctx)          // Count matches
exists, _ := sel.Exists(ctx)        // Check existence (no wait)
sel.Click(ctx)                       // Wait and click
sel.ClickExists(ctx, 3*time.Second)  // Click if found
sel.Wait(ctx)                        // Wait, nil if not found
sel.WaitGone(ctx)                    // Wait for disappearance
sel.Child(gowda.ByClassName("Text")) // Scoped child search

Element

el.Label(ctx)                  // Label attribute
el.Text(ctx)                   // Text content
el.Name(ctx)                   // Name
el.Value(ctx)                  // Value attribute
el.ClassName(ctx)              // Element type
el.Enabled(ctx)                // Is enabled
el.Displayed(ctx)              // Is displayed
el.Visible(ctx)                // Is visible
el.Accessible(ctx)             // Is accessible
el.AccessibilityContainer(ctx) // Is accessibility container
el.Selected(ctx)               // Is selected
el.Bounds(ctx)                 // Bounding Rect
el.Tap(ctx)                    // Tap via element endpoint
el.Click(ctx)                  // Tap at center coordinates
el.TapHold(ctx, 2.0)           // Long press (seconds)
el.SetText(ctx, "hello")       // Set text
el.ClearText(ctx)              // Clear text
el.Scroll(ctx, "down", 1.0)   // Scroll (visible/up/down/left/right)
el.Pinch(ctx, 2.0, 1.0)       // Pinch (scale, velocity)

Alert

alert := sess.Alert()
alert.Exists(ctx)                     // Check if alert is showing
alert.Text(ctx)                       // Alert text
alert.SetText(ctx, "input")           // Set alert text field
alert.Accept(ctx)                     // Accept
alert.Dismiss(ctx)                    // Dismiss
alert.Buttons(ctx)                    // List button names
alert.Click(ctx, "Allow")            // Click specific button
alert.ClickAny(ctx, []string{"Allow", "OK"}) // Click first match
alert.ClickExists(ctx, buttonNames)   // Click if found, "" if not
alert.Wait(ctx, 10*time.Second)       // Wait for alert

Development

Requires Go 1.26+ and Task.

task build          # Build library and CLI
task test           # Run all tests
task lint           # Run go vet
go test ./... -run TestName -v  # Run a single test

License

MIT

Contributors

DimmKirr

Issues