LiveTemplate Testing Framework

A comprehensive e2e testing framework for LiveTemplate applications that reduces boilerplate by 85-90%.

Installation

go get github.com/livetemplate/livetemplate/cmd/lvt/testing

Quick Start

package main

import (
    "testing"
    lvttest "github.com/livetemplate/livetemplate/cmd/lvt/testing"
)

func TestMyApp(t *testing.T) {
    // Setup test environment (Chrome + Server)
    test := lvttest.Setup(t, &lvttest.SetupOptions{
        AppPath: "./main.go",
    })
    defer test.Cleanup()

    // Navigate to page
    test.Navigate("/")

    // Create assertion helper
    assert := lvttest.NewAssert(test)

    // Run assertions
    assert.PageContains("Welcome")
    assert.WebSocketConnected()
    assert.NoConsoleErrors()
}

Features

Automatic Setup

Comprehensive Loggers

// Browser console logs
test.Console.GetLogs()
test.Console.GetErrors()
test.Console.PrintErrors()

// Server logs
test.Server.FindLog("pattern")
test.Server.PrintLast(10)

// WebSocket messages
test.WebSocket.GetMessages()
test.WebSocket.CountByDirection("sent")
test.WebSocket.Print()

17 Built-in Assertions

assert := lvttest.NewAssert(test)

// Content
assert.PageContains("text")
assert.PageNotContains("text")

// Elements
assert.ElementExists("selector")
assert.ElementNotExists("selector")
assert.ElementVisible("selector")
assert.ElementHidden("selector")
assert.ElementCount("selector", 5)

// Text
assert.TextContent("selector", "exact text")
assert.TextContains("selector", "substring")

// Attributes & Classes
assert.AttributeValue("selector", "data-id", "123")
assert.HasClass("selector", "active")
assert.NotHasClass("selector", "disabled")

// Tables
assert.TableRowCount(10)

// Forms
assert.FormFieldValue("input[name='email']", "test@example.com")

// Validation
assert.WebSocketConnected()
assert.NoTemplateErrors()
assert.NoConsoleErrors()

CRUD Testing

crud := lvttest.NewCRUDTester(test, "/products")

// Create with typed fields
crud.Create(
    lvttest.TextField("name", "Widget"),
    lvttest.FloatField("price", 29.99),
    lvttest.IntField("quantity", 100),
    lvttest.BoolField("enabled", true),
)

// Verify existence
crud.VerifyExists("Widget")

// Delete
crud.Delete("record-id")
modal := lvttest.NewModalTester(test).
    WithModalSelector("[data-test-id='create-modal']")

// Open by action
modal.OpenByAction("open_create")

// Verify visibility
modal.VerifyVisible()

// Fill form
modal.FillForm(
    lvttest.TextField("name", "New Item"),
)

// Click button
modal.ClickButton("Create")

// Wait for close
modal.WaitForClose(2 * time.Second)

Chrome Modes

Docker (Default)

test := lvttest.Setup(t, &lvttest.SetupOptions{
    AppPath:    "./main.go",
    ChromeMode: lvttest.ChromeDocker, // default
})

Local Chrome

test := lvttest.Setup(t, &lvttest.SetupOptions{
    AppPath:    "./main.go",
    ChromeMode: lvttest.ChromeLocal,
    ChromePath: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
})

Field Types

// All field types for form filling
lvttest.TextField("name", "value")
lvttest.TextAreaField("description", "long text")
lvttest.IntField("quantity", 42)
lvttest.FloatField("price", 19.99)
lvttest.BoolField("enabled", true)
lvttest.SelectField("category", "Electronics")

Examples

See examples/testing/ for complete examples:

Code Reduction

Before (Manual Setup):

// ~100 lines of boilerplate
func TestManual(t *testing.T) {
    serverPort, _ := e2etest.GetFreePort()
    chromePort, _ := e2etest.GetFreePort()

    serverCmd := exec.Command("go", "run", "main.go")
    serverCmd.Env = append([]string{"PORT=" + fmt.Sprintf("%d", serverPort)}, ...)
    serverCmd.Start()
    defer serverCmd.Process.Kill()

    time.Sleep(2 * time.Second)

    chromeCmd := e2etest.StartDockerChrome(t, chromePort)
    defer e2etest.StopDockerChrome(t, chromeCmd, chromePort)

    allocCtx, allocCancel := chromedp.NewRemoteAllocator(...)
    defer allocCancel()

    ctx, cancel := chromedp.NewContext(allocCtx, ...)
    defer cancel()

    ctx, cancel = context.WithTimeout(ctx, 60*time.Second)
    defer cancel()

    var html string
    chromedp.Run(ctx,
        chromedp.Navigate(fmt.Sprintf("http://localhost:%d", serverPort)),
        chromedp.WaitVisible("h1", chromedp.ByQuery),
        chromedp.OuterHTML("body", &html, chromedp.ByQuery),
    )

    if !strings.Contains(html, "Welcome") {
        t.Error("Page title not found")
    }
}

After (With Framework):

// ~10 lines - 90% reduction!
func TestFramework(t *testing.T) {
    test := lvttest.Setup(t, &lvttest.SetupOptions{
        AppPath: "./main.go",
    })
    defer test.Cleanup()

    test.Navigate("/")

    assert := lvttest.NewAssert(test)
    assert.PageContains("Welcome")
}

Generated Tests

When using lvt gen, tests are automatically generated with the framework:

lvt gen products name price:float quantity:int

Generates:

func TestProductsE2E(t *testing.T) {
    test := lvttest.Setup(t, &lvttest.SetupOptions{
        AppPath: "../../cmd/myapp/main.go",
    })
    defer test.Cleanup()

    test.Navigate("/products")

    assert := lvttest.NewAssert(test)
    crud := lvttest.NewCRUDTester(test, "/products")

    // Automatic CRUD testing
    crud.Create(
        lvttest.TextField("name", "Test Product"),
        lvttest.FloatField("price", 29.99),
        lvttest.IntField("quantity", 100),
    )

    crud.VerifyExists("Test Product")
}

Requirements

Best Practices

  1. Skip in short mode:

    if testing.Short() {
        t.Skip("Skipping E2E test in short mode")
    }
    
  2. Use subtests: Group related tests with t.Run()

  3. Print debug info on failure:

    if err := assert.NoConsoleErrors(); err != nil {
        test.Console.PrintErrors()
        test.WebSocket.PrintLast(10)
        t.Error(err)
    }
    
  4. Clean up test data: Use defer for cleanup operations

  5. Use descriptive test names: Make failures easy to understand

Troubleshooting

Chrome Container Issues

# Restart Docker
docker restart <chrome-container>

# Check logs
docker logs <chrome-container>

WebSocket Connection Timeout

Test Flakiness

API Reference

Core Types

E2ETest

SetupOptions

Assert

CRUDTester

ModalTester

Wait Utilities

License

Same as LiveTemplate project.

Contributing

Contributions welcome! Please ensure: