From d776efbc232fe8fb2e720f86b8a910b661dc04f6 Mon Sep 17 00:00:00 2001 From: Tim Voronov Date: Fri, 10 Sep 2021 10:16:45 -0400 Subject: [PATCH 01/11] Added remote type reference resolver --- pkg/drivers/cdp/dom/document.go | 62 +++--- pkg/drivers/cdp/dom/element.go | 192 +----------------- pkg/drivers/cdp/dom/helpers.go | 55 ----- pkg/drivers/cdp/dom/manager.go | 8 - pkg/drivers/cdp/eval/resolver.go | 172 ++++++++++++++++ pkg/drivers/cdp/eval/runtime.go | 111 +++++----- pkg/drivers/cdp/eval/types.go | 40 ++++ pkg/drivers/cdp/input/manager.go | 69 ------- pkg/drivers/cdp/input/quad.go | 37 ---- pkg/drivers/cdp/network/manager.go | 2 +- .../cdp/templates/{global.go => document.go} | 12 ++ 11 files changed, 310 insertions(+), 450 deletions(-) create mode 100644 pkg/drivers/cdp/eval/resolver.go create mode 100644 pkg/drivers/cdp/eval/types.go rename pkg/drivers/cdp/templates/{global.go => document.go} (53%) diff --git a/pkg/drivers/cdp/dom/document.go b/pkg/drivers/cdp/dom/document.go index 7dd2cc79..b4c728f2 100644 --- a/pkg/drivers/cdp/dom/document.go +++ b/pkg/drivers/cdp/dom/document.go @@ -2,10 +2,10 @@ package dom import ( "context" + "github.com/mafredri/cdp/protocol/runtime" "hash/fnv" "github.com/mafredri/cdp" - "github.com/mafredri/cdp/protocol/dom" "github.com/mafredri/cdp/protocol/page" "github.com/pkg/errors" "github.com/rs/zerolog" @@ -26,7 +26,7 @@ type HTMLDocument struct { client *cdp.Client dom *Manager input *input.Manager - exec *eval.Runtime + eval *eval.Runtime frameTree page.FrameTree element *HTMLElement } @@ -39,24 +39,12 @@ func LoadRootHTMLDocument( mouse *input.Mouse, keyboard *input.Keyboard, ) (*HTMLDocument, error) { - gdRepl, err := client.DOM.GetDocument(ctx, dom.NewGetDocumentArgs().SetDepth(1)) - - if err != nil { - return nil, err - } - ftRepl, err := client.Page.GetFrameTree(ctx) if err != nil { return nil, err } - exec, err := eval.New(ctx, logger, client, ftRepl.FrameTree.Frame.ID) - - if err != nil { - return nil, err - } - return LoadHTMLDocument( ctx, logger, @@ -64,9 +52,7 @@ func LoadRootHTMLDocument( domManager, mouse, keyboard, - gdRepl.Root, ftRepl.FrameTree, - exec, ) } @@ -77,21 +63,21 @@ func LoadHTMLDocument( domManager *Manager, mouse *input.Mouse, keyboard *input.Keyboard, - node dom.Node, frameTree page.FrameTree, - exec *eval.Runtime, ) (*HTMLDocument, error) { + exec, err := eval.Create(ctx, logger, client, frameTree.Frame.ID) + + if err != nil { + return nil, err + } + inputManager := input.NewManager(logger, client, exec, keyboard, mouse) - rootElement, err := LoadHTMLElement( - ctx, - logger, - client, - domManager, - inputManager, - exec, - node.NodeID, - ) + exec.SetLoader(func(ctx context.Context, remoteType eval.RemoteType, id runtime.RemoteObjectID) (core.Value, error) { + return NewHTMLElement(logger, client, domManager, inputManager, exec, id), nil + }) + + rootElement, err := exec.EvalElement(ctx, templates.GetDocument()) if err != nil { return nil, errors.Wrap(err, "failed to load root element") @@ -103,7 +89,7 @@ func LoadHTMLDocument( domManager, inputManager, exec, - rootElement, + rootElement.(*HTMLElement), frameTree, ), nil } @@ -122,7 +108,7 @@ func NewHTMLDocument( doc.client = client doc.dom = domManager doc.input = input - doc.exec = exec + doc.eval = exec doc.element = rootElement doc.frameTree = frames @@ -233,7 +219,7 @@ func (doc *HTMLDocument) ExistsBySelector(ctx context.Context, selector values.S } func (doc *HTMLDocument) GetTitle() values.String { - value, err := doc.exec.ReadProperty(context.Background(), doc.element.id, "title") + value, err := doc.eval.EvalValue(context.Background(), templates.GetTitle()) if err != nil { doc.logError(errors.Wrap(err, "failed to read document title")) @@ -300,7 +286,7 @@ func (doc *HTMLDocument) MoveMouseByXY(ctx context.Context, x, y values.Float) e func (doc *HTMLDocument) WaitForElement(ctx context.Context, selector values.String, when drivers.WaitEvent) error { task := events.NewEvalWaitTask( - doc.exec, + doc.eval, templates.WaitForElement(doc.element.id, selector, when), events.DefaultPolling, ) @@ -312,7 +298,7 @@ func (doc *HTMLDocument) WaitForElement(ctx context.Context, selector values.Str func (doc *HTMLDocument) WaitForClassBySelector(ctx context.Context, selector, class values.String, when drivers.WaitEvent) error { task := events.NewEvalWaitTask( - doc.exec, + doc.eval, templates.WaitForClassBySelector(doc.element.id, selector, class, when), events.DefaultPolling, ) @@ -324,7 +310,7 @@ func (doc *HTMLDocument) WaitForClassBySelector(ctx context.Context, selector, c func (doc *HTMLDocument) WaitForClassBySelectorAll(ctx context.Context, selector, class values.String, when drivers.WaitEvent) error { task := events.NewEvalWaitTask( - doc.exec, + doc.eval, templates.WaitForClassBySelectorAll(doc.element.id, selector, class, when), events.DefaultPolling, ) @@ -342,7 +328,7 @@ func (doc *HTMLDocument) WaitForAttributeBySelector( when drivers.WaitEvent, ) error { task := events.NewEvalWaitTask( - doc.exec, + doc.eval, templates.WaitForAttributeBySelector(doc.element.id, selector, name, value, when), events.DefaultPolling, ) @@ -360,7 +346,7 @@ func (doc *HTMLDocument) WaitForAttributeBySelectorAll( when drivers.WaitEvent, ) error { task := events.NewEvalWaitTask( - doc.exec, + doc.eval, templates.WaitForAttributeBySelectorAll(doc.element.id, selector, name, value, when), events.DefaultPolling, ) @@ -372,7 +358,7 @@ func (doc *HTMLDocument) WaitForAttributeBySelectorAll( func (doc *HTMLDocument) WaitForStyleBySelector(ctx context.Context, selector, name, value values.String, when drivers.WaitEvent) error { task := events.NewEvalWaitTask( - doc.exec, + doc.eval, templates.WaitForStyleBySelector(doc.element.id, selector, name, value, when), events.DefaultPolling, ) @@ -384,7 +370,7 @@ func (doc *HTMLDocument) WaitForStyleBySelector(ctx context.Context, selector, n func (doc *HTMLDocument) WaitForStyleBySelectorAll(ctx context.Context, selector, name, value values.String, when drivers.WaitEvent) error { task := events.NewEvalWaitTask( - doc.exec, + doc.eval, templates.WaitForStyleBySelectorAll(doc.element.id, selector, name, value, when), events.DefaultPolling, ) @@ -411,7 +397,7 @@ func (doc *HTMLDocument) Scroll(ctx context.Context, options drivers.ScrollOptio } func (doc *HTMLDocument) Eval(ctx context.Context, expression string) (core.Value, error) { - return doc.exec.EvalValue(ctx, eval.F(expression)) + return doc.eval.EvalValue(ctx, eval.F(expression)) } func (doc *HTMLDocument) logError(err error) *zerolog.Event { diff --git a/pkg/drivers/cdp/dom/element.go b/pkg/drivers/cdp/dom/element.go index b9106cd5..311c869d 100644 --- a/pkg/drivers/cdp/dom/element.go +++ b/pkg/drivers/cdp/dom/element.go @@ -2,7 +2,6 @@ package dom import ( "context" - "encoding/json" "fmt" "hash/fnv" "strings" @@ -63,37 +62,13 @@ func LoadHTMLElement( return nil, core.Error(core.ErrNotFound, fmt.Sprintf("element %s", ref.Object.Value)) } - return ResolveHTMLElement( - logger, - client, - domManager, - input, - exec, - ref.Object, - ) -} - -func ResolveHTMLElement( - logger zerolog.Logger, - client *cdp.Client, - domManager *Manager, - input *input.Manager, - exec *eval.Runtime, - ref runtime.RemoteObject, -) (*HTMLElement, error) { - if ref.ObjectID == nil { - return nil, core.Error(core.ErrNotFound, fmt.Sprintf("element %s", ref.Value)) - } - - id := *ref.ObjectID - return NewHTMLElement( logger, client, domManager, input, exec, - id, + *ref.Object.ObjectID, ), nil } @@ -286,59 +261,35 @@ func (el *HTMLElement) RemoveAttribute(ctx context.Context, names ...values.Stri } func (el *HTMLElement) GetChildNodes(ctx context.Context) (*values.Array, error) { - out, err := el.evalTo(ctx, templates.GetChildren(el.id)) - - if err != nil { - return values.EmptyArray(), err - } - - return values.ToArray(ctx, out), nil + return el.exec.EvalElements(ctx, templates.GetChildren(el.id)) } func (el *HTMLElement) GetChildNode(ctx context.Context, idx values.Int) (core.Value, error) { - return el.evalToElement(ctx, templates.GetChildByIndex(el.id, idx)) + return el.exec.EvalElement(ctx, templates.GetChildByIndex(el.id, idx)) } func (el *HTMLElement) GetParentElement(ctx context.Context) (core.Value, error) { - return el.evalToElement(ctx, templates.GetParent(el.id)) + return el.exec.EvalElement(ctx, templates.GetParent(el.id)) } func (el *HTMLElement) GetPreviousElementSibling(ctx context.Context) (core.Value, error) { - return el.evalToElement(ctx, templates.GetPreviousElementSibling(el.id)) + return el.exec.EvalElement(ctx, templates.GetPreviousElementSibling(el.id)) } func (el *HTMLElement) GetNextElementSibling(ctx context.Context) (core.Value, error) { - return el.evalToElement(ctx, templates.GetNextElementSibling(el.id)) + return el.exec.EvalElement(ctx, templates.GetNextElementSibling(el.id)) } func (el *HTMLElement) QuerySelector(ctx context.Context, selector values.String) (core.Value, error) { - return el.evalToElement(ctx, templates.QuerySelector(el.id, selector)) + return el.exec.EvalElement(ctx, templates.QuerySelector(el.id, selector)) } func (el *HTMLElement) QuerySelectorAll(ctx context.Context, selector values.String) (*values.Array, error) { - out, err := el.exec.EvalRef(ctx, templates.QuerySelectorAll(el.id, selector)) - - if err != nil { - return values.EmptyArray(), err - } - - res, err := el.fromEvalRef(ctx, out) - - if err != nil { - return values.EmptyArray(), err - } - - return values.ToArray(ctx, res), nil + return el.exec.EvalElements(ctx, templates.QuerySelectorAll(el.id, selector)) } func (el *HTMLElement) XPath(ctx context.Context, expression values.String) (result core.Value, err error) { - out, err := el.exec.EvalRef(ctx, templates.XPath(el.id, expression)) - - if err != nil { - return values.None, err - } - - return el.fromEvalRef(ctx, out) + return el.exec.EvalValue(ctx, templates.XPath(el.id, expression)) } func (el *HTMLElement) GetInnerText(ctx context.Context) (values.String, error) { @@ -687,131 +638,6 @@ func (el *HTMLElement) HoverBySelector(ctx context.Context, selector values.Stri return el.input.MoveMouseBySelector(ctx, el.id, selector) } -func (el *HTMLElement) evalTo(ctx context.Context, fn *eval.Function) (core.Value, error) { - out, err := el.exec.EvalRef(ctx, fn) - - if err != nil { - return values.None, err - } - - return el.fromEvalRef(ctx, out) -} - -func (el *HTMLElement) evalToElement(ctx context.Context, fn *eval.Function) (core.Value, error) { - obj, err := el.exec.EvalRef(ctx, fn) - - if err != nil { - return values.None, err - } - - if obj.Type != "object" || obj.ObjectID == nil { - return values.None, nil - } - - return ResolveHTMLElement( - el.logger, - el.client, - el.dom, - el.input, - el.exec, - obj, - ) -} - -func (el *HTMLElement) fromEvalRef(ctx context.Context, out runtime.RemoteObject) (core.Value, error) { - var subtype string - - if out.Subtype != nil { - subtype = *out.Subtype - } - - if subtype == "null" || subtype == "undefined" { - return values.None, nil - } - - switch subtype { - case "array", "HTMLCollection", "NodeList": - if out.ObjectID == nil { - return values.None, nil - } - - props, err := el.client.Runtime.GetProperties(ctx, runtime.NewGetPropertiesArgs(*out.ObjectID).SetOwnProperties(true)) - - if err != nil { - return values.None, err - } - - if props.ExceptionDetails != nil { - exception := *props.ExceptionDetails - - return values.None, errors.New(exception.Text) - } - - result := values.NewArray(len(props.Result)) - - for _, descr := range props.Result { - if !descr.Enumerable { - continue - } - - if descr.Value == nil { - continue - } - - // it's not a Node, it's an attr value - if descr.Value.ObjectID == nil { - var value interface{} - - if err := json.Unmarshal(descr.Value.Value, &value); err != nil { - return values.None, err - } - - result.Push(values.Parse(value)) - - continue - } - - el, err := ResolveHTMLElement( - el.logger, - el.client, - el.dom, - el.input, - el.exec, - *descr.Value, - ) - - if err != nil { - return values.None, err - } - - result.Push(el) - } - - return result, nil - case "node": - if out.ObjectID == nil { - var value interface{} - - if err := json.Unmarshal(out.Value, &value); err != nil { - return values.None, err - } - - return values.Parse(value), nil - } - - return ResolveHTMLElement( - el.logger, - el.client, - el.dom, - el.input, - el.exec, - out, - ) - default: - return eval.Unmarshal(out) - } -} - func (el *HTMLElement) logError(err error) *zerolog.Event { return el.logger. Error(). diff --git a/pkg/drivers/cdp/dom/helpers.go b/pkg/drivers/cdp/dom/helpers.go index 6d210756..9f9f369c 100644 --- a/pkg/drivers/cdp/dom/helpers.go +++ b/pkg/drivers/cdp/dom/helpers.go @@ -2,67 +2,12 @@ package dom import ( "bytes" - "context" - "github.com/rs/zerolog" "regexp" "strings" - - "github.com/mafredri/cdp" - "github.com/mafredri/cdp/protocol/dom" - "github.com/mafredri/cdp/protocol/page" - "github.com/pkg/errors" - - "github.com/MontFerret/ferret/pkg/drivers/cdp/eval" ) var camelMatcher = regexp.MustCompile("[A-Za-z0-9]+") -func resolveFrame(ctx context.Context, logger zerolog.Logger, client *cdp.Client, frameID page.FrameID) (dom.Node, *eval.Runtime, error) { - exec, err := eval.New(ctx, logger, client, frameID) - - if err != nil { - return dom.Node{}, nil, errors.Wrap(err, "create JS executor") - } - - evalRes, err := exec.EvalRef(ctx, eval.F("return document")) - - if err != nil { - return dom.Node{}, nil, err - } - - if evalRes.ObjectID == nil { - return dom.Node{}, nil, errors.New("failed to resolve frame document") - } - - req, err := client.DOM.RequestNode(ctx, dom.NewRequestNodeArgs(*evalRes.ObjectID)) - - if err != nil { - return dom.Node{}, nil, err - } - - if req.NodeID == 0 { - return dom.Node{}, nil, errors.New("framed document is resolved with empty node id") - } - - desc, err := client.DOM.DescribeNode( - ctx, - dom. - NewDescribeNodeArgs(). - SetNodeID(req.NodeID). - SetDepth(1), - ) - - if err != nil { - return dom.Node{}, nil, err - } - - // Returned node, by some reason, does not contain the NodeID - // So, we have to set it manually - desc.Node.NodeID = req.NodeID - - return desc.Node, exec, nil -} - func toCamelCase(input string) string { var buf bytes.Buffer diff --git a/pkg/drivers/cdp/dom/manager.go b/pkg/drivers/cdp/dom/manager.go index 98c2c2ce..f62db582 100644 --- a/pkg/drivers/cdp/dom/manager.go +++ b/pkg/drivers/cdp/dom/manager.go @@ -213,12 +213,6 @@ func (m *Manager) getFrameInternal(ctx context.Context, frameID page.FrameID) (* } // the frames is not loaded yet - node, exec, err := resolveFrame(ctx, m.logger, m.client, frameID) - - if err != nil { - return nil, errors.Wrapf(err, "failed to resolve frame node: %s", frameID) - } - doc, err := LoadHTMLDocument( ctx, m.logger, @@ -226,9 +220,7 @@ func (m *Manager) getFrameInternal(ctx context.Context, frameID page.FrameID) (* m, m.mouse, m.keyboard, - node, frame.tree, - exec, ) if err != nil { diff --git a/pkg/drivers/cdp/eval/resolver.go b/pkg/drivers/cdp/eval/resolver.go new file mode 100644 index 00000000..5996f77a --- /dev/null +++ b/pkg/drivers/cdp/eval/resolver.go @@ -0,0 +1,172 @@ +package eval + +import ( + "context" + "github.com/MontFerret/ferret/pkg/drivers" + "github.com/MontFerret/ferret/pkg/runtime/core" + "github.com/MontFerret/ferret/pkg/runtime/values" + "github.com/mafredri/cdp" + "github.com/mafredri/cdp/protocol/runtime" + "github.com/pkg/errors" +) + +type ( + ValueLoader func(ctx context.Context, remoteType RemoteType, id runtime.RemoteObjectID) (core.Value, error) + + Resolver struct { + runtime cdp.Runtime + loader ValueLoader + } +) + +func NewResolver(runtime cdp.Runtime) *Resolver { + return &Resolver{runtime, nil} +} + +func (r *Resolver) SetLoader(loader ValueLoader) *Resolver { + r.loader = loader + + return r +} + +func (r *Resolver) ToValue(ctx context.Context, ref runtime.RemoteObject) (core.Value, error) { + // It's not an actual ref but rather a plain value + if ref.ObjectID == nil { + return values.Unmarshal(ref.Value) + } + + switch ToRemoteType(ref) { + case NullType, UndefinedType: + return values.None, nil + case ArrayType: + props, err := r.runtime.GetProperties(ctx, runtime.NewGetPropertiesArgs(*ref.ObjectID).SetOwnProperties(true)) + + if err != nil { + return values.None, err + } + + if props.ExceptionDetails != nil { + exception := *props.ExceptionDetails + + return values.None, errors.New(exception.Text) + } + + result := values.NewArray(len(props.Result)) + + for _, descr := range props.Result { + if !descr.Enumerable { + continue + } + + if descr.Value == nil { + continue + } + + el, err := r.ToValue(ctx, *descr.Value) + + if err != nil { + return values.None, err + } + + result.Push(el) + } + + return result, nil + case NodeType: + // could it be possible? + if ref.ObjectID == nil { + return values.Unmarshal(ref.Value) + } + + return r.loadValue(ctx, NodeType, *ref.ObjectID) + default: + return Unmarshal(ref) + } +} + +func (r *Resolver) ToElement(ctx context.Context, ref runtime.RemoteObject) (drivers.HTMLElement, error) { + if ref.ObjectID == nil { + return nil, core.Error(core.ErrInvalidArgument, "ref id") + } + + val, err := r.loadValue(ctx, ToRemoteType(ref), *ref.ObjectID) + + if err != nil { + return nil, err + } + + return drivers.ToElement(val) +} + +func (r *Resolver) ToProperty( + ctx context.Context, + id runtime.RemoteObjectID, + propName string, +) (core.Value, error) { + res, err := r.runtime.GetProperties( + ctx, + runtime.NewGetPropertiesArgs(id), + ) + + if err != nil { + return values.None, err + } + + if err := parseRuntimeException(res.ExceptionDetails); err != nil { + return values.None, err + } + + for _, prop := range res.Result { + if prop.Name == propName { + if prop.Value != nil { + return r.ToValue(ctx, *prop.Value) + } + + return values.None, nil + } + } + + return values.None, nil +} + +func (r *Resolver) ToProperties( + ctx context.Context, + id runtime.RemoteObjectID, +) (*values.Array, error) { + res, err := r.runtime.GetProperties( + ctx, + runtime.NewGetPropertiesArgs(id), + ) + + if err != nil { + return values.EmptyArray(), err + } + + if err := parseRuntimeException(res.ExceptionDetails); err != nil { + return values.EmptyArray(), err + } + + arr := values.NewArray(len(res.Result)) + + for _, prop := range res.Result { + if prop.Value != nil { + val, err := r.ToValue(ctx, *prop.Value) + + if err != nil { + return values.EmptyArray(), err + } + + arr.Push(val) + } + } + + return arr, nil +} + +func (r *Resolver) loadValue(ctx context.Context, remoteType RemoteType, id runtime.RemoteObjectID) (core.Value, error) { + if r.loader == nil { + return values.None, core.Error(core.ErrNotImplemented, "ValueLoader") + } + + return r.loader(ctx, remoteType, id) +} diff --git a/pkg/drivers/cdp/eval/runtime.go b/pkg/drivers/cdp/eval/runtime.go index 9b3f1492..eec50bad 100644 --- a/pkg/drivers/cdp/eval/runtime.go +++ b/pkg/drivers/cdp/eval/runtime.go @@ -2,15 +2,16 @@ package eval import ( "context" - "github.com/MontFerret/ferret/pkg/runtime/logging" - "github.com/rs/zerolog" "github.com/mafredri/cdp" "github.com/mafredri/cdp/protocol/page" "github.com/mafredri/cdp/protocol/runtime" "github.com/pkg/errors" + "github.com/rs/zerolog" + "github.com/MontFerret/ferret/pkg/drivers" "github.com/MontFerret/ferret/pkg/runtime/core" + "github.com/MontFerret/ferret/pkg/runtime/logging" "github.com/MontFerret/ferret/pkg/runtime/values" ) @@ -21,23 +22,40 @@ type Runtime struct { client *cdp.Client frame page.Frame contextID runtime.ExecutionContextID + resolver *Resolver } -func New(ctx context.Context, logger zerolog.Logger, client *cdp.Client, frameID page.FrameID) (*Runtime, error) { +func Create( + ctx context.Context, + logger zerolog.Logger, + client *cdp.Client, + frameID page.FrameID, +) (*Runtime, error) { world, err := client.Page.CreateIsolatedWorld(ctx, page.NewCreateIsolatedWorldArgs(frameID)) if err != nil { return nil, err } - return Create(logger, client, world.ExecutionContextID), nil + return New(logger, client, world.ExecutionContextID), nil } -func Create(logger zerolog.Logger, client *cdp.Client, contextID runtime.ExecutionContextID) *Runtime { +func New( + logger zerolog.Logger, + client *cdp.Client, + contextID runtime.ExecutionContextID, +) *Runtime { rt := new(Runtime) rt.logger = logging.WithName(logger.With(), "js-eval").Logger() rt.client = client rt.contextID = contextID + rt.resolver = NewResolver(client.Runtime) + + return rt +} + +func (rt *Runtime) SetLoader(loader ValueLoader) *Runtime { + rt.resolver.SetLoader(loader) return rt } @@ -52,16 +70,6 @@ func (rt *Runtime) Eval(ctx context.Context, fn *Function) error { return err } -func (rt *Runtime) EvalValue(ctx context.Context, fn *Function) (core.Value, error) { - out, err := rt.call(ctx, fn.returnValue()) - - if err != nil { - return values.None, err - } - - return CastToValue(out) -} - func (rt *Runtime) EvalRef(ctx context.Context, fn *Function) (runtime.RemoteObject, error) { out, err := rt.call(ctx, fn.returnRef()) @@ -69,60 +77,52 @@ func (rt *Runtime) EvalRef(ctx context.Context, fn *Function) (runtime.RemoteObj return runtime.RemoteObject{}, err } - return CastToReference(out) + return out, nil } -func (rt *Runtime) ReadProperty( - ctx context.Context, - objectID runtime.RemoteObjectID, - propName string, -) (core.Value, error) { - res, err := rt.client.Runtime.GetProperties( - ctx, - runtime.NewGetPropertiesArgs(objectID), - ) +func (rt *Runtime) EvalValue(ctx context.Context, fn *Function) (core.Value, error) { + out, err := rt.call(ctx, fn.returnValue()) if err != nil { return values.None, err } - if res.ExceptionDetails != nil { - return values.None, res.ExceptionDetails + return rt.resolver.ToValue(ctx, out) +} + +func (rt *Runtime) EvalElement(ctx context.Context, fn *Function) (drivers.HTMLElement, error) { + ref, err := rt.EvalRef(ctx, fn) + + if err != nil { + return nil, err } - // all props - if propName == "" { - arr := values.NewArray(len(res.Result)) + return rt.resolver.ToElement(ctx, ref) +} - for _, prop := range res.Result { - if prop.Value != nil { - val, err := Unmarshal(*prop.Value) +func (rt *Runtime) EvalElements(ctx context.Context, fn *Function) (*values.Array, error) { + ref, err := rt.EvalRef(ctx, fn) - if err != nil { - return values.None, err - } + if err != nil { + return nil, err + } - arr.Push(val) - } - } + val, err := rt.resolver.ToValue(ctx, ref) - return arr, nil + if err != nil { + return nil, err } - for _, prop := range res.Result { - if prop.Name == propName { - if prop.Value != nil { - return Unmarshal(*prop.Value) - } + arr, ok := val.(*values.Array) - return values.None, nil - } + if ok { + return arr, nil } - return values.None, nil + return values.NewArrayWith(val), nil } -func (rt *Runtime) call(ctx context.Context, fn *Function) (interface{}, error) { +func (rt *Runtime) call(ctx context.Context, fn *Function) (runtime.RemoteObject, error) { log := rt.logger.With(). Str("expression", fn.String()). Str("returns", fn.returnType.String()). @@ -138,13 +138,13 @@ func (rt *Runtime) call(ctx context.Context, fn *Function) (interface{}, error) if err != nil { log.Trace().Err(err).Msg("failed executing expression") - return nil, errors.Wrap(err, "runtime call") + return runtime.RemoteObject{}, errors.Wrap(err, "runtime call") } if err := parseRuntimeException(repl.ExceptionDetails); err != nil { log.Trace().Err(err).Msg("expression has failed with runtime exception") - return nil, err + return runtime.RemoteObject{}, err } var className string @@ -166,12 +166,5 @@ func (rt *Runtime) call(ctx context.Context, fn *Function) (interface{}, error) Str("return-value", string(repl.Result.Value)). Msg("succeeded executing expression") - switch fn.returnType { - case ReturnValue: - return Unmarshal(repl.Result) - case ReturnRef: - return repl.Result, nil - default: - return nil, nil - } + return repl.Result, nil } diff --git a/pkg/drivers/cdp/eval/types.go b/pkg/drivers/cdp/eval/types.go new file mode 100644 index 00000000..b851b25a --- /dev/null +++ b/pkg/drivers/cdp/eval/types.go @@ -0,0 +1,40 @@ +package eval + +import ( + "github.com/mafredri/cdp/protocol/runtime" +) + +type RemoteType string + +// List of possible remote types +const ( + UnknownType RemoteType = "" + NullType RemoteType = "null" + UndefinedType RemoteType = "undefined" + ArrayType RemoteType = "array" + NodeType RemoteType = "node" + RegexpType RemoteType = "regexp" + DateType RemoteType = "date" + MapType RemoteType = "map" + SetType RemoteType = "set" + WeakMapType RemoteType = "weakmap" + WeakSetType RemoteType = "weakset" + IteratorType RemoteType = "iterator" + GeneratorType RemoteType = "generator" + ErrorType RemoteType = "error" + ProxyType RemoteType = "proxy" + PromiseType RemoteType = "promise" + TypedArrayType RemoteType = "typedarray" + ArrayBufferType RemoteType = "arraybuffer" + DataViewType RemoteType = "dataview" +) + +func ToRemoteType(ref runtime.RemoteObject) RemoteType { + var subtype string + + if ref.Subtype != nil { + subtype = *ref.Subtype + } + + return RemoteType(subtype) +} diff --git a/pkg/drivers/cdp/input/manager.go b/pkg/drivers/cdp/input/manager.go index 2d744850..c3f1423d 100644 --- a/pkg/drivers/cdp/input/manager.go +++ b/pkg/drivers/cdp/input/manager.go @@ -476,75 +476,6 @@ func (m *Manager) ClickBySelector(ctx context.Context, id runtime.RemoteObjectID return nil } -func (m *Manager) ClickBySelectorAll(_ context.Context, _ runtime.RemoteObjectID, _ values.String, _ values.Int) error { - // TODO: Use dom.QueryManager - //m.logger.Trace(). - // Str("parent_object_id", string(id)). - // Str("selector", string(selector)). - // Int("count", int(count)). - // Msg("starting to click on elements by selector") - // - //if err := m.ScrollIntoViewBySelector(ctx, id, selector, drivers.ScrollOptions{ - // Behavior: drivers.ScrollBehaviorAuto, - // Block: drivers.ScrollVerticalAlignmentCenter, - // Inline: drivers.ScrollHorizontalAlignmentCenter, - //}); err != nil { - // return err - //} - // - //m.logger.Trace().Msg("looking up for elements by selector") - // - //found, err := m.exec.EvalRef(ctx, templates.QuerySelectorAll(id, selector)) - // - //if err != nil { - // m.logger.Trace().Err(err).Msg("failed to find an element by selector") - // - // return err - //} - // - //if found.ObjectID == nil { - // m.logger.Trace(). - // Err(core.ErrNotFound). - // Msg("element not found by selector") - // - // return core.ErrNotFound - //} - // - //for idx, nodeID := range found.NodeIDs { - // if idx > 0 { - // m.logger.Trace().Msg("pausing") - // beforeClickDelay := time.Duration(core.NumberLowerBoundary(drivers.DefaultMouseDelay*10)) * time.Millisecond - // - // time.Sleep(beforeClickDelay) - // } - // - // m.logger.Trace().Int("object_id", int(nodeID)).Msg("calculating clickable element points") - // - // points, err := GetClickablePointByNodeID(ctx, m.client, nodeID) - // - // if err != nil { - // m.logger.Trace().Err(err).Msg("failed calculating clickable element points") - // - // return err - // } - // - // m.logger.Trace().Float64("x", points.X).Float64("y", points.Y).Msg("calculated clickable element points") - // - // delay := time.Duration(drivers.DefaultMouseDelay) * time.Millisecond - // - // if err := m.mouse.ClickWithCount(ctx, points.X, points.Y, delay, count); err != nil { - // m.logger.Trace().Err(err).Msg("failed to click on an element") - // return nil - // } - // - // m.logger.Trace().Msg("clicked on an element") - //} - // - //m.logger.Trace().Msg("clicked on all elements") - // - return nil -} - func (m *Manager) Type(ctx context.Context, objectID runtime.RemoteObjectID, params TypeParams) error { m.logger.Trace(). Str("object_id", string(objectID)). diff --git a/pkg/drivers/cdp/input/quad.go b/pkg/drivers/cdp/input/quad.go index 7f4fe17b..176802f3 100644 --- a/pkg/drivers/cdp/input/quad.go +++ b/pkg/drivers/cdp/input/quad.go @@ -111,43 +111,6 @@ func getClickablePoint(ctx context.Context, client *cdp.Client, qargs *dom.GetCo }, nil } -func getClickablePoint2(ctx context.Context, client *cdp.Client, qargs *dom.GetContentQuadsArgs) (Quad, error) { - contentQuadsReply, err := client.DOM.GetContentQuads(ctx, qargs) - - if err != nil { - return Quad{}, err - } - - if contentQuadsReply.Quads == nil || len(contentQuadsReply.Quads) == 0 { - return Quad{}, errors.New("node is either not visible or not an HTMLElement") - } - - content := contentQuadsReply.Quads[0] - - c := len(content) - - if c%2 != 0 || c < 1 { - return Quad{}, errors.New("node is either not visible or not an HTMLElement") - } - - var x, y float64 - for i := 0; i < c; i += 2 { - x += content[i] - y += content[i+1] - } - x /= float64(c / 2) - y /= float64(c / 2) - - return Quad{ - X: x, - Y: y, - }, nil -} - -func GetClickablePointByNodeID(ctx context.Context, client *cdp.Client, nodeID dom.NodeID) (Quad, error) { - return getClickablePoint(ctx, client, dom.NewGetContentQuadsArgs().SetNodeID(nodeID)) -} - func GetClickablePointByObjectID(ctx context.Context, client *cdp.Client, objectID runtime.RemoteObjectID) (Quad, error) { return getClickablePoint(ctx, client, dom.NewGetContentQuadsArgs().SetObjectID(objectID)) } diff --git a/pkg/drivers/cdp/network/manager.go b/pkg/drivers/cdp/network/manager.go index cd02fcc8..5c129fed 100644 --- a/pkg/drivers/cdp/network/manager.go +++ b/pkg/drivers/cdp/network/manager.go @@ -605,7 +605,7 @@ func (m *Manager) WaitForFrameNavigation(ctx context.Context, frameID page.Frame if ctx.Err() == nil { log.Trace().Msg("creating frame execution context") - ec, err := eval.New(ctx, m.logger, m.client, repl.Frame.ID) + ec, err := eval.Create(ctx, m.logger, m.client, repl.Frame.ID) if err != nil { log.Trace().Err(err).Msg("failed to create frame execution context") diff --git a/pkg/drivers/cdp/templates/global.go b/pkg/drivers/cdp/templates/document.go similarity index 53% rename from pkg/drivers/cdp/templates/global.go rename to pkg/drivers/cdp/templates/document.go index c33cb032..c437e7e8 100644 --- a/pkg/drivers/cdp/templates/global.go +++ b/pkg/drivers/cdp/templates/document.go @@ -13,3 +13,15 @@ return null; func DOMReady() *eval.Function { return eval.F(domReady) } + +const getTitle = `() => document.title` + +func GetTitle() *eval.Function { + return eval.F(getTitle) +} + +const getDocument = `() => document` + +func GetDocument() *eval.Function { + return eval.F(getDocument) +} From cca1aae429ce13247fa5ddb3c890b0f749d4c6a9 Mon Sep 17 00:00:00 2001 From: Tim Voronov Date: Sat, 11 Sep 2021 23:18:12 -0400 Subject: [PATCH 02/11] Added support of XPath query selector --- pkg/drivers/cdp/dom/document.go | 24 ++-- pkg/drivers/cdp/dom/element.go | 54 ++++---- pkg/drivers/cdp/eval/function.go | 5 + pkg/drivers/cdp/input/manager.go | 32 ++--- pkg/drivers/cdp/templates/blur.go | 34 +++-- pkg/drivers/cdp/templates/helpers.go | 102 ++++++--------- pkg/drivers/cdp/templates/inner_html.go | 65 +++++++--- pkg/drivers/cdp/templates/inner_text.go | 76 ++++++++--- pkg/drivers/cdp/templates/query.go | 80 +++++++++--- pkg/drivers/cdp/templates/scroll.go | 43 ++++-- pkg/drivers/cdp/templates/select.go | 49 ++++--- pkg/drivers/cdp/templates/wait.go | 165 +++++++++++++++++------- pkg/drivers/cdp/templates/xpath.go | 15 +++ pkg/drivers/common/getter.go | 2 +- pkg/drivers/helpers.go | 11 ++ pkg/drivers/http/document.go | 10 +- pkg/drivers/http/element.go | 81 +++++++----- pkg/drivers/http/element_test.go | 4 +- pkg/drivers/selector.go | 115 +++++++++++++++++ pkg/drivers/type.go | 28 ++-- pkg/drivers/value.go | 62 ++++----- pkg/stdlib/html/attr_query.go | 2 +- pkg/stdlib/html/blur.go | 2 +- pkg/stdlib/html/clear.go | 2 +- pkg/stdlib/html/click.go | 4 +- pkg/stdlib/html/click_all.go | 2 +- pkg/stdlib/html/element.go | 14 +- pkg/stdlib/html/focus.go | 4 +- pkg/stdlib/html/get_inner_html.go | 2 +- pkg/stdlib/html/get_inner_html_all.go | 2 +- pkg/stdlib/html/get_inner_text.go | 2 +- pkg/stdlib/html/get_inner_text_all.go | 2 +- pkg/stdlib/html/hover.go | 4 +- pkg/stdlib/html/input.go | 6 +- pkg/stdlib/html/lib.go | 1 + pkg/stdlib/html/pagination.go | 6 +- pkg/stdlib/html/press_selector.go | 2 +- pkg/stdlib/html/scroll_element.go | 8 +- pkg/stdlib/html/select.go | 2 +- pkg/stdlib/html/set_inner_html.go | 2 +- pkg/stdlib/html/set_inner_text.go | 2 +- pkg/stdlib/html/wait_attr.go | 2 +- pkg/stdlib/html/wait_attr_all.go | 2 +- pkg/stdlib/html/wait_class.go | 2 +- pkg/stdlib/html/wait_class_all.go | 2 +- pkg/stdlib/html/wait_element.go | 4 +- pkg/stdlib/html/wait_style.go | 2 +- pkg/stdlib/html/wait_style_all.go | 2 +- pkg/stdlib/html/xpath_selector.go | 20 +++ 49 files changed, 786 insertions(+), 378 deletions(-) create mode 100644 pkg/drivers/selector.go create mode 100644 pkg/stdlib/html/xpath_selector.go diff --git a/pkg/drivers/cdp/dom/document.go b/pkg/drivers/cdp/dom/document.go index b4c728f2..cf8875c4 100644 --- a/pkg/drivers/cdp/dom/document.go +++ b/pkg/drivers/cdp/dom/document.go @@ -202,19 +202,19 @@ func (doc *HTMLDocument) GetChildNode(ctx context.Context, idx values.Int) (core return doc.element.GetChildNode(ctx, idx) } -func (doc *HTMLDocument) QuerySelector(ctx context.Context, selector values.String) (core.Value, error) { +func (doc *HTMLDocument) QuerySelector(ctx context.Context, selector drivers.QuerySelector) (core.Value, error) { return doc.element.QuerySelector(ctx, selector) } -func (doc *HTMLDocument) QuerySelectorAll(ctx context.Context, selector values.String) (*values.Array, error) { +func (doc *HTMLDocument) QuerySelectorAll(ctx context.Context, selector drivers.QuerySelector) (*values.Array, error) { return doc.element.QuerySelectorAll(ctx, selector) } -func (doc *HTMLDocument) CountBySelector(ctx context.Context, selector values.String) (values.Int, error) { +func (doc *HTMLDocument) CountBySelector(ctx context.Context, selector drivers.QuerySelector) (values.Int, error) { return doc.element.CountBySelector(ctx, selector) } -func (doc *HTMLDocument) ExistsBySelector(ctx context.Context, selector values.String) (values.Boolean, error) { +func (doc *HTMLDocument) ExistsBySelector(ctx context.Context, selector drivers.QuerySelector) (values.Boolean, error) { return doc.element.ExistsBySelector(ctx, selector) } @@ -284,7 +284,7 @@ func (doc *HTMLDocument) MoveMouseByXY(ctx context.Context, x, y values.Float) e return doc.input.MoveMouseByXY(ctx, x, y) } -func (doc *HTMLDocument) WaitForElement(ctx context.Context, selector values.String, when drivers.WaitEvent) error { +func (doc *HTMLDocument) WaitForElement(ctx context.Context, selector drivers.QuerySelector, when drivers.WaitEvent) error { task := events.NewEvalWaitTask( doc.eval, templates.WaitForElement(doc.element.id, selector, when), @@ -296,7 +296,7 @@ func (doc *HTMLDocument) WaitForElement(ctx context.Context, selector values.Str return err } -func (doc *HTMLDocument) WaitForClassBySelector(ctx context.Context, selector, class values.String, when drivers.WaitEvent) error { +func (doc *HTMLDocument) WaitForClassBySelector(ctx context.Context, selector drivers.QuerySelector, class values.String, when drivers.WaitEvent) error { task := events.NewEvalWaitTask( doc.eval, templates.WaitForClassBySelector(doc.element.id, selector, class, when), @@ -308,7 +308,7 @@ func (doc *HTMLDocument) WaitForClassBySelector(ctx context.Context, selector, c return err } -func (doc *HTMLDocument) WaitForClassBySelectorAll(ctx context.Context, selector, class values.String, when drivers.WaitEvent) error { +func (doc *HTMLDocument) WaitForClassBySelectorAll(ctx context.Context, selector drivers.QuerySelector, class values.String, when drivers.WaitEvent) error { task := events.NewEvalWaitTask( doc.eval, templates.WaitForClassBySelectorAll(doc.element.id, selector, class, when), @@ -322,7 +322,7 @@ func (doc *HTMLDocument) WaitForClassBySelectorAll(ctx context.Context, selector func (doc *HTMLDocument) WaitForAttributeBySelector( ctx context.Context, - selector, + selector drivers.QuerySelector, name, value values.String, when drivers.WaitEvent, @@ -340,7 +340,7 @@ func (doc *HTMLDocument) WaitForAttributeBySelector( func (doc *HTMLDocument) WaitForAttributeBySelectorAll( ctx context.Context, - selector, + selector drivers.QuerySelector, name, value values.String, when drivers.WaitEvent, @@ -356,7 +356,7 @@ func (doc *HTMLDocument) WaitForAttributeBySelectorAll( return err } -func (doc *HTMLDocument) WaitForStyleBySelector(ctx context.Context, selector, name, value values.String, when drivers.WaitEvent) error { +func (doc *HTMLDocument) WaitForStyleBySelector(ctx context.Context, selector drivers.QuerySelector, name, value values.String, when drivers.WaitEvent) error { task := events.NewEvalWaitTask( doc.eval, templates.WaitForStyleBySelector(doc.element.id, selector, name, value, when), @@ -368,7 +368,7 @@ func (doc *HTMLDocument) WaitForStyleBySelector(ctx context.Context, selector, n return err } -func (doc *HTMLDocument) WaitForStyleBySelectorAll(ctx context.Context, selector, name, value values.String, when drivers.WaitEvent) error { +func (doc *HTMLDocument) WaitForStyleBySelectorAll(ctx context.Context, selector drivers.QuerySelector, name, value values.String, when drivers.WaitEvent) error { task := events.NewEvalWaitTask( doc.eval, templates.WaitForStyleBySelectorAll(doc.element.id, selector, name, value, when), @@ -388,7 +388,7 @@ func (doc *HTMLDocument) ScrollBottom(ctx context.Context, options drivers.Scrol return doc.input.ScrollBottom(ctx, options) } -func (doc *HTMLDocument) ScrollBySelector(ctx context.Context, selector values.String, options drivers.ScrollOptions) error { +func (doc *HTMLDocument) ScrollBySelector(ctx context.Context, selector drivers.QuerySelector, options drivers.ScrollOptions) error { return doc.input.ScrollIntoViewBySelector(ctx, doc.element.id, selector, options) } diff --git a/pkg/drivers/cdp/dom/element.go b/pkg/drivers/cdp/dom/element.go index 311c869d..0335bc03 100644 --- a/pkg/drivers/cdp/dom/element.go +++ b/pkg/drivers/cdp/dom/element.go @@ -280,11 +280,11 @@ func (el *HTMLElement) GetNextElementSibling(ctx context.Context) (core.Value, e return el.exec.EvalElement(ctx, templates.GetNextElementSibling(el.id)) } -func (el *HTMLElement) QuerySelector(ctx context.Context, selector values.String) (core.Value, error) { +func (el *HTMLElement) QuerySelector(ctx context.Context, selector drivers.QuerySelector) (core.Value, error) { return el.exec.EvalElement(ctx, templates.QuerySelector(el.id, selector)) } -func (el *HTMLElement) QuerySelectorAll(ctx context.Context, selector values.String) (*values.Array, error) { +func (el *HTMLElement) QuerySelectorAll(ctx context.Context, selector drivers.QuerySelector) (*values.Array, error) { return el.exec.EvalElements(ctx, templates.QuerySelectorAll(el.id, selector)) } @@ -309,7 +309,7 @@ func (el *HTMLElement) SetInnerText(ctx context.Context, innerText values.String ) } -func (el *HTMLElement) GetInnerTextBySelector(ctx context.Context, selector values.String) (values.String, error) { +func (el *HTMLElement) GetInnerTextBySelector(ctx context.Context, selector drivers.QuerySelector) (values.String, error) { out, err := el.exec.EvalValue(ctx, templates.GetInnerTextBySelector(el.id, selector)) if err != nil { @@ -319,14 +319,14 @@ func (el *HTMLElement) GetInnerTextBySelector(ctx context.Context, selector valu return values.ToString(out), nil } -func (el *HTMLElement) SetInnerTextBySelector(ctx context.Context, selector, innerText values.String) error { +func (el *HTMLElement) SetInnerTextBySelector(ctx context.Context, selector drivers.QuerySelector, innerText values.String) error { return el.exec.Eval( ctx, templates.SetInnerTextBySelector(el.id, selector, innerText), ) } -func (el *HTMLElement) GetInnerTextBySelectorAll(ctx context.Context, selector values.String) (*values.Array, error) { +func (el *HTMLElement) GetInnerTextBySelectorAll(ctx context.Context, selector drivers.QuerySelector) (*values.Array, error) { out, err := el.exec.EvalValue(ctx, templates.GetInnerTextBySelectorAll(el.id, selector)) if err != nil { @@ -350,7 +350,7 @@ func (el *HTMLElement) SetInnerHTML(ctx context.Context, innerHTML values.String return el.exec.Eval(ctx, templates.SetInnerHTML(el.id, innerHTML)) } -func (el *HTMLElement) GetInnerHTMLBySelector(ctx context.Context, selector values.String) (values.String, error) { +func (el *HTMLElement) GetInnerHTMLBySelector(ctx context.Context, selector drivers.QuerySelector) (values.String, error) { out, err := el.exec.EvalValue(ctx, templates.GetInnerHTMLBySelector(el.id, selector)) if err != nil { @@ -360,11 +360,11 @@ func (el *HTMLElement) GetInnerHTMLBySelector(ctx context.Context, selector valu return values.ToString(out), nil } -func (el *HTMLElement) SetInnerHTMLBySelector(ctx context.Context, selector, innerHTML values.String) error { +func (el *HTMLElement) SetInnerHTMLBySelector(ctx context.Context, selector drivers.QuerySelector, innerHTML values.String) error { return el.exec.Eval(ctx, templates.SetInnerHTMLBySelector(el.id, selector, innerHTML)) } -func (el *HTMLElement) GetInnerHTMLBySelectorAll(ctx context.Context, selector values.String) (*values.Array, error) { +func (el *HTMLElement) GetInnerHTMLBySelectorAll(ctx context.Context, selector drivers.QuerySelector) (*values.Array, error) { out, err := el.exec.EvalValue(ctx, templates.GetInnerHTMLBySelectorAll(el.id, selector)) if err != nil { @@ -374,7 +374,7 @@ func (el *HTMLElement) GetInnerHTMLBySelectorAll(ctx context.Context, selector v return values.ToArray(ctx, out), nil } -func (el *HTMLElement) CountBySelector(ctx context.Context, selector values.String) (values.Int, error) { +func (el *HTMLElement) CountBySelector(ctx context.Context, selector drivers.QuerySelector) (values.Int, error) { out, err := el.exec.EvalValue(ctx, templates.CountBySelector(el.id, selector)) if err != nil { @@ -384,7 +384,7 @@ func (el *HTMLElement) CountBySelector(ctx context.Context, selector values.Stri return values.ToInt(out), nil } -func (el *HTMLElement) ExistsBySelector(ctx context.Context, selector values.String) (values.Boolean, error) { +func (el *HTMLElement) ExistsBySelector(ctx context.Context, selector drivers.QuerySelector) (values.Boolean, error) { out, err := el.exec.EvalValue(ctx, templates.ExistsBySelector(el.id, selector)) if err != nil { @@ -394,7 +394,7 @@ func (el *HTMLElement) ExistsBySelector(ctx context.Context, selector values.Str return values.ToBoolean(out), nil } -func (el *HTMLElement) WaitForElement(ctx context.Context, selector values.String, when drivers.WaitEvent) error { +func (el *HTMLElement) WaitForElement(ctx context.Context, selector drivers.QuerySelector, when drivers.WaitEvent) error { task := events.NewEvalWaitTask( el.exec, templates.WaitForElement(el.id, selector, when), @@ -406,7 +406,7 @@ func (el *HTMLElement) WaitForElement(ctx context.Context, selector values.Strin return err } -func (el *HTMLElement) WaitForElementAll(ctx context.Context, selector values.String, when drivers.WaitEvent) error { +func (el *HTMLElement) WaitForElementAll(ctx context.Context, selector drivers.QuerySelector, when drivers.WaitEvent) error { task := events.NewEvalWaitTask( el.exec, templates.WaitForElementAll(el.id, selector, when), @@ -430,7 +430,7 @@ func (el *HTMLElement) WaitForClass(ctx context.Context, class values.String, wh return err } -func (el *HTMLElement) WaitForClassBySelector(ctx context.Context, selector, class values.String, when drivers.WaitEvent) error { +func (el *HTMLElement) WaitForClassBySelector(ctx context.Context, selector drivers.QuerySelector, class values.String, when drivers.WaitEvent) error { task := events.NewEvalWaitTask( el.exec, templates.WaitForClassBySelector(el.id, selector, class, when), @@ -442,7 +442,7 @@ func (el *HTMLElement) WaitForClassBySelector(ctx context.Context, selector, cla return err } -func (el *HTMLElement) WaitForClassBySelectorAll(ctx context.Context, selector, class values.String, when drivers.WaitEvent) error { +func (el *HTMLElement) WaitForClassBySelectorAll(ctx context.Context, selector drivers.QuerySelector, class values.String, when drivers.WaitEvent) error { task := events.NewEvalWaitTask( el.exec, templates.WaitForClassBySelectorAll(el.id, selector, class, when), @@ -471,7 +471,7 @@ func (el *HTMLElement) WaitForAttribute( return err } -func (el *HTMLElement) WaitForAttributeBySelector(ctx context.Context, selector, name values.String, value core.Value, when drivers.WaitEvent) error { +func (el *HTMLElement) WaitForAttributeBySelector(ctx context.Context, selector drivers.QuerySelector, name values.String, value core.Value, when drivers.WaitEvent) error { task := events.NewEvalWaitTask( el.exec, templates.WaitForAttributeBySelector(el.id, selector, name, value, when), @@ -483,7 +483,7 @@ func (el *HTMLElement) WaitForAttributeBySelector(ctx context.Context, selector, return err } -func (el *HTMLElement) WaitForAttributeBySelectorAll(ctx context.Context, selector, name values.String, value core.Value, when drivers.WaitEvent) error { +func (el *HTMLElement) WaitForAttributeBySelectorAll(ctx context.Context, selector drivers.QuerySelector, name values.String, value core.Value, when drivers.WaitEvent) error { task := events.NewEvalWaitTask( el.exec, templates.WaitForAttributeBySelectorAll(el.id, selector, name, value, when), @@ -507,7 +507,7 @@ func (el *HTMLElement) WaitForStyle(ctx context.Context, name values.String, val return err } -func (el *HTMLElement) WaitForStyleBySelector(ctx context.Context, selector, name values.String, value core.Value, when drivers.WaitEvent) error { +func (el *HTMLElement) WaitForStyleBySelector(ctx context.Context, selector drivers.QuerySelector, name values.String, value core.Value, when drivers.WaitEvent) error { task := events.NewEvalWaitTask( el.exec, templates.WaitForStyleBySelector(el.id, selector, name, value, when), @@ -519,7 +519,7 @@ func (el *HTMLElement) WaitForStyleBySelector(ctx context.Context, selector, nam return err } -func (el *HTMLElement) WaitForStyleBySelectorAll(ctx context.Context, selector, name values.String, value core.Value, when drivers.WaitEvent) error { +func (el *HTMLElement) WaitForStyleBySelectorAll(ctx context.Context, selector drivers.QuerySelector, name values.String, value core.Value, when drivers.WaitEvent) error { task := events.NewEvalWaitTask( el.exec, templates.WaitForStyleBySelectorAll(el.id, selector, name, value, when), @@ -535,11 +535,11 @@ func (el *HTMLElement) Click(ctx context.Context, count values.Int) error { return el.input.Click(ctx, el.id, int(count)) } -func (el *HTMLElement) ClickBySelector(ctx context.Context, selector values.String, count values.Int) error { +func (el *HTMLElement) ClickBySelector(ctx context.Context, selector drivers.QuerySelector, count values.Int) error { return el.input.ClickBySelector(ctx, el.id, selector, count) } -func (el *HTMLElement) ClickBySelectorAll(ctx context.Context, selector values.String, count values.Int) error { +func (el *HTMLElement) ClickBySelectorAll(ctx context.Context, selector drivers.QuerySelector, count values.Int) error { elements, err := el.QuerySelectorAll(ctx, selector) if err != nil { @@ -578,7 +578,7 @@ func (el *HTMLElement) Input(ctx context.Context, value core.Value, delay values }) } -func (el *HTMLElement) InputBySelector(ctx context.Context, selector values.String, value core.Value, delay values.Int) error { +func (el *HTMLElement) InputBySelector(ctx context.Context, selector drivers.QuerySelector, value core.Value, delay values.Int) error { return el.input.TypeBySelector(ctx, el.id, selector, input.TypeParams{ Text: value.String(), Clear: false, @@ -590,7 +590,7 @@ func (el *HTMLElement) Press(ctx context.Context, keys []values.String, count va return el.input.Press(ctx, values.UnwrapStrings(keys), int(count)) } -func (el *HTMLElement) PressBySelector(ctx context.Context, selector values.String, keys []values.String, count values.Int) error { +func (el *HTMLElement) PressBySelector(ctx context.Context, selector drivers.QuerySelector, keys []values.String, count values.Int) error { return el.input.PressBySelector(ctx, el.id, selector, values.UnwrapStrings(keys), int(count)) } @@ -598,7 +598,7 @@ func (el *HTMLElement) Clear(ctx context.Context) error { return el.input.Clear(ctx, el.id) } -func (el *HTMLElement) ClearBySelector(ctx context.Context, selector values.String) error { +func (el *HTMLElement) ClearBySelector(ctx context.Context, selector drivers.QuerySelector) error { return el.input.ClearBySelector(ctx, el.id, selector) } @@ -606,7 +606,7 @@ func (el *HTMLElement) Select(ctx context.Context, value *values.Array) (*values return el.input.Select(ctx, el.id, value) } -func (el *HTMLElement) SelectBySelector(ctx context.Context, selector values.String, value *values.Array) (*values.Array, error) { +func (el *HTMLElement) SelectBySelector(ctx context.Context, selector drivers.QuerySelector, value *values.Array) (*values.Array, error) { return el.input.SelectBySelector(ctx, el.id, selector, value) } @@ -618,7 +618,7 @@ func (el *HTMLElement) Focus(ctx context.Context) error { return el.input.Focus(ctx, el.id) } -func (el *HTMLElement) FocusBySelector(ctx context.Context, selector values.String) error { +func (el *HTMLElement) FocusBySelector(ctx context.Context, selector drivers.QuerySelector) error { return el.input.FocusBySelector(ctx, el.id, selector) } @@ -626,7 +626,7 @@ func (el *HTMLElement) Blur(ctx context.Context) error { return el.input.Blur(ctx, el.id) } -func (el *HTMLElement) BlurBySelector(ctx context.Context, selector values.String) error { +func (el *HTMLElement) BlurBySelector(ctx context.Context, selector drivers.QuerySelector) error { return el.input.BlurBySelector(ctx, el.id, selector) } @@ -634,7 +634,7 @@ func (el *HTMLElement) Hover(ctx context.Context) error { return el.input.MoveMouse(ctx, el.id) } -func (el *HTMLElement) HoverBySelector(ctx context.Context, selector values.String) error { +func (el *HTMLElement) HoverBySelector(ctx context.Context, selector drivers.QuerySelector) error { return el.input.MoveMouseBySelector(ctx, el.id, selector) } diff --git a/pkg/drivers/cdp/eval/function.go b/pkg/drivers/cdp/eval/function.go index 2f6dc88f..7ee0d0c0 100644 --- a/pkg/drivers/cdp/eval/function.go +++ b/pkg/drivers/cdp/eval/function.go @@ -1,6 +1,7 @@ package eval import ( + "github.com/MontFerret/ferret/pkg/drivers" "github.com/MontFerret/ferret/pkg/runtime/core" "github.com/mafredri/cdp/protocol/runtime" "github.com/rs/zerolog" @@ -74,6 +75,10 @@ func (fn *Function) WithArgValue(value core.Value) *Function { }) } +func (fn *Function) WithArgSelector(selector drivers.QuerySelector) *Function { + return fn.WithArg(selector.String()) +} + func (fn *Function) WithArg(value interface{}) *Function { raw, err := jettison.MarshalOpts(value, jettison.NoHTMLEscaping()) diff --git a/pkg/drivers/cdp/input/manager.go b/pkg/drivers/cdp/input/manager.go index c3f1423d..dd92d374 100644 --- a/pkg/drivers/cdp/input/manager.go +++ b/pkg/drivers/cdp/input/manager.go @@ -2,18 +2,18 @@ package input import ( "context" - "github.com/MontFerret/ferret/pkg/runtime/logging" - "github.com/rs/zerolog" "time" "github.com/mafredri/cdp" "github.com/mafredri/cdp/protocol/dom" "github.com/mafredri/cdp/protocol/runtime" + "github.com/rs/zerolog" "github.com/MontFerret/ferret/pkg/drivers" "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/logging" "github.com/MontFerret/ferret/pkg/runtime/values" ) @@ -114,7 +114,7 @@ func (m *Manager) ScrollIntoView(ctx context.Context, id runtime.RemoteObjectID, return nil } -func (m *Manager) ScrollIntoViewBySelector(ctx context.Context, id runtime.RemoteObjectID, selector values.String, options drivers.ScrollOptions) error { +func (m *Manager) ScrollIntoViewBySelector(ctx context.Context, id runtime.RemoteObjectID, selector drivers.QuerySelector, options drivers.ScrollOptions) error { m.logger.Trace(). Str("selector", selector.String()). Str("behavior", options.Behavior.String()). @@ -179,7 +179,7 @@ func (m *Manager) Focus(ctx context.Context, objectID runtime.RemoteObjectID) er return nil } -func (m *Manager) FocusBySelector(ctx context.Context, id runtime.RemoteObjectID, selector values.String) error { +func (m *Manager) FocusBySelector(ctx context.Context, id runtime.RemoteObjectID, selector drivers.QuerySelector) error { m.logger.Trace(). Str("parent_object_id", string(id)). Str("selector", selector.String()). @@ -246,7 +246,7 @@ func (m *Manager) Blur(ctx context.Context, objectID runtime.RemoteObjectID) err return nil } -func (m *Manager) BlurBySelector(ctx context.Context, id runtime.RemoteObjectID, selector values.String) error { +func (m *Manager) BlurBySelector(ctx context.Context, id runtime.RemoteObjectID, selector drivers.QuerySelector) error { m.logger.Trace(). Str("parent_object_id", string(id)). Str("selector", selector.String()). @@ -299,7 +299,7 @@ func (m *Manager) MoveMouse(ctx context.Context, objectID runtime.RemoteObjectID return nil } -func (m *Manager) MoveMouseBySelector(ctx context.Context, id runtime.RemoteObjectID, selector values.String) error { +func (m *Manager) MoveMouseBySelector(ctx context.Context, id runtime.RemoteObjectID, selector drivers.QuerySelector) error { m.logger.Trace(). Str("parent_object_id", string(id)). Str("selector", selector.String()). @@ -419,10 +419,10 @@ func (m *Manager) Click(ctx context.Context, objectID runtime.RemoteObjectID, co return nil } -func (m *Manager) ClickBySelector(ctx context.Context, id runtime.RemoteObjectID, selector values.String, count values.Int) error { +func (m *Manager) ClickBySelector(ctx context.Context, id runtime.RemoteObjectID, selector drivers.QuerySelector, count values.Int) error { m.logger.Trace(). Str("parent_object_id", string(id)). - Str("selector", string(selector)). + Str("selector", selector.String()). Int("count", int(count)). Msg("starting to click on an element by selector") @@ -539,10 +539,10 @@ func (m *Manager) Type(ctx context.Context, objectID runtime.RemoteObjectID, par return nil } -func (m *Manager) TypeBySelector(ctx context.Context, id runtime.RemoteObjectID, selector values.String, params TypeParams) error { +func (m *Manager) TypeBySelector(ctx context.Context, id runtime.RemoteObjectID, selector drivers.QuerySelector, params TypeParams) error { m.logger.Trace(). Str("parent_object_id", string(id)). - Str("selector", string(selector)). + Str("selector", selector.String()). Msg("starting to type text by selector") err := m.ScrollIntoViewBySelector(ctx, id, selector, drivers.ScrollOptions{ @@ -672,10 +672,10 @@ func (m *Manager) Clear(ctx context.Context, objectID runtime.RemoteObjectID) er return nil } -func (m *Manager) ClearBySelector(ctx context.Context, id runtime.RemoteObjectID, selector values.String) error { +func (m *Manager) ClearBySelector(ctx context.Context, id runtime.RemoteObjectID, selector drivers.QuerySelector) error { m.logger.Trace(). Str("parent_object_id", string(id)). - Str("selector", string(selector)). + Str("selector", selector.String()). Msg("starting to clear element by selector") err := m.ScrollIntoViewBySelector(ctx, id, selector, drivers.ScrollOptions{ @@ -790,10 +790,10 @@ func (m *Manager) Press(ctx context.Context, keys []string, count int) error { return nil } -func (m *Manager) PressBySelector(ctx context.Context, id runtime.RemoteObjectID, selector values.String, keys []string, count int) error { +func (m *Manager) PressBySelector(ctx context.Context, id runtime.RemoteObjectID, selector drivers.QuerySelector, keys []string, count int) error { m.logger.Trace(). Str("parent_object_id", string(id)). - Str("selector", string(selector)). + Str("selector", selector.String()). Strs("keys", keys). Int("count", count). Msg("starting to press keyboard keys by selector") @@ -840,10 +840,10 @@ func (m *Manager) Select(ctx context.Context, id runtime.RemoteObjectID, value * return arr, nil } -func (m *Manager) SelectBySelector(ctx context.Context, id runtime.RemoteObjectID, selector values.String, value *values.Array) (*values.Array, error) { +func (m *Manager) SelectBySelector(ctx context.Context, id runtime.RemoteObjectID, selector drivers.QuerySelector, value *values.Array) (*values.Array, error) { m.logger.Trace(). Str("parent_object_id", string(id)). - Str("selector", string(selector)). + Str("selector", selector.String()). Msg("starting to select values by selector") if err := m.FocusBySelector(ctx, id, selector); err != nil { diff --git a/pkg/drivers/cdp/templates/blur.go b/pkg/drivers/cdp/templates/blur.go index 670eb6f7..91d1c03d 100644 --- a/pkg/drivers/cdp/templates/blur.go +++ b/pkg/drivers/cdp/templates/blur.go @@ -2,9 +2,9 @@ package templates import ( "fmt" + "github.com/MontFerret/ferret/pkg/drivers" "github.com/MontFerret/ferret/pkg/drivers/cdp/eval" - "github.com/MontFerret/ferret/pkg/runtime/values" "github.com/mafredri/cdp/protocol/runtime" ) @@ -16,18 +16,36 @@ func Blur(id runtime.RemoteObjectID) *eval.Function { return eval.F(blur).WithArgRef(id) } -var blurBySelector = fmt.Sprintf(` +var ( + blurByCSSSelector = fmt.Sprintf(` (el, selector) => { const found = el.querySelector(selector); - if (found == null) { - throw new Error(%s) - } + %s + + found.blur(); + } +`, notFoundErrorFragment) + + blurByXPathSelector = fmt.Sprintf(` + (el, selector) => { + %s + + %s found.blur(); } -`, ParamErr(drivers.ErrNotFound)) +`, xpathAsElementFragment, notFoundErrorFragment) +) + +func BlurBySelector(id runtime.RemoteObjectID, selector drivers.QuerySelector) *eval.Function { + var f *eval.Function + + if selector.Variant() == drivers.CSSSelector { + f = eval.F(blurByCSSSelector) + } else { + f = eval.F(blurByXPathSelector) + } -func BlurBySelector(id runtime.RemoteObjectID, selector values.String) *eval.Function { - return eval.F(blurBySelector).WithArgRef(id).WithArgValue(selector) + return f.WithArgRef(id).WithArgSelector(selector) } diff --git a/pkg/drivers/cdp/templates/helpers.go b/pkg/drivers/cdp/templates/helpers.go index e1193f0e..4110993e 100644 --- a/pkg/drivers/cdp/templates/helpers.go +++ b/pkg/drivers/cdp/templates/helpers.go @@ -1,86 +1,66 @@ package templates import ( - "bytes" + "fmt" "github.com/MontFerret/ferret/pkg/drivers" - "strconv" - - "github.com/MontFerret/ferret/pkg/runtime/core" - "github.com/MontFerret/ferret/pkg/runtime/values" + "github.com/MontFerret/ferret/pkg/drivers/cdp/eval" ) -func Param(input core.Value) string { - switch value := input.(type) { - case values.String: - return ParamString(value) - case values.Float: - return ParamFloat(value) - case values.Int: - return ParamInt(value) - default: - if value != values.None { - return value.String() - } - - return "null" - } -} - -func ParamList(value []core.Value) string { - var buf bytes.Buffer - lastIndex := len(value) - 1 - - for i, input := range value { - switch v := input.(type) { - case values.String: - buf.WriteString(EscapeString(string(v))) - default: - buf.WriteString(v.String()) +var ( + notFoundErrorFragment = fmt.Sprintf(` + if (found == null) { + throw new Error(%s); } +`, ParamErr(drivers.ErrNotFound)) +) - if i != lastIndex { - buf.WriteString(",") - } +const ( + toElementFragment = ` +(input) => { + let result = null; + + if (input instanceof Element) { + result = input; + } else if (Array.isArray(input) && input[0] instanceof Element) { + result = input[0] + } else if (input instanceof NodeList) { + result = input[0] } - return buf.String() + return result; } - -func ParamStringList(value []values.String) string { - var buf bytes.Buffer - lastIndex := len(value) - 1 - - for i, input := range value { - buf.WriteString(EscapeString(string(input))) - - if i != lastIndex { - buf.WriteString(",") - } +` + toElementArrayFragment = ` +(input) => { + if (input == null) { + return []; } - return buf.String() -} + if (Array.isArray(input)) { + return input.filter(i => i instanceof Element); + } -func ParamString(value values.String) string { - return EscapeString(string(value)) + if (input instanceof NodeList) { + return Array.from(input) + } + + return [input]; } +` +) func ParamErr(err error) string { return EscapeString(err.Error()) } -func ParamFloat(value values.Float) string { - return strconv.FormatFloat(float64(value), 'f', 6, 64) -} - -func ParamInt(value values.Int) string { - return strconv.Itoa(int(value)) -} - func EscapeString(value string) string { return "`" + value + "`" } -func flipWhen(when drivers.WaitEvent) drivers.WaitEvent { - return drivers.WaitEvent((int(when) + 1) % 2) +func toFunction(selector drivers.QuerySelector, cssTmpl, xPathTmpl string) *eval.Function { + if selector.Variant() == drivers.CSSSelector { + return eval.F(cssTmpl) + } + + return eval.F(xPathTmpl) } diff --git a/pkg/drivers/cdp/templates/inner_html.go b/pkg/drivers/cdp/templates/inner_html.go index 702bfd55..f7586682 100644 --- a/pkg/drivers/cdp/templates/inner_html.go +++ b/pkg/drivers/cdp/templates/inner_html.go @@ -28,40 +28,71 @@ func GetInnerHTML(id runtime.RemoteObjectID) *eval.Function { return eval.F(getInnerHTML).WithArgRef(id) } -var setInnerHTMLBySelector = fmt.Sprintf(`(el, selector, value) => { +var ( + setInnerHTMLByCSSSelector = fmt.Sprintf(`(el, selector, value) => { const found = el.querySelector(selector); - if (found == null) { - throw new Error(%s); - } + %s + + found.innerHTML = value; +}`, notFoundErrorFragment) + + setInnerHTMLByXPathSelector = fmt.Sprintf(`(el, selector, value) => { + %s + + %s found.innerHTML = value; -}`, ParamErr(drivers.ErrNotFound)) +}`, xpathAsElementFragment, notFoundErrorFragment) +) -func SetInnerHTMLBySelector(id runtime.RemoteObjectID, selector, value values.String) *eval.Function { - return eval.F(setInnerHTMLBySelector).WithArgRef(id).WithArgValue(selector).WithArgValue(value) +func SetInnerHTMLBySelector(id runtime.RemoteObjectID, selector drivers.QuerySelector, value values.String) *eval.Function { + return toFunction(selector, setInnerHTMLByCSSSelector, setInnerHTMLByXPathSelector). + WithArgRef(id). + WithArgSelector(selector). + WithArgValue(value) } -var getInnerHTMLBySelector = fmt.Sprintf(`(el, selector) => { +var ( + getInnerHTMLByCSSSelector = fmt.Sprintf(`(el, selector) => { const found = el.querySelector(selector); - if (found == null) { - throw new Error(%s); - } + %s return found.innerHTML; -}`, ParamErr(drivers.ErrNotFound)) +}`, notFoundErrorFragment) + + getInnerHTMLByXPathSelector = fmt.Sprintf(`(el, selector) => { + %s -func GetInnerHTMLBySelector(id runtime.RemoteObjectID, selector values.String) *eval.Function { - return eval.F(getInnerHTMLBySelector).WithArgRef(id).WithArgValue(selector) + %s + + return found.innerHTML; +}`, xpathAsElementFragment, notFoundErrorFragment) +) + +func GetInnerHTMLBySelector(id runtime.RemoteObjectID, selector drivers.QuerySelector) *eval.Function { + return toFunction(selector, getInnerHTMLByCSSSelector, getInnerHTMLByXPathSelector). + WithArgRef(id). + WithArgSelector(selector) } -const getInnerHTMLBySelectorAll = `(el, selector) => { +const getInnerHTMLByCSSSelectorAll = `(el, selector) => { const found = el.querySelectorAll(selector); return Array.from(found).map(i => i.innerHTML); }` -func GetInnerHTMLBySelectorAll(id runtime.RemoteObjectID, selector values.String) *eval.Function { - return eval.F(getInnerHTMLBySelectorAll).WithArgRef(id).WithArgValue(selector) +var getInnerHTMLByXPathSelectorAll = fmt.Sprintf(`(el, selector) => { + %s + + %s + + return found.map(i => i.innerHTML); +}`, xpathAsElementArrayFragment, notFoundErrorFragment) + +func GetInnerHTMLBySelectorAll(id runtime.RemoteObjectID, selector drivers.QuerySelector) *eval.Function { + return toFunction(selector, getInnerHTMLByCSSSelectorAll, getInnerHTMLByXPathSelectorAll). + WithArgRef(id). + WithArgSelector(selector) } diff --git a/pkg/drivers/cdp/templates/inner_text.go b/pkg/drivers/cdp/templates/inner_text.go index b591720f..09a80052 100644 --- a/pkg/drivers/cdp/templates/inner_text.go +++ b/pkg/drivers/cdp/templates/inner_text.go @@ -28,47 +28,81 @@ func GetInnerText(id runtime.RemoteObjectID) *eval.Function { return eval.F(getInnerText).WithArgRef(id) } -var setInnerTextBySelector = fmt.Sprintf(` +var ( + setInnerTextByCSSSelector = fmt.Sprintf(` (el, selector, value) => { const found = el.querySelector(selector); - if (found == null) { - throw new Error(%s); - } + %s + + found.innerText = value; +}`, notFoundErrorFragment) + + setInnerTextByXPathSelector = fmt.Sprintf(` +(el, selector, value) => { + %s + + %s found.innerText = value; -}`, ParamErr(drivers.ErrNotFound)) +}`, xpathAsElementFragment, notFoundErrorFragment) +) -func SetInnerTextBySelector(id runtime.RemoteObjectID, selector, value values.String) *eval.Function { - return eval.F(setInnerTextBySelector).WithArgRef(id).WithArgValue(selector).WithArgValue(value) +func SetInnerTextBySelector(id runtime.RemoteObjectID, selector drivers.QuerySelector, value values.String) *eval.Function { + return toFunction(selector, setInnerTextByCSSSelector, setInnerTextByXPathSelector). + WithArgRef(id). + WithArgSelector(selector). + WithArgValue(value) } -var getInnerTextBySelector = fmt.Sprintf(` +var ( + getInnerTextByCSSSelector = fmt.Sprintf(` (el, selector) => { const found = el.querySelector(selector); - if (found == null) { - throw new Error(%s); - } + %s + + return found.innerText; +}`, notFoundErrorFragment) + + getInnerTextByXPathSelector = fmt.Sprintf(` +(el, selector) => { + %s + + %s return found.innerText; -}`, ParamErr(drivers.ErrNotFound)) +}`, xpathAsElementFragment, notFoundErrorFragment) +) -func GetInnerTextBySelector(id runtime.RemoteObjectID, selector values.String) *eval.Function { - return eval.F(getInnerTextBySelector).WithArgRef(id).WithArgValue(selector) +func GetInnerTextBySelector(id runtime.RemoteObjectID, selector drivers.QuerySelector) *eval.Function { + return toFunction(selector, getInnerTextByCSSSelector, getInnerTextByXPathSelector). + WithArgRef(id). + WithArgSelector(selector) } -var getInnerTextBySelectorAll = fmt.Sprintf(` +var ( + getInnerTextByCSSSelectorAll = fmt.Sprintf(` (el, selector) => { const found = el.querySelectorAll(selector); - if (found == null) { - throw new Error(%s); - } + %s return Array.from(found).map(i => i.innerText); -}`, ParamErr(drivers.ErrNotFound)) +}`, notFoundErrorFragment) + + getInnerTextByXPathSelectorAll = fmt.Sprintf(` +(el, selector) => { + %s + + %s + + return found.map(i => i.innerText); +}`, xpathAsElementArrayFragment, notFoundErrorFragment) +) -func GetInnerTextBySelectorAll(id runtime.RemoteObjectID, selector values.String) *eval.Function { - return eval.F(getInnerTextBySelectorAll).WithArgRef(id).WithArgValue(selector) +func GetInnerTextBySelectorAll(id runtime.RemoteObjectID, selector drivers.QuerySelector) *eval.Function { + return toFunction(selector, getInnerTextByCSSSelectorAll, getInnerTextByXPathSelectorAll). + WithArgRef(id). + WithArgSelector(selector) } diff --git a/pkg/drivers/cdp/templates/query.go b/pkg/drivers/cdp/templates/query.go index 533d7072..382f4413 100644 --- a/pkg/drivers/cdp/templates/query.go +++ b/pkg/drivers/cdp/templates/query.go @@ -4,37 +4,63 @@ import ( "fmt" "github.com/MontFerret/ferret/pkg/drivers" "github.com/MontFerret/ferret/pkg/drivers/cdp/eval" - "github.com/MontFerret/ferret/pkg/runtime/values" "github.com/mafredri/cdp/protocol/runtime" ) -var querySelector = fmt.Sprintf(` +const ( + queryCSSSelectorFragment = "const found = el.querySelector(selector);" + + queryCSSSelectorAllFragment = "const found = el.querySelectorAll(selector);" +) + +var ( + queryCSSSelector = fmt.Sprintf(` (el, selector) => { const found = el.querySelector(selector); - if (found == null) { - throw new Error(%s); - } + %s return found; } `, - ParamErr(drivers.ErrNotFound), + notFoundErrorFragment, + ) + queryXPathSelector = fmt.Sprintf(` + (el, selector) => { + %s + + %s + + return found; + } + `, + xpathAsElementFragment, notFoundErrorFragment, + ) ) -func QuerySelector(id runtime.RemoteObjectID, selector values.String) *eval.Function { - return eval.F(querySelector).WithArgRef(id).WithArgValue(selector) +func QuerySelector(id runtime.RemoteObjectID, selector drivers.QuerySelector) *eval.Function { + return toFunction(selector, queryCSSSelector, queryXPathSelector). + WithArgRef(id). + WithArgSelector(selector) } -const querySelectorAll = `(el, selector) => { +const queryCSSSelectorAll = `(el, selector) => { return el.querySelectorAll(selector); }` -func QuerySelectorAll(id runtime.RemoteObjectID, selector values.String) *eval.Function { - return eval.F(querySelectorAll).WithArgRef(id).WithArgValue(selector) +var queryXPathSelectorAll = fmt.Sprintf(`(el, selector) => { + %s + + return found; +}`, xpathAsElementArrayFragment) + +func QuerySelectorAll(id runtime.RemoteObjectID, selector drivers.QuerySelector) *eval.Function { + return toFunction(selector, queryCSSSelectorAll, queryXPathSelectorAll). + WithArgRef(id). + WithArgSelector(selector) } -const existsBySelector = ` +const existsByCSSSelector = ` (el, selector) => { const found = el.querySelector(selector); @@ -42,11 +68,21 @@ const existsBySelector = ` } ` -func ExistsBySelector(id runtime.RemoteObjectID, selector values.String) *eval.Function { - return eval.F(existsBySelector).WithArgRef(id).WithArgValue(selector) +var existsByXPathSelector = fmt.Sprintf(` + (el, selector) => { + %s + + return found != null; + } +`, xpathAsElementFragment) + +func ExistsBySelector(id runtime.RemoteObjectID, selector drivers.QuerySelector) *eval.Function { + return toFunction(selector, existsByCSSSelector, existsByXPathSelector). + WithArgRef(id). + WithArgSelector(selector) } -const countBySelector = ` +const countByCSSSelector = ` (el, selector) => { const found = el.querySelectorAll(selector); @@ -54,6 +90,16 @@ const countBySelector = ` } ` -func CountBySelector(id runtime.RemoteObjectID, selector values.String) *eval.Function { - return eval.F(countBySelector).WithArgRef(id).WithArgValue(selector) +var countByXPathSelector = fmt.Sprintf(` + (el, selector) => { + %s + + return found.length; + } +`, xpathAsElementArrayFragment) + +func CountBySelector(id runtime.RemoteObjectID, selector drivers.QuerySelector) *eval.Function { + return toFunction(selector, countByCSSSelector, countByXPathSelector). + WithArgRef(id). + WithArgSelector(selector) } diff --git a/pkg/drivers/cdp/templates/scroll.go b/pkg/drivers/cdp/templates/scroll.go index d3be7582..fe2b8edc 100644 --- a/pkg/drivers/cdp/templates/scroll.go +++ b/pkg/drivers/cdp/templates/scroll.go @@ -2,11 +2,11 @@ package templates import ( "fmt" + + "github.com/mafredri/cdp/protocol/runtime" + "github.com/MontFerret/ferret/pkg/drivers" "github.com/MontFerret/ferret/pkg/drivers/cdp/eval" - "github.com/MontFerret/ferret/pkg/runtime/core" - "github.com/MontFerret/ferret/pkg/runtime/values" - "github.com/mafredri/cdp/protocol/runtime" ) const ( @@ -67,17 +67,33 @@ var ( return true; }`, isElementInViewportFragment) - scrollIntoViewBySelector = fmt.Sprintf(`(parent, selector, opts) => { - const el = parent.querySelector(selector); + scrollIntoViewByCSSSelector = fmt.Sprintf(`(el, selector, opts) => { + const found = el.querySelector(selector); - if (el == null) { - throw new Error(%s); + %s + + %s + + if (!isInViewport(found)) { + found.scrollIntoView({ + behavior: opts.behavior, + block: opts.block, + inline: opts.inline + }); } + return true; +}`, notFoundErrorFragment, isElementInViewportFragment) + + scrollIntoViewByXPathSelector = fmt.Sprintf(`(el, selector, opts) => { + %s + + %s + %s - if (!isInViewport(el)) { - el.scrollIntoView({ + if (!isInViewport(found)) { + found.scrollIntoView({ behavior: opts.behavior, block: opts.block, inline: opts.inline @@ -85,7 +101,7 @@ var ( } return true; -}`, ParamErr(core.ErrNotFound), isElementInViewportFragment) +}`, xpathAsElementFragment, notFoundErrorFragment, isElementInViewportFragment) ) func Scroll(options drivers.ScrollOptions) *eval.Function { @@ -104,6 +120,9 @@ func ScrollIntoView(id runtime.RemoteObjectID, options drivers.ScrollOptions) *e return eval.F(scrollIntoView).WithArgRef(id).WithArg(options) } -func ScrollIntoViewBySelector(id runtime.RemoteObjectID, selector values.String, options drivers.ScrollOptions) *eval.Function { - return eval.F(scrollIntoViewBySelector).WithArgRef(id).WithArgValue(selector).WithArg(options) +func ScrollIntoViewBySelector(id runtime.RemoteObjectID, selector drivers.QuerySelector, options drivers.ScrollOptions) *eval.Function { + return toFunction(selector, scrollIntoViewByCSSSelector, scrollIntoViewByXPathSelector). + WithArgRef(id). + WithArgSelector(selector). + WithArg(options) } diff --git a/pkg/drivers/cdp/templates/select.go b/pkg/drivers/cdp/templates/select.go index 2403db66..86a0db51 100644 --- a/pkg/drivers/cdp/templates/select.go +++ b/pkg/drivers/cdp/templates/select.go @@ -2,51 +2,66 @@ package templates import ( "fmt" + "github.com/MontFerret/ferret/pkg/drivers" "github.com/MontFerret/ferret/pkg/drivers/cdp/eval" - "github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime/values" "github.com/mafredri/cdp/protocol/runtime" ) const selectFragment = ` - if (el.nodeName.toLowerCase() !== 'select') { + if (found.nodeName.toLowerCase() !== 'select') { throw new Error('element is not a