capybara

package module
v0.0.0-...-e67dbb9 Latest Latest
Warning

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

Go to latest
Published: Jul 17, 2026 License: BSD-3-Clause Imports: 7 Imported by: 0

README

go-ruby-capybara/capybara

capybara — go-ruby-capybara

Docs License Go Coverage

A pure-Go (no cgo) reimplementation of the deterministic core of Ruby's capybara gem together with its default, browser-free :rack_test driver. It reproduces the acceptance-test DSL — visit, click_link, fill_in, find, has_content?, within — over an in-process HTML DOM, driving a Rack app directly, without any Ruby runtime, browser, or JavaScript.

It is the Capybara for go-embedded-ruby, but a standalone, reusable module — a sibling of the other go-ruby-* testing gems.

What it is — and isn't. Everything the rack_test driver does is deterministic and needs no interpreter: parse the last HTML response, resolve a CSS/XPath/semantic selector to a node, mutate the in-memory DOM as the "user" interacts, and reconstruct the next request (a link's href, a form's serialized fields + method + action). The application under test is a host seam — an App func mapping a *Request to a *Response. Tests inject a plain in-memory Go func and no socket is opened. The rbgo binding wires the seam to a real Ruby Rack app: the *Request becomes a Rack env hash, app.call(env) runs, and the [status, headers, body] triple becomes a *Response.

Features

