From f83ccec5ce53e3dcd6caff0e822220d0bf4333e7 Mon Sep 17 00:00:00 2001 From: Guy Goldenberg Date: Fri, 11 Oct 2024 02:25:49 +0300 Subject: [PATCH 1/7] Add stream logger support for driver stdout and stderr --- internal/logger/logger.go | 55 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 internal/logger/logger.go diff --git a/internal/logger/logger.go b/internal/logger/logger.go new file mode 100644 index 0000000..1ba148f --- /dev/null +++ b/internal/logger/logger.go @@ -0,0 +1,55 @@ +package logger + +import ( + "log/slog" + "strings" +) + +// StreamType represents the type of output stream +type StreamType int + +const ( + StdoutStream StreamType = iota + StderrStream +) + +func (st StreamType) String() string { + switch st { + case StdoutStream: + return "stdout" + case StderrStream: + return "stderr" + default: + return "unknown" + } +} + +func (st StreamType) LogValue() slog.Value { + return slog.StringValue(st.String()) +} + +// SlogWriter is a custom type that implements io.Writer +type SlogWriter struct { + logger *slog.Logger + stream StreamType + cmdAttrs []slog.Attr +} + +// Write implements the io.Writer interface +func (sw *SlogWriter) Write(p []byte) (n int, err error) { + message := strings.TrimSpace(string(p)) + attrs := append(sw.cmdAttrs, + slog.String("stream", sw.stream.String()), + ) + sw.logger.LogAttrs(nil, slog.LevelInfo, message, attrs...) + return len(p), nil +} + +// NewSlogWriter creates a new SlogWriter with the specified stream type and command attributes +func NewSlogWriter(logger *slog.Logger, stream StreamType, cmdAttrs ...slog.Attr) *SlogWriter { + return &SlogWriter{ + logger: logger, + stream: stream, + cmdAttrs: cmdAttrs, + } +} From a6bd64480b48a5171d1da8203b590213db125bb0 Mon Sep 17 00:00:00 2001 From: Guy Goldenberg Date: Fri, 11 Oct 2024 02:26:32 +0300 Subject: [PATCH 2/7] Use `slog` as the main logger --- run.go | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/run.go b/run.go index 23eb5cb..feee498 100644 --- a/run.go +++ b/run.go @@ -6,21 +6,24 @@ import ( "errors" "fmt" "io" - "log" + "log/slog" "net/http" "os" "os/exec" "path/filepath" "runtime" "strings" + + pwlogger "github.com/playwright-community/playwright-go/internal/logger" ) const ( - playwrightCliVersion = "1.47.2" + playwrightCliVersion = "1.47.2" + playwrightDriverLogPrefix = "playwright-driver" ) var ( - logger = log.Default() + logger = slog.Default() playwrightCDNMirrors = []string{ "https://playwright.azureedge.net", "https://playwright-akamai.azureedge.net", @@ -186,7 +189,7 @@ func (d *PlaywrightDriver) DownloadDriver() error { func (d *PlaywrightDriver) log(s string) { if d.options.Verbose { - logger.Println(s) + logger.Info(s) } } @@ -232,6 +235,10 @@ type RunOptions struct { Verbose bool // default true Stdout io.Writer Stderr io.Writer + Logger *slog.Logger + // If set, will capture all output to the logger + // This will override Stdout and Stderr + CaptureAllOutputWithLogger bool } // Install does download the driver and the browsers. @@ -291,8 +298,17 @@ func transformRunOptions(options ...*RunOptions) (*RunOptions, error) { } if option.Stderr == nil { option.Stderr = os.Stderr + } + if option.Logger == nil { + logger = slog.New(slog.NewTextHandler(option.Stderr, nil)) } else { - logger.SetOutput(option.Stderr) + logger = option.Logger + } + + if option.CaptureAllOutputWithLogger { + sourceLogAttr := slog.String("source", playwrightDriverLogPrefix) // Indicate that the logs are from the driver + option.Stdout = pwlogger.NewSlogWriter(logger, pwlogger.StdoutStream, sourceLogAttr) + option.Stderr = pwlogger.NewSlogWriter(logger, pwlogger.StderrStream, sourceLogAttr) } return option, nil } From 9b85d5151cb6b0d59c4e9324cbf7b8eab4bdd526 Mon Sep 17 00:00:00 2001 From: Guy Goldenberg Date: Fri, 11 Oct 2024 02:26:55 +0300 Subject: [PATCH 3/7] Change all log calls to use `slog` --- binding_call.go | 4 ++-- browser_context.go | 2 +- channel.go | 2 +- frame.go | 2 +- har_router.go | 6 +++--- page.go | 2 +- transport.go | 4 ++-- websocket.go | 4 ++-- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/binding_call.go b/binding_call.go index a018557..d3fbeb5 100644 --- a/binding_call.go +++ b/binding_call.go @@ -34,7 +34,7 @@ func (b *bindingCallImpl) Call(f BindingCallFunction) { if _, err := b.channel.Send("reject", map[string]interface{}{ "error": serializeError(r.(error)), }); err != nil { - logger.Printf("could not reject BindingCall: %v\n", err) + logger.Error("could not reject BindingCall: %v", err) } } }() @@ -60,7 +60,7 @@ func (b *bindingCallImpl) Call(f BindingCallFunction) { "result": serializeArgument(result), }) if err != nil { - logger.Printf("could not resolve BindingCall: %v\n", err) + logger.Error("could not resolve BindingCall: %v", err) } } diff --git a/browser_context.go b/browser_context.go index dc4f4a3..5687bb7 100644 --- a/browser_context.go +++ b/browser_context.go @@ -589,7 +589,7 @@ func (b *browserContextImpl) onRoute(route *routeImpl) { return nil, err }, true) if err != nil { - logger.Printf("could not update interception patterns: %v\n", err) + logger.Error("could not update interception patterns: %v", err) } } } diff --git a/channel.go b/channel.go index 29de05a..974d2fa 100644 --- a/channel.go +++ b/channel.go @@ -59,7 +59,7 @@ func (c *channel) SendNoReply(method string, options ...interface{}) { return c.connection.sendMessageToServer(c.owner, method, params, true) }, false) if err != nil { - logger.Printf("SendNoReply failed: %v\n", err) + logger.Error("SendNoReply failed: %v", err) } } diff --git a/frame.go b/frame.go index dfd342e..be7c9e9 100644 --- a/frame.go +++ b/frame.go @@ -210,7 +210,7 @@ func (f *frameImpl) ExpectNavigation(cb func() error, options ...FrameExpectNavi err, ok := ev["error"] if ok { // Any failed navigation results in a rejection. - logger.Printf("navigated to %s error: %v", ev["url"].(string), err) + logger.Error("navigated to %s error: %v", ev["url"].(string), err) return true } return matcher == nil || matcher.Matches(ev["url"].(string)) diff --git a/har_router.go b/har_router.go index 2a92d6d..abb64a9 100644 --- a/har_router.go +++ b/har_router.go @@ -19,7 +19,7 @@ func (r *harRouter) addContextRoute(context BrowserContext) error { err := context.Route(r.urlOrPredicate, func(route Route) { err := r.handle(route) if err != nil { - logger.Println(err) + logger.Error("%v", err) } }) if err != nil { @@ -35,7 +35,7 @@ func (r *harRouter) addPageRoute(page Page) error { err := page.Route(r.urlOrPredicate, func(route Route) { err := r.handle(route) if err != nil { - logger.Println(err) + logger.Error("%v", err) } }) if err != nil { @@ -86,7 +86,7 @@ func (r *harRouter) handle(route Route) error { Headers: deserializeNameAndValueToMap(response.Headers), }) case "error": - logger.Printf("har action error: %v\n", *response.Message) + logger.Error("har action error: %v", *response.Message) fallthrough case "noentry": } diff --git a/page.go b/page.go index 8130009..0e23b44 100644 --- a/page.go +++ b/page.go @@ -927,7 +927,7 @@ func (p *pageImpl) onRoute(route *routeImpl) { return nil, err }, true) if err != nil { - logger.Printf("could not update interception patterns: %v\n", err) + logger.Error("could not update interception patterns: %v", err) } } } diff --git a/transport.go b/transport.go index d083c3b..44506d7 100644 --- a/transport.go +++ b/transport.go @@ -44,7 +44,7 @@ func (t *pipeTransport) Poll() (*message, error) { if os.Getenv("DEBUGP") != "" { fmt.Fprint(os.Stdout, "\x1b[33mRECV>\x1b[0m\n") if err := json.NewEncoder(os.Stdout).Encode(msg); err != nil { - logger.Printf("could not encode json: %v\n", err) + logger.Error("could not encode json: %v", err) } } return msg, nil @@ -72,7 +72,7 @@ func (t *pipeTransport) Send(msg map[string]interface{}) error { if os.Getenv("DEBUGP") != "" { fmt.Fprint(os.Stdout, "\x1b[32mSEND>\x1b[0m\n") if err := json.NewEncoder(os.Stdout).Encode(msg); err != nil { - logger.Printf("could not encode json: %v\n", err) + logger.Error("could not encode json: %v", err) } } lengthPadding := make([]byte, 4) diff --git a/websocket.go b/websocket.go index f28601d..85b7e45 100644 --- a/websocket.go +++ b/websocket.go @@ -50,7 +50,7 @@ func (ws *webSocketImpl) onFrameSent(opcode float64, data string) { if opcode == 2 { payload, err := base64.StdEncoding.DecodeString(data) if err != nil { - logger.Printf("could not decode WebSocket.onFrameSent payload: %v\n", err) + logger.Error("could not decode WebSocket.onFrameSent payload: %v\n", err) return } ws.Emit("framesent", payload) @@ -63,7 +63,7 @@ func (ws *webSocketImpl) onFrameReceived(opcode float64, data string) { if opcode == 2 { payload, err := base64.StdEncoding.DecodeString(data) if err != nil { - logger.Printf("could not decode WebSocket.onFrameReceived payload: %v\n", err) + logger.Error("could not decode WebSocket.onFrameReceived payload: %v\n", err) return } ws.Emit("framereceived", payload) From 116ff748aa365918d1656a0d9b20484568c83509 Mon Sep 17 00:00:00 2001 From: Guy Goldenberg Date: Fri, 11 Oct 2024 02:38:30 +0300 Subject: [PATCH 4/7] Rename to log source --- run.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/run.go b/run.go index feee498..3781f37 100644 --- a/run.go +++ b/run.go @@ -19,7 +19,7 @@ import ( const ( playwrightCliVersion = "1.47.2" - playwrightDriverLogPrefix = "playwright-driver" + playwrightDriverLogSource = "playwright-driver" ) var ( @@ -306,7 +306,7 @@ func transformRunOptions(options ...*RunOptions) (*RunOptions, error) { } if option.CaptureAllOutputWithLogger { - sourceLogAttr := slog.String("source", playwrightDriverLogPrefix) // Indicate that the logs are from the driver + sourceLogAttr := slog.String("source", playwrightDriverLogSource) // Indicate that the logs are from the driver option.Stdout = pwlogger.NewSlogWriter(logger, pwlogger.StdoutStream, sourceLogAttr) option.Stderr = pwlogger.NewSlogWriter(logger, pwlogger.StderrStream, sourceLogAttr) } From e08235e170c07e26e7b954ce37a782e034bf3578 Mon Sep 17 00:00:00 2001 From: Guy Goldenberg Date: Fri, 11 Oct 2024 02:50:51 +0300 Subject: [PATCH 5/7] Rename to `pwlogger` and use `ErrAttr` --- binding_call.go | 5 +++-- browser_context.go | 3 ++- channel.go | 7 +++++-- frame.go | 3 ++- har_router.go | 8 +++++--- internal/{logger => pwlogger}/logger.go | 6 +++++- page.go | 3 ++- run.go | 3 +-- transport.go | 5 +++-- websocket.go | 5 +++-- 10 files changed, 31 insertions(+), 17 deletions(-) rename internal/{logger => pwlogger}/logger.go (92%) diff --git a/binding_call.go b/binding_call.go index d3fbeb5..ef3e7b8 100644 --- a/binding_call.go +++ b/binding_call.go @@ -2,6 +2,7 @@ package playwright import ( "fmt" + "github.com/playwright-community/playwright-go/internal/pwlogger" "strings" "github.com/go-stack/stack" @@ -34,7 +35,7 @@ func (b *bindingCallImpl) Call(f BindingCallFunction) { if _, err := b.channel.Send("reject", map[string]interface{}{ "error": serializeError(r.(error)), }); err != nil { - logger.Error("could not reject BindingCall: %v", err) + logger.Error("could not reject BindingCall", pwlogger.ErrAttr(err)) } } }() @@ -60,7 +61,7 @@ func (b *bindingCallImpl) Call(f BindingCallFunction) { "result": serializeArgument(result), }) if err != nil { - logger.Error("could not resolve BindingCall: %v", err) + logger.Error("could not resolve BindingCall", pwlogger.ErrAttr(err)) } } diff --git a/browser_context.go b/browser_context.go index 5687bb7..fb5d4fe 100644 --- a/browser_context.go +++ b/browser_context.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/playwright-community/playwright-go/internal/pwlogger" "os" "regexp" "slices" @@ -589,7 +590,7 @@ func (b *browserContextImpl) onRoute(route *routeImpl) { return nil, err }, true) if err != nil { - logger.Error("could not update interception patterns: %v", err) + logger.Error("Could not update interception patterns", pwlogger.ErrAttr(err)) } } } diff --git a/channel.go b/channel.go index 974d2fa..7989ca0 100644 --- a/channel.go +++ b/channel.go @@ -1,6 +1,9 @@ package playwright -import "encoding/json" +import ( + "encoding/json" + "github.com/playwright-community/playwright-go/internal/pwlogger" +) type channel struct { eventEmitter @@ -59,7 +62,7 @@ func (c *channel) SendNoReply(method string, options ...interface{}) { return c.connection.sendMessageToServer(c.owner, method, params, true) }, false) if err != nil { - logger.Error("SendNoReply failed: %v", err) + logger.Error("SendNoReply failed", pwlogger.ErrAttr(err)) } } diff --git a/frame.go b/frame.go index be7c9e9..f1f8321 100644 --- a/frame.go +++ b/frame.go @@ -3,6 +3,7 @@ package playwright import ( "errors" "fmt" + "log/slog" "os" "time" @@ -210,7 +211,7 @@ func (f *frameImpl) ExpectNavigation(cb func() error, options ...FrameExpectNavi err, ok := ev["error"] if ok { // Any failed navigation results in a rejection. - logger.Error("navigated to %s error: %v", ev["url"].(string), err) + logger.Error("navigation error", slog.Any("url", ev["url"].(string)), slog.Any("error", err)) return true } return matcher == nil || matcher.Matches(ev["url"].(string)) diff --git a/har_router.go b/har_router.go index abb64a9..865270d 100644 --- a/har_router.go +++ b/har_router.go @@ -2,6 +2,8 @@ package playwright import ( "errors" + "github.com/playwright-community/playwright-go/internal/pwlogger" + "log/slog" ) type harRouter struct { @@ -19,7 +21,7 @@ func (r *harRouter) addContextRoute(context BrowserContext) error { err := context.Route(r.urlOrPredicate, func(route Route) { err := r.handle(route) if err != nil { - logger.Error("%v", err) + logger.Error("Error handling context route", pwlogger.ErrAttr(err)) } }) if err != nil { @@ -35,7 +37,7 @@ func (r *harRouter) addPageRoute(page Page) error { err := page.Route(r.urlOrPredicate, func(route Route) { err := r.handle(route) if err != nil { - logger.Error("%v", err) + logger.Error("Error handling page route", pwlogger.ErrAttr(err)) } }) if err != nil { @@ -86,7 +88,7 @@ func (r *harRouter) handle(route Route) error { Headers: deserializeNameAndValueToMap(response.Headers), }) case "error": - logger.Error("har action error: %v", *response.Message) + logger.Error("har action error", slog.Any("error", *response.Message)) fallthrough case "noentry": } diff --git a/internal/logger/logger.go b/internal/pwlogger/logger.go similarity index 92% rename from internal/logger/logger.go rename to internal/pwlogger/logger.go index 1ba148f..bac1fcc 100644 --- a/internal/logger/logger.go +++ b/internal/pwlogger/logger.go @@ -1,4 +1,4 @@ -package logger +package pwlogger import ( "log/slog" @@ -53,3 +53,7 @@ func NewSlogWriter(logger *slog.Logger, stream StreamType, cmdAttrs ...slog.Attr cmdAttrs: cmdAttrs, } } + +func ErrAttr(err error) slog.Attr { + return slog.Any("error", err) +} diff --git a/page.go b/page.go index 0e23b44..da86c77 100644 --- a/page.go +++ b/page.go @@ -4,6 +4,7 @@ import ( "encoding/base64" "errors" "fmt" + "github.com/playwright-community/playwright-go/internal/pwlogger" "os" "slices" "sync" @@ -927,7 +928,7 @@ func (p *pageImpl) onRoute(route *routeImpl) { return nil, err }, true) if err != nil { - logger.Error("could not update interception patterns: %v", err) + logger.Error("could not update interception patterns", pwlogger.ErrAttr(err)) } } } diff --git a/run.go b/run.go index 3781f37..49fa5ce 100644 --- a/run.go +++ b/run.go @@ -5,6 +5,7 @@ import ( "bytes" "errors" "fmt" + "github.com/playwright-community/playwright-go/internal/pwlogger" "io" "log/slog" "net/http" @@ -13,8 +14,6 @@ import ( "path/filepath" "runtime" "strings" - - pwlogger "github.com/playwright-community/playwright-go/internal/logger" ) const ( diff --git a/transport.go b/transport.go index 44506d7..2c80bf9 100644 --- a/transport.go +++ b/transport.go @@ -5,6 +5,7 @@ import ( "encoding/binary" "errors" "fmt" + "github.com/playwright-community/playwright-go/internal/pwlogger" "io" "os" @@ -44,7 +45,7 @@ func (t *pipeTransport) Poll() (*message, error) { if os.Getenv("DEBUGP") != "" { fmt.Fprint(os.Stdout, "\x1b[33mRECV>\x1b[0m\n") if err := json.NewEncoder(os.Stdout).Encode(msg); err != nil { - logger.Error("could not encode json: %v", err) + logger.Error("could not encode json", pwlogger.ErrAttr(err)) } } return msg, nil @@ -72,7 +73,7 @@ func (t *pipeTransport) Send(msg map[string]interface{}) error { if os.Getenv("DEBUGP") != "" { fmt.Fprint(os.Stdout, "\x1b[32mSEND>\x1b[0m\n") if err := json.NewEncoder(os.Stdout).Encode(msg); err != nil { - logger.Error("could not encode json: %v", err) + logger.Error("could not encode json", pwlogger.ErrAttr(err)) } } lengthPadding := make([]byte, 4) diff --git a/websocket.go b/websocket.go index 85b7e45..9404c49 100644 --- a/websocket.go +++ b/websocket.go @@ -3,6 +3,7 @@ package playwright import ( "encoding/base64" "errors" + "github.com/playwright-community/playwright-go/internal/pwlogger" ) type webSocketImpl struct { @@ -50,7 +51,7 @@ func (ws *webSocketImpl) onFrameSent(opcode float64, data string) { if opcode == 2 { payload, err := base64.StdEncoding.DecodeString(data) if err != nil { - logger.Error("could not decode WebSocket.onFrameSent payload: %v\n", err) + logger.Error("could not decode WebSocket.onFrameSent payload", pwlogger.ErrAttr(err)) return } ws.Emit("framesent", payload) @@ -63,7 +64,7 @@ func (ws *webSocketImpl) onFrameReceived(opcode float64, data string) { if opcode == 2 { payload, err := base64.StdEncoding.DecodeString(data) if err != nil { - logger.Error("could not decode WebSocket.onFrameReceived payload: %v\n", err) + logger.Error("could not decode WebSocket.onFrameReceived payload", pwlogger.ErrAttr(err)) return } ws.Emit("framereceived", payload) From e3a6a5fa4274849d1e95dbc19d20c1ebd31bf7ab Mon Sep 17 00:00:00 2001 From: Guy Goldenberg Date: Fri, 11 Oct 2024 02:52:01 +0300 Subject: [PATCH 6/7] Add context to stream logger --- internal/pwlogger/logger.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/pwlogger/logger.go b/internal/pwlogger/logger.go index bac1fcc..3c81d20 100644 --- a/internal/pwlogger/logger.go +++ b/internal/pwlogger/logger.go @@ -1,6 +1,7 @@ package pwlogger import ( + "context" "log/slog" "strings" ) @@ -41,7 +42,7 @@ func (sw *SlogWriter) Write(p []byte) (n int, err error) { attrs := append(sw.cmdAttrs, slog.String("stream", sw.stream.String()), ) - sw.logger.LogAttrs(nil, slog.LevelInfo, message, attrs...) + sw.logger.LogAttrs(context.Background(), slog.LevelInfo, message, attrs...) return len(p), nil } From 36d88c0b5d08333bb1579479695bd82d0d56ca13 Mon Sep 17 00:00:00 2001 From: Guy Goldenberg Date: Fri, 11 Oct 2024 02:54:39 +0300 Subject: [PATCH 7/7] Fix formatting --- binding_call.go | 3 ++- browser_context.go | 3 ++- channel.go | 1 + har_router.go | 3 ++- page.go | 3 ++- run.go | 3 ++- transport.go | 3 ++- websocket.go | 1 + 8 files changed, 14 insertions(+), 6 deletions(-) diff --git a/binding_call.go b/binding_call.go index ef3e7b8..de61908 100644 --- a/binding_call.go +++ b/binding_call.go @@ -2,9 +2,10 @@ package playwright import ( "fmt" - "github.com/playwright-community/playwright-go/internal/pwlogger" "strings" + "github.com/playwright-community/playwright-go/internal/pwlogger" + "github.com/go-stack/stack" ) diff --git a/browser_context.go b/browser_context.go index fb5d4fe..8582957 100644 --- a/browser_context.go +++ b/browser_context.go @@ -4,13 +4,14 @@ import ( "encoding/json" "errors" "fmt" - "github.com/playwright-community/playwright-go/internal/pwlogger" "os" "regexp" "slices" "strings" "sync" + "github.com/playwright-community/playwright-go/internal/pwlogger" + "github.com/playwright-community/playwright-go/internal/safe" ) diff --git a/channel.go b/channel.go index 7989ca0..a5e39f6 100644 --- a/channel.go +++ b/channel.go @@ -2,6 +2,7 @@ package playwright import ( "encoding/json" + "github.com/playwright-community/playwright-go/internal/pwlogger" ) diff --git a/har_router.go b/har_router.go index 865270d..ebe68e9 100644 --- a/har_router.go +++ b/har_router.go @@ -2,8 +2,9 @@ package playwright import ( "errors" - "github.com/playwright-community/playwright-go/internal/pwlogger" "log/slog" + + "github.com/playwright-community/playwright-go/internal/pwlogger" ) type harRouter struct { diff --git a/page.go b/page.go index da86c77..48b1e06 100644 --- a/page.go +++ b/page.go @@ -4,11 +4,12 @@ import ( "encoding/base64" "errors" "fmt" - "github.com/playwright-community/playwright-go/internal/pwlogger" "os" "slices" "sync" + "github.com/playwright-community/playwright-go/internal/pwlogger" + "github.com/playwright-community/playwright-go/internal/safe" ) diff --git a/run.go b/run.go index 49fa5ce..05b46db 100644 --- a/run.go +++ b/run.go @@ -5,7 +5,6 @@ import ( "bytes" "errors" "fmt" - "github.com/playwright-community/playwright-go/internal/pwlogger" "io" "log/slog" "net/http" @@ -14,6 +13,8 @@ import ( "path/filepath" "runtime" "strings" + + "github.com/playwright-community/playwright-go/internal/pwlogger" ) const ( diff --git a/transport.go b/transport.go index 2c80bf9..ceb184d 100644 --- a/transport.go +++ b/transport.go @@ -5,10 +5,11 @@ import ( "encoding/binary" "errors" "fmt" - "github.com/playwright-community/playwright-go/internal/pwlogger" "io" "os" + "github.com/playwright-community/playwright-go/internal/pwlogger" + "github.com/go-jose/go-jose/v3/json" ) diff --git a/websocket.go b/websocket.go index 9404c49..ff626cf 100644 --- a/websocket.go +++ b/websocket.go @@ -3,6 +3,7 @@ package playwright import ( "encoding/base64" "errors" + "github.com/playwright-community/playwright-go/internal/pwlogger" )