From ab77e922bd04363a116b5a17a526df2f3238db58 Mon Sep 17 00:00:00 2001 From: Snider Date: Mon, 16 Mar 2026 21:10:49 +0000 Subject: [PATCH] refactor: replace fmt.Errorf/errors.New with coreerr.E() Co-Authored-By: Virgil --- actions.go | 16 ++++++++------- angular.go | 10 +++++---- cdp.go | 45 ++++++++++++++++++++-------------------- go.mod | 2 ++ go.sum | 10 +++++++++ webview.go | 60 ++++++++++++++++++++++++++++-------------------------- 6 files changed, 81 insertions(+), 62 deletions(-) diff --git a/actions.go b/actions.go index 6502a63..254906d 100644 --- a/actions.go +++ b/actions.go @@ -4,6 +4,8 @@ import ( "context" "fmt" "time" + + coreerr "forge.lthn.ai/core/go-log" ) // Action represents a browser action that can be performed. @@ -43,7 +45,7 @@ func (a NavigateAction) Execute(ctx context.Context, wv *Webview) error { "url": a.URL, }) if err != nil { - return fmt.Errorf("failed to navigate: %w", err) + return coreerr.E("NavigateAction.Execute", "failed to navigate", err) } return wv.waitForLoad(ctx) } @@ -191,7 +193,7 @@ func (a HoverAction) Execute(ctx context.Context, wv *Webview) error { } if elem.BoundingBox == nil { - return fmt.Errorf("element has no bounding box") + return coreerr.E("HoverAction.Execute", "element has no bounding box", nil) } x := elem.BoundingBox.X + elem.BoundingBox.Width/2 @@ -459,7 +461,7 @@ func (s *ActionSequence) WaitForSelector(selector string) *ActionSequence { func (s *ActionSequence) Execute(ctx context.Context, wv *Webview) error { for i, action := range s.actions { if err := action.Execute(ctx, wv); err != nil { - return fmt.Errorf("action %d failed: %w", i, err) + return coreerr.E("ActionSequence.Execute", fmt.Sprintf("action %d failed", i), err) } } return nil @@ -492,18 +494,18 @@ func (wv *Webview) DragAndDrop(sourceSelector, targetSelector string) error { // Get source and target elements source, err := wv.querySelector(ctx, sourceSelector) if err != nil { - return fmt.Errorf("source element not found: %w", err) + return coreerr.E("Webview.DragAndDrop", "source element not found", err) } if source.BoundingBox == nil { - return fmt.Errorf("source element has no bounding box") + return coreerr.E("Webview.DragAndDrop", "source element has no bounding box", nil) } target, err := wv.querySelector(ctx, targetSelector) if err != nil { - return fmt.Errorf("target element not found: %w", err) + return coreerr.E("Webview.DragAndDrop", "target element not found", err) } if target.BoundingBox == nil { - return fmt.Errorf("target element has no bounding box") + return coreerr.E("Webview.DragAndDrop", "target element has no bounding box", nil) } // Calculate center points diff --git a/angular.go b/angular.go index 0ec7023..844e21a 100644 --- a/angular.go +++ b/angular.go @@ -5,6 +5,8 @@ import ( "fmt" "strings" "time" + + coreerr "forge.lthn.ai/core/go-log" ) // AngularHelper provides Angular-specific testing utilities. @@ -43,7 +45,7 @@ func (ah *AngularHelper) waitForAngular(ctx context.Context) error { return err } if !isAngular { - return fmt.Errorf("not an Angular application") + return coreerr.E("AngularHelper.waitForAngular", "not an Angular application", nil) } // Wait for Zone.js stability @@ -238,7 +240,7 @@ func (ah *AngularHelper) NavigateByRouter(path string) error { _, err := ah.wv.evaluate(ctx, script) if err != nil { - return fmt.Errorf("failed to navigate: %w", err) + return coreerr.E("AngularHelper.NavigateByRouter", "failed to navigate", err) } // Wait for navigation to complete @@ -279,13 +281,13 @@ func (ah *AngularHelper) GetRouterState() (*AngularRouterState, error) { } if result == nil { - return nil, fmt.Errorf("could not get router state") + return nil, coreerr.E("AngularHelper.GetRouterState", "could not get router state", nil) } // Parse result resultMap, ok := result.(map[string]any) if !ok { - return nil, fmt.Errorf("invalid router state format") + return nil, coreerr.E("AngularHelper.GetRouterState", "invalid router state format", nil) } state := &AngularRouterState{ diff --git a/cdp.go b/cdp.go index 9848a4c..8855d5d 100644 --- a/cdp.go +++ b/cdp.go @@ -3,7 +3,6 @@ package webview import ( "context" "encoding/json" - "fmt" "io" "iter" "net/http" @@ -12,6 +11,8 @@ import ( "sync/atomic" "github.com/gorilla/websocket" + + coreerr "forge.lthn.ai/core/go-log" ) // CDPClient handles communication with Chrome DevTools Protocol via WebSocket. @@ -78,18 +79,18 @@ func NewCDPClient(debugURL string) (*CDPClient, error) { // Get available targets resp, err := http.Get(debugURL + "/json") if err != nil { - return nil, fmt.Errorf("failed to get targets: %w", err) + return nil, coreerr.E("CDPClient.New", "failed to get targets", err) } defer func() { _ = resp.Body.Close() }() body, err := io.ReadAll(resp.Body) if err != nil { - return nil, fmt.Errorf("failed to read targets: %w", err) + return nil, coreerr.E("CDPClient.New", "failed to read targets", err) } var targets []TargetInfo if err := json.Unmarshal(body, &targets); err != nil { - return nil, fmt.Errorf("failed to parse targets: %w", err) + return nil, coreerr.E("CDPClient.New", "failed to parse targets", err) } // Find a page target @@ -105,31 +106,31 @@ func NewCDPClient(debugURL string) (*CDPClient, error) { // Try to create a new target resp, err := http.Get(debugURL + "/json/new") if err != nil { - return nil, fmt.Errorf("no page targets found and failed to create new: %w", err) + return nil, coreerr.E("CDPClient.New", "no page targets found and failed to create new", err) } defer func() { _ = resp.Body.Close() }() body, err := io.ReadAll(resp.Body) if err != nil { - return nil, fmt.Errorf("failed to read new target: %w", err) + return nil, coreerr.E("CDPClient.New", "failed to read new target", err) } var newTarget TargetInfo if err := json.Unmarshal(body, &newTarget); err != nil { - return nil, fmt.Errorf("failed to parse new target: %w", err) + return nil, coreerr.E("CDPClient.New", "failed to parse new target", err) } wsURL = newTarget.WebSocketDebuggerURL } if wsURL == "" { - return nil, fmt.Errorf("no WebSocket URL available") + return nil, coreerr.E("CDPClient.New", "no WebSocket URL available", nil) } // Connect to WebSocket conn, _, err := websocket.DefaultDialer.Dial(wsURL, nil) if err != nil { - return nil, fmt.Errorf("failed to connect to WebSocket: %w", err) + return nil, coreerr.E("CDPClient.New", "failed to connect to WebSocket", err) } ctx, cancel := context.WithCancel(context.Background()) @@ -185,7 +186,7 @@ func (c *CDPClient) Call(ctx context.Context, method string, params map[string]a err := c.conn.WriteJSON(msg) c.mu.Unlock() if err != nil { - return nil, fmt.Errorf("failed to send message: %w", err) + return nil, coreerr.E("CDPClient.Call", "failed to send message", err) } // Wait for response @@ -194,7 +195,7 @@ func (c *CDPClient) Call(ctx context.Context, method string, params map[string]a return nil, ctx.Err() case resp := <-respCh: if resp.Error != nil { - return nil, fmt.Errorf("CDP error %d: %s", resp.Error.Code, resp.Error.Message) + return nil, coreerr.E("CDPClient.Call", resp.Error.Message, nil) } return resp.Result, nil } @@ -293,28 +294,28 @@ func (c *CDPClient) NewTab(url string) (*CDPClient, error) { resp, err := http.Get(endpoint) if err != nil { - return nil, fmt.Errorf("failed to create new tab: %w", err) + return nil, coreerr.E("CDPClient.NewTab", "failed to create new tab", err) } defer func() { _ = resp.Body.Close() }() body, err := io.ReadAll(resp.Body) if err != nil { - return nil, fmt.Errorf("failed to read response: %w", err) + return nil, coreerr.E("CDPClient.NewTab", "failed to read response", err) } var target TargetInfo if err := json.Unmarshal(body, &target); err != nil { - return nil, fmt.Errorf("failed to parse target: %w", err) + return nil, coreerr.E("CDPClient.NewTab", "failed to parse target", err) } if target.WebSocketDebuggerURL == "" { - return nil, fmt.Errorf("no WebSocket URL for new tab") + return nil, coreerr.E("CDPClient.NewTab", "no WebSocket URL for new tab", nil) } // Connect to new tab conn, _, err := websocket.DefaultDialer.Dial(target.WebSocketDebuggerURL, nil) if err != nil { - return nil, fmt.Errorf("failed to connect to new tab: %w", err) + return nil, coreerr.E("CDPClient.NewTab", "failed to connect to new tab", err) } ctx, cancel := context.WithCancel(context.Background()) @@ -350,18 +351,18 @@ func (c *CDPClient) CloseTab() error { func ListTargets(debugURL string) ([]TargetInfo, error) { resp, err := http.Get(debugURL + "/json") if err != nil { - return nil, fmt.Errorf("failed to get targets: %w", err) + return nil, coreerr.E("ListTargets", "failed to get targets", err) } defer func() { _ = resp.Body.Close() }() body, err := io.ReadAll(resp.Body) if err != nil { - return nil, fmt.Errorf("failed to read targets: %w", err) + return nil, coreerr.E("ListTargets", "failed to read targets", err) } var targets []TargetInfo if err := json.Unmarshal(body, &targets); err != nil { - return nil, fmt.Errorf("failed to parse targets: %w", err) + return nil, coreerr.E("ListTargets", "failed to parse targets", err) } return targets, nil @@ -386,18 +387,18 @@ func ListTargetsAll(debugURL string) iter.Seq[TargetInfo] { func GetVersion(debugURL string) (map[string]string, error) { resp, err := http.Get(debugURL + "/json/version") if err != nil { - return nil, fmt.Errorf("failed to get version: %w", err) + return nil, coreerr.E("GetVersion", "failed to get version", err) } defer func() { _ = resp.Body.Close() }() body, err := io.ReadAll(resp.Body) if err != nil { - return nil, fmt.Errorf("failed to read version: %w", err) + return nil, coreerr.E("GetVersion", "failed to read version", err) } var version map[string]string if err := json.Unmarshal(body, &version); err != nil { - return nil, fmt.Errorf("failed to parse version: %w", err) + return nil, coreerr.E("GetVersion", "failed to parse version", err) } return version, nil diff --git a/go.mod b/go.mod index df463ec..b04a469 100644 --- a/go.mod +++ b/go.mod @@ -3,3 +3,5 @@ module forge.lthn.ai/core/go-webview go 1.26.0 require github.com/gorilla/websocket v1.5.3 + +require forge.lthn.ai/core/go-log v0.0.4 diff --git a/go.sum b/go.sum index 25a9fc4..783326b 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,12 @@ +forge.lthn.ai/core/go-log v0.0.4 h1:KTuCEPgFmuM8KJfnyQ8vPOU1Jg654W74h8IJvfQMfv0= +forge.lthn.ai/core/go-log v0.0.4/go.mod h1:r14MXKOD3LF/sI8XUJQhRk/SZHBE7jAFVuCfgkXoZPw= +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= 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 ba9a354..ede519f 100644 --- a/webview.go +++ b/webview.go @@ -30,6 +30,8 @@ import ( "strings" "sync" "time" + + coreerr "forge.lthn.ai/core/go-log" ) // Webview represents a connection to a Chrome DevTools Protocol endpoint. @@ -80,7 +82,7 @@ func WithDebugURL(url string) Option { return func(wv *Webview) error { client, err := NewCDPClient(url) if err != nil { - return fmt.Errorf("failed to connect to Chrome DevTools: %w", err) + return coreerr.E("Webview.WithDebugURL", "failed to connect to Chrome DevTools", err) } wv.client = client return nil @@ -125,13 +127,13 @@ func New(opts ...Option) (*Webview, error) { if wv.client == nil { cancel() - return nil, fmt.Errorf("no debug URL provided; use WithDebugURL option") + return nil, coreerr.E("Webview.New", "no debug URL provided; use WithDebugURL option", nil) } // Enable console capture if err := wv.enableConsole(); err != nil { cancel() - return nil, fmt.Errorf("failed to enable console capture: %w", err) + return nil, coreerr.E("Webview.New", "failed to enable console capture", err) } return wv, nil @@ -155,7 +157,7 @@ func (wv *Webview) Navigate(url string) error { "url": url, }) if err != nil { - return fmt.Errorf("failed to navigate: %w", err) + return coreerr.E("Webview.Navigate", "failed to navigate", err) } // Wait for page load @@ -248,17 +250,17 @@ func (wv *Webview) Screenshot() ([]byte, error) { "format": "png", }) if err != nil { - return nil, fmt.Errorf("failed to capture screenshot: %w", err) + return nil, coreerr.E("Webview.Screenshot", "failed to capture screenshot", err) } dataStr, ok := result["data"].(string) if !ok { - return nil, fmt.Errorf("invalid screenshot data") + return nil, coreerr.E("Webview.Screenshot", "invalid screenshot data", nil) } data, err := base64.StdEncoding.DecodeString(dataStr) if err != nil { - return nil, fmt.Errorf("failed to decode screenshot: %w", err) + return nil, coreerr.E("Webview.Screenshot", "failed to decode screenshot", err) } return data, nil @@ -294,7 +296,7 @@ func (wv *Webview) GetURL() (string, error) { url, ok := result.(string) if !ok { - return "", fmt.Errorf("invalid URL result") + return "", coreerr.E("Webview.GetURL", "invalid URL result", nil) } return url, nil @@ -312,7 +314,7 @@ func (wv *Webview) GetTitle() (string, error) { title, ok := result.(string) if !ok { - return "", fmt.Errorf("invalid title result") + return "", coreerr.E("Webview.GetTitle", "invalid title result", nil) } return title, nil @@ -337,7 +339,7 @@ func (wv *Webview) GetHTML(selector string) (string, error) { html, ok := result.(string) if !ok { - return "", fmt.Errorf("invalid HTML result") + return "", coreerr.E("Webview.GetHTML", "invalid HTML result", nil) } return html, nil @@ -375,7 +377,7 @@ func (wv *Webview) Reload() error { _, err := wv.client.Call(ctx, "Page.reload", nil) if err != nil { - return fmt.Errorf("failed to reload: %w", err) + return coreerr.E("Webview.Reload", "failed to reload", err) } return wv.waitForLoad(ctx) @@ -541,17 +543,17 @@ func (wv *Webview) evaluate(ctx context.Context, script string) (any, error) { "returnByValue": true, }) if err != nil { - return nil, fmt.Errorf("failed to evaluate script: %w", err) + return nil, coreerr.E("Webview.evaluate", "failed to evaluate script", err) } // Check for exception if exceptionDetails, ok := result["exceptionDetails"].(map[string]any); ok { if exception, ok := exceptionDetails["exception"].(map[string]any); ok { if description, ok := exception["description"].(string); ok { - return nil, fmt.Errorf("JavaScript error: %s", description) + return nil, coreerr.E("Webview.evaluate", description, nil) } } - return nil, fmt.Errorf("JavaScript error") + return nil, coreerr.E("Webview.evaluate", "JavaScript error", nil) } // Extract result value @@ -567,17 +569,17 @@ func (wv *Webview) querySelector(ctx context.Context, selector string) (*Element // Get document root docResult, err := wv.client.Call(ctx, "DOM.getDocument", nil) if err != nil { - return nil, fmt.Errorf("failed to get document: %w", err) + return nil, coreerr.E("Webview.querySelector", "failed to get document", err) } root, ok := docResult["root"].(map[string]any) if !ok { - return nil, fmt.Errorf("invalid document root") + return nil, coreerr.E("Webview.querySelector", "invalid document root", nil) } rootID, ok := root["nodeId"].(float64) if !ok { - return nil, fmt.Errorf("invalid root node ID") + return nil, coreerr.E("Webview.querySelector", "invalid root node ID", nil) } // Query selector @@ -586,12 +588,12 @@ func (wv *Webview) querySelector(ctx context.Context, selector string) (*Element "selector": selector, }) if err != nil { - return nil, fmt.Errorf("failed to query selector: %w", err) + return nil, coreerr.E("Webview.querySelector", "failed to query selector", err) } nodeID, ok := queryResult["nodeId"].(float64) if !ok || nodeID == 0 { - return nil, fmt.Errorf("element not found: %s", selector) + return nil, coreerr.E("Webview.querySelector", "element not found: "+selector, nil) } return wv.getElementInfo(ctx, int(nodeID)) @@ -602,17 +604,17 @@ func (wv *Webview) querySelectorAll(ctx context.Context, selector string) ([]*El // Get document root docResult, err := wv.client.Call(ctx, "DOM.getDocument", nil) if err != nil { - return nil, fmt.Errorf("failed to get document: %w", err) + return nil, coreerr.E("Webview.querySelectorAll", "failed to get document", err) } root, ok := docResult["root"].(map[string]any) if !ok { - return nil, fmt.Errorf("invalid document root") + return nil, coreerr.E("Webview.querySelectorAll", "invalid document root", nil) } rootID, ok := root["nodeId"].(float64) if !ok { - return nil, fmt.Errorf("invalid root node ID") + return nil, coreerr.E("Webview.querySelectorAll", "invalid root node ID", nil) } // Query selector all @@ -621,12 +623,12 @@ func (wv *Webview) querySelectorAll(ctx context.Context, selector string) ([]*El "selector": selector, }) if err != nil { - return nil, fmt.Errorf("failed to query selector all: %w", err) + return nil, coreerr.E("Webview.querySelectorAll", "failed to query selector all", err) } nodeIDs, ok := queryResult["nodeIds"].([]any) if !ok { - return nil, fmt.Errorf("invalid node IDs") + return nil, coreerr.E("Webview.querySelectorAll", "invalid node IDs", nil) } elements := make([]*ElementInfo, 0, len(nodeIDs)) @@ -653,7 +655,7 @@ func (wv *Webview) getElementInfo(ctx context.Context, nodeID int) (*ElementInfo node, ok := descResult["node"].(map[string]any) if !ok { - return nil, fmt.Errorf("invalid node description") + return nil, coreerr.E("Webview.getElementInfo", "invalid node description", nil) } tagName, _ := node["nodeName"].(string) @@ -726,7 +728,7 @@ func (wv *Webview) click(ctx context.Context, selector string) error { "clickCount": 1, }) if err != nil { - return fmt.Errorf("failed to dispatch %s: %w", eventType, err) + return coreerr.E("Webview.click", "failed to dispatch "+eventType, err) } } @@ -739,7 +741,7 @@ func (wv *Webview) typeText(ctx context.Context, selector, text string) error { script := fmt.Sprintf("document.querySelector(%q)?.focus()", selector) _, err := wv.evaluate(ctx, script) if err != nil { - return fmt.Errorf("failed to focus element: %w", err) + return coreerr.E("Webview.typeText", "failed to focus element", err) } // Type each character @@ -749,14 +751,14 @@ func (wv *Webview) typeText(ctx context.Context, selector, text string) error { "text": string(char), }) if err != nil { - return fmt.Errorf("failed to dispatch keyDown: %w", err) + return coreerr.E("Webview.typeText", "failed to dispatch keyDown", err) } _, err = wv.client.Call(ctx, "Input.dispatchKeyEvent", map[string]any{ "type": "keyUp", }) if err != nil { - return fmt.Errorf("failed to dispatch keyUp: %w", err) + return coreerr.E("Webview.typeText", "failed to dispatch keyUp", err) } }