go-playwright-testkit

module
v0.0.0-...-2e3c39d Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 27, 2026 License: MIT

README ¶

go-playwright-testkit

CI Go Report Card GoDoc

Battle-tested Playwright wrapper for Go with graceful degradation and observability.

Extracted from a production Hugo site with 45+ test functions, this library provides clean abstractions over playwright-go with patterns proven in real-world browser testing.

Features

  • 🎭 Clean Playwright wrapper - Simple API over playwright-go bindings
  • 🔄 Graceful degradation - Tests skip (not fail) when Playwright unavailable
  • ✅ Battle-tested patterns - Error handling and timeouts tuned from production
  • 🧪 TestContext abstraction - Unified test setup with pluggable server support
  • 📊 OpenTelemetry integration - Optional observability (Logfire, Jaeger, custom)
  • ♿ Accessibility scanning - Optional axe-core integration with pluggable reporting
  • 🚀 Production-ready - Proven in real-world browser testing scenarios

Installation

go get github.com/pwarnock/go-playwright-testkit
Prerequisites

Install Playwright browsers:

go run github.com/playwright-community/playwright-go/cmd/playwright@latest install --with-deps chromium

Quick Start

package main_test

import (
    "testing"
    "github.com/pwarnock/go-playwright-testkit/pkg/browser"
)

func TestBasicNavigation(t *testing.T) {
    browser.SkipIfUnavailable(t)

    b, err := browser.NewBrowser()
    if err != nil {
        t.Fatal(err)
    }
    defer b.Close()

    err = b.NavigateTo("https://example.com")
    if err != nil {
        t.Fatal(err)
    }

    url := b.GetURL()
    if url != "https://example.com/" {
        t.Errorf("Expected https://example.com/, got %s", url)
    }
}

Core Concepts

Browser Wrapper

The browser package provides a clean interface to Playwright:

// Create browser with default options (headless=true)
b, err := browser.NewBrowser()

// Or with custom options
opts := browser.BrowserOptions{
    Headless: false, // Show browser window
}
b, err := browser.NewBrowserWithOptions(opts)

// Navigate and interact
b.NavigateTo("https://example.com")
b.ClickElement("#button")
b.WaitForSelector(".result")
visible, _ := b.IsElementVisible(".message")
b.TakeScreenshot("/tmp/screenshot.png")

Environment Variables:

  • PLAYWRIGHT_HEADLESS=false - Override default headless mode
Graceful Degradation

Tests automatically skip when Playwright is not available:

func TestMyFeature(t *testing.T) {
    browser.SkipIfUnavailable(t)
    // Test only runs if Playwright is installed
}

This provides a better CI experience than hard failures, especially in environments where Playwright may not be available.

TestContext

The context package provides unified test setup with pluggable server support:

import (
    "github.com/pwarnock/go-playwright-testkit/pkg/context"
    "github.com/pwarnock/go-playwright-testkit/pkg/browser"
)

// Define your server (implements context.ServerManager)
type MyServer struct {
    server *httptest.Server
}

func (s *MyServer) Start() error {
    s.server = httptest.NewServer(...)
    return nil
}

func (s *MyServer) Stop() error {
    s.server.Close()
    return nil
}

func (s *MyServer) GetBaseURL() string {
    return s.server.URL
}

func (s *MyServer) IsReady() bool {
    return s.server != nil
}

// Use in tests
func TestWithServer(t *testing.T) {
    browser.SkipIfUnavailable(t)

    tc := context.NewTestContext(t, "")
    tc.Server = &MyServer{}

    tc.Setup()
    defer tc.Teardown()

    tc.SetupBrowser()
    tc.Browser.NavigateTo(tc.BaseURL)

    // Use assertion helpers
    tc.AssertNoError(err)
    tc.AssertEqual(expected, actual)
    tc.AssertTrue(condition)
}
Observability (Optional)

The logger and telemetry packages provide optional OpenTelemetry integration:

