diff --git a/angular.go b/angular.go index 1d8c45a..5158ef2 100644 --- a/angular.go +++ b/angular.go @@ -290,8 +290,8 @@ func (ah *AngularHelper) GetRouterState() (*AngularRouterState, error) { state.Fragment = core.Sprint(fragment) } - state.Params = stringifyMap(resultMap["params"]) - state.QueryParams = stringifyMap(resultMap["queryParams"]) + state.Params = copyStringOnlyMap(resultMap["params"]) + state.QueryParams = copyStringOnlyMap(resultMap["queryParams"]) return state, nil } @@ -598,12 +598,14 @@ func getString(m map[string]any, key string) string { return "" } -func stringifyMap(value any) map[string]string { +func copyStringOnlyMap(value any) map[string]string { switch typed := value.(type) { case map[string]any: result := make(map[string]string, len(typed)) for key, item := range typed { - result[key] = core.Sprint(item) + if text, ok := item.(string); ok { + result[key] = text + } } return result case map[string]string: diff --git a/audit_issue2_test.go b/audit_issue2_test.go index 521ef6e..bd11a35 100644 --- a/audit_issue2_test.go +++ b/audit_issue2_test.go @@ -672,7 +672,7 @@ func TestExceptionWatcherWaitForException_Good_PreservesExistingHandlers(t *test } } -func TestWebviewGoBack_Good_UsesNavigationHistoryAndWaitsForLoad(t *testing.T) { +func TestWebviewGoBack_Good_UsesGoBackOrForwardAndWaitsForLoad(t *testing.T) { server := newFakeCDPServer(t) target := server.primaryTarget() @@ -681,17 +681,9 @@ func TestWebviewGoBack_Good_UsesNavigationHistoryAndWaitsForLoad(t *testing.T) { methods = append(methods, msg.Method) switch msg.Method { - case "Page.getNavigationHistory": - target.reply(msg.ID, map[string]any{ - "currentIndex": float64(1), - "entries": []map[string]any{ - {"id": float64(101), "url": "https://example.com/one"}, - {"id": float64(202), "url": "https://example.com/two"}, - }, - }) - case "Page.navigateToHistoryEntry": - if got, ok := msg.Params["entryId"].(float64); !ok || got != 101 { - t.Fatalf("navigateToHistoryEntry entryId = %v, want 101", msg.Params["entryId"]) + case "Page.goBackOrForward": + if got, ok := msg.Params["delta"].(float64); !ok || got != -1 { + t.Fatalf("goBackOrForward delta = %v, want -1", msg.Params["delta"]) } target.reply(msg.ID, map[string]any{}) case "Runtime.evaluate": @@ -717,27 +709,29 @@ func TestWebviewGoBack_Good_UsesNavigationHistoryAndWaitsForLoad(t *testing.T) { t.Fatalf("GoBack returned error: %v", err) } - if len(methods) != 3 { - t.Fatalf("expected 3 CDP calls, got %d (%v)", len(methods), methods) + if len(methods) != 2 { + t.Fatalf("expected 2 CDP calls, got %d (%v)", len(methods), methods) } - if methods[0] != "Page.getNavigationHistory" || methods[1] != "Page.navigateToHistoryEntry" || methods[2] != "Runtime.evaluate" { + if methods[0] != "Page.goBackOrForward" || methods[1] != "Runtime.evaluate" { t.Fatalf("unexpected call sequence: %v", methods) } } -func TestWebviewGoForward_Bad_NoHistoryEntry(t *testing.T) { +func TestWebviewGoForward_Good_UsesGoBackOrForwardAndWaitsForLoad(t *testing.T) { server := newFakeCDPServer(t) target := server.primaryTarget() target.onMessage = func(target *fakeCDPTarget, msg cdpMessage) { - if msg.Method != "Page.getNavigationHistory" { + switch msg.Method { + case "Page.goBackOrForward": + if got, ok := msg.Params["delta"].(float64); !ok || got != 1 { + t.Fatalf("goBackOrForward delta = %v, want 1", msg.Params["delta"]) + } + target.reply(msg.ID, map[string]any{}) + case "Runtime.evaluate": + target.replyValue(msg.ID, "complete") + default: t.Fatalf("unexpected method %q", msg.Method) } - target.reply(msg.ID, map[string]any{ - "currentIndex": float64(0), - "entries": []map[string]any{ - {"id": float64(101), "url": "https://example.com/one"}, - }, - }) } client, err := NewCDPClient(server.DebugURL()) @@ -752,8 +746,8 @@ func TestWebviewGoForward_Bad_NoHistoryEntry(t *testing.T) { timeout: time.Second, } - if err := wv.GoForward(); err == nil { - t.Fatal("GoForward succeeded without a forward history entry") + if err := wv.GoForward(); err != nil { + t.Fatalf("GoForward returned error: %v", err) } } @@ -791,7 +785,7 @@ func TestWebviewEvaluate_Bad_UsesExceptionText(t *testing.T) { } } -func TestAngularHelperGetRouterState_Good_StringifiesParams(t *testing.T) { +func TestAngularHelperGetRouterState_Good_KeepsOnlyStringParams(t *testing.T) { server := newFakeCDPServer(t) target := server.primaryTarget() target.onMessage = func(target *fakeCDPTarget, msg cdpMessage) { @@ -802,11 +796,12 @@ func TestAngularHelperGetRouterState_Good_StringifiesParams(t *testing.T) { "url": "/items/123", "fragment": "details", "params": map[string]any{ - "id": float64(123), + "id": "123", "active": true, }, "queryParams": map[string]any{ - "page": float64(2), + "page": "2", + "debug": float64(1), }, }) } @@ -828,10 +823,16 @@ func TestAngularHelperGetRouterState_Good_StringifiesParams(t *testing.T) { if err != nil { t.Fatalf("GetRouterState returned error: %v", err) } - if state.Params["id"] != "123" || state.Params["active"] != "true" { + if state.Params["id"] != "123" { t.Fatalf("unexpected params: %#v", state.Params) } + if _, ok := state.Params["active"]; ok { + t.Fatalf("expected non-string params to be omitted, got %#v", state.Params) + } if state.QueryParams["page"] != "2" { t.Fatalf("unexpected query params: %#v", state.QueryParams) } + if _, ok := state.QueryParams["debug"]; ok { + t.Fatalf("expected non-string query params to be omitted, got %#v", state.QueryParams) + } } diff --git a/console.go b/console.go index dbb4182..cd272a4 100644 --- a/console.go +++ b/console.go @@ -27,7 +27,7 @@ type ConsoleWatcher struct { // ConsoleFilter filters console messages. type ConsoleFilter struct { - Type string // Filter by type (log, warn, error, info, debug), empty for all + Type string // Exact message type match, empty for all Pattern string // Filter by text pattern (substring match) } @@ -60,12 +60,7 @@ func NewConsoleWatcher(wv *Webview) *ConsoleWatcher { // normalizeConsoleType converts CDP event types to package-level values. func normalizeConsoleType(raw string) string { normalized := strings.ToLower(core.Trim(core.Sprint(raw))) - switch normalized { - case "warning": - return "warn" - default: - return normalized - } + return normalized } // consoleTextFromArgs extracts message text from Runtime.consoleAPICalled args. @@ -279,7 +274,7 @@ func (cw *ConsoleWatcher) ErrorsAll() iter.Seq[ConsoleMessage] { defer cw.mu.RUnlock() for _, msg := range cw.messages { - if normalizeConsoleType(msg.Type) == "error" { + if msg.Type == "error" { if !yield(msg) { return } @@ -300,7 +295,7 @@ func (cw *ConsoleWatcher) WarningsAll() iter.Seq[ConsoleMessage] { defer cw.mu.RUnlock() for _, msg := range cw.messages { - if normalizeConsoleType(msg.Type) == "warn" { + if msg.Type == "warning" { if !yield(msg) { return } @@ -361,7 +356,7 @@ func (cw *ConsoleWatcher) HasErrors() bool { defer cw.mu.RUnlock() for _, msg := range cw.messages { - if normalizeConsoleType(msg.Type) == "error" { + if msg.Type == "error" { return true } } @@ -382,7 +377,7 @@ func (cw *ConsoleWatcher) ErrorCount() int { count := 0 for _, msg := range cw.messages { - if normalizeConsoleType(msg.Type) == "error" { + if msg.Type == "error" { count++ } } @@ -459,7 +454,7 @@ func (cw *ConsoleWatcher) matchesFilter(msg ConsoleMessage) bool { // matchesSingleFilter checks if a message matches a specific filter. func (cw *ConsoleWatcher) matchesSingleFilter(msg ConsoleMessage, filter ConsoleFilter) bool { - if filter.Type != "" && msg.Type != normalizeConsoleType(filter.Type) { + if filter.Type != "" && msg.Type != filter.Type { return false } if filter.Pattern != "" { @@ -684,7 +679,7 @@ func FormatConsoleOutput(messages []ConsoleMessage) string { switch normalizeConsoleType(msg.Type) { case "error": prefix = "[ERROR]" - case "warn": + case "warning", "warn": prefix = "[WARN]" case "info": prefix = "[INFO]" diff --git a/webview.go b/webview.go index 64270e8..299f635 100644 --- a/webview.go +++ b/webview.go @@ -47,7 +47,7 @@ type Webview struct { // ConsoleMessage represents a captured console log message. type ConsoleMessage struct { - Type string `json:"type"` // log, warn, error, info, debug + Type string `json:"type"` // log, warning, error, info, debug Text string `json:"text"` // Message text Timestamp time.Time `json:"timestamp"` // When the message was logged URL string `json:"url"` // Source URL @@ -415,41 +415,11 @@ func (wv *Webview) navigateHistory(delta int, scope string) error { ctx, cancel := context.WithTimeout(wv.ctx, wv.timeout) defer cancel() - result, err := wv.client.Call(ctx, "Page.getNavigationHistory", nil) - if err != nil { - return coreerr.E(scope, "failed to get navigation history", err) - } - - currentIndex, ok := result["currentIndex"].(float64) - if !ok { - return coreerr.E(scope, "invalid navigation history index", nil) - } - - entries, ok := result["entries"].([]any) - if !ok { - return coreerr.E(scope, "invalid navigation history entries", nil) - } - - targetIndex := int(currentIndex) + delta - if targetIndex < 0 || targetIndex >= len(entries) { - return coreerr.E(scope, "no history entry available", nil) - } - - entry, ok := entries[targetIndex].(map[string]any) - if !ok { - return coreerr.E(scope, "invalid navigation history entry", nil) - } - - entryID, ok := entry["id"].(float64) - if !ok { - return coreerr.E(scope, "invalid navigation history entry ID", nil) - } - - _, err = wv.client.Call(ctx, "Page.navigateToHistoryEntry", map[string]any{ - "entryId": int(entryID), + _, err := wv.client.Call(ctx, "Page.goBackOrForward", map[string]any{ + "delta": delta, }) if err != nil { - return coreerr.E(scope, "failed to navigate to history entry", err) + return coreerr.E(scope, "failed to navigate history", err) } return wv.waitForLoad(ctx) diff --git a/webview_test.go b/webview_test.go index 70e2515..0989c28 100644 --- a/webview_test.go +++ b/webview_test.go @@ -579,6 +579,15 @@ func TestConsoleWatcherFilter_Good(t *testing.T) { if cw.matchesFilter(msg) { t.Error("Expected 'test error' NOT to match pattern 'hello'") } + + cw.ClearFilters() + cw.AddFilter(ConsoleFilter{Type: "warning"}) + if !cw.matchesFilter(ConsoleMessage{Type: "warning", Text: "deprecated"}) { + t.Error("Expected warning message to match warning filter") + } + if cw.matchesFilter(ConsoleMessage{Type: "warn", Text: "deprecated"}) { + t.Error("Expected warn message not to match warning filter") + } } // TestConsoleWatcherCounts_Good verifies console watcher counting methods.