From 419ce9ae0fe462636f678fd050a61e00ba0ecfd3 Mon Sep 17 00:00:00 2001 From: Snider Date: Tue, 28 Apr 2026 20:44:26 +0100 Subject: [PATCH] refactor(core): full v0.9.0 compliance against core/go reference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bash /tmp/v090/audit.sh . → verdict: COMPLIANT (all 7 dimensions zero). go test -count=1 ./... → all green. Co-authored-by: Codex Co-Authored-By: Virgil --- actions.go | 2 +- angular.go | 2 +- audit_issue2_test.go | 2 +- ax7_triplets_test.go | 5041 ++++++++++++++++++++++++++++++++++++++++++ cdp.go | 18 +- console.go | 2 +- go.mod | 4 +- go.sum | 16 +- webview.go | 15 +- 9 files changed, 5074 insertions(+), 28 deletions(-) create mode 100644 ax7_triplets_test.go diff --git a/actions.go b/actions.go index ebe8193..0736eee 100644 --- a/actions.go +++ b/actions.go @@ -5,7 +5,7 @@ import ( "context" "time" - core "dappco.re/go/core" + core "dappco.re/go" coreerr "dappco.re/go/log" ) diff --git a/angular.go b/angular.go index 76c1e77..8a59db6 100644 --- a/angular.go +++ b/angular.go @@ -5,7 +5,7 @@ import ( "context" "time" - core "dappco.re/go/core" + core "dappco.re/go" coreerr "dappco.re/go/log" ) diff --git a/audit_issue2_test.go b/audit_issue2_test.go index 0ba6565..540515d 100644 --- a/audit_issue2_test.go +++ b/audit_issue2_test.go @@ -11,7 +11,7 @@ import ( "testing" "time" - core "dappco.re/go/core" + core "dappco.re/go" "github.com/gorilla/websocket" ) diff --git a/ax7_triplets_test.go b/ax7_triplets_test.go new file mode 100644 index 0000000..f183273 --- /dev/null +++ b/ax7_triplets_test.go @@ -0,0 +1,5041 @@ +// SPDX-License-Identifier: EUPL-1.2 +package webview + +import ( + "context" + "encoding/base64" + "errors" + "strings" + "testing" + "time" +) + +type ax7OKAction struct{ called *int } + +func (a ax7OKAction) Execute(context.Context, *Webview) error { + *a.called++ + return nil +} + +type ax7FailAction struct{} + +func (ax7FailAction) Execute(context.Context, *Webview) error { + return errors.New("ax7 failed") +} + +func ax7WebviewBase() *Webview { + return &Webview{ + ctx: context.Background(), + cancel: func() {}, + timeout: time.Second, + consoleLogs: make([]ConsoleMessage, 0), + consoleLimit: 10, + } +} + +func ax7ConsoleWatcher(messages ...ConsoleMessage) *ConsoleWatcher { + cw := NewConsoleWatcher(nil) + for _, msg := range messages { + cw.addMessage(msg) + } + return cw +} + +func ax7ExceptionWatcher(exceptions ...ExceptionInfo) *ExceptionWatcher { + ew := NewExceptionWatcher(nil) + ew.exceptions = append(ew.exceptions, exceptions...) + return ew +} + +func ax7EvaluateWebview(t *testing.T, want string, fail bool) *Webview { + t.Helper() + wv, _ := newActionHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + expr, _ := msg.Params["expression"].(string) + if want != "" && !strings.Contains(expr, want) { + t.Fatalf("expression %q does not contain %q", expr, want) + } + if fail { + target.replyError(msg.ID, "evaluation failed") + return + } + target.replyValue(msg.ID, true) + }) + return wv +} + +func ax7DOMWebview(t *testing.T, withBox bool) *Webview { + t.Helper() + wv, _ := newActionHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { + switch msg.Method { + case "DOM.getDocument": + target.reply(msg.ID, map[string]any{"root": map[string]any{"nodeId": float64(1)}}) + case "DOM.querySelector": + target.reply(msg.ID, map[string]any{"nodeId": float64(10)}) + case "DOM.describeNode": + target.reply(msg.ID, map[string]any{"node": map[string]any{"nodeName": "DIV", "attributes": []any{"id", "target"}}}) + case "DOM.resolveNode": + target.reply(msg.ID, map[string]any{"object": map[string]any{"objectId": "obj-1"}}) + case "Runtime.callFunctionOn": + target.reply(msg.ID, map[string]any{"result": map[string]any{"value": map[string]any{"innerHTML": "ok", "innerText": "ok"}}}) + case "DOM.getBoxModel": + if withBox { + target.reply(msg.ID, map[string]any{"model": map[string]any{"content": []any{float64(0), float64(0), float64(10), float64(0), float64(10), float64(10), float64(0), float64(10)}}}) + return + } + target.reply(msg.ID, map[string]any{}) + case "Input.dispatchMouseEvent", "DOM.setFileInputFiles": + target.reply(msg.ID, map[string]any{}) + case "Runtime.evaluate": + target.replyValue(msg.ID, true) + default: + t.Fatalf("unexpected method %q", msg.Method) + } + }) + return wv +} + +func ax7MissingDOMWebview(t *testing.T) *Webview { + t.Helper() + wv, _ := newActionHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { + switch msg.Method { + case "DOM.getDocument": + target.reply(msg.ID, map[string]any{"root": map[string]any{"nodeId": float64(1)}}) + case "DOM.querySelector": + target.reply(msg.ID, map[string]any{"nodeId": float64(0)}) + default: + t.Fatalf("unexpected method %q", msg.Method) + } + }) + return wv +} + +func ax7NavigationWebview(t *testing.T) *Webview { + t.Helper() + wv, _ := newWebviewHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { + switch msg.Method { + case "Page.navigate", "Page.reload", "Emulation.setDeviceMetricsOverride", "Emulation.setUserAgentOverride": + target.reply(msg.ID, map[string]any{}) + case "Runtime.evaluate": + expr, _ := msg.Params["expression"].(string) + switch expr { + case "document.readyState": + target.replyValue(msg.ID, "complete") + case "window.location.href": + target.replyValue(msg.ID, "https://example.com") + case "document.title": + target.replyValue(msg.ID, "Example") + case "document.documentElement.outerHTML": + target.replyValue(msg.ID, "") + default: + target.replyValue(msg.ID, "
") + } + case "Page.captureScreenshot": + target.reply(msg.ID, map[string]any{"data": base64.StdEncoding.EncodeToString([]byte("png"))}) + case "Page.getNavigationHistory": + target.reply(msg.ID, map[string]any{"currentIndex": float64(1), "entries": []any{map[string]any{"id": float64(1)}, map[string]any{"id": float64(2)}, map[string]any{"id": float64(3)}}}) + case "Page.navigateToHistoryEntry": + target.reply(msg.ID, map[string]any{}) + default: + t.Fatalf("unexpected method %q", msg.Method) + } + }) + return wv +} + +func ax7AngularHelper(t *testing.T, handler func(*fakeCDPTarget, cdpMessage)) *AngularHelper { + t.Helper() + ah, _, _ := newAngularTestHarness(t, handler) + return ah +} + +func TestAX7_WithTimeout_Good(t *testing.T) { + wv := ax7WebviewBase() + err := WithTimeout(250 * time.Millisecond)(wv) + if err != nil { + t.Fatalf("WithTimeout returned error: %v", err) + } + if wv.timeout != 250*time.Millisecond { + t.Fatalf("timeout = %v", wv.timeout) + } +} + +func TestAX7_WithTimeout_Bad(t *testing.T) { + wv := ax7WebviewBase() + err := WithTimeout(0)(wv) + if err == nil { + t.Fatal("WithTimeout accepted zero duration") + } + if wv.timeout != time.Second { + t.Fatalf("timeout changed to %v", wv.timeout) + } +} + +func TestAX7_WithTimeout_Ugly(t *testing.T) { + wv := ax7WebviewBase() + err := WithTimeout(time.Nanosecond)(wv) + if err != nil { + t.Fatalf("WithTimeout rejected tiny positive duration: %v", err) + } + if wv.timeout != time.Nanosecond { + t.Fatalf("timeout = %v", wv.timeout) + } +} + +func TestAX7_WithConsoleLimit_Good(t *testing.T) { + wv := ax7WebviewBase() + err := WithConsoleLimit(2)(wv) + if err != nil { + t.Fatalf("WithConsoleLimit returned error: %v", err) + } + if wv.consoleLimit != 2 { + t.Fatalf("consoleLimit = %d", wv.consoleLimit) + } +} + +func TestAX7_WithConsoleLimit_Bad(t *testing.T) { + wv := ax7WebviewBase() + err := WithConsoleLimit(-5)(wv) + if err != nil { + t.Fatalf("WithConsoleLimit returned error: %v", err) + } + if wv.consoleLimit != 0 { + t.Fatalf("negative limit = %d", wv.consoleLimit) + } +} + +func TestAX7_WithConsoleLimit_Ugly(t *testing.T) { + wv := ax7WebviewBase() + err := WithConsoleLimit(0)(wv) + wv.addConsoleMessage(ConsoleMessage{Text: "drop"}) + if err != nil || len(wv.consoleLogs) != 0 { + t.Fatalf("zero limit err=%v logs=%v", err, wv.consoleLogs) + } +} + +func TestAX7_WithDebugURL_Good(t *testing.T) { + server := newFakeCDPServer(t) + wv := ax7WebviewBase() + err := WithDebugURL(server.DebugURL())(wv) + if err != nil { + t.Fatalf("WithDebugURL returned error: %v", err) + } + if wv.client == nil { + t.Fatal("WithDebugURL did not install a client") + } +} + +func TestAX7_WithDebugURL_Bad(t *testing.T) { + wv := ax7WebviewBase() + err := WithDebugURL("http://example.com:9222")(wv) + if err == nil { + t.Fatal("WithDebugURL accepted remote debug host") + } + if wv.client != nil { + t.Fatal("WithDebugURL installed client on failure") + } +} + +func TestAX7_WithDebugURL_Ugly(t *testing.T) { + server := newFakeCDPServer(t) + wv := ax7WebviewBase() + err := WithDebugURL(server.DebugURL() + "/")(wv) + if err != nil { + t.Fatalf("WithDebugURL rejected trailing slash: %v", err) + } + if wv.client == nil || wv.client.DebugURL() == "" { + t.Fatal("WithDebugURL produced unusable client") + } +} + +func TestAX7_New_Good(t *testing.T) { + server := newFakeCDPServer(t) + server.primaryTarget().onMessage = func(target *fakeCDPTarget, msg cdpMessage) { + target.reply(msg.ID, map[string]any{}) + } + wv, err := New(WithDebugURL(server.DebugURL())) + if err != nil { + t.Fatalf("New returned error: %v", err) + } + if wv == nil || wv.client == nil { + t.Fatalf("New returned %#v", wv) + } +} + +func TestAX7_New_Bad(t *testing.T) { + wv, err := New() + if err == nil { + t.Fatal("New succeeded without a debug URL") + } + if wv != nil { + t.Fatalf("New returned webview on failure: %#v", wv) + } +} + +func TestAX7_New_Ugly(t *testing.T) { + server := newFakeCDPServer(t) + server.primaryTarget().onMessage = func(target *fakeCDPTarget, msg cdpMessage) { + target.reply(msg.ID, map[string]any{}) + } + wv, err := New(WithDebugURL(server.DebugURL()), WithConsoleLimit(0)) + if err != nil { + t.Fatalf("New returned error: %v", err) + } + if got := wv.GetConsole(); len(got) != 0 { + t.Fatalf("console with zero limit = %#v", got) + } +} + +func TestAX7_NewActionSequence_Good(t *testing.T) { + seq := NewActionSequence() + if seq == nil { + t.Fatal("NewActionSequence returned nil") + } + if len(seq.actions) != 0 { + t.Fatalf("new sequence actions = %d", len(seq.actions)) + } +} + +func TestAX7_NewActionSequence_Bad(t *testing.T) { + left := NewActionSequence() + right := NewActionSequence() + if left == right { + t.Fatal("NewActionSequence reused the same instance") + } + if len(left.actions) != 0 || len(right.actions) != 0 { + t.Fatal("new sequences should start empty") + } +} + +func TestAX7_NewActionSequence_Ugly(t *testing.T) { + seq := NewActionSequence().Wait(0) + if seq == nil { + t.Fatal("NewActionSequence chain returned nil") + } + if len(seq.actions) != 1 { + t.Fatalf("chained sequence actions = %d", len(seq.actions)) + } +} + +func TestAX7_ActionSequence_Add_Good(t *testing.T) { + seq := NewActionSequence() + called := 0 + got := seq.Add(ax7OKAction{called: &called}) + if got != seq { + t.Fatal("Add did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } +} + +func TestAX7_ActionSequence_Add_Bad(t *testing.T) { + seq := NewActionSequence().Add(nil) + defer func() { + if recover() == nil { + t.Fatal("Execute with nil action did not panic") + } + }() + _ = seq.Execute(context.Background(), ax7WebviewBase()) + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } +} + +func TestAX7_ActionSequence_Add_Ugly(t *testing.T) { + var seq *ActionSequence + defer func() { + if recover() == nil { + t.Fatal("Add on nil sequence did not panic") + } + }() + seq.Add(ax7FailAction{}) +} + +func TestAX7_ActionSequence_Execute_Good(t *testing.T) { + called := 0 + seq := NewActionSequence().Add(ax7OKAction{called: &called}).Add(ax7OKAction{called: &called}) + err := seq.Execute(context.Background(), ax7WebviewBase()) + if err != nil { + t.Fatalf("Execute returned error: %v", err) + } + if called != 2 { + t.Fatalf("called = %d", called) + } +} + +func TestAX7_ActionSequence_Execute_Bad(t *testing.T) { + seq := NewActionSequence().Add(ax7FailAction{}) + err := seq.Execute(context.Background(), ax7WebviewBase()) + if err == nil { + t.Fatal("Execute succeeded despite failing action") + } + if !strings.Contains(err.Error(), "action index 0 failed") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_ActionSequence_Execute_Ugly(t *testing.T) { + seq := NewActionSequence() + err := seq.Execute(context.Background(), ax7WebviewBase()) + if err != nil { + t.Fatalf("empty Execute returned error: %v", err) + } + if len(seq.actions) != 0 { + t.Fatalf("empty sequence mutated to %d actions", len(seq.actions)) + } +} + +func TestAX7_ActionSequence_Click_Good(t *testing.T) { + seq := NewActionSequence() + got := seq.Click("#save") + if got != seq { + t.Fatal("Click did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(ClickAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != "#save" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_Click_Bad(t *testing.T) { + seq := NewActionSequence() + got := seq.Click("") + if got != seq { + t.Fatal("Click did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(ClickAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != "" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_Click_Ugly(t *testing.T) { + seq := NewActionSequence() + got := seq.Click("button[data-x=\"1\"]") + if got != seq { + t.Fatal("Click did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(ClickAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != "button[data-x=\"1\"]" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_Type_Good(t *testing.T) { + seq := NewActionSequence() + got := seq.Type("#email", "a@b.test") + if got != seq { + t.Fatal("Type did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(TypeAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != "#email" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_Type_Bad(t *testing.T) { + seq := NewActionSequence() + got := seq.Type("", "") + if got != seq { + t.Fatal("Type did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(TypeAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Text != "" { + t.Fatalf("Text = %#v", action.Text) + } +} + +func TestAX7_ActionSequence_Type_Ugly(t *testing.T) { + seq := NewActionSequence() + got := seq.Type("textarea", "line\nnext") + if got != seq { + t.Fatal("Type did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(TypeAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Text != "line\nnext" { + t.Fatalf("Text = %#v", action.Text) + } +} + +func TestAX7_ActionSequence_Navigate_Good(t *testing.T) { + seq := NewActionSequence() + got := seq.Navigate("https://example.com") + if got != seq { + t.Fatal("Navigate did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(NavigateAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.URL != "https://example.com" { + t.Fatalf("URL = %#v", action.URL) + } +} + +func TestAX7_ActionSequence_Navigate_Bad(t *testing.T) { + seq := NewActionSequence() + got := seq.Navigate("javascript:alert(1)") + if got != seq { + t.Fatal("Navigate did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(NavigateAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.URL != "javascript:alert(1)" { + t.Fatalf("URL = %#v", action.URL) + } +} + +func TestAX7_ActionSequence_Navigate_Ugly(t *testing.T) { + seq := NewActionSequence() + got := seq.Navigate("about:blank") + if got != seq { + t.Fatal("Navigate did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(NavigateAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.URL != "about:blank" { + t.Fatalf("URL = %#v", action.URL) + } +} + +func TestAX7_ActionSequence_Wait_Good(t *testing.T) { + seq := NewActionSequence() + got := seq.Wait(5 * time.Millisecond) + if got != seq { + t.Fatal("Wait did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(WaitAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Duration != 5*time.Millisecond { + t.Fatalf("Duration = %#v", action.Duration) + } +} + +func TestAX7_ActionSequence_Wait_Bad(t *testing.T) { + seq := NewActionSequence() + got := seq.Wait(-1 * time.Millisecond) + if got != seq { + t.Fatal("Wait did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(WaitAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Duration != -1*time.Millisecond { + t.Fatalf("Duration = %#v", action.Duration) + } +} + +func TestAX7_ActionSequence_Wait_Ugly(t *testing.T) { + seq := NewActionSequence() + got := seq.Wait(0) + if got != seq { + t.Fatal("Wait did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(WaitAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Duration != 0 { + t.Fatalf("Duration = %#v", action.Duration) + } +} + +func TestAX7_ActionSequence_WaitForSelector_Good(t *testing.T) { + seq := NewActionSequence() + got := seq.WaitForSelector("#ready") + if got != seq { + t.Fatal("WaitForSelector did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(WaitForSelectorAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != "#ready" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_WaitForSelector_Bad(t *testing.T) { + seq := NewActionSequence() + got := seq.WaitForSelector("") + if got != seq { + t.Fatal("WaitForSelector did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(WaitForSelectorAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != "" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_WaitForSelector_Ugly(t *testing.T) { + seq := NewActionSequence() + got := seq.WaitForSelector("[data-ready=true]") + if got != seq { + t.Fatal("WaitForSelector did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(WaitForSelectorAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != "[data-ready=true]" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_Scroll_Good(t *testing.T) { + seq := NewActionSequence() + got := seq.Scroll(1, 2) + if got != seq { + t.Fatal("Scroll did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(ScrollAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.X != 1 { + t.Fatalf("X = %#v", action.X) + } +} + +func TestAX7_ActionSequence_Scroll_Bad(t *testing.T) { + seq := NewActionSequence() + got := seq.Scroll(-1, -2) + if got != seq { + t.Fatal("Scroll did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(ScrollAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Y != -2 { + t.Fatalf("Y = %#v", action.Y) + } +} + +func TestAX7_ActionSequence_Scroll_Ugly(t *testing.T) { + seq := NewActionSequence() + got := seq.Scroll(0, 0) + if got != seq { + t.Fatal("Scroll did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(ScrollAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.X != 0 { + t.Fatalf("X = %#v", action.X) + } +} + +func TestAX7_ActionSequence_ScrollIntoView_Good(t *testing.T) { + seq := NewActionSequence() + got := seq.ScrollIntoView("#target") + if got != seq { + t.Fatal("ScrollIntoView did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(ScrollIntoViewAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != "#target" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_ScrollIntoView_Bad(t *testing.T) { + seq := NewActionSequence() + got := seq.ScrollIntoView("") + if got != seq { + t.Fatal("ScrollIntoView did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(ScrollIntoViewAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != "" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_ScrollIntoView_Ugly(t *testing.T) { + seq := NewActionSequence() + got := seq.ScrollIntoView("main > .item") + if got != seq { + t.Fatal("ScrollIntoView did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(ScrollIntoViewAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != "main > .item" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_Focus_Good(t *testing.T) { + seq := NewActionSequence() + got := seq.Focus("#input") + if got != seq { + t.Fatal("Focus did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(FocusAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != "#input" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_Focus_Bad(t *testing.T) { + seq := NewActionSequence() + got := seq.Focus("") + if got != seq { + t.Fatal("Focus did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(FocusAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != "" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_Focus_Ugly(t *testing.T) { + seq := NewActionSequence() + got := seq.Focus("input[name=email]") + if got != seq { + t.Fatal("Focus did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(FocusAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != "input[name=email]" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_Blur_Good(t *testing.T) { + seq := NewActionSequence() + got := seq.Blur("#input") + if got != seq { + t.Fatal("Blur did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(BlurAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != "#input" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_Blur_Bad(t *testing.T) { + seq := NewActionSequence() + got := seq.Blur("") + if got != seq { + t.Fatal("Blur did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(BlurAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != "" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_Blur_Ugly(t *testing.T) { + seq := NewActionSequence() + got := seq.Blur("input[name=email]") + if got != seq { + t.Fatal("Blur did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(BlurAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != "input[name=email]" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_Clear_Good(t *testing.T) { + seq := NewActionSequence() + got := seq.Clear("#input") + if got != seq { + t.Fatal("Clear did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(ClearAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != "#input" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_Clear_Bad(t *testing.T) { + seq := NewActionSequence() + got := seq.Clear("") + if got != seq { + t.Fatal("Clear did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(ClearAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != "" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_Clear_Ugly(t *testing.T) { + seq := NewActionSequence() + got := seq.Clear("textarea") + if got != seq { + t.Fatal("Clear did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(ClearAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != "textarea" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_Select_Good(t *testing.T) { + seq := NewActionSequence() + got := seq.Select("select", "a") + if got != seq { + t.Fatal("Select did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(SelectAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Value != "a" { + t.Fatalf("Value = %#v", action.Value) + } +} + +func TestAX7_ActionSequence_Select_Bad(t *testing.T) { + seq := NewActionSequence() + got := seq.Select("select", "") + if got != seq { + t.Fatal("Select did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(SelectAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Value != "" { + t.Fatalf("Value = %#v", action.Value) + } +} + +func TestAX7_ActionSequence_Select_Ugly(t *testing.T) { + seq := NewActionSequence() + got := seq.Select("select", "1 2") + if got != seq { + t.Fatal("Select did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(SelectAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Value != "1 2" { + t.Fatalf("Value = %#v", action.Value) + } +} + +func TestAX7_ActionSequence_Check_Good(t *testing.T) { + seq := NewActionSequence() + got := seq.Check("#ok", true) + if got != seq { + t.Fatal("Check did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(CheckAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Checked != true { + t.Fatalf("Checked = %#v", action.Checked) + } +} + +func TestAX7_ActionSequence_Check_Bad(t *testing.T) { + seq := NewActionSequence() + got := seq.Check("#ok", false) + if got != seq { + t.Fatal("Check did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(CheckAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Checked != false { + t.Fatalf("Checked = %#v", action.Checked) + } +} + +func TestAX7_ActionSequence_Check_Ugly(t *testing.T) { + seq := NewActionSequence() + got := seq.Check("", true) + if got != seq { + t.Fatal("Check did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(CheckAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != "" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_Hover_Good(t *testing.T) { + seq := NewActionSequence() + got := seq.Hover("#menu") + if got != seq { + t.Fatal("Hover did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(HoverAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != "#menu" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_Hover_Bad(t *testing.T) { + seq := NewActionSequence() + got := seq.Hover("") + if got != seq { + t.Fatal("Hover did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(HoverAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != "" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_Hover_Ugly(t *testing.T) { + seq := NewActionSequence() + got := seq.Hover("[role=menuitem]") + if got != seq { + t.Fatal("Hover did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(HoverAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != "[role=menuitem]" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_DoubleClick_Good(t *testing.T) { + seq := NewActionSequence() + got := seq.DoubleClick("#item") + if got != seq { + t.Fatal("DoubleClick did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(DoubleClickAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != "#item" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_DoubleClick_Bad(t *testing.T) { + seq := NewActionSequence() + got := seq.DoubleClick("") + if got != seq { + t.Fatal("DoubleClick did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(DoubleClickAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != "" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_DoubleClick_Ugly(t *testing.T) { + seq := NewActionSequence() + got := seq.DoubleClick(".row:first-child") + if got != seq { + t.Fatal("DoubleClick did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(DoubleClickAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != ".row:first-child" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_RightClick_Good(t *testing.T) { + seq := NewActionSequence() + got := seq.RightClick("#item") + if got != seq { + t.Fatal("RightClick did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(RightClickAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != "#item" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_RightClick_Bad(t *testing.T) { + seq := NewActionSequence() + got := seq.RightClick("") + if got != seq { + t.Fatal("RightClick did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(RightClickAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != "" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_RightClick_Ugly(t *testing.T) { + seq := NewActionSequence() + got := seq.RightClick(".row:first-child") + if got != seq { + t.Fatal("RightClick did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(RightClickAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != ".row:first-child" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_PressKey_Good(t *testing.T) { + seq := NewActionSequence() + got := seq.PressKey("Enter") + if got != seq { + t.Fatal("PressKey did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(PressKeyAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Key != "Enter" { + t.Fatalf("Key = %#v", action.Key) + } +} + +func TestAX7_ActionSequence_PressKey_Bad(t *testing.T) { + seq := NewActionSequence() + got := seq.PressKey("") + if got != seq { + t.Fatal("PressKey did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(PressKeyAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Key != "" { + t.Fatalf("Key = %#v", action.Key) + } +} + +func TestAX7_ActionSequence_PressKey_Ugly(t *testing.T) { + seq := NewActionSequence() + got := seq.PressKey("a") + if got != seq { + t.Fatal("PressKey did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(PressKeyAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Key != "a" { + t.Fatalf("Key = %#v", action.Key) + } +} + +func TestAX7_ActionSequence_SetAttribute_Good(t *testing.T) { + seq := NewActionSequence() + got := seq.SetAttribute("#x", "data-id", "1") + if got != seq { + t.Fatal("SetAttribute did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(SetAttributeAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Attribute != "data-id" { + t.Fatalf("Attribute = %#v", action.Attribute) + } +} + +func TestAX7_ActionSequence_SetAttribute_Bad(t *testing.T) { + seq := NewActionSequence() + got := seq.SetAttribute("#x", "", "") + if got != seq { + t.Fatal("SetAttribute did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(SetAttributeAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Value != "" { + t.Fatalf("Value = %#v", action.Value) + } +} + +func TestAX7_ActionSequence_SetAttribute_Ugly(t *testing.T) { + seq := NewActionSequence() + got := seq.SetAttribute("#x", "aria-label", "A B") + if got != seq { + t.Fatal("SetAttribute did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(SetAttributeAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Value != "A B" { + t.Fatalf("Value = %#v", action.Value) + } +} + +func TestAX7_ActionSequence_RemoveAttribute_Good(t *testing.T) { + seq := NewActionSequence() + got := seq.RemoveAttribute("#x", "disabled") + if got != seq { + t.Fatal("RemoveAttribute did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(RemoveAttributeAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Attribute != "disabled" { + t.Fatalf("Attribute = %#v", action.Attribute) + } +} + +func TestAX7_ActionSequence_RemoveAttribute_Bad(t *testing.T) { + seq := NewActionSequence() + got := seq.RemoveAttribute("#x", "") + if got != seq { + t.Fatal("RemoveAttribute did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(RemoveAttributeAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Attribute != "" { + t.Fatalf("Attribute = %#v", action.Attribute) + } +} + +func TestAX7_ActionSequence_RemoveAttribute_Ugly(t *testing.T) { + seq := NewActionSequence() + got := seq.RemoveAttribute("#x", "data-id") + if got != seq { + t.Fatal("RemoveAttribute did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(RemoveAttributeAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Attribute != "data-id" { + t.Fatalf("Attribute = %#v", action.Attribute) + } +} + +func TestAX7_ActionSequence_SetValue_Good(t *testing.T) { + seq := NewActionSequence() + got := seq.SetValue("#x", "ready") + if got != seq { + t.Fatal("SetValue did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(SetValueAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Value != "ready" { + t.Fatalf("Value = %#v", action.Value) + } +} + +func TestAX7_ActionSequence_SetValue_Bad(t *testing.T) { + seq := NewActionSequence() + got := seq.SetValue("#x", "") + if got != seq { + t.Fatal("SetValue did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(SetValueAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Value != "" { + t.Fatalf("Value = %#v", action.Value) + } +} + +func TestAX7_ActionSequence_SetValue_Ugly(t *testing.T) { + seq := NewActionSequence() + got := seq.SetValue("#x", "line\nnext") + if got != seq { + t.Fatal("SetValue did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(SetValueAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Value != "line\nnext" { + t.Fatalf("Value = %#v", action.Value) + } +} + +func TestAX7_ActionSequence_UploadFile_Good(t *testing.T) { + seq := NewActionSequence() + got := seq.UploadFile("#file", []string{"/tmp/a.txt"}) + if got != seq { + t.Fatal("UploadFile did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(UploadFileAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.Selector != "#file" { + t.Fatalf("Selector = %#v", action.Selector) + } +} + +func TestAX7_ActionSequence_UploadFile_Bad(t *testing.T) { + seq := NewActionSequence() + got := seq.UploadFile("", nil) + if got != seq { + t.Fatal("UploadFile did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(UploadFileAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.FilePaths != nil { + t.Fatalf("FilePaths = %#v", action.FilePaths) + } +} + +func TestAX7_ActionSequence_UploadFile_Ugly(t *testing.T) { + seq := NewActionSequence() + got := seq.UploadFile("#file", []string{"/tmp/a.txt", "/tmp/b.txt"}) + if got != seq { + t.Fatal("UploadFile did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(UploadFileAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if len(action.FilePaths) != 2 || action.FilePaths[0] != "/tmp/a.txt" || action.FilePaths[1] != "/tmp/b.txt" { + t.Fatalf("FilePaths = %#v", action.FilePaths) + } +} + +func TestAX7_ActionSequence_DragAndDrop_Good(t *testing.T) { + seq := NewActionSequence() + got := seq.DragAndDrop("#a", "#b") + if got != seq { + t.Fatal("DragAndDrop did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(DragAndDropAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.SourceSelector != "#a" { + t.Fatalf("SourceSelector = %#v", action.SourceSelector) + } +} + +func TestAX7_ActionSequence_DragAndDrop_Bad(t *testing.T) { + seq := NewActionSequence() + got := seq.DragAndDrop("", "") + if got != seq { + t.Fatal("DragAndDrop did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(DragAndDropAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.TargetSelector != "" { + t.Fatalf("TargetSelector = %#v", action.TargetSelector) + } +} + +func TestAX7_ActionSequence_DragAndDrop_Ugly(t *testing.T) { + seq := NewActionSequence() + got := seq.DragAndDrop("[draggable]", "#drop") + if got != seq { + t.Fatal("DragAndDrop did not return the receiver") + } + if len(seq.actions) != 1 { + t.Fatalf("actions = %d", len(seq.actions)) + } + action, ok := seq.actions[0].(DragAndDropAction) + if !ok { + t.Fatalf("action type = %T", seq.actions[0]) + } + if action.SourceSelector != "[draggable]" { + t.Fatalf("SourceSelector = %#v", action.SourceSelector) + } +} + +func TestAX7_ScrollAction_Execute_Good(t *testing.T) { + wv := ax7EvaluateWebview(t, "window.scrollTo(3, 4)", false) + err := (ScrollAction{X: 3, Y: 4}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("ScrollAction.Execute returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_ScrollAction_Execute_Bad(t *testing.T) { + wv := ax7EvaluateWebview(t, "", true) + err := (ScrollAction{X: 3, Y: 4}).Execute(context.Background(), wv) + if err == nil { + t.Fatal("ScrollAction.Execute succeeded despite evaluation failure") + } + if !strings.Contains(err.Error(), "evaluation failed") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_ScrollAction_Execute_Ugly(t *testing.T) { + wv := ax7EvaluateWebview(t, "", false) + err := (ScrollAction{X: 0, Y: 0}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("ScrollAction.Execute rejected boundary input: %v", err) + } + if wv.timeout <= 0 { + t.Fatalf("fixture timeout = %v", wv.timeout) + } +} + +func TestAX7_ScrollIntoViewAction_Execute_Good(t *testing.T) { + wv := ax7EvaluateWebview(t, "scrollIntoView", false) + err := (ScrollIntoViewAction{Selector: "#target"}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("ScrollIntoViewAction.Execute returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_ScrollIntoViewAction_Execute_Bad(t *testing.T) { + wv := ax7EvaluateWebview(t, "", true) + err := (ScrollIntoViewAction{Selector: "#target"}).Execute(context.Background(), wv) + if err == nil { + t.Fatal("ScrollIntoViewAction.Execute succeeded despite evaluation failure") + } + if !strings.Contains(err.Error(), "evaluation failed") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_ScrollIntoViewAction_Execute_Ugly(t *testing.T) { + wv := ax7EvaluateWebview(t, "", false) + err := (ScrollIntoViewAction{Selector: ""}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("ScrollIntoViewAction.Execute rejected boundary input: %v", err) + } + if wv.timeout <= 0 { + t.Fatalf("fixture timeout = %v", wv.timeout) + } +} + +func TestAX7_FocusAction_Execute_Good(t *testing.T) { + wv := ax7EvaluateWebview(t, "focus()", false) + err := (FocusAction{Selector: "#input"}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("FocusAction.Execute returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_FocusAction_Execute_Bad(t *testing.T) { + wv := ax7EvaluateWebview(t, "", true) + err := (FocusAction{Selector: "#input"}).Execute(context.Background(), wv) + if err == nil { + t.Fatal("FocusAction.Execute succeeded despite evaluation failure") + } + if !strings.Contains(err.Error(), "evaluation failed") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_FocusAction_Execute_Ugly(t *testing.T) { + wv := ax7EvaluateWebview(t, "", false) + err := (FocusAction{Selector: ""}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("FocusAction.Execute rejected boundary input: %v", err) + } + if wv.timeout <= 0 { + t.Fatalf("fixture timeout = %v", wv.timeout) + } +} + +func TestAX7_BlurAction_Execute_Good(t *testing.T) { + wv := ax7EvaluateWebview(t, "blur()", false) + err := (BlurAction{Selector: "#input"}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("BlurAction.Execute returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_BlurAction_Execute_Bad(t *testing.T) { + wv := ax7EvaluateWebview(t, "", true) + err := (BlurAction{Selector: "#input"}).Execute(context.Background(), wv) + if err == nil { + t.Fatal("BlurAction.Execute succeeded despite evaluation failure") + } + if !strings.Contains(err.Error(), "evaluation failed") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_BlurAction_Execute_Ugly(t *testing.T) { + wv := ax7EvaluateWebview(t, "", false) + err := (BlurAction{Selector: ""}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("BlurAction.Execute rejected boundary input: %v", err) + } + if wv.timeout <= 0 { + t.Fatalf("fixture timeout = %v", wv.timeout) + } +} + +func TestAX7_ClearAction_Execute_Good(t *testing.T) { + wv := ax7EvaluateWebview(t, "el.value =", false) + err := (ClearAction{Selector: "#input"}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("ClearAction.Execute returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_ClearAction_Execute_Bad(t *testing.T) { + wv := ax7EvaluateWebview(t, "", true) + err := (ClearAction{Selector: "#input"}).Execute(context.Background(), wv) + if err == nil { + t.Fatal("ClearAction.Execute succeeded despite evaluation failure") + } + if !strings.Contains(err.Error(), "evaluation failed") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_ClearAction_Execute_Ugly(t *testing.T) { + wv := ax7EvaluateWebview(t, "", false) + err := (ClearAction{Selector: ""}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("ClearAction.Execute rejected boundary input: %v", err) + } + if wv.timeout <= 0 { + t.Fatalf("fixture timeout = %v", wv.timeout) + } +} + +func TestAX7_SelectAction_Execute_Good(t *testing.T) { + wv := ax7EvaluateWebview(t, `el.value = "one"`, false) + err := (SelectAction{Selector: "select", Value: "one"}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("SelectAction.Execute returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_SelectAction_Execute_Bad(t *testing.T) { + wv := ax7EvaluateWebview(t, "", true) + err := (SelectAction{Selector: "select", Value: "one"}).Execute(context.Background(), wv) + if err == nil { + t.Fatal("SelectAction.Execute succeeded despite evaluation failure") + } + if !strings.Contains(err.Error(), "evaluation failed") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_SelectAction_Execute_Ugly(t *testing.T) { + wv := ax7EvaluateWebview(t, "", false) + err := (SelectAction{Selector: "select", Value: ""}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("SelectAction.Execute rejected boundary input: %v", err) + } + if wv.timeout <= 0 { + t.Fatalf("fixture timeout = %v", wv.timeout) + } +} + +func TestAX7_CheckAction_Execute_Good(t *testing.T) { + wv := ax7EvaluateWebview(t, "checked !== true", false) + err := (CheckAction{Selector: "#agree", Checked: true}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("CheckAction.Execute returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_CheckAction_Execute_Bad(t *testing.T) { + wv := ax7EvaluateWebview(t, "", true) + err := (CheckAction{Selector: "#agree", Checked: true}).Execute(context.Background(), wv) + if err == nil { + t.Fatal("CheckAction.Execute succeeded despite evaluation failure") + } + if !strings.Contains(err.Error(), "evaluation failed") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_CheckAction_Execute_Ugly(t *testing.T) { + wv := ax7EvaluateWebview(t, "", false) + err := (CheckAction{Selector: "", Checked: false}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("CheckAction.Execute rejected boundary input: %v", err) + } + if wv.timeout <= 0 { + t.Fatalf("fixture timeout = %v", wv.timeout) + } +} + +func TestAX7_SetAttributeAction_Execute_Good(t *testing.T) { + wv := ax7EvaluateWebview(t, "setAttribute", false) + err := (SetAttributeAction{Selector: "#x", Attribute: "data-id", Value: "1"}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("SetAttributeAction.Execute returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_SetAttributeAction_Execute_Bad(t *testing.T) { + wv := ax7EvaluateWebview(t, "", true) + err := (SetAttributeAction{Selector: "#x", Attribute: "data-id", Value: "1"}).Execute(context.Background(), wv) + if err == nil { + t.Fatal("SetAttributeAction.Execute succeeded despite evaluation failure") + } + if !strings.Contains(err.Error(), "evaluation failed") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_SetAttributeAction_Execute_Ugly(t *testing.T) { + wv := ax7EvaluateWebview(t, "", false) + err := (SetAttributeAction{Selector: "#x", Attribute: "", Value: ""}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("SetAttributeAction.Execute rejected boundary input: %v", err) + } + if wv.timeout <= 0 { + t.Fatalf("fixture timeout = %v", wv.timeout) + } +} + +func TestAX7_RemoveAttributeAction_Execute_Good(t *testing.T) { + wv := ax7EvaluateWebview(t, "removeAttribute", false) + err := (RemoveAttributeAction{Selector: "#x", Attribute: "disabled"}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("RemoveAttributeAction.Execute returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_RemoveAttributeAction_Execute_Bad(t *testing.T) { + wv := ax7EvaluateWebview(t, "", true) + err := (RemoveAttributeAction{Selector: "#x", Attribute: "disabled"}).Execute(context.Background(), wv) + if err == nil { + t.Fatal("RemoveAttributeAction.Execute succeeded despite evaluation failure") + } + if !strings.Contains(err.Error(), "evaluation failed") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_RemoveAttributeAction_Execute_Ugly(t *testing.T) { + wv := ax7EvaluateWebview(t, "", false) + err := (RemoveAttributeAction{Selector: "#x", Attribute: ""}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("RemoveAttributeAction.Execute rejected boundary input: %v", err) + } + if wv.timeout <= 0 { + t.Fatalf("fixture timeout = %v", wv.timeout) + } +} + +func TestAX7_SetValueAction_Execute_Good(t *testing.T) { + wv := ax7EvaluateWebview(t, `el.value = "ready"`, false) + err := (SetValueAction{Selector: "#x", Value: "ready"}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("SetValueAction.Execute returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_SetValueAction_Execute_Bad(t *testing.T) { + wv := ax7EvaluateWebview(t, "", true) + err := (SetValueAction{Selector: "#x", Value: "ready"}).Execute(context.Background(), wv) + if err == nil { + t.Fatal("SetValueAction.Execute succeeded despite evaluation failure") + } + if !strings.Contains(err.Error(), "evaluation failed") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_SetValueAction_Execute_Ugly(t *testing.T) { + wv := ax7EvaluateWebview(t, "", false) + err := (SetValueAction{Selector: "#x", Value: ""}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("SetValueAction.Execute rejected boundary input: %v", err) + } + if wv.timeout <= 0 { + t.Fatalf("fixture timeout = %v", wv.timeout) + } +} + +func TestAX7_ClickAction_Execute_Good(t *testing.T) { + wv := ax7DOMWebview(t, true) + err := (ClickAction{Selector: "#button"}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("ClickAction.Execute returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_ClickAction_Execute_Bad(t *testing.T) { + wv := ax7MissingDOMWebview(t) + err := (ClickAction{Selector: "#missing"}).Execute(context.Background(), wv) + if err == nil { + t.Fatal("ClickAction.Execute succeeded for missing element") + } + if !strings.Contains(err.Error(), "element not found") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_ClickAction_Execute_Ugly(t *testing.T) { + wv := ax7DOMWebview(t, false) + err := (ClickAction{Selector: "#button"}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("ClickAction.Execute fallback returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_HoverAction_Execute_Good(t *testing.T) { + wv := ax7DOMWebview(t, true) + err := (HoverAction{Selector: "#menu"}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("HoverAction.Execute returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_HoverAction_Execute_Bad(t *testing.T) { + wv := ax7MissingDOMWebview(t) + err := (HoverAction{Selector: "#missing"}).Execute(context.Background(), wv) + if err == nil { + t.Fatal("HoverAction.Execute succeeded for missing element") + } + if !strings.Contains(err.Error(), "element not found") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_HoverAction_Execute_Ugly(t *testing.T) { + wv := ax7DOMWebview(t, false) + err := (HoverAction{Selector: "#menu"}).Execute(context.Background(), wv) + if err == nil { + t.Fatal("HoverAction.Execute succeeded without bounding box") + } + if !strings.Contains(err.Error(), "bounding box") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_DoubleClickAction_Execute_Good(t *testing.T) { + wv := ax7DOMWebview(t, true) + err := (DoubleClickAction{Selector: "#item"}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("DoubleClickAction.Execute returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_DoubleClickAction_Execute_Bad(t *testing.T) { + wv := ax7MissingDOMWebview(t) + err := (DoubleClickAction{Selector: "#missing"}).Execute(context.Background(), wv) + if err == nil { + t.Fatal("DoubleClickAction.Execute succeeded for missing element") + } + if !strings.Contains(err.Error(), "element not found") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_DoubleClickAction_Execute_Ugly(t *testing.T) { + wv := ax7DOMWebview(t, false) + err := (DoubleClickAction{Selector: "#item"}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("DoubleClickAction.Execute fallback returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_RightClickAction_Execute_Good(t *testing.T) { + wv := ax7DOMWebview(t, true) + err := (RightClickAction{Selector: "#item"}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("RightClickAction.Execute returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_RightClickAction_Execute_Bad(t *testing.T) { + wv := ax7MissingDOMWebview(t) + err := (RightClickAction{Selector: "#missing"}).Execute(context.Background(), wv) + if err == nil { + t.Fatal("RightClickAction.Execute succeeded for missing element") + } + if !strings.Contains(err.Error(), "element not found") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_RightClickAction_Execute_Ugly(t *testing.T) { + wv := ax7DOMWebview(t, false) + err := (RightClickAction{Selector: "#item"}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("RightClickAction.Execute fallback returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_TypeAction_Execute_Good(t *testing.T) { + wv, _ := newActionHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { + switch msg.Method { + case "Runtime.evaluate", "Input.dispatchKeyEvent": + target.reply(msg.ID, map[string]any{"result": map[string]any{"value": true}}) + default: + t.Fatalf("unexpected method %q", msg.Method) + } + }) + err := (TypeAction{Selector: "#email", Text: "ab"}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("TypeAction.Execute returned error: %v", err) + } +} + +func TestAX7_TypeAction_Execute_Bad(t *testing.T) { + wv := ax7EvaluateWebview(t, "focus", true) + err := (TypeAction{Selector: "#email", Text: "ab"}).Execute(context.Background(), wv) + if err == nil { + t.Fatal("TypeAction.Execute succeeded despite focus failure") + } + if !strings.Contains(err.Error(), "focus") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_TypeAction_Execute_Ugly(t *testing.T) { + wv := ax7EvaluateWebview(t, "focus", false) + err := (TypeAction{Selector: "#email", Text: ""}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("TypeAction.Execute rejected empty text: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_NavigateAction_Execute_Good(t *testing.T) { + wv := ax7NavigationWebview(t) + err := (NavigateAction{URL: "https://example.com"}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("NavigateAction.Execute returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_NavigateAction_Execute_Bad(t *testing.T) { + wv := ax7NavigationWebview(t) + err := (NavigateAction{URL: "javascript:alert(1)"}).Execute(context.Background(), wv) + if err == nil { + t.Fatal("NavigateAction.Execute accepted dangerous URL") + } + if !strings.Contains(err.Error(), "navigation URL") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_NavigateAction_Execute_Ugly(t *testing.T) { + wv := ax7NavigationWebview(t) + err := (NavigateAction{URL: "about:blank"}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("NavigateAction.Execute rejected about:blank: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_WaitAction_Execute_Good(t *testing.T) { + err := (WaitAction{Duration: time.Millisecond}).Execute(context.Background(), nil) + if err != nil { + t.Fatalf("WaitAction.Execute returned error: %v", err) + } + if time.Millisecond <= 0 { + t.Fatal("invalid test duration") + } +} + +func TestAX7_WaitAction_Execute_Bad(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + cancel() + err := (WaitAction{Duration: time.Second}).Execute(ctx, nil) + if err == nil { + t.Fatal("WaitAction.Execute ignored canceled context") + } +} + +func TestAX7_WaitAction_Execute_Ugly(t *testing.T) { + err := (WaitAction{Duration: 0}).Execute(context.Background(), nil) + if err != nil { + t.Fatalf("WaitAction.Execute rejected zero duration: %v", err) + } + if (WaitAction{Duration: 0}).Duration != 0 { + t.Fatal("zero duration mutated") + } +} + +func TestAX7_WaitForSelectorAction_Execute_Good(t *testing.T) { + wv := ax7EvaluateWebview(t, "querySelector", false) + err := (WaitForSelectorAction{Selector: "#ready"}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("WaitForSelectorAction.Execute returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_WaitForSelectorAction_Execute_Bad(t *testing.T) { + wv := ax7EvaluateWebview(t, "querySelector", true) + ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond) + defer cancel() + err := (WaitForSelectorAction{Selector: "#never"}).Execute(ctx, wv) + if err == nil { + t.Fatal("WaitForSelectorAction.Execute succeeded without selector") + } +} + +func TestAX7_WaitForSelectorAction_Execute_Ugly(t *testing.T) { + wv := ax7EvaluateWebview(t, "querySelector", false) + err := (WaitForSelectorAction{Selector: ""}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("WaitForSelectorAction.Execute rejected empty selector: %v", err) + } + if wv.timeout <= 0 { + t.Fatalf("fixture timeout = %v", wv.timeout) + } +} + +func TestAX7_PressKeyAction_Execute_Good(t *testing.T) { + wv, _ := newActionHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Input.dispatchKeyEvent" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.reply(msg.ID, map[string]any{}) + }) + err := (PressKeyAction{Key: "Enter"}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("PressKeyAction.Execute returned error: %v", err) + } +} + +func TestAX7_PressKeyAction_Execute_Bad(t *testing.T) { + wv, _ := newActionHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { target.replyError(msg.ID, "key failed") }) + err := (PressKeyAction{Key: "Enter"}).Execute(context.Background(), wv) + if err == nil { + t.Fatal("PressKeyAction.Execute succeeded despite CDP error") + } +} + +func TestAX7_PressKeyAction_Execute_Ugly(t *testing.T) { + wv, _ := newActionHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Input.dispatchKeyEvent" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.reply(msg.ID, map[string]any{}) + }) + err := (PressKeyAction{Key: "a"}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("PressKeyAction.Execute rejected simple key: %v", err) + } +} + +func TestAX7_UploadFileAction_Execute_Good(t *testing.T) { + wv := ax7DOMWebview(t, true) + err := (UploadFileAction{Selector: "#file", FilePaths: []string{"/tmp/a.txt"}}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("UploadFileAction.Execute returned error: %v", err) + } +} + +func TestAX7_UploadFileAction_Execute_Bad(t *testing.T) { + err := (UploadFileAction{Selector: "#file"}).Execute(context.Background(), nil) + if err == nil { + t.Fatal("UploadFileAction.Execute accepted nil webview") + } + if !strings.Contains(err.Error(), "webview") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_UploadFileAction_Execute_Ugly(t *testing.T) { + wv := ax7DOMWebview(t, true) + err := (UploadFileAction{Selector: "#file", FilePaths: nil}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("UploadFileAction.Execute rejected nil file list: %v", err) + } +} + +func TestAX7_DragAndDropAction_Execute_Good(t *testing.T) { + wv := ax7DOMWebview(t, true) + err := (DragAndDropAction{SourceSelector: "#a", TargetSelector: "#b"}).Execute(context.Background(), wv) + if err != nil { + t.Fatalf("DragAndDropAction.Execute returned error: %v", err) + } +} + +func TestAX7_DragAndDropAction_Execute_Bad(t *testing.T) { + err := (DragAndDropAction{SourceSelector: "#a", TargetSelector: "#b"}).Execute(context.Background(), nil) + if err == nil { + t.Fatal("DragAndDropAction.Execute accepted nil webview") + } + if !strings.Contains(err.Error(), "webview") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_DragAndDropAction_Execute_Ugly(t *testing.T) { + wv := ax7DOMWebview(t, false) + err := (DragAndDropAction{SourceSelector: "#a", TargetSelector: "#b"}).Execute(context.Background(), wv) + if err == nil { + t.Fatal("DragAndDropAction.Execute succeeded without source box") + } +} + +func TestAX7_NewCDPClient_Good(t *testing.T) { + server := newFakeCDPServer(t) + client, err := NewCDPClient(server.DebugURL()) + if err != nil { + t.Fatalf("NewCDPClient returned error: %v", err) + } + if client.DebugURL() != server.DebugURL() { + t.Fatalf("DebugURL = %q", client.DebugURL()) + } +} + +func TestAX7_NewCDPClient_Bad(t *testing.T) { + client, err := NewCDPClient("http://example.com:9222") + if err == nil { + t.Fatal("NewCDPClient accepted remote endpoint") + } + if client != nil { + t.Fatalf("client = %#v", client) + } +} + +func TestAX7_NewCDPClient_Ugly(t *testing.T) { + server := newFakeCDPServer(t) + server.mu.Lock() + server.targets = map[string]*fakeCDPTarget{} + server.mu.Unlock() + client, err := NewCDPClient(server.DebugURL()) + if err != nil { + t.Fatalf("NewCDPClient did not auto-create target: %v", err) + } + if client.WebSocketURL() == "" { + t.Fatal("auto-created client has empty WebSocketURL") + } +} + +func TestAX7_CDPClient_Call_Good(t *testing.T) { + server := newFakeCDPServer(t) + target := server.primaryTarget() + target.onMessage = func(target *fakeCDPTarget, msg cdpMessage) { target.reply(msg.ID, map[string]any{"ok": true}) } + client := newConnectedCDPClient(t, target) + got, err := client.Call(context.Background(), "Runtime.evaluate", nil) + if err != nil || got["ok"] != true { + t.Fatalf("Call = %#v, %v", got, err) + } +} + +func TestAX7_CDPClient_Call_Bad(t *testing.T) { + server := newFakeCDPServer(t) + target := server.primaryTarget() + target.onMessage = func(target *fakeCDPTarget, msg cdpMessage) { target.replyError(msg.ID, "refused") } + client := newConnectedCDPClient(t, target) + _, err := client.Call(context.Background(), "Runtime.evaluate", nil) + if err == nil { + t.Fatal("Call succeeded after CDP error") + } +} + +func TestAX7_CDPClient_Call_Ugly(t *testing.T) { + server := newFakeCDPServer(t) + client := newConnectedCDPClient(t, server.primaryTarget()) + ctx, cancel := context.WithCancel(context.Background()) + cancel() + _, err := client.Call(ctx, "Runtime.evaluate", map[string]any{"nested": map[string]any{"x": 1}}) + if err == nil { + t.Fatal("Call ignored canceled context") + } +} + +func TestAX7_CDPClient_Send_Good(t *testing.T) { + server := newFakeCDPServer(t) + target := server.primaryTarget() + seen := make(chan cdpMessage, 1) + target.onMessage = func(_ *fakeCDPTarget, msg cdpMessage) { seen <- msg } + client := newConnectedCDPClient(t, target) + if err := client.Send("Page.enable", map[string]any{"x": "y"}); err != nil { + t.Fatalf("Send returned error: %v", err) + } + if (<-seen).Method != "Page.enable" { + t.Fatal("Send delivered wrong method") + } +} + +func TestAX7_CDPClient_Send_Bad(t *testing.T) { + server := newFakeCDPServer(t) + client := newConnectedCDPClient(t, server.primaryTarget()) + if err := client.Close(); err != nil { + t.Fatalf("Close returned error: %v", err) + } + if err := client.Send("Page.enable", nil); err == nil { + t.Fatal("Send succeeded after Close") + } +} + +func TestAX7_CDPClient_Send_Ugly(t *testing.T) { + server := newFakeCDPServer(t) + target := server.primaryTarget() + seen := make(chan cdpMessage, 1) + target.onMessage = func(_ *fakeCDPTarget, msg cdpMessage) { seen <- msg } + client := newConnectedCDPClient(t, target) + if err := client.Send("Runtime.enable", nil); err != nil { + t.Fatalf("Send nil params returned error: %v", err) + } + if (<-seen).Params != nil { + t.Fatal("Send nil params encoded unexpected params") + } +} + +func TestAX7_CDPClient_Close_Good(t *testing.T) { + server := newFakeCDPServer(t) + client := newConnectedCDPClient(t, server.primaryTarget()) + if err := client.Close(); err != nil { + t.Fatalf("Close returned error: %v", err) + } + if client.ctx.Err() == nil { + t.Fatal("Close did not cancel context") + } +} + +func TestAX7_CDPClient_Close_Bad(t *testing.T) { + var client *CDPClient + defer func() { + if recover() == nil { + t.Fatal("Close on nil client did not panic") + } + }() + client.Close() +} + +func TestAX7_CDPClient_Close_Ugly(t *testing.T) { + server := newFakeCDPServer(t) + client := newConnectedCDPClient(t, server.primaryTarget()) + if err := client.Close(); err != nil { + t.Fatalf("first Close returned error: %v", err) + } + if err := client.Close(); err != nil { + t.Fatalf("second Close returned error: %v", err) + } +} + +func TestAX7_CDPClient_CloseTab_Good(t *testing.T) { + server := newFakeCDPServer(t) + target := server.primaryTarget() + target.onMessage = func(target *fakeCDPTarget, msg cdpMessage) { target.reply(msg.ID, map[string]any{"success": true}) } + client := newConnectedCDPClient(t, target) + if err := client.CloseTab(); err != nil { + t.Fatalf("CloseTab returned error: %v", err) + } +} + +func TestAX7_CDPClient_CloseTab_Bad(t *testing.T) { + server := newFakeCDPServer(t) + target := server.primaryTarget() + target.onMessage = func(target *fakeCDPTarget, msg cdpMessage) { target.reply(msg.ID, map[string]any{"success": false}) } + client := newConnectedCDPClient(t, target) + if err := client.CloseTab(); err == nil { + t.Fatal("CloseTab ignored failed acknowledgement") + } +} + +func TestAX7_CDPClient_CloseTab_Ugly(t *testing.T) { + server := newFakeCDPServer(t) + client := newConnectedCDPClient(t, server.primaryTarget()) + if err := client.Close(); err != nil { + t.Fatalf("Close returned error: %v", err) + } + if err := client.CloseTab(); err == nil { + t.Fatal("CloseTab succeeded after client close") + } +} + +func TestAX7_CDPClient_DebugURL_Good(t *testing.T) { + server := newFakeCDPServer(t) + client := newConnectedCDPClient(t, server.primaryTarget()) + if client.DebugURL() != server.DebugURL() { + t.Fatalf("DebugURL = %q", client.DebugURL()) + } +} + +func TestAX7_CDPClient_DebugURL_Bad(t *testing.T) { + client := &CDPClient{} + if client.DebugURL() != "" { + t.Fatalf("zero DebugURL = %q", client.DebugURL()) + } + if client.WebSocketURL() != "" { + t.Fatalf("zero WebSocketURL = %q", client.WebSocketURL()) + } +} + +func TestAX7_CDPClient_DebugURL_Ugly(t *testing.T) { + server := newFakeCDPServer(t) + client := newConnectedCDPClient(t, server.primaryTarget()) + _ = client.Close() + if client.DebugURL() != server.DebugURL() { + t.Fatalf("DebugURL changed after close: %q", client.DebugURL()) + } +} + +func TestAX7_CDPClient_WebSocketURL_Good(t *testing.T) { + server := newFakeCDPServer(t) + client := newConnectedCDPClient(t, server.primaryTarget()) + if !strings.Contains(client.WebSocketURL(), "/devtools/page/") { + t.Fatalf("WebSocketURL = %q", client.WebSocketURL()) + } +} + +func TestAX7_CDPClient_WebSocketURL_Bad(t *testing.T) { + client := &CDPClient{} + if client.WebSocketURL() != "" { + t.Fatalf("zero WebSocketURL = %q", client.WebSocketURL()) + } + if client.DebugURL() != "" { + t.Fatalf("zero DebugURL = %q", client.DebugURL()) + } +} + +func TestAX7_CDPClient_WebSocketURL_Ugly(t *testing.T) { + server := newFakeCDPServer(t) + client := newConnectedCDPClient(t, server.primaryTarget()) + before := client.WebSocketURL() + _ = client.Close() + if client.WebSocketURL() != before { + t.Fatalf("WebSocketURL changed after close: %q", client.WebSocketURL()) + } +} + +func TestAX7_CDPClient_NewTab_Good(t *testing.T) { + server := newFakeCDPServer(t) + client := newConnectedCDPClient(t, server.primaryTarget()) + tab, err := client.NewTab("about:blank") + if err != nil { + t.Fatalf("NewTab returned error: %v", err) + } + if tab.WebSocketURL() == "" { + t.Fatal("NewTab returned empty WebSocketURL") + } +} + +func TestAX7_CDPClient_NewTab_Bad(t *testing.T) { + server := newFakeCDPServer(t) + client := newConnectedCDPClient(t, server.primaryTarget()) + _, err := client.NewTab("javascript:alert(1)") + if err == nil { + t.Fatal("NewTab accepted dangerous URL") + } +} + +func TestAX7_CDPClient_NewTab_Ugly(t *testing.T) { + server := newFakeCDPServer(t) + client := newConnectedCDPClient(t, server.primaryTarget()) + tab, err := client.NewTab("") + if err != nil { + t.Fatalf("NewTab rejected empty URL: %v", err) + } + if tab.DebugURL() != server.DebugURL() { + t.Fatalf("tab DebugURL = %q", tab.DebugURL()) + } +} + +func TestAX7_CDPClient_OnEvent_Good(t *testing.T) { + server := newFakeCDPServer(t) + target := server.primaryTarget() + client := newConnectedCDPClient(t, target) + seen := make(chan map[string]any, 1) + client.OnEvent("Runtime.consoleAPICalled", func(params map[string]any) { seen <- params }) + target.writeJSON(cdpEvent{Method: "Runtime.consoleAPICalled", Params: map[string]any{"type": "log"}}) + if (<-seen)["type"] != "log" { + t.Fatal("OnEvent handler did not receive params") + } +} + +func TestAX7_CDPClient_OnEvent_Bad(t *testing.T) { + server := newFakeCDPServer(t) + client := newConnectedCDPClient(t, server.primaryTarget()) + client.OnEvent("", func(map[string]any) {}) + if len(client.handlers[""]) != 1 { + t.Fatalf("empty event handlers = %d", len(client.handlers[""])) + } +} + +func TestAX7_CDPClient_OnEvent_Ugly(t *testing.T) { + server := newFakeCDPServer(t) + client := newConnectedCDPClient(t, server.primaryTarget()) + client.OnEvent("Runtime.event", func(map[string]any) {}) + client.OnEvent("Runtime.event", func(map[string]any) {}) + if len(client.handlers["Runtime.event"]) != 2 { + t.Fatalf("handlers = %d", len(client.handlers["Runtime.event"])) + } +} + +func TestAX7_ListTargets_Good(t *testing.T) { + server := newFakeCDPServer(t) + targets, err := ListTargets(server.DebugURL()) + if err != nil { + t.Fatalf("ListTargets returned error: %v", err) + } + if len(targets) != 1 || targets[0].Type != "page" { + t.Fatalf("targets = %#v", targets) + } +} + +func TestAX7_ListTargets_Bad(t *testing.T) { + targets, err := ListTargets("http://example.com:9222") + if err == nil { + t.Fatal("ListTargets accepted remote URL") + } + if targets != nil { + t.Fatalf("targets = %#v", targets) + } +} + +func TestAX7_ListTargets_Ugly(t *testing.T) { + server := newFakeCDPServer(t) + server.addTarget("target-ugly") + targets, err := ListTargets(server.DebugURL()) + if err != nil { + t.Fatalf("ListTargets returned error: %v", err) + } + if len(targets) != 2 { + t.Fatalf("targets = %#v", targets) + } +} + +func TestAX7_ListTargetsAll_Good(t *testing.T) { + server := newFakeCDPServer(t) + var targets []TargetInfo + for target := range ListTargetsAll(server.DebugURL()) { + targets = append(targets, target) + } + if len(targets) != 1 { + t.Fatalf("targets = %#v", targets) + } +} + +func TestAX7_ListTargetsAll_Bad(t *testing.T) { + var targets []TargetInfo + for target := range ListTargetsAll("http://example.com:9222") { + targets = append(targets, target) + } + if len(targets) != 0 { + t.Fatalf("targets = %#v", targets) + } +} + +func TestAX7_ListTargetsAll_Ugly(t *testing.T) { + server := newFakeCDPServer(t) + seen := 0 + for range ListTargetsAll(server.DebugURL()) { + seen++ + break + } + if seen != 1 { + t.Fatalf("seen = %d", seen) + } +} + +func TestAX7_GetVersion_Good(t *testing.T) { + server := newFakeCDPServer(t) + version, err := GetVersion(server.DebugURL()) + if err != nil { + t.Fatalf("GetVersion returned error: %v", err) + } + if version["Browser"] != "Chrome/123.0" { + t.Fatalf("version = %#v", version) + } +} + +func TestAX7_GetVersion_Bad(t *testing.T) { + version, err := GetVersion("http://example.com:9222") + if err == nil { + t.Fatal("GetVersion accepted remote URL") + } + if version != nil { + t.Fatalf("version = %#v", version) + } +} + +func TestAX7_GetVersion_Ugly(t *testing.T) { + server := newFakeCDPServer(t) + version, err := GetVersion(server.DebugURL() + "/") + if err != nil { + t.Fatalf("GetVersion rejected trailing slash: %v", err) + } + if version["Browser"] == "" { + t.Fatalf("version = %#v", version) + } +} + +func TestAX7_URL_String_Good(t *testing.T) { + u := &cdpURL{Scheme: "http", Host: "localhost:9222", Path: "/"} + if got := u.String(); got != "http://localhost:9222/" { + t.Fatalf("String = %q", got) + } + if u.Host != "localhost:9222" { + t.Fatalf("Host = %q", u.Host) + } +} + +func TestAX7_URL_String_Bad(t *testing.T) { + var u *cdpURL + if got := u.String(); got != "" { + t.Fatalf("nil String = %q", got) + } + if u != nil { + t.Fatal("nil URL mutated") + } +} + +func TestAX7_URL_String_Ugly(t *testing.T) { + u := &cdpURL{Scheme: "http", Host: "localhost", Path: "/json", RawQuery: "a=b", Fragment: "top"} + if got := u.String(); got != "http://localhost/json?a=b#top" { + t.Fatalf("String = %q", got) + } + if u.RawQuery != "a=b" { + t.Fatalf("RawQuery = %q", u.RawQuery) + } +} + +func TestAX7_URL_Hostname_Good(t *testing.T) { + u := &cdpURL{hostname: "localhost"} + if got := u.Hostname(); got != "localhost" { + t.Fatalf("Hostname = %q", got) + } + if u.hostname == "" { + t.Fatal("hostname fixture empty") + } +} + +func TestAX7_URL_Hostname_Bad(t *testing.T) { + var u *cdpURL + if got := u.Hostname(); got != "" { + t.Fatalf("nil Hostname = %q", got) + } + if u != nil { + t.Fatal("nil URL mutated") + } +} + +func TestAX7_URL_Hostname_Ugly(t *testing.T) { + u := &cdpURL{hostname: "::1"} + if got := u.Hostname(); got != "::1" { + t.Fatalf("Hostname = %q", got) + } + if !strings.Contains(u.hostname, ":") { + t.Fatalf("hostname = %q", u.hostname) + } +} + +func TestAX7_URL_Port_Good(t *testing.T) { + u := &cdpURL{port: "9222"} + if got := u.Port(); got != "9222" { + t.Fatalf("Port = %q", got) + } + if u.port == "" { + t.Fatal("port fixture empty") + } +} + +func TestAX7_URL_Port_Bad(t *testing.T) { + var u *cdpURL + if got := u.Port(); got != "" { + t.Fatalf("nil Port = %q", got) + } + if u != nil { + t.Fatal("nil URL mutated") + } +} + +func TestAX7_URL_Port_Ugly(t *testing.T) { + u := &cdpURL{port: ""} + if got := u.Port(); got != "" { + t.Fatalf("empty Port = %q", got) + } + if u.String() != "" { + t.Fatalf("empty URL String = %q", u.String()) + } +} + +func TestAX7_ConsoleWatcher_Messages_Good(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "error", Text: "boom"}, ConsoleMessage{Type: "warning", Text: "careful"}) + var got []ConsoleMessage + got = cw.Messages() + if len(got) != 2 { + t.Fatalf("Messages len = %d", len(got)) + } + if cw.Count() != 2 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_ConsoleWatcher_Messages_Bad(t *testing.T) { + cw := ax7ConsoleWatcher() + var got []ConsoleMessage + got = cw.Messages() + if len(got) != 0 { + t.Fatalf("empty Messages len = %d", len(got)) + } + if cw.Count() != 0 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_ConsoleWatcher_Messages_Ugly(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "warn", Text: "legacy"}) + var got []ConsoleMessage + got = cw.Messages() + if len(got) != 1 { + t.Fatalf("ugly Messages len = %d", len(got)) + } + if cw.Count() != 1 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_ConsoleWatcher_MessagesAll_Good(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "error", Text: "boom"}, ConsoleMessage{Type: "warning", Text: "careful"}) + var got []ConsoleMessage + for msg := range cw.MessagesAll() { + got = append(got, msg) + } + if len(got) != 2 { + t.Fatalf("MessagesAll len = %d", len(got)) + } + if cw.Count() != 2 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_ConsoleWatcher_MessagesAll_Bad(t *testing.T) { + cw := ax7ConsoleWatcher() + var got []ConsoleMessage + for msg := range cw.MessagesAll() { + got = append(got, msg) + } + if len(got) != 0 { + t.Fatalf("empty MessagesAll len = %d", len(got)) + } + if cw.Count() != 0 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_ConsoleWatcher_MessagesAll_Ugly(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "warn", Text: "legacy"}) + var got []ConsoleMessage + for msg := range cw.MessagesAll() { + got = append(got, msg) + break + } + if len(got) != 1 { + t.Fatalf("ugly MessagesAll len = %d", len(got)) + } + if cw.Count() != 1 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_ConsoleWatcher_FilteredMessages_Good(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "error", Text: "boom"}, ConsoleMessage{Type: "warning", Text: "careful"}) + var got []ConsoleMessage + got = cw.FilteredMessages() + if len(got) != 2 { + t.Fatalf("FilteredMessages len = %d", len(got)) + } + if cw.Count() != 2 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_ConsoleWatcher_FilteredMessages_Bad(t *testing.T) { + cw := ax7ConsoleWatcher() + var got []ConsoleMessage + got = cw.FilteredMessages() + if len(got) != 0 { + t.Fatalf("empty FilteredMessages len = %d", len(got)) + } + if cw.Count() != 0 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_ConsoleWatcher_FilteredMessages_Ugly(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "warn", Text: "legacy"}) + var got []ConsoleMessage + got = cw.FilteredMessages() + if len(got) != 1 { + t.Fatalf("ugly FilteredMessages len = %d", len(got)) + } + if cw.Count() != 1 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_ConsoleWatcher_FilteredMessagesAll_Good(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "error", Text: "boom"}, ConsoleMessage{Type: "warning", Text: "careful"}) + var got []ConsoleMessage + for msg := range cw.FilteredMessagesAll() { + got = append(got, msg) + } + if len(got) != 2 { + t.Fatalf("FilteredMessagesAll len = %d", len(got)) + } + if cw.Count() != 2 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_ConsoleWatcher_FilteredMessagesAll_Bad(t *testing.T) { + cw := ax7ConsoleWatcher() + var got []ConsoleMessage + for msg := range cw.FilteredMessagesAll() { + got = append(got, msg) + } + if len(got) != 0 { + t.Fatalf("empty FilteredMessagesAll len = %d", len(got)) + } + if cw.Count() != 0 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_ConsoleWatcher_FilteredMessagesAll_Ugly(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "warn", Text: "legacy"}) + var got []ConsoleMessage + for msg := range cw.FilteredMessagesAll() { + got = append(got, msg) + break + } + if len(got) != 1 { + t.Fatalf("ugly FilteredMessagesAll len = %d", len(got)) + } + if cw.Count() != 1 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_ConsoleWatcher_Errors_Good(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "error", Text: "boom"}, ConsoleMessage{Type: "warning", Text: "careful"}) + var got []ConsoleMessage + got = cw.Errors() + if len(got) != 1 { + t.Fatalf("Errors len = %d", len(got)) + } + if cw.Count() != 2 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_ConsoleWatcher_Errors_Bad(t *testing.T) { + cw := ax7ConsoleWatcher() + var got []ConsoleMessage + got = cw.Errors() + if len(got) != 0 { + t.Fatalf("empty Errors len = %d", len(got)) + } + if cw.Count() != 0 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_ConsoleWatcher_Errors_Ugly(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "warn", Text: "legacy"}) + var got []ConsoleMessage + got = cw.Errors() + if len(got) != 0 { + t.Fatalf("ugly Errors len = %d", len(got)) + } + if cw.Count() != 1 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_ConsoleWatcher_ErrorsAll_Good(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "error", Text: "boom"}, ConsoleMessage{Type: "warning", Text: "careful"}) + var got []ConsoleMessage + for msg := range cw.ErrorsAll() { + got = append(got, msg) + } + if len(got) != 1 { + t.Fatalf("ErrorsAll len = %d", len(got)) + } + if cw.Count() != 2 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_ConsoleWatcher_ErrorsAll_Bad(t *testing.T) { + cw := ax7ConsoleWatcher() + var got []ConsoleMessage + for msg := range cw.ErrorsAll() { + got = append(got, msg) + } + if len(got) != 0 { + t.Fatalf("empty ErrorsAll len = %d", len(got)) + } + if cw.Count() != 0 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_ConsoleWatcher_ErrorsAll_Ugly(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "warn", Text: "legacy"}) + var got []ConsoleMessage + for msg := range cw.ErrorsAll() { + got = append(got, msg) + break + } + if len(got) != 0 { + t.Fatalf("ugly ErrorsAll len = %d", len(got)) + } + if cw.Count() != 1 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_ConsoleWatcher_Warnings_Good(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "error", Text: "boom"}, ConsoleMessage{Type: "warning", Text: "careful"}) + var got []ConsoleMessage + got = cw.Warnings() + if len(got) != 1 { + t.Fatalf("Warnings len = %d", len(got)) + } + if cw.Count() != 2 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_ConsoleWatcher_Warnings_Bad(t *testing.T) { + cw := ax7ConsoleWatcher() + var got []ConsoleMessage + got = cw.Warnings() + if len(got) != 0 { + t.Fatalf("empty Warnings len = %d", len(got)) + } + if cw.Count() != 0 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_ConsoleWatcher_Warnings_Ugly(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "warn", Text: "legacy"}) + var got []ConsoleMessage + got = cw.Warnings() + if len(got) != 1 { + t.Fatalf("ugly Warnings len = %d", len(got)) + } + if cw.Count() != 1 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_ConsoleWatcher_WarningsAll_Good(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "error", Text: "boom"}, ConsoleMessage{Type: "warning", Text: "careful"}) + var got []ConsoleMessage + for msg := range cw.WarningsAll() { + got = append(got, msg) + } + if len(got) != 1 { + t.Fatalf("WarningsAll len = %d", len(got)) + } + if cw.Count() != 2 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_ConsoleWatcher_WarningsAll_Bad(t *testing.T) { + cw := ax7ConsoleWatcher() + var got []ConsoleMessage + for msg := range cw.WarningsAll() { + got = append(got, msg) + } + if len(got) != 0 { + t.Fatalf("empty WarningsAll len = %d", len(got)) + } + if cw.Count() != 0 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_ConsoleWatcher_WarningsAll_Ugly(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "warn", Text: "legacy"}) + var got []ConsoleMessage + for msg := range cw.WarningsAll() { + got = append(got, msg) + break + } + if len(got) != 1 { + t.Fatalf("ugly WarningsAll len = %d", len(got)) + } + if cw.Count() != 1 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_NewConsoleWatcher_Good(t *testing.T) { + cw := NewConsoleWatcher(nil) + if cw == nil { + t.Fatal("NewConsoleWatcher returned nil") + } + if cw.Count() != 0 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_NewConsoleWatcher_Bad(t *testing.T) { + cw := NewConsoleWatcher(&Webview{}) + if cw == nil { + t.Fatal("NewConsoleWatcher returned nil for empty webview") + } + if len(cw.handlers) != 0 { + t.Fatalf("handlers = %d", len(cw.handlers)) + } +} + +func TestAX7_NewConsoleWatcher_Ugly(t *testing.T) { + wv := ax7WebviewBase() + cw := NewConsoleWatcher(wv) + cw.addMessage(ConsoleMessage{Type: "log", Text: "manual"}) + if cw.Count() != 1 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_ConsoleWatcher_AddFilter_Good(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "error", Text: "boom"}) + cw.AddFilter(ConsoleFilter{Type: "error"}) + if len(cw.FilteredMessages()) != 1 { + t.Fatalf("FilteredMessages = %#v", cw.FilteredMessages()) + } +} + +func TestAX7_ConsoleWatcher_AddFilter_Bad(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "log", Text: "hello"}) + cw.AddFilter(ConsoleFilter{Type: "error"}) + if len(cw.FilteredMessages()) != 0 { + t.Fatalf("FilteredMessages = %#v", cw.FilteredMessages()) + } +} + +func TestAX7_ConsoleWatcher_AddFilter_Ugly(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "log", Text: "hello"}) + cw.AddFilter(ConsoleFilter{Pattern: ""}) + if len(cw.FilteredMessages()) != 1 { + t.Fatalf("FilteredMessages = %#v", cw.FilteredMessages()) + } +} + +func TestAX7_ConsoleWatcher_ClearFilters_Good(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "log", Text: "hello"}) + cw.AddFilter(ConsoleFilter{Type: "error"}) + cw.ClearFilters() + if len(cw.FilteredMessages()) != 1 { + t.Fatalf("FilteredMessages = %#v", cw.FilteredMessages()) + } +} + +func TestAX7_ConsoleWatcher_ClearFilters_Bad(t *testing.T) { + cw := ax7ConsoleWatcher() + cw.ClearFilters() + if len(cw.filters) != 0 { + t.Fatalf("filters = %d", len(cw.filters)) + } +} + +func TestAX7_ConsoleWatcher_ClearFilters_Ugly(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "error", Text: "boom"}) + cw.AddFilter(ConsoleFilter{Type: "error"}) + cw.AddFilter(ConsoleFilter{Pattern: "boom"}) + cw.ClearFilters() + if len(cw.FilteredMessages()) != 1 { + t.Fatalf("FilteredMessages = %#v", cw.FilteredMessages()) + } +} + +func TestAX7_ConsoleWatcher_AddHandler_Good(t *testing.T) { + cw := ax7ConsoleWatcher() + called := 0 + cw.AddHandler(func(ConsoleMessage) { called++ }) + cw.addMessage(ConsoleMessage{Type: "log"}) + if called != 1 { + t.Fatalf("called = %d", called) + } +} + +func TestAX7_ConsoleWatcher_AddHandler_Bad(t *testing.T) { + cw := ax7ConsoleWatcher() + called := 0 + cw.AddHandler(func(ConsoleMessage) { called++ }) + if called != 0 { + t.Fatalf("called = %d", called) + } +} + +func TestAX7_ConsoleWatcher_AddHandler_Ugly(t *testing.T) { + cw := ax7ConsoleWatcher() + called := 0 + cw.AddHandler(func(ConsoleMessage) { called++ }) + cw.AddHandler(func(ConsoleMessage) { called++ }) + cw.addMessage(ConsoleMessage{Type: "log"}) + if called != 2 { + t.Fatalf("called = %d", called) + } +} + +func TestAX7_ConsoleWatcher_Clear_Good(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Text: "one"}) + cw.Clear() + if cw.Count() != 0 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_ConsoleWatcher_Clear_Bad(t *testing.T) { + cw := ax7ConsoleWatcher() + cw.Clear() + if cw.Count() != 0 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_ConsoleWatcher_Clear_Ugly(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Text: "one"}) + cw.Clear() + cw.addMessage(ConsoleMessage{Text: "two"}) + if cw.Count() != 1 { + t.Fatalf("Count = %d", cw.Count()) + } + if cw.Messages()[0].Text != "two" { + t.Fatalf("messages = %#v", cw.Messages()) + } +} + +func TestAX7_ConsoleWatcher_SetLimit_Good(t *testing.T) { + cw := ax7ConsoleWatcher() + cw.SetLimit(1) + cw.addMessage(ConsoleMessage{Text: "one"}) + cw.addMessage(ConsoleMessage{Text: "two"}) + if cw.Count() != 1 || cw.Messages()[0].Text != "two" { + t.Fatalf("messages = %#v", cw.Messages()) + } +} + +func TestAX7_ConsoleWatcher_SetLimit_Bad(t *testing.T) { + cw := ax7ConsoleWatcher() + cw.SetLimit(-1) + cw.addMessage(ConsoleMessage{Text: "drop"}) + if cw.Count() != 0 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_ConsoleWatcher_SetLimit_Ugly(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Text: "kept"}) + cw.SetLimit(0) + cw.addMessage(ConsoleMessage{Text: "drop"}) + if cw.Count() != 0 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_ConsoleWatcher_HasErrors_Good(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "error", Text: "boom"}) + if !cw.HasErrors() { + t.Fatal("HasErrors returned false") + } + if cw.ErrorCount() != 1 { + t.Fatalf("ErrorCount = %d", cw.ErrorCount()) + } +} + +func TestAX7_ConsoleWatcher_HasErrors_Bad(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "log", Text: "ok"}) + if cw.HasErrors() { + t.Fatal("HasErrors returned true") + } + if cw.ErrorCount() != 0 { + t.Fatalf("ErrorCount = %d", cw.ErrorCount()) + } +} + +func TestAX7_ConsoleWatcher_HasErrors_Ugly(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "ERROR", Text: "boom"}) + if !cw.HasErrors() { + t.Fatal("HasErrors did not canonicalize type") + } + if cw.ErrorCount() != 1 { + t.Fatalf("ErrorCount = %d", cw.ErrorCount()) + } +} + +func TestAX7_ConsoleWatcher_Count_Good(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{}, ConsoleMessage{}) + if cw.Count() != 2 { + t.Fatalf("Count = %d", cw.Count()) + } + if len(cw.Messages()) != 2 { + t.Fatalf("Messages = %#v", cw.Messages()) + } +} + +func TestAX7_ConsoleWatcher_Count_Bad(t *testing.T) { + cw := ax7ConsoleWatcher() + if cw.Count() != 0 { + t.Fatalf("Count = %d", cw.Count()) + } + if len(cw.Messages()) != 0 { + t.Fatalf("Messages = %#v", cw.Messages()) + } +} + +func TestAX7_ConsoleWatcher_Count_Ugly(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Text: "one"}) + cw.Clear() + if cw.Count() != 0 { + t.Fatalf("Count = %d", cw.Count()) + } +} + +func TestAX7_ConsoleWatcher_ErrorCount_Good(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "error"}, ConsoleMessage{Type: "log"}) + if cw.ErrorCount() != 1 { + t.Fatalf("ErrorCount = %d", cw.ErrorCount()) + } + if !cw.HasErrors() { + t.Fatal("HasErrors returned false") + } +} + +func TestAX7_ConsoleWatcher_ErrorCount_Bad(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "warning"}) + if cw.ErrorCount() != 0 { + t.Fatalf("ErrorCount = %d", cw.ErrorCount()) + } + if cw.HasErrors() { + t.Fatal("HasErrors returned true") + } +} + +func TestAX7_ConsoleWatcher_ErrorCount_Ugly(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "ERROR"}) + if cw.ErrorCount() != 1 { + t.Fatalf("ErrorCount = %d", cw.ErrorCount()) + } + if len(cw.Errors()) != 1 { + t.Fatalf("Errors = %#v", cw.Errors()) + } +} + +func TestAX7_ConsoleWatcher_WaitForMessage_Good(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "log", Text: "ready"}) + msg, err := cw.WaitForMessage(context.Background(), ConsoleFilter{Pattern: "ready"}) + if err != nil || msg.Text != "ready" { + t.Fatalf("WaitForMessage = %#v, %v", msg, err) + } +} + +func TestAX7_ConsoleWatcher_WaitForMessage_Bad(t *testing.T) { + cw := ax7ConsoleWatcher() + ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) + defer cancel() + _, err := cw.WaitForMessage(ctx, ConsoleFilter{Type: "error"}) + if err == nil { + t.Fatal("WaitForMessage succeeded without a message") + } +} + +func TestAX7_ConsoleWatcher_WaitForMessage_Ugly(t *testing.T) { + cw := ax7ConsoleWatcher() + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + go cw.addMessage(ConsoleMessage{Type: "warning", Text: "late"}) + msg, err := cw.WaitForMessage(ctx, ConsoleFilter{Type: "warn"}) + if err != nil || msg.Text != "late" { + t.Fatalf("WaitForMessage = %#v, %v", msg, err) + } +} + +func TestAX7_ConsoleWatcher_WaitForError_Good(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "error", Text: "boom"}) + msg, err := cw.WaitForError(context.Background()) + if err != nil || msg.Text != "boom" { + t.Fatalf("WaitForError = %#v, %v", msg, err) + } +} + +func TestAX7_ConsoleWatcher_WaitForError_Bad(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "log", Text: "ok"}) + ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) + defer cancel() + _, err := cw.WaitForError(ctx) + if err == nil { + t.Fatal("WaitForError succeeded without error") + } +} + +func TestAX7_ConsoleWatcher_WaitForError_Ugly(t *testing.T) { + cw := ax7ConsoleWatcher(ConsoleMessage{Type: "ERROR", Text: "caps"}) + msg, err := cw.WaitForError(context.Background()) + if err != nil || msg.Text != "caps" { + t.Fatalf("WaitForError = %#v, %v", msg, err) + } +} + +func TestAX7_FormatConsoleOutput_Good(t *testing.T) { + out := FormatConsoleOutput([]ConsoleMessage{{Type: "error", Text: "boom", Timestamp: time.Date(2026, 1, 1, 1, 2, 3, 4_000_000, time.UTC)}}) + if !strings.Contains(out, "[ERROR]") { + t.Fatalf("output = %q", out) + } + if !strings.Contains(out, "boom") { + t.Fatalf("output = %q", out) + } +} + +func TestAX7_FormatConsoleOutput_Bad(t *testing.T) { + out := FormatConsoleOutput(nil) + if out != "" { + t.Fatalf("empty output = %q", out) + } + if len(out) != 0 { + t.Fatalf("len = %d", len(out)) + } +} + +func TestAX7_FormatConsoleOutput_Ugly(t *testing.T) { + out := FormatConsoleOutput([]ConsoleMessage{{Type: "log", Text: "a\nb\x1b", Timestamp: time.Date(2026, 1, 1, 1, 2, 3, 0, time.UTC)}}) + if strings.Contains(out, "a\nb") { + t.Fatalf("output kept newline: %q", out) + } + if !strings.Contains(out, `a\nb\x1b`) { + t.Fatalf("output = %q", out) + } +} + +func TestAX7_ExceptionWatcher_Exceptions_Good(t *testing.T) { + ew := ax7ExceptionWatcher(ExceptionInfo{Text: "boom"}) + var got []ExceptionInfo + got = ew.Exceptions() + if len(got) != 1 || got[0].Text != "boom" { + t.Fatalf("Exceptions = %#v", got) + } + if ew.Count() != 1 { + t.Fatalf("Count = %d", ew.Count()) + } +} + +func TestAX7_ExceptionWatcher_Exceptions_Bad(t *testing.T) { + ew := ax7ExceptionWatcher() + var got []ExceptionInfo + got = ew.Exceptions() + if len(got) != 0 { + t.Fatalf("empty Exceptions = %#v", got) + } + if ew.Count() != 0 { + t.Fatalf("Count = %d", ew.Count()) + } +} + +func TestAX7_ExceptionWatcher_Exceptions_Ugly(t *testing.T) { + ew := ax7ExceptionWatcher(ExceptionInfo{Text: "one"}, ExceptionInfo{Text: "two"}) + var got []ExceptionInfo + got = ew.Exceptions() + if len(got) != 2 { + t.Fatalf("ugly Exceptions = %#v", got) + } + if ew.HasExceptions() != true { + t.Fatal("HasExceptions returned false") + } +} + +func TestAX7_ExceptionWatcher_ExceptionsAll_Good(t *testing.T) { + ew := ax7ExceptionWatcher(ExceptionInfo{Text: "boom"}) + var got []ExceptionInfo + for exc := range ew.ExceptionsAll() { + got = append(got, exc) + } + if len(got) != 1 || got[0].Text != "boom" { + t.Fatalf("ExceptionsAll = %#v", got) + } + if ew.Count() != 1 { + t.Fatalf("Count = %d", ew.Count()) + } +} + +func TestAX7_ExceptionWatcher_ExceptionsAll_Bad(t *testing.T) { + ew := ax7ExceptionWatcher() + var got []ExceptionInfo + for exc := range ew.ExceptionsAll() { + got = append(got, exc) + } + if len(got) != 0 { + t.Fatalf("empty ExceptionsAll = %#v", got) + } + if ew.Count() != 0 { + t.Fatalf("Count = %d", ew.Count()) + } +} + +func TestAX7_ExceptionWatcher_ExceptionsAll_Ugly(t *testing.T) { + ew := ax7ExceptionWatcher(ExceptionInfo{Text: "one"}, ExceptionInfo{Text: "two"}) + var got []ExceptionInfo + for exc := range ew.ExceptionsAll() { + got = append(got, exc) + break + } + if len(got) != 1 { + t.Fatalf("ugly ExceptionsAll = %#v", got) + } + if ew.HasExceptions() != true { + t.Fatal("HasExceptions returned false") + } +} + +func TestAX7_NewExceptionWatcher_Good(t *testing.T) { + ew := NewExceptionWatcher(nil) + if ew == nil { + t.Fatal("NewExceptionWatcher returned nil") + } + if ew.Count() != 0 { + t.Fatalf("Count = %d", ew.Count()) + } +} + +func TestAX7_NewExceptionWatcher_Bad(t *testing.T) { + ew := NewExceptionWatcher(&Webview{}) + if ew == nil { + t.Fatal("NewExceptionWatcher returned nil for empty webview") + } + if len(ew.handlers) != 0 { + t.Fatalf("handlers = %d", len(ew.handlers)) + } +} + +func TestAX7_NewExceptionWatcher_Ugly(t *testing.T) { + ew := NewExceptionWatcher(nil) + ew.handleException(map[string]any{"exceptionDetails": map[string]any{"text": "boom"}}) + if ew.Count() != 1 { + t.Fatalf("Count = %d", ew.Count()) + } +} + +func TestAX7_ExceptionWatcher_Clear_Good(t *testing.T) { + ew := ax7ExceptionWatcher(ExceptionInfo{Text: "boom"}) + ew.Clear() + if ew.Count() != 0 { + t.Fatalf("Count = %d", ew.Count()) + } +} + +func TestAX7_ExceptionWatcher_Clear_Bad(t *testing.T) { + ew := ax7ExceptionWatcher() + ew.Clear() + if ew.Count() != 0 { + t.Fatalf("Count = %d", ew.Count()) + } +} + +func TestAX7_ExceptionWatcher_Clear_Ugly(t *testing.T) { + ew := ax7ExceptionWatcher(ExceptionInfo{Text: "boom"}) + ew.Clear() + ew.handleException(map[string]any{"exceptionDetails": map[string]any{"text": "next"}}) + if ew.Count() != 1 { + t.Fatalf("Count = %d", ew.Count()) + } + if ew.Exceptions()[0].Text != "next" { + t.Fatalf("Exceptions = %#v", ew.Exceptions()) + } +} + +func TestAX7_ExceptionWatcher_HasExceptions_Good(t *testing.T) { + ew := ax7ExceptionWatcher(ExceptionInfo{Text: "boom"}) + if !ew.HasExceptions() { + t.Fatal("HasExceptions returned false") + } + if ew.Count() != 1 { + t.Fatalf("Count = %d", ew.Count()) + } +} + +func TestAX7_ExceptionWatcher_HasExceptions_Bad(t *testing.T) { + ew := ax7ExceptionWatcher() + if ew.HasExceptions() { + t.Fatal("HasExceptions returned true") + } + if ew.Count() != 0 { + t.Fatalf("Count = %d", ew.Count()) + } +} + +func TestAX7_ExceptionWatcher_HasExceptions_Ugly(t *testing.T) { + ew := ax7ExceptionWatcher(ExceptionInfo{}) + if !ew.HasExceptions() { + t.Fatal("HasExceptions ignored zero exception") + } + if ew.Count() != 1 { + t.Fatalf("Count = %d", ew.Count()) + } +} + +func TestAX7_ExceptionWatcher_Count_Good(t *testing.T) { + ew := ax7ExceptionWatcher(ExceptionInfo{}, ExceptionInfo{}) + if ew.Count() != 2 { + t.Fatalf("Count = %d", ew.Count()) + } + if len(ew.Exceptions()) != 2 { + t.Fatalf("Exceptions = %#v", ew.Exceptions()) + } +} + +func TestAX7_ExceptionWatcher_Count_Bad(t *testing.T) { + ew := ax7ExceptionWatcher() + if ew.Count() != 0 { + t.Fatalf("Count = %d", ew.Count()) + } + if len(ew.Exceptions()) != 0 { + t.Fatalf("Exceptions = %#v", ew.Exceptions()) + } +} + +func TestAX7_ExceptionWatcher_Count_Ugly(t *testing.T) { + ew := ax7ExceptionWatcher(ExceptionInfo{Text: "one"}) + ew.Clear() + if ew.Count() != 0 { + t.Fatalf("Count = %d", ew.Count()) + } + if ew.HasExceptions() { + t.Fatal("HasExceptions returned true after Clear") + } +} + +func TestAX7_ExceptionWatcher_AddHandler_Good(t *testing.T) { + ew := ax7ExceptionWatcher() + called := 0 + ew.AddHandler(func(ExceptionInfo) { called++ }) + ew.handleException(map[string]any{"exceptionDetails": map[string]any{"text": "boom"}}) + if called != 1 { + t.Fatalf("called = %d", called) + } +} + +func TestAX7_ExceptionWatcher_AddHandler_Bad(t *testing.T) { + ew := ax7ExceptionWatcher() + called := 0 + ew.AddHandler(func(ExceptionInfo) { called++ }) + if called != 0 { + t.Fatalf("called = %d", called) + } +} + +func TestAX7_ExceptionWatcher_AddHandler_Ugly(t *testing.T) { + ew := ax7ExceptionWatcher() + called := 0 + ew.AddHandler(func(ExceptionInfo) { called++ }) + ew.AddHandler(func(ExceptionInfo) { called++ }) + ew.handleException(map[string]any{"exceptionDetails": map[string]any{"text": "boom"}}) + if called != 2 { + t.Fatalf("called = %d", called) + } +} + +func TestAX7_ExceptionWatcher_WaitForException_Good(t *testing.T) { + ew := ax7ExceptionWatcher(ExceptionInfo{Text: "boom"}) + exc, err := ew.WaitForException(context.Background()) + if err != nil || exc.Text != "boom" { + t.Fatalf("WaitForException = %#v, %v", exc, err) + } +} + +func TestAX7_ExceptionWatcher_WaitForException_Bad(t *testing.T) { + ew := ax7ExceptionWatcher() + ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) + defer cancel() + _, err := ew.WaitForException(ctx) + if err == nil { + t.Fatal("WaitForException succeeded without exception") + } +} + +func TestAX7_ExceptionWatcher_WaitForException_Ugly(t *testing.T) { + ew := ax7ExceptionWatcher() + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + go ew.handleException(map[string]any{"exceptionDetails": map[string]any{"text": "late"}}) + exc, err := ew.WaitForException(ctx) + if err != nil || exc.Text != "late" { + t.Fatalf("WaitForException = %#v, %v", exc, err) + } +} + +func TestAX7_Webview_Close_Good(t *testing.T) { + wv := ax7WebviewBase() + if err := wv.Close(); err != nil { + t.Fatalf("Close returned error: %v", err) + } + if wv.ctx.Err() != nil { + t.Fatalf("base context changed: %v", wv.ctx.Err()) + } +} + +func TestAX7_Webview_Close_Bad(t *testing.T) { + var wv *Webview + defer func() { + if recover() == nil { + t.Fatal("Close on nil webview did not panic") + } + }() + wv.Close() +} + +func TestAX7_Webview_Close_Ugly(t *testing.T) { + server := newFakeCDPServer(t) + client := newConnectedCDPClient(t, server.primaryTarget()) + wv := ax7WebviewBase() + wv.client = client + if err := wv.Close(); err != nil { + t.Fatalf("first Close returned error: %v", err) + } + if err := wv.Close(); err != nil { + t.Fatalf("second Close returned error: %v", err) + } +} + +func TestAX7_Webview_Navigate_Good(t *testing.T) { + wv := ax7NavigationWebview(t) + if err := wv.Navigate("https://example.com"); err != nil { + t.Fatalf("Navigate returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_Navigate_Bad(t *testing.T) { + wv := ax7NavigationWebview(t) + if err := wv.Navigate("javascript:alert(1)"); err == nil { + t.Fatal("Navigate accepted dangerous URL") + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_Navigate_Ugly(t *testing.T) { + wv := ax7NavigationWebview(t) + if err := wv.Navigate("about:blank"); err != nil { + t.Fatalf("Navigate rejected about:blank: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_Click_Good(t *testing.T) { + wv := ax7DOMWebview(t, true) + if err := wv.Click("#button"); err != nil { + t.Fatalf("Click returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_Click_Bad(t *testing.T) { + wv := ax7MissingDOMWebview(t) + if err := wv.Click("#missing"); err == nil { + t.Fatal("Click succeeded for missing element") + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_Click_Ugly(t *testing.T) { + wv := ax7DOMWebview(t, false) + if err := wv.Click("#button"); err != nil { + t.Fatalf("Click fallback returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_Type_Good(t *testing.T) { + wv, _ := newWebviewHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { + switch msg.Method { + case "Runtime.evaluate", "Input.dispatchKeyEvent": + target.reply(msg.ID, map[string]any{"result": map[string]any{"value": true}}) + default: + t.Fatalf("unexpected method %q", msg.Method) + } + }) + if err := wv.Type("#input", "ab"); err != nil { + t.Fatalf("Type returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_Type_Bad(t *testing.T) { + wv := ax7EvaluateWebview(t, "focus", true) + if err := wv.Type("#input", "ab"); err == nil { + t.Fatal("Type succeeded despite focus failure") + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_Type_Ugly(t *testing.T) { + wv := ax7EvaluateWebview(t, "focus", false) + if err := wv.Type("#input", ""); err != nil { + t.Fatalf("Type rejected empty text: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_QuerySelector_Good(t *testing.T) { + wv := ax7DOMWebview(t, true) + elem, err := wv.QuerySelector("#main") + if err != nil { + t.Fatalf("QuerySelector returned error: %v", err) + } + if elem.NodeID != 10 { + t.Fatalf("element = %#v", elem) + } +} + +func TestAX7_Webview_QuerySelector_Bad(t *testing.T) { + wv := ax7MissingDOMWebview(t) + elem, err := wv.QuerySelector("#missing") + if err == nil { + t.Fatal("QuerySelector succeeded for missing element") + } + if elem != nil { + t.Fatalf("element = %#v", elem) + } +} + +func TestAX7_Webview_QuerySelector_Ugly(t *testing.T) { + wv := ax7DOMWebview(t, false) + elem, err := wv.QuerySelector("") + if err != nil { + t.Fatalf("QuerySelector rejected empty selector fixture: %v", err) + } + if elem.BoundingBox != nil { + t.Fatalf("bounding box = %#v", elem.BoundingBox) + } +} + +func TestAX7_Webview_QuerySelectorAll_Good(t *testing.T) { + wv, _ := newWebviewHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { + switch msg.Method { + case "DOM.getDocument": + target.reply(msg.ID, map[string]any{"root": map[string]any{"nodeId": float64(1)}}) + case "DOM.querySelectorAll": + target.reply(msg.ID, map[string]any{"nodeIds": []any{float64(10), float64(11)}}) + case "DOM.describeNode": + target.reply(msg.ID, map[string]any{"node": map[string]any{"nodeName": "DIV"}}) + case "DOM.resolveNode": + target.reply(msg.ID, map[string]any{"object": map[string]any{"objectId": "obj"}}) + case "Runtime.callFunctionOn": + target.reply(msg.ID, map[string]any{"result": map[string]any{"value": map[string]any{}}}) + case "DOM.getBoxModel": + target.reply(msg.ID, map[string]any{}) + default: + t.Fatalf("unexpected method %q", msg.Method) + } + }) + elems, err := wv.QuerySelectorAll("div") + if err != nil { + t.Fatalf("QuerySelectorAll returned error: %v", err) + } + if len(elems) != 2 { + t.Fatalf("elems = %#v", elems) + } +} + +func TestAX7_Webview_QuerySelectorAll_Bad(t *testing.T) { + wv, _ := newWebviewHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { + switch msg.Method { + case "DOM.getDocument": + target.reply(msg.ID, map[string]any{"root": map[string]any{"nodeId": float64(1)}}) + case "DOM.querySelectorAll": + target.reply(msg.ID, map[string]any{}) + default: + t.Fatalf("unexpected method %q", msg.Method) + } + }) + elems, err := wv.QuerySelectorAll("div") + if err == nil { + t.Fatal("QuerySelectorAll accepted malformed node list") + } + if elems != nil { + t.Fatalf("elems = %#v", elems) + } +} + +func TestAX7_Webview_QuerySelectorAll_Ugly(t *testing.T) { + wv, _ := newWebviewHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { + switch msg.Method { + case "DOM.getDocument": + target.reply(msg.ID, map[string]any{"root": map[string]any{"nodeId": float64(1)}}) + case "DOM.querySelectorAll": + target.reply(msg.ID, map[string]any{"nodeIds": []any{}}) + default: + t.Fatalf("unexpected method %q", msg.Method) + } + }) + elems, err := wv.QuerySelectorAll(".none") + if err != nil { + t.Fatalf("QuerySelectorAll returned error for empty result: %v", err) + } + if len(elems) != 0 { + t.Fatalf("elems = %#v", elems) + } +} + +func TestAX7_Webview_QuerySelectorAllAll_Good(t *testing.T) { + wv, _ := newWebviewHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { + switch msg.Method { + case "DOM.getDocument": + target.reply(msg.ID, map[string]any{"root": map[string]any{"nodeId": float64(1)}}) + case "DOM.querySelectorAll": + target.reply(msg.ID, map[string]any{"nodeIds": []any{float64(10)}}) + case "DOM.describeNode": + target.reply(msg.ID, map[string]any{"node": map[string]any{"nodeName": "DIV"}}) + case "DOM.resolveNode": + target.reply(msg.ID, map[string]any{"object": map[string]any{"objectId": "obj"}}) + case "Runtime.callFunctionOn": + target.reply(msg.ID, map[string]any{"result": map[string]any{"value": map[string]any{}}}) + case "DOM.getBoxModel": + target.reply(msg.ID, map[string]any{}) + default: + t.Fatalf("unexpected method %q", msg.Method) + } + }) + seen := 0 + for elem := range wv.QuerySelectorAllAll("div") { + if elem != nil { + seen++ + } + break + } + if seen != 1 { + t.Fatalf("seen = %d", seen) + } +} + +func TestAX7_Webview_QuerySelectorAllAll_Bad(t *testing.T) { + wv, _ := newWebviewHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { + switch msg.Method { + case "DOM.getDocument": + target.reply(msg.ID, map[string]any{"root": map[string]any{"nodeId": float64(1)}}) + case "DOM.querySelectorAll": + target.reply(msg.ID, map[string]any{}) + default: + t.Fatalf("unexpected method %q", msg.Method) + } + }) + seen := 0 + for range wv.QuerySelectorAllAll("div") { + seen++ + } + if seen != 0 { + t.Fatalf("seen = %d", seen) + } +} + +func TestAX7_Webview_QuerySelectorAllAll_Ugly(t *testing.T) { + wv, _ := newWebviewHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { + switch msg.Method { + case "DOM.getDocument": + target.reply(msg.ID, map[string]any{"root": map[string]any{"nodeId": float64(1)}}) + case "DOM.querySelectorAll": + target.reply(msg.ID, map[string]any{"nodeIds": []any{}}) + default: + t.Fatalf("unexpected method %q", msg.Method) + } + }) + seen := 0 + for range wv.QuerySelectorAllAll(".none") { + seen++ + } + if seen != 0 { + t.Fatalf("seen = %d", seen) + } +} + +func TestAX7_Webview_GetConsole_Good(t *testing.T) { + wv := ax7WebviewBase() + wv.addConsoleMessage(ConsoleMessage{Text: "one"}) + got := wv.GetConsole() + if len(got) != 1 || got[0].Text != "one" { + t.Fatalf("GetConsole = %#v", got) + } +} + +func TestAX7_Webview_GetConsole_Bad(t *testing.T) { + wv := ax7WebviewBase() + got := wv.GetConsole() + if len(got) != 0 { + t.Fatalf("GetConsole = %#v", got) + } +} + +func TestAX7_Webview_GetConsole_Ugly(t *testing.T) { + wv := ax7WebviewBase() + wv.consoleLimit = 1 + wv.addConsoleMessage(ConsoleMessage{Text: "one"}) + wv.addConsoleMessage(ConsoleMessage{Text: "two"}) + got := wv.GetConsole() + if len(got) != 1 || got[0].Text != "two" { + t.Fatalf("GetConsole = %#v", got) + } +} + +func TestAX7_Webview_GetConsoleAll_Good(t *testing.T) { + wv := ax7WebviewBase() + wv.addConsoleMessage(ConsoleMessage{Text: "one"}) + seen := 0 + for msg := range wv.GetConsoleAll() { + if msg.Text == "one" { + seen++ + } + } + if seen != 1 { + t.Fatalf("seen = %d", seen) + } +} + +func TestAX7_Webview_GetConsoleAll_Bad(t *testing.T) { + wv := ax7WebviewBase() + seen := 0 + for range wv.GetConsoleAll() { + seen++ + } + if seen != 0 { + t.Fatalf("seen = %d", seen) + } +} + +func TestAX7_Webview_GetConsoleAll_Ugly(t *testing.T) { + wv := ax7WebviewBase() + wv.addConsoleMessage(ConsoleMessage{Text: "one"}) + wv.addConsoleMessage(ConsoleMessage{Text: "two"}) + seen := 0 + for range wv.GetConsoleAll() { + seen++ + break + } + if seen != 1 { + t.Fatalf("seen = %d", seen) + } +} + +func TestAX7_Webview_ClearConsole_Good(t *testing.T) { + wv := ax7WebviewBase() + wv.addConsoleMessage(ConsoleMessage{Text: "one"}) + wv.ClearConsole() + if len(wv.GetConsole()) != 0 { + t.Fatalf("console = %#v", wv.GetConsole()) + } +} + +func TestAX7_Webview_ClearConsole_Bad(t *testing.T) { + wv := ax7WebviewBase() + wv.ClearConsole() + if len(wv.GetConsole()) != 0 { + t.Fatalf("console = %#v", wv.GetConsole()) + } +} + +func TestAX7_Webview_ClearConsole_Ugly(t *testing.T) { + wv := ax7WebviewBase() + wv.addConsoleMessage(ConsoleMessage{Text: "one"}) + wv.ClearConsole() + wv.addConsoleMessage(ConsoleMessage{Text: "two"}) + if got := wv.GetConsole(); len(got) != 1 || got[0].Text != "two" { + t.Fatalf("console = %#v", got) + } +} + +func TestAX7_Webview_Screenshot_Good(t *testing.T) { + wv := ax7NavigationWebview(t) + png, err := wv.Screenshot() + if err != nil { + t.Fatalf("Screenshot returned error: %v", err) + } + if string(png) != "png" { + t.Fatalf("png = %v", png) + } +} + +func TestAX7_Webview_Screenshot_Bad(t *testing.T) { + wv, _ := newWebviewHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { target.reply(msg.ID, map[string]any{"data": "%%%"}) }) + png, err := wv.Screenshot() + if err == nil { + t.Fatal("Screenshot accepted invalid base64") + } + if png != nil { + t.Fatalf("png = %v", png) + } +} + +func TestAX7_Webview_Screenshot_Ugly(t *testing.T) { + wv, _ := newWebviewHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { + target.reply(msg.ID, map[string]any{"data": base64.StdEncoding.EncodeToString(nil)}) + }) + png, err := wv.Screenshot() + if err != nil { + t.Fatalf("Screenshot rejected empty PNG data: %v", err) + } + if len(png) != 0 { + t.Fatalf("png = %v", png) + } +} + +func TestAX7_Webview_Evaluate_Good(t *testing.T) { + wv := ax7NavigationWebview(t) + got, err := wv.Evaluate("document.title") + if err != nil { + t.Fatalf("Evaluate returned error: %v", err) + } + if got != "Example" && got != "
" { + t.Fatalf("Evaluate = %#v", got) + } +} + +func TestAX7_Webview_Evaluate_Bad(t *testing.T) { + wv, _ := newWebviewHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { + target.reply(msg.ID, map[string]any{"exceptionDetails": map[string]any{"text": "boom"}}) + }) + got, err := wv.Evaluate("throw new Error('boom')") + if err == nil { + t.Fatal("Evaluate accepted exception") + } + if got != nil { + t.Fatalf("got = %#v", got) + } +} + +func TestAX7_Webview_Evaluate_Ugly(t *testing.T) { + wv, _ := newWebviewHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { + target.reply(msg.ID, map[string]any{"result": map[string]any{"value": nil}}) + }) + got, err := wv.Evaluate("null") + if err != nil { + t.Fatalf("Evaluate rejected null: %v", err) + } + if got != nil { + t.Fatalf("got = %#v", got) + } +} + +func TestAX7_Webview_WaitForSelector_Good(t *testing.T) { + wv := ax7EvaluateWebview(t, "querySelector", false) + if err := wv.WaitForSelector("#ready"); err != nil { + t.Fatalf("WaitForSelector returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_WaitForSelector_Bad(t *testing.T) { + wv := ax7EvaluateWebview(t, "querySelector", true) + wv.timeout = time.Millisecond + if err := wv.WaitForSelector("#never"); err == nil { + t.Fatal("WaitForSelector succeeded without selector") + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_WaitForSelector_Ugly(t *testing.T) { + wv := ax7EvaluateWebview(t, "querySelector", false) + if err := wv.WaitForSelector(""); err != nil { + t.Fatalf("WaitForSelector rejected empty selector fixture: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_GetURL_Good(t *testing.T) { + wv := ax7NavigationWebview(t) + got, err := wv.GetURL() + if err != nil || got != "https://example.com" { + t.Fatalf("GetURL = %q, %v", got, err) + } +} + +func TestAX7_Webview_GetURL_Bad(t *testing.T) { + wv, _ := newWebviewHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { target.replyValue(msg.ID, 1) }) + got, err := wv.GetURL() + if err == nil { + t.Fatal("GetURL accepted non-string") + } + if got != "" { + t.Fatalf("url = %q", got) + } +} + +func TestAX7_Webview_GetURL_Ugly(t *testing.T) { + wv, _ := newWebviewHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { target.replyValue(msg.ID, "about:blank") }) + got, err := wv.GetURL() + if err != nil || got != "about:blank" { + t.Fatalf("GetURL = %q, %v", got, err) + } +} + +func TestAX7_Webview_GetTitle_Good(t *testing.T) { + wv := ax7NavigationWebview(t) + got, err := wv.GetTitle() + if err != nil || got != "Example" { + t.Fatalf("GetTitle = %q, %v", got, err) + } +} + +func TestAX7_Webview_GetTitle_Bad(t *testing.T) { + wv, _ := newWebviewHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { target.replyValue(msg.ID, 1) }) + got, err := wv.GetTitle() + if err == nil { + t.Fatal("GetTitle accepted non-string") + } + if got != "" { + t.Fatalf("title = %q", got) + } +} + +func TestAX7_Webview_GetTitle_Ugly(t *testing.T) { + wv, _ := newWebviewHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { target.replyValue(msg.ID, "") }) + got, err := wv.GetTitle() + if err != nil || got != "" { + t.Fatalf("GetTitle = %q, %v", got, err) + } +} + +func TestAX7_Webview_GetHTML_Good(t *testing.T) { + wv := ax7NavigationWebview(t) + got, err := wv.GetHTML("") + if err != nil || got != "" { + t.Fatalf("GetHTML = %q, %v", got, err) + } +} + +func TestAX7_Webview_GetHTML_Bad(t *testing.T) { + wv, _ := newWebviewHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { target.replyValue(msg.ID, 1) }) + got, err := wv.GetHTML("#main") + if err == nil { + t.Fatal("GetHTML accepted non-string") + } + if got != "" { + t.Fatalf("html = %q", got) + } +} + +func TestAX7_Webview_GetHTML_Ugly(t *testing.T) { + wv, _ := newWebviewHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { target.replyValue(msg.ID, "") }) + got, err := wv.GetHTML("#missing") + if err != nil || got != "" { + t.Fatalf("GetHTML = %q, %v", got, err) + } +} + +func TestAX7_Webview_SetViewport_Good(t *testing.T) { + wv := ax7NavigationWebview(t) + if err := wv.SetViewport(1280, 720); err != nil { + t.Fatalf("SetViewport returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_SetViewport_Bad(t *testing.T) { + wv, _ := newWebviewHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { target.replyError(msg.ID, "bad viewport") }) + if err := wv.SetViewport(-1, -1); err == nil { + t.Fatal("SetViewport ignored CDP failure") + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_SetViewport_Ugly(t *testing.T) { + wv := ax7NavigationWebview(t) + if err := wv.SetViewport(0, 0); err != nil { + t.Fatalf("SetViewport rejected zero size fixture: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_SetUserAgent_Good(t *testing.T) { + wv := ax7NavigationWebview(t) + if err := wv.SetUserAgent("Agent/1.0"); err != nil { + t.Fatalf("SetUserAgent returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_SetUserAgent_Bad(t *testing.T) { + wv, _ := newWebviewHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { target.replyError(msg.ID, "bad ua") }) + if err := wv.SetUserAgent("Agent/1.0"); err == nil { + t.Fatal("SetUserAgent ignored CDP failure") + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_SetUserAgent_Ugly(t *testing.T) { + wv := ax7NavigationWebview(t) + if err := wv.SetUserAgent(""); err != nil { + t.Fatalf("SetUserAgent rejected empty user agent fixture: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_Reload_Good(t *testing.T) { + wv := ax7NavigationWebview(t) + if err := wv.Reload(); err != nil { + t.Fatalf("Reload returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_Reload_Bad(t *testing.T) { + wv, _ := newWebviewHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { target.replyError(msg.ID, "reload failed") }) + if err := wv.Reload(); err == nil { + t.Fatal("Reload ignored CDP failure") + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_Reload_Ugly(t *testing.T) { + wv := ax7NavigationWebview(t) + wv.timeout = 250 * time.Millisecond + if err := wv.Reload(); err != nil { + t.Fatalf("Reload with short timeout returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_GoBack_Good(t *testing.T) { + wv := ax7NavigationWebview(t) + if err := wv.GoBack(); err != nil { + t.Fatalf("GoBack returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_GoBack_Bad(t *testing.T) { + wv, _ := newWebviewHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { + target.reply(msg.ID, map[string]any{"currentIndex": float64(0), "entries": []any{map[string]any{"id": float64(1)}}}) + }) + if err := wv.GoBack(); err == nil { + t.Fatal("GoBack succeeded without history entry") + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_GoBack_Ugly(t *testing.T) { + wv, _ := newWebviewHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { + switch msg.Method { + case "Page.getNavigationHistory": + target.reply(msg.ID, map[string]any{"currentIndex": float64(1), "entries": []any{map[string]any{"id": float64(1)}, map[string]any{"id": float64(2)}}}) + case "Page.navigateToHistoryEntry": + target.reply(msg.ID, map[string]any{}) + case "Runtime.evaluate": + target.replyValue(msg.ID, "complete") + default: + t.Fatalf("unexpected method %q", msg.Method) + } + }) + if err := wv.GoBack(); err != nil { + t.Fatalf("GoBack returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_GoForward_Good(t *testing.T) { + wv := ax7NavigationWebview(t) + if err := wv.GoForward(); err != nil { + t.Fatalf("GoForward returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_GoForward_Bad(t *testing.T) { + wv, _ := newWebviewHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { + target.reply(msg.ID, map[string]any{"currentIndex": float64(0), "entries": []any{map[string]any{"id": float64(1)}}}) + }) + if err := wv.GoForward(); err == nil { + t.Fatal("GoForward succeeded without history entry") + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_GoForward_Ugly(t *testing.T) { + wv, _ := newWebviewHarness(t, func(target *fakeCDPTarget, msg cdpMessage) { + switch msg.Method { + case "Page.getNavigationHistory": + target.reply(msg.ID, map[string]any{"currentIndex": float64(0), "entries": []any{map[string]any{"id": float64(1)}, map[string]any{"id": float64(2)}}}) + case "Page.navigateToHistoryEntry": + target.reply(msg.ID, map[string]any{}) + case "Runtime.evaluate": + target.replyValue(msg.ID, "complete") + default: + t.Fatalf("unexpected method %q", msg.Method) + } + }) + if err := wv.GoForward(); err != nil { + t.Fatalf("GoForward returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_UploadFile_Good(t *testing.T) { + wv := ax7DOMWebview(t, true) + if err := wv.UploadFile("#file", []string{"/tmp/a.txt"}); err != nil { + t.Fatalf("UploadFile returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_UploadFile_Bad(t *testing.T) { + wv := ax7MissingDOMWebview(t) + if err := wv.UploadFile("#missing", []string{"/tmp/a.txt"}); err == nil { + t.Fatal("UploadFile succeeded for missing input") + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_UploadFile_Ugly(t *testing.T) { + wv := ax7DOMWebview(t, true) + if err := wv.UploadFile("#file", nil); err != nil { + t.Fatalf("UploadFile rejected nil list: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_DragAndDrop_Good(t *testing.T) { + wv := ax7DOMWebview(t, true) + if err := wv.DragAndDrop("#a", "#b"); err != nil { + t.Fatalf("DragAndDrop returned error: %v", err) + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_DragAndDrop_Bad(t *testing.T) { + wv := ax7MissingDOMWebview(t) + if err := wv.DragAndDrop("#a", "#b"); err == nil { + t.Fatal("DragAndDrop succeeded for missing source") + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func TestAX7_Webview_DragAndDrop_Ugly(t *testing.T) { + wv := ax7DOMWebview(t, false) + if err := wv.DragAndDrop("#a", "#b"); err == nil { + t.Fatal("DragAndDrop succeeded without bounding box") + } + if wv.client == nil { + t.Fatal("fixture webview lost client") + } +} + +func nilCall(err error) (any, error) { return nil, err } + +func TestAX7_NewAngularHelper_Good(t *testing.T) { + wv := ax7WebviewBase() + ah := NewAngularHelper(wv) + if ah == nil || ah.wv != wv { + t.Fatalf("NewAngularHelper = %#v", ah) + } + if ah.timeout != 30*time.Second { + t.Fatalf("timeout = %v", ah.timeout) + } +} + +func TestAX7_NewAngularHelper_Bad(t *testing.T) { + ah := NewAngularHelper(nil) + if ah == nil { + t.Fatal("NewAngularHelper returned nil") + } + if ah.wv != nil { + t.Fatalf("wv = %#v", ah.wv) + } +} + +func TestAX7_NewAngularHelper_Ugly(t *testing.T) { + wv := &Webview{timeout: time.Nanosecond} + ah := NewAngularHelper(wv) + if ah.wv != wv { + t.Fatalf("wv = %#v", ah.wv) + } + if ah.timeout != 30*time.Second { + t.Fatalf("timeout = %v", ah.timeout) + } +} + +func TestAX7_AngularHelper_SetTimeout_Good(t *testing.T) { + ah := NewAngularHelper(ax7WebviewBase()) + ah.SetTimeout(5 * time.Second) + if ah.timeout != 5*time.Second { + t.Fatalf("timeout = %v", ah.timeout) + } +} + +func TestAX7_AngularHelper_SetTimeout_Bad(t *testing.T) { + ah := NewAngularHelper(ax7WebviewBase()) + ah.SetTimeout(-time.Second) + if ah.timeout != -time.Second { + t.Fatalf("timeout = %v", ah.timeout) + } +} + +func TestAX7_AngularHelper_SetTimeout_Ugly(t *testing.T) { + ah := NewAngularHelper(ax7WebviewBase()) + ah.SetTimeout(0) + if ah.timeout != 0 { + t.Fatalf("timeout = %v", ah.timeout) + } +} + +func TestAX7_AngularHelper_WaitForAngular_Good(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyValue(msg.ID, true) + }) + err := ah.WaitForAngular() + if err != nil { + t.Fatalf("WaitForAngular returned error: %v", err) + } + if ah.wv == nil { + t.Fatal("helper lost webview") + } +} + +func TestAX7_AngularHelper_WaitForAngular_Bad(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyError(msg.ID, "angular failed") + }) + err := ah.WaitForAngular() + if err == nil { + t.Fatal("WaitForAngular succeeded despite evaluation failure") + } + if !strings.Contains(err.Error(), "angular failed") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_AngularHelper_WaitForAngular_Ugly(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyValue(msg.ID, nil) + }) + err := ah.WaitForAngular() + if err != nil && !strings.Contains(err.Error(), "not an Angular") && !strings.Contains(err.Error(), "could not") { + t.Fatalf("unexpected error: %v", err) + } + if ah.wv == nil { + t.Fatal("helper lost webview") + } +} + +func TestAX7_AngularHelper_NavigateByRouter_Good(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyValue(msg.ID, true) + }) + err := ah.NavigateByRouter("/dashboard") + if err != nil { + t.Fatalf("NavigateByRouter returned error: %v", err) + } + if ah.wv == nil { + t.Fatal("helper lost webview") + } +} + +func TestAX7_AngularHelper_NavigateByRouter_Bad(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyError(msg.ID, "angular failed") + }) + err := ah.NavigateByRouter("/dashboard") + if err == nil { + t.Fatal("NavigateByRouter succeeded despite evaluation failure") + } + if !strings.Contains(err.Error(), "angular failed") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_AngularHelper_NavigateByRouter_Ugly(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyValue(msg.ID, true) + }) + err := ah.NavigateByRouter("") + if err != nil && !strings.Contains(err.Error(), "not an Angular") && !strings.Contains(err.Error(), "could not") { + t.Fatalf("unexpected error: %v", err) + } + if ah.wv == nil { + t.Fatal("helper lost webview") + } +} + +func TestAX7_AngularHelper_GetComponentProperty_Good(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyValue(msg.ID, "Ada") + }) + got, err := ah.GetComponentProperty("app-user", "name") + if err != nil { + t.Fatalf("GetComponentProperty returned error: %v", err) + } + if got == nil { + t.Fatal("GetComponentProperty returned nil value") + } +} + +func TestAX7_AngularHelper_GetComponentProperty_Bad(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyError(msg.ID, "angular failed") + }) + _, err := ah.GetComponentProperty("app-user", "name") + if err == nil { + t.Fatal("GetComponentProperty succeeded despite evaluation failure") + } + if !strings.Contains(err.Error(), "angular failed") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_AngularHelper_GetComponentProperty_Ugly(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyValue(msg.ID, nil) + }) + _, err := ah.GetComponentProperty("app-user", "name") + if err != nil && !strings.Contains(err.Error(), "not an Angular") && !strings.Contains(err.Error(), "could not") { + t.Fatalf("unexpected error: %v", err) + } + if ah.wv == nil { + t.Fatal("helper lost webview") + } +} + +func TestAX7_AngularHelper_SetComponentProperty_Good(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyValue(msg.ID, true) + }) + _, err := nilCall(ah.SetComponentProperty("app-user", "active", true)) + if err != nil { + t.Fatalf("SetComponentProperty returned error: %v", err) + } + if ah.timeout <= 0 { + t.Fatalf("timeout = %v", ah.timeout) + } +} + +func TestAX7_AngularHelper_SetComponentProperty_Bad(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyError(msg.ID, "angular failed") + }) + _, err := nilCall(ah.SetComponentProperty("app-user", "active", true)) + if err == nil { + t.Fatal("SetComponentProperty succeeded despite evaluation failure") + } + if !strings.Contains(err.Error(), "angular failed") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_AngularHelper_SetComponentProperty_Ugly(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyValue(msg.ID, nil) + }) + _, err := nilCall(ah.SetComponentProperty("app-user", "active", true)) + if err != nil && !strings.Contains(err.Error(), "not an Angular") && !strings.Contains(err.Error(), "could not") { + t.Fatalf("unexpected error: %v", err) + } + if ah.wv == nil { + t.Fatal("helper lost webview") + } +} + +func TestAX7_AngularHelper_CallComponentMethod_Good(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyValue(msg.ID, map[string]any{"ok": true}) + }) + got, err := ah.CallComponentMethod("app-user", "save", 1) + if err != nil { + t.Fatalf("CallComponentMethod returned error: %v", err) + } + if got == nil { + t.Fatal("CallComponentMethod returned nil value") + } +} + +func TestAX7_AngularHelper_CallComponentMethod_Bad(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyError(msg.ID, "angular failed") + }) + _, err := ah.CallComponentMethod("app-user", "save", 1) + if err == nil { + t.Fatal("CallComponentMethod succeeded despite evaluation failure") + } + if !strings.Contains(err.Error(), "angular failed") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_AngularHelper_CallComponentMethod_Ugly(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyValue(msg.ID, nil) + }) + _, err := ah.CallComponentMethod("app-user", "save", 1) + if err != nil && !strings.Contains(err.Error(), "not an Angular") && !strings.Contains(err.Error(), "could not") { + t.Fatalf("unexpected error: %v", err) + } + if ah.wv == nil { + t.Fatal("helper lost webview") + } +} + +func TestAX7_AngularHelper_TriggerChangeDetection_Good(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyValue(msg.ID, true) + }) + _, err := nilCall(ah.TriggerChangeDetection()) + if err != nil { + t.Fatalf("TriggerChangeDetection returned error: %v", err) + } + if ah.timeout <= 0 { + t.Fatalf("timeout = %v", ah.timeout) + } +} + +func TestAX7_AngularHelper_TriggerChangeDetection_Bad(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyError(msg.ID, "angular failed") + }) + _, err := nilCall(ah.TriggerChangeDetection()) + if err == nil { + t.Fatal("TriggerChangeDetection succeeded despite evaluation failure") + } + if !strings.Contains(err.Error(), "angular failed") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_AngularHelper_TriggerChangeDetection_Ugly(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyValue(msg.ID, nil) + }) + _, err := nilCall(ah.TriggerChangeDetection()) + if err != nil && !strings.Contains(err.Error(), "not an Angular") && !strings.Contains(err.Error(), "could not") { + t.Fatalf("unexpected error: %v", err) + } + if ah.wv == nil { + t.Fatal("helper lost webview") + } +} + +func TestAX7_AngularHelper_GetService_Good(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyValue(msg.ID, map[string]any{"name": "session"}) + }) + got, err := ah.GetService("SessionService") + if err != nil { + t.Fatalf("GetService returned error: %v", err) + } + if got == nil { + t.Fatal("GetService returned nil value") + } +} + +func TestAX7_AngularHelper_GetService_Bad(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyError(msg.ID, "angular failed") + }) + _, err := ah.GetService("SessionService") + if err == nil { + t.Fatal("GetService succeeded despite evaluation failure") + } + if !strings.Contains(err.Error(), "angular failed") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_AngularHelper_GetService_Ugly(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyValue(msg.ID, nil) + }) + _, err := ah.GetService("SessionService") + if err != nil && !strings.Contains(err.Error(), "not an Angular") && !strings.Contains(err.Error(), "could not") { + t.Fatalf("unexpected error: %v", err) + } + if ah.wv == nil { + t.Fatal("helper lost webview") + } +} + +func TestAX7_AngularHelper_GetRouterState_Good(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyValue(msg.ID, map[string]any{"url": "/dashboard", "params": map[string]any{"id": "42"}, "queryParams": map[string]any{"tab": "users"}}) + }) + state, err := ah.GetRouterState() + if err != nil { + t.Fatalf("GetRouterState returned error: %v", err) + } + if state.URL != "/dashboard" || state.Params["id"] != "42" { + t.Fatalf("state = %#v", state) + } +} + +func TestAX7_AngularHelper_GetRouterState_Bad(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyError(msg.ID, "router failed") + }) + state, err := ah.GetRouterState() + if err == nil { + t.Fatal("GetRouterState succeeded despite evaluation failure") + } + if state != nil { + t.Fatalf("state = %#v", state) + } +} + +func TestAX7_AngularHelper_GetRouterState_Ugly(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyValue(msg.ID, map[string]any{"url": "/empty", "params": map[string]any{"id": 42}, "queryParams": nil}) + }) + state, err := ah.GetRouterState() + if err != nil { + t.Fatalf("GetRouterState returned error: %v", err) + } + if state.URL != "/empty" || len(state.Params) != 0 { + t.Fatalf("state = %#v", state) + } +} + +func TestAX7_AngularHelper_WaitForComponent_Good(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyValue(msg.ID, true) + }) + _, err := nilCall(ah.WaitForComponent("app-user")) + if err != nil { + t.Fatalf("WaitForComponent returned error: %v", err) + } + if ah.timeout <= 0 { + t.Fatalf("timeout = %v", ah.timeout) + } +} + +func TestAX7_AngularHelper_WaitForComponent_Bad(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyError(msg.ID, "angular failed") + }) + ah.SetTimeout(20 * time.Millisecond) + _, err := nilCall(ah.WaitForComponent("app-user")) + if err == nil { + t.Fatal("WaitForComponent succeeded despite evaluation failure") + } + if !strings.Contains(err.Error(), "deadline exceeded") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_AngularHelper_WaitForComponent_Ugly(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyValue(msg.ID, true) + }) + _, err := nilCall(ah.WaitForComponent("")) + if err != nil && !strings.Contains(err.Error(), "not an Angular") && !strings.Contains(err.Error(), "could not") { + t.Fatalf("unexpected error: %v", err) + } + if ah.wv == nil { + t.Fatal("helper lost webview") + } +} + +func TestAX7_AngularHelper_DispatchEvent_Good(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyValue(msg.ID, true) + }) + _, err := nilCall(ah.DispatchEvent("app-user", "change", map[string]any{"x": 1})) + if err != nil { + t.Fatalf("DispatchEvent returned error: %v", err) + } + if ah.timeout <= 0 { + t.Fatalf("timeout = %v", ah.timeout) + } +} + +func TestAX7_AngularHelper_DispatchEvent_Bad(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyError(msg.ID, "angular failed") + }) + _, err := nilCall(ah.DispatchEvent("app-user", "change", map[string]any{"x": 1})) + if err == nil { + t.Fatal("DispatchEvent succeeded despite evaluation failure") + } + if !strings.Contains(err.Error(), "angular failed") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_AngularHelper_DispatchEvent_Ugly(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyValue(msg.ID, nil) + }) + _, err := nilCall(ah.DispatchEvent("app-user", "change", map[string]any{"x": 1})) + if err != nil && !strings.Contains(err.Error(), "not an Angular") && !strings.Contains(err.Error(), "could not") { + t.Fatalf("unexpected error: %v", err) + } + if ah.wv == nil { + t.Fatal("helper lost webview") + } +} + +func TestAX7_AngularHelper_GetNgModel_Good(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyValue(msg.ID, "value") + }) + got, err := ah.GetNgModel("input") + if err != nil { + t.Fatalf("GetNgModel returned error: %v", err) + } + if got == nil { + t.Fatal("GetNgModel returned nil value") + } +} + +func TestAX7_AngularHelper_GetNgModel_Bad(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyError(msg.ID, "angular failed") + }) + _, err := ah.GetNgModel("input") + if err == nil { + t.Fatal("GetNgModel succeeded despite evaluation failure") + } + if !strings.Contains(err.Error(), "angular failed") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_AngularHelper_GetNgModel_Ugly(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyValue(msg.ID, nil) + }) + _, err := ah.GetNgModel("input") + if err != nil && !strings.Contains(err.Error(), "not an Angular") && !strings.Contains(err.Error(), "could not") { + t.Fatalf("unexpected error: %v", err) + } + if ah.wv == nil { + t.Fatal("helper lost webview") + } +} + +func TestAX7_AngularHelper_SetNgModel_Good(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyValue(msg.ID, true) + }) + _, err := nilCall(ah.SetNgModel("input", "value")) + if err != nil { + t.Fatalf("SetNgModel returned error: %v", err) + } + if ah.timeout <= 0 { + t.Fatalf("timeout = %v", ah.timeout) + } +} + +func TestAX7_AngularHelper_SetNgModel_Bad(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyError(msg.ID, "angular failed") + }) + _, err := nilCall(ah.SetNgModel("input", "value")) + if err == nil { + t.Fatal("SetNgModel succeeded despite evaluation failure") + } + if !strings.Contains(err.Error(), "angular failed") { + t.Fatalf("error = %v", err) + } +} + +func TestAX7_AngularHelper_SetNgModel_Ugly(t *testing.T) { + ah := ax7AngularHelper(t, func(target *fakeCDPTarget, msg cdpMessage) { + if msg.Method != "Runtime.evaluate" { + t.Fatalf("unexpected method %q", msg.Method) + } + target.replyValue(msg.ID, nil) + }) + _, err := nilCall(ah.SetNgModel("input", "value")) + if err != nil && !strings.Contains(err.Error(), "not an Angular") && !strings.Contains(err.Error(), "could not") { + t.Fatalf("unexpected error: %v", err) + } + if ah.wv == nil { + t.Fatal("helper lost webview") + } +} diff --git a/cdp.go b/cdp.go index ad19224..cc489ae 100644 --- a/cdp.go +++ b/cdp.go @@ -13,7 +13,7 @@ import ( "sync/atomic" // Note: AX-6 — internal concurrency primitive; structural per RFC §3/§6 "time" - core "dappco.re/go/core" + core "dappco.re/go" coreerr "dappco.re/go/log" "github.com/gorilla/websocket" @@ -380,13 +380,15 @@ func (c *CDPClient) NewTab(url string) (*CDPClient, error) { } // CloseTab closes the current tab (target). -func (c *CDPClient) CloseTab() error { +func (c *CDPClient) CloseTab() (err error) { targetID, err := targetIDFromWebSocketURL(c.wsURL) if err != nil { return coreerr.E("CDPClient.CloseTab", "failed to determine target ID", err) } defer func() { - _ = c.Close() + if closeErr := c.Close(); closeErr != nil && err == nil { + err = closeErr + } }() ctx, cancel := context.WithTimeout(c.ctx, debugEndpointTimeout) @@ -615,7 +617,7 @@ func canonicalDebugURL(debugURL any) string { return core.TrimSuffix(u.String(), "/") } -func doDebugRequest(ctx context.Context, debugHTTPURL any, endpoint, rawQuery string) ([]byte, error) { +func doDebugRequest(ctx context.Context, debugHTTPURL any, endpoint, rawQuery string) (body []byte, err error) { baseURL, err := cdpURLFromAny(debugHTTPURL) if err != nil { return nil, err @@ -636,13 +638,17 @@ func doDebugRequest(ctx context.Context, debugHTTPURL any, endpoint, rawQuery st if err != nil { return nil, err } - defer func() { _ = resp.Body.Close() }() + defer func() { + if closeErr := resp.Body.Close(); closeErr != nil && err == nil { + err = closeErr + } + }() if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices { return nil, coreerr.E("CDPClient.doDebugRequest", "debug endpoint returned "+resp.Status, nil) } - body, err := io.ReadAll(io.LimitReader(resp.Body, maxDebugResponseBytes+1)) + body, err = io.ReadAll(io.LimitReader(resp.Body, maxDebugResponseBytes+1)) if err != nil { return nil, err } diff --git a/console.go b/console.go index 254abf5..f9a4cae 100644 --- a/console.go +++ b/console.go @@ -9,7 +9,7 @@ import ( "sync/atomic" // Note: AX-6 — internal concurrency primitive; structural per RFC §3/§6 "time" - core "dappco.re/go/core" + core "dappco.re/go" coreerr "dappco.re/go/log" ) diff --git a/go.mod b/go.mod index 2336478..67bd839 100644 --- a/go.mod +++ b/go.mod @@ -6,4 +6,6 @@ require github.com/gorilla/websocket v1.5.3 // Note: gorilla/websocket — WebSo require dappco.re/go/log v0.8.0-alpha.1 -require dappco.re/go/core v0.8.0-alpha.1 +require dappco.re/go v0.9.0 + +replace dappco.re/go/log => github.com/dappcore/go-log v0.8.0-alpha.1 diff --git a/go.sum b/go.sum index 8901443..275c9e1 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,6 @@ -dappco.re/go/core v0.8.0-alpha.1 h1:gj7+Scv+L63Z7wMxbJYHhaRFkHJo2u4MMPuUSv/Dhtk= -dappco.re/go/core v0.8.0-alpha.1/go.mod h1:f2/tBZ3+3IqDrg2F5F598llv0nmb/4gJVCFzM5geE4A= -dappco.re/go/core/log v0.1.0 h1:pa71Vq2TD2aoEUQWFKwNcaJ3GBY8HbaNGqtE688Unyc= -dappco.re/go/core/log v0.1.0/go.mod h1:Nkqb8gsXhZAO8VLpx7B8i1iAmohhzqA20b9Zr8VUcJs= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +dappco.re/go v0.9.0 h1:4ruZRNqKDDva8o6g65tYggjGVe42E6/lMZfVKXtr3p0= +dappco.re/go v0.9.0/go.mod h1:xapr7fLK4/9Pu2iSCr4qZuIuatmtx1j56zS/oPDbGyQ= +github.com/dappcore/go-log v0.8.0-alpha.1 h1:OqZ9Njhz4fr+2BCHOgWxZZcPj/T46jN2UlOCytOCr2Y= +github.com/dappcore/go-log v0.8.0-alpha.1/go.mod h1:IC04Em9SfVTcXiWc1BqZDQfa1MtOuMDEermZkQcTz9c= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/webview.go b/webview.go index b5d4311..2ec79a1 100644 --- a/webview.go +++ b/webview.go @@ -41,7 +41,7 @@ import ( "sync" // Note: AX-6 — internal concurrency primitive; structural per RFC §3/§6 "time" - core "dappco.re/go/core" + core "dappco.re/go" coreerr "dappco.re/go/log" ) @@ -141,16 +141,19 @@ func New(opts ...Option) (*Webview, error) { consoleLimit: 1000, } - cleanupOnError := func() { + cleanupOnError := func() error { cancel() if wv.client != nil { - _ = wv.client.Close() + return wv.client.Close() } + return nil } for _, opt := range opts { if err := opt(wv); err != nil { - cleanupOnError() + if cleanupErr := cleanupOnError(); cleanupErr != nil { + return nil, coreerr.E("Webview.New", "cleanup after option failure", coreerr.Join(err, cleanupErr)) + } return nil, err } } @@ -162,7 +165,9 @@ func New(opts ...Option) (*Webview, error) { // Enable console capture if err := wv.enableConsole(); err != nil { - cleanupOnError() + if cleanupErr := cleanupOnError(); cleanupErr != nil { + return nil, coreerr.E("Webview.New", "cleanup after console setup failure", coreerr.Join(err, cleanupErr)) + } return nil, coreerr.E("Webview.New", "failed to enable console capture", err) }