1
0
mirror of https://github.com/MontFerret/ferret.git synced 2025-03-05 15:16:07 +02:00

Feature/#221 mouse events (#237)

* Initial work

* Added MoveMouseByXY and ScrollByXY

* Fixed liniting issues
This commit is contained in:
Tim Voronov 2019-02-23 17:52:01 -05:00 committed by GitHub
parent efa0444c39
commit eb523f01cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 479 additions and 18 deletions

View File

@ -483,7 +483,7 @@ func (doc *HTMLDocument) SelectBySelector(ctx context.Context, selector values.S
return nil, core.TypeError(types.Array, res.Type())
}
func (doc *HTMLDocument) HoverBySelector(ctx context.Context, selector values.String) error {
func (doc *HTMLDocument) MoveMouseBySelector(ctx context.Context, selector values.String) error {
err := doc.ScrollBySelector(ctx, selector)
if err != nil {
@ -519,6 +519,13 @@ func (doc *HTMLDocument) HoverBySelector(ctx context.Context, selector values.St
)
}
func (doc *HTMLDocument) MoveMouseByXY(ctx context.Context, x, y values.Float) error {
return doc.client.Input.DispatchMouseEvent(
ctx,
input.NewDispatchMouseEventArgs("mouseMoved", float64(x), float64(y)),
)
}
func (doc *HTMLDocument) WaitForSelector(ctx context.Context, selector values.String) error {
task := events.NewEvalWaitTask(
doc.client,
@ -863,6 +870,21 @@ func (doc *HTMLDocument) ScrollBySelector(ctx context.Context, selector values.S
return err
}
func (doc *HTMLDocument) ScrollByXY(ctx context.Context, x, y values.Float) error {
_, err := eval.Eval(ctx, doc.client, fmt.Sprintf(`
window.scrollBy({
top: %s,
left: %s,
behavior: 'instant'
});
`,
eval.ParamFloat(float64(x)),
eval.ParamFloat(float64(y)),
), false, false)
return err
}
func (doc *HTMLDocument) handlePageLoad(ctx context.Context, _ interface{}) {
doc.Lock()
defer doc.Unlock()

View File

@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"strconv"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
@ -19,6 +20,10 @@ func ParamString(param string) string {
return "`" + param + "`"
}
func ParamFloat(param float64) string {
return strconv.FormatFloat(param, 'f', 6, 64)
}
func Eval(ctx context.Context, client *cdp.Client, exp string, ret bool, async bool) (core.Value, error) {
args := runtime.
NewEvaluateArgs(PrepareEval(exp)).

View File

@ -189,10 +189,6 @@ func (doc *HTMLDocument) SelectBySelector(_ context.Context, _ values.String, _
return nil, core.ErrNotSupported
}
func (doc *HTMLDocument) HoverBySelector(_ context.Context, _ values.String) error {
return core.ErrNotSupported
}
func (doc *HTMLDocument) PrintToPDF(_ context.Context, _ drivers.PDFParams) (values.Binary, error) {
return nil, core.ErrNotSupported
}
@ -213,6 +209,18 @@ func (doc *HTMLDocument) ScrollBySelector(_ context.Context, _ values.String) er
return core.ErrNotSupported
}
func (doc *HTMLDocument) ScrollByXY(_ context.Context, _, _ values.Float) error {
return core.ErrNotSupported
}
func (doc *HTMLDocument) MoveMouseBySelector(_ context.Context, _ values.String) error {
return core.ErrNotSupported
}
func (doc *HTMLDocument) MoveMouseByXY(_ context.Context, _, _ values.Float) error {
return core.ErrNotSupported
}
func (doc *HTMLDocument) WaitForNavigation(_ context.Context) error {
return core.ErrNotSupported
}

View File

@ -102,8 +102,6 @@ type (
SelectBySelector(ctx context.Context, selector values.String, value *values.Array) (*values.Array, error)
HoverBySelector(ctx context.Context, selector values.String) error
PrintToPDF(ctx context.Context, params PDFParams) (values.Binary, error)
CaptureScreenshot(ctx context.Context, params ScreenshotParams) (values.Binary, error)
@ -114,6 +112,12 @@ type (
ScrollBySelector(ctx context.Context, selector values.String) error
ScrollByXY(ctx context.Context, x, y values.Float) error
MoveMouseByXY(ctx context.Context, x, y values.Float) error
MoveMouseBySelector(ctx context.Context, selector values.String) error
WaitForNavigation(ctx context.Context) error
WaitForSelector(ctx context.Context, selector values.String) error

View File

@ -7,6 +7,7 @@ import (
"hash/fnv"
"reflect"
"sort"
"strconv"
"time"
"github.com/MontFerret/ferret/pkg/runtime/core"
@ -257,6 +258,44 @@ func ToBoolean(input core.Value) core.Value {
}
}
func ToFloat(input core.Value) (Float, error) {
switch val := input.(type) {
case Float:
return val, nil
case Int:
return Float(val), nil
case String:
i, err := strconv.ParseFloat(string(val), 64)
if err != nil {
return ZeroFloat, err
}
return Float(i), nil
default:
return ZeroFloat, core.TypeError(input.Type(), types.Int, types.Float, types.String)
}
}
func ToInt(input core.Value) (Int, error) {
switch val := input.(type) {
case Int:
return val, nil
case Float:
return Int(val), nil
case String:
i, err := strconv.ParseInt(string(val), 10, 64)
if err != nil {
return ZeroInt, err
}
return Int(i), nil
default:
return ZeroInt, core.TypeError(input.Type(), types.Int, types.Float, types.String)
}
}
func ToArray(ctx context.Context, input core.Value) (core.Value, error) {
switch value := input.(type) {
case Boolean,
@ -277,7 +316,7 @@ func ToArray(ctx context.Context, input core.Value) (core.Value, error) {
return true
})
return value, nil
return arr, nil
case core.Iterable:
iterator, err := value.Iterate(ctx)

View File

@ -155,5 +155,243 @@ func TestHelpers(t *testing.T) {
So(qaz, ShouldEqual, values.NewString("foobar"))
})
})
Convey("ToBoolean", func() {
Convey("Should convert values", func() {
inputs := [][]core.Value{
{
values.None,
values.False,
},
{
values.True,
values.True,
},
{
values.False,
values.False,
},
{
values.NewInt(1),
values.True,
},
{
values.NewInt(0),
values.False,
},
{
values.NewFloat(1),
values.True,
},
{
values.NewFloat(0),
values.False,
},
{
values.NewString("Foo"),
values.True,
},
{
values.EmptyString,
values.False,
},
{
values.NewCurrentDateTime(),
values.True,
},
{
values.NewArray(1),
values.True,
},
{
values.NewObject(),
values.True,
},
{
values.NewBinary([]byte("")),
values.True,
},
}
for _, pair := range inputs {
actual := values.ToBoolean(pair[0])
expected := pair[1]
So(actual, ShouldEqual, expected)
}
})
})
Convey("ToFloat", func() {
Convey("Should convert Int", func() {
input := values.NewInt(100)
output, err := values.ToFloat(input)
So(err, ShouldBeNil)
So(output, ShouldEqual, values.NewFloat(100))
})
Convey("Should convert Float", func() {
input := values.NewFloat(100)
output, err := values.ToFloat(input)
So(err, ShouldBeNil)
So(output, ShouldEqual, values.NewFloat(100))
})
Convey("Should convert String", func() {
input := values.NewString("100.1")
output, err := values.ToFloat(input)
So(err, ShouldBeNil)
So(output, ShouldEqual, values.NewFloat(100.1))
})
Convey("Should NOT convert other types", func() {
inputs := []core.Value{
values.NewBoolean(true),
values.NewCurrentDateTime(),
values.NewArray(1),
values.NewObject(),
values.NewBinary([]byte("")),
}
for _, input := range inputs {
_, err := values.ToFloat(input)
So(err, ShouldNotBeNil)
}
})
})
Convey("ToInt", func() {
Convey("Should convert Int", func() {
input := values.NewInt(100)
output, err := values.ToInt(input)
So(err, ShouldBeNil)
So(output, ShouldEqual, values.NewInt(100))
})
Convey("Should convert Float", func() {
input := values.NewFloat(100.1)
output, err := values.ToInt(input)
So(err, ShouldBeNil)
So(output, ShouldEqual, values.NewInt(100))
})
Convey("Should convert String", func() {
input := values.NewString("100")
output, err := values.ToInt(input)
So(err, ShouldBeNil)
So(output, ShouldEqual, values.NewInt(100))
})
Convey("Should NOT convert other types", func() {
inputs := []core.Value{
values.NewBoolean(true),
values.NewCurrentDateTime(),
values.NewArray(1),
values.NewObject(),
values.NewBinary([]byte("")),
}
for _, input := range inputs {
_, err := values.ToInt(input)
So(err, ShouldNotBeNil)
}
})
})
Convey("ToArray", func() {
Convey("Should convert primitives", func() {
dt := values.NewCurrentDateTime()
inputs := [][]core.Value{
{
values.None,
values.NewArray(0),
},
{
values.True,
values.NewArrayWith(values.True),
},
{
values.NewInt(1),
values.NewArrayWith(values.NewInt(1)),
},
{
values.NewFloat(1),
values.NewArrayWith(values.NewFloat(1)),
},
{
values.NewString("foo"),
values.NewArrayWith(values.NewString("foo")),
},
{
dt,
values.NewArrayWith(dt),
},
}
for _, pairs := range inputs {
actual, err := values.ToArray(context.Background(), pairs[0])
expected := pairs[1]
So(err, ShouldBeNil)
So(actual.Compare(expected), ShouldEqual, 0)
}
})
Convey("Should create a copy of a given array", func() {
vals := []core.Value{
values.NewInt(1),
values.NewInt(2),
values.NewInt(3),
values.NewInt(4),
values.NewArray(10),
values.NewObject(),
}
input := values.NewArrayWith(vals...)
output, err := values.ToArray(context.Background(), input)
So(err, ShouldBeNil)
arr := output.(*values.Array)
So(input == arr, ShouldBeFalse)
So(arr.Length() == input.Length(), ShouldBeTrue)
for idx := range vals {
expected := input.Get(values.NewInt(idx))
actual := arr.Get(values.NewInt(idx))
// same ref
So(actual == expected, ShouldBeTrue)
So(actual.Compare(expected), ShouldEqual, 0)
}
})
Convey("Should convert object to an array", func() {
input := values.NewObjectWith(
values.NewObjectProperty("foo", values.NewString("bar")),
values.NewObjectProperty("baz", values.NewInt(1)),
values.NewObjectProperty("qaz", values.NewObject()),
)
output, err := values.ToArray(context.Background(), input)
So(err, ShouldBeNil)
arr := output.(*values.Array).Sort()
So(arr.String(), ShouldEqual, "[1,\"bar\",{}]")
So(arr.Get(values.NewInt(2)) == input.MustGet("qaz"), ShouldBeTrue)
})
})
})
}

View File

@ -196,6 +196,12 @@ func (t *Object) ForEach(predicate ObjectPredicate) {
}
}
func (t *Object) MustGet(key String) core.Value {
val, _ := t.Get(key)
return val
}
func (t *Object) Get(key String) (core.Value, Boolean) {
val, found := t.value[string(key)]

View File

@ -27,22 +27,46 @@ func Hover(ctx context.Context, args ...core.Value) (core.Value, error) {
return values.None, err
}
if len(args) == 2 {
selector := values.EmptyString
if len(args) > 1 {
err = core.ValidateType(args[1], types.String)
if err != nil {
return values.None, err
}
// Document with a selector
doc := args[0].(drivers.HTMLDocument)
selector := args[1].(values.String)
return values.None, doc.HoverBySelector(ctx, selector)
selector = args[1].(values.String)
}
// Element
el := args[0].(drivers.HTMLElement)
switch n := args[0].(type) {
case drivers.HTMLDocument:
if selector == values.EmptyString {
return values.None, core.Error(core.ErrMissedArgument, "selector")
}
return values.None, el.Hover(ctx)
return values.None, n.MoveMouseBySelector(ctx, selector)
case drivers.HTMLElement:
if selector == values.EmptyString {
return values.None, n.Hover(ctx)
}
found := n.QuerySelector(ctx, selector)
if found == values.None {
return values.None, core.Errorf(core.ErrNotFound, "element by selector %s", selector)
}
el, ok := found.(drivers.HTMLElement)
if !ok {
return values.None, core.Errorf(core.ErrNotFound, "element by selector %s", selector)
}
defer el.Close()
return values.None, el.Hover(ctx)
default:
return values.None, core.TypeError(n.Type(), drivers.HTMLDocumentType, drivers.HTMLElementType)
}
}

View File

@ -2,11 +2,12 @@ package html
import (
"context"
"time"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
"time"
)
const defaultTimeout = 5000
@ -27,12 +28,14 @@ func NewLib() map[string]core.Function {
"INNER_TEXT": InnerText,
"INNER_TEXT_ALL": InnerTextAll,
"INPUT": Input,
"MOUSE": MouseMoveXY,
"NAVIGATE": Navigate,
"NAVIGATE_BACK": NavigateBack,
"NAVIGATE_FORWARD": NavigateForward,
"PAGINATION": Pagination,
"PDF": PDF,
"SCREENSHOT": Screenshot,
"SCROLL": ScrollXY,
"SCROLL_BOTTOM": ScrollBottom,
"SCROLL_ELEMENT": ScrollInto,
"SCROLL_TOP": ScrollTop,

View File

@ -0,0 +1,56 @@
package html
import (
"context"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// MouseMoveXY moves mouse by given coordinates.
// @param doc (HTMLDocument) - HTML document.
// @param x (Int|Float) - X coordinate.
// @param y (Int|Float) - Y coordinate.
func MouseMoveXY(ctx context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 3, 3)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], drivers.HTMLDocumentType)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[1], types.Int, types.Float)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[2], types.Int, types.Float)
if err != nil {
return values.None, err
}
x, err := values.ToFloat(args[0])
if err != nil {
return values.None, err
}
y, err := values.ToFloat(args[1])
if err != nil {
return values.None, err
}
doc := args[0].(drivers.HTMLDocument)
return values.None, doc.MoveMouseByXY(ctx, x, y)
}

View File

@ -0,0 +1,56 @@
package html
import (
"context"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// ScrollXY scrolls by given coordinates.
// @param doc (HTMLDocument) - HTML document.
// @param x (Int|Float) - X coordinate.
// @param y (Int|Float) - Y coordinate.
func ScrollXY(ctx context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 3, 3)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[0], drivers.HTMLDocumentType)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[1], types.Int, types.Float)
if err != nil {
return values.None, err
}
err = core.ValidateType(args[2], types.Int, types.Float)
if err != nil {
return values.None, err
}
x, err := values.ToFloat(args[0])
if err != nil {
return values.None, err
}
y, err := values.ToFloat(args[1])
if err != nil {
return values.None, err
}
doc := args[0].(drivers.HTMLDocument)
return values.None, doc.ScrollByXY(ctx, x, y)
}