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:
parent
efa0444c39
commit
eb523f01cc
@ -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()
|
||||
|
@ -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)).
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -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)]
|
||||
|
||||
|
@ -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, 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)
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
56
pkg/stdlib/html/mouse_xy.go
Normal file
56
pkg/stdlib/html/mouse_xy.go
Normal 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)
|
||||
}
|
56
pkg/stdlib/html/scroll_xy.go
Normal file
56
pkg/stdlib/html/scroll_xy.go
Normal 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)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user