1
0
mirror of https://github.com/MontFerret/ferret.git synced 2025-02-13 13:58:32 +02:00

Added INPUT_CLEAR function (#366)

* Added INPUT_CLEAR function

* Fixed linting issue

* Fixed formatting
This commit is contained in:
Tim Voronov 2019-09-01 16:09:35 -04:00 committed by GitHub
parent 2a8135657d
commit af1125c8e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1705 additions and 88 deletions

View File

@ -0,0 +1,12 @@
LET url = @dynamic + "?redirect=/forms"
LET doc = DOCUMENT(url, true)
WAIT_ELEMENT(doc, "form")
LET input = ELEMENT(doc, "#text_input")
INPUT(input, "Foo", 100)
INPUT_CLEAR(input)
RETURN EXPECT("", INNER_TEXT(doc, "#text_output"))

View File

@ -0,0 +1,14 @@
LET url = @dynamic + "?redirect=/forms"
LET doc = DOCUMENT(url, true)
WAIT_ELEMENT(doc, "form")
LET form = ELEMENT(doc, "#page-form")
INPUT(form, "#text_input", "foo")
INPUT_CLEAR(form, "#text_input")
LET input = ELEMENT(doc, "#text_input")
LET output = ELEMENT(doc, "#text_output")
RETURN EXPECT("", output.innerText)

View File

@ -0,0 +1,12 @@
LET url = @dynamic + "?redirect=/forms"
LET doc = DOCUMENT(url, true)
WAIT_ELEMENT(doc, "form")
LET form = ELEMENT(doc, "#page-form")
INPUT(form, "#text_input", "foo")
LET output = ELEMENT(doc, "#text_output")
RETURN EXPECT(output.innerText, "foo")

View File

@ -325,10 +325,6 @@ func (doc *HTMLDocument) ClickBySelectorAll(ctx context.Context, selector values
return doc.element.ClickBySelectorAll(ctx, selector)
}
func (doc *HTMLDocument) InputBySelector(ctx context.Context, selector values.String, value core.Value, delay values.Int) error {
return doc.input.TypeBySelector(ctx, doc.element.id.nodeID, selector, value, delay)
}
func (doc *HTMLDocument) SelectBySelector(ctx context.Context, selector values.String, value *values.Array) (*values.Array, error) {
return doc.input.SelectBySelector(ctx, doc.element.id.nodeID, selector, value)
}

View File

@ -1068,7 +1068,27 @@ func (el *HTMLElement) Input(ctx context.Context, value core.Value, delay values
return core.Error(core.ErrInvalidOperation, "element is not an <input> element.")
}
return el.input.Type(ctx, el.id.objectID, value, delay)
return el.input.Type(ctx, el.id.objectID, input.TypeParams{
Text: value,
Clear: false,
Delay: delay,
})
}
func (el *HTMLElement) InputBySelector(ctx context.Context, selector values.String, value core.Value, delay values.Int) error {
return el.input.TypeBySelector(ctx, el.id.nodeID, selector, input.TypeParams{
Text: value,
Clear: false,
Delay: delay,
})
}
func (el *HTMLElement) Clear(ctx context.Context) error {
return el.input.Clear(ctx, el.id.objectID)
}
func (el *HTMLElement) ClearBySelector(ctx context.Context, selector values.String) error {
return el.input.ClearBySelector(ctx, el.id.nodeID, selector)
}
func (el *HTMLElement) Select(ctx context.Context, value *values.Array) (*values.Array, error) {
@ -1215,6 +1235,10 @@ func (el *HTMLElement) handleAttrModified(ctx context.Context, message interface
return
}
if el.IsDetached() {
return
}
el.attributes.Mutate(ctx, func(v core.Value, err error) {
if err != nil {
el.logError(err).Msg("failed to update element")
@ -1255,6 +1279,10 @@ func (el *HTMLElement) handleAttrRemoved(ctx context.Context, message interface{
return
}
if el.IsDetached() {
return
}
el.attributes.Mutate(ctx, func(v core.Value, err error) {
if err != nil {
el.logError(err).Msg("failed to update element")
@ -1287,6 +1315,13 @@ func (el *HTMLElement) handleChildrenCountChanged(ctx context.Context, message i
return
}
if el.IsDetached() {
return
}
el.mu.Lock()
defer el.mu.Unlock()
node, err := el.client.DOM.DescribeNode(
ctx,
dom.NewDescribeNodeArgs().SetObjectID(el.id.objectID),
@ -1298,9 +1333,6 @@ func (el *HTMLElement) handleChildrenCountChanged(ctx context.Context, message i
return
}
el.mu.Lock()
defer el.mu.Unlock()
el.children = createChildrenArray(node.Node.Children)
}
@ -1319,6 +1351,10 @@ func (el *HTMLElement) handleChildInserted(ctx context.Context, message interfac
prevID := reply.PreviousNodeID
nextID := reply.Node.NodeID
if el.IsDetached() {
return
}
el.mu.Lock()
defer el.mu.Unlock()
@ -1375,6 +1411,10 @@ func (el *HTMLElement) handleChildRemoved(ctx context.Context, message interface
targetIDx := -1
targetID := reply.NodeID
if el.IsDetached() {
return
}
el.mu.Lock()
defer el.mu.Unlock()

View File

@ -2,15 +2,45 @@ package input
import (
"context"
"github.com/pkg/errors"
"time"
"github.com/mafredri/cdp"
"github.com/mafredri/cdp/protocol/input"
)
type Keyboard struct {
client *cdp.Client
}
const DefaultDelay = 25
type (
KeyboardModifier int
KeyboardLocation int
KeyboardKey struct {
KeyCode int
Key string
Code string
Modifier KeyboardModifier
Location KeyboardLocation
}
Keyboard struct {
client *cdp.Client
}
)
const (
KeyboardModifierNone KeyboardModifier = 0
KeyboardModifierAlt KeyboardModifier = 1
KeyboardModifierCtrl KeyboardModifier = 2
KeyboardModifierCmd KeyboardModifier = 4
KeyboardModifierShift KeyboardModifier = 8
// 1=Left, 2=Right
KeyboardLocationNone KeyboardLocation = 0
KeyboardLocationLeft KeyboardLocation = 1
KeyboardLocationRight KeyboardLocation = 2
)
func NewKeyboard(client *cdp.Client) *Keyboard {
return &Keyboard{client}
@ -40,7 +70,7 @@ func (k *Keyboard) Type(ctx context.Context, text string, delay int) error {
return err
}
releaseDelay := randomDuration(delay)
releaseDelay := randomDuration(delay) * time.Millisecond
time.Sleep(releaseDelay)
if err := k.Up(ctx, ch); err != nil {
@ -50,3 +80,34 @@ func (k *Keyboard) Type(ctx context.Context, text string, delay int) error {
return nil
}
func (k *Keyboard) Press(ctx context.Context, name string) error {
key, found := usKeyboardLayout[name]
if !found {
return errors.New("invalid key")
}
err := k.client.Input.DispatchKeyEvent(
ctx,
input.NewDispatchKeyEventArgs("keyDown").
SetCode(key.Code).
SetKey(key.Key).
SetWindowsVirtualKeyCode(key.KeyCode),
)
if err != nil {
return err
}
releaseDelay := randomDuration(DefaultDelay)
time.Sleep(releaseDelay)
return k.client.Input.DispatchKeyEvent(
ctx,
input.NewDispatchKeyEventArgs("keyUp").
SetCode(key.Code).
SetKey(key.Key).
SetWindowsVirtualKeyCode(key.KeyCode),
)
}

File diff suppressed because it is too large Load Diff

View File

@ -14,12 +14,20 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/values"
)
type Manager struct {
client *cdp.Client
exec *eval.ExecutionContext
keyboard *Keyboard
mouse *Mouse
}
type (
TypeParams struct {
Text core.Value
Clear values.Boolean
Delay values.Int
}
Manager struct {
client *cdp.Client
exec *eval.ExecutionContext
keyboard *Keyboard
mouse *Mouse
}
)
func NewManager(
client *cdp.Client,
@ -213,7 +221,7 @@ func (m *Manager) ClickBySelectorAll(ctx context.Context, parentNodeID dom.NodeI
return nil
}
func (m *Manager) Type(ctx context.Context, objectID runtime.RemoteObjectID, text core.Value, delay values.Int) error {
func (m *Manager) Type(ctx context.Context, objectID runtime.RemoteObjectID, params TypeParams) error {
err := m.ScrollIntoView(ctx, objectID)
if err != nil {
@ -226,15 +234,27 @@ func (m *Manager) Type(ctx context.Context, objectID runtime.RemoteObjectID, tex
return err
}
_, min := core.NumberBoundaries(float64(delay))
if params.Clear {
points, err := GetClickablePointByObjectID(ctx, m.client, objectID)
if err != nil {
return err
}
if err := m.ClearByXY(ctx, points); err != nil {
return err
}
}
_, min := core.NumberBoundaries(float64(params.Delay))
beforeTypeDelay := time.Duration(min)
time.Sleep(beforeTypeDelay * time.Millisecond)
return m.keyboard.Type(ctx, text.String(), int(delay))
return m.keyboard.Type(ctx, params.Text.String(), int(params.Delay))
}
func (m *Manager) TypeBySelector(ctx context.Context, parentNodeID dom.NodeID, selector values.String, text core.Value, delay values.Int) error {
func (m *Manager) TypeBySelector(ctx context.Context, parentNodeID dom.NodeID, selector values.String, params TypeParams) error {
err := m.ScrollIntoViewBySelector(ctx, selector)
if err != nil {
@ -253,12 +273,84 @@ func (m *Manager) TypeBySelector(ctx context.Context, parentNodeID dom.NodeID, s
return err
}
_, min := core.NumberBoundaries(float64(delay))
if params.Clear {
points, err := GetClickablePointByNodeID(ctx, m.client, found.NodeID)
if err != nil {
return err
}
if err := m.ClearByXY(ctx, points); err != nil {
return err
}
}
_, min := core.NumberBoundaries(float64(params.Delay))
beforeTypeDelay := time.Duration(min)
time.Sleep(beforeTypeDelay * time.Millisecond)
return m.keyboard.Type(ctx, text.String(), int(delay))
return m.keyboard.Type(ctx, params.Text.String(), int(params.Delay))
}
func (m *Manager) Clear(ctx context.Context, objectID runtime.RemoteObjectID) error {
err := m.ScrollIntoView(ctx, objectID)
if err != nil {
return err
}
points, err := GetClickablePointByObjectID(ctx, m.client, objectID)
if err != nil {
return err
}
err = m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetObjectID(objectID))
if err != nil {
return err
}
return m.ClearByXY(ctx, points)
}
func (m *Manager) ClearBySelector(ctx context.Context, parentNodeID dom.NodeID, selector values.String) error {
err := m.ScrollIntoViewBySelector(ctx, selector)
if err != nil {
return err
}
found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector.String()))
if err != nil {
return err
}
points, err := GetClickablePointByNodeID(ctx, m.client, found.NodeID)
if err != nil {
return err
}
err = m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetNodeID(found.NodeID))
if err != nil {
return err
}
return m.ClearByXY(ctx, points)
}
func (m *Manager) ClearByXY(ctx context.Context, points Quad) error {
err := m.mouse.ClickWithCount(ctx, points.X, points.Y, 3, 5)
if err != nil {
return err
}
return m.keyboard.Press(ctx, "Backspace")
}
func (m *Manager) Select(ctx context.Context, objectID runtime.RemoteObjectID, value *values.Array) (*values.Array, error) {

View File

@ -19,11 +19,15 @@ func NewMouse(client *cdp.Client) *Mouse {
}
func (m *Mouse) Click(ctx context.Context, x, y float64, delay int) error {
return m.ClickWithCount(ctx, x, y, 1, delay)
}
func (m *Mouse) ClickWithCount(ctx context.Context, x, y float64, count, delay int) error {
if err := m.Move(ctx, x, y); err != nil {
return err
}
if err := m.Down(ctx, "left"); err != nil {
if err := m.DownWithCount(ctx, "left", count); err != nil {
return err
}
@ -31,23 +35,31 @@ func (m *Mouse) Click(ctx context.Context, x, y float64, delay int) error {
time.Sleep(releaseDelay * time.Millisecond)
return m.Up(ctx, "left")
return m.UpWithCount(ctx, "left", count)
}
func (m *Mouse) Down(ctx context.Context, button string) error {
return m.DownWithCount(ctx, button, 1)
}
func (m *Mouse) DownWithCount(ctx context.Context, button string, count int) error {
return m.client.Input.DispatchMouseEvent(
ctx,
input.NewDispatchMouseEventArgs("mousePressed", m.x, m.y).
SetClickCount(1).
SetClickCount(count).
SetButton(button),
)
}
func (m *Mouse) Up(ctx context.Context, button string) error {
return m.UpWithCount(ctx, button, 1)
}
func (m *Mouse) UpWithCount(ctx context.Context, button string, count int) error {
return m.client.Input.DispatchMouseEvent(
ctx,
input.NewDispatchMouseEventArgs("mouseReleased", m.x, m.y).
SetClickCount(1).
SetClickCount(count).
SetButton(button),
)
}

View File

@ -215,10 +215,6 @@ func (doc *HTMLDocument) ClickBySelectorAll(_ context.Context, _ values.String)
return core.ErrNotSupported
}
func (doc *HTMLDocument) InputBySelector(_ context.Context, _ values.String, _ core.Value, _ values.Int) error {
return core.ErrNotSupported
}
func (doc *HTMLDocument) SelectBySelector(_ context.Context, _ values.String, _ *values.Array) (*values.Array, error) {
return nil, core.ErrNotSupported
}

View File

@ -501,10 +501,22 @@ func (el *HTMLElement) ClickBySelectorAll(_ context.Context, _ values.String) er
return core.ErrNotSupported
}
func (el *HTMLElement) Clear(_ context.Context) error {
return core.ErrNotSupported
}
func (el *HTMLElement) ClearBySelector(_ context.Context, _ values.String) error {
return core.ErrNotSupported
}
func (el *HTMLElement) Input(_ context.Context, _ core.Value, _ values.Int) error {
return core.ErrNotSupported
}
func (el *HTMLElement) InputBySelector(_ context.Context, _ values.String, _ core.Value, _ values.Int) error {
return core.ErrNotSupported
}
func (el *HTMLElement) Select(_ context.Context, _ *values.Array) (*values.Array, error) {
return nil, core.ErrNotSupported
}

View File

@ -99,8 +99,14 @@ type (
Click(ctx context.Context) error
Clear(ctx context.Context) error
ClearBySelector(ctx context.Context, selector values.String) error
Input(ctx context.Context, value core.Value, delay values.Int) error
InputBySelector(ctx context.Context, selector values.String, value core.Value, delay values.Int) error
Select(ctx context.Context, value *values.Array) (*values.Array, error)
ScrollIntoView(ctx context.Context) error
@ -131,8 +137,6 @@ type (
GetChildDocuments(ctx context.Context) (*values.Array, error)
InputBySelector(ctx context.Context, selector values.String, value core.Value, delay values.Int) error
SelectBySelector(ctx context.Context, selector values.String, value *values.Array) (*values.Array, error)
ScrollTop(ctx context.Context) error

33
pkg/stdlib/html/clear.go Normal file
View File

@ -0,0 +1,33 @@
package html
import (
"context"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
// InputClear clears a value from an underlying input element.
// @param source (HTMLPage | HTMLDocument | HTMLElement) - Event target.
// @param selector (String, options) - Selector.
func InputClear(ctx context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 2)
if err != nil {
return values.None, err
}
el, err := drivers.ToElement(args[0])
if err != nil {
return values.None, err
}
// CLEAR(el)
if len(args) == 1 {
return values.None, el.Clear(ctx)
}
return values.None, el.ClearBySelector(ctx, values.ToString(args[1]))
}

View File

@ -2,7 +2,6 @@ package html
import (
"context"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
@ -10,93 +9,74 @@ import (
)
// Input types a value to an underlying input element.
// @param source (Open | GetElement) - Event target.
// @param source (HTMLPage | HTMLDocument | HTMLElement) - Event target.
// @param valueOrSelector (String) - Selector or a value.
// @param value (String) - Target value.
// @param delay (Int, optional) - Waits delay milliseconds between keystrokes
// @param delay (Int, optional) - Target value.
// @returns (Boolean) - Returns true if an element was found.
func Input(ctx context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 4)
if err != nil {
return values.None, err
return values.False, err
}
arg1 := args[0]
err = core.ValidateType(arg1, drivers.HTMLPageType, drivers.HTMLDocumentType, drivers.HTMLElementType)
el, err := drivers.ToElement(args[0])
if err != nil {
return values.False, err
}
if arg1.Type() == drivers.HTMLPageType || arg1.Type() == drivers.HTMLDocumentType {
doc, err := drivers.ToDocument(arg1)
delay := values.NewInt(25)
if err != nil {
return values.False, err
}
// INPUT(el, value)
if len(args) == 2 {
return values.True, el.Input(ctx, args[1], delay)
}
// selector
arg2 := args[1]
err = core.ValidateType(arg2, types.String)
var selector values.String
var value core.Value
if err != nil {
return values.False, err
}
// INPUT(el, valueOrSelector, valueOrOpts)
if len(args) == 3 {
switch v := args[2].(type) {
// INPUT(el, value, delay)
case values.Int, values.Float:
value = args[1]
delay = values.ToInt(v)
selector := values.ToString(arg2)
delay := values.Int(0)
if len(args) == 4 {
arg4 := args[3]
err = core.ValidateType(arg4, types.Int)
return values.True, el.Input(ctx, value, delay)
default:
// INPUT(el, selector, value)
err := core.ValidateType(args[1], types.String)
if err != nil {
return values.False, err
}
delay = values.ToInt(arg4)
selector = values.ToString(args[1])
value = args[2]
}
exists, err := doc.ExistsBySelector(ctx, selector)
} else {
// INPUT(el, selector, value, delay)
err := core.ValidateType(args[3], types.Int)
if err != nil {
return values.False, err
}
if !exists {
return values.False, nil
}
return values.True, doc.InputBySelector(ctx, selector, args[2], delay)
delay = values.ToInt(args[3])
}
el, err := drivers.ToElement(arg1)
if err != nil {
return values.None, err
}
delay := values.Int(0)
if len(args) == 3 {
arg3 := args[2]
err = core.ValidateType(arg3, types.Int)
if err != nil {
return values.False, err
}
delay = arg3.(values.Int)
}
err = el.Input(ctx, args[1], delay)
exists, err := el.ExistsBySelector(ctx, selector)
if err != nil {
return values.False, err
}
return values.True, nil
if !exists {
return values.False, nil
}
return values.True, el.InputBySelector(ctx, selector, value, delay)
}

View File

@ -37,6 +37,7 @@ func RegisterLib(ns core.Namespace) error {
"INNER_TEXT_SET": SetInnerText,
"INNER_TEXT_ALL": GetInnerTextAll,
"INPUT": Input,
"INPUT_CLEAR": InputClear,
"MOUSE": MouseMoveXY,
"NAVIGATE": Navigate,
"NAVIGATE_BACK": NavigateBack,