diff --git a/browser_test.go b/browser_test.go index 096a8b03..7ea0faf5 100644 --- a/browser_test.go +++ b/browser_test.go @@ -163,7 +163,7 @@ func (t T) BrowserWaitEvent() { func (t T) BrowserCrash() { browser := rod.New().Context(t.Context()).MustConnect() page := browser.MustPage() - js := `new Promise(r => setTimeout(r, 10000))` + js := `() => new Promise(r => setTimeout(r, 10000))` go t.Panic(func() { page.MustEval(js) diff --git a/dev_helpers_test.go b/dev_helpers_test.go index 935b45d1..43ae8cc4 100644 --- a/dev_helpers_test.go +++ b/dev_helpers_test.go @@ -93,5 +93,5 @@ func (t T) ExposeHelpers() { p := t.newPage(t.srcFile("fixtures/click.html")) p.ExposeHelpers(js.ElementR) - t.Eq(p.MustElementByJS(`rod.elementR('button', 'click me')`).MustText(), "click me") + t.Eq(p.MustElementByJS(`() => rod.elementR('button', 'click me')`).MustText(), "click me") } diff --git a/element.go b/element.go index b270818c..f2b004e9 100644 --- a/element.go +++ b/element.go @@ -58,7 +58,7 @@ func (el *Element) Focus() error { return err } - _, err = el.Evaluate(Eval(`this.focus()`).ByUser()) + _, err = el.Evaluate(Eval(`() => this.focus()`).ByUser()) return err } @@ -143,7 +143,7 @@ func (el *Element) Tap() error { // The cursor can be mouse, finger, stylus, etc. // If not interactable err will be ErrNotInteractable, such as when covered by a modal, func (el *Element) Interactable() (pt *proto.Point, err error) { - noPointerEvents, err := el.Eval(`getComputedStyle(this).pointerEvents === 'none'`) + noPointerEvents, err := el.Eval(`() => getComputedStyle(this).pointerEvents === 'none'`) if err != nil { return nil, err } @@ -167,7 +167,7 @@ func (el *Element) Interactable() (pt *proto.Point, err error) { return } - scroll, err := el.page.root.Eval(`{ x: window.scrollX, y: window.scrollY }`) + scroll, err := el.page.root.Eval(`() => ({ x: window.scrollX, y: window.scrollY })`) if err != nil { return } @@ -302,7 +302,7 @@ func (el *Element) InputTime(t time.Time) error { // Blur is similar to the method Blur func (el *Element) Blur() error { - _, err := el.Evaluate(Eval("this.blur()").ByUser()) + _, err := el.Evaluate(Eval("() => this.blur()").ByUser()) return err } @@ -591,14 +591,14 @@ func (el *Element) WaitVisible() error { // Doc for readonly: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/readonly func (el *Element) WaitEnabled() error { defer el.tryTrace(TraceTypeWait, "enabled")() - return el.Wait(Eval(`!this.disabled`)) + return el.Wait(Eval(`() => !this.disabled`)) } // WaitWritable until the element is not readonly. // Doc for disabled: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/disabled func (el *Element) WaitWritable() error { defer el.tryTrace(TraceTypeWait, "writable")() - return el.Wait(Eval(`!this.readonly`)) + return el.Wait(Eval(`() => !this.readonly`)) } // WaitInvisible until the element invisible @@ -633,7 +633,7 @@ func (el *Element) Resource() ([]byte, error) { // BackgroundImage returns the css background-image of the element func (el *Element) BackgroundImage() ([]byte, error) { - res, err := el.Eval(`window.getComputedStyle(this).backgroundImage.replace(/^url\("/, '').replace(/"\)$/, '')`) + res, err := el.Eval(`() => window.getComputedStyle(this).backgroundImage.replace(/^url\("/, '').replace(/"\)$/, '')`) if err != nil { return nil, err } @@ -684,7 +684,7 @@ func (el *Element) Release() error { // Remove the element from the page func (el *Element) Remove() error { - _, err := el.Eval(`this.remove()`) + _, err := el.Eval(`() => this.remove()`) if err != nil { return err } @@ -696,9 +696,9 @@ func (el *Element) Call(ctx context.Context, sessionID, methodName string, param return el.page.Call(ctx, sessionID, methodName, params) } -// Eval js on the page. For more info check the Element.Evaluate +// Eval is a shortcut for Element.Evaluate with AwaitPromise, ByValue and AutoExp set to true. func (el *Element) Eval(js string, params ...interface{}) (*proto.RuntimeRemoteObject, error) { - return el.Evaluate(Eval(js, params...)) + return el.Evaluate(Eval(js, params...).ByPromise()) } // Evaluate is just a shortcut of Page.Evaluate with This set to current element. diff --git a/element_test.go b/element_test.go index 18429596..e860f882 100644 --- a/element_test.go +++ b/element_test.go @@ -177,9 +177,9 @@ func (t T) WaitInteractable() { func (t T) Hover() { p := t.page.MustNavigate(t.srcFile("fixtures/click.html")) el := p.MustElement("button") - el.MustEval(`this.onmouseenter = () => this.dataset['a'] = 1`) + el.MustEval(`() => this.onmouseenter = () => this.dataset['a'] = 1`) el.MustHover() - t.Eq("1", el.MustEval(`this.dataset['a']`).String()) + t.Eq("1", el.MustEval(`() => this.dataset['a']`).String()) t.mc.stubErr(1, proto.DOMScrollIntoViewIfNeeded{}) t.Err(el.Hover()) @@ -197,7 +197,7 @@ func (t T) Hover() { func (t T) ElementMoveMouseOut() { p := t.page.MustNavigate(t.srcFile("fixtures/click.html")) btn := p.MustElement("button") - btn.MustEval(`this.onmouseout = () => this.setAttribute('name', 'mouse moved.')`) + btn.MustEval(`() => this.onmouseout = () => this.setAttribute('name', 'mouse moved.')`) t.Eq("mouse moved.", *btn.MustHover().MustMoveMouseOut().MustAttribute("name")) t.mc.stubErr(1, proto.DOMGetContentQuads{}) @@ -227,7 +227,7 @@ func (t T) Iframes() { el := frame02.MustElement("button") el.MustClick() - t.Eq(frame01.MustEval(`testIsolation()`).Str(), "ok") + t.Eq(frame01.MustEval(`() => testIsolation()`).Str(), "ok") t.True(frame02.MustHas("[a=ok]")) } @@ -429,7 +429,7 @@ func (t T) SelectQuery() { err := el.Select([]string{`[value="c"]`}, true, rod.SelectorTypeCSSSector) t.E(err) - t.Eq(2, el.MustEval("this.selectedIndex").Int()) + t.Eq(2, el.MustEval("() => this.selectedIndex").Int()) } func (t T) SelectOptions() { @@ -520,7 +520,7 @@ func (t T) SetFiles() { slash("fixtures/alert.html"), ) - list := el.MustEval("Array.from(this.files).map(f => f.name)").Arr() + list := el.MustEval("() => Array.from(this.files).map(f => f.name)").Arr() t.Len(list, 2) t.Eq("alert.html", list[1].String()) } @@ -544,9 +544,9 @@ func (t T) WaitInvisible() { go func() { utils.Sleep(0.03) - h4.MustEval(`this.remove()`) + h4.MustEval(`() => this.remove()`) utils.Sleep(0.03) - btn.MustEval(`this.style.visibility = 'hidden'`) + btn.MustEval(`() => this.style.visibility = 'hidden'`) }() h4.MustWaitInvisible() @@ -568,7 +568,7 @@ func (t T) WaitWritable() { func (t T) WaitStable() { p := t.page.MustNavigate(t.srcFile("fixtures/wait-stable.html")) el := p.MustElement("button") - el.MustEval(`this.classList.add("play")`) + el.MustEval(`() => this.classList.add("play")`) start := time.Now() el.MustWaitStable() t.Gt(time.Since(start), time.Second) @@ -596,7 +596,7 @@ func (t T) WaitStable() { func (t T) WaitStableRAP() { p := t.page.MustNavigate(t.srcFile("fixtures/wait-stable.html")) el := p.MustElement("button") - el.MustEval(`this.classList.add("play")`) + el.MustEval(`() => this.classList.add("play")`) start := time.Now() t.E(el.WaitStableRAF()) t.Gt(time.Since(start), time.Second) @@ -723,7 +723,7 @@ func (t T) FnErr() { t.True(errors.As(err, &e)) t.Eq(proto.RuntimeRemoteObjectSubtypeError, e.Exception.Subtype) - _, err = el.ElementByJS(rod.Eval("foo()")) + _, err = el.ElementByJS(rod.Eval("() => foo()")) t.Err(err) t.Has(err.Error(), "ReferenceError: foo is not defined") t.True(errors.Is(err, &rod.ErrEval{})) @@ -749,9 +749,9 @@ func (t T) ElementOthers() { el.MustScrollIntoView() t.Eq("submit", el.MustElement("[type=submit]").MustText()) t.Eq("", el.MustElement("[type=submit]").MustHTML()) - el.MustWait(`true`) - t.Eq("form", el.MustElementByJS(`this`).MustDescribe().LocalName) - t.Len(el.MustElementsByJS(`[]`), 0) + el.MustWait(`() => true`) + t.Eq("form", el.MustElementByJS(`() => this`).MustDescribe().LocalName) + t.Len(el.MustElementsByJS(`() => []`), 0) } func (t T) ElementEqual() { diff --git a/examples_test.go b/examples_test.go index 12357f5f..8a6a85d5 100644 --- a/examples_test.go +++ b/examples_test.go @@ -43,13 +43,13 @@ func Example() { fmt.Println("Found", len(page.MustElements("input")), "input elements") // Eval js on the page - page.MustEval(`console.log("hello world")`) + page.MustEval(`() => console.log("hello world")`) // Pass parameters as json objects to the js function. This MustEval will result 3 fmt.Println("1 + 2 =", page.MustEval(`(a, b) => a + b`, 1, 2).Int()) // When eval on an element, "this" in the js is the current DOM element. - fmt.Println(page.MustElement("title").MustEval(`this.innerText`).String()) + fmt.Println(page.MustElement("title").MustEval(`() => this.innerText`).String()) // Output: // Git is the most widely used version control system. @@ -433,7 +433,7 @@ func Example_handle_events() { } } - page.MustEval(`console.log("hello", "world")`) + page.MustEval(`() => console.log("hello", "world")`) <-done @@ -483,7 +483,7 @@ func Example_hijack_requests() { go router.Run() - browser.MustPage("https://go-rod.github.io").MustWait(`document.title === 'hi'`) + browser.MustPage("https://go-rod.github.io").MustWait(`() => document.title === 'hi'`) fmt.Println("done") @@ -494,7 +494,7 @@ func Example_hijack_requests() { func Example_eval_reuse_remote_object() { page := rod.New().MustConnect().MustPage() - fn := page.MustEvaluate(rod.Eval(`Math.random`).ByObject()) + fn := page.MustEvaluate(rod.Eval(`() => Math.random`).ByObject()) res := page.MustEval(`f => f()`, fn) @@ -580,7 +580,7 @@ func Example_load_extension() { page := rod.New().ControlURL(u).MustConnect().MustPage("http://mdn.dev") - page.MustWait(`document.title === 'test-extension'`) + page.MustWait(`() => document.title === 'test-extension'`) fmt.Println("ok") diff --git a/hijack_test.go b/hijack_test.go index b014475f..6292ede1 100644 --- a/hijack_test.go +++ b/hijack_test.go @@ -213,7 +213,7 @@ func (t T) HijackFailRequest() { t.page.MustNavigate(s.URL("/page")).MustWaitLoad() - t.page.MustWait(`document.title === 'Failed to fetch'`) + t.page.MustWait(`() => document.title === 'Failed to fetch'`) { // test error log t.mc.stub(1, proto.FetchFailRequest{}, func(send StubSend) (gson.JSON, error) { diff --git a/page.go b/page.go index 368229ac..b73437bb 100644 --- a/page.go +++ b/page.go @@ -173,14 +173,14 @@ func (p *Page) Navigate(url string) error { // NavigateBack history. func (p *Page) NavigateBack() error { // Not using cdp API because it doesn't work for iframe - _, err := p.Evaluate(Eval(`history.back()`).ByUser()) + _, err := p.Evaluate(Eval(`() => history.back()`).ByUser()) return err } // NavigateForward history. func (p *Page) NavigateForward() error { // Not using cdp API because it doesn't work for iframe - _, err := p.Evaluate(Eval(`history.forward()`).ByUser()) + _, err := p.Evaluate(Eval(`() => history.forward()`).ByUser()) return err } @@ -194,7 +194,7 @@ func (p *Page) Reload() error { }) // Not using cdp API because it doesn't work for iframe - _, err := p.Evaluate(Eval(`location.reload()`).ByUser()) + _, err := p.Evaluate(Eval(`() => location.reload()`).ByUser()) if err != nil { return err } @@ -542,7 +542,7 @@ func (p *Page) WaitIdle(timeout time.Duration) (err error) { // Doc: https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame func (p *Page) WaitRepaint() error { // we use root here because iframe doesn't trigger requestAnimationFrame - _, err := p.root.Eval(`new Promise(r => requestAnimationFrame(r))`) + _, err := p.root.Eval(`() => new Promise(r => requestAnimationFrame(r))`) return err } diff --git a/page_eval.go b/page_eval.go index 5d3900ea..b6ed4492 100644 --- a/page_eval.go +++ b/page_eval.go @@ -26,20 +26,15 @@ type EvalOptions struct { // ThisObj represents the "this" object in the JS ThisObj *proto.RuntimeRemoteObject - // JS code to eval. It can be an expression or function definition. If it's a function definition - // the function will be executed with the JSArgs. Such as - // 1 + 2 - // is the same as - // () => 1 + 2 - // or - // function() { - // return 1 + 2 - // } + // JS function definition to execute. JS string - // JSArgs represents the arguments in the JS if the JS is a function definition. + // JSArgs represents the arguments that will be passed to JS. // If an argument is *proto.RuntimeRemoteObject type, the corresponding remote object will be used. // Or it will be passed as a plain JSON value. + // When an arg in the args is a *js.Function, the arg will be cached on the page's js context. + // When the arg.Name exists in the page's cache, it reuse the cache without sending the definition to the browser again. + // Useful when you need to eval a huge js expression many times. JSArgs []interface{} // Whether execution should be treated as initiated by user in the UI. @@ -47,10 +42,6 @@ type EvalOptions struct { } // Eval creates a EvalOptions with ByValue set to true. -// -// When an arg in the args is a *js.Function, the arg will be cached on the page's js context. -// When the arg.Name exists in the page's cache, it reuse the cache without sending the definition to the browser again. -// Useful when you need to eval a huge js expression many times. func Eval(js string, args ...interface{}) *EvalOptions { return &EvalOptions{ ByValue: true, @@ -66,7 +57,7 @@ func evalHelper(fn *js.Function, args ...interface{}) *EvalOptions { return &EvalOptions{ ByValue: true, JSArgs: append([]interface{}{fn}, args...), - JS: `(f, ...args) => f.apply(this, args)`, + JS: `function (f, ...args) { return f.apply(this, args) }`, } } @@ -119,15 +110,12 @@ func (e *EvalOptions) ByPromise() *EvalOptions { func (e *EvalOptions) formatToJSFunc() string { js := strings.TrimSpace(e.JS) - if detectJSFunction(js) { - return fmt.Sprintf(`function() { return (%s).apply(this, arguments) }`, js) - } - return fmt.Sprintf(`function() { return %s }`, js) + return fmt.Sprintf(`function() { return (%s).apply(this, arguments) }`, js) } -// Eval is just a shortcut for Page.Evaluate with AwaitPromise set true. -func (p *Page) Eval(js string, jsArgs ...interface{}) (*proto.RuntimeRemoteObject, error) { - return p.Evaluate(Eval(js, jsArgs...).ByPromise()) +// Eval is a shortcut for Page.Evaluate with AwaitPromise, ByValue set to true. +func (p *Page) Eval(js string, args ...interface{}) (*proto.RuntimeRemoteObject, error) { + return p.Evaluate(Eval(js, args...).ByPromise()) } // Evaluate js on the page. @@ -202,13 +190,12 @@ func (p *Page) Expose(name string, fn func(gson.JSON) (interface{}, error)) (sto return } - code := fmt.Sprintf(`(%s)("%s", "%s")`, js.ExposeFunc.Definition, name, bind) - - _, err = p.Evaluate(Eval(code)) + _, err = p.Evaluate(Eval(js.ExposeFunc.Definition, name, bind)) if err != nil { return } + code := fmt.Sprintf(`(%s)("%s", "%s")`, js.ExposeFunc.Definition, name, bind) remove, err := p.EvalOnNewDocument(code) if err != nil { return diff --git a/page_eval_test.go b/page_eval_test.go index 18ac4319..2c7bbfb5 100644 --- a/page_eval_test.go +++ b/page_eval_test.go @@ -18,7 +18,7 @@ func (t T) PageEvalOnNewDocument() { // to activate the script p.MustNavigate(t.blank()) - t.Eq(p.MustEval("rod").String(), "ok") + t.Eq(p.MustEval("() => rod").String(), "ok") t.Panic(func() { t.mc.stubErr(1, proto.PageAddScriptToEvaluateOnNewDocument{}) @@ -32,19 +32,13 @@ func (t T) PageEval() { t.Eq(3, page.MustEval(` (a, b) => a + b `, 1, 2).Int()) - t.Eq(10, page.MustEval(` - 10 - `).Int()) - t.Eq(1, page.MustEval(`a => 1`).Int()) - t.Eq(1, page.MustEval(`function() { return 1 }`).Int()) - t.Eq(1, page.MustEval(`((1))`).Int()) - t.Neq(1, page.MustEval(`a = () => 1`).Int()) - t.Neq(1, page.MustEval(`a = function() { return 1 }`)) - t.Neq(1, page.MustEval(`/* ) */`)) // reuse obj obj := page.MustEvaluate(rod.Eval(`() => () => 'ok'`).ByObject()) t.Eq("ok", page.MustEval(`f => f()`, obj).Str()) + + _, err := page.Eval(`10`) + t.Has(err.Error(), `eval js error: TypeError: 10.apply is not a function`) } func (t T) PageEvaluateRetry() { @@ -56,7 +50,7 @@ func (t T) PageEvaluateRetry() { }) return gson.New(nil), cdp.ErrCtxNotFound }) - t.Eq(1, page.MustEval(`1`).Int()) + t.Eq(1, page.MustEval(`() => 1`).Int()) } func (t T) PageUpdateJSCtxIDErr() { @@ -66,7 +60,7 @@ func (t T) PageUpdateJSCtxIDErr() { t.mc.stubErr(1, proto.RuntimeEvaluate{}) return gson.New(nil), cdp.ErrCtxNotFound }) - t.Err(page.Eval(`1`)) + t.Err(page.Eval(`() => 1`)) frame := page.MustElement("iframe").MustFrame() @@ -87,16 +81,16 @@ func (t T) PageExpose() { }) utils.All(func() { - res := page.MustEval(`exposedFunc({k: 'a'})`) + res := page.MustEval(`() => exposedFunc({k: 'a'})`) t.Eq("a", res.Str()) }, func() { - res := page.MustEval(`exposedFunc({k: 'b'})`) + res := page.MustEval(`() => exposedFunc({k: 'b'})`) t.Eq("b", res.Str()) })() // survive the reload page.MustReload().MustWaitLoad() - res := page.MustEval(`exposedFunc({k: 'ok'})`) + res := page.MustEval(`() => exposedFunc({k: 'ok'})`) t.Eq("ok", res.Str()) stop() @@ -105,7 +99,7 @@ func (t T) PageExpose() { stop() }) t.Panic(func() { - page.MustReload().MustWaitLoad().MustEval(`exposedFunc()`) + page.MustReload().MustWaitLoad().MustEval(`() => exposedFunc()`) }) t.Panic(func() { t.mc.stubErr(1, proto.RuntimeCallFunctionOn{}) @@ -122,7 +116,7 @@ func (t T) PageExpose() { } func (t T) Release() { - res, err := t.page.Evaluate(rod.Eval(`document`).ByObject()) + res, err := t.page.Evaluate(rod.Eval(`() => document`).ByObject()) t.E(err) t.page.MustRelease(res) } @@ -136,7 +130,7 @@ func (t T) PromiseLeak() { p := t.page.MustNavigate(t.blank()) utils.All(func() { - _, err := p.Eval(`new Promise(r => setTimeout(() => r(location.href), 1000))`) + _, err := p.Eval(`() => new Promise(r => setTimeout(() => r(location.href), 1000))`) t.Is(err, cdp.ErrCtxDestroyed) }, func() { utils.Sleep(0.3) @@ -151,7 +145,7 @@ func (t T) ObjectLeak() { p := t.page.MustNavigate(t.blank()) - obj := p.MustEvaluate(rod.Eval("{a:1}").ByObject()) + obj := p.MustEvaluate(rod.Eval("() => ({a:1})").ByObject()) p.MustReload().MustWaitLoad() t.Panic(func() { p.MustEvaluate(rod.Eval(`obj => obj`, obj)) @@ -189,9 +183,9 @@ func (t T) ConcurrentEval() { start := time.Now() utils.All(func() { - list <- p.MustEval(`new Promise(r => setTimeout(r, 1000, 2))`).Int() + list <- p.MustEval(`() => new Promise(r => setTimeout(r, 1000, 2))`).Int() }, func() { - list <- p.MustEval(`new Promise(r => setTimeout(r, 500, 1))`).Int() + list <- p.MustEval(`() => new Promise(r => setTimeout(r, 500, 1))`).Int() })() duration := time.Since(start) @@ -220,11 +214,11 @@ func (t T) PageIframeReload() { func (t T) PageObjCrossNavigation() { p := t.page.MustNavigate(t.blank()) - obj := p.MustEvaluate(rod.Eval(`{}`).ByObject()) + obj := p.MustEvaluate(rod.Eval(`() => ({})`).ByObject()) t.page.MustNavigate(t.blank()) - _, err := p.Evaluate(rod.Eval(`1`).This(obj)) + _, err := p.Evaluate(rod.Eval(`() => 1`).This(obj)) t.Is(err, &rod.ErrObjectNotFound{}) t.Has(err.Error(), "cannot find object: {\"type\":\"object\"") } @@ -240,5 +234,5 @@ func (t T) EvalOptionsString() { p := t.page.MustNavigate(t.srcFile("fixtures/click.html")) el := p.MustElement("button") - t.Eq(rod.Eval(`this.parentElement`).This(el.Object).String(), "this.parentElement() button") + t.Eq(rod.Eval(`() => this.parentElement`).This(el.Object).String(), "() => this.parentElement() button") } diff --git a/page_test.go b/page_test.go index f30f06be..858a6f64 100644 --- a/page_test.go +++ b/page_test.go @@ -146,7 +146,7 @@ func (t T) PageCloseCancel() { }() t.Eq(page.Close().Error(), "page close canceled") - page.MustEval(`window.onbeforeunload = null`) + page.MustEval(`() => window.onbeforeunload = null`) page.MustClose() } @@ -159,7 +159,7 @@ func (t T) DisableDomain() { } func (t T) PageContext() { - t.page.Timeout(time.Hour).CancelTimeout().MustEval(`1`) + t.page.Timeout(time.Hour).CancelTimeout().MustEval(`() => 1`) } func (t T) PageActivate() { @@ -186,8 +186,8 @@ func (t T) Window() { page.MustWindowMinimize() page.MustWindowNormal() page.MustSetWindow(0, 0, 1211, 611) - t.Eq(1211, page.MustEval(`window.innerWidth`).Int()) - t.Eq(611, page.MustEval(`window.innerHeight`).Int()) + t.Eq(1211, page.MustEval(`() => window.innerWidth`).Int()) + t.Eq(611, page.MustEval(`() => window.innerHeight`).Int()) t.Panic(func() { t.mc.stubErr(1, proto.BrowserGetWindowForTarget{}) @@ -206,12 +206,12 @@ func (t T) Window() { func (t T) SetViewport() { page := t.newPage(t.blank()) page.MustSetViewport(317, 419, 0, false) - res := page.MustEval(`[window.innerWidth, window.innerHeight]`) + res := page.MustEval(`() => [window.innerWidth, window.innerHeight]`) t.Eq(317, res.Get("0").Int()) t.Eq(419, res.Get("1").Int()) page2 := t.newPage(t.blank()) - res = page2.MustEval(`[window.innerWidth, window.innerHeight]`) + res = page2.MustEval(`() => [window.innerWidth, window.innerHeight]`) t.Neq(int(317), res.Get("0").Int()) } @@ -271,7 +271,7 @@ func (t T) SetDocumentContent() { func (t T) EmulateDevice() { page := t.newPage(t.blank()) page.MustEmulate(devices.IPhone6or7or8) - res := page.MustEval(`[window.innerWidth, window.innerHeight, navigator.userAgent]`) + res := page.MustEval(`() => [window.innerWidth, window.innerHeight, navigator.userAgent]`) // TODO: this seems like a bug of chromium { @@ -304,14 +304,14 @@ func (t T) PageCloseErr() { func (t T) PageAddScriptTag() { p := t.page.MustNavigate(t.blank()).MustWaitLoad() - res := p.MustAddScriptTag(t.srcFile("fixtures/add-script-tag.js")).MustEval(`count()`) + res := p.MustAddScriptTag(t.srcFile("fixtures/add-script-tag.js")).MustEval(`() => count()`) t.Eq(0, res.Int()) - res = p.MustAddScriptTag(t.srcFile("fixtures/add-script-tag.js")).MustEval(`count()`) + res = p.MustAddScriptTag(t.srcFile("fixtures/add-script-tag.js")).MustEval(`() => count()`) t.Eq(1, res.Int()) t.E(p.AddScriptTag("", `let ok = 'yes'`)) - res = p.MustEval(`ok`) + res = p.MustEval(`() => ok`) t.Eq("yes", res.String()) } @@ -319,14 +319,14 @@ func (t T) PageAddStyleTag() { p := t.page.MustNavigate(t.srcFile("fixtures/click.html")).MustWaitLoad() res := p.MustAddStyleTag(t.srcFile("fixtures/add-style-tag.css")). - MustElement("h4").MustEval(`getComputedStyle(this).color`) + MustElement("h4").MustEval(`() => getComputedStyle(this).color`) t.Eq("rgb(255, 0, 0)", res.String()) p.MustAddStyleTag(t.srcFile("fixtures/add-style-tag.css")) t.Len(p.MustElements("link"), 1) t.E(p.AddStyleTag("", "h4 { color: green; }")) - res = p.MustElement("h4").MustEval(`getComputedStyle(this).color`) + res = p.MustElement("h4").MustEval(`() => getComputedStyle(this).color`) t.Eq("rgb(0, 128, 0)", res.String()) } @@ -340,12 +340,12 @@ func (t T) PageWaitOpen() { newPage := wait() defer newPage.MustClose() - t.Eq("new page", newPage.MustEval("window.a").String()) + t.Eq("new page", newPage.MustEval("() => window.a").String()) } func (t T) PageWait() { page := t.page.MustNavigate(t.srcFile("fixtures/click.html")) - page.MustWait(`document.querySelector('button') !== null`) + page.MustWait(`() => document.querySelector('button') !== null`) t.Panic(func() { t.mc.stubErr(1, proto.RuntimeCallFunctionOn{}) @@ -561,7 +561,7 @@ func (t T) MouseDrag() { mouse.MustUp("left") utils.Sleep(0.3) - t.Eq(page.MustEval(`dragTrack`).Str(), " move 3 3 down 3 3 move 22 28 move 41 54 move 60 80 up 60 80") + t.Eq(page.MustEval(`() => dragTrack`).Str(), " move 3 3 down 3 3 move 22 28 move 41 54 move 60 80 up 60 80") } func (t T) NativeDrag(got.Skip) { // devtools doesn't support to use mouse event to simulate it for now @@ -601,7 +601,7 @@ func (t T) Touch() { p.MoveTo(50, 60) touch.MustMove(p).MustCancel() - page.MustWait(`touchTrack == ' start 10 20 end start 30 40 end start 30 40 move 50 60 cancel'`) + page.MustWait(`() => touchTrack == ' start 10 20 end start 30 40 end start 30 40 move 50 60 cancel'`) t.Panic(func() { t.mc.stubErr(1, proto.InputDispatchTouchEvent{}) @@ -635,12 +635,12 @@ func (t T) ScreenshotFullPage() { data := p.MustScreenshotFullPage() img, err := png.Decode(bytes.NewBuffer(data)) t.E(err) - res := p.MustEval(`({w: document.documentElement.scrollWidth, h: document.documentElement.scrollHeight})`) + res := p.MustEval(`() => ({w: document.documentElement.scrollWidth, h: document.documentElement.scrollHeight})`) t.Eq(res.Get("w").Int(), img.Bounds().Dx()) t.Eq(res.Get("h").Int(), img.Bounds().Dy()) // after the full page screenshot the window size should be the same as before - res = p.MustEval(`({w: innerWidth, h: innerHeight})`) + res = p.MustEval(`() => ({w: innerWidth, h: innerHeight})`) t.Eq(1280, res.Get("w").Int()) t.Eq(800, res.Get("h").Int()) @@ -707,14 +707,14 @@ func (t T) PageScroll() { p.Mouse.MustScroll(100, 190) t.E(p.Mouse.Scroll(200, 300, 5)) - p.MustWait(`pageXOffset > 200 && pageYOffset > 300`) + p.MustWait(`() => pageXOffset > 200 && pageYOffset > 300`) } func (t T) PageConsoleLog() { p := t.newPage(t.blank()).MustWaitLoad() e := &proto.RuntimeConsoleAPICalled{} wait := p.WaitEvent(e) - p.MustEval(`console.log(1, {b: ['test']})`) + p.MustEval(`() => console.log(1, {b: ['test']})`) wait() t.Eq("test", p.MustObjectToJSON(e.Args[1]).Get("b.0").String()) t.Eq(`1 map[b:[test]]`, p.MustObjectsToJSON(e.Args).Join(" ")) diff --git a/query.go b/query.go index e6bc9fb8..07da9dea 100644 --- a/query.go +++ b/query.go @@ -89,7 +89,7 @@ func (ps Pages) Find(selector string) (*Page, error) { // FindByURL returns the page that has the url that matches the jsRegex func (ps Pages) FindByURL(jsRegex string) (*Page, error) { for _, page := range ps { - res, err := page.Eval(`location.href`) + res, err := page.Eval(`() => location.href`) if err != nil { return nil, err } @@ -493,7 +493,7 @@ func (el *Element) ElementByJS(opts *EvalOptions) (*Element, error) { // Parent returns the parent element in the DOM tree func (el *Element) Parent() (*Element, error) { - return el.ElementByJS(Eval(`this.parentElement`)) + return el.ElementByJS(Eval(`() => this.parentElement`)) } // Parents that match the selector @@ -503,12 +503,12 @@ func (el *Element) Parents(selector string) (Elements, error) { // Next returns the next sibling element in the DOM tree func (el *Element) Next() (*Element, error) { - return el.ElementByJS(Eval(`this.nextElementSibling`)) + return el.ElementByJS(Eval(`() => this.nextElementSibling`)) } // Previous returns the previous sibling element in the DOM tree func (el *Element) Previous() (*Element, error) { - return el.ElementByJS(Eval(`this.previousElementSibling`)) + return el.ElementByJS(Eval(`() => this.previousElementSibling`)) } // Elements returns all elements that match the css selector diff --git a/query_test.go b/query_test.go index ad3fa28e..ec0dcfb1 100644 --- a/query_test.go +++ b/query_test.go @@ -172,9 +172,9 @@ func (t T) PageRace() { p.Race().ElementR("button", "02").MustHandle(func(e *rod.Element) { t.Eq("02", e.MustText()) }).MustDo() t.Eq("02", p.Race().ElementR("button", "02").MustDo().MustText()) - p.Race().MustElementByJS("document.querySelector('button')", nil). + p.Race().MustElementByJS("() => document.querySelector('button')", nil). MustHandle(func(e *rod.Element) { t.Eq("01", e.MustText()) }).MustDo() - t.Eq("01", p.Race().MustElementByJS("document.querySelector('button')", nil).MustDo().MustText()) + t.Eq("01", p.Race().MustElementByJS("() => document.querySelector('button')", nil).MustDo().MustText()) el, err := p.Sleeper(func() utils.Sleeper { return utils.CountSleeper(2) }).Race(). Element("not-exists").MustHandle(func(e *rod.Element) {}). @@ -184,7 +184,7 @@ func (t T) PageRace() { t.Err(err) t.Nil(el) - el, err = p.Race().MustElementByJS(`notExists()`, nil).Do() + el, err = p.Race().MustElementByJS(`() => notExists()`, nil).Do() t.Err(err) t.Nil(el) } @@ -194,9 +194,9 @@ func (t T) PageRaceRetryInHandle() { p.Race().Element("div").MustHandle(func(e *rod.Element) { go func() { utils.Sleep(0.5) - e.MustElement("button").MustEval(`this.innerText = '04'`) + e.MustElement("button").MustEval(`() => this.innerText = '04'`) }() - e.MustElement("button").MustWait("this.innerText === '04'") + e.MustElement("button").MustWait("() => this.innerText === '04'") }).MustDo() } @@ -254,7 +254,7 @@ func (t T) ElementsFromElement() { func (t T) ElementParent() { p := t.page.MustNavigate(t.srcFile("fixtures/input.html")) el := p.MustElement("input").MustParent() - t.Eq("FORM", el.MustEval(`this.tagName`).String()) + t.Eq("FORM", el.MustEval(`() => this.tagName`).String()) } func (t T) ElementParents() { @@ -300,9 +300,9 @@ func (t T) ElementTracing() { func (t T) PageElementByJS() { p := t.page.MustNavigate(t.srcFile("fixtures/click.html")) - t.Eq(p.MustElementByJS(`document.querySelector('button')`).MustText(), "click me") + t.Eq(p.MustElementByJS(`() => document.querySelector('button')`).MustText(), "click me") - _, err := p.ElementByJS(rod.Eval(`1`)) + _, err := p.ElementByJS(rod.Eval(`() => 1`)) t.Is(err, &rod.ErrExpectElement{}) t.Eq(err.Error(), "expect js to return an element, but got: {\"type\":\"number\",\"value\":1,\"description\":\"1\"}") } @@ -310,18 +310,18 @@ func (t T) PageElementByJS() { func (t T) PageElementsByJS() { p := t.page.MustNavigate(t.srcFile("fixtures/selector.html")).MustWaitLoad() - t.Len(p.MustElementsByJS("document.querySelectorAll('button')"), 4) + t.Len(p.MustElementsByJS("() => document.querySelectorAll('button')"), 4) - _, err := p.ElementsByJS(rod.Eval(`[1]`)) + _, err := p.ElementsByJS(rod.Eval(`() => [1]`)) t.Is(err, &rod.ErrExpectElements{}) t.Eq(err.Error(), "expect js to return an array of elements, but got: {\"type\":\"number\",\"value\":1,\"description\":\"1\"}") - _, err = p.ElementsByJS(rod.Eval(`1`)) + _, err = p.ElementsByJS(rod.Eval(`() => 1`)) t.Eq(err.Error(), "expect js to return an array of elements, but got: {\"type\":\"number\",\"value\":1,\"description\":\"1\"}") - _, err = p.ElementsByJS(rod.Eval(`foo()`)) + _, err = p.ElementsByJS(rod.Eval(`() => foo()`)) t.Err(err) t.mc.stubErr(1, proto.RuntimeGetProperties{}) - _, err = p.ElementsByJS(rod.Eval(`[document.body]`)) + _, err = p.ElementsByJS(rod.Eval(`() => [document.body]`)) t.Err(err) t.mc.stubErr(4, proto.RuntimeCallFunctionOn{}) diff --git a/utils.go b/utils.go index a04480d7..65547e85 100644 --- a/utils.go +++ b/utils.go @@ -286,53 +286,6 @@ func mustToJSONForDev(value interface{}) string { return buf.String() } -// detect if a js string is a function definition -var regFn = regexp.MustCompile(`\A\s*function\s*\(`) - -// detect if a js string is a function definition -// Samples: -// -// function () {} -// a => {} -// (a, b, c) => -// ({a: b}, ...list) => {} -func detectJSFunction(js string) bool { - if regFn.MatchString(js) { - return true - } - - // The algorithm is pretty simple, the braces before "=>" must be balanced. - // Such as "foo(() => {})", there are 2 "(", but only 1 ")". - // Here we use a simple state machine. - - balanced := true - last := ' ' - for _, r := range js { - if r == '(' { - if balanced { - balanced = false - } else { - return false - } - } - if r == ')' { - if balanced { - return false - } - balanced = true - } - - if last == '=' { - if r == '>' { - return balanced - } - return false - } - last = r - } - return false -} - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs var regDataURI = regexp.MustCompile(`\Adata:(.+?)?(;base64)?,`)