From 39a9dc9fc27e20f2d83bb222ca724e679ec9a08a Mon Sep 17 00:00:00 2001 From: Zeke Lu Date: Sun, 9 May 2021 01:35:57 +0800 Subject: [PATCH 1/2] allow passing nil as res to chromedp.Evaluate() 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. And it eliminates the case that "undefined" results in an error. --- eval.go | 23 +++--- eval_test.go | 206 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+), 10 deletions(-) create mode 100644 eval_test.go diff --git a/eval.go b/eval.go index 5f16c5d0..4988ad95 100644 --- a/eval.go +++ b/eval.go @@ -12,30 +12,29 @@ import ( // runtime.Evaluate. type EvaluateAction Action -// Evaluate is an action to evaluate the Javascript expression, unmarshaling +// Evaluate is an action to evaluate the Javascript expression, unmarshalling // 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) } @@ -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 diff --git a/eval_test.go b/eval_test.go new file mode 100644 index 00000000..007dfc61 --- /dev/null +++ b/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) + } + }) + } +} From d085b08d7247dd02f7cea1e290d57b49618985f1 Mon Sep 17 00:00:00 2001 From: Zeke Lu Date: Sun, 9 May 2021 09:11:33 +0800 Subject: [PATCH 2/2] revert the spelling change to unmarshaling according to https://github.com/golang/go/wiki/Spelling --- eval.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eval.go b/eval.go index 4988ad95..0a6fbed5 100644 --- a/eval.go +++ b/eval.go @@ -12,7 +12,7 @@ import ( // runtime.Evaluate. type EvaluateAction Action -// Evaluate is an action to evaluate the Javascript expression, unmarshalling +// 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 **runtime.RemoteObject,