From 8e13cf91341489e73e270e774a5260a53d45032f Mon Sep 17 00:00:00 2001 From: Tim Voronov Date: Tue, 16 Jul 2019 18:17:42 -0400 Subject: [PATCH] Refactored input and select (#331) * Refactored input and select * WIP * Fixed serialization * Fixed scriolling * Fixed XPath result handling * Renamed some methods --- Makefile | 2 +- pkg/drivers/cdp/document.go | 14 +- pkg/drivers/cdp/element.go | 40 +-- pkg/drivers/cdp/eval/context.go | 137 ++++++-- pkg/drivers/cdp/events/wait.go | 2 +- pkg/drivers/cdp/helpers.go | 8 +- pkg/drivers/cdp/input/manager.go | 329 +++++++++--------- pkg/drivers/cdp/input/quad.go | 4 - pkg/drivers/cdp/templates/scroll.go | 68 ++++ pkg/drivers/cdp/templates/select.go | 56 +++ pkg/drivers/cdp/templates/wait_by_selector.go | 4 +- 11 files changed, 409 insertions(+), 255 deletions(-) create mode 100644 pkg/drivers/cdp/templates/scroll.go create mode 100644 pkg/drivers/cdp/templates/select.go diff --git a/Makefile b/Makefile index bfdf27b6..e43d1599 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ cover: e2e: go run ${DIR_E2E}/main.go --tests ${DIR_E2E}/tests --pages ${DIR_E2E}/pages - # --filter=e2e/tests/dynamic/**/inner_text/*.fql + # --filter=e2e/tests/**/xpath/*.fql bench: go test -run=XXX -bench=. ${DIR_PKG}/... diff --git a/pkg/drivers/cdp/document.go b/pkg/drivers/cdp/document.go index 0c01e96c..9a0a92c7 100644 --- a/pkg/drivers/cdp/document.go +++ b/pkg/drivers/cdp/document.go @@ -326,18 +326,10 @@ func (doc *HTMLDocument) ClickBySelector(ctx context.Context, selector values.St } func (doc *HTMLDocument) ClickBySelectorAll(ctx context.Context, selector values.String) (values.Boolean, error) { - found, err := doc.client.DOM.QuerySelectorAll(ctx, dom.NewQuerySelectorAllArgs(doc.element.id.nodeID, selector.String())) - - if err != nil { + if err := doc.input.ClickBySelectorAll(ctx, doc.element.id.nodeID, selector); err != nil { return values.False, err } - for _, nodeID := range found.NodeIDs { - if err := doc.input.ClickByNodeID(ctx, nodeID); err != nil { - return values.False, err - } - } - return values.True, nil } @@ -358,7 +350,7 @@ func (doc *HTMLDocument) MoveMouseBySelector(ctx context.Context, selector value } func (doc *HTMLDocument) MoveMouseByXY(ctx context.Context, x, y values.Float) error { - return doc.input.MoveMouse(ctx, x, y) + return doc.input.MoveMouseByXY(ctx, x, y) } func (doc *HTMLDocument) WaitForElement(ctx context.Context, selector values.String, when drivers.WaitEvent) error { @@ -521,7 +513,7 @@ func (doc *HTMLDocument) ScrollBySelector(ctx context.Context, selector values.S } func (doc *HTMLDocument) ScrollByXY(ctx context.Context, x, y values.Float) error { - return doc.input.Scroll(ctx, x, y) + return doc.input.ScrollByXY(ctx, x, y) } func (doc *HTMLDocument) loadChildren(ctx context.Context) (value core.Value, e error) { diff --git a/pkg/drivers/cdp/element.go b/pkg/drivers/cdp/element.go index fc06494e..6e0391c7 100644 --- a/pkg/drivers/cdp/element.go +++ b/pkg/drivers/cdp/element.go @@ -576,7 +576,7 @@ func (el *HTMLElement) XPath(ctx context.Context, expression values.String) (res return values.None, err } - out, err := el.exec.CallFunction(ctx, templates.XPath(), + out, err := el.exec.EvalWithArgumentsAndReturnReference(ctx, templates.XPath(), runtime.CallArgument{ ObjectID: &el.id.objectID, }, @@ -593,25 +593,7 @@ func (el *HTMLElement) XPath(ctx context.Context, expression values.String) (res // checking whether it's actually an array if typeName == "object" { - isArrayRes, err := el.exec.CallFunction(ctx, ` - (target) => Array.isArray(target) - `, - runtime.CallArgument{ - ObjectID: out.ObjectID, - }, - ) - - if err != nil { - return values.None, err - } - - isArray, err := eval.Unmarshal(&isArrayRes) - - if err != nil { - return values.None, err - } - - if isArray == values.True { + if out.ClassName != nil && *out.ClassName == "Array" { typeName = "array" } } @@ -745,7 +727,7 @@ func (el *HTMLElement) GetInnerTextBySelector(ctx context.Context, selector valu return values.EmptyString, drivers.ErrDetached } - out, err := el.exec.EvalWithValue(ctx, templates.GetInnerTextBySelector(selector.String())) + out, err := el.exec.EvalWithReturnValue(ctx, templates.GetInnerTextBySelector(selector.String())) if err != nil { return values.EmptyString, err @@ -767,7 +749,7 @@ func (el *HTMLElement) GetInnerTextBySelectorAll(ctx context.Context, selector v return values.NewArray(0), drivers.ErrDetached } - out, err := el.exec.EvalWithValue(ctx, templates.GetInnerTextBySelectorAll(selector.String())) + out, err := el.exec.EvalWithReturnValue(ctx, templates.GetInnerTextBySelectorAll(selector.String())) if err != nil { return values.NewArray(0), err @@ -811,7 +793,7 @@ func (el *HTMLElement) GetInnerHTMLBySelector(ctx context.Context, selector valu return values.EmptyString, drivers.ErrDetached } - out, err := el.exec.EvalWithValue(ctx, templates.GetInnerHTMLBySelector(selector.String())) + out, err := el.exec.EvalWithReturnValue(ctx, templates.GetInnerHTMLBySelector(selector.String())) if err != nil { return values.EmptyString, err @@ -833,7 +815,7 @@ func (el *HTMLElement) GetInnerHTMLBySelectorAll(ctx context.Context, selector v return values.NewArray(0), drivers.ErrDetached } - out, err := el.exec.EvalWithValue(ctx, templates.GetInnerHTMLBySelectorAll(selector.String())) + out, err := el.exec.EvalWithReturnValue(ctx, templates.GetInnerHTMLBySelectorAll(selector.String())) if err != nil { return values.NewArray(0), err @@ -966,7 +948,7 @@ func (el *HTMLElement) WaitForStyle(ctx context.Context, name values.String, val } func (el *HTMLElement) Click(ctx context.Context) (values.Boolean, error) { - if err := el.input.ClickByNodeID(ctx, el.id.nodeID); err != nil { + if err := el.input.Click(ctx, el.id.objectID); err != nil { return values.False, err } @@ -978,19 +960,19 @@ func (el *HTMLElement) Input(ctx context.Context, value core.Value, delay values return core.Error(core.ErrInvalidOperation, "element is not an element.") } - return el.input.TypeByNodeID(ctx, el.id.nodeID, value, delay) + return el.input.Type(ctx, el.id.objectID, value, delay) } func (el *HTMLElement) Select(ctx context.Context, value *values.Array) (*values.Array, error) { - return el.input.SelectByNodeID(ctx, el.id.nodeID, value) + return el.input.Select(ctx, el.id.objectID, value) } func (el *HTMLElement) ScrollIntoView(ctx context.Context) error { - return el.input.ScrollIntoViewByNodeID(ctx, el.id.nodeID) + return el.input.ScrollIntoView(ctx, el.id.objectID) } func (el *HTMLElement) Hover(ctx context.Context) error { - return el.input.MoveMouseByNodeID(ctx, el.id.nodeID) + return el.input.MoveMouse(ctx, el.id.objectID) } func (el *HTMLElement) IsDetached() values.Boolean { diff --git a/pkg/drivers/cdp/eval/context.go b/pkg/drivers/cdp/eval/context.go index ccb7449d..06ec096d 100644 --- a/pkg/drivers/cdp/eval/context.go +++ b/pkg/drivers/cdp/eval/context.go @@ -47,7 +47,27 @@ func (ec *ExecutionContext) Eval(ctx context.Context, exp string) error { return err } -func (ec *ExecutionContext) EvalWithValue(ctx context.Context, exp string) (core.Value, error) { +func (ec *ExecutionContext) EvalWithArguments(ctx context.Context, exp string, args ...runtime.CallArgument) error { + _, err := ec.evalWithArgumentsInternal(ctx, exp, args, false) + + return err +} + +func (ec *ExecutionContext) EvalWithArgumentsAndReturnValue(ctx context.Context, exp string, args ...runtime.CallArgument) (core.Value, error) { + out, err := ec.evalWithArgumentsInternal(ctx, exp, args, true) + + if err != nil { + return values.None, err + } + + return Unmarshal(&out) +} + +func (ec *ExecutionContext) EvalWithArgumentsAndReturnReference(ctx context.Context, exp string, args ...runtime.CallArgument) (runtime.RemoteObject, error) { + return ec.evalWithArgumentsInternal(ctx, exp, args, false) +} + +func (ec *ExecutionContext) EvalWithReturnValue(ctx context.Context, exp string) (core.Value, error) { return ec.evalWithValueInternal( ctx, runtime. @@ -56,6 +76,15 @@ func (ec *ExecutionContext) EvalWithValue(ctx context.Context, exp string) (core ) } +func (ec *ExecutionContext) EvalWithReturnReference(ctx context.Context, exp string) (runtime.RemoteObject, error) { + return ec.evalInternal( + ctx, + runtime. + NewEvaluateArgs(PrepareEval(exp)). + SetReturnByValue(false), + ) +} + func (ec *ExecutionContext) EvalAsync(ctx context.Context, exp string) (core.Value, error) { return ec.evalWithValueInternal( ctx, @@ -66,20 +95,6 @@ func (ec *ExecutionContext) EvalAsync(ctx context.Context, exp string) (core.Val ) } -func (ec *ExecutionContext) ResolveRemoteObject(ctx context.Context, exp string) (runtime.RemoteObject, error) { - res, err := ec.evalInternal(ctx, runtime.NewEvaluateArgs(PrepareEval(exp))) - - if err != nil { - return runtime.RemoteObject{}, err - } - - if res.ObjectID == nil { - return runtime.RemoteObject{}, errors.Wrap(core.ErrUnexpected, "unable to resolve remote object") - } - - return res, nil -} - func (ec *ExecutionContext) CallMethod( ctx context.Context, objectID runtime.RemoteObjectID, @@ -103,8 +118,10 @@ func (ec *ExecutionContext) CallMethod( return nil, err } - if found.ExceptionDetails != nil { - return nil, found.ExceptionDetails + err = ec.handleException(found.ExceptionDetails) + + if err != nil { + return nil, err } if found.Result.ObjectID == nil { @@ -195,8 +212,10 @@ func (ec *ExecutionContext) DispatchEvent( return values.False, nil } - if evt.ExceptionDetails != nil { - return values.False, evt.ExceptionDetails + err = ec.handleException(evt.ExceptionDetails) + + if err != nil { + return values.False, nil } if evt.Result.ObjectID == nil { @@ -226,8 +245,45 @@ func (ec *ExecutionContext) DispatchEvent( return values.True, nil } -func (ec *ExecutionContext) CallFunction(ctx context.Context, declaration string, args ...runtime.CallArgument) (runtime.RemoteObject, error) { - cfArgs := runtime.NewCallFunctionOnArgs(declaration).SetArguments(args) +func (ec *ExecutionContext) ResolveRemoteObject(ctx context.Context, exp string) (runtime.RemoteObject, error) { + res, err := ec.evalInternal(ctx, runtime.NewEvaluateArgs(PrepareEval(exp))) + + if err != nil { + return runtime.RemoteObject{}, err + } + + if res.ObjectID == nil { + return runtime.RemoteObject{}, errors.Wrap(core.ErrUnexpected, "unable to resolve remote object") + } + + return res, nil +} + +func (ec *ExecutionContext) ResolveNode(ctx context.Context, nodeID dom.NodeID) (runtime.RemoteObject, error) { + args := dom.NewResolveNodeArgs().SetNodeID(nodeID) + + if ec.contextID != EmptyExecutionContextID { + args.SetExecutionContextID(ec.contextID) + } + + repl, err := ec.client.DOM.ResolveNode(ctx, args) + + if err != nil { + return runtime.RemoteObject{}, err + } + + if repl.Object.ObjectID == nil { + return runtime.RemoteObject{}, errors.Wrap(core.ErrUnexpected, "unable to resolve remote object") + } + + return repl.Object, nil +} + +func (ec *ExecutionContext) evalWithArgumentsInternal(ctx context.Context, exp string, args []runtime.CallArgument, ret bool) (runtime.RemoteObject, error) { + cfArgs := runtime. + NewCallFunctionOnArgs(exp). + SetArguments(args). + SetReturnByValue(ret) if ec.contextID != EmptyExecutionContextID { cfArgs.SetExecutionContextID(ec.contextID) @@ -239,10 +295,10 @@ func (ec *ExecutionContext) CallFunction(ctx context.Context, declaration string return runtime.RemoteObject{}, err } - if repl.ExceptionDetails != nil { - exception := *repl.ExceptionDetails + err = ec.handleException(repl.ExceptionDetails) - return runtime.RemoteObject{}, errors.New(exception.Error()) + if err != nil { + return runtime.RemoteObject{}, err } return repl.Result, nil @@ -273,23 +329,28 @@ func (ec *ExecutionContext) evalInternal(ctx context.Context, args *runtime.Eval return runtime.RemoteObject{}, err } - if out.ExceptionDetails != nil { - ex := out.ExceptionDetails - desc := *ex.Exception.Description - - var err error - - if strings.Contains(desc, drivers.ErrNotFound.Error()) { - err = drivers.ErrNotFound - } else { - err = core.Error( - core.ErrUnexpected, - fmt.Sprintf("%s: %s", ex.Text, desc), - ) - } + err = ec.handleException(out.ExceptionDetails) + if err != nil { return runtime.RemoteObject{}, err } return out.Result, nil } + +func (ec *ExecutionContext) handleException(details *runtime.ExceptionDetails) error { + if details == nil { + return nil + } + + desc := *details.Exception.Description + + if strings.Contains(desc, drivers.ErrNotFound.Error()) { + return drivers.ErrNotFound + } + + return core.Error( + core.ErrUnexpected, + fmt.Sprintf("%s: %s", details.Text, desc), + ) +} diff --git a/pkg/drivers/cdp/events/wait.go b/pkg/drivers/cdp/events/wait.go index 0aaf5643..a3cf3da8 100644 --- a/pkg/drivers/cdp/events/wait.go +++ b/pkg/drivers/cdp/events/wait.go @@ -64,7 +64,7 @@ func NewEvalWaitTask( ) *WaitTask { return NewWaitTask( func(ctx context.Context) (core.Value, error) { - return ec.EvalWithValue( + return ec.EvalWithReturnValue( ctx, predicate, ) diff --git a/pkg/drivers/cdp/helpers.go b/pkg/drivers/cdp/helpers.go index b359c8da..be6f9ead 100644 --- a/pkg/drivers/cdp/helpers.go +++ b/pkg/drivers/cdp/helpers.go @@ -93,7 +93,7 @@ func setInnerHTML(ctx context.Context, client *cdp.Client, exec *eval.ExecutionC return err } - _, err = exec.CallFunction(ctx, templates.SetInnerHTML(), + err = exec.EvalWithArguments(ctx, templates.SetInnerHTML(), runtime.CallArgument{ ObjectID: objID, }, @@ -135,7 +135,7 @@ func getInnerHTML(ctx context.Context, client *cdp.Client, exec *eval.ExecutionC return values.NewString(res.String()), nil } - repl, err := exec.EvalWithValue(ctx, "return document.documentElement.innerHTML") + repl, err := exec.EvalWithReturnValue(ctx, "return document.documentElement.innerHTML") if err != nil { return "", err @@ -169,7 +169,7 @@ func setInnerText(ctx context.Context, client *cdp.Client, exec *eval.ExecutionC return err } - _, err = exec.CallFunction(ctx, templates.SetInnerText(), + err = exec.EvalWithArguments(ctx, templates.SetInnerText(), runtime.CallArgument{ ObjectID: objID, }, @@ -211,7 +211,7 @@ func getInnerText(ctx context.Context, client *cdp.Client, exec *eval.ExecutionC return values.NewString(res.String()), err } - repl, err := exec.EvalWithValue(ctx, "return document.documentElement.innerText") + repl, err := exec.EvalWithReturnValue(ctx, "return document.documentElement.innerText") if err != nil { return "", err diff --git a/pkg/drivers/cdp/input/manager.go b/pkg/drivers/cdp/input/manager.go index 310665a8..5c53ecd1 100644 --- a/pkg/drivers/cdp/input/manager.go +++ b/pkg/drivers/cdp/input/manager.go @@ -2,17 +2,16 @@ package input import ( "context" - "fmt" "time" - "github.com/gofrs/uuid" "github.com/mafredri/cdp" "github.com/mafredri/cdp/protocol/dom" + "github.com/mafredri/cdp/protocol/runtime" "github.com/MontFerret/ferret/pkg/drivers/cdp/eval" + "github.com/MontFerret/ferret/pkg/drivers/cdp/templates" "github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime/values" - "github.com/MontFerret/ferret/pkg/runtime/values/types" ) type Manager struct { @@ -44,116 +43,67 @@ func (m *Manager) Mouse() *Mouse { return m.mouse } -func (m *Manager) Scroll(ctx context.Context, x, y values.Float) error { - return m.exec.Eval(ctx, fmt.Sprintf(` - window.scrollBy({ - top: %s, - left: %s, - behavior: 'instant' - }); - `, - eval.ParamFloat(float64(x)), - eval.ParamFloat(float64(y)), - )) -} - -func (m *Manager) ScrollIntoViewBySelector(ctx context.Context, selector values.String) error { - return m.exec.Eval(ctx, fmt.Sprintf(` - var el = document.querySelector(%s); - - if (el == null) { - throw new Error("element not found"); - } - - el.scrollIntoView({ - behavior: 'instant' - }); - - return true; - `, eval.ParamString(selector.String()), - )) -} - -func (m *Manager) ScrollIntoViewByNodeID(ctx context.Context, nodeID dom.NodeID) error { - var attrID = "data-ferret-scroll" - - id, err := uuid.NewV4() - - if err != nil { - return err - } - - err = m.client.DOM.SetAttributeValue(ctx, dom.NewSetAttributeValueArgs(nodeID, attrID, id.String())) - - if err != nil { - return err - } - - err = m.exec.Eval( - ctx, - fmt.Sprintf(` - var el = document.querySelector('[%s="%s"]'); - if (el == null) { - throw new Error('element not found'); - } - - el.scrollIntoView({ - behavior: 'instant', - inline: 'center', - block: 'center' - }); - `, - attrID, - id.String(), - )) - - if err != nil { - return err - } - - err = m.client.DOM.RemoveAttribute(ctx, dom.NewRemoveAttributeArgs(nodeID, attrID)) - - return err -} - func (m *Manager) ScrollTop(ctx context.Context) error { - return m.exec.Eval(ctx, ` - window.scrollTo({ - left: 0, - top: 0, - behavior: 'instant' - }); - `) + return m.exec.Eval(ctx, templates.ScrollTop()) } func (m *Manager) ScrollBottom(ctx context.Context) error { - return m.exec.Eval(ctx, ` - window.scrollTo({ - left: 0, - top: window.document.body.scrollHeight, - behavior: 'instant' - }); - `) + return m.exec.Eval(ctx, templates.ScrollBottom()) } -func (m *Manager) MoveMouseBySelector(ctx context.Context, parentNodeID dom.NodeID, selector values.String) error { +func (m *Manager) ScrollIntoView(ctx context.Context, objectID runtime.RemoteObjectID) error { + return m.exec.EvalWithArguments( + ctx, + templates.ScrollIntoView(), + runtime.CallArgument{ + ObjectID: &objectID, + }, + ) +} + +func (m *Manager) ScrollIntoViewBySelector(ctx context.Context, selector values.String) error { + return m.exec.Eval(ctx, templates.ScrollIntoViewBySelector(selector.String())) +} + +func (m *Manager) ScrollByXY(ctx context.Context, x, y values.Float) error { + return m.exec.Eval( + ctx, + templates.Scroll(eval.ParamFloat(float64(x)), eval.ParamFloat(float64(y))), + ) +} + +func (m *Manager) Focus(ctx context.Context, objectID runtime.RemoteObjectID) error { + err := m.ScrollIntoView(ctx, objectID) + + if err != nil { + return err + } + + return m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetObjectID(objectID)) +} + +func (m *Manager) FocusBySelector(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 + return nil } - return m.MoveMouseByNodeID(ctx, found.NodeID) + return m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetNodeID(found.NodeID)) } -func (m *Manager) MoveMouseByNodeID(ctx context.Context, nodeID dom.NodeID) error { - err := m.ScrollIntoViewByNodeID(ctx, nodeID) - - if err != nil { +func (m *Manager) MoveMouse(ctx context.Context, objectID runtime.RemoteObjectID) error { + if err := m.ScrollIntoView(ctx, objectID); err != nil { return err } - q, err := GetClickablePointByNodeID(ctx, m.client, nodeID) + q, err := GetClickablePointByObjectID(ctx, m.client, objectID) if err != nil { return err @@ -162,26 +112,40 @@ func (m *Manager) MoveMouseByNodeID(ctx context.Context, nodeID dom.NodeID) erro return m.mouse.Move(ctx, q.X, q.Y) } -func (m *Manager) MoveMouse(ctx context.Context, x, y values.Float) error { - return m.mouse.Move(ctx, float64(x), float64(y)) -} +func (m *Manager) MoveMouseBySelector(ctx context.Context, parentNodeID dom.NodeID, selector values.String) error { + if err := m.ScrollIntoViewBySelector(ctx, selector); err != nil { + return err + } -func (m *Manager) ClickBySelector(ctx context.Context, parentNodeID dom.NodeID, selector values.String) error { found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector.String())) if err != nil { return err } - return m.ClickByNodeID(ctx, found.NodeID) -} + q, err := GetClickablePointByNodeID(ctx, m.client, found.NodeID) -func (m *Manager) ClickByNodeID(ctx context.Context, nodeID dom.NodeID) error { - if err := m.ScrollIntoViewByNodeID(ctx, nodeID); err != nil { + if err != nil { return err } - points, err := GetClickablePointByNodeID(ctx, m.client, nodeID) + return m.mouse.Move(ctx, q.X, q.Y) +} + +func (m *Manager) MoveMouseByXY(ctx context.Context, x, y values.Float) error { + if err := m.ScrollByXY(ctx, x, y); err != nil { + return err + } + + return m.mouse.Move(ctx, float64(x), float64(y)) +} + +func (m *Manager) Click(ctx context.Context, objectID runtime.RemoteObjectID) error { + if err := m.ScrollIntoView(ctx, objectID); err != nil { + return err + } + + points, err := GetClickablePointByObjectID(ctx, m.client, objectID) if err != nil { return err @@ -194,22 +158,71 @@ func (m *Manager) ClickByNodeID(ctx context.Context, nodeID dom.NodeID) error { return nil } -func (m *Manager) TypeBySelector(ctx context.Context, parentNodeID dom.NodeID, selector values.String, text core.Value, delay values.Int) error { +func (m *Manager) ClickBySelector(ctx context.Context, parentNodeID dom.NodeID, selector values.String) error { + if err := m.ScrollIntoViewBySelector(ctx, selector); err != nil { + return err + } + found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector.String())) if err != nil { return err } - return m.TypeByNodeID(ctx, found.NodeID, text, delay) -} + points, err := GetClickablePointByNodeID(ctx, m.client, found.NodeID) -func (m *Manager) TypeByNodeID(ctx context.Context, nodeID dom.NodeID, text core.Value, delay values.Int) error { - if err := m.ScrollIntoViewByNodeID(ctx, nodeID); err != nil { + if err != nil { return err } - if err := m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetNodeID(nodeID)); err != nil { + if err := m.mouse.Click(ctx, points.X, points.Y, 50); err != nil { + return nil + } + + return nil +} + +func (m *Manager) ClickBySelectorAll(ctx context.Context, parentNodeID dom.NodeID, selector values.String) error { + if err := m.ScrollIntoViewBySelector(ctx, selector); err != nil { + return err + } + + found, err := m.client.DOM.QuerySelectorAll(ctx, dom.NewQuerySelectorAllArgs(parentNodeID, selector.String())) + + if err != nil { + return err + } + + for _, nodeID := range found.NodeIDs { + _, min := core.NumberBoundaries(100) + beforeTypeDelay := time.Duration(min) + + time.Sleep(beforeTypeDelay * time.Millisecond) + + points, err := GetClickablePointByNodeID(ctx, m.client, nodeID) + + if err != nil { + return err + } + + if err := m.mouse.Click(ctx, points.X, points.Y, 50); err != nil { + return nil + } + } + + return nil +} + +func (m *Manager) Type(ctx context.Context, objectID runtime.RemoteObjectID, text core.Value, delay values.Int) error { + err := m.ScrollIntoView(ctx, objectID) + + if err != nil { + return err + } + + err = m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetObjectID(objectID)) + + if err != nil { return err } @@ -221,85 +234,71 @@ func (m *Manager) TypeByNodeID(ctx context.Context, nodeID dom.NodeID, text core return m.keyboard.Type(ctx, text.String(), int(delay)) } -func (m *Manager) SelectBySelector(ctx context.Context, parentNodeID dom.NodeID, selector values.String, value *values.Array) (*values.Array, error) { +func (m *Manager) TypeBySelector(ctx context.Context, parentNodeID dom.NodeID, selector values.String, text core.Value, delay values.Int) 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 nil, err + return err } - return m.SelectByNodeID(ctx, found.NodeID, value) + err = m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetNodeID(found.NodeID)) + + if err != nil { + return err + } + + _, min := core.NumberBoundaries(float64(delay)) + beforeTypeDelay := time.Duration(min) + + time.Sleep(beforeTypeDelay * time.Millisecond) + + return m.keyboard.Type(ctx, text.String(), int(delay)) } -func (m *Manager) SelectByNodeID(ctx context.Context, nodeID dom.NodeID, value *values.Array) (*values.Array, error) { - if err := m.ScrollIntoViewByNodeID(ctx, nodeID); err != nil { - return nil, err +func (m *Manager) Select(ctx context.Context, objectID runtime.RemoteObjectID, value *values.Array) (*values.Array, error) { + if err := m.Focus(ctx, objectID); err != nil { + return values.NewArray(0), err } - if err := m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetNodeID(nodeID)); err != nil { - return nil, err - } - - var attrID = "data-ferret-select" - - id, err := uuid.NewV4() + val, err := m.exec.EvalWithArgumentsAndReturnValue(ctx, templates.Select(value.String()), runtime.CallArgument{ + ObjectID: &objectID, + }) if err != nil { return nil, err } - err = m.client.DOM.SetAttributeValue(ctx, dom.NewSetAttributeValueArgs(nodeID, attrID, id.String())) + arr, ok := val.(*values.Array) - if err != nil { - return nil, err + if !ok { + return values.NewArray(0), core.ErrUnexpected } - res, err := m.exec.EvalWithValue( - ctx, - fmt.Sprintf(` - var el = document.querySelector('[%s="%s"]'); - if (el == null) { - return []; - } - var values = %s; - if (el.nodeName.toLowerCase() !== 'select') { - throw new Error('element is not a element.'); + } + + const options = Array.from(el.options); + + el.value = undefined; + + for (var option of options) { + option.selected = values.includes(option.value); + + if (option.selected && !el.multiple) { + break; + } + } + + el.dispatchEvent(new Event('input', { 'bubbles': true })); + el.dispatchEvent(new Event('change', { 'bubbles': true })); + + return options.filter(option => option.selected).map(option => option.value); + `, values, + ) +} + +func Select(values string) string { + return fmt.Sprintf(` + (el) => { + %s + } + `, selectBase(values), + ) +} + +func SelectBySelector(selector, values string) string { + return fmt.Sprintf(` + const el = document.querySelector('%s'); + + if (el == null) { + throw new Error("%s") + } + + %s + `, selector, drivers.ErrNotFound, selectBase(values), + ) +} diff --git a/pkg/drivers/cdp/templates/wait_by_selector.go b/pkg/drivers/cdp/templates/wait_by_selector.go index b8c42175..2cb95c1e 100644 --- a/pkg/drivers/cdp/templates/wait_by_selector.go +++ b/pkg/drivers/cdp/templates/wait_by_selector.go @@ -12,13 +12,13 @@ import ( func WaitBySelector(selector values.String, when drivers.WaitEvent, value core.Value, check string) string { return fmt.Sprintf( ` - var el = document.querySelector(%s); // selector + const el = document.querySelector(%s); // selector if (el == null) { return false; } - var result = %s; // check + const result = %s; // check // when value if (result %s %s) {