Skip to content
This repository was archived by the owner on Jan 30, 2025. It is now read-only.

Commit 4e2e427

Browse files
author
Ivan Mirić
committed
Implement RemoteObject conversion to native types
Previously we used to throw an error if we encountered a custom object as returned by CDP from an `Evaluate()` or `console.log()` call. This should handle those cases properly now by converting objects to `map[string]interface{}` internally, and using a custom Logrus formatter to marshall the map to JSON for safe output when used with `console.log()`. Fixes #120
1 parent 99d1ec9 commit 4e2e427

File tree

11 files changed

+379
-123
lines changed

11 files changed

+379
-123
lines changed

common/execution_context.go

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,20 +40,25 @@ var /* const */ sourceURLRegex = regexp.MustCompile(`^(?s)[\040\t]*//[@#] source
4040
// ExecutionContext represents a JS execution context
4141
type ExecutionContext struct {
4242
ctx context.Context
43+
logger *Logger
4344
session *Session
4445
frame *Frame
4546
id runtime.ExecutionContextID
4647
injectedScript api.JSHandle
4748
}
4849

4950
// NewExecutionContext creates a new JS execution context
50-
func NewExecutionContext(ctx context.Context, session *Session, frame *Frame, id runtime.ExecutionContextID) *ExecutionContext {
51+
func NewExecutionContext(
52+
ctx context.Context, session *Session, frame *Frame,
53+
id runtime.ExecutionContextID, logger *Logger,
54+
) *ExecutionContext {
5155
return &ExecutionContext{
5256
ctx: ctx,
5357
session: session,
5458
frame: frame,
5559
id: id,
5660
injectedScript: nil,
61+
logger: logger,
5762
}
5863
}
5964

@@ -69,7 +74,7 @@ func (e *ExecutionContext) adoptBackendNodeId(backendNodeID cdp.BackendNodeID) (
6974
return nil, fmt.Errorf("unable to get DOM node: %w", err)
7075
}
7176

72-
return NewJSHandle(e.ctx, e.session, e, e.frame, remoteObj).AsElement().(*ElementHandle), nil
77+
return NewJSHandle(e.ctx, e.session, e, e.frame, remoteObj, e.logger).AsElement().(*ElementHandle), nil
7378
}
7479

7580
// Adopts the specified element handle into this execution context from another execution context
@@ -123,13 +128,13 @@ func (e *ExecutionContext) evaluate(apiCtx context.Context, forceCallable bool,
123128
}
124129
if remoteObject != nil {
125130
if returnByValue {
126-
res, err = valueFromRemoteObject(apiCtx, remoteObject)
131+
res, err = valueFromRemoteObject(apiCtx, remoteObject, e.logger.log.WithContext(e.ctx))
127132
if err != nil {
128133
return nil, fmt.Errorf("unable to extract value from remote object: %w", err)
129134
}
130135
} else if remoteObject.ObjectID != "" {
131136
// Note: we don't use the passed in apiCtx here as it could be tied to a timeout
132-
res = NewJSHandle(e.ctx, e.session, e, e.frame, remoteObject)
137+
res = NewJSHandle(e.ctx, e.session, e, e.frame, remoteObject, e.logger)
133138
}
134139
}
135140
} else {
@@ -159,13 +164,13 @@ func (e *ExecutionContext) evaluate(apiCtx context.Context, forceCallable bool,
159164
}
160165
if remoteObject != nil {
161166
if returnByValue {
162-
res, err = valueFromRemoteObject(apiCtx, remoteObject)
167+
res, err = valueFromRemoteObject(apiCtx, remoteObject, e.logger.log.WithContext(apiCtx))
163168
if err != nil {
164169
return nil, fmt.Errorf("unable to extract value from remote object: %w", err)
165170
}
166171
} else if remoteObject.ObjectID != "" {
167172
// Note: we don't use the passed in apiCtx here as it could be tied to a timeout
168-
res = NewJSHandle(e.ctx, e.session, e, e.frame, remoteObject)
173+
res = NewJSHandle(e.ctx, e.session, e, e.frame, remoteObject, e.logger)
169174
}
170175
}
171176
}

common/frame_session.go

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import (
4141
"github.com/chromedp/cdproto/security"
4242
"github.com/chromedp/cdproto/target"
4343
"github.com/grafana/xk6-browser/api"
44+
"github.com/sirupsen/logrus"
4445
k6lib "go.k6.io/k6/lib"
4546
k6stats "go.k6.io/k6/stats"
4647
)
@@ -77,10 +78,21 @@ type FrameSession struct {
7778
childSessions map[cdp.FrameID]*FrameSession
7879

7980
logger *Logger
81+
// logger that will properly serialize RemoteObject instances
82+
loggerObj *logrus.Logger
8083
}
8184

82-
func NewFrameSession(ctx context.Context, session *Session, page *Page, parent *FrameSession, targetID target.ID, logger *Logger) (*FrameSession, error) {
85+
func NewFrameSession(
86+
ctx context.Context, session *Session, page *Page, parent *FrameSession,
87+
targetID target.ID, logger *Logger,
88+
) (*FrameSession, error) {
8389
logger.Debugf("NewFrameSession", "sid:%v tid:%v", session.id, targetID)
90+
state := k6lib.GetState(ctx)
91+
loggerObj := &logrus.Logger{
92+
Out: state.Logger.Out,
93+
Level: state.Logger.Level,
94+
Formatter: &mapAsObjectFormatter{state.Logger.Formatter},
95+
}
8496
fs := FrameSession{
8597
ctx: ctx, // TODO: create cancelable context that can be used to cancel and close all child sessions
8698
session: session,
@@ -96,6 +108,7 @@ func NewFrameSession(ctx context.Context, session *Session, page *Page, parent *
96108
eventCh: make(chan Event),
97109
childSessions: make(map[cdp.FrameID]*FrameSession),
98110
logger: logger,
111+
loggerObj: loggerObj,
99112
}
100113
var err error
101114
if fs.parent != nil {
@@ -454,34 +467,39 @@ func (fs *FrameSession) navigateFrame(frame *Frame, url, referrer string) (strin
454467
}
455468

456469
func (fs *FrameSession) onConsoleAPICalled(event *runtime.EventConsoleAPICalled) {
457-
// TODO: switch to using browser logger instead of directly outputting to k6 logging system
458470
state := k6lib.GetState(fs.ctx)
459-
l := state.Logger.
471+
// TODO: switch to using browser logger instead of directly outputting to k6 logging system
472+
l := fs.loggerObj.
460473
WithTime(event.Timestamp.Time()).
461474
WithField("source", "browser-console-api")
475+
462476
if state.Group.Path != "" {
463477
l = l.WithField("group", state.Group.Path)
464478
}
465-
var convertedArgs []interface{}
466-
for _, arg := range event.Args {
467-
i, err := interfaceFromRemoteObject(arg)
479+
480+
var parsedObjects []interface{}
481+
for _, robj := range event.Args {
482+
i, err := parseRemoteObject(robj, l)
468483
if err != nil {
469-
// TODO(fix): this should not throw!
484+
// If this throws it's a bug :)
470485
k6Throw(fs.ctx, "unable to parse remote object value: %w", err)
471486
}
472-
convertedArgs = append(convertedArgs, i)
487+
parsedObjects = append(parsedObjects, i)
473488
}
489+
490+
l = l.WithField("objects", parsedObjects)
491+
474492
switch event.Type {
475493
case "log":
476-
l.Info(convertedArgs...)
494+
l.Info()
477495
case "info":
478-
l.Info(convertedArgs...)
496+
l.Info()
479497
case "warning":
480-
l.Warn(convertedArgs...)
498+
l.Warn()
481499
case "error":
482-
l.Error(convertedArgs...)
500+
l.Error()
483501
default:
484-
l.Debug(convertedArgs...)
502+
l.Debug()
485503
}
486504
}
487505

@@ -517,7 +535,7 @@ func (fs *FrameSession) onExecutionContextCreated(event *runtime.EventExecutionC
517535
if i.Type == "isolated" {
518536
fs.isolatedWorlds[event.Context.Name] = true
519537
}
520-
context := NewExecutionContext(fs.ctx, fs.session, frame, event.Context.ID)
538+
context := NewExecutionContext(fs.ctx, fs.session, frame, event.Context.ID, fs.logger)
521539
if world != "" {
522540
frame.setContext(world, context)
523541
}

0 commit comments

Comments
 (0)