Gost-DOM logo

Gost-DOM

The go-to solution for a web application TDD workflow

Gost-DOM is a headless browser written in Go to help build provide a fast feedback loop of the HTTP for web application development in Go. It features a DOM implemented in Go and a V8 JavaScript engine exposing the DOM to client-scripting; as well as a subset of the browser APIs.

Gost-DOM is specifically written with HTMX in mind.

How it works

Gost-DOM can eliminate the overhead TCP transport layer by consuming the HTTP handler directly. As well as improving test performance, it eliminates all complexity of managing server startup and shutdown in test code; while providing the ability for isolated parallel tests of the web application.

You can construct an instance of the Browser passing an http.Handler as argument.1

Any window opened from the browser will be instantiated with the default script engine, currently V8.2 The window provides a subset3 of the DOM, allowing developers to write the tests using a familiar syntax, the DOM.

// server.go
var MyRootHttpServer = http.DefaultServeMux

func init() {
    http.HandleFunc("GET /", func(
        w http.ResponseWriter, 
        r *http.Request) {
            w.Write([]byte(html))
        })
}

const html = `<body>
    <h1>My title</h1>
    <p>Lorem ipsum</p>
</body>`
// server.go
var MyRootHttpServer = http.DefaultServeMux

func init() {
    http.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("<body><h1>My title</h1><p>Lorem ipsum</p></body>"))
    })
}
// server_test.go
func TestMyServer(t *testing.T) {
    browser := browser.New(MyRootHttpServer)
    // Ignore errors in this example
    win, _ := browser.Open("/")
    pageTitle, _ := win
        .Document()
        .QuerySelector("h1")
    assert.Equal(t, 
        "My title", 
        pageTitle.TextContent())
}
// server_test.go
func TestMyServer(t *testing.T) {
    browser := browser.New(MyRootHttpServer)
    // Ignore errors in this example
    win, _ := browser.Open("/")
    pageTitle, _ := win.Document().QuerySelector("h1")
    assert.Equal(t, "My title", pageTitle.TextContent())
}

Selling points

Gost-DOM is an efficient tool for testing, partially because it allows bypassing the TCP stack and consume the ServeHTTP function directly.

Shaman - Helper library for more expressive tests

An unrelease side project, Shaman, is in the works. This provides helpers on top of Gost allowing test cases to be more expressive, and simulate user behaviour.

func TestMyServer(t *testing.T) {
    window := OpenWindow()
    scope := scope.New(window.Document())
    form := scope.SubScope(
        scope.ByRole(ariarole.Form))
    form.Find(
        ByRole(ariarole.TextBox),
        ByName("Email"),
    ).Type("smith@example.com")
    form.Find(
        ByRole(ariarole.Button),
        ByName("Reset password"),
    ).Click()
    // Assert something happened!
}
func TestMyServer(t *testing.T) {
    window := OpenWindow()
    scope := scope.New(window.Document())
    form := scope.SubScope(scope.ByRole(ariarole.Form))
    form.Find(ByRole(ariarole.TextBox), ByName("Email")).Type("smith@example.com")
    form.Find(ByRole(ariarole.Button), ByName("Reset password")).Click()
    // Assert something happened!
}

Locate elements like users do

When a user interacts with a page, the locate the elements to interact with by contextual information for the elements.

A visual user relies on visual affinity of elements to bring context, e.g. the “text” next to an input field signifies what to type here.

A user relying on a screen reader depends on proper semantic meaning in the codes to being the same context. They rely on the accessibility name of the element to bring the same meaning.

In both cases, the user sees input fields with names. Often, test code relies on implementation details, such as element attributes like id or data-testid. Shaman promotes writing tests that express how a user interact with the application, yieling benefits:

Shaman is sponsors only

At the time of the first pre-release, Shaman will be an exclusive to sponsors at the appropriate sponsorship tiers; It might eventually be made public.

I hope to make shaman publically available in the future, but I need to provide some boon for to encourage sponsorships.


  1. You can create an instance of html.Window directly, and can potentially yield better performance in the current release. But the the browser is it brings sensible defaults, and it’s less likely to undergo a breaking change. 

  2. Work is in progress to support goja as a pure Go alternative to V8, eliminating the need for CGo. 

  3. While we may work towards full compliance, the priority is writing a tool for testing modern web applications. Adding support for old deprecated standards is low on the priority list.