Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow passing nil as res to chromedp.Evaluate() #816

Merged
merged 2 commits into from May 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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)
}
})
}
}