3 Angular-Testing
Virgil edited this page 2026-02-19 16:57:36 +00:00

Angular Testing

This page covers the Angular-specific testing helpers for automating Angular applications. The AngularHelper understands Zone.js, the Angular Router, dependency injection, and change detection.

See also: Home | Getting-Started | DOM-Queries | Actions | Console-Monitoring

Creating an AngularHelper

wv, _ := webview.New(webview.WithDebugURL("http://localhost:9222"))
defer wv.Close()

ah := webview.NewAngularHelper(wv)
ah.SetTimeout(15 * time.Second) // Optional; default is 30s

The helper wraps the Webview instance and provides Angular-aware methods that account for Zone.js asynchronous processing and change detection cycles.

Waiting for Angular

WaitForAngular

The most important method. Waits for all pending Zone.js microtasks and macrotasks to complete, ensuring the application is in a stable state before you interact with it.

wv.Navigate("http://localhost:4200")
if err := ah.WaitForAngular(); err != nil {
    log.Fatal("Angular did not stabilise:", err)
}

Under the hood, WaitForAngular:

  1. Checks whether the page is an Angular application (via getAllAngularRootElements, [ng-version] attribute, or window.ng.probe)
  2. Locates the NgZone via the root element's injector
  3. Subscribes to zone.onStable and waits for it to fire
  4. Falls back to polling Zone.current._inner pending task flags if the injector is not accessible

This method detects both Angular 2+ and AngularJS (1.x) applications.

Angular Detection

The helper automatically detects Angular presence by checking for:

  • window.getAllAngularRootElements() returning elements
  • DOM elements with the [ng-version] attribute
  • window.ng.probe function (Angular debug tools)
  • window.angular.element (AngularJS 1.x)

If the page is not an Angular application, WaitForAngular returns an error.

Router Navigation

NavigateByRouter

Navigates using Angular's Router service directly, bypassing full page reloads. This is faster and preserves application state.

err := ah.NavigateByRouter("/dashboard/settings")

The method:

  1. Finds the Angular root elements
  2. Gets the Router service from the injector
  3. Calls router.navigateByUrl(path)
  4. Waits for Zone.js stability after navigation

GetRouterState

Retrieves the current Angular router state, including URL, route parameters, query parameters, and fragment.

state, err := ah.GetRouterState()
if err != nil {
    log.Fatal(err)
}

log.Printf("URL: %s", state.URL)
log.Printf("Params: %v", state.Params)
log.Printf("Query: %v", state.QueryParams)
log.Printf("Fragment: %s", state.Fragment)

The AngularRouterState struct:

type AngularRouterState struct {
    URL         string            // Current router URL
    Fragment    string            // URL fragment (#section)
    Params      map[string]string // Route parameters
    QueryParams map[string]string // Query string parameters
}

Component Interaction

GetComponentProperty

Reads a property value from an Angular component instance attached to a DOM element.

// Get the current value of a component property
count, err := ah.GetComponentProperty("app-counter", "count")
if err != nil {
    log.Fatal(err)
}
log.Printf("Counter value: %v", count)

SetComponentProperty

Sets a property on a component and triggers change detection so the view updates.

err := ah.SetComponentProperty("app-counter", "count", 42)

The method automatically calls ApplicationRef.tick() after setting the property to ensure the view reflects the change.

CallComponentMethod

Calls a method on a component instance with optional arguments.

// Call a method with no arguments
result, err := ah.CallComponentMethod("app-cart", "clearItems")

// Call a method with arguments
result, err = ah.CallComponentMethod("app-cart", "addItem", "SKU-001", 3)

Like SetComponentProperty, this triggers change detection after the call.

Change Detection

TriggerChangeDetection

Manually triggers Angular's change detection cycle across all root components.

err := ah.TriggerChangeDetection()

This is useful after modifying the DOM or application state via JavaScript evaluation, when Angular would not automatically detect the changes.

Services

GetService

Retrieves an Angular service by its injection token name. The service is serialised to JSON and returned as a Go value.