import (
    "context"
    "github.com/pwarnock/go-playwright-testkit/pkg/logger"
    "github.com/pwarnock/go-playwright-testkit/pkg/telemetry"
)

func TestWithTelemetry(t *testing.T) {
    ctx := context.Background()

    // Initialize OTEL if enabled (Logfire, Jaeger, or custom endpoint)
    if telemetry.IsOTELEnabled() {
        shutdown, _ := telemetry.InitOTEL(ctx)
        defer shutdown(ctx)
    }

    // Create structured logger
    logger := logger.NewStructuredLogger("MyTest")
    defer logger.Close()

    logger.Logf("Starting test")
    logger.LogPerformance("page_load", 123.45, "ms")
    logger.LogError(err, "Something failed")
}

Environment Variables:

  • LOGFIRE_TOKEN - Enable Logfire backend
  • OTEL_EXPORTER_OTLP_ENDPOINT - Custom OTEL endpoint
  • Or run Jaeger locally on localhost:4317

If none are configured, logs print to console (graceful degradation).

Accessibility Scanning (Optional)

The scanner package integrates with axe-core for accessibility testing:

import "github.com/pwarnock/go-playwright-testkit/pkg/scanner"

func TestAccessibility(t *testing.T) {
    if !scanner.IsAxeCoreAvailable() {
        t.Skip("axe-core not available")
    }

    s := scanner.NewAccessibilityScanner("http://localhost:8080")

    err := s.ScanPage("http://localhost:8080/")
    if err != nil {
        t.Fatal(err)
    }

    // Get issues by severity
    critical := s.GetCriticalIssues()
    serious := s.GetSeriousIssues()

    // Summary
    summary := s.GetSummary()
    t.Logf("Found %d issues: %d critical, %d serious",
        summary["total"], summary["critical"], summary["serious"])
}

Pluggable Issue Reporting:

Implement the IssueReporter interface to integrate with your issue tracker:

type MyIssueReporter struct {
    // Your issue tracker client
}

func (r *MyIssueReporter) ReportIssue(issue scanner.AccessibilityIssue) error {
    // Create issue in GitHub, Jira, etc.
    return nil
}

// Use it
s := scanner.NewAccessibilityScanner(baseURL)
s.SetReporter(&MyIssueReporter{})
s.ReportCriticalAndSerious() // Auto-create issues

Examples

See the examples/ directory for comprehensive usage:

API Reference

Browser Package
type Browser
    func NewBrowser() (*Browser, error)
    func NewBrowserWithOptions(opts BrowserOptions) (*Browser, error)
    func (b *Browser) Close() error
    func (b *Browser) NavigateTo(url string) error
    func (b *Browser) GetURL() string
    func (b *Browser) ClickElement(selector string) error
    func (b *Browser) WaitForSelector(selector string) error
    func (b *Browser) IsElementVisible(selector string) (bool, error)
    func (b *Browser) TakeScreenshot(path string) error
    func (b *Browser) WaitForPageLoad() error
    func (b *Browser) GetPage() playwright.Page
    func (b *Browser) SetViewport(width, height int64) error
    func (b *Browser) Evaluate(jsScript string) (interface{}, error)
    func (b *Browser) WaitForFunction(jsFunction string) error

func IsPlaywrightAvailable() bool
func SkipIfUnavailable(t *testing.T)
Context Package
type ServerManager interface {
    Start() error
    Stop() error
    GetBaseURL() string
    IsReady() bool
}

type TestContext
    func NewTestContext(t *testing.T, baseURL string) *TestContext
    func (tc *TestContext) Setup() error
    func (tc *TestContext) Teardown()
    func (tc *TestContext) SetupBrowser() error
    func (tc *TestContext) AssertNoError(err error, msgAndArgs ...interface{})
    func (tc *TestContext) AssertEqual(expected, actual interface{}, msgAndArgs ...interface{})
    func (tc *TestContext) AssertContains(s, contains interface{}, msgAndArgs ...interface{})
    func (tc *TestContext) AssertTrue(value bool, msgAndArgs ...interface{})
    func (tc *TestContext) TakeScreenshot(filename string) error
    func (tc *TestContext) TakeScreenshotOnError(testName string)
    func (tc *TestContext) WaitForServer(maxAttempts int) error
