refactor: replace fmt.Errorf/errors.New with coreerr.E()
All checks were successful
Security Scan / security (push) Successful in 8s
Test / test (push) Successful in 1m14s

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-03-16 21:10:49 +00:00
parent 978e153615
commit ab77e922bd
6 changed files with 81 additions and 62 deletions

View file

@ -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

View file

@ -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{

45
cdp.go
View file

@ -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

2
go.mod
View file

@ -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

10
go.sum
View file

@ -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=

View file

@ -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)
}
}