mirror of
https://github.com/MontFerret/ferret.git
synced 2024-12-14 11:23:02 +02:00
Refactored input and select (#331)
* Refactored input and select * WIP * Fixed serialization * Fixed scriolling * Fixed XPath result handling * Renamed some methods
This commit is contained in:
parent
a5cbdb435c
commit
8e13cf9134
2
Makefile
2
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}/...
|
||||
|
@ -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) {
|
||||
|
@ -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 <input> 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 {
|
||||
|
@ -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),
|
||||
)
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ func NewEvalWaitTask(
|
||||
) *WaitTask {
|
||||
return NewWaitTask(
|
||||
func(ctx context.Context) (core.Value, error) {
|
||||
return ec.EvalWithValue(
|
||||
return ec.EvalWithReturnValue(
|
||||
ctx,
|
||||
predicate,
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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 <select> element.');
|
||||
}
|
||||
var 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);
|
||||
`,
|
||||
attrID,
|
||||
id.String(),
|
||||
value.String(),
|
||||
),
|
||||
)
|
||||
return arr, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (m *Manager) SelectBySelector(ctx context.Context, parentNodeID dom.NodeID, selector values.String, value *values.Array) (*values.Array, error) {
|
||||
if err := m.FocusBySelector(ctx, parentNodeID, selector); err != nil {
|
||||
return values.NewArray(0), err
|
||||
}
|
||||
|
||||
err = m.client.DOM.RemoveAttribute(ctx, dom.NewRemoveAttributeArgs(nodeID, attrID))
|
||||
res, err := m.exec.EvalWithReturnValue(ctx, templates.SelectBySelector(selector.String(), value.String()))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return values.NewArray(0), err
|
||||
}
|
||||
|
||||
arr, ok := res.(*values.Array)
|
||||
|
||||
if ok {
|
||||
return arr, nil
|
||||
if !ok {
|
||||
return values.NewArray(0), core.ErrUnexpected
|
||||
}
|
||||
|
||||
return nil, core.TypeError(types.Array, res.Type())
|
||||
return arr, nil
|
||||
}
|
||||
|
@ -118,7 +118,3 @@ func GetClickablePointByNodeID(ctx context.Context, client *cdp.Client, nodeID d
|
||||
func GetClickablePointByObjectID(ctx context.Context, client *cdp.Client, objectID runtime.RemoteObjectID) (Quad, error) {
|
||||
return getClickablePoint(ctx, client, dom.NewGetContentQuadsArgs().SetObjectID(objectID))
|
||||
}
|
||||
|
||||
func GetClickablePointByBackendID(ctx context.Context, client *cdp.Client, backendID dom.BackendNodeID) (Quad, error) {
|
||||
return getClickablePoint(ctx, client, dom.NewGetContentQuadsArgs().SetBackendNodeID(backendID))
|
||||
}
|
||||
|
68
pkg/drivers/cdp/templates/scroll.go
Normal file
68
pkg/drivers/cdp/templates/scroll.go
Normal file
@ -0,0 +1,68 @@
|
||||
package templates
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/MontFerret/ferret/pkg/drivers"
|
||||
)
|
||||
|
||||
const (
|
||||
scrollTopTemplate = `
|
||||
window.scrollTo({
|
||||
left: 0,
|
||||
top: 0,
|
||||
behavior: 'instant'
|
||||
});
|
||||
`
|
||||
|
||||
scrollBottomTemplate = `
|
||||
window.scrollTo({
|
||||
left: 0,
|
||||
top: window.document.body.scrollHeight,
|
||||
behavior: 'instant'
|
||||
});
|
||||
`
|
||||
|
||||
scrollIntoViewTemplate = `
|
||||
(el) => {
|
||||
el.scrollIntoView({
|
||||
behavior: 'instant'
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
func Scroll(x, y string) string {
|
||||
return fmt.Sprintf(`
|
||||
window.scrollBy(%s, %s);
|
||||
`, x, y)
|
||||
}
|
||||
|
||||
func ScrollTop() string {
|
||||
return scrollTopTemplate
|
||||
}
|
||||
|
||||
func ScrollBottom() string {
|
||||
return scrollBottomTemplate
|
||||
}
|
||||
|
||||
func ScrollIntoView() string {
|
||||
return scrollIntoViewTemplate
|
||||
}
|
||||
|
||||
func ScrollIntoViewBySelector(selector string) string {
|
||||
return fmt.Sprintf(`
|
||||
const el = document.querySelector('%s');
|
||||
|
||||
if (el == null) {
|
||||
throw new Error('%s');
|
||||
}
|
||||
|
||||
el.scrollIntoView({
|
||||
behavior: 'instant'
|
||||
});
|
||||
|
||||
return true;
|
||||
`, selector, drivers.ErrNotFound)
|
||||
}
|
56
pkg/drivers/cdp/templates/select.go
Normal file
56
pkg/drivers/cdp/templates/select.go
Normal file
@ -0,0 +1,56 @@
|
||||
package templates
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/MontFerret/ferret/pkg/drivers"
|
||||
)
|
||||
|
||||
func selectBase(values string) string {
|
||||
return fmt.Sprintf(`
|
||||
const values = %s;
|
||||
|
||||
if (el.nodeName.toLowerCase() !== 'select') {
|
||||
throw new Error('element is not a <select> 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),
|
||||
)
|
||||
}
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user