Logger Package
type StructuredLogger
    func NewStructuredLogger(testName string) *StructuredLogger
    func (sl *StructuredLogger) Logf(format string, args ...interface{})
    func (sl *StructuredLogger) LogError(err error, msg string)
    func (sl *StructuredLogger) LogPerformance(metricName string, value float64, unit string)
    func (sl *StructuredLogger) LogAccessibility(violations []interface{})
    func (sl *StructuredLogger) Close() error
    func (sl *StructuredLogger) IsOTELEnabled() bool
Telemetry Package
func InitOTEL(ctx context.Context) (func(context.Context) error, error)
func IsOTELEnabled() bool
Scanner Package
type AccessibilityIssue struct {
    ID          string
    Title       string
    Description string
    Impact      string
    Tags        []string
    Selector    string
    URL         string
    Metadata    map[string]interface{}
}

type IssueReporter interface {
    ReportIssue(issue AccessibilityIssue) error
}

type AccessibilityScanner
    func NewAccessibilityScanner(baseURL string) *AccessibilityScanner
    func (as *AccessibilityScanner) SetReporter(reporter IssueReporter)
    func (as *AccessibilityScanner) ScanPage(url string) error
    func (as *AccessibilityScanner) GetIssues() []AccessibilityIssue
    func (as *AccessibilityScanner) GetCriticalIssues() []AccessibilityIssue
    func (as *AccessibilityScanner) GetSeriousIssues() []AccessibilityIssue
    func (as *AccessibilityScanner) ReportAll() error
    func (as *AccessibilityScanner) ReportCriticalAndSerious() error
    func (as *AccessibilityScanner) GetSummary() map[string]int

func IsAxeCoreAvailable() bool

CI Integration

GitHub Actions
name: Test

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-go@v5
        with:
          go-version: '1.23'

      - name: Install Playwright
        run: |
          go run github.com/playwright-community/playwright-go/cmd/playwright@latest install --with-deps chromium

      - name: Run tests
        run: go test -v ./...
Local Development
# Install Playwright browsers
go run github.com/playwright-community/playwright-go/cmd/playwright@latest install --with-deps chromium

# Run tests
go test -v ./...

# Run with coverage
go test -v -race -coverprofile=coverage.out ./...

# View coverage
go tool cover -html=coverage.out

Design Patterns

Battle-Tested Timeouts

Timeouts are tuned from production experience:

  • Navigation: 30 seconds (handles slow servers)
  • Element interactions: 5 seconds (balance between responsiveness and reliability)
  • Page load: Uses networkidle state for complete loading
Graceful Cleanup

The Browser.Close() method handles cleanup gracefully:

  • Closes page, context, and browser in correct order
  • Continues cleanup even if one step fails
  • Logs errors without panicking
Optional Features

Core features (browser wrapper, test context) have no external dependencies beyond playwright-go.

Optional features (OTEL, accessibility) gracefully degrade when unavailable:

  • Logger works without OTEL (prints to console)
  • Scanner checks for axe-core before scanning
  • Tests skip cleanly when tools unavailable

Contributing

Contributions welcome! Please:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Please ensure:

  • Tests pass (go test ./...)
  • Code is formatted (go fmt ./...)
  • Lint is clean (golangci-lint run)

License

MIT License - see LICENSE for details.

Acknowledgments

  • Built on playwright-go
  • Inspired by battle-tested patterns from production Hugo site testing
  • OpenTelemetry integration for modern observability

Directories ¶

Path Synopsis
pkg

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL