diff --git a/examples/input.fql b/examples/input.fql index 302ee4fb..2ed1c9ad 100644 --- a/examples/input.fql +++ b/examples/input.fql @@ -1,6 +1,6 @@ LET g = DOCUMENT("https://www.google.com/", true) -INPUT(g, 'input[name="q"]', "ferret") +INPUT(g, 'input[name="q"]', "ferret", 25) CLICK(g, 'input[name="btnK"]') WAIT_NAVIGATION(g) diff --git a/pkg/html/dynamic/document.go b/pkg/html/dynamic/document.go index b769c3ff..89ad76a5 100644 --- a/pkg/html/dynamic/document.go +++ b/pkg/html/dynamic/document.go @@ -3,6 +3,10 @@ package dynamic import ( "context" "fmt" + "hash/fnv" + "sync" + "time" + "github.com/MontFerret/ferret/pkg/html/dynamic/eval" "github.com/MontFerret/ferret/pkg/html/dynamic/events" "github.com/MontFerret/ferret/pkg/runtime/core" @@ -10,13 +14,11 @@ import ( "github.com/MontFerret/ferret/pkg/runtime/values" "github.com/mafredri/cdp" "github.com/mafredri/cdp/protocol/dom" + "github.com/mafredri/cdp/protocol/input" "github.com/mafredri/cdp/protocol/page" "github.com/mafredri/cdp/rpcc" "github.com/pkg/errors" "github.com/rs/zerolog" - "hash/fnv" - "sync" - "time" ) const BlankPageURL = "about:blank" @@ -416,27 +418,21 @@ func (doc *HTMLDocument) ClickBySelectorAll(selector values.String) (values.Bool return values.False, nil } -func (doc *HTMLDocument) InputBySelector(selector values.String, value core.Value) (values.Boolean, error) { +func (doc *HTMLDocument) InputBySelector(selector values.String, value core.Value, delay values.Int) (values.Boolean, error) { + ctx := context.Background() + + valStr := value.String() + res, err := eval.Eval( doc.client, - fmt.Sprintf( - ` + fmt.Sprintf(` var el = document.querySelector(%s); - if (el == null) { return false; } - - var evt = new window.Event('input', { bubbles: true }); - - el.value = %s - el.dispatchEvent(evt); - + el.focus(); return true; - `, - eval.ParamString(selector.String()), - eval.ParamString(value.String()), - ), + `, eval.ParamString(selector.String())), true, false, ) @@ -445,11 +441,25 @@ func (doc *HTMLDocument) InputBySelector(selector values.String, value core.Valu return values.False, err } - if res.Type() == core.BooleanType { - return res.(values.Boolean), nil + if res.Type() == core.BooleanType && res.(values.Boolean) == values.False { + return values.False, nil } - return values.False, nil + delayMs := time.Duration(delay) + + time.Sleep(delayMs * time.Millisecond) + + for _, ch := range valStr { + for _, ev := range []string{"keyDown", "keyUp"} { + ke := input.NewDispatchKeyEventArgs(ev).SetText(string(ch)) + if err := doc.client.Input.DispatchKeyEvent(ctx, ke); err != nil { + return values.False, err + } + time.Sleep(delayMs * time.Millisecond) + } + } + + return values.True, nil } func (doc *HTMLDocument) WaitForSelector(selector values.String, timeout values.Int) error { diff --git a/pkg/html/dynamic/element.go b/pkg/html/dynamic/element.go index 9ceb2efd..efa85de1 100644 --- a/pkg/html/dynamic/element.go +++ b/pkg/html/dynamic/element.go @@ -3,6 +3,12 @@ package dynamic import ( "context" "encoding/json" + "hash/fnv" + "strconv" + "strings" + "sync" + "time" + "github.com/MontFerret/ferret/pkg/html/common" "github.com/MontFerret/ferret/pkg/html/dynamic/eval" "github.com/MontFerret/ferret/pkg/html/dynamic/events" @@ -10,12 +16,8 @@ import ( "github.com/MontFerret/ferret/pkg/runtime/values" "github.com/mafredri/cdp" "github.com/mafredri/cdp/protocol/dom" + "github.com/mafredri/cdp/protocol/input" "github.com/rs/zerolog" - "hash/fnv" - "strconv" - "strings" - "sync" - "time" ) const DefaultTimeout = time.Second * 30 @@ -586,11 +588,31 @@ func (el *HTMLElement) Click() (values.Boolean, error) { return events.DispatchEvent(ctx, el.client, el.id, "click") } -func (el *HTMLElement) Input(value core.Value) error { +func (el *HTMLElement) Input(value core.Value, delay values.Int) error { ctx, cancel := contextWithTimeout() defer cancel() - return el.client.DOM.SetAttributeValue(ctx, dom.NewSetAttributeValueArgs(el.id, "value", value.String())) + if err := el.client.DOM.Focus(ctx, dom.NewFocusArgs().SetNodeID(el.id)); err != nil { + return err + } + + delayMs := time.Duration(delay) + + time.Sleep(delayMs * time.Millisecond) + + valStr := value.String() + + for _, ch := range valStr { + for _, ev := range []string{"keyDown", "keyUp"} { + ke := input.NewDispatchKeyEventArgs(ev).SetText(string(ch)) + if err := el.client.Input.DispatchKeyEvent(ctx, ke); err != nil { + return err + } + time.Sleep(delayMs * time.Millisecond) + } + } + + return nil } func (el *HTMLElement) IsConnected() values.Boolean { diff --git a/pkg/stdlib/html/input.go b/pkg/stdlib/html/input.go index 3c4bc282..401361c6 100644 --- a/pkg/stdlib/html/input.go +++ b/pkg/stdlib/html/input.go @@ -2,71 +2,92 @@ package html import ( "context" + "github.com/MontFerret/ferret/pkg/html/dynamic" "github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime/values" ) /* - * Sends a value to an underlying input element. + * Types a value to an underlying input element. * @param source (Document | Element) - Event target. * @param valueOrSelector (String) - Selector or a value. * @param value (String) - Target value. + * @param delay (Int, optional) - Waits delay milliseconds between keystrokes * @returns (Boolean) - Returns true if an element was found. */ func Input(_ context.Context, args ...core.Value) (core.Value, error) { - err := core.ValidateArgs(args, 2, 3) + err := core.ValidateArgs(args, 2, 4) if err != nil { return values.None, err } - // TYPE(el, "foobar") - if len(args) == 2 { - arg1 := args[0] + arg1 := args[0] + err = core.ValidateType(arg1, core.HTMLDocumentType, core.HTMLElementType) - err := core.ValidateType(arg1, core.HTMLElementType) + if err != nil { + return values.False, err + } + + switch args[0].(type) { + case *dynamic.HTMLDocument: + + doc, ok := arg1.(*dynamic.HTMLDocument) + + if !ok { + return values.False, core.Errors(core.ErrInvalidType, ErrNotDynamic) + } + + // selector + arg2 := args[1] + err = core.ValidateType(arg2, core.StringType) if err != nil { return values.False, err } + delay := values.Int(0) + if len(args) == 4 { + arg4 := args[3] + + err = core.ValidateType(arg4, core.IntType) + if err != nil { + return values.False, err + } + + delay = arg4.(values.Int) + } + + return doc.InputBySelector(arg2.(values.String), args[2], delay) + + case *dynamic.HTMLElement: el, ok := arg1.(*dynamic.HTMLElement) if !ok { return values.False, core.Errors(core.ErrInvalidType, ErrNotDynamic) } - err = el.Input(args[1]) + delay := values.Int(0) + if len(args) == 3 { + arg3 := args[2] + + err = core.ValidateType(arg3, core.IntType) + if err != nil { + return values.False, err + } + + delay = arg3.(values.Int) + } + + err = el.Input(args[1], delay) if err != nil { return values.False, err } return values.True, nil + default: + return values.False, core.Errors(core.ErrInvalidArgument) } - - arg1 := args[0] - - err = core.ValidateType(arg1, core.HTMLDocumentType) - - if err != nil { - return values.False, err - } - - arg2 := args[1] - - err = core.ValidateType(arg2, core.StringType) - - if err != nil { - return values.False, err - } - - doc, ok := arg1.(*dynamic.HTMLDocument) - - if !ok { - return values.False, core.Errors(core.ErrInvalidType, ErrNotDynamic) - } - - return doc.InputBySelector(arg2.(values.String), args[2]) }