Fixing issue when node is offscreen and more.

- Adding ScrollIntoNode action.
- Adding isOnViewJS snippet.
- Fixing race issue in some unit tests.
This commit is contained in:
Randy Cahyana 2017-02-25 14:34:53 +07:00
parent d63d697c77
commit f85f3777fa
6 changed files with 113 additions and 31 deletions

View File

@ -55,10 +55,10 @@ func MouseClickNode(n *cdp.Node, opts ...MouseOption) Action {
return ActionFunc(func(ctxt context.Context, h cdp.Handler) error {
var err error
/*err = dom.Focus(n.NodeID).Do(ctxt, h)
err = ScrollIntoNode(n).Do(ctxt, h)
if err != nil {
return err
}*/
}
box, err := dom.GetBoxModel(n.NodeID).Do(ctxt, h)
if err != nil {
@ -78,9 +78,6 @@ func MouseClickNode(n *cdp.Node, opts ...MouseOption) Action {
x /= int64(c / 2)
y /= int64(c / 2)
/*var pos []int64
err = EvaluateAsDevTools(fmt.Sprintf(scrollJS, x, y), &pos).Do(ctxt, h)*/
return MouseClickXY(x, y, opts...).Do(ctxt, h)
})
}

View File

@ -17,7 +17,7 @@ func TestMouseClickXY(t *testing.T) {
defer c.Release()
var err error
err = c.Run(defaultContext, Sleep(time.Millisecond*50))
err = c.Run(defaultContext, Sleep(time.Millisecond*100))
if err != nil {
t.Fatal(err)
}
@ -37,12 +37,14 @@ func TestMouseClickXY(t *testing.T) {
t.Fatalf("test %d got error: %v", i, err)
}
var value string
err = c.Run(defaultContext, Value("#input1", &value, ByID))
time.Sleep(time.Millisecond * 100)
var xstr, ystr string
err = c.Run(defaultContext, Value("#input1", &xstr, ByID))
if err != nil {
t.Fatalf("test %d got error: %v", i, err)
}
x, err := strconv.ParseInt(value, 10, 64)
x, err := strconv.ParseInt(xstr, 10, 64)
if err != nil {
t.Fatalf("test %d got error: %v", i, err)
}
@ -50,11 +52,11 @@ func TestMouseClickXY(t *testing.T) {
t.Fatalf("test %d expected x to be: %d, got: %d", i, test.x, x)
}
err = c.Run(defaultContext, Value("#input2", &value, ByID))
err = c.Run(defaultContext, Value("#input2", &ystr, ByID))
if err != nil {
t.Fatalf("test %d got error: %v", i, err)
}
y, err := strconv.ParseInt(value, 10, 64)
y, err := strconv.ParseInt(ystr, 10, 64)
if err != nil {
t.Fatalf("test %d got error: %v", i, err)
}
@ -66,18 +68,18 @@ func TestMouseClickXY(t *testing.T) {
func TestMouseClickNode(t *testing.T) {
tests := []struct {
exp string
sel, exp string
opt MouseOption
by QueryOption
}{
{"foo", ButtonType(input.ButtonNone), ByID},
{"bar", ButtonType(input.ButtonLeft), ByID},
{"bar-middle", ButtonType(input.ButtonMiddle), ByID},
{"bar-right", ButtonType(input.ButtonRight), ByID},
{"bar2", ClickCount(2), ByID},
{"button2", "foo", ButtonType(input.ButtonNone), ByID},
{"button2", "bar", ButtonType(input.ButtonLeft), ByID},
{"button2", "bar-middle", ButtonType(input.ButtonMiddle), ByID},
{"input3", "bar-right", ButtonType(input.ButtonRight), ByID},
{"input3", "bar-right", ButtonModifiers(input.ModifierNone), ByID},
{"input3", "bar-right", Button("right"), ByID},
}
var err error
for i, test := range tests {
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
t.Parallel()
@ -85,8 +87,9 @@ func TestMouseClickNode(t *testing.T) {
c := testAllocate(t, "input.html")
defer c.Release()
var err error
var nodes []*cdp.Node
err = c.Run(defaultContext, Nodes("#button2", &nodes, test.by))
err = c.Run(defaultContext, Nodes(test.sel, &nodes, test.by))
if err != nil {
t.Fatalf("got error: %v", err)
}
@ -124,7 +127,6 @@ func TestMouseClickOffscreenNode(t *testing.T) {
{"#button3", 10, ByID},
}
var err error
for i, test := range tests {
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
t.Parallel()
@ -132,6 +134,7 @@ func TestMouseClickOffscreenNode(t *testing.T) {
c := testAllocate(t, "input.html")
defer c.Release()
var err error
var nodes []*cdp.Node
err = c.Run(defaultContext, Nodes(test.sel, &nodes, test.by))
if err != nil {
@ -141,6 +144,15 @@ func TestMouseClickOffscreenNode(t *testing.T) {
t.Fatalf("expected nodes to have exactly 1 element, got: %d", len(nodes))
}
var ok bool
err = c.Run(defaultContext, EvaluateAsDevTools(fmt.Sprintf(isOnViewJS, nodes[0].FullXPath()), &ok))
if err != nil {
t.Fatalf("got error: %v", err)
}
if ok {
t.Fatal("expected node to be offscreen")
}
for i := test.exp; i > 0; i-- {
err = c.Run(defaultContext, MouseClickNode(nodes[0]))
if err != nil {
@ -148,7 +160,7 @@ func TestMouseClickOffscreenNode(t *testing.T) {
}
}
time.Sleep(time.Millisecond * 50)
time.Sleep(time.Millisecond * 100)
var value int
err = c.Run(defaultContext, Evaluate("window.document.test_i", &value))
@ -175,7 +187,6 @@ func TestKeyAction(t *testing.T) {
{"#input4", "\n\nfoo\n\nbar\n\n", ByID},
}
var err error
for i, test := range tests {
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
t.Parallel()
@ -183,6 +194,7 @@ func TestKeyAction(t *testing.T) {
c := testAllocate(t, "input.html")
defer c.Release()
var err error
var nodes []*cdp.Node
err = c.Run(defaultContext, Nodes(test.sel, &nodes, test.by))
if err != nil {
@ -227,7 +239,6 @@ func TestKeyActionNode(t *testing.T) {
{"#input4", "\n\nfoo\n\nbar\n\n", ByID},
}
var err error
for i, test := range tests {
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
t.Parallel()
@ -235,6 +246,7 @@ func TestKeyActionNode(t *testing.T) {
c := testAllocate(t, "input.html")
defer c.Release()
var err error
var nodes []*cdp.Node
err = c.Run(defaultContext, Nodes(test.sel, &nodes, test.by))
if err != nil {

25
nav.go
View File

@ -3,6 +3,7 @@ package chromedp
import (
"context"
"errors"
"fmt"
"github.com/knq/chromedp/cdp"
"github.com/knq/chromedp/cdp/page"
@ -130,3 +131,27 @@ func Title(title *string) Action {
return EvaluateAsDevTools(`document.title`, title)
}
// ScrollIntoNode scrolls the window to the specified node.
func ScrollIntoNode(n *cdp.Node, opts ...QueryOption) Action {
return ActionFunc(func(ctxt context.Context, h cdp.Handler) error {
var res bool
err := EvaluateAsDevTools(fmt.Sprintf(isOnViewJS, n.FullXPath()), &res).Do(ctxt, h)
if err != nil {
return err
}
if res {
return nil
}
err = EvaluateAsDevTools(fmt.Sprintf(scrollIntoViewJS, n.FullXPath()), &res).Do(ctxt, h)
if err != nil {
return err
}
if !res {
return fmt.Errorf("could not scroll into node %d", n.NodeID)
}
return nil
})
}

View File

@ -821,4 +821,33 @@ func TestComputedStyle(t *testing.T) {
}
}
// MatchedStyle
func TestMatchedStyle(t *testing.T) {
tests := []struct {
sel string
by QueryOption
}{
{`//*[@id="input1"]`, BySearch},
{`body > input[type="number"]:nth-child(1)`, ByQueryAll},
{`body > input[type="number"]:nth-child(1)`, ByQuery},
{"#input1", ByID},
}
for i, test := range tests {
t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) {
t.Parallel()
c := testAllocate(t, "js.html")
defer c.Release()
time.Sleep(time.Millisecond * 50)
var styles *css.GetMatchedStylesForNodeReturns
err := c.Run(defaultContext, MatchedStyle(test.sel, &styles, test.by))
if err != nil {
t.Fatalf("got error: %v", err)
}
// TODO: Add logic to check if the style returned is true and valid.
})
}
}

9
testdata/input.html vendored
View File

@ -1,6 +1,6 @@
<!doctype html>
<html>
<body style="background-color: white">
<body style="background-color: white;">
<script>
window.document.test_i = 0
</script>
@ -11,7 +11,7 @@
<input id="input3" type="text" value="foo"/>
<textarea id="input4"/></textarea>
<input id="button2" type="button" value="button 2"/>
<input id="button3" type="button" style="float: right;margin-left: 500px;" value="offscreen button" onclick="javascript:window.document.test_i++;return false;"/>
<input id="button3" type="button" style="float: right;margin-right: -500px" value="offscreen button" onclick="javascript:window.document.test_i++;return false;"/>
<script>
document.addEventListener("click", function(){
document.getElementById('input1').value = event.clientX;
@ -26,9 +26,8 @@
e.preventDefault();
document.getElementById('input3').value = 'bar2';return false;
}, false);
document.getElementById('button2').addEventListener('contextmenu', function(e) {
e.preventDefault();
document.getElementById('input3').value = 'bar-right';return false;
document.getElementById('input3').addEventListener('contextmenu', function(e) {
this.value = 'bar-right';return false;
}, false);
document.getElementById('button2').addEventListener('auxclick', function(e) {
e.preventDefault();

20
util.go
View File

@ -38,6 +38,26 @@ const (
return true;
})($x('%s'))`
// isOnViewJS is a javascript snippet that will get the specified node
// position relative to the viewport and returns true or false depending
// on if the specified node is on view port.
isOnViewJS = `(function(a) {
var rect = a[0].getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= window.innerHeight &&
rect.right <= window.innerWidth
);
})($x('%s'))`
// scrollIntoViewJS is a javascript snippet that scrolls the window to the
// specified node.
scrollIntoViewJS = `(function(a) {
a[0].scrollIntoView(true);
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.