diff --git a/browser.go b/browser.go index e926f437..a67ea347 100644 --- a/browser.go +++ b/browser.go @@ -34,6 +34,8 @@ type Browser struct { // BrowserContextID is the id for incognito window BrowserContextID proto.BrowserBrowserContextID + e func(args ...interface{}) + ctx context.Context sleeper func() utils.Sleeper @@ -60,7 +62,7 @@ type Browser struct { // DefaultDevice is set to devices.LaptopWithMDPIScreen.Landescape() . You can use // NoDefaultDevice to disable it. func New() *Browser { - return &Browser{ + return (&Browser{ ctx: context.Background(), sleeper: DefaultSleeper, slowMotion: defaults.Slow, @@ -70,7 +72,7 @@ func New() *Browser { defaultDevice: devices.LaptopWithMDPIScreen.Landescape(), targetsLock: &sync.Mutex{}, states: &sync.Map{}, - } + }).WithPanic(utils.Panic) } // Incognito creates a new incognito browser @@ -248,6 +250,7 @@ func (b *Browser) Call(ctx context.Context, sessionID, methodName string, params // PageFromSession is used for low-level debugging func (b *Browser) PageFromSession(sessionID proto.TargetSessionID) *Page { return &Page{ + e: b.e, ctx: b.ctx, sleeper: b.sleeper, browser: b, @@ -274,6 +277,7 @@ func (b *Browser) PageFromTarget(targetID proto.TargetTargetID) (*Page, error) { } page = &Page{ + e: b.e, ctx: b.ctx, sleeper: b.sleeper, browser: b, diff --git a/element.go b/element.go index dec76161..050efdb1 100644 --- a/element.go +++ b/element.go @@ -26,6 +26,8 @@ var _ proto.Sessionable = &Element{} type Element struct { Object *proto.RuntimeRemoteObject + e func(args ...interface{}) + ctx context.Context sleeper func() utils.Sleeper diff --git a/hijack.go b/hijack.go index 9c7bd5b5..fcc9461b 100644 --- a/hijack.go +++ b/hijack.go @@ -174,6 +174,8 @@ func (r *HijackRouter) new(ctx context.Context, e *proto.FetchRequestPaused) *Hi }, }, OnError: func(err error) {}, + + browser: r.browser, } } @@ -208,6 +210,8 @@ type Hijack struct { // CustomState is used to store things for this context CustomState interface{} + + browser *Browser } // ContinueRequest without hijacking. The RequestID will be set by the router, you don't have to set it. diff --git a/lib/utils/utils.go b/lib/utils/utils.go index fa6184a5..8dec722a 100644 --- a/lib/utils/utils.go +++ b/lib/utils/utils.go @@ -60,11 +60,14 @@ func MultiLogger(list ...Logger) Log { }) } +// Panic is the same as the built-in panic +var Panic = func(v interface{}) { panic(v) } + // E if the last arg is error, panic it func E(args ...interface{}) []interface{} { err, ok := args[len(args)-1].(error) if ok { - panic(err) + Panic(err) } return args } diff --git a/must.go b/must.go index 2c64b288..e9af54dd 100644 --- a/must.go +++ b/must.go @@ -23,9 +23,48 @@ import ( "github.com/ysmood/gson" ) +// WithPanic returns a browser clone with the specified panic function. +// The fail must stop the current goroutine's execution immediately, such as use runtime.Goexit() or panic inside it. +func (b *Browser) WithPanic(fail func(interface{})) *Browser { + n := *b + n.e = func(args ...interface{}) { + err, ok := args[len(args)-1].(error) + if ok { + fail(err) + } + } + return &n +} + +// WithPanic returns a page clone with the specified panic function. +// The fail must stop the current goroutine's execution immediately, such as use runtime.Goexit() or panic inside it. +func (p *Page) WithPanic(fail func(interface{})) *Page { + n := *p + n.e = func(args ...interface{}) { + err, ok := args[len(args)-1].(error) + if ok { + fail(err) + } + } + return &n +} + +// WithPanic returns an element clone with the specified panic function. +// The fail must stop the current goroutine's execution immediately, such as use runtime.Goexit() or panic inside it. +func (el *Element) WithPanic(fail func(interface{})) *Element { + n := *el + n.e = func(args ...interface{}) { + err, ok := args[len(args)-1].(error) + if ok { + fail(err) + } + } + return &n +} + // MustConnect is similar to Browser.Connect func (b *Browser) MustConnect() *Browser { - utils.E(b.Connect()) + b.e(b.Connect()) return b } @@ -37,7 +76,7 @@ func (b *Browser) MustClose() { // MustIncognito is similar to Browser.Incognito func (b *Browser) MustIncognito() *Browser { b, err := b.Incognito() - utils.E(err) + b.e(err) return b } @@ -45,40 +84,40 @@ func (b *Browser) MustIncognito() *Browser { // The url list will be joined by "/". func (b *Browser) MustPage(url ...string) *Page { p, err := b.Page(proto.TargetCreateTarget{URL: strings.Join(url, "/")}) - utils.E(err) + b.e(err) return p } // MustPages is similar to Browser.Pages func (b *Browser) MustPages() Pages { list, err := b.Pages() - utils.E(err) + b.e(err) return list } // MustPageFromTargetID is similar to Browser.PageFromTargetID func (b *Browser) MustPageFromTargetID(targetID proto.TargetTargetID) *Page { p, err := b.PageFromTarget(targetID) - utils.E(err) + b.e(err) return p } // MustHandleAuth is similar to Browser.HandleAuth func (b *Browser) MustHandleAuth(username, password string) (wait func()) { w := b.HandleAuth(username, password) - return func() { utils.E(w()) } + return func() { b.e(w()) } } // MustIgnoreCertErrors is similar to Browser.IgnoreCertErrors func (b *Browser) MustIgnoreCertErrors(enable bool) *Browser { - utils.E(b.IgnoreCertErrors(enable)) + b.e(b.IgnoreCertErrors(enable)) return b } // MustGetCookies is similar Browser.GetCookies func (b *Browser) MustGetCookies() []*proto.NetworkCookie { nc, err := b.GetCookies() - utils.E(err) + b.e(err) return nc } @@ -86,9 +125,9 @@ func (b *Browser) MustGetCookies() []*proto.NetworkCookie { // If the len(cookies) is 0 it will clear all the cookies. func (b *Browser) MustSetCookies(cookies ...*proto.NetworkCookie) *Browser { if len(cookies) == 0 { - utils.E(b.SetCookies(nil)) + b.e(b.SetCookies(nil)) } else { - utils.E(b.SetCookies(proto.CookiesToParams(cookies))) + b.e(b.SetCookies(proto.CookiesToParams(cookies))) } return b } @@ -103,44 +142,60 @@ func (b *Browser) MustWaitDownload() func() []byte { info := wait() path := filepath.Join(tmpDir, info.GUID) defer func() { _ = os.Remove(path) }() - b, err := ioutil.ReadFile(path) - utils.E(err) - return b + data, err := ioutil.ReadFile(path) + b.e(err) + return data } } // MustFind is similar to Browser.Find func (ps Pages) MustFind(selector string) *Page { p, err := ps.Find(selector) - utils.E(err) + if err != nil { + if len(ps) > 0 { + ps[0].e(err) + } else { + // fallback to utils.E, because we don't have enough + // context to call the scope `.e`. + utils.E(err) + } + } return p } // MustFindByURL is similar to Page.FindByURL func (ps Pages) MustFindByURL(regex string) *Page { p, err := ps.FindByURL(regex) - utils.E(err) + if err != nil { + if len(ps) > 0 { + ps[0].e(err) + } else { + // fallback to utils.E, because we don't have enough + // context to call the scope `.e`. + utils.E(err) + } + } return p } // MustInfo is similar to Page.Info func (p *Page) MustInfo() *proto.TargetTargetInfo { info, err := p.Info() - utils.E(err) + p.e(err) return info } // MustHTML is similar to Page.HTML func (p *Page) MustHTML() string { html, err := p.HTML() - utils.E(err) + p.e(err) return html } // MustCookies is similar to Page.Cookies func (p *Page) MustCookies(urls ...string) []*proto.NetworkCookie { cookies, err := p.Cookies(urls) - utils.E(err) + p.e(err) return cookies } @@ -150,63 +205,63 @@ func (p *Page) MustSetCookies(cookies ...*proto.NetworkCookieParam) *Page { if len(cookies) == 0 { cookies = nil } - utils.E(p.SetCookies(cookies)) + p.e(p.SetCookies(cookies)) return p } // MustSetExtraHeaders is similar to Page.SetExtraHeaders func (p *Page) MustSetExtraHeaders(dict ...string) (cleanup func()) { cleanup, err := p.SetExtraHeaders(dict) - utils.E(err) + p.e(err) return } // MustSetUserAgent is similar to Page.SetUserAgent func (p *Page) MustSetUserAgent(req *proto.NetworkSetUserAgentOverride) *Page { - utils.E(p.SetUserAgent(req)) + p.e(p.SetUserAgent(req)) return p } // MustNavigate is similar to Page.Navigate func (p *Page) MustNavigate(url string) *Page { - utils.E(p.Navigate(url)) + p.e(p.Navigate(url)) return p } // MustReload is similar to Page.Reload func (p *Page) MustReload() *Page { - utils.E(p.Reload()) + p.e(p.Reload()) return p } // MustActivate is similar to Page.Activate func (p *Page) MustActivate() *Page { - utils.E(p.Activate()) + p.e(p.Activate()) return p } // MustNavigateBack is similar to Page.NavigateBack func (p *Page) MustNavigateBack() *Page { - utils.E(p.NavigateBack()) + p.e(p.NavigateBack()) return p } // MustNavigateForward is similar to Page.NavigateForward func (p *Page) MustNavigateForward() *Page { - utils.E(p.NavigateForward()) + p.e(p.NavigateForward()) return p } // MustGetWindow is similar to Page.GetWindow func (p *Page) MustGetWindow() *proto.BrowserBounds { bounds, err := p.GetWindow() - utils.E(err) + p.e(err) return bounds } // MustSetWindow is similar to Page.SetWindow func (p *Page) MustSetWindow(left, top, width, height int) *Page { - utils.E(p.SetWindow(&proto.BrowserBounds{ + p.e(p.SetWindow(&proto.BrowserBounds{ Left: left, Top: top, Width: width, @@ -218,7 +273,7 @@ func (p *Page) MustSetWindow(left, top, width, height int) *Page { // MustWindowMinimize is similar to Page.WindowMinimize func (p *Page) MustWindowMinimize() *Page { - utils.E(p.SetWindow(&proto.BrowserBounds{ + p.e(p.SetWindow(&proto.BrowserBounds{ WindowState: proto.BrowserWindowStateMinimized, })) return p @@ -226,7 +281,7 @@ func (p *Page) MustWindowMinimize() *Page { // MustWindowMaximize is similar to Page.WindowMaximize func (p *Page) MustWindowMaximize() *Page { - utils.E(p.SetWindow(&proto.BrowserBounds{ + p.e(p.SetWindow(&proto.BrowserBounds{ WindowState: proto.BrowserWindowStateMaximized, })) return p @@ -234,7 +289,7 @@ func (p *Page) MustWindowMaximize() *Page { // MustWindowFullscreen is similar to Page.WindowFullscreen func (p *Page) MustWindowFullscreen() *Page { - utils.E(p.SetWindow(&proto.BrowserBounds{ + p.e(p.SetWindow(&proto.BrowserBounds{ WindowState: proto.BrowserWindowStateFullscreen, })) return p @@ -242,7 +297,7 @@ func (p *Page) MustWindowFullscreen() *Page { // MustWindowNormal is similar to Page.WindowNormal func (p *Page) MustWindowNormal() *Page { - utils.E(p.SetWindow(&proto.BrowserBounds{ + p.e(p.SetWindow(&proto.BrowserBounds{ WindowState: proto.BrowserWindowStateNormal, })) return p @@ -250,7 +305,7 @@ func (p *Page) MustWindowNormal() *Page { // MustSetViewport is similar to Page.SetViewport func (p *Page) MustSetViewport(width, height int, deviceScaleFactor float64, mobile bool) *Page { - utils.E(p.SetViewport(&proto.EmulationSetDeviceMetricsOverride{ + p.e(p.SetViewport(&proto.EmulationSetDeviceMetricsOverride{ Width: width, Height: height, DeviceScaleFactor: deviceScaleFactor, @@ -261,26 +316,26 @@ func (p *Page) MustSetViewport(width, height int, deviceScaleFactor float64, mob // MustEmulate is similar to Page.Emulate func (p *Page) MustEmulate(device devices.Device) *Page { - utils.E(p.Emulate(device)) + p.e(p.Emulate(device)) return p } // MustStopLoading is similar to Page.StopLoading func (p *Page) MustStopLoading() *Page { - utils.E(p.StopLoading()) + p.e(p.StopLoading()) return p } // MustClose is similar to Page.Close func (p *Page) MustClose() { - utils.E(p.Close()) + p.e(p.Close()) } // MustHandleDialog is similar to Page.HandleDialog func (p *Page) MustHandleDialog() (wait func() *proto.PageJavascriptDialogOpening, handle func(bool, string)) { w, h := p.HandleDialog() return w, func(accept bool, promptText string) { - utils.E(h(&proto.PageHandleJavaScriptDialog{ + p.e(h(&proto.PageHandleJavaScriptDialog{ Accept: accept, PromptText: promptText, })) @@ -291,8 +346,8 @@ func (p *Page) MustHandleDialog() (wait func() *proto.PageJavascriptDialogOpenin // If the toFile is "", it Page.will save output to "tmp/screenshots" folder, time as the file name. func (p *Page) MustScreenshot(toFile ...string) []byte { bin, err := p.Screenshot(false, nil) - utils.E(err) - utils.E(saveFile(saveFileTypeScreenshot, bin, toFile)) + p.e(err) + p.e(saveFile(saveFileTypeScreenshot, bin, toFile)) return bin } @@ -300,8 +355,8 @@ func (p *Page) MustScreenshot(toFile ...string) []byte { // If the toFile is "", it Page.will save output to "tmp/screenshots" folder, time as the file name. func (p *Page) MustScreenshotFullPage(toFile ...string) []byte { bin, err := p.Screenshot(true, nil) - utils.E(err) - utils.E(saveFile(saveFileTypeScreenshot, bin, toFile)) + p.e(err) + p.e(saveFile(saveFileTypeScreenshot, bin, toFile)) return bin } @@ -309,11 +364,11 @@ func (p *Page) MustScreenshotFullPage(toFile ...string) []byte { // If the toFile is "", it Page.will save output to "tmp/pdf" folder, time as the file name. func (p *Page) MustPDF(toFile ...string) []byte { r, err := p.PDF(&proto.PagePrintToPDF{}) - utils.E(err) + p.e(err) bin, err := ioutil.ReadAll(r) - utils.E(err) + p.e(err) - utils.E(saveFile(saveFileTypePDF, bin, toFile)) + p.e(saveFile(saveFileTypePDF, bin, toFile)) return bin } @@ -322,7 +377,7 @@ func (p *Page) MustWaitOpen() (wait func() (newPage *Page)) { w := p.WaitOpen() return func() *Page { page, err := w() - utils.E(err) + p.e(err) return page } } @@ -339,71 +394,71 @@ func (p *Page) MustWaitRequestIdle(excludes ...string) (wait func()) { // MustWaitIdle is similar to Page.WaitIdle func (p *Page) MustWaitIdle() *Page { - utils.E(p.WaitIdle(time.Minute)) + p.e(p.WaitIdle(time.Minute)) return p } // MustWaitLoad is similar to Page.WaitLoad func (p *Page) MustWaitLoad() *Page { - utils.E(p.WaitLoad()) + p.e(p.WaitLoad()) return p } // MustAddScriptTag is similar to Page.AddScriptTag func (p *Page) MustAddScriptTag(url string) *Page { - utils.E(p.AddScriptTag(url, "")) + p.e(p.AddScriptTag(url, "")) return p } // MustAddStyleTag is similar to Page.AddStyleTag func (p *Page) MustAddStyleTag(url string) *Page { - utils.E(p.AddStyleTag(url, "")) + p.e(p.AddStyleTag(url, "")) return p } // MustEvalOnNewDocument is similar to Page.EvalOnNewDocument func (p *Page) MustEvalOnNewDocument(js string) { _, err := p.EvalOnNewDocument(js) - utils.E(err) + p.e(err) } // MustExpose is similar to Page.Expose func (p *Page) MustExpose(name string, fn func(gson.JSON) (interface{}, error)) (stop func()) { s, err := p.Expose(name, fn) - utils.E(err) - return func() { utils.E(s()) } + p.e(err) + return func() { p.e(s()) } } // MustEval is similar to Page.Eval func (p *Page) MustEval(js string, params ...interface{}) gson.JSON { res, err := p.Eval(js, params...) - utils.E(err) + p.e(err) return res.Value } // MustEvaluate is similar to Page.Evaluate func (p *Page) MustEvaluate(opts *EvalOptions) *proto.RuntimeRemoteObject { res, err := p.Evaluate(opts) - utils.E(err) + p.e(err) return res } // MustWait is similar to Page.Wait func (p *Page) MustWait(js string, params ...interface{}) *Page { - utils.E(p.Wait(nil, js, params)) + p.e(p.Wait(nil, js, params)) return p } // MustWaitElementsMoreThan is similar to Page.WaitElementsMoreThan func (p *Page) MustWaitElementsMoreThan(selector string, num int) *Page { - utils.E(p.WaitElementsMoreThan(selector, num)) + p.e(p.WaitElementsMoreThan(selector, num)) return p } // MustObjectToJSON is similar to Page.ObjectToJSON func (p *Page) MustObjectToJSON(obj *proto.RuntimeRemoteObject) gson.JSON { j, err := p.ObjectToJSON(obj) - utils.E(err) + p.e(err) return j } @@ -412,7 +467,7 @@ func (p *Page) MustObjectsToJSON(list []*proto.RuntimeRemoteObject) gson.JSON { arr := []interface{}{} for _, obj := range list { j, err := p.ObjectToJSON(obj) - utils.E(err) + p.e(err) arr = append(arr, j.Val()) } return gson.New(arr) @@ -421,41 +476,41 @@ func (p *Page) MustObjectsToJSON(list []*proto.RuntimeRemoteObject) gson.JSON { // MustElementFromNode is similar to Page.ElementFromNode func (p *Page) MustElementFromNode(node *proto.DOMNode) *Element { el, err := p.ElementFromNode(node) - utils.E(err) + p.e(err) return el } // MustElementFromPoint is similar to Page.ElementFromPoint func (p *Page) MustElementFromPoint(left, top int) *Element { el, err := p.ElementFromPoint(left, top) - utils.E(err) + p.e(err) return el } // MustRelease is similar to Page.Release func (p *Page) MustRelease(obj *proto.RuntimeRemoteObject) *Page { - utils.E(p.Release(obj)) + p.e(p.Release(obj)) return p } // MustHas is similar to Page.Has func (p *Page) MustHas(selector string) bool { has, _, err := p.Has(selector) - utils.E(err) + p.e(err) return has } // MustHasX is similar to Page.HasX func (p *Page) MustHasX(selector string) bool { has, _, err := p.HasX(selector) - utils.E(err) + p.e(err) return has } // MustHasR is similar to Page.HasR func (p *Page) MustHasR(selector, regex string) bool { has, _, err := p.HasR(selector, regex) - utils.E(err) + p.e(err) return has } @@ -463,7 +518,7 @@ func (p *Page) MustHasR(selector, regex string) bool { // It only returns the first element in the search result. func (p *Page) MustSearch(query string) *Element { res, err := p.Search(query) - utils.E(err) + p.e(err) res.Release() return res.First } @@ -471,49 +526,49 @@ func (p *Page) MustSearch(query string) *Element { // MustElement is similar to Page.Element func (p *Page) MustElement(selector string) *Element { el, err := p.Element(selector) - utils.E(err) + p.e(err) return el } // MustElementR is similar to Page.ElementR func (p *Page) MustElementR(selector, jsRegex string) *Element { el, err := p.ElementR(selector, jsRegex) - utils.E(err) + p.e(err) return el } // MustElementX is similar to Page.ElementX func (p *Page) MustElementX(xPath string) *Element { el, err := p.ElementX(xPath) - utils.E(err) + p.e(err) return el } // MustElementByJS is similar to Page.ElementByJS func (p *Page) MustElementByJS(js string, params ...interface{}) *Element { el, err := p.ElementByJS(Eval(js, params...)) - utils.E(err) + p.e(err) return el } // MustElements is similar to Page.Elements func (p *Page) MustElements(selector string) Elements { list, err := p.Elements(selector) - utils.E(err) + p.e(err) return list } // MustElementsX is similar to Page.ElementsX func (p *Page) MustElementsX(xpath string) Elements { list, err := p.ElementsX(xpath) - utils.E(err) + p.e(err) return list } // MustElementsByJS is similar to Page.ElementsByJS func (p *Page) MustElementsByJS(js string, params ...interface{}) Elements { list, err := p.ElementsByJS(Eval(js, params...)) - utils.E(err) + p.e(err) return list } @@ -533,143 +588,142 @@ func (rc *RaceContext) MustHandle(callback func(*Element)) *RaceContext { // MustDo is similar to RaceContext.Do func (rc *RaceContext) MustDo() *Element { el, err := rc.Do() - utils.E(err) - + rc.page.e(err) return el } // MustMove is similar to Mouse.Move func (m *Mouse) MustMove(x, y float64) *Mouse { - utils.E(m.Move(x, y, 0)) + m.page.e(m.Move(x, y, 0)) return m } // MustScroll is similar to Mouse.Scroll func (m *Mouse) MustScroll(x, y float64) *Mouse { - utils.E(m.Scroll(x, y, 0)) + m.page.e(m.Scroll(x, y, 0)) return m } // MustDown is similar to Mouse.Down func (m *Mouse) MustDown(button proto.InputMouseButton) *Mouse { - utils.E(m.Down(button, 1)) + m.page.e(m.Down(button, 1)) return m } // MustUp is similar to Mouse.Up func (m *Mouse) MustUp(button proto.InputMouseButton) *Mouse { - utils.E(m.Up(button, 1)) + m.page.e(m.Up(button, 1)) return m } // MustClick is similar to Mouse.Click func (m *Mouse) MustClick(button proto.InputMouseButton) *Mouse { - utils.E(m.Click(button)) + m.page.e(m.Click(button)) return m } // MustDown is similar to Keyboard.Down func (k *Keyboard) MustDown(key rune) *Keyboard { - utils.E(k.Down(key)) + k.page.e(k.Down(key)) return k } // MustUp is similar to Keyboard.Up func (k *Keyboard) MustUp(key rune) *Keyboard { - utils.E(k.Up(key)) + k.page.e(k.Up(key)) return k } // MustPress is similar to Keyboard.Press func (k *Keyboard) MustPress(key rune) *Keyboard { - utils.E(k.Press(key)) + k.page.e(k.Press(key)) return k } // MustInsertText is similar to Keyboard.InsertText func (k *Keyboard) MustInsertText(text string) *Keyboard { - utils.E(k.InsertText(text)) + k.page.e(k.InsertText(text)) return k } // MustStart is similar to Touch.Start func (t *Touch) MustStart(points ...*proto.InputTouchPoint) *Touch { - utils.E(t.Start(points...)) + t.page.e(t.Start(points...)) return t } // MustMove is similar to Touch.Move func (t *Touch) MustMove(points ...*proto.InputTouchPoint) *Touch { - utils.E(t.Move(points...)) + t.page.e(t.Move(points...)) return t } // MustEnd is similar to Touch.End func (t *Touch) MustEnd() *Touch { - utils.E(t.End()) + t.page.e(t.End()) return t } // MustCancel is similar to Touch.Cancel func (t *Touch) MustCancel() *Touch { - utils.E(t.Cancel()) + t.page.e(t.Cancel()) return t } // MustTap is similar to Touch.Tap func (t *Touch) MustTap(x, y float64) *Touch { - utils.E(t.Tap(x, y)) + t.page.e(t.Tap(x, y)) return t } // MustDescribe is similar to Element.Describe func (el *Element) MustDescribe() *proto.DOMNode { node, err := el.Describe(1, false) - utils.E(err) + el.e(err) return node } // MustShadowRoot is similar to Element.ShadowRoot func (el *Element) MustShadowRoot() *Element { node, err := el.ShadowRoot() - utils.E(err) + el.e(err) return node } // MustFrame is similar to Element.Frame func (el *Element) MustFrame() *Page { p, err := el.Frame() - utils.E(err) + el.e(err) return p } // MustFocus is similar to Element.Focus func (el *Element) MustFocus() *Element { - utils.E(el.Focus()) + el.e(el.Focus()) return el } // MustScrollIntoView is similar to Element.ScrollIntoView func (el *Element) MustScrollIntoView() *Element { - utils.E(el.ScrollIntoView()) + el.e(el.ScrollIntoView()) return el } // MustHover is similar to Element.Hover func (el *Element) MustHover() *Element { - utils.E(el.Hover()) + el.e(el.Hover()) return el } // MustClick is similar to Element.Click func (el *Element) MustClick() *Element { - utils.E(el.Click(proto.InputMouseButtonLeft)) + el.e(el.Click(proto.InputMouseButtonLeft)) return el } // MustTap is similar to Element.Tap func (el *Element) MustTap() *Element { - utils.E(el.Tap()) + el.e(el.Tap()) return el } @@ -679,337 +733,337 @@ func (el *Element) MustInteractable() bool { if errors.Is(err, &ErrNotInteractable{}) { return false } - utils.E(err) + el.e(err) return true } // MustWaitInteractable is similar to Element.WaitInteractable func (el *Element) MustWaitInteractable() *Element { - utils.E(el.WaitInteractable()) + el.e(el.WaitInteractable()) return el } // MustPress is similar to Element.Press func (el *Element) MustPress(keys ...rune) *Element { - utils.E(el.Press(keys...)) + el.e(el.Press(keys...)) return el } // MustSelectText is similar to Element.SelectText func (el *Element) MustSelectText(regex string) *Element { - utils.E(el.SelectText(regex)) + el.e(el.SelectText(regex)) return el } // MustSelectAllText is similar to Element.SelectAllText func (el *Element) MustSelectAllText() *Element { - utils.E(el.SelectAllText()) + el.e(el.SelectAllText()) return el } // MustInput is similar to Element.Input func (el *Element) MustInput(text string) *Element { - utils.E(el.Input(text)) + el.e(el.Input(text)) return el } // MustInputTime is similar to Element.Input func (el *Element) MustInputTime(t time.Time) *Element { - utils.E(el.InputTime(t)) + el.e(el.InputTime(t)) return el } // MustBlur is similar to Element.Blur func (el *Element) MustBlur() *Element { - utils.E(el.Blur()) + el.e(el.Blur()) return el } // MustSelect is similar to Element.Select func (el *Element) MustSelect(selectors ...string) *Element { - utils.E(el.Select(selectors, true, SelectorTypeText)) + el.e(el.Select(selectors, true, SelectorTypeText)) return el } // MustMatches is similar to Element.Matches func (el *Element) MustMatches(selector string) bool { res, err := el.Matches(selector) - utils.E(err) + el.e(err) return res } // MustAttribute is similar to Element.Attribute func (el *Element) MustAttribute(name string) *string { attr, err := el.Attribute(name) - utils.E(err) + el.e(err) return attr } // MustProperty is similar to Element.Property func (el *Element) MustProperty(name string) gson.JSON { prop, err := el.Property(name) - utils.E(err) + el.e(err) return prop } // MustContainsElement is similar to Element.ContainsElement func (el *Element) MustContainsElement(target *Element) bool { contains, err := el.ContainsElement(target) - utils.E(err) + el.e(err) return contains } // MustSetFiles is similar to Element.SetFiles func (el *Element) MustSetFiles(paths ...string) *Element { - utils.E(el.SetFiles(paths)) + el.e(el.SetFiles(paths)) return el } // MustText is similar to Element.Text func (el *Element) MustText() string { s, err := el.Text() - utils.E(err) + el.e(err) return s } // MustHTML is similar to Element.HTML func (el *Element) MustHTML() string { s, err := el.HTML() - utils.E(err) + el.e(err) return s } // MustVisible is similar to Element.Visible func (el *Element) MustVisible() bool { v, err := el.Visible() - utils.E(err) + el.e(err) return v } // MustWaitLoad is similar to Element.WaitLoad func (el *Element) MustWaitLoad() *Element { - utils.E(el.WaitLoad()) + el.e(el.WaitLoad()) return el } // MustWaitStable is similar to Element.WaitStable func (el *Element) MustWaitStable() *Element { - utils.E(el.WaitStable(300 * time.Millisecond)) + el.e(el.WaitStable(300 * time.Millisecond)) return el } // MustWait is similar to Element.Wait func (el *Element) MustWait(js string, params ...interface{}) *Element { - utils.E(el.Wait(Eval(js, params))) + el.e(el.Wait(Eval(js, params))) return el } // MustWaitVisible is similar to Element.WaitVisible func (el *Element) MustWaitVisible() *Element { - utils.E(el.WaitVisible()) + el.e(el.WaitVisible()) return el } // MustWaitInvisible is similar to Element.WaitInvisible func (el *Element) MustWaitInvisible() *Element { - utils.E(el.WaitInvisible()) + el.e(el.WaitInvisible()) return el } // MustWaitEnabled is similar to Element.WaitEnabled func (el *Element) MustWaitEnabled() *Element { - utils.E(el.WaitEnabled()) + el.e(el.WaitEnabled()) return el } // MustWaitWritable is similar to Element.WaitWritable func (el *Element) MustWaitWritable() *Element { - utils.E(el.WaitWritable()) + el.e(el.WaitWritable()) return el } // MustShape is similar to Element.Shape func (el *Element) MustShape() *proto.DOMGetContentQuadsResult { shape, err := el.Shape() - utils.E(err) + el.e(err) return shape } // MustCanvasToImage is similar to Element.CanvasToImage func (el *Element) MustCanvasToImage() []byte { bin, err := el.CanvasToImage("", -1) - utils.E(err) + el.e(err) return bin } // MustResource is similar to Element.Resource func (el *Element) MustResource() []byte { bin, err := el.Resource() - utils.E(err) + el.e(err) return bin } // MustBackgroundImage is similar to Element.BackgroundImage func (el *Element) MustBackgroundImage() []byte { bin, err := el.BackgroundImage() - utils.E(err) + el.e(err) return bin } // MustScreenshot is similar to Element.Screenshot func (el *Element) MustScreenshot(toFile ...string) []byte { bin, err := el.Screenshot(proto.PageCaptureScreenshotFormatPng, 0) - utils.E(err) - utils.E(saveFile(saveFileTypeScreenshot, bin, toFile)) + el.e(err) + el.e(saveFile(saveFileTypeScreenshot, bin, toFile)) return bin } // MustRelease is similar to Element.Release func (el *Element) MustRelease() { - utils.E(el.Release()) + el.e(el.Release()) } // MustRemove the element from the page func (el *Element) MustRemove() { - utils.E(el.Remove()) + el.e(el.Remove()) } // MustEval is similar to Element.Eval func (el *Element) MustEval(js string, params ...interface{}) gson.JSON { res, err := el.Eval(js, params...) - utils.E(err) + el.e(err) return res.Value } // MustHas is similar to Element.Has func (el *Element) MustHas(selector string) bool { has, _, err := el.Has(selector) - utils.E(err) + el.e(err) return has } // MustHasX is similar to Element.HasX func (el *Element) MustHasX(selector string) bool { has, _, err := el.HasX(selector) - utils.E(err) + el.e(err) return has } // MustHasR is similar to Element.HasR func (el *Element) MustHasR(selector, regex string) bool { has, _, err := el.HasR(selector, regex) - utils.E(err) + el.e(err) return has } // MustElement is similar to Element.Element func (el *Element) MustElement(selector string) *Element { el, err := el.Element(selector) - utils.E(err) + el.e(err) return el } // MustElementX is similar to Element.ElementX func (el *Element) MustElementX(xpath string) *Element { el, err := el.ElementX(xpath) - utils.E(err) + el.e(err) return el } // MustElementByJS is similar to Element.ElementByJS func (el *Element) MustElementByJS(js string, params ...interface{}) *Element { el, err := el.ElementByJS(Eval(js, params...)) - utils.E(err) + el.e(err) return el } // MustParent is similar to Element.Parent func (el *Element) MustParent() *Element { parent, err := el.Parent() - utils.E(err) + el.e(err) return parent } // MustParents is similar to Element.Parents func (el *Element) MustParents(selector string) Elements { list, err := el.Parents(selector) - utils.E(err) + el.e(err) return list } // MustNext is similar to Element.Next func (el *Element) MustNext() *Element { parent, err := el.Next() - utils.E(err) + el.e(err) return parent } // MustPrevious is similar to Element.Previous func (el *Element) MustPrevious() *Element { parent, err := el.Previous() - utils.E(err) + el.e(err) return parent } // MustElementR is similar to Element.ElementR func (el *Element) MustElementR(selector, regex string) *Element { - el, err := el.ElementR(selector, regex) - utils.E(err) - return el + sub, err := el.ElementR(selector, regex) + el.e(err) + return sub } // MustElements is similar to Element.Elements func (el *Element) MustElements(selector string) Elements { list, err := el.Elements(selector) - utils.E(err) + el.e(err) return list } // MustElementsX is similar to Element.ElementsX func (el *Element) MustElementsX(xpath string) Elements { list, err := el.ElementsX(xpath) - utils.E(err) + el.e(err) return list } // MustElementsByJS is similar to Element.ElementsByJS func (el *Element) MustElementsByJS(js string, params ...interface{}) Elements { list, err := el.ElementsByJS(Eval(js, params...)) - utils.E(err) + el.e(err) return list } // MustAdd is similar to HijackRouter.Add func (r *HijackRouter) MustAdd(pattern string, handler func(*Hijack)) *HijackRouter { - utils.E(r.Add(pattern, "", handler)) + r.browser.e(r.Add(pattern, "", handler)) return r } // MustRemove is similar to HijackRouter.Remove func (r *HijackRouter) MustRemove(pattern string) *HijackRouter { - utils.E(r.Remove(pattern)) + r.browser.e(r.Remove(pattern)) return r } // MustStop is similar to HijackRouter.Stop func (r *HijackRouter) MustStop() { - utils.E(r.Stop()) + r.browser.e(r.Stop()) } // MustLoadResponse is similar to Hijack.LoadResponse func (h *Hijack) MustLoadResponse() { - utils.E(h.LoadResponse(http.DefaultClient, true)) + h.browser.e(h.LoadResponse(http.DefaultClient, true)) } // MustEqual is similar to Element.Equal func (el *Element) MustEqual(elm *Element) bool { res, err := el.Equal(elm) - utils.E(err) + el.e(err) return res } // MustMoveMouseOut is similar to Element.MoveMouseOut func (el *Element) MustMoveMouseOut() *Element { - utils.E(el.MoveMouseOut()) + el.e(el.MoveMouseOut()) return el } diff --git a/must_test.go b/must_test.go new file mode 100644 index 00000000..f4aedb5c --- /dev/null +++ b/must_test.go @@ -0,0 +1,84 @@ +package rod_test + +import ( + "github.com/go-rod/rod" + "github.com/go-rod/rod/lib/proto" +) + +func (t T) BrowserWithPanic() { + var triggers int + trigger := func(x interface{}) { + triggers++ + panic(x) + } + + browser := t.browser.Sleeper(rod.NotFoundSleeper).WithPanic(trigger) + t.Panic(func() { browser.MustPage("____") }) + t.Eq(1, triggers) + + page := browser.MustPage(t.blank()) + defer page.MustClose() + + t.Panic(func() { page.MustElement("____") }) + t.Eq(2, triggers) + + el := page.MustElement("html") + + t.Panic(func() { + t.mc.stubErr(1, proto.RuntimeCallFunctionOn{}) + el.MustClick() + }) + t.Eq(3, triggers) +} + +func (t T) PageWithPanic() { + var triggers int + trigger := func(x interface{}) { + triggers++ + panic(x) + } + + browser := t.browser.Sleeper(rod.NotFoundSleeper) + t.Panic(func() { browser.MustPage("____") }) + t.Eq(0, triggers) + + page := browser.MustPage(t.blank()).WithPanic(trigger) + defer page.MustClose() + + t.Panic(func() { page.MustElement("____") }) + t.Eq(1, triggers) + + el := page.MustElement("html") + + t.Panic(func() { + t.mc.stubErr(1, proto.RuntimeCallFunctionOn{}) + el.MustClick() + }) + t.Eq(2, triggers) +} + +func (t T) ElementWithPanic() { + var triggers int + trigger := func(x interface{}) { + triggers++ + panic(x) + } + + browser := t.browser.Sleeper(rod.NotFoundSleeper) + t.Panic(func() { browser.MustPage("____") }) + t.Eq(0, triggers) + + page := browser.MustPage(t.blank()) + defer page.MustClose() + + t.Panic(func() { page.MustElement("____") }) + t.Eq(0, triggers) + + el := page.MustElement("html").WithPanic(trigger) + + t.Panic(func() { + t.mc.stubErr(1, proto.RuntimeCallFunctionOn{}) + el.MustClick() + }) + t.Eq(1, triggers) +} diff --git a/page.go b/page.go index 705e4f91..6d9ee06b 100644 --- a/page.go +++ b/page.go @@ -38,6 +38,8 @@ type Page struct { // A page can attached to multiple controllers, the browser uses it distinguish controllers. SessionID proto.TargetSessionID + e func(args ...interface{}) + ctx context.Context root *Page @@ -625,6 +627,7 @@ func (p *Page) ElementFromObject(obj *proto.RuntimeRemoteObject) (*Element, erro } return &Element{ + e: p.e, ctx: p.ctx, sleeper: p.sleeper, page: p, diff --git a/query_test.go b/query_test.go index 40e94397..ad3fa28e 100644 --- a/query_test.go +++ b/query_test.go @@ -26,7 +26,9 @@ func (t T) Pages() { pages := t.browser.MustPages() t.True(pages.MustFind("button").MustHas("button")) + t.Panic(func() { rod.Pages{}.MustFind("____") }) t.True(pages.MustFindByURL("click.html").MustHas("button")) + t.Panic(func() { rod.Pages{}.MustFindByURL("____") }) _, err := pages.Find("____") t.Err(err)