Fixing Visible/NotVisible issues; adding new actions
- Fixed issues with ElementVisible/ElementNotVisible - Added a few extra Javascript based actions
This commit is contained in:
parent
705f3be8e5
commit
6769aefc5e
|
@ -3,7 +3,6 @@ package main
|
|||
import (
|
||||
"context"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
cdp "github.com/knq/chromedp"
|
||||
)
|
||||
|
@ -22,7 +21,8 @@ func main() {
|
|||
}
|
||||
|
||||
// run task list
|
||||
err = c.Run(ctxt, submit(`https://brank.as/`, `#outro-invite-form input[name="email"]`))
|
||||
var res string
|
||||
err = c.Run(ctxt, submit(`https://github.com/search`, `//input[@name="q"]`, `chromedp`, &res))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
@ -38,15 +38,16 @@ func main() {
|
|||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Printf("got: `%s`", res)
|
||||
}
|
||||
|
||||
func submit(urlstr, sel string) cdp.Tasks {
|
||||
func submit(urlstr, sel, q string, res *string) cdp.Tasks {
|
||||
return cdp.Tasks{
|
||||
cdp.Navigate(urlstr),
|
||||
cdp.Sleep(2 * time.Second),
|
||||
cdp.WaitVisible(sel, cdp.ByQuery),
|
||||
cdp.WaitNotVisible(`div.v-middle > div.la-ball-clip-rotate`, cdp.ByQuery),
|
||||
cdp.Submit(sel, cdp.ElementVisible, cdp.ByQuery),
|
||||
cdp.Sleep(120 * time.Second),
|
||||
cdp.WaitVisible(sel),
|
||||
cdp.SendKeys(sel, q),
|
||||
cdp.Submit(sel),
|
||||
cdp.Text(`//*[@id="js-pjax-container"]/div[2]/div/div[2]/ul/li/p`, res),
|
||||
}
|
||||
}
|
||||
|
|
5
input.go
5
input.go
|
@ -55,8 +55,9 @@ func MouseClickXY(x, y int64, opts ...MouseOption) Action {
|
|||
})
|
||||
}
|
||||
|
||||
// MouseActionNode dispatches a mouse event at the center of a specified node.
|
||||
func MouseActionNode(n *cdp.Node, opts ...MouseOption) Action {
|
||||
// MouseClickNode dispatches a mouse left button click event at the center of a
|
||||
// specified node.
|
||||
func MouseClickNode(n *cdp.Node, opts ...MouseOption) Action {
|
||||
return ActionFunc(func(ctxt context.Context, h cdp.FrameHandler) error {
|
||||
var err error
|
||||
|
||||
|
|
187
query.go
187
query.go
|
@ -22,6 +22,36 @@ var (
|
|||
ErrInvalidBoxModel = errors.New("invalid box model")
|
||||
)
|
||||
|
||||
// Nodes retrieves the DOM nodes matching the selector.
|
||||
func Nodes(sel interface{}, nodes *[]*cdp.Node, opts ...QueryOption) Action {
|
||||
if nodes == nil {
|
||||
panic("nodes cannot be nil")
|
||||
}
|
||||
|
||||
return QueryAfter(sel, func(ctxt context.Context, h cdp.FrameHandler, n ...*cdp.Node) error {
|
||||
*nodes = n
|
||||
return nil
|
||||
}, opts...)
|
||||
}
|
||||
|
||||
// NodeIDs returns the node IDs of the matching selector.
|
||||
func NodeIDs(sel interface{}, ids *[]cdp.NodeID, opts ...QueryOption) Action {
|
||||
if ids == nil {
|
||||
panic("nodes cannot be nil")
|
||||
}
|
||||
|
||||
return QueryAfter(sel, func(ctxt context.Context, h cdp.FrameHandler, nodes ...*cdp.Node) error {
|
||||
nodeIDs := make([]cdp.NodeID, len(nodes))
|
||||
for i, n := range nodes {
|
||||
nodeIDs[i] = n.NodeID
|
||||
}
|
||||
|
||||
*ids = nodeIDs
|
||||
|
||||
return nil
|
||||
}, opts...)
|
||||
}
|
||||
|
||||
// Focus focuses the first element returned by the selector.
|
||||
func Focus(sel interface{}, opts ...QueryOption) Action {
|
||||
return QueryAfter(sel, func(ctxt context.Context, h cdp.FrameHandler, nodes ...*cdp.Node) error {
|
||||
|
@ -33,6 +63,40 @@ func Focus(sel interface{}, opts ...QueryOption) Action {
|
|||
}, opts...)
|
||||
}
|
||||
|
||||
// Blur unfocuses (blurs) the first element returned by the selector.
|
||||
func Blur(sel interface{}, opts ...QueryOption) Action {
|
||||
return QueryAfter(sel, func(ctxt context.Context, h cdp.FrameHandler, nodes ...*cdp.Node) error {
|
||||
if len(nodes) < 1 {
|
||||
return fmt.Errorf("selector `%s` did not return any nodes", sel)
|
||||
}
|
||||
|
||||
var res bool
|
||||
err := EvaluateAsDevTools(fmt.Sprintf(blurJS, nodes[0].FullXPath()), &res).Do(ctxt, h)
|
||||
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.
|
||||
func Text(sel interface{}, text *string, opts ...QueryOption) Action {
|
||||
if text == nil {
|
||||
panic("text cannot be nil")
|
||||
}
|
||||
|
||||
return QueryAfter(sel, func(ctxt context.Context, h cdp.FrameHandler, nodes ...*cdp.Node) error {
|
||||
if len(nodes) < 1 {
|
||||
return fmt.Errorf("selector `%s` did not return any nodes", sel)
|
||||
}
|
||||
|
||||
return EvaluateAsDevTools(fmt.Sprintf(textJS, nodes[0].FullXPath()), text).Do(ctxt, h)
|
||||
}, opts...)
|
||||
}
|
||||
|
||||
// Clear clears input and textarea fields of their values.
|
||||
func Clear(sel interface{}, opts ...QueryOption) Action {
|
||||
return QueryAfter(sel, func(ctxt context.Context, h cdp.FrameHandler, nodes ...*cdp.Node) error {
|
||||
|
@ -74,36 +138,6 @@ func Clear(sel interface{}, opts ...QueryOption) Action {
|
|||
}, opts...)
|
||||
}
|
||||
|
||||
// Nodes retrieves the DOM nodes matching the selector.
|
||||
func Nodes(sel interface{}, nodes *[]*cdp.Node, opts ...QueryOption) Action {
|
||||
if nodes == nil {
|
||||
panic("nodes cannot be nil")
|
||||
}
|
||||
|
||||
return QueryAfter(sel, func(ctxt context.Context, h cdp.FrameHandler, n ...*cdp.Node) error {
|
||||
*nodes = n
|
||||
return nil
|
||||
}, opts...)
|
||||
}
|
||||
|
||||
// NodeIDs returns the node IDs of the matching selector.
|
||||
func NodeIDs(sel interface{}, ids *[]cdp.NodeID, opts ...QueryOption) Action {
|
||||
if ids == nil {
|
||||
panic("nodes cannot be nil")
|
||||
}
|
||||
|
||||
return QueryAfter(sel, func(ctxt context.Context, h cdp.FrameHandler, nodes ...*cdp.Node) error {
|
||||
nodeIDs := make([]cdp.NodeID, len(nodes))
|
||||
for i, n := range nodes {
|
||||
nodeIDs[i] = n.NodeID
|
||||
}
|
||||
|
||||
*ids = nodeIDs
|
||||
|
||||
return nil
|
||||
}, 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 {
|
||||
|
@ -155,21 +189,6 @@ func SetValue(sel interface{}, value string, opts ...QueryOption) Action {
|
|||
}, opts...)
|
||||
}
|
||||
|
||||
// Text retrieves the text of the first element matching the selector.
|
||||
func Text(sel interface{}, text *string, opts ...QueryOption) Action {
|
||||
if text == nil {
|
||||
panic("text cannot be nil")
|
||||
}
|
||||
|
||||
return QueryAfter(sel, func(ctxt context.Context, h cdp.FrameHandler, nodes ...*cdp.Node) error {
|
||||
if len(nodes) < 1 {
|
||||
return fmt.Errorf("selector `%s` did not return any nodes", sel)
|
||||
}
|
||||
|
||||
return EvaluateAsDevTools(fmt.Sprintf(textJS, nodes[0].FullXPath()), text).Do(ctxt, h)
|
||||
}, opts...)
|
||||
}
|
||||
|
||||
// Attributes retrieves the attributes for the specified element.
|
||||
func Attributes(sel interface{}, attributes *map[string]string, opts ...QueryOption) Action {
|
||||
if attributes == nil {
|
||||
|
@ -270,7 +289,7 @@ func Click(sel interface{}, opts ...QueryOption) Action {
|
|||
return fmt.Errorf("selector `%s` did not return any nodes", sel)
|
||||
}
|
||||
|
||||
return MouseActionNode(nodes[0], ClickCount(1)).Do(ctxt, h)
|
||||
return MouseClickNode(nodes[0]).Do(ctxt, h)
|
||||
}, append(opts, ElementVisible)...)
|
||||
}
|
||||
|
||||
|
@ -281,21 +300,23 @@ func DoubleClick(sel interface{}, opts ...QueryOption) Action {
|
|||
return fmt.Errorf("selector `%s` did not return any nodes", sel)
|
||||
}
|
||||
|
||||
return MouseActionNode(nodes[0], ButtonLeft, ClickCount(2)).Do(ctxt, h)
|
||||
return MouseClickNode(nodes[0], ClickCount(2)).Do(ctxt, h)
|
||||
}, append(opts, ElementVisible)...)
|
||||
}
|
||||
|
||||
// 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.FrameHandler, nodes ...*cdp.Node) error {
|
||||
if len(nodes) < 1 {
|
||||
return fmt.Errorf("selector `%s` did not return any nodes", sel)
|
||||
}
|
||||
|
||||
return MouseActionNode(nodes[0], ButtonNone).Do(ctxt, h)
|
||||
}, append(opts, ElementVisible)...)
|
||||
}
|
||||
//func Hover(sel interface{}, opts ...QueryOption) Action {
|
||||
// return QueryAfter(sel, func(ctxt context.Context, h cdp.FrameHandler, 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.
|
||||
func SendKeys(sel interface{}, v string, opts ...QueryOption) Action {
|
||||
|
@ -369,7 +390,8 @@ func Screenshot(sel interface{}, picbuf *[]byte, opts ...QueryOption) Action {
|
|||
}, append(opts, ElementVisible)...)
|
||||
}
|
||||
|
||||
// Submit is an action that submits whatever form the first element belongs to.
|
||||
// Submit is an action that submits whatever form the first element matching
|
||||
// the selector belongs to.
|
||||
func Submit(sel interface{}, opts ...QueryOption) Action {
|
||||
return QueryAfter(sel, func(ctxt context.Context, h cdp.FrameHandler, nodes ...*cdp.Node) error {
|
||||
if len(nodes) < 1 {
|
||||
|
@ -383,7 +405,29 @@ func Submit(sel interface{}, opts ...QueryOption) Action {
|
|||
}
|
||||
|
||||
if !res {
|
||||
return fmt.Errorf("submit on node %d returned false", nodes[0].NodeID)
|
||||
return fmt.Errorf("could not call submit on node %d", nodes[0].NodeID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}, opts...)
|
||||
}
|
||||
|
||||
// Reset is an action that resets whatever form the first element matching the
|
||||
// selector belongs to.
|
||||
func Reset(sel interface{}, opts ...QueryOption) Action {
|
||||
return QueryAfter(sel, func(ctxt context.Context, h cdp.FrameHandler, nodes ...*cdp.Node) error {
|
||||
if len(nodes) < 1 {
|
||||
return fmt.Errorf("selector `%s` did not return any nodes", sel)
|
||||
}
|
||||
|
||||
var res bool
|
||||
err := EvaluateAsDevTools(fmt.Sprintf(resetJS, nodes[0].FullXPath()), &res).Do(ctxt, h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !res {
|
||||
return fmt.Errorf("could not call reset on node %d", nodes[0].NodeID)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -403,6 +447,12 @@ const (
|
|||
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.
|
||||
|
@ -414,8 +464,25 @@ const (
|
|||
// 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].form !== null) {
|
||||
return a[0].form.submit();
|
||||
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'))`
|
||||
|
|
30
sel.go
30
sel.go
|
@ -301,8 +301,21 @@ func ElementReady(s *Selector) {
|
|||
// ElementVisible is a query option to wait until the element is visible.
|
||||
func ElementVisible(s *Selector) {
|
||||
WaitFunc(s.waitReady(func(ctxt context.Context, h cdp.FrameHandler, n *cdp.Node) error {
|
||||
var err error
|
||||
|
||||
// check box model
|
||||
_, err = dom.GetBoxModel(n.NodeID).Do(ctxt, h)
|
||||
if err != nil {
|
||||
if isCouldNotComputeBoxModelError(err) {
|
||||
return ErrNotVisible
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// check offsetParent
|
||||
var res bool
|
||||
err := EvaluateAsDevTools(fmt.Sprintf(visibleJS, n.FullXPath()), &res).Do(ctxt, h)
|
||||
err = EvaluateAsDevTools(fmt.Sprintf(visibleJS, n.FullXPath()), &res).Do(ctxt, h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -316,8 +329,21 @@ func ElementVisible(s *Selector) {
|
|||
// ElementNotVisible is a query option to wait until the element is not visible.
|
||||
func ElementNotVisible(s *Selector) {
|
||||
WaitFunc(s.waitReady(func(ctxt context.Context, h cdp.FrameHandler, n *cdp.Node) error {
|
||||
var err error
|
||||
|
||||
// check box model
|
||||
_, err = dom.GetBoxModel(n.NodeID).Do(ctxt, h)
|
||||
if err != nil {
|
||||
if isCouldNotComputeBoxModelError(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// check offsetParent
|
||||
var res bool
|
||||
err := EvaluateAsDevTools(fmt.Sprintf(visibleJS, n.FullXPath()), &res).Do(ctxt, h)
|
||||
err = EvaluateAsDevTools(fmt.Sprintf(visibleJS, n.FullXPath()), &res).Do(ctxt, h)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
7
util.go
7
util.go
|
@ -260,3 +260,10 @@ func removeNode(n []*cdp.Node, id cdp.NodeID) []*cdp.Node {
|
|||
|
||||
return append(n[:i], n[i+1:]...)
|
||||
}
|
||||
|
||||
// isCouldNotComputeBoxModelError unwraps the err as a MessagError and
|
||||
// determines if it is a compute box model error.
|
||||
func isCouldNotComputeBoxModelError(err error) bool {
|
||||
e, ok := err.(*cdp.MessageError)
|
||||
return ok && e.Code == -32000 && e.Message == "Could not compute box model."
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user