Faithful port of Capybara's session DSL over the rack_test driver:

  • NavigationVisit(path), CurrentPath, CurrentURL, automatic redirect following (limit 5, then InfiniteRedirect), ClickLink, ClickButton, ClickOn.
  • FormsFillIn, Choose, Check/Uncheck, Select(value, from), AttachFile, FindField, with HTML-correct submission (GET → query string, POST → application/x-www-form-urlencoded; disabled controls, unchecked boxes, and select defaults handled as the spec requires).
  • Querying / matchersFind/FindXPath, All/AllXPath, First, HasSelector?/HasCSS?/HasXPath?/HasContent?/HasText?/HasLink?/ HasButton?/HasField? (and negations), the Assert* variants, and Within(scope, fn) to restrict a block to a subtree.
  • NodesText, Attr/AttrOr (#[]), Value, Click, Set, TagName, Checked, Selected, Visible.
  • Selector engine — an HTML parser (golang.org/x/net/html, pure Go) plus a hand-written CSS matcher (type, *, #id, .class, [attr], [a=v], ~= ^= $= *= |=, descendant/child combinators, selector lists; pseudo-classes tolerated) and a practical XPath subset (//, /, .// steps; @attr, text(), ., contains, starts-with, normalize-space, not, position, last; =, !=, and, or). Semantic Capybara selectors (:link, :button, :field, :checkbox, :radio, :select) match a string locator by id, name, label, placeholder, value, or visible text the way the gem does.

CGO-free, one pure-Go dependency (golang.org/x/net/html), 100% test coverage, gofmt + go vet clean, and green across the six 64-bit Go targets (amd64, arm64, riscv64, loong64, ppc64le, s390x — big-endian) plus js/wasm and wasip1/wasm.

Install

go get github.com/go-ruby-capybara/capybara

Usage

package main

import (
	"fmt"

	"github.com/go-ruby-capybara/capybara"
)

func main() {
	// The application under test is any func(*Request) *Response.
	app := func(req *capybara.Request) *capybara.Response {
		switch req.Path {
		case "/":
			return capybara.NewResponse(200, "", `
				<h1>Welcome</h1>
				<a href="/login">Sign in</a>`)
		case "/login":
			return capybara.NewResponse(200, "", `
				<form action="/session" method="post">
					<label for="user">Username</label>
					<input type="text" id="user" name="user">
					<button type="submit">Log in</button>
				</form>`)
		default:
			return capybara.NewResponse(200, "", "<h1>Signed in</h1>")
		}
	}

	page := capybara.New(app)
	_ = page.Visit("/")
	fmt.Println(page.HasContent("Welcome")) // true

	_ = page.ClickLink("Sign in")
	_ = page.FillIn("Username", "ada")      // by <label> text
	_ = page.ClickButton("Log in")

	fmt.Println(page.CurrentPath())          // /session
	fmt.Println(page.HasContent("Signed in")) // true
}
Scoping and assertions
_ = page.Within("#sidebar", func() error {
	return page.AssertSelector("a.active")
})

node, _ := page.Find("table#users tr.row")
fmt.Println(node.Text(), node.AttrOr("data-id", ""))

Value model

gem this package
Capybara::Session.new(:rack_test, app) capybara.New(app)
session.visit("/x") session.Visit("/x")
session.click_link / click_button session.ClickLink / ClickButton
session.fill_in(loc, with: v) session.FillIn(loc, v)
session.choose / check / uncheck / select session.Choose / Check / Uncheck / Select
session.find(sel) / all(sel) session.Find(sel) / All(sel)
session.has_content? / has_link? session.HasContent / HasLink
session.within(scope) { … } session.Within(scope, fn)
node.text / node[attr] / node.value node.Text() / node.Attr() / node.Value()
the Rack app under test App (host seam; a Go func in tests)
Capybara::ElementNotFound / Ambiguous *ElementNotFound / *Ambiguous

Tests & coverage

The suite is deterministic and browser-free: every test drives an in-memory Go App func — no network, no headless browser. Field types, redirect limits, CSS + XPath selector branches, within-scope, and the not-found / ambiguous / expectation-not-met error paths are all exercised, so every arch and OS lane holds coverage at 100%.

COVERPKG=$(go list ./... | paste -sd, -)
go test -race -coverpkg="$COVERPKG" -coverprofile=cover.out ./...
go tool cover -func=cover.out | tail -1   # 100.0%

WebAssembly

Being pure Go (CGO=0), this library also compiles to WebAssembly — both GOOS=js GOARCH=wasm (browser / Node.js) and GOOS=wasip1 GOARCH=wasm (WASI). CI builds both targets on every push, alongside the six 64-bit native/qemu arches.

GOOS=js     GOARCH=wasm go build ./...   # browser / Node
GOOS=wasip1 GOARCH=wasm go build ./...   # WASI (wasmtime, wasmer, wasmedge, …)

License

BSD-3-Clause — see LICENSE. Copyright the go-ruby-capybara/capybara authors.

Documentation

Overview

Package capybara is a pure-Go (CGO-free) reimplementation of the deterministic core of Ruby's `capybara` gem together with its default, browser-free `:rack_test` driver.

Capybara is an acceptance-test DSL: you drive a web application the way a user would — visit a path, click a link, fill in a form, assert that the resulting page contains some text — without ever touching a real browser. The default rack_test driver does this entirely in-process: it speaks to a Rack application directly (no sockets, no JavaScript), parses each HTML response, and mutates an in-memory DOM as the "user" interacts with it. Every follow-up request (a link's href, a form's action) is reconstructed from that DOM and handed back to the app.

This package reproduces that model in Go. The application under test is an injectable seam — an App func that maps a Request to a Response — so tests drive a plain Go function and never reach a network. In the go-embedded-ruby binding the seam wraps a real Ruby Rack app: the Request is converted to a Rack `env` hash, `app.call(env)` is invoked, and the `[status, headers, body]` triple is converted back into a Response.

Ruby surface it mirrors

  • Capybara::Session#visit / #current_path / #current_url
  • #click_link / #click_button / #click_on
  • #fill_in / #choose / #check / #uncheck / #select / #attach_file / #find_field
  • #find / #all / #first
  • #has_selector? / #has_content? / #has_text? / #has_link? / #has_button? / #has_field? / #has_css? / #has_xpath? and their negations
  • #assert_selector / #assert_text and friends
  • #within(scope) { ... }
  • Node#text / #[] / #value / #click / #set / #tag_name / #checked? / #selected? / #visible?

Selectors

The heart of the driver is an HTML parser (golang.org/x/net/html, pure Go) plus a query engine. Two low-level selector kinds are supported directly:

  • CSS: type, universal (*), #id, .class, [attr], [attr=v], [attr~=v], [attr^=v], [attr$=v], [attr*=v], [attr|=v], the descendant ( ) and child (>) combinators, and comma-separated selector lists.
  • XPath: a practical subset — // and / and .// steps, element or * node tests, and predicates over @attr, text(), contains(), position, and and/or.

On top of those, the semantic Capybara selectors (:link, :button, :field, :fillable_field, :checkbox, :radio_button, :select, :option, :link_or_button, :id, :css, :xpath) are implemented so that a plain string locator matches by id, name, label text, placeholder or visible text the way the gem does.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Ambiguous

type Ambiguous struct {
	Query string
	Count int
}

Ambiguous is returned when a singular finder matches more than one element. It mirrors Capybara::Ambiguous.

func (*Ambiguous) Error

func (e *Ambiguous) Error() string

type App

type App func(*Request) *Response

App is the seam between Capybara and the application under test. It is the Go analogue of a Rack app: it maps a Request to a Response. In tests it is a plain in-memory func; in the go-embedded-ruby binding it wraps a real Ruby Rack app (converting to/from the Rack `env` hash).

type ElementNotFound

type ElementNotFound struct {
	// Query is a human description of what was searched for, e.g.
	// `link "Sign in"` or `css ".btn"`.
	Query string
}

ElementNotFound is returned when a finder that expects exactly one match finds none. It mirrors Capybara::ElementNotFound.

func (*ElementNotFound) Error

func (e *ElementNotFound) Error() string

type ExpectationNotMet

type ExpectationNotMet struct {
	Message string
}

ExpectationNotMet is returned by the assert_* helpers when a positive or negative expectation fails. It mirrors Capybara::ExpectationNotMet.

func (*ExpectationNotMet) Error

func (e *ExpectationNotMet) Error() string

type InfiniteRedirect

type InfiniteRedirect struct {
	Limit int
}

InfiniteRedirect is returned when following redirects exceeds the driver's redirect limit. It mirrors Capybara::InfiniteRedirectError.

func (*InfiniteRedirect) Error

func (e *InfiniteRedirect) Error() string

type Node

type Node struct {
	// contains filtered or unexported fields
}

Node is a single element found in the current document. It mirrors Capybara::Node::Element: it wraps a DOM node and a back-reference to the Session so that interactions (click/set/...) can issue new requests.

func (*Node) Attr

func (n *Node) Attr(name string) (string, bool)

Attr returns the value of the named attribute and whether it was present. It backs the Ruby Node#[] accessor.

func (*Node) AttrOr

func (n *Node) AttrOr(name, fallback string) string

AttrOr returns the attribute value or a fallback when absent.

func (*Node) Checked

func (n *Node) Checked() bool

Checked reports whether a checkbox/radio is checked (Node#checked?).

func (*Node) Click

func (n *Node) Click() error

Click performs the element's default action, mirroring Node#click: following a link's href, or submitting the form a button belongs to. Checkboxes and radios toggle. It returns an error if the element is not clickable.

func (*Node) Selected

func (n *Node) Selected() bool

Selected reports whether an option is selected (Node#selected?).

func (*Node) Set

func (n *Node) Set(value string) error

Set assigns a value, mirroring Node#set. For text inputs and textareas it updates the value; for checkboxes/radios a bool-ish value toggles checked.

func (*Node) TagName

func (n *Node) TagName() string

TagName returns the lower-case tag name, e.g. "a", "input". It mirrors Node#tag_name.

func (*Node) Text

func (n *Node) Text() string

Text returns the normalized visible text of the node, mirroring Node#text: descendant text nodes concatenated, runs of whitespace collapsed, trimmed. Text inside <script>/<style> and non-visible elements is excluded.

func (*Node) Value

func (n *Node) Value() string

Value returns the node's value the way Node#value does: the value attribute for inputs, the concatenated text for a textarea, and the selected option's value (or values) for a select.

func (*Node) Visible

func (n *Node) Visible() bool

Visible reports whether the node is visible, applying the same coarse rules as the rack_test driver: hidden inputs, type=hidden, display:none and the hidden attribute are invisible; everything else is visible.

type ParseError

type ParseError struct {
	Err error
}

ParseError wraps a failure to parse an HTML response body.

func (*ParseError) Error

func (e *ParseError) Error() string

func (*ParseError) Unwrap

func (e *ParseError) Unwrap() error

type Request

type Request struct {
	// Method is the upper-case HTTP verb ("GET", "POST", ...).
	Method string
	// Path is the request path (PATH_INFO), always starting with "/".
	Path string
	// Query is the raw query string, without a leading "?".
	Query string
	// Body is the raw request body (rack.input).
	Body string
	// Header holds request headers such as Content-Type.
	Header http.Header
	// Scheme is "http" or "https" (rack.url_scheme).
	Scheme string
	// Host is the server name (SERVER_NAME).
	Host string
}

Request is the driver's representation of a single HTTP request to the app. It carries enough to build the equivalent Rack `env` hash in the binding.

func (*Request) RackEnv

func (r *Request) RackEnv() map[string]string

RackEnv renders the request as a Rack-style env map. The binding hands this to the wrapped Ruby app; the pure-Go tests never need it, but it documents exactly how a Request maps onto Rack and is convenient for app authors.

type Response

type Response struct {
	Status int
	Header http.Header
	Body   string
}

Response is the app's reply: an HTTP status, headers, and a body. It is the Go analogue of a Rack response triple `[status, headers, body]`.

func NewResponse

func NewResponse(status int, contentType, body string) *Response

NewResponse is a convenience constructor for app authors and tests. The Content-Type defaults to text/html when contentType is empty.

type Session

type Session struct {

	// RedirectLimit bounds automatic redirect following (Capybara default 5).
	RedirectLimit int
	// Host is the SERVER_NAME used for requests (Capybara default
	// "www.example.com"; here trimmed to "www.example.com").
	Host string
	// Scheme is the URL scheme for requests.
	Scheme string
	// contains filtered or unexported fields
}

Session drives an application under test, mirroring Capybara::Session with the default rack_test driver. Construct one with New.

func New

func New(app App) *Session

New returns a Session driving app with Capybara's default configuration.

func (*Session) All

func (s *Session) All(sel string) ([]*Node, error)

All returns every visible element matching the CSS selector (Session#all).

func (*Session) AllXPath

func (s *Session) AllXPath(sel string) ([]*Node, error)

AllXPath is All using an XPath selector.

func (*Session) AssertNoSelector

func (s *Session) AssertNoSelector(sel string) error

AssertNoSelector returns an error if any visible element matches (Session#assert_no_selector).

func (*Session) AssertNoText

func (s *Session) AssertNoText(text string) error

AssertNoText returns an error if the scope contains text (Session#assert_no_text).

func (*Session) AssertSelector

func (s *Session) AssertSelector(sel string) error

AssertSelector returns an ExpectationNotMet error unless a visible element matches the CSS selector (Session#assert_selector).

func (*Session) AssertText

func (s *Session) AssertText(text string) error

AssertText returns an ExpectationNotMet error unless the scope contains text (Session#assert_text).

func (*Session) AttachFile

func (s *Session) AttachFile(locator, path string) error

AttachFile finds a file input by locator and sets its value to path, mirroring Session#attach_file. Multipart encoding of the upload is out of scope for this subset; the path is submitted as the field value.

func (*Session) Body

func (s *Session) Body() string

Body returns the raw body of the last response.

func (*Session) Check

func (s *Session) Check(locator string) error

Check checks a checkbox by locator (Session#check).

func (*Session) Choose

func (s *Session) Choose(locator string) error

Choose selects a radio button by locator (Session#choose).

func (*Session) ClickButton

func (s *Session) ClickButton(locator string) error

ClickButton finds a button by locator and submits its form (Session#click_button).

func (s *Session) ClickLink(locator string) error

ClickLink finds a link by locator and follows its href (Session#click_link).

func (*Session) ClickOn

func (s *Session) ClickOn(locator string) error

ClickOn clicks a link or a button matching locator, whichever exists, mirroring Session#click_on / #click_link_or_button.

func (*Session) CurrentPath

func (s *Session) CurrentPath() string

CurrentPath returns the path of the current page (Session#current_path).

func (*Session) CurrentURL

func (s *Session) CurrentURL() string

CurrentURL returns the full URL of the current page (Session#current_url).

func (*Session) FillIn

func (s *Session) FillIn(locator, with string) error

FillIn finds a fillable field by locator and sets its value to with, mirroring Session#fill_in(locator, with: value).

func (*Session) Find

func (s *Session) Find(sel string) (*Node, error)

Find returns the single visible element matching the CSS selector, or an error if none (ElementNotFound) or several (Ambiguous) match. It mirrors Session#find with the default :css selector.

func (*Session) FindButton

func (s *Session) FindButton(locator string) (*Node, error)

FindButton returns the single button matching locator, mirroring find(:button, locator).

func (*Session) FindField

func (s *Session) FindField(locator string) (*Node, error)

FindField returns the single field matching locator (id, name, placeholder or label), mirroring find(:field, locator).

func (s *Session) FindLink(locator string) (*Node, error)

FindLink returns the single link matching locator (id, title, text or image alt), mirroring find(:link, locator).

func (*Session) FindXPath

func (s *Session) FindXPath(sel string) (*Node, error)

FindXPath is Find using an XPath selector (Session#find(:xpath, ...)).

func (*Session) First

func (s *Session) First(sel string) (*Node, error)

First returns the first visible element matching the CSS selector, or an error if none match (Session#first).

func (*Session) HasButton

func (s *Session) HasButton(locator string) bool

HasButton reports whether a matching button is present (Session#has_button?).

func (*Session) HasContent

func (s *Session) HasContent(text string) bool

HasContent reports whether the current scope's visible text contains text, after whitespace normalization (Session#has_content? / #has_text?).

func (*Session) HasField

func (s *Session) HasField(locator string) bool

HasField reports whether a matching field is present (Session#has_field?).

func (s *Session) HasLink(locator string) bool

HasLink reports whether a matching link is present (Session#has_link?).

func (*Session) HasNoButton

func (s *Session) HasNoButton(locator string) bool

HasNoButton is the negation of HasButton.

func (*Session) HasNoContent

func (s *Session) HasNoContent(text string) bool

HasNoContent is the negation of HasContent (Session#has_no_content?).

func (*Session) HasNoField

func (s *Session) HasNoField(locator string) bool

HasNoField is the negation of HasField.

func (s *Session) HasNoLink(locator string) bool

HasNoLink is the negation of HasLink.

func (*Session) HasNoSelector

func (s *Session) HasNoSelector(sel string) bool

HasNoSelector is the negation of HasSelector (Session#has_no_selector?).

func (*Session) HasNoXPath

func (s *Session) HasNoXPath(sel string) bool

HasNoXPath is the negation of HasXPath.

func (*Session) HasSelector

func (s *Session) HasSelector(sel string) bool

HasSelector reports whether at least one visible element matches the CSS selector (Session#has_selector? / #has_css?).

func (*Session) HasText

func (s *Session) HasText(text string) bool

HasText is an alias for HasContent.

func (*Session) HasXPath

func (s *Session) HasXPath(sel string) bool

HasXPath reports whether at least one visible element matches the XPath.

func (*Session) Response

func (s *Session) Response() *Response

Response returns the most recent raw Response, or nil before the first request. It backs Capybara's page.status_code / page.response_headers / page.body helpers.

func (*Session) Select

func (s *Session) Select(value, from string) error

Select chooses the option labelled value within the select identified by from, mirroring Session#select(value, from: locator). When the select is single-valued, previously selected options are cleared.

func (*Session) StatusCode

func (s *Session) StatusCode() int

StatusCode returns the last response's HTTP status.

func (*Session) Uncheck

func (s *Session) Uncheck(locator string) error

Uncheck unchecks a checkbox by locator (Session#uncheck).

func (*Session) Visit

func (s *Session) Visit(path string) error

Visit issues a GET request for path (which may include a query string) and makes the response the current page, mirroring Session#visit.

func (*Session) Within

func (s *Session) Within(sel string, fn func() error) error

Within restricts subsequent queries inside fn to the subtree of the single element matching the CSS selector, mirroring Session#within(scope){ }. The previous scope is restored afterwards, even if fn returns an error.

type UnselectableError

type UnselectableError struct {
	Message string
}

UnselectableError is returned when set/choose/check is called on a node whose tag cannot receive that interaction (e.g. filling in a <div>).

func (*UnselectableError) Error

func (e *UnselectableError) Error() string

Jump to

Keyboard shortcuts

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