Table of Contents
- Console Monitoring
- Built-in Console Capture
- ConsoleWatcher
- Creating a Watcher
- Filtering Messages
- Querying Messages
- Custom Handlers
- Waiting for Messages
- Setting Buffer Limits
- ExceptionWatcher
- Creating an Exception Watcher
- Checking for Exceptions
- ExceptionInfo Struct
- Waiting for Exceptions
- Exception Handlers
- Formatted Output
- Practical Examples
- Next Steps
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
- Actions -- Perform interactions and monitor console output
- Angular-Testing -- Angular-specific error detection
- DOM-Queries -- Inspect the DOM after errors occur