config, err := ah.GetService("AppConfig")
if err != nil {
    log.Fatal(err)
}
log.Printf("Config: %v", config)

Note that only JSON-serialisable service properties are returned. Functions, circular references, and other non-serialisable values are omitted.

Waiting for Components

WaitForComponent

Polls until an Angular component is mounted on an element matching the selector.

err := ah.WaitForComponent("app-user-profile")

This differs from WaitForSelector in that it not only waits for the DOM element to appear, but also verifies that an Angular component instance is attached to it via window.ng.probe.

Events

DispatchEvent

Dispatches a custom DOM event on an element, which Angular event bindings will pick up.

// Simple event
err := ah.DispatchEvent("app-button", "click", nil)

// Event with detail data
err = ah.DispatchEvent("app-list", "itemSelected", map[string]any{
    "id":   42,
    "name": "Widget",
})

The event is created with bubbles: true so it propagates through the Angular component tree.

NgModel (Two-Way Binding)

GetNgModel

Reads the value of an ngModel-bound input element.

value, err := ah.GetNgModel("input[name=email]")
log.Printf("Email: %v", value)

SetNgModel

Sets the value of an ngModel-bound input and triggers the appropriate input and change events, plus Angular change detection.

err := ah.SetNgModel("input[name=email]", "user@example.com")

This properly dispatches events so that Angular's form control system registers the change, validators run, and the model updates.

Practical Examples

Testing a Login Flow

func TestAngularLogin(t *testing.T) {
    wv, _ := webview.New(webview.WithDebugURL("http://localhost:9222"))
    defer wv.Close()

    ah := webview.NewAngularHelper(wv)

    wv.Navigate("http://localhost:4200/login")
    ah.WaitForAngular()

    // Fill form using ngModel
    ah.SetNgModel("input[formControlName=email]", "admin@example.com")
    ah.SetNgModel("input[formControlName=password]", "secret123")

    // Submit
    wv.Click("button[type=submit]")
    ah.WaitForAngular()

    // Verify navigation
    state, _ := ah.GetRouterState()
    if state.URL != "/dashboard" {
        t.Errorf("expected /dashboard, got %s", state.URL)
    }
}

Inspecting Component State

wv.Navigate("http://localhost:4200/products")
ah.WaitForAngular()

// Check how many products are loaded
count, _ := ah.GetComponentProperty("app-product-list", "products.length")
log.Printf("Products loaded: %v", count)

// Call a component method to apply a filter
ah.CallComponentMethod("app-product-list", "filterByCategory", "electronics")
ah.WaitForAngular()

// Verify the filter was applied
filteredCount, _ := ah.GetComponentProperty("app-product-list", "filteredProducts.length")
log.Printf("Filtered products: %v", filteredCount)

Waiting for Async Data

wv.Navigate("http://localhost:4200/reports")

// Wait for the component to mount
ah.WaitForComponent("app-report-viewer")

// Wait for Angular to finish all HTTP requests
ah.WaitForAngular()

// Now the data should be loaded -- capture a screenshot
data, _ := wv.Screenshot()
os.WriteFile("report.png", data, 0644)

Router Navigation Without Page Reload

wv.Navigate("http://localhost:4200")
ah.WaitForAngular()

// Navigate using Angular Router (no full page reload)
ah.NavigateByRouter("/settings/profile")
ah.WaitForAngular()

// Navigate to another route
ah.NavigateByRouter("/settings/security")
ah.WaitForAngular()

state, _ := ah.GetRouterState()
log.Printf("Current route: %s", state.URL)

Compatibility

The AngularHelper supports:

  • Angular 2+ (including Angular 14, 15, 16, 17, 18, 19) via window.ng.probe and getAllAngularRootElements
  • AngularJS 1.x via window.angular.element (detection only; Zone.js methods are specific to Angular 2+)

For the helper to work, the application must be built with Angular debug tools enabled (the default in development mode). Production builds that call enableProdMode() may disable ng.probe, in which case the helper falls back to Zone.js polling for stability checks.

Next Steps