Skip to content

Commit

Permalink
allow passing nil as res to chromedp.Evaluate() (#816)
Browse files Browse the repository at this point in the history
Sometimes, users just want to call a js function (for example,
window.scrollTo(0, 0)), and don't care about the return value.
So it's not necessary to force res to not be nil.
It also updates the doc comment to describe that in some cases,
"undefined" will result in an error.
  • Loading branch information
ZekeLu committed May 10, 2021
1 parent 538d83f commit 864094d
Show file tree
Hide file tree
Showing 2 changed files with 218 additions and 9 deletions.
21 changes: 12 additions & 9 deletions eval.go
Expand Up @@ -15,27 +15,26 @@ type EvaluateAction Action
// Evaluate is an action to evaluate the Javascript expression, unmarshaling
// the result of the script evaluation to res.
//
// When res is a type other than *[]byte, or **chromedp/cdproto/runtime.RemoteObject,
// When res is a type other than *[]byte, or **runtime.RemoteObject,
// then the result of the script evaluation will be returned "by value" (ie,
// JSON-encoded), and subsequently an attempt will be made to json.Unmarshal
// the script result to res.
// the script result to res. It returns an error if the script result is
// "undefined" in this case.
//
// Otherwise, when res is a *[]byte, the raw JSON-encoded value of the script
// result will be placed in res. Similarly, if res is a *runtime.RemoteObject,
// result will be placed in res. Similarly, if res is a **runtime.RemoteObject,
// then res will be set to the low-level protocol type, and no attempt will be
// made to convert the result.
// made to convert the result. "undefined" is okay in this case.
//
// When res is nil, the script result will be ignored (including "undefined").
//
// Note: any exception encountered will be returned as an error.
func Evaluate(expression string, res interface{}, opts ...EvaluateOption) EvaluateAction {
if res == nil {
panic("res cannot be nil")
}

return ActionFunc(func(ctx context.Context) error {
// set up parameters
p := runtime.Evaluate(expression)
switch res.(type) {
case **runtime.RemoteObject:
case nil, **runtime.RemoteObject:
default:
p = p.WithReturnByValue(true)
}
Expand All @@ -54,6 +53,10 @@ func Evaluate(expression string, res interface{}, opts ...EvaluateOption) Evalua
return exp
}

if res == nil {
return nil
}

switch x := res.(type) {
case **runtime.RemoteObject:
*x = v
Expand Down
206 changes: 206 additions & 0 deletions eval_test.go
@@ -0,0 +1,206 @@
package chromedp

import (
"reflect"
"testing"

"github.com/chromedp/cdproto/runtime"
)

func TestEvaluateNumber(t *testing.T) {
t.Parallel()

tests := []struct {
name string
expression string
res int
want int
wantErr string
}{
{
name: "normal",
expression: "123",
want: 123,
},
{
name: "undefined",
expression: "",
wantErr: "encountered an undefined value",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx, cancel := testAllocate(t, "")
defer cancel()

err := Run(ctx,
Evaluate(tt.expression, &tt.res),
)
if tt.wantErr == "" && err != nil {
t.Fatalf("got error: %v", err)
}
if tt.wantErr != "" && (err == nil || tt.wantErr != err.Error()) {
t.Fatalf("wanted error: %q, got: %q", tt.wantErr, err)
} else if tt.res != tt.want {
t.Fatalf("want: %v, got: %v", tt.want, tt.res)
}
})
}
}

func TestEvaluateString(t *testing.T) {
t.Parallel()

tests := []struct {
name string
expression string
res string
want string
wantErr string
}{
{
name: "normal",
expression: "'str'",
want: "str",
},
{
name: "undefined",
expression: "",
wantErr: "encountered an undefined value",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx, cancel := testAllocate(t, "")
defer cancel()

err := Run(ctx,
Evaluate(tt.expression, &tt.res),
)
if tt.wantErr == "" && err != nil {
t.Fatalf("got error: %v", err)
}
if tt.wantErr != "" && (err == nil || tt.wantErr != err.Error()) {
t.Fatalf("wanted error: %q, got: %q", tt.wantErr, err)
} else if tt.res != tt.want {
t.Fatalf("want: %v, got: %v", tt.want, tt.res)
}
})
}
}

func TestEvaluateBytes(t *testing.T) {
t.Parallel()

tests := []struct {
name string
expression string
res []byte
want []byte
}{
{
name: "normal",
expression: "'bytes'",
want: []byte(`"bytes"`),
},
{
name: "undefined",
expression: "",
want: []byte(nil),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx, cancel := testAllocate(t, "")
defer cancel()

err := Run(ctx,
Evaluate(tt.expression, &tt.res),
)
if err != nil {
t.Fatalf("got error: %v", err)
}
if !reflect.DeepEqual(tt.res, tt.want) {
t.Fatalf("want: %v, got: %v", tt.want, tt.res)
}
})
}
}

func TestEvaluateRemoteObject(t *testing.T) {
t.Parallel()

tests := []struct {
name string
expression string
res *runtime.RemoteObject
wantType string
}{
{
name: "object",
expression: "window",
wantType: "object",
},
{
name: "function",
expression: "window.alert",
wantType: "function",
},
{
name: "undefined",
expression: "",
wantType: "undefined",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx, cancel := testAllocate(t, "")
defer cancel()

err := Run(ctx,
Evaluate(tt.expression, &tt.res),
)
if err != nil {
t.Fatalf("got error: %v", err)
}
if string(tt.res.Type) != tt.wantType {
t.Fatalf("want type: %v, got type: %v", tt.wantType, tt.res.Type)
}
})
}
}

func TestEvaluateNil(t *testing.T) {
t.Parallel()

tests := []struct {
name string
expression string
}{
{
name: "number",
expression: "123",
},
{
name: "string",
expression: "'str'",
},
{
name: "undefined",
expression: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx, cancel := testAllocate(t, "")
defer cancel()

err := Run(ctx,
Evaluate(tt.expression, nil),
)
if err != nil {
t.Fatalf("got error: %v", err)
}
})
}
}

0 comments on commit 864094d

Please sign in to comment.