3 Console-Monitoring
Virgil edited this page 2026-02-19 16:56:44 +00:00

Console Monitoring

This page covers capturing and filtering browser console output, watching for JavaScript exceptions, and using the formatted output helpers.

See also: Home | Getting-Started | DOM-Queries | Actions | Angular-Testing

Built-in Console Capture

Every Webview instance automatically captures console messages (console.log, console.warn, console.error, console.info, console.debug) via the CDP Runtime.consoleAPICalled event. Messages are stored in a ring buffer configured by WithConsoleLimit (default 1000).

Retrieving Messages

wv.Navigate("https://example.com")

// Get all captured messages (thread-safe copy)
messages := wv.GetConsole()
for _, msg := range messages {
    fmt.Printf("[%s] %s
", msg.Type, msg.Text)
}

// Clear the buffer
wv.ClearConsole()

ConsoleMessage Struct

type ConsoleMessage struct {
    Type      string    // "log", "warn", "error", "info", "debug"
    Text      string    // The message text (all arguments joined)
    Timestamp time.Time // When the message was captured
    URL       string    // Source file URL
    Line      int       // Source line number
    Column    int       // Source column number
}

The URL, Line, and Column fields are extracted from the first frame of the stack trace, giving you the exact source location that produced the message.

ConsoleWatcher

For more advanced monitoring, use ConsoleWatcher. It provides filtering, custom handlers, and convenience methods for common queries.

Creating a Watcher

watcher := webview.NewConsoleWatcher(wv)

The watcher subscribes to the same Runtime.consoleAPICalled event and maintains its own independent message store.

Filtering Messages

Add filters to narrow down which messages are captured:

// Only capture errors
watcher.AddFilter(webview.ConsoleFilter{Type: "error"})

// Only capture messages containing a specific pattern
watcher.AddFilter(webview.ConsoleFilter{Pattern: "API request"})

// Combine type and pattern
watcher.AddFilter(webview.ConsoleFilter{
    Type:    "warning",
    Pattern: "deprecated",
})

// Clear all filters
watcher.ClearFilters()

Filters use OR logic: a message matches if it satisfies any of the active filters. Within a single filter, both Type and Pattern must match (AND logic).

Querying Messages

// All messages (ignoring filters)
all := watcher.Messages()

// Only messages matching active filters
filtered := watcher.FilteredMessages()

// Only errors
errors := watcher.Errors()

// Only warnings
warnings := watcher.Warnings()

// Counts
total := watcher.Count()
errorCount := watcher.ErrorCount()
hasErrors := watcher.HasErrors()

// Clear captured messages
watcher.Clear()

Custom Handlers

Register callback functions that fire immediately when a console message is received:

watcher.AddHandler(func(msg webview.ConsoleMessage) {
    if msg.Type == "error" {
        log.Printf("BROWSER ERROR: %s at %s:%d", msg.Text, msg.URL, msg.Line)
    }
})

Handlers are called synchronously in the order they were added, from within the CDP event processing goroutine. Keep handler functions fast to avoid blocking message processing.

Waiting for Messages

Block until a specific message appears, with context-based timeout:

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

// Wait for any error
errMsg, err := watcher.WaitForError(ctx)
if err != nil {
    log.Println("No errors within timeout -- good!")
} else {
    log.Printf("Got error: %s", errMsg.Text)
}

// Wait for a specific message pattern
msg, err := watcher.WaitForMessage(ctx, webview.ConsoleFilter{
    Pattern: "Data loaded successfully",
})

WaitForMessage first checks existing messages, then sets up a temporary handler to wait for new ones. This avoids race conditions where a message arrives between checking and subscribing.

Setting Buffer Limits

watcher.SetLimit(5000) // Keep up to 5000 messages

When the limit is reached, the oldest messages are pruned in batches to avoid constant reallocation.

ExceptionWatcher

The ExceptionWatcher specifically monitors for uncaught JavaScript exceptions via the CDP Runtime.exceptionThrown event.

Creating an Exception Watcher

exWatcher := webview.NewExceptionWatcher(wv)

Checking for Exceptions

// Check if any exceptions occurred
if exWatcher.HasExceptions() {
    for _, exc := range exWatcher.Exceptions() {
        log.Printf("Exception: %s", exc.Text)
        log.Printf("  at %s:%d:%d", exc.URL, exc.LineNumber, exc.ColumnNumber)
        if exc.StackTrace != "" {
            log.Printf("  Stack:
%s", exc.StackTrace)
        }
    }
}

// Get count
count := exWatcher.Count()

// Clear exceptions
exWatcher.Clear()

ExceptionInfo Struct

type ExceptionInfo struct {
    Text         string    // Exception message or description
    LineNumber   int       // Source line number
    ColumnNumber int       // Source column number
    URL          string    // Source file URL
    StackTrace   string    // Formatted stack trace
    Timestamp    time.Time // When the exception was thrown
}

Waiting for Exceptions

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

exc, err := exWatcher.WaitForException(ctx)
if err == nil {
    log.Printf("Caught exception: %s", exc.Text)
}

Exception Handlers

exWatcher.AddHandler(func(exc webview.ExceptionInfo) {
    log.Printf("UNHANDLED EXCEPTION: %s
%s", exc.Text, exc.StackTrace)
})

Formatted Output

The FormatConsoleOutput helper formats a slice of console messages for display:

messages := watcher.Messages()
output := webview.FormatConsoleOutput(messages)
fmt.Println(output)

Output format:

15:04:05.123 [LOG] Application started
15:04:05.456 [WARN] Using deprecated API
15:04:05.789 [ERROR] Failed to load resource: net::ERR_CONNECTION_REFUSED
15:04:06.012 [INFO] Connected to WebSocket
15:04:06.345 [DEBUG] Render cycle complete

Practical Examples

Error-Free Navigation Test

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

    watcher := webview.NewConsoleWatcher(wv)
    exWatcher := webview.NewExceptionWatcher(wv)

    wv.Navigate("http://localhost:3000")
    wv.WaitForSelector(".app-loaded")

    // Verify no errors
    if watcher.HasErrors() {
        for _, err := range watcher.Errors() {
            t.Errorf("Console error: %s at %s:%d", err.Text, err.URL, err.Line)
        }
    }

    if exWatcher.HasExceptions() {
        for _, exc := range exWatcher.Exceptions() {
            t.Errorf("Exception: %s
%s", exc.Text, exc.StackTrace)
        }
    }
}

Monitoring API Calls

watcher := webview.NewConsoleWatcher(wv)
watcher.AddFilter(webview.ConsoleFilter{Pattern: "fetch"})
watcher.AddFilter(webview.ConsoleFilter{Pattern: "XMLHttpRequest"})

wv.Navigate("https://example.com/dashboard")

// Wait for API responses to be logged
time.Sleep(3 * time.Second)

for _, msg := range watcher.FilteredMessages() {
    log.Printf("API activity: %s", msg.Text)
}

Real-Time Error Logging

watcher := webview.NewConsoleWatcher(wv)
watcher.AddHandler(func(msg webview.ConsoleMessage) {
    if msg.Type == "error" {
        // Write to a log file in real time
        f, _ := os.OpenFile("browser-errors.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
        defer f.Close()
        fmt.Fprintf(f, "%s [%s:%d] %s
",
            msg.Timestamp.Format(time.RFC3339), msg.URL, msg.Line, msg.Text)
    }
})

Next Steps