From 3673164aef5b7e1172fedf100f843f07f9508638 Mon Sep 17 00:00:00 2001 From: Kenneth Shaw Date: Sun, 12 Feb 2017 14:08:40 +0700 Subject: [PATCH] Code cleanup - Refactored API calls to be cleaner - Changed types that shoudn't be exported to not-exported - Updated examples with API changes - Added unit test for Title action --- chromedp_test.go | 12 ++- errors.go | 39 +++++++ examples/click/main.go | 2 +- examples/text/main.go | 2 +- handler.go | 4 +- input.go | 6 -- nav.go | 28 +++-- pool.go | 12 +-- query.go | 229 +++++++++++++++++------------------------ sel.go | 153 ++++----------------------- util.go | 112 ++++++++++++++++---- 11 files changed, 283 insertions(+), 316 deletions(-) create mode 100644 errors.go diff --git a/chromedp_test.go b/chromedp_test.go index f8ab0ce..db83ab4 100644 --- a/chromedp_test.go +++ b/chromedp_test.go @@ -54,9 +54,17 @@ func TestNavigate(t *testing.T) { if err != nil { t.Fatal(err) } - if !strings.HasPrefix(urlstr, "https://www.google.") { - t.Errorf("expected to be on google, got: %v", urlstr) + t.Errorf("expected to be on google domain, at: %s", urlstr) + } + + var title string + err = c.Run(defaultContext, Title(&title)) + if err != nil { + t.Fatal(err) + } + if !strings.Contains(strings.ToLower(title), "google") { + t.Errorf("expected title to contain google, instead title is: %s", title) } } diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..5891279 --- /dev/null +++ b/errors.go @@ -0,0 +1,39 @@ +package chromedp + +import ( + "errors" +) + +// Error types. +var ( + // ErrInvalidDimensions is the error returned when the retrieved box model is + // invalid. + ErrInvalidDimensions = errors.New("invalid dimensions") + + // ErrNoResults is the error returned when there are no matching nodes. + ErrNoResults = errors.New("no results") + + // ErrHasResults is the error returned when there should not be any + // matching nodes. + ErrHasResults = errors.New("has results") + + // ErrNotVisible is the error returned when a non-visible node should be + // visible. + ErrNotVisible = errors.New("not visible") + + // ErrVisible is the error returned when a visible node should be + // non-visible. + ErrVisible = errors.New("visible") + + // ErrDisabled is the error returned when a disabled node should be + // enabled. + ErrDisabled = errors.New("disabled") + + // ErrNotSelected is the error returned when a non-selected node should be + // selected. + ErrNotSelected = errors.New("not selected") + + // ErrInvalidBoxModel is the error returned when the retrieved box model + // data is invalid. + ErrInvalidBoxModel = errors.New("invalid box model") +) diff --git a/examples/click/main.go b/examples/click/main.go index 018b821..e3f100f 100644 --- a/examples/click/main.go +++ b/examples/click/main.go @@ -44,7 +44,7 @@ func click() cdp.Tasks { return cdp.Tasks{ cdp.Navigate(`https://golang.org/pkg/time/`), cdp.WaitVisible(`#footer`), - cdp.Click(`#pkg-overview`, cdp.ElementVisible), + cdp.Click(`#pkg-overview`, cdp.NodeVisible), cdp.Sleep(150 * time.Second), } } diff --git a/examples/text/main.go b/examples/text/main.go index 9daa0d4..3fa5638 100644 --- a/examples/text/main.go +++ b/examples/text/main.go @@ -45,6 +45,6 @@ func main() { func text(res *string) cdp.Tasks { return cdp.Tasks{ cdp.Navigate(`https://golang.org/pkg/time/`), - cdp.Text(`#pkg-overview`, res, cdp.ElementVisible, cdp.ByID), + cdp.Text(`#pkg-overview`, res, cdp.NodeVisible, cdp.ByID), } } diff --git a/handler.go b/handler.go index 92dabef..4678c5b 100644 --- a/handler.go +++ b/handler.go @@ -504,7 +504,7 @@ func (h *TargetHandler) pageEvent(ctxt context.Context, ev interface{}) { defer h.pageWaitGroup.Done() var id cdp.FrameID - var op FrameOp + var op frameOp switch e := ev.(type) { case *page.EventFrameNavigated: @@ -569,7 +569,7 @@ func (h *TargetHandler) domEvent(ctxt context.Context, ev interface{}) { } var id cdp.NodeID - var op NodeOp + var op nodeOp switch e := ev.(type) { case *dom.EventSetChildNodes: diff --git a/input.go b/input.go index 772758a..64d43b7 100644 --- a/input.go +++ b/input.go @@ -2,7 +2,6 @@ package chromedp import ( "context" - "errors" "time" "github.com/knq/chromedp/cdp" @@ -11,11 +10,6 @@ import ( "github.com/knq/chromedp/kb" ) -// Error types. -var ( - ErrInvalidDimensions = errors.New("invalid box dimensions") -) - // MouseAction is a mouse action. func MouseAction(typ input.MouseType, x, y int64, opts ...MouseOption) Action { me := input.DispatchMouseEvent(typ, x, y) diff --git a/nav.go b/nav.go index 16765d0..062ae2c 100644 --- a/nav.go +++ b/nav.go @@ -82,6 +82,16 @@ func NavigateForward(ctxt context.Context, h cdp.Handler) error { return page.NavigateToHistoryEntry(entries[i+1].ID).Do(ctxt, h) } +// Stop stops all navigation and pending resource retrieval. +func Stop() Action { + return page.StopLoading() +} + +// Reload reloads the current page. +func Reload() Action { + return page.Reload() +} + // CaptureScreenshot captures takes a full page screenshot. func CaptureScreenshot(res *[]byte) Action { if res == nil { @@ -113,16 +123,20 @@ func RemoveOnLoadScript(id page.ScriptIdentifier) Action { return page.RemoveScriptToEvaluateOnLoad(id) } -// Stop stops all navigation and pending resource retrieval. -func Stop() Action { - return page.StopLoading() -} - -// Location retrieves the URL location. +// Location retrieves the document location. func Location(urlstr *string) Action { if urlstr == nil { panic("urlstr cannot be nil") } - return EvaluateAsDevTools(`location.toString()`, urlstr) + return EvaluateAsDevTools(`document.location.toString()`, urlstr) +} + +// Title retrieves the document title. +func Title(title *string) Action { + if title == nil { + panic("title cannot be nil") + } + + return EvaluateAsDevTools(`document.title`, title) } diff --git a/pool.go b/pool.go index 24126c2..97c561e 100644 --- a/pool.go +++ b/pool.go @@ -8,14 +8,6 @@ import ( "github.com/knq/chromedp/runner" ) -const ( - // DefaultStartPort is the default start port number. - DefaultStartPort = 9000 - - // DefaultEndPort is the default end port number. - DefaultEndPort = 10000 -) - // Pool manages a pool of running Chrome processes. type Pool struct { // start is the start port. @@ -35,8 +27,8 @@ func NewPool(opts ...PoolOption) (*Pool, error) { var err error p := &Pool{ - start: DefaultStartPort, - end: DefaultEndPort, + start: DefaultPoolStartPort, + end: DefaultPoolEndPort, res: make(map[int]*Res), } diff --git a/query.go b/query.go index fdbe8ec..2bc54e9 100644 --- a/query.go +++ b/query.go @@ -12,17 +12,12 @@ import ( "github.com/disintegration/imaging" "github.com/knq/chromedp/cdp" + "github.com/knq/chromedp/cdp/css" "github.com/knq/chromedp/cdp/dom" "github.com/knq/chromedp/cdp/page" ) -var ( - // ErrInvalidBoxModel is the error returned when the retrieved box model is - // invalid. - ErrInvalidBoxModel = errors.New("invalid box model") -) - -// Nodes retrieves the DOM nodes matching the selector. +// Nodes retrieves the document nodes matching the selector. func Nodes(sel interface{}, nodes *[]*cdp.Node, opts ...QueryOption) Action { if nodes == nil { panic("nodes cannot be nil") @@ -34,7 +29,7 @@ func Nodes(sel interface{}, nodes *[]*cdp.Node, opts ...QueryOption) Action { }, opts...) } -// NodeIDs returns the node IDs of the matching selector. +// NodeIDs retrieves the node IDs matching the selector. func NodeIDs(sel interface{}, ids *[]cdp.NodeID, opts ...QueryOption) Action { if ids == nil { panic("nodes cannot be nil") @@ -52,7 +47,7 @@ func NodeIDs(sel interface{}, ids *[]cdp.NodeID, opts ...QueryOption) Action { }, opts...) } -// Focus focuses the first element returned by the selector. +// Focus focuses the first node matching the selector. func Focus(sel interface{}, opts ...QueryOption) Action { return QueryAfter(sel, func(ctxt context.Context, h cdp.Handler, nodes ...*cdp.Node) error { if len(nodes) < 1 { @@ -63,7 +58,7 @@ func Focus(sel interface{}, opts ...QueryOption) Action { }, opts...) } -// Blur unfocuses (blurs) the first element returned by the selector. +// Blur unfocuses (blurs) the first node matching the selector. func Blur(sel interface{}, opts ...QueryOption) Action { return QueryAfter(sel, func(ctxt context.Context, h cdp.Handler, nodes ...*cdp.Node) error { if len(nodes) < 1 { @@ -75,14 +70,32 @@ func Blur(sel interface{}, opts ...QueryOption) Action { if err != nil { return err } + if !res { return fmt.Errorf("could not blur node %d", nodes[0].NodeID) } + return nil }, opts...) } -// Text retrieves the text of the first element matching the selector. +// Dimensions retrieves the box model dimensions for the first node matching +// the selector. +func Dimensions(sel interface{}, model **dom.BoxModel, opts ...QueryOption) Action { + if model == nil { + panic("model cannot be nil") + } + return QueryAfter(sel, func(ctxt context.Context, h cdp.Handler, nodes ...*cdp.Node) error { + if len(nodes) < 1 { + return fmt.Errorf("selector `%s` did not return any nodes", sel) + } + var err error + *model, err = dom.GetBoxModel(nodes[0].NodeID).Do(ctxt, h) + return err + }, opts...) +} + +// Text retrieves the visible text of the first node matching the selector. func Text(sel interface{}, text *string, opts ...QueryOption) Action { if text == nil { panic("text cannot be nil") @@ -97,7 +110,7 @@ func Text(sel interface{}, text *string, opts ...QueryOption) Action { }, opts...) } -// Clear clears input and textarea fields of their values. +// Clear clears the values of any input/textarea nodes matching the selector. func Clear(sel interface{}, opts ...QueryOption) Action { return QueryAfter(sel, func(ctxt context.Context, h cdp.Handler, nodes ...*cdp.Node) error { if len(nodes) < 1 { @@ -138,23 +151,7 @@ func Clear(sel interface{}, opts ...QueryOption) Action { }, opts...) } -// Dimensions retrieves the box model dimensions for the first node matching -// the specified selector. -func Dimensions(sel interface{}, model **dom.BoxModel, opts ...QueryOption) Action { - if model == nil { - panic("model cannot be nil") - } - return QueryAfter(sel, func(ctxt context.Context, h cdp.Handler, nodes ...*cdp.Node) error { - if len(nodes) < 1 { - return fmt.Errorf("selector `%s` did not return any nodes", sel) - } - var err error - *model, err = dom.GetBoxModel(nodes[0].NodeID).Do(ctxt, h) - return err - }, opts...) -} - -// Value retrieves the value of an element. +// Value retrieves the value of the first node matching the selector. func Value(sel interface{}, value *string, opts ...QueryOption) Action { if value == nil { panic("value cannot be nil") @@ -189,7 +186,7 @@ func SetValue(sel interface{}, value string, opts ...QueryOption) Action { }, opts...) } -// Attributes retrieves the attributes for the specified element. +// Attributes retrieves the attributes for the first node matching the selector. func Attributes(sel interface{}, attributes *map[string]string, opts ...QueryOption) Action { if attributes == nil { panic("attributes cannot be nil") @@ -215,19 +212,26 @@ func Attributes(sel interface{}, attributes *map[string]string, opts ...QueryOpt }, opts...) } -// SetAttributes sets the attributes for the specified element. +// SetAttributes sets the attributes for the first node matching the selector. func SetAttributes(sel interface{}, attributes map[string]string, opts ...QueryOption) Action { return QueryAfter(sel, func(ctxt context.Context, h cdp.Handler, nodes ...*cdp.Node) error { if len(nodes) < 1 { return errors.New("expected at least one element") } - return nil + attrs := make([]string, len(attributes)*2) + i := 0 + for k, v := range attributes { + attrs[i], attrs[i+1] = k, v + i += 2 + } + + return dom.SetAttributesAsText(nodes[0].NodeID, strings.Join(attrs, " ")).Do(ctxt, h) }, opts...) } -// AttributeValue retrieves the name'd attribute value for the specified -// element. +// AttributeValue retrieves the attribute value for the first node matching the +// selector. func AttributeValue(sel interface{}, name string, value *string, ok *bool, opts ...QueryOption) Action { if value == nil { panic("value cannot be nil") @@ -260,7 +264,8 @@ func AttributeValue(sel interface{}, name string, value *string, ok *bool, opts }, opts...) } -// SetAttributeValue sets an element's attribute with name to value. +// SetAttributeValue sets the attribute with name to value on the first node +// matching the selector. func SetAttributeValue(sel interface{}, name, value string, opts ...QueryOption) Action { return QueryAfter(sel, func(ctxt context.Context, h cdp.Handler, nodes ...*cdp.Node) error { if len(nodes) < 1 { @@ -271,7 +276,8 @@ func SetAttributeValue(sel interface{}, name, value string, opts ...QueryOption) }, opts...) } -// RemoveAttribute removes an element's attribute with name. +// RemoveAttribute removes the attribute with name from the first node matching +// the selector. func RemoveAttribute(sel interface{}, name string, opts ...QueryOption) Action { return QueryAfter(sel, func(ctxt context.Context, h cdp.Handler, nodes ...*cdp.Node) error { if len(nodes) < 1 { @@ -282,7 +288,7 @@ func RemoveAttribute(sel interface{}, name string, opts ...QueryOption) Action { }, opts...) } -// Click sends a click to the first element returned by the selector. +// Click sends a mouse click event to the first node matching the selector. func Click(sel interface{}, opts ...QueryOption) Action { return QueryAfter(sel, func(ctxt context.Context, h cdp.Handler, nodes ...*cdp.Node) error { if len(nodes) < 1 { @@ -290,10 +296,11 @@ func Click(sel interface{}, opts ...QueryOption) Action { } return MouseClickNode(nodes[0]).Do(ctxt, h) - }, append(opts, ElementVisible)...) + }, append(opts, NodeVisible)...) } -// DoubleClick does a double click on the first element returned by selector. +// DoubleClick sends a mouse double click event to the first node matching the +// selector. func DoubleClick(sel interface{}, opts ...QueryOption) Action { return QueryAfter(sel, func(ctxt context.Context, h cdp.Handler, nodes ...*cdp.Node) error { if len(nodes) < 1 { @@ -301,34 +308,21 @@ func DoubleClick(sel interface{}, opts ...QueryOption) Action { } return MouseClickNode(nodes[0], ClickCount(2)).Do(ctxt, h) - }, append(opts, ElementVisible)...) + }, append(opts, NodeVisible)...) } -// NOTE: temporarily disabling this until a proper unit test can be written. -// -// Hover hovers (moves) the mouse over the first element returned by the -// selector. -//func Hover(sel interface{}, opts ...QueryOption) Action { -// return QueryAfter(sel, func(ctxt context.Context, h cdp.Handler, nodes ...*cdp.Node) error { -// if len(nodes) < 1 { -// return fmt.Errorf("selector `%s` did not return any nodes", sel) -// } -// -// return MouseClickNode(nodes[0], ButtonNone).Do(ctxt, h) -// }, append(opts, ElementVisible)...) -//} - -// SendKeys sends keys to the first element returned by selector. +// SendKeys synthesizes the key up, char, and down events as needed for the +// runes in v, sending them to the first node matching the selector. func SendKeys(sel interface{}, v string, opts ...QueryOption) Action { return QueryAfter(sel, func(ctxt context.Context, h cdp.Handler, nodes ...*cdp.Node) error { if len(nodes) < 1 { return fmt.Errorf("selector `%s` did not return any nodes", sel) } return KeyActionNode(nodes[0], v).Do(ctxt, h) - }, append(opts, ElementVisible)...) + }, append(opts, NodeVisible)...) } -// Screenshot takes a screenshot of the first element matching the selector. +// Screenshot takes a screenshot of the first node matching the selector. func Screenshot(sel interface{}, picbuf *[]byte, opts ...QueryOption) Action { if picbuf == nil { panic("picbuf cannot be nil") @@ -387,11 +381,11 @@ func Screenshot(sel interface{}, picbuf *[]byte, opts ...QueryOption) Action { *picbuf = croppedBuf.Bytes() return nil - }, append(opts, ElementVisible)...) + }, append(opts, NodeVisible)...) } -// Submit is an action that submits whatever form the first element matching -// the selector belongs to. +// Submit is an action that submits the form of the first node matching the +// selector belongs to. func Submit(sel interface{}, opts ...QueryOption) Action { return QueryAfter(sel, func(ctxt context.Context, h cdp.Handler, nodes ...*cdp.Node) error { if len(nodes) < 1 { @@ -412,7 +406,7 @@ func Submit(sel interface{}, opts ...QueryOption) Action { }, opts...) } -// Reset is an action that resets whatever form the first element matching the +// Reset is an action that resets the form of the first node matching the // selector belongs to. func Reset(sel interface{}, opts ...QueryOption) Action { return QueryAfter(sel, func(ctxt context.Context, h cdp.Handler, nodes ...*cdp.Node) error { @@ -434,84 +428,51 @@ func Reset(sel interface{}, opts ...QueryOption) Action { }, opts...) } -const ( - // textJS is a javascript snippet that returns the concatenated textContent - // of all visible (ie, offsetParent !== null) children. - textJS = `(function(a) { - var s = ''; - for (var i = 0; i < a.length; i++) { - if (a[i].offsetParent !== null) { - s += a[i].textContent; - } +// ComputedStyle retrieves the computed style of the first node matching the selector. +func ComputedStyle(sel interface{}, style *[]*css.ComputedProperty, opts ...QueryOption) Action { + if style == nil { + panic("style cannot be nil") + } + + return QueryAfter(sel, func(ctxt context.Context, h cdp.Handler, nodes ...*cdp.Node) error { + if len(nodes) < 1 { + return fmt.Errorf("selector `%s` did not return any nodes", sel) } - return s; - })($x("%s/node()"))` - // blurJS is a javscript snippet that blurs the specified element. - blurJS = `(function(a) { - a[0].blur(); - return true; - })($x('%s'))` - - // scrollJS is a javascript snippet that scrolls the window to the - // specified x, y coordinates and then returns the actual window x/y after - // execution. - scrollJS = `(function(x, y) { - window.scrollTo(x, y); - return [window.scrollX, window.scrollY]; - })(%d, %d)` - - // submitJS is a javascript snippet that will call the containing form's - // submit function, returning true or false if the call was successful. - submitJS = `(function(a) { - if (a[0].nodeName === 'FORM') { - a[0].submit(); - return true; - } else if (a[0].form !== null) { - a[0].form.submit(); - return true; + computed, err := css.GetComputedStyleForNode(nodes[0].NodeID).Do(ctxt, h) + if err != nil { + return err } - return false; - })($x('%s'))` - // resetJS is a javascript snippet that will call the containing form's - // reset function, returning true or false if the call was successful. - resetJS = `(function(a) { - if (a[0].nodeName === 'FORM') { - a[0].reset(); - return true; - } else if (a[0].form !== null) { - a[0].form.reset(); - return true; + *style = computed + + return nil + }, opts...) +} + +// MatchedStyle retrieves the matched style information for the first node +// matching the selector. +func MatchedStyle(sel interface{}, style **css.GetMatchedStylesForNodeReturns, opts ...QueryOption) Action { + if style == nil { + panic("style cannot be nil") + } + + return QueryAfter(sel, func(ctxt context.Context, h cdp.Handler, nodes ...*cdp.Node) error { + if len(nodes) < 1 { + return fmt.Errorf("selector `%s` did not return any nodes", sel) } - return false; - })($x('%s'))` - // valueJS is a javascript snippet that returns the value of a specified - // node. - valueJS = `(function(a) { - return a[0].value; - })($x('%s'))` + var err error + ret := &css.GetMatchedStylesForNodeReturns{} + ret.InlineStyle, ret.AttributesStyle, ret.MatchedCSSRules, + ret.PseudoElements, ret.Inherited, ret.CSSKeyframesRules, + err = css.GetMatchedStylesForNode(nodes[0].NodeID).Do(ctxt, h) + if err != nil { + return err + } - // setValueJS is a javascript snippet that sets the value of the specified - // node, and returns the value. - setValueJS = `(function(a, val) { - return a[0].value = val; - })($x('%s'), '%s')` -) + *style = ret -/* - -Title -SetTitle -OuterHTML -SetOuterHTML - -NodeName -- ? - -Style(Matched) -Style(Computed) -SetStyle -GetStyle(Inline) - -*/ + return nil + }, opts...) +} diff --git a/sel.go b/sel.go index 7848599..6243e74 100644 --- a/sel.go +++ b/sel.go @@ -2,14 +2,12 @@ package chromedp import ( "context" - "errors" "fmt" "strings" "sync" "time" "github.com/knq/chromedp/cdp" - "github.com/knq/chromedp/cdp/css" "github.com/knq/chromedp/cdp/dom" ) @@ -24,16 +22,6 @@ tagname */ -// Error types. -var ( - ErrNoResults = errors.New("no results") - ErrHasResults = errors.New("has results") - ErrNotVisible = errors.New("not visible") - ErrVisible = errors.New("visible") - ErrDisabled = errors.New("disabled") - ErrNotSelected = errors.New("not selected") -) - // Selector holds information pertaining to an element query select action. type Selector struct { sel interface{} @@ -61,7 +49,7 @@ func Query(sel interface{}, opts ...QueryOption) Action { } if s.wait == nil { - ElementReady(s) + NodeReady(s) } return s @@ -294,13 +282,13 @@ func WaitFunc(wait func(context.Context, cdp.Handler, *cdp.Node, ...cdp.NodeID) } } -// ElementReady is a query option to wait until the element is ready. -func ElementReady(s *Selector) { +// NodeReady is a query option to wait until the element is ready. +func NodeReady(s *Selector) { WaitFunc(s.waitReady(nil))(s) } -// ElementVisible is a query option to wait until the element is visible. -func ElementVisible(s *Selector) { +// NodeVisible is a query option to wait until the element is visible. +func NodeVisible(s *Selector) { WaitFunc(s.waitReady(func(ctxt context.Context, h cdp.Handler, n *cdp.Node) error { var err error @@ -327,8 +315,8 @@ func ElementVisible(s *Selector) { }))(s) } -// ElementNotVisible is a query option to wait until the element is not visible. -func ElementNotVisible(s *Selector) { +// NodeNotVisible is a query option to wait until the element is not visible. +func NodeNotVisible(s *Selector) { WaitFunc(s.waitReady(func(ctxt context.Context, h cdp.Handler, n *cdp.Node) error { var err error @@ -355,105 +343,8 @@ func ElementNotVisible(s *Selector) { }))(s) } -// ElementVisibleOld is a query option to wait until the element is visible. -// -// This is the old, complicated, implementation (deprecated). -func ElementVisibleOld(s *Selector) { - WaitFunc(s.waitReady(func(ctxt context.Context, h cdp.Handler, n *cdp.Node) error { - var err error - - // check node has box model - _, err = dom.GetBoxModel(n.NodeID).Do(ctxt, h) - if err != nil { - return err - } - - // check if any of the parents are not visible ... - var hidden bool - for ; n.Parent != nil; n = n.Parent { - // get style - style, err := css.GetComputedStyleForNode(n.NodeID).Do(ctxt, h) - if err != nil { - return err - } - - // check if hidden - for _, c := range style { - switch c.Name { - case "display": - //log.Printf("%d >>>> %s=%s", n.NodeID, c.Name, c.Value) - hidden = c.Value == "none" - - case "visibility": - //log.Printf("%d >>>> %s=%s", n.NodeID, c.Name, c.Value) - hidden = c.Value != "visible" - - case "hidden": - //log.Printf("%d >>>> %s=%s", n.NodeID, c.Name, c.Value) - hidden = true - } - - if hidden { - return ErrNotVisible - } - } - } - - return nil - }))(s) -} - -// ElementNotVisibleOld is a query option to wait until the element is not -// visible. -// -// This is the old, complicated, implementation (deprecated). -func ElementNotVisibleOld(s *Selector) { - WaitFunc(s.waitReady(func(ctxt context.Context, h cdp.Handler, n *cdp.Node) error { - var err error - - // check node has box model - _, err = dom.GetBoxModel(n.NodeID).Do(ctxt, h) - if err != nil { - return nil - } - - // check if any of the parents are not visible ... - var hidden bool - for ; n.Parent != nil; n = n.Parent { - // get style - style, err := css.GetComputedStyleForNode(n.NodeID).Do(ctxt, h) - if err != nil { - return err - } - - // check if hidden - for _, c := range style { - switch c.Name { - case "display": - //log.Printf("%d >>>> %s=%s", n.NodeID, c.Name, c.Value) - hidden = c.Value == "none" - - case "visibility": - //log.Printf("%d >>>> %s=%s", n.NodeID, c.Name, c.Value) - hidden = c.Value != "visible" - - case "hidden": - //log.Printf("%d >>>> %s=%s", n.NodeID, c.Name, c.Value) - hidden = true - } - - if hidden { - return nil - } - } - } - - return ErrVisible - }))(s) -} - -// ElementEnabled is a query option to wait until the element is enabled. -func ElementEnabled(s *Selector) { +// NodeEnabled is a query option to wait until the element is enabled. +func NodeEnabled(s *Selector) { WaitFunc(s.waitReady(func(ctxt context.Context, h cdp.Handler, n *cdp.Node) error { n.RLock() defer n.RUnlock() @@ -468,8 +359,8 @@ func ElementEnabled(s *Selector) { }))(s) } -// ElementSelected is a query option to wait until the element is selected. -func ElementSelected(s *Selector) { +// NodeSelected is a query option to wait until the element is selected. +func NodeSelected(s *Selector) { WaitFunc(s.waitReady(func(ctxt context.Context, h cdp.Handler, n *cdp.Node) error { n.RLock() defer n.RUnlock() @@ -484,9 +375,9 @@ func ElementSelected(s *Selector) { }))(s) } -// ElementNotPresent is a query option to wait until no elements match are +// NodeNotPresent is a query option to wait until no elements match are // present matching the selector. -func ElementNotPresent(s *Selector) { +func NodeNotPresent(s *Selector) { s.exp = 0 WaitFunc(func(ctxt context.Context, h cdp.Handler, n *cdp.Node, ids ...cdp.NodeID) ([]*cdp.Node, error) { if len(ids) != 0 { @@ -519,34 +410,26 @@ func WaitReady(sel interface{}, opts ...QueryOption) Action { // WaitVisible waits until the selected element is visible. func WaitVisible(sel interface{}, opts ...QueryOption) Action { - return Query(sel, append(opts, ElementVisible)...) + return Query(sel, append(opts, NodeVisible)...) } // WaitNotVisible waits until the selected element is not visible. func WaitNotVisible(sel interface{}, opts ...QueryOption) Action { - return Query(sel, append(opts, ElementNotVisible)...) + return Query(sel, append(opts, NodeNotVisible)...) } // WaitEnabled waits until the selected element is enabled (does not have // attribute 'disabled'). func WaitEnabled(sel interface{}, opts ...QueryOption) Action { - return Query(sel, append(opts, ElementEnabled)...) + return Query(sel, append(opts, NodeEnabled)...) } // WaitSelected waits until the element is selected (has attribute 'selected'). func WaitSelected(sel interface{}, opts ...QueryOption) Action { - return Query(sel, append(opts, ElementSelected)...) + return Query(sel, append(opts, NodeSelected)...) } // WaitNotPresent waits until no elements match the specified selector. func WaitNotPresent(sel interface{}, opts ...QueryOption) Action { - return Query(sel, append(opts, ElementNotPresent)...) + return Query(sel, append(opts, NodeNotPresent)...) } - -const ( - // visibleJS is a javascript snippet that returns true or false depending - // on if the specified node's offsetParent is not null. - visibleJS = `(function(a) { - return a[0].offsetParent !== null - })($x('%s'))` -) diff --git a/util.go b/util.go index e196492..8f807e6 100644 --- a/util.go +++ b/util.go @@ -15,11 +15,87 @@ const ( // DefaultCheckDuration is the default time to sleep between a check. DefaultCheckDuration = 50 * time.Millisecond + // DefaultPoolStartPort is the default start port number. + DefaultPoolStartPort = 9000 + + // DefaultPoolEndPort is the default end port number. + DefaultPoolEndPort = 10000 + // EmptyFrameID is the "non-existent" (ie current) frame. EmptyFrameID cdp.FrameID = cdp.FrameID("") // EmptyNodeID is the "non-existent" node id. EmptyNodeID cdp.NodeID = cdp.NodeID(0) + + // textJS is a javascript snippet that returns the concatenated textContent + // of all visible (ie, offsetParent !== null) children. + textJS = `(function(a) { + var s = ''; + for (var i = 0; i < a.length; i++) { + if (a[i].offsetParent !== null) { + s += a[i].textContent; + } + } + return s; + })($x("%s/node()"))` + + // blurJS is a javscript snippet that blurs the specified element. + blurJS = `(function(a) { + a[0].blur(); + return true; + })($x('%s'))` + + // scrollJS is a javascript snippet that scrolls the window to the + // specified x, y coordinates and then returns the actual window x/y after + // execution. + scrollJS = `(function(x, y) { + window.scrollTo(x, y); + return [window.scrollX, window.scrollY]; + })(%d, %d)` + + // submitJS is a javascript snippet that will call the containing form's + // submit function, returning true or false if the call was successful. + submitJS = `(function(a) { + if (a[0].nodeName === 'FORM') { + a[0].submit(); + return true; + } else if (a[0].form !== null) { + a[0].form.submit(); + return true; + } + return false; + })($x('%s'))` + + // resetJS is a javascript snippet that will call the containing form's + // reset function, returning true or false if the call was successful. + resetJS = `(function(a) { + if (a[0].nodeName === 'FORM') { + a[0].reset(); + return true; + } else if (a[0].form !== null) { + a[0].form.reset(); + return true; + } + return false; + })($x('%s'))` + + // valueJS is a javascript snippet that returns the value of a specified + // node. + valueJS = `(function(a) { + return a[0].value; + })($x('%s'))` + + // setValueJS is a javascript snippet that sets the value of the specified + // node, and returns the value. + setValueJS = `(function(a, val) { + return a[0].value = val; + })($x('%s'), '%s')` + + // visibleJS is a javascript snippet that returns true or false depending + // on if the specified node's offsetParent is not null. + visibleJS = `(function(a) { + return a[0].offsetParent !== null + })($x('%s'))` ) // UnmarshalMessage unmarshals the message result or params. @@ -27,8 +103,8 @@ func UnmarshalMessage(msg *cdp.Message) (interface{}, error) { return util.UnmarshalMessage(msg) } -// FrameOp is a frame manipulation operation. -type FrameOp func(*cdp.Frame) +// frameOp is a frame manipulation operation. +type frameOp func(*cdp.Frame) /*func domContentEventFired(f *cdp.Frame) { } @@ -36,7 +112,7 @@ type FrameOp func(*cdp.Frame) func loadEventFired(f *cdp.Frame) { }*/ -func frameAttached(id cdp.FrameID) FrameOp { +func frameAttached(id cdp.FrameID) frameOp { return func(f *cdp.Frame) { f.ParentID = id setFrameState(f, cdp.FrameAttached) @@ -82,8 +158,8 @@ func clearFrameState(f *cdp.Frame, fs cdp.FrameState) { f.State &^= fs } -// NodeOp is a node manipulation operation. -type NodeOp func(*cdp.Node) +// nodeOp is a node manipulation operation. +type nodeOp func(*cdp.Node) func walk(m map[cdp.NodeID]*cdp.Node, n *cdp.Node) { m[n.NodeID] = n @@ -117,14 +193,14 @@ func walk(m map[cdp.NodeID]*cdp.Node, n *cdp.Node) { } } -func setChildNodes(m map[cdp.NodeID]*cdp.Node, nodes []*cdp.Node) NodeOp { +func setChildNodes(m map[cdp.NodeID]*cdp.Node, nodes []*cdp.Node) nodeOp { return func(n *cdp.Node) { n.Children = nodes walk(m, n) } } -func attributeModified(name, value string) NodeOp { +func attributeModified(name, value string) nodeOp { return func(n *cdp.Node) { var found bool @@ -145,7 +221,7 @@ func attributeModified(name, value string) NodeOp { } } -func attributeRemoved(name string) NodeOp { +func attributeRemoved(name string) nodeOp { return func(n *cdp.Node) { var a []string for i := 0; i < len(n.Attributes); i += 2 { @@ -158,66 +234,66 @@ func attributeRemoved(name string) NodeOp { } } -func inlineStyleInvalidated(ids []cdp.NodeID) NodeOp { +func inlineStyleInvalidated(ids []cdp.NodeID) nodeOp { return func(n *cdp.Node) { } } -func characterDataModified(characterData string) NodeOp { +func characterDataModified(characterData string) nodeOp { return func(n *cdp.Node) { n.Value = characterData } } -func childNodeCountUpdated(count int64) NodeOp { +func childNodeCountUpdated(count int64) nodeOp { return func(n *cdp.Node) { n.ChildNodeCount = count } } -func childNodeInserted(m map[cdp.NodeID]*cdp.Node, prevID cdp.NodeID, c *cdp.Node) NodeOp { +func childNodeInserted(m map[cdp.NodeID]*cdp.Node, prevID cdp.NodeID, c *cdp.Node) nodeOp { return func(n *cdp.Node) { n.Children = insertNode(n.Children, prevID, c) walk(m, n) } } -func childNodeRemoved(m map[cdp.NodeID]*cdp.Node, id cdp.NodeID) NodeOp { +func childNodeRemoved(m map[cdp.NodeID]*cdp.Node, id cdp.NodeID) nodeOp { return func(n *cdp.Node) { n.Children = removeNode(n.Children, id) //delete(m, id) } } -func shadowRootPushed(m map[cdp.NodeID]*cdp.Node, c *cdp.Node) NodeOp { +func shadowRootPushed(m map[cdp.NodeID]*cdp.Node, c *cdp.Node) nodeOp { return func(n *cdp.Node) { n.ShadowRoots = append(n.ShadowRoots, c) walk(m, n) } } -func shadowRootPopped(m map[cdp.NodeID]*cdp.Node, id cdp.NodeID) NodeOp { +func shadowRootPopped(m map[cdp.NodeID]*cdp.Node, id cdp.NodeID) nodeOp { return func(n *cdp.Node) { n.ShadowRoots = removeNode(n.ShadowRoots, id) //delete(m, id) } } -func pseudoElementAdded(m map[cdp.NodeID]*cdp.Node, c *cdp.Node) NodeOp { +func pseudoElementAdded(m map[cdp.NodeID]*cdp.Node, c *cdp.Node) nodeOp { return func(n *cdp.Node) { n.PseudoElements = append(n.PseudoElements, c) walk(m, n) } } -func pseudoElementRemoved(m map[cdp.NodeID]*cdp.Node, id cdp.NodeID) NodeOp { +func pseudoElementRemoved(m map[cdp.NodeID]*cdp.Node, id cdp.NodeID) nodeOp { return func(n *cdp.Node) { n.PseudoElements = removeNode(n.PseudoElements, id) //delete(m, id) } } -func distributedNodesUpdated(nodes []*cdp.BackendNode) NodeOp { +func distributedNodesUpdated(nodes []*cdp.BackendNode) nodeOp { return func(n *cdp.Node) { n.DistributedNodes = nodes }