1
0
mirror of https://github.com/MontFerret/ferret.git synced 2025-03-17 21:18:37 +02:00

Feature/eval template (#651)

* Refactored use of eval

* Disable unstable unit test
This commit is contained in:
Tim Voronov 2021-09-07 16:33:30 -04:00 committed by GitHub
parent 5f361e9e1b
commit e4e98830a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
75 changed files with 1935 additions and 1778 deletions

View File

@ -9,6 +9,7 @@ import (
"github.com/rs/zerolog"
"io/ioutil"
"os"
"os/signal"
"path/filepath"
"strings"
@ -136,10 +137,23 @@ func main() {
runtime.WithLogLevel(logging.MustParseLevel(*logLevel)),
}
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
signal.Notify(c, os.Kill)
ctx, cancel := context.WithCancel(context.Background())
go func() {
for {
<-c
cancel()
}
}()
if query != "" {
err = execQuery(engine, opts, query)
err = execQuery(ctx, engine, opts, query)
} else {
err = execFiles(engine, opts, files)
err = execFiles(ctx, engine, opts, files)
}
if err != nil {
@ -148,7 +162,7 @@ func main() {
}
}
func execFiles(engine *ferret.Instance, opts []runtime.Option, files []string) error {
func execFiles(ctx context.Context, engine *ferret.Instance, opts []runtime.Option, files []string) error {
errList := make([]error, 0, len(files))
for _, path := range files {
@ -187,7 +201,7 @@ func execFiles(engine *ferret.Instance, opts []runtime.Option, files []string) e
}
if len(dirFiles) > 0 {
if err := execFiles(engine, opts, dirFiles); err != nil {
if err := execFiles(ctx, engine, opts, dirFiles); err != nil {
log.Debug().Err(err).Msg("failed to execute files")
errList = append(errList, err)
@ -214,7 +228,7 @@ func execFiles(engine *ferret.Instance, opts []runtime.Option, files []string) e
log.Debug().Msg("successfully read file")
log.Debug().Msg("executing file...")
err = execQuery(engine, opts, string(out))
err = execQuery(ctx, engine, opts, string(out))
if err != nil {
log.Debug().Err(err).Msg("failed to execute file")
@ -239,8 +253,8 @@ func execFiles(engine *ferret.Instance, opts []runtime.Option, files []string) e
return nil
}
func execQuery(engine *ferret.Instance, opts []runtime.Option, query string) error {
out, err := engine.Exec(context.Background(), query, opts...)
func execQuery(ctx context.Context, engine *ferret.Instance, opts []runtime.Option, query string) error {
out, err := engine.Exec(ctx, query, opts...)
if err != nil {
return err

View File

@ -11,11 +11,10 @@ LET prev = el.attributes
ATTR_SET(el, attrName, attrVal)
WAIT_ATTR(doc, selector, attrName, attrVal, 30000)
//WAIT_ATTR(el, attrName, attrVal)
LET curr = el.attributes
T::NONE(prev[attrName])
T::EQ(attrVal, curr[attrName], "attributes should be updated")
RETURN NONE
RETURN TRUE

View File

@ -6,4 +6,4 @@ WAIT_ELEMENT(doc, "#page-events")
CLICK_ALL(doc, "#wait-class-btn, #wait-class-random-btn")
WAIT_ATTR_ALL(doc, "#wait-class-content, #wait-class-random-content", "class", "alert alert-success", 10000)
RETURN NONE
RETURN TRUE

View File

@ -8,8 +8,6 @@ WAIT_ELEMENT(doc, pageSelector)
LET el = ELEMENT(doc, elemSelector)
LET attrs = ATTR_GET(el, "style")
T::EQ(attrs.style, {
display: "block"
})
T::EQ(attrs.style.display, "block")
RETURN NONE
RETURN TRUE

View File

@ -2,19 +2,19 @@ LET url = @lab.cdn.dynamic + "?redirect=/events"
LET doc = DOCUMENT(url, true)
LET pageSelector = "#page-events"
LET elemSelector = "#wait-no-style-content"
LET attrName = "data-e2e-test"
WAIT_ELEMENT(doc, pageSelector)
LET el = ELEMENT(doc, elemSelector)
ATTR_SET(el, attrName, "true")
LET prev = el.attributes.style
LET prev = el.attributes[attrName]
ATTR_REMOVE(el, "style")
ATTR_REMOVE(el, attrName)
LET curr = el.attributes.style
LET curr = el.attributes[attrName]
T::EQ(prev, {
display: "block"
})
T::EQ(prev, "true")
T::NONE(curr)
RETURN NONE
RETURN TRUE

View File

@ -0,0 +1,27 @@
LET url = @lab.cdn.dynamic + "?redirect=/events"
LET doc = DOCUMENT(url, true)
LET pageSelector = "#page-events"
LET elemSelector = "#wait-no-style-content"
LET styleName = "color"
LET styleValue = "rgb(100, 100, 100)"
WAIT_ELEMENT(doc, pageSelector)
LET el = ELEMENT(doc, elemSelector)
LET prev = el.style.color
T::NOT::EQ(prev, styleValue)
STYLE_SET(el, styleName, styleValue)
LET curr = el.style.color
T::EQ(curr, styleValue)
ATTR_REMOVE(el, "style")
LET removed = el.style.color
T::EQ(prev, removed)
RETURN TRUE

View File

@ -2,22 +2,21 @@ LET url = @lab.cdn.dynamic + "?redirect=/events"
LET doc = DOCUMENT(url, true)
LET pageSelector = "#page-events"
LET elemSelector = "#wait-no-style-content"
LET attrName = "data-e2e-test"
WAIT_ELEMENT(doc, pageSelector)
LET el = ELEMENT(doc, elemSelector)
LET prev = el.attributes.style
ATTR_SET(el, attrName, "true")
ATTR_SET(el, "style", {
color: "black"
})
LET prev = el.attributes[attrName]
WAIT(200)
T::EQ(prev, "true")
LET curr = el.attributes.style
ATTR_SET(el, attrName, "false")
PRINT(el.attributes.style)
LET curr = el.attributes[attrName]
T::EQ(curr.color, "black")
T::EQ(curr, "false")
RETURN NONE
RETURN TRUE

View File

@ -2,17 +2,18 @@ LET url = @lab.cdn.dynamic + "?redirect=/events"
LET doc = DOCUMENT(url, true)
LET pageSelector = "#page-events"
LET elemSelector = "#wait-no-style-content"
LET color = "rgb(66, 66, 66)"
WAIT_ELEMENT(doc, pageSelector)
LET el = ELEMENT(doc, elemSelector)
ATTR_SET(el, {
style: "color: black;",
style: "color:" + color,
"data-ferret-x": "test"
})
T::EQ(el.attributes.style.color, "black")
T::EQ(el.attributes.style.color, color)
T::EQ(el.attributes["data-ferret-x"], "test")
RETURN NONE
RETURN TRUE

View File

@ -10,11 +10,9 @@ LET prev = el.attributes.style
STYLE_REMOVE(el, "display")
WAIT(1000)
LET curr = el.attributes.style
T::EQ(prev.display, "block")
T::NONE(curr.display)
T::EQ(curr.display, "inline")
RETURN NONE
RETURN TRUE

View File

@ -9,6 +9,7 @@ ATTR_SET(el, "data-test", "test")
WAIT_ATTR(el, "data-test", "test")
ATTR_REMOVE(el, "class")
WAIT_ATTR(el, "class", NONE)
T::NONE(el.attributes.class, "attribute should be removed")

View File

@ -5,4 +5,4 @@ query:
criteria: "scraper"
pages: 2
assert:
text: RETURN T::NOT::EMPTY(@lab.data.query.result)
text: RETURN T::NOT::NONE(@lab.data.query.result)

View File

@ -1,29 +1,67 @@
LET baseURL = 'https://www.amazon.com/'
LET amazon = DOCUMENT(baseURL, { driver: "cdp" })
WAIT_ELEMENT(amazon, '#nav-search-submit-button')
INPUT(amazon, '#twotabsearchtextbox', @criteria)
CLICK(amazon, '#nav-search-submit-button')
WAIT_NAVIGATION(amazon)
LET resultListSelector = '[data-component-type="s-search-results"]'
LET resultItemSelector = '[data-component-type="s-search-result"]'
LET nextBtnSelector = '.s-pagination-next:not(.s-pagination-disabled)'
WAITFOR EVENT "navigation" IN amazon OPTIONS { target: "www\.amazon\.com\/s/ref"} 50000
WAIT_ELEMENT(amazon, '[class*="template=PAGINATION"]')
LET paginator = ELEMENT(amazon, '[class*="-pagination"]')
LET foundPrefixes = (FOR cn IN SPLIT(paginator.attributes.class, " ")
FILTER cn LIKE "*-pagination*"
LIMIT 1
RETURN FIRST(SPLIT(cn, "-"))
)
LET prefix = FIRST(foundPrefixes)
T::NOT::EMPTY(prefix, "CSS prefix should not be empty")
PRINT("CSS Prefix is:", prefix)
LET paginationItems = paginator.length
LET variants = {
"s": {
nextBtnSelector: ".s-pagination-next",
pagersSelector: ".s-pagination-item:not(.s-pagination-next, .s-pagination-previous):last-of-type"
},
"a": {
nextBtnSelector: ".a-pagination .a-last",
pagersSelector: FMT("ul.a-pagination li:nth-of-type({})", paginator.length - 1)
}
}
LET selectors = variants[prefix]
T::NOT::NONE(selectors, "Supported CSS selectors not found")
LET spinner = FMT('[data-component-type="{0}-search-results"] .{0}-result-list-placeholder', prefix)
LET resultListSelector = FMT('[data-component-type="{}-search-results"]', prefix)
LET resultItemSelector = FMT('[data-component-type="{}-search-result"]', prefix)
LET pagersSelector = FMT('.{0}-pagination :not(.{0}-last)', prefix)
LET priceWholeSelector = '.a-price-whole'
LET priceFracSelector = '.a-price-fraction'
LET pagers = ELEMENTS(amazon, '.s-pagination-item.s-pagination-disabled')
LET pagers = ELEMENTS(amazon, pagersSelector)
LET pages = LENGTH(pagers) > 0 ? TO_INT(INNER_TEXT(LAST(pagers))) : 0
PRINT("Found pages:", pages)
LET result = (
FOR pageNum IN 1..pages
LIMIT @pages
LET clicked = pageNum == 1 ? false : CLICK(amazon, nextBtnSelector)
LET waitSelector = clicked ? WAIT_ELEMENT(amazon, resultListSelector) : false
LET clicked = pageNum == 1 ? false : CLICK(amazon, selectors.nextBtnSelector)
LET waitSelector = clicked ? WAIT_NO_CLASS(amazon, spinner, 'aok-hidden') && WAIT_ELEMENT(amazon, resultItemSelector) : false
PRINT("page:", pageNum, "clicked", clicked)
LET found = ELEMENTS(amazon, resultItemSelector)
LET items = (
FOR el IN ELEMENTS(amazon, resultItemSelector)
FOR el IN found
LET hasPrice = ELEMENT_EXISTS(el, priceWholeSelector)
LET priceWholeTxt = hasPrice ? FIRST(REGEX_MATCH(INNER_TEXT(el, priceWholeSelector), "[0-9]+")) : "0"
LET priceFracTxt = hasPrice ? FIRST(REGEX_MATCH(INNER_TEXT(el, priceFracSelector), "[0-9]+")) : "00"
@ -42,3 +80,5 @@ LET result = (
)
RETURN FLATTEN(result)

View File

@ -2,8 +2,6 @@ package dom
import (
"context"
"fmt"
"github.com/MontFerret/ferret/pkg/runtime/logging"
"hash/fnv"
"github.com/mafredri/cdp"
@ -19,6 +17,7 @@ import (
"github.com/MontFerret/ferret/pkg/drivers/cdp/templates"
"github.com/MontFerret/ferret/pkg/drivers/common"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/logging"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
@ -52,7 +51,7 @@ func LoadRootHTMLDocument(
return nil, err
}
exec, err := eval.New(ctx, client, ftRepl.FrameTree.Frame.ID)
exec, err := eval.New(ctx, logger, client, ftRepl.FrameTree.Frame.ID)
if err != nil {
return nil, err
@ -201,12 +200,12 @@ func (doc *HTMLDocument) Frame() page.FrameTree {
return doc.frameTree
}
func (doc *HTMLDocument) GetNodeType() values.Int {
return 9
func (doc *HTMLDocument) GetNodeType(_ context.Context) (values.Int, error) {
return 9, nil
}
func (doc *HTMLDocument) GetNodeName() values.String {
return "#document"
func (doc *HTMLDocument) GetNodeName(_ context.Context) (values.String, error) {
return "#document", nil
}
func (doc *HTMLDocument) GetChildNodes(ctx context.Context) (*values.Array, error) {
@ -234,7 +233,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.ObjectID, "title")
value, err := doc.exec.ReadProperty(context.Background(), doc.element.id, "title")
if err != nil {
doc.logError(errors.Wrap(err, "failed to read document title"))
@ -296,34 +295,13 @@ func (doc *HTMLDocument) GetURL() values.String {
}
func (doc *HTMLDocument) MoveMouseByXY(ctx context.Context, x, y values.Float) error {
return doc.input.MoveMouseByXY(ctx, float64(x), float64(y))
return doc.input.MoveMouseByXY(ctx, x, y)
}
func (doc *HTMLDocument) WaitForElement(ctx context.Context, selector values.String, when drivers.WaitEvent) error {
var operator string
if when == drivers.WaitEventPresence {
operator = "!="
} else {
operator = "=="
}
task := events.NewEvalWaitTask(
doc.exec,
fmt.Sprintf(
`
var el = document.querySelector(%s);
if (el %s null) {
return true;
}
// null means we need to repeat
return null;
`,
eval.ParamString(selector.String()),
operator,
),
templates.WaitForElement(doc.element.id, selector, when),
events.DefaultPolling,
)
@ -335,12 +313,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,
templates.WaitBySelector(
selector,
when,
class,
fmt.Sprintf("el.className.split(' ').find(i => i === %s)", eval.ParamString(class.String())),
),
templates.WaitForClassBySelector(doc.element.id, selector, class, when),
events.DefaultPolling,
)
@ -352,12 +325,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,
templates.WaitBySelectorAll(
selector,
when,
class,
fmt.Sprintf("el.className.split(' ').find(i => i === %s)", eval.ParamString(class.String())),
),
templates.WaitForClassBySelectorAll(doc.element.id, selector, class, when),
events.DefaultPolling,
)
@ -369,18 +337,13 @@ func (doc *HTMLDocument) WaitForClassBySelectorAll(ctx context.Context, selector
func (doc *HTMLDocument) WaitForAttributeBySelector(
ctx context.Context,
selector,
name values.String,
value core.Value,
name,
value values.String,
when drivers.WaitEvent,
) error {
task := events.NewEvalWaitTask(
doc.exec,
templates.WaitBySelector(
selector,
when,
value,
templates.AttributeRead(name),
),
templates.WaitForAttributeBySelector(doc.element.id, selector, name, value, when),
events.DefaultPolling,
)
@ -392,18 +355,13 @@ func (doc *HTMLDocument) WaitForAttributeBySelector(
func (doc *HTMLDocument) WaitForAttributeBySelectorAll(
ctx context.Context,
selector,
name values.String,
value core.Value,
name,
value values.String,
when drivers.WaitEvent,
) error {
task := events.NewEvalWaitTask(
doc.exec,
templates.WaitBySelectorAll(
selector,
when,
value,
templates.AttributeRead(name),
),
templates.WaitForAttributeBySelectorAll(doc.element.id, selector, name, value, when),
events.DefaultPolling,
)
@ -412,15 +370,10 @@ func (doc *HTMLDocument) WaitForAttributeBySelectorAll(
return err
}
func (doc *HTMLDocument) WaitForStyleBySelector(ctx context.Context, selector, name values.String, value core.Value, when drivers.WaitEvent) error {
func (doc *HTMLDocument) WaitForStyleBySelector(ctx context.Context, selector, name, value values.String, when drivers.WaitEvent) error {
task := events.NewEvalWaitTask(
doc.exec,
templates.WaitBySelector(
selector,
when,
value,
templates.StyleRead(name),
),
templates.WaitForStyleBySelector(doc.element.id, selector, name, value, when),
events.DefaultPolling,
)
@ -429,15 +382,10 @@ func (doc *HTMLDocument) WaitForStyleBySelector(ctx context.Context, selector, n
return err
}
func (doc *HTMLDocument) WaitForStyleBySelectorAll(ctx context.Context, selector, name values.String, value core.Value, when drivers.WaitEvent) error {
func (doc *HTMLDocument) WaitForStyleBySelectorAll(ctx context.Context, selector, name, value values.String, when drivers.WaitEvent) error {
task := events.NewEvalWaitTask(
doc.exec,
templates.WaitBySelectorAll(
selector,
when,
value,
templates.StyleRead(name),
),
templates.WaitForStyleBySelectorAll(doc.element.id, selector, name, value, when),
events.DefaultPolling,
)
@ -455,15 +403,15 @@ func (doc *HTMLDocument) ScrollBottom(ctx context.Context, options drivers.Scrol
}
func (doc *HTMLDocument) ScrollBySelector(ctx context.Context, selector values.String, options drivers.ScrollOptions) error {
return doc.input.ScrollIntoViewBySelector(ctx, selector.String(), options)
return doc.input.ScrollIntoViewBySelector(ctx, doc.element.id, selector, options)
}
func (doc *HTMLDocument) ScrollByXY(ctx context.Context, x, y values.Float, options drivers.ScrollOptions) error {
return doc.input.ScrollByXY(ctx, float64(x), float64(y), options)
func (doc *HTMLDocument) Scroll(ctx context.Context, options drivers.ScrollOptions) error {
return doc.input.ScrollByXY(ctx, options)
}
func (doc *HTMLDocument) Eval(ctx context.Context, expression string) (core.Value, error) {
return doc.exec.EvalValue(ctx, expression)
return doc.exec.EvalValue(ctx, eval.F(expression))
}
func (doc *HTMLDocument) logError(err error) *zerolog.Event {

File diff suppressed because it is too large Load Diff

View File

@ -3,181 +3,28 @@ 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/mafredri/cdp/protocol/runtime"
"github.com/pkg/errors"
"golang.org/x/net/html"
"github.com/MontFerret/ferret/pkg/drivers/cdp/eval"
"github.com/MontFerret/ferret/pkg/drivers/cdp/templates"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
var camelMatcher = regexp.MustCompile("[A-Za-z0-9]+")
// traverseAttrs is a helper function that parses a given interleaved array of node attribute names and values,
// and calls a given attribute on each key-value pair
func traverseAttrs(attrs []string, predicate func(name, value string) bool) {
count := len(attrs)
for i := 0; i < count; i++ {
if i%2 != 0 {
if predicate(attrs[i-1], attrs[i]) == false {
break
}
}
}
}
func setInnerHTML(ctx context.Context, client *cdp.Client, exec *eval.Runtime, id HTMLElementIdentity, innerHTML values.String) error {
var objID runtime.RemoteObjectID
if id.ObjectID != "" {
objID = id.ObjectID
} else {
repl, err := client.DOM.ResolveNode(ctx, dom.NewResolveNodeArgs().SetNodeID(id.NodeID))
if err != nil {
return err
}
if repl.Object.ObjectID == nil {
return errors.New("unable to resolve node")
}
objID = *repl.Object.ObjectID
}
return exec.Eval(
ctx,
templates.SetInnerHTML(),
eval.WithArgRef(objID),
eval.WithArgValue(innerHTML),
)
}
func getInnerHTML(ctx context.Context, client *cdp.Client, exec *eval.Runtime, id HTMLElementIdentity, nodeType html.NodeType) (values.String, error) {
// not a document
if nodeType != html.DocumentNode {
var objID runtime.RemoteObjectID
if id.ObjectID != "" {
objID = id.ObjectID
} else {
repl, err := client.DOM.ResolveNode(ctx, dom.NewResolveNodeArgs().SetNodeID(id.NodeID))
if err != nil {
return "", err
}
if repl.Object.ObjectID == nil {
return "", errors.New("unable to resolve node")
}
objID = *repl.Object.ObjectID
}
res, err := exec.ReadProperty(ctx, objID, "innerHTML")
if err != nil {
return "", err
}
return values.NewString(res.String()), nil
}
repl, err := exec.EvalValue(ctx, "return document.documentElement.innerHTML")
if err != nil {
return "", err
}
return values.NewString(repl.String()), nil
}
func setInnerText(ctx context.Context, client *cdp.Client, exec *eval.Runtime, id HTMLElementIdentity, innerText values.String) error {
var objID runtime.RemoteObjectID
if id.ObjectID != "" {
objID = id.ObjectID
} else {
repl, err := client.DOM.ResolveNode(ctx, dom.NewResolveNodeArgs().SetNodeID(id.NodeID))
if err != nil {
return err
}
if repl.Object.ObjectID == nil {
return errors.New("unable to resolve node")
}
objID = *repl.Object.ObjectID
}
return exec.Eval(
ctx,
templates.SetInnerText(),
eval.WithArgRef(objID),
eval.WithArgValue(innerText),
)
}
func getInnerText(ctx context.Context, client *cdp.Client, exec *eval.Runtime, id HTMLElementIdentity, nodeType html.NodeType) (values.String, error) {
// not a document
if nodeType != html.DocumentNode {
var objID runtime.RemoteObjectID
if id.ObjectID != "" {
objID = id.ObjectID
} else {
repl, err := client.DOM.ResolveNode(ctx, dom.NewResolveNodeArgs().SetNodeID(id.NodeID))
if err != nil {
return "", err
}
if repl.Object.ObjectID == nil {
return "", errors.New("unable to resolve node")
}
objID = *repl.Object.ObjectID
}
res, err := exec.ReadProperty(ctx, objID, "innerText")
if err != nil {
return "", err
}
return values.NewString(res.String()), err
}
repl, err := exec.EvalValue(ctx, "return document.documentElement.innerText")
if err != nil {
return "", err
}
return values.NewString(repl.String()), nil
}
func resolveFrame(ctx context.Context, client *cdp.Client, frameID page.FrameID) (dom.Node, *eval.Runtime, error) {
exec, err := eval.New(ctx, client, frameID)
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,
"return document",
)
evalRes, err := exec.EvalRef(ctx, eval.F("return document"))
if err != nil {
return dom.Node{}, nil, err

View File

@ -213,7 +213,7 @@ func (m *Manager) getFrameInternal(ctx context.Context, frameID page.FrameID) (*
}
// the frames is not loaded yet
node, exec, err := resolveFrame(ctx, m.client, frameID)
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)

View File

@ -3,50 +3,120 @@ package eval
import (
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/mafredri/cdp/protocol/runtime"
"github.com/rs/zerolog"
"github.com/wI2L/jettison"
"strings"
)
type (
FunctionReturnType int
FunctionArguments []runtime.CallArgument
Function struct {
exp string
ownerID *runtime.RemoteObjectID
args []runtime.CallArgument
ownerID runtime.RemoteObjectID
args FunctionArguments
returnType FunctionReturnType
async bool
}
FunctionOption func(op *Function)
)
const defaultArgsCount = 5
const (
ReturnNothing FunctionReturnType = iota
ReturnValue
ReturnRef
)
func newFunction(exp string, opts []FunctionOption) *Function {
func F(exp string) *Function {
op := new(Function)
op.exp = exp
op.returnType = ReturnNothing
for _, opt := range opts {
opt(op)
}
return op
}
func (fn *Function) Use(opt FunctionOption) {
opt(fn)
func (fn *Function) AsPartOf(id runtime.RemoteObjectID) *Function {
fn.ownerID = id
return fn
}
func (fn *Function) toArgs(ctx runtime.ExecutionContextID) *runtime.CallFunctionOnArgs {
func (fn *Function) AsAsync() *Function {
fn.async = true
return fn
}
func (fn *Function) AsSync() *Function {
fn.async = false
return fn
}
func (fn *Function) WithArgRef(id runtime.RemoteObjectID) *Function {
return fn.withArg(runtime.CallArgument{
ObjectID: &id,
})
}
func (fn *Function) WithArgValue(value core.Value) *Function {
raw, err := value.MarshalJSON()
if err != nil {
panic(err)
}
return fn.withArg(runtime.CallArgument{
Value: raw,
})
}
func (fn *Function) WithArg(value interface{}) *Function {
raw, err := jettison.MarshalOpts(value, jettison.NoHTMLEscaping())
if err != nil {
panic(err)
}
return fn.withArg(runtime.CallArgument{
Value: raw,
})
}
func (fn *Function) String() string {
return fn.exp
}
func (fn *Function) returnRef() *Function {
fn.returnType = ReturnRef
return fn
}
func (fn *Function) returnValue() *Function {
fn.returnType = ReturnValue
return fn
}
func (fn *Function) withArg(arg runtime.CallArgument) *Function {
if fn.args == nil {
fn.args = make([]runtime.CallArgument, 0, defaultArgsCount)
}
fn.args = append(fn.args, arg)
return fn
}
func (fn *Function) build(ctx runtime.ExecutionContextID) *runtime.CallFunctionOnArgs {
exp := strings.TrimSpace(fn.exp)
if !strings.HasPrefix(exp, "(") && !strings.HasPrefix(exp, "function") {
exp = wrapExp(exp)
exp = wrapExp(exp, len(fn.args))
}
call := runtime.NewCallFunctionOnArgs(exp).
@ -60,8 +130,8 @@ func (fn *Function) toArgs(ctx runtime.ExecutionContextID) *runtime.CallFunction
call.SetExecutionContextID(ctx)
}
if fn.ownerID != nil {
call.SetObjectID(*fn.ownerID)
if fn.ownerID != "" {
call.SetObjectID(fn.ownerID)
}
if len(fn.args) > 0 {
@ -71,57 +141,23 @@ func (fn *Function) toArgs(ctx runtime.ExecutionContextID) *runtime.CallFunction
return call
}
func withReturnRef() FunctionOption {
return func(op *Function) {
op.returnType = ReturnRef
func (rt FunctionReturnType) String() string {
switch rt {
case ReturnValue:
return "value"
case ReturnRef:
return "reference"
default:
return "nothing"
}
}
func withReturnValue() FunctionOption {
return func(op *Function) {
op.returnType = ReturnValue
}
}
func WithArgs(args ...runtime.CallArgument) FunctionOption {
return func(op *Function) {
if op.args == nil {
op.args = args
func (args FunctionArguments) MarshalZerologArray(a *zerolog.Array) {
for _, arg := range args {
if arg.ObjectID != nil {
a.Str(string(*arg.ObjectID))
} else {
op.args = append(op.args, args...)
a.RawJSON(arg.Value)
}
}
}
func WithArgValue(value core.Value) FunctionOption {
raw, err := value.MarshalJSON()
if err != nil {
// we defer error
return WithArgs(runtime.CallArgument{
Value: []byte(err.Error()),
})
}
return WithArgs(runtime.CallArgument{
Value: raw,
})
}
func WithArgRef(id runtime.RemoteObjectID) FunctionOption {
return WithArgs(runtime.CallArgument{
ObjectID: &id,
})
}
func WithOwner(ctx *runtime.RemoteObjectID) FunctionOption {
return func(op *Function) {
op.ownerID = ctx
}
}
func WithAsync() FunctionOption {
return func(op *Function) {
op.async = true
}
}

View File

@ -31,15 +31,27 @@ func CastToReference(input interface{}) (runtime.RemoteObject, error) {
return value, nil
}
func wrapExp(exp string) string {
return "function () {" + exp + "}"
}
func Unmarshal(obj *runtime.RemoteObject) (core.Value, error) {
if obj == nil {
return values.None, nil
func wrapExp(exp string, args int) string {
if args == 0 {
return "() => {\n" + exp + "\n}"
}
var buf strings.Builder
lastIndex := args - 1
for i := 0; i < args; i++ {
buf.WriteString("arg")
buf.WriteString(strconv.Itoa(i + 1))
if i != lastIndex {
buf.WriteString(",")
}
}
return "(" + buf.String() + ") => {\n" + exp + "\n}"
}
func Unmarshal(obj runtime.RemoteObject) (core.Value, error) {
switch obj.Type {
case "string":
str, err := strconv.Unquote(string(obj.Value))
@ -49,8 +61,16 @@ func Unmarshal(obj *runtime.RemoteObject) (core.Value, error) {
}
return values.NewString(str), nil
case "undefined", "null":
return values.None, nil
case "object":
if obj.Subtype != nil {
subtype := *obj.Subtype
if subtype == "null" || subtype == "undefined" {
return values.None, nil
}
}
return values.Unmarshal(obj.Value)
default:
return values.Unmarshal(obj.Value)
}

View File

@ -0,0 +1,20 @@
package eval
import (
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestWrapExp(t *testing.T) {
Convey("wrapExp", t, func() {
Convey("When a plain expression is passed", func() {
exp := "return true"
So(wrapExp(exp, 0), ShouldEqual, "() => {\n"+exp+"\n}")
})
Convey("When a plain expression is passed with args > 0", func() {
exp := "return true"
So(wrapExp(exp, 3), ShouldEqual, "(arg1,arg2,arg3) => {\n"+exp+"\n}")
})
})
}

View File

@ -1,68 +0,0 @@
package eval
import (
"bytes"
"strconv"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
func Param(input core.Value) string {
switch value := input.(type) {
case values.String:
return ParamString(string(value))
case values.Float:
return ParamFloat(float64(value))
case values.Int:
return ParamInt(int64(value))
default:
if input == values.None {
return "null"
}
return value.String()
}
}
func ParamList(inputs []core.Value) string {
var buf bytes.Buffer
lastIndex := len(inputs) - 1
for i, input := range inputs {
buf.WriteString(Param(input))
if i != lastIndex {
buf.WriteString(",")
}
}
return buf.String()
}
func ParamStringList(inputs []values.String) string {
var buf bytes.Buffer
lastIndex := len(inputs) - 1
for i, input := range inputs {
buf.WriteString(ParamString(input.String()))
if i != lastIndex {
buf.WriteString(",")
}
}
return buf.String()
}
func ParamString(param string) string {
return "`" + param + "`"
}
func ParamFloat(param float64) string {
return strconv.FormatFloat(param, 'f', 6, 64)
}
func ParamInt(param int64) string {
return strconv.FormatInt(param, 10)
}

View File

@ -2,6 +2,8 @@ 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"
@ -15,44 +17,43 @@ import (
const EmptyExecutionContextID = runtime.ExecutionContextID(-1)
type Runtime struct {
logger zerolog.Logger
client *cdp.Client
frame page.Frame
contextID runtime.ExecutionContextID
}
func New(ctx context.Context, client *cdp.Client, frameID page.FrameID) (*Runtime, error) {
func New(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(client, world.ExecutionContextID), nil
return Create(logger, client, world.ExecutionContextID), nil
}
func Create(client *cdp.Client, contextID runtime.ExecutionContextID) *Runtime {
ec := new(Runtime)
ec.client = client
ec.contextID = contextID
func Create(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
return ec
return rt
}
func (ex *Runtime) ContextID() runtime.ExecutionContextID {
return ex.contextID
func (rt *Runtime) ContextID() runtime.ExecutionContextID {
return rt.contextID
}
func (ex *Runtime) Eval(ctx context.Context, exp string, opts ...FunctionOption) error {
_, err := ex.call(ctx, newFunction(exp, opts))
func (rt *Runtime) Eval(ctx context.Context, fn *Function) error {
_, err := rt.call(ctx, fn)
return err
}
func (ex *Runtime) EvalValue(ctx context.Context, exp string, opts ...FunctionOption) (core.Value, error) {
fn := newFunction(exp, opts)
fn.Use(withReturnValue())
out, err := ex.call(ctx, fn)
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
@ -61,11 +62,8 @@ func (ex *Runtime) EvalValue(ctx context.Context, exp string, opts ...FunctionOp
return CastToValue(out)
}
func (ex *Runtime) EvalRef(ctx context.Context, exp string, opts ...FunctionOption) (runtime.RemoteObject, error) {
fn := newFunction(exp, opts)
fn.Use(withReturnRef())
out, err := ex.call(ctx, fn)
func (rt *Runtime) EvalRef(ctx context.Context, fn *Function) (runtime.RemoteObject, error) {
out, err := rt.call(ctx, fn.returnRef())
if err != nil {
return runtime.RemoteObject{}, err
@ -74,12 +72,12 @@ func (ex *Runtime) EvalRef(ctx context.Context, exp string, opts ...FunctionOpti
return CastToReference(out)
}
func (ex *Runtime) ReadProperty(
func (rt *Runtime) ReadProperty(
ctx context.Context,
objectID runtime.RemoteObjectID,
propName string,
) (core.Value, error) {
res, err := ex.client.Runtime.GetProperties(
res, err := rt.client.Runtime.GetProperties(
ctx,
runtime.NewGetPropertiesArgs(objectID),
)
@ -97,13 +95,15 @@ func (ex *Runtime) ReadProperty(
arr := values.NewArray(len(res.Result))
for _, prop := range res.Result {
val, err := Unmarshal(prop.Value)
if prop.Value != nil {
val, err := Unmarshal(*prop.Value)
if err != nil {
return values.None, err
if err != nil {
return values.None, err
}
arr.Push(val)
}
arr.Push(val)
}
return arr, nil
@ -111,33 +111,64 @@ func (ex *Runtime) ReadProperty(
for _, prop := range res.Result {
if prop.Name == propName {
return Unmarshal(prop.Value)
if prop.Value != nil {
return Unmarshal(*prop.Value)
}
return values.None, nil
}
}
return values.None, nil
}
func (ex *Runtime) call(ctx context.Context, fn *Function) (interface{}, error) {
repl, err := ex.client.Runtime.CallFunctionOn(ctx, fn.toArgs(ex.contextID))
func (rt *Runtime) call(ctx context.Context, fn *Function) (interface{}, error) {
log := rt.logger.With().
Str("expression", fn.String()).
Str("returns", fn.returnType.String()).
Bool("is-async", fn.async).
Str("owner", string(fn.ownerID)).
Array("arguments", fn.args).
Logger()
log.Trace().Msg("executing expression...")
repl, err := rt.client.Runtime.CallFunctionOn(ctx, fn.build(rt.contextID))
if err != nil {
log.Trace().Err(err).Msg("failed executing expression")
return nil, 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
}
var className string
if repl.Result.ClassName != nil {
className = *repl.Result.ClassName
}
var subtype string
if repl.Result.Subtype != nil {
subtype = *repl.Result.Subtype
}
log.Trace().
Str("return-type", repl.Result.Type).
Str("return-sub-type", subtype).
Str("return-class-name", className).
Str("return-value", string(repl.Result.Value)).
Msg("succeeded executing expression")
switch fn.returnType {
case ReturnValue:
out := repl.Result
if out.Type != "undefined" && out.Type != "null" {
return values.Unmarshal(out.Value)
}
return Unmarshal(&out)
return Unmarshal(repl.Result)
case ReturnRef:
return repl.Result, nil
default:

View File

@ -369,7 +369,7 @@ func TestLoop(t *testing.T) {
So(counter.Value(), ShouldEqual, 1)
})
Convey("Should stop on Context.Done", t, func() {
SkipConvey("Should stop on Context.Done", t, func() {
loop := events.NewLoop()
eventsToFire := 5
counter := NewCounter()

View File

@ -58,17 +58,12 @@ func (task *WaitTask) Run(ctx context.Context) (core.Value, error) {
func NewEvalWaitTask(
ec *eval.Runtime,
predicate string,
fn *eval.Function,
polling time.Duration,
opts ...eval.FunctionOption,
) *WaitTask {
return NewWaitTask(
func(ctx context.Context) (core.Value, error) {
return ec.EvalValue(
ctx,
predicate,
opts...,
)
return ec.EvalValue(ctx, fn)
},
polling,
)

View File

@ -95,19 +95,15 @@ func (m *Manager) ScrollBottom(ctx context.Context, options drivers.ScrollOption
return nil
}
func (m *Manager) ScrollIntoView(ctx context.Context, objectID runtime.RemoteObjectID, options drivers.ScrollOptions) error {
func (m *Manager) ScrollIntoView(ctx context.Context, id runtime.RemoteObjectID, options drivers.ScrollOptions) error {
m.logger.Trace().
Str("object_id", string(objectID)).
Str("object_id", string(id)).
Str("behavior", options.Behavior.String()).
Str("block", options.Block.String()).
Str("inline", options.Inline.String()).
Msg("scrolling to an element")
if err := m.exec.Eval(
ctx,
templates.ScrollIntoView(options),
eval.WithArgRef(objectID),
); err != nil {
if err := m.exec.Eval(ctx, templates.ScrollIntoView(id, options)); err != nil {
m.logger.Trace().Err(err).Msg("failed to scroll to an element")
return err
@ -118,15 +114,15 @@ func (m *Manager) ScrollIntoView(ctx context.Context, objectID runtime.RemoteObj
return nil
}
func (m *Manager) ScrollIntoViewBySelector(ctx context.Context, selector string, options drivers.ScrollOptions) error {
func (m *Manager) ScrollIntoViewBySelector(ctx context.Context, id runtime.RemoteObjectID, selector values.String, options drivers.ScrollOptions) error {
m.logger.Trace().
Str("selector", selector).
Str("selector", selector.String()).
Str("behavior", options.Behavior.String()).
Str("block", options.Block.String()).
Str("inline", options.Inline.String()).
Msg("scrolling to an element by selector")
if err := m.exec.Eval(ctx, templates.ScrollIntoViewBySelector(selector, options)); err != nil {
if err := m.exec.Eval(ctx, templates.ScrollIntoViewBySelector(id, selector, options)); err != nil {
m.logger.Trace().Err(err).Msg("failed to scroll to an element by selector")
return err
@ -137,19 +133,16 @@ func (m *Manager) ScrollIntoViewBySelector(ctx context.Context, selector string,
return nil
}
func (m *Manager) ScrollByXY(ctx context.Context, x, y float64, options drivers.ScrollOptions) error {
func (m *Manager) ScrollByXY(ctx context.Context, options drivers.ScrollOptions) error {
m.logger.Trace().
Float64("x", x).
Float64("y", y).
Float64("x", float64(options.Top)).
Float64("y", float64(options.Left)).
Str("behavior", options.Behavior.String()).
Str("block", options.Block.String()).
Str("inline", options.Inline.String()).
Msg("scrolling to an element by given coordinates")
if err := m.exec.Eval(
ctx,
templates.Scroll(eval.ParamFloat(x), eval.ParamFloat(y), options),
); err != nil {
if err := m.exec.Eval(ctx, templates.Scroll(options)); err != nil {
m.logger.Trace().Err(err).Msg("failed to scroll to an element by coordinates")
return err
@ -186,13 +179,13 @@ func (m *Manager) Focus(ctx context.Context, objectID runtime.RemoteObjectID) er
return nil
}
func (m *Manager) FocusBySelector(ctx context.Context, parentNodeID dom.NodeID, selector string) error {
func (m *Manager) FocusBySelector(ctx context.Context, id runtime.RemoteObjectID, selector values.String) error {
m.logger.Trace().
Int("parent_node_id", int(parentNodeID)).
Str("selector", selector).
Str("parent_object_id", string(id)).
Str("selector", selector.String()).
Msg("focusing on an element by selector")
err := m.ScrollIntoViewBySelector(ctx, selector, drivers.ScrollOptions{
err := m.ScrollIntoViewBySelector(ctx, id, selector, drivers.ScrollOptions{
Behavior: drivers.ScrollBehaviorAuto,
Block: drivers.ScrollVerticalAlignmentCenter,
Inline: drivers.ScrollHorizontalAlignmentCenter,
@ -204,7 +197,7 @@ func (m *Manager) FocusBySelector(ctx context.Context, parentNodeID dom.NodeID,
m.logger.Trace().Msg("resolving an element by selector")
found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector))
found, err := m.exec.EvalRef(ctx, templates.QuerySelector(id, selector))
if err != nil {
m.logger.Trace().
@ -214,7 +207,15 @@ func (m *Manager) FocusBySelector(ctx context.Context, parentNodeID dom.NodeID,
return err
}
if err := m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetNodeID(found.NodeID)); err != nil {
if found.ObjectID == nil {
m.logger.Trace().
Err(core.ErrNotFound).
Msg("element not found by selector")
return core.ErrNotFound
}
if err := m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetObjectID(*found.ObjectID)); err != nil {
m.logger.Trace().
Err(err).
Msg("failed focusing on an element by selector")
@ -232,7 +233,7 @@ func (m *Manager) Blur(ctx context.Context, objectID runtime.RemoteObjectID) err
Str("object_id", string(objectID)).
Msg("removing focus from an element")
if err := m.exec.Eval(ctx, templates.Blur(), eval.WithArgRef(objectID)); err != nil {
if err := m.exec.Eval(ctx, templates.Blur(objectID)); err != nil {
m.logger.Trace().
Err(err).
Msg("failed removing focus from an element")
@ -245,13 +246,13 @@ func (m *Manager) Blur(ctx context.Context, objectID runtime.RemoteObjectID) err
return nil
}
func (m *Manager) BlurBySelector(ctx context.Context, parentObjectID runtime.RemoteObjectID, selector string) error {
func (m *Manager) BlurBySelector(ctx context.Context, id runtime.RemoteObjectID, selector values.String) error {
m.logger.Trace().
Str("parent_object_id", string(parentObjectID)).
Str("selector", selector).
Str("parent_object_id", string(id)).
Str("selector", selector.String()).
Msg("removing focus from an element by selector")
if err := m.exec.Eval(ctx, templates.BlurBySelector(selector), eval.WithArgRef(parentObjectID)); err != nil {
if err := m.exec.Eval(ctx, templates.BlurBySelector(id, selector)); err != nil {
m.logger.Trace().
Err(err).
Msg("failed removing focus from an element by selector")
@ -298,19 +299,19 @@ func (m *Manager) MoveMouse(ctx context.Context, objectID runtime.RemoteObjectID
return nil
}
func (m *Manager) MoveMouseBySelector(ctx context.Context, parentNodeID dom.NodeID, selector string) error {
func (m *Manager) MoveMouseBySelector(ctx context.Context, id runtime.RemoteObjectID, selector values.String) error {
m.logger.Trace().
Int("parent_node_id", int(parentNodeID)).
Str("selector", selector).
Str("parent_object_id", string(id)).
Str("selector", selector.String()).
Msg("starting to move the mouse towards an element by selector")
if err := m.ScrollIntoViewBySelector(ctx, selector, drivers.ScrollOptions{}); err != nil {
if err := m.ScrollIntoViewBySelector(ctx, id, selector, drivers.ScrollOptions{}); err != nil {
return err
}
m.logger.Trace().Msg("looking up for an element by selector")
found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector))
found, err := m.exec.EvalRef(ctx, templates.QuerySelector(id, selector))
if err != nil {
m.logger.Trace().Err(err).Msg("failed to find an element by selector")
@ -318,9 +319,17 @@ func (m *Manager) MoveMouseBySelector(ctx context.Context, parentNodeID dom.Node
return err
}
m.logger.Trace().Int("node_id", int(found.NodeID)).Msg("calculating clickable element points")
if found.ObjectID == nil {
m.logger.Trace().
Err(core.ErrNotFound).
Msg("element not found by selector")
points, err := GetClickablePointByNodeID(ctx, m.client, found.NodeID)
return core.ErrNotFound
}
m.logger.Trace().Str("object_id", string(*found.ObjectID)).Msg("calculating clickable element points")
points, err := GetClickablePointByObjectID(ctx, m.client, *found.ObjectID)
if err != nil {
m.logger.Trace().Err(err).Msg("failed calculating clickable element points")
@ -341,13 +350,19 @@ func (m *Manager) MoveMouseBySelector(ctx context.Context, parentNodeID dom.Node
return nil
}
func (m *Manager) MoveMouseByXY(ctx context.Context, x, y float64) error {
func (m *Manager) MoveMouseByXY(ctx context.Context, xv, yv values.Float) error {
x := float64(xv)
y := float64(yv)
m.logger.Trace().
Float64("x", x).
Float64("y", y).
Msg("starting to move the mouse towards an element by given coordinates")
if err := m.ScrollByXY(ctx, x, y, drivers.ScrollOptions{}); err != nil {
if err := m.ScrollByXY(ctx, drivers.ScrollOptions{
Top: xv,
Left: yv,
}); err != nil {
return err
}
@ -404,14 +419,14 @@ func (m *Manager) Click(ctx context.Context, objectID runtime.RemoteObjectID, co
return nil
}
func (m *Manager) ClickBySelector(ctx context.Context, parentNodeID dom.NodeID, selector string, count int) error {
func (m *Manager) ClickBySelector(ctx context.Context, id runtime.RemoteObjectID, selector values.String, count values.Int) error {
m.logger.Trace().
Int("parent_node_id", int(parentNodeID)).
Str("selector", selector).
Int("count", count).
Str("parent_object_id", string(id)).
Str("selector", string(selector)).
Int("count", int(count)).
Msg("starting to click on an element by selector")
if err := m.ScrollIntoViewBySelector(ctx, selector, drivers.ScrollOptions{
if err := m.ScrollIntoViewBySelector(ctx, id, selector, drivers.ScrollOptions{
Behavior: drivers.ScrollBehaviorAuto,
Block: drivers.ScrollVerticalAlignmentCenter,
Inline: drivers.ScrollHorizontalAlignmentCenter,
@ -421,7 +436,7 @@ func (m *Manager) ClickBySelector(ctx context.Context, parentNodeID dom.NodeID,
m.logger.Trace().Msg("looking up for an element by selector")
found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector))
found, err := m.exec.EvalRef(ctx, templates.QuerySelector(id, selector))
if err != nil {
m.logger.Trace().Err(err).Msg("failed to find an element by selector")
@ -429,9 +444,17 @@ func (m *Manager) ClickBySelector(ctx context.Context, parentNodeID dom.NodeID,
return err
}
m.logger.Trace().Int("node_id", int(found.NodeID)).Msg("calculating clickable element points")
if found.ObjectID == nil {
m.logger.Trace().
Err(core.ErrNotFound).
Msg("element not found by selector")
points, err := GetClickablePointByNodeID(ctx, m.client, found.NodeID)
return core.ErrNotFound
}
m.logger.Trace().Str("object_id", string(*found.ObjectID)).Msg("calculating clickable element points")
points, err := GetClickablePointByObjectID(ctx, m.client, *found.ObjectID)
if err != nil {
m.logger.Trace().Err(err).Msg("failed calculating clickable element points")
@ -443,7 +466,7 @@ func (m *Manager) ClickBySelector(ctx context.Context, parentNodeID dom.NodeID,
delay := time.Duration(drivers.DefaultMouseDelay) * time.Millisecond
if err := m.mouse.ClickWithCount(ctx, points.X, points.Y, delay, count); err != nil {
if err := m.mouse.ClickWithCount(ctx, points.X, points.Y, delay, int(count)); err != nil {
m.logger.Trace().Err(err).Msg("failed to click on an element")
return nil
}
@ -453,63 +476,72 @@ func (m *Manager) ClickBySelector(ctx context.Context, parentNodeID dom.NodeID,
return nil
}
func (m *Manager) ClickBySelectorAll(ctx context.Context, parentNodeID dom.NodeID, selector string, count int) error {
m.logger.Trace().
Int("parent_node_id", int(parentNodeID)).
Str("selector", selector).
Int("count", count).
Msg("starting to click on elements by selector")
if err := m.ScrollIntoViewBySelector(ctx, 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.client.DOM.QuerySelectorAll(ctx, dom.NewQuerySelectorAllArgs(parentNodeID, selector))
if err != nil {
m.logger.Trace().Err(err).Msg("failed to find elements by selector")
return err
}
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("node_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")
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
}
@ -576,13 +608,13 @@ func (m *Manager) Type(ctx context.Context, objectID runtime.RemoteObjectID, par
return nil
}
func (m *Manager) TypeBySelector(ctx context.Context, parentNodeID dom.NodeID, selector string, params TypeParams) error {
func (m *Manager) TypeBySelector(ctx context.Context, id runtime.RemoteObjectID, selector values.String, params TypeParams) error {
m.logger.Trace().
Int("parent_node_id", int(parentNodeID)).
Str("selector", selector).
Str("parent_object_id", string(id)).
Str("selector", string(selector)).
Msg("starting to type text by selector")
err := m.ScrollIntoViewBySelector(ctx, selector, drivers.ScrollOptions{
err := m.ScrollIntoViewBySelector(ctx, id, selector, drivers.ScrollOptions{
Behavior: drivers.ScrollBehaviorAuto,
Block: drivers.ScrollVerticalAlignmentCenter,
Inline: drivers.ScrollHorizontalAlignmentCenter,
@ -594,7 +626,7 @@ func (m *Manager) TypeBySelector(ctx context.Context, parentNodeID dom.NodeID, s
m.logger.Trace().Msg("looking up for an element by selector")
found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector))
found, err := m.exec.EvalRef(ctx, templates.QuerySelector(id, selector))
if err != nil {
m.logger.Trace().Err(err).Msg("failed to find an element by selector")
@ -602,9 +634,17 @@ func (m *Manager) TypeBySelector(ctx context.Context, parentNodeID dom.NodeID, s
return err
}
m.logger.Trace().Int("node_id", int(found.NodeID)).Msg("focusing on an element")
if found.ObjectID == nil {
m.logger.Trace().
Err(core.ErrNotFound).
Msg("element not found by selector")
err = m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetNodeID(found.NodeID))
return core.ErrNotFound
}
m.logger.Trace().Str("object_id", string(*found.ObjectID)).Msg("focusing on an element")
err = m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetObjectID(*found.ObjectID))
if err != nil {
m.logger.Trace().Err(err).Msg("failed to focus on an element")
@ -617,7 +657,7 @@ func (m *Manager) TypeBySelector(ctx context.Context, parentNodeID dom.NodeID, s
if params.Clear {
m.logger.Trace().Msg("calculating clickable element points")
points, err := GetClickablePointByNodeID(ctx, m.client, found.NodeID)
points, err := GetClickablePointByObjectID(ctx, m.client, *found.ObjectID)
if err != nil {
m.logger.Trace().Err(err).Msg("failed calculating clickable element points")
@ -701,13 +741,13 @@ func (m *Manager) Clear(ctx context.Context, objectID runtime.RemoteObjectID) er
return nil
}
func (m *Manager) ClearBySelector(ctx context.Context, parentNodeID dom.NodeID, selector string) error {
func (m *Manager) ClearBySelector(ctx context.Context, id runtime.RemoteObjectID, selector values.String) error {
m.logger.Trace().
Int("parent_node_id", int(parentNodeID)).
Str("selector", selector).
Str("parent_object_id", string(id)).
Str("selector", string(selector)).
Msg("starting to clear element by selector")
err := m.ScrollIntoViewBySelector(ctx, selector, drivers.ScrollOptions{
err := m.ScrollIntoViewBySelector(ctx, id, selector, drivers.ScrollOptions{
Behavior: drivers.ScrollBehaviorAuto,
Block: drivers.ScrollVerticalAlignmentCenter,
Inline: drivers.ScrollHorizontalAlignmentCenter,
@ -719,7 +759,7 @@ func (m *Manager) ClearBySelector(ctx context.Context, parentNodeID dom.NodeID,
m.logger.Trace().Msg("looking up for an element by selector")
found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector))
found, err := m.exec.EvalRef(ctx, templates.QuerySelector(id, selector))
if err != nil {
m.logger.Trace().Err(err).Msg("failed to find an element by selector")
@ -727,9 +767,17 @@ func (m *Manager) ClearBySelector(ctx context.Context, parentNodeID dom.NodeID,
return err
}
m.logger.Trace().Int("node_id", int(found.NodeID)).Msg("calculating clickable element points")
if found.ObjectID == nil {
m.logger.Trace().
Err(core.ErrNotFound).
Msg("element not found by selector")
points, err := GetClickablePointByNodeID(ctx, m.client, found.NodeID)
return core.ErrNotFound
}
m.logger.Trace().Str("object_id", string(*found.ObjectID)).Msg("calculating clickable element points")
points, err := GetClickablePointByObjectID(ctx, m.client, *found.ObjectID)
if err != nil {
m.logger.Trace().Err(err).Msg("failed calculating clickable element points")
@ -741,7 +789,7 @@ func (m *Manager) ClearBySelector(ctx context.Context, parentNodeID dom.NodeID,
m.logger.Trace().Msg("focusing on an element")
err = m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetNodeID(found.NodeID))
err = m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetObjectID(*found.ObjectID))
if err != nil {
m.logger.Trace().Err(err).Msg("failed to focus on an element")
@ -811,38 +859,34 @@ func (m *Manager) Press(ctx context.Context, keys []string, count int) error {
return nil
}
func (m *Manager) PressBySelector(ctx context.Context, parentNodeID dom.NodeID, selector string, keys []string, count int) error {
func (m *Manager) PressBySelector(ctx context.Context, id runtime.RemoteObjectID, selector values.String, keys []string, count int) error {
m.logger.Trace().
Int("parent_node_id", int(parentNodeID)).
Str("selector", selector).
Str("parent_object_id", string(id)).
Str("selector", string(selector)).
Strs("keys", keys).
Int("count", count).
Msg("starting to press keyboard keys by selector")
if err := m.FocusBySelector(ctx, parentNodeID, selector); err != nil {
if err := m.FocusBySelector(ctx, id, selector); err != nil {
return err
}
return m.Press(ctx, keys, count)
}
func (m *Manager) Select(ctx context.Context, objectID runtime.RemoteObjectID, value *values.Array) (*values.Array, error) {
func (m *Manager) Select(ctx context.Context, id runtime.RemoteObjectID, value *values.Array) (*values.Array, error) {
m.logger.Trace().
Str("object_id", string(objectID)).
Str("object_id", string(id)).
Msg("starting to select values")
if err := m.Focus(ctx, objectID); err != nil {
if err := m.Focus(ctx, id); err != nil {
return values.NewArray(0), err
}
m.logger.Trace().Msg("selecting values")
m.logger.Trace().Msg("evaluating a JS function")
val, err := m.exec.EvalValue(
ctx,
templates.Select(value.String()),
eval.WithArgRef(objectID),
)
val, err := m.exec.EvalValue(ctx, templates.Select(id, value))
if err != nil {
m.logger.Trace().Err(err).Msg("failed to evaluate a JS function")
@ -865,20 +909,20 @@ func (m *Manager) Select(ctx context.Context, objectID runtime.RemoteObjectID, v
return arr, nil
}
func (m *Manager) SelectBySelector(ctx context.Context, parentNodeID dom.NodeID, selector string, value *values.Array) (*values.Array, error) {
func (m *Manager) SelectBySelector(ctx context.Context, id runtime.RemoteObjectID, selector values.String, value *values.Array) (*values.Array, error) {
m.logger.Trace().
Int("parent_node_id", int(parentNodeID)).
Str("selector", selector).
Str("parent_object_id", string(id)).
Str("selector", string(selector)).
Msg("starting to select values by selector")
if err := m.FocusBySelector(ctx, parentNodeID, selector); err != nil {
if err := m.FocusBySelector(ctx, id, selector); err != nil {
return values.NewArray(0), err
}
m.logger.Trace().Msg("selecting values")
m.logger.Trace().Msg("evaluating a JS function")
res, err := m.exec.EvalValue(ctx, templates.SelectBySelector(selector, value.String()))
res, err := m.exec.EvalValue(ctx, templates.SelectBySelector(id, selector, value))
if err != nil {
m.logger.Trace().Err(err).Msg("failed to evaluate a JS function")

View File

@ -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.client, repl.Frame.ID)
ec, err := eval.New(ctx, m.logger, m.client, repl.Frame.ID)
if err != nil {
log.Trace().Err(err).Msg("failed to create frame execution context")

View File

@ -277,7 +277,13 @@ func (p *HTMLPage) Close() error {
p.mu.Lock()
defer p.mu.Unlock()
url := p.dom.GetMainFrame().GetURL().String()
var url string
frame := p.dom.GetMainFrame()
if frame != nil {
url = frame.GetURL().String()
}
p.closed = values.True
err := p.dom.Close()
@ -321,7 +327,7 @@ func (p *HTMLPage) IsClosed() values.Boolean {
}
func (p *HTMLPage) GetURL() values.String {
res, err := p.getCurrentDocument().Eval(context.Background(), templates.GetURL())
res, err := p.getCurrentDocument().Eval(context.Background(), templates.GetURL().String())
if err == nil {
return values.ToString(res)

View File

@ -2,15 +2,88 @@ package templates
import (
"fmt"
"github.com/MontFerret/ferret/pkg/drivers/cdp/eval"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/mafredri/cdp/protocol/runtime"
)
func AttributeRead(name values.String) string {
n := name.String()
return fmt.Sprintf(`
el.attributes[%s] != null ? el.attributes[%s].value : null
`, eval.ParamString(n), eval.ParamString(n))
const getAttribute = `(el, name) => {
return el.getAttribute(name)
}`
func GetAttribute(id runtime.RemoteObjectID, name values.String) *eval.Function {
if name == "style" {
return GetStyles(id)
}
return eval.F(getAttribute).WithArgRef(id).WithArgValue(name)
}
var getAttributes = fmt.Sprintf(`(element) => {
const getStyles = %s;
return element.getAttributeNames().reduce((res, name) => {
const out = res;
let value;
if (name !== "style") {
value = element.getAttribute(name);
} else {
value = getStyles(element);
}
out[name] = value;
return out;
}, {});
}`, getStyles)
func GetAttributes(id runtime.RemoteObjectID) *eval.Function {
return eval.F(getAttributes).WithArgRef(id)
}
const setAttribute = `(el, name, value) => {
el.setAttribute(name, value)
}`
func SetAttribute(id runtime.RemoteObjectID, name, value values.String) *eval.Function {
return eval.F(setAttribute).WithArgRef(id).WithArgValue(name).WithArgValue(value)
}
const setAttributes = `(el, values) => {
Object.keys(values).forEach((name) => {
const value = values[name];
el.setAttribute(name, value)
});
}`
func SetAttributes(id runtime.RemoteObjectID, values *values.Object) *eval.Function {
return eval.F(setAttributes).WithArgRef(id).WithArgValue(values)
}
const removeAttribute = `(el, name) => {
el.removeAttribute(name)
}`
func RemoveAttribute(id runtime.RemoteObjectID, name values.String) *eval.Function {
return eval.F(removeAttribute).WithArgRef(id).WithArgValue(name)
}
const removeAttributes = `(el, names) => {
names.forEach(name => el.removeAttribute(name));
}`
func RemoveAttributes(id runtime.RemoteObjectID, names []values.String) *eval.Function {
return eval.F(removeAttributes).WithArgRef(id).WithArg(names)
}
const getNodeType = `(el) => el.nodeType`
func GetNodeType(id runtime.RemoteObjectID) *eval.Function {
return eval.F(getNodeType).WithArgRef(id)
}
const getNodeName = `(el) => el.nodeName`
func GetNodeName(id runtime.RemoteObjectID) *eval.Function {
return eval.F(getNodeName).WithArgRef(id)
}

View File

@ -3,26 +3,31 @@ 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"
)
func Blur() string {
return `
(el) => {
el.blur()
}
`
const blur = `(el) => {
el.blur()
}`
func Blur(id runtime.RemoteObjectID) *eval.Function {
return eval.F(blur).WithArgRef(id)
}
func BlurBySelector(selector string) string {
return fmt.Sprintf(`
(parent) => {
const el = parent.querySelector('%s');
var blurBySelector = fmt.Sprintf(`
(el, selector) => {
const found = el.querySelector(selector);
if (el == null) {
throw new Error('%s')
if (found == null) {
throw new Error(%s)
}
el.blur();
found.blur();
}
`, selector, drivers.ErrNotFound)
`, ParamErr(drivers.ErrNotFound))
func BlurBySelector(id runtime.RemoteObjectID, selector values.String) *eval.Function {
return eval.F(blurBySelector).WithArgRef(id).WithArgValue(selector)
}

View File

@ -1,23 +1,25 @@
package templates
import (
"fmt"
"github.com/MontFerret/ferret/pkg/drivers/cdp/eval"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/mafredri/cdp/protocol/runtime"
)
const getChildren = "(el) => Array.from(el.children)"
func GetChildren(id runtime.RemoteObjectID) *eval.Function {
return eval.F(getChildren).WithArgRef(id)
}
const getChildrenCount = "(el) => el.children.length"
func GetChildren() string {
return getChildren
func GetChildrenCount(id runtime.RemoteObjectID) *eval.Function {
return eval.F(getChildrenCount).WithArgRef(id)
}
func GetChildrenCount() string {
return getChildrenCount
}
const getChildByIndex = "(el, idx) => el.children[idx]"
func GetChildByIndex(idx int64) string {
return fmt.Sprintf(`
(el) => el.children[%s]
`, eval.ParamInt(idx))
func GetChildByIndex(id runtime.RemoteObjectID, index values.Int) *eval.Function {
return eval.F(getChildByIndex).WithArgRef(id).WithArgValue(index)
}

View File

@ -1,13 +0,0 @@
package templates
const domReadyTemplate = `
if (document.readyState === 'complete') {
return true;
}
return null;
`
func DOMReady() string {
return domReadyTemplate
}

View File

@ -1,40 +0,0 @@
package templates
import (
"fmt"
"github.com/MontFerret/ferret/pkg/drivers"
)
var getInnerHTMLBySelectorTemplate = fmt.Sprintf(`
(el, selector) => {
const found = el.querySelector(selector);
if (found == null) {
throw new Error('%s');
}
return found.innerHTML;
}
`, drivers.ErrNotFound,
)
func GetInnerHTMLBySelector() string {
return getInnerHTMLBySelectorTemplate
}
var getInnerHTMLBySelectorAllTemplate = fmt.Sprintf(`
(el, selector) => {
const found = el.querySelectorAll(selector);
if (found == null) {
throw new Error('%s');
}
return Array.from(found).map(i => i.innerHTML);
}
`, drivers.ErrNotFound,
)
func GetInnerHTMLBySelectorAll() string {
return getInnerHTMLBySelectorAllTemplate
}

View File

@ -1,40 +0,0 @@
package templates
import (
"fmt"
"github.com/MontFerret/ferret/pkg/drivers"
)
var getInnerTextBySelectorTemplate = fmt.Sprintf(`
(el, selector) => {
const found = el.querySelector(selector);
if (found == null) {
throw new Error("%s");
}
return found.innerText;
}
`, drivers.ErrNotFound,
)
func GetInnerTextBySelector() string {
return getInnerTextBySelectorTemplate
}
var getInnerTextBySelectorAllTemplate = fmt.Sprintf(`
(el, selector) => {
const found = el.querySelectorAll(selector);
if (found == null) {
throw new Error("%s");
}
return Array.from(found).map(i => i.innerText);
}
`, drivers.ErrNotFound,
)
func GetInnerTextBySelectorAll() string {
return getInnerTextBySelectorAllTemplate
}

View File

@ -0,0 +1,15 @@
package templates
import "github.com/MontFerret/ferret/pkg/drivers/cdp/eval"
const domReady = `() => {
if (document.readyState === 'complete') {
return true;
}
return null;
}`
func DOMReady() *eval.Function {
return eval.F(domReady)
}

View File

@ -1,13 +1,86 @@
package templates
import (
"bytes"
"github.com/MontFerret/ferret/pkg/drivers"
"strconv"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
func WaitEventToEqOperator(when drivers.WaitEvent) string {
if when == drivers.WaitEventPresence {
return "=="
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())
}
if i != lastIndex {
buf.WriteString(",")
}
}
return "!="
return buf.String()
}
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(",")
}
}
return buf.String()
}
func ParamString(value values.String) string {
return EscapeString(string(value))
}
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)
}

View File

@ -0,0 +1,67 @@
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"
)
const setInnerHTML = `(el, value) => {
el.innerHTML = value;
}`
func SetInnerHTML(id runtime.RemoteObjectID, value values.String) *eval.Function {
return eval.F(setInnerHTML).WithArgRef(id).WithArgValue(value)
}
const getInnerHTML = `(el) => {
if (el.nodeType !== 9) {
return el.innerHTML;
}
return document.documentElement.innerHTML;
}`
func GetInnerHTML(id runtime.RemoteObjectID) *eval.Function {
return eval.F(getInnerHTML).WithArgRef(id)
}
var setInnerHTMLBySelector = fmt.Sprintf(`(el, selector, value) => {
const found = el.querySelector(selector);
if (found == null) {
throw new Error(%s);
}
found.innerHTML = value;
}`, ParamErr(drivers.ErrNotFound))
func SetInnerHTMLBySelector(id runtime.RemoteObjectID, selector, value values.String) *eval.Function {
return eval.F(setInnerHTMLBySelector).WithArgRef(id).WithArgValue(selector).WithArgValue(value)
}
var getInnerHTMLBySelector = fmt.Sprintf(`(el, selector) => {
const found = el.querySelector(selector);
if (found == null) {
throw new Error(%s);
}
return found.innerHTML;
}`, ParamErr(drivers.ErrNotFound))
func GetInnerHTMLBySelector(id runtime.RemoteObjectID, selector values.String) *eval.Function {
return eval.F(getInnerHTMLBySelector).WithArgRef(id).WithArgValue(selector)
}
const getInnerHTMLBySelectorAll = `(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)
}

View File

@ -0,0 +1,74 @@
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"
)
const setInnerText = `(el, value) => {
el.innerText = value;
}`
func SetInnerText(id runtime.RemoteObjectID, value values.String) *eval.Function {
return eval.F(setInnerText).WithArgRef(id).WithArgValue(value)
}
const getInnerText = `(el) => {
if (el.nodeType !== 9) {
return el.innerText;
}
return document.documentElement.innerText;
}`
func GetInnerText(id runtime.RemoteObjectID) *eval.Function {
return eval.F(getInnerText).WithArgRef(id)
}
var setInnerTextBySelector = fmt.Sprintf(`
(el, selector, value) => {
const found = el.querySelector(selector);
if (found == null) {
throw new Error(%s);
}
found.innerText = value;
}`, ParamErr(drivers.ErrNotFound))
func SetInnerTextBySelector(id runtime.RemoteObjectID, selector, value values.String) *eval.Function {
return eval.F(setInnerTextBySelector).WithArgRef(id).WithArgValue(selector).WithArgValue(value)
}
var getInnerTextBySelector = fmt.Sprintf(`
(el, selector) => {
const found = el.querySelector(selector);
if (found == null) {
throw new Error(%s);
}
return found.innerText;
}`, ParamErr(drivers.ErrNotFound))
func GetInnerTextBySelector(id runtime.RemoteObjectID, selector values.String) *eval.Function {
return eval.F(getInnerTextBySelector).WithArgRef(id).WithArgValue(selector)
}
var getInnerTextBySelectorAll = fmt.Sprintf(`
(el, selector) => {
const found = el.querySelectorAll(selector);
if (found == null) {
throw new Error(%s);
}
return Array.from(found).map(i => i.innerText);
}`, ParamErr(drivers.ErrNotFound))
func GetInnerTextBySelectorAll(id runtime.RemoteObjectID, selector values.String) *eval.Function {
return eval.F(getInnerTextBySelectorAll).WithArgRef(id).WithArgValue(selector)
}

View File

@ -1,7 +1,12 @@
package templates
import (
"github.com/MontFerret/ferret/pkg/drivers/cdp/eval"
"github.com/mafredri/cdp/protocol/runtime"
)
const getParent = "(el) => el.parentElement"
func GetParent() string {
return getParent
func GetParent(id runtime.RemoteObjectID) *eval.Function {
return eval.F(getParent).WithArgRef(id)
}

View File

@ -4,12 +4,13 @@ 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"
)
func QuerySelector(selector string) string {
return fmt.Sprintf(`
(el) => {
const found = el.querySelector(%s);
var querySelector = fmt.Sprintf(`
(el, selector) => {
const found = el.querySelector(selector);
if (found == null) {
throw new Error(%s);
@ -18,24 +19,41 @@ func QuerySelector(selector string) string {
return found;
}
`,
eval.ParamString(selector),
eval.ParamString(drivers.ErrNotFound.Error()),
)
ParamErr(drivers.ErrNotFound),
)
func QuerySelector(id runtime.RemoteObjectID, selector values.String) *eval.Function {
return eval.F(querySelector).WithArgRef(id).WithArgValue(selector)
}
func QuerySelectorAll(selector string) string {
return fmt.Sprintf(`
(el) => {
const found = el.querySelectorAll(%s);
if (found == null) {
throw new Error(%s);
}
return found;
}
`,
eval.ParamString(selector),
eval.ParamString(drivers.ErrNotFound.Error()),
)
const querySelectorAll = `(el, selector) => {
return el.querySelectorAll(selector);
}`
func QuerySelectorAll(id runtime.RemoteObjectID, selector values.String) *eval.Function {
return eval.F(querySelectorAll).WithArgRef(id).WithArgValue(selector)
}
const existsBySelector = `
(el, selector) => {
const found = el.querySelector(selector);
return found != null;
}
`
func ExistsBySelector(id runtime.RemoteObjectID, selector values.String) *eval.Function {
return eval.F(existsBySelector).WithArgRef(id).WithArgValue(selector)
}
const countBySelector = `
(el, selector) => {
const found = el.querySelectorAll(selector);
return found.length;
}
`
func CountBySelector(id runtime.RemoteObjectID, selector values.String) *eval.Function {
return eval.F(countBySelector).WithArgRef(id).WithArgValue(selector)
}

View File

@ -2,136 +2,108 @@ 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 (
isElementInViewportTemplate = `
function isInViewport(elem) {
var bounding = elem.getBoundingClientRect();
return (
bounding.top >= 0 &&
bounding.left >= 0 &&
bounding.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
bounding.right <= (window.innerWidth || document.documentElement.clientWidth)
);
};
`
scrollTemplate = `
window.scrollTo({
left: %s,
top: %s,
behavior: '%s',
block: '%s',
inline: '%s'
});
`
scrollTopTemplate = `
window.scrollTo({
left: 0,
top: 0,
behavior: '%s',
block: '%s',
inline: '%s'
});
`
scrollBottomTemplate = `
window.scrollTo({
left: 0,
top: window.document.body.scrollHeight,
behavior: '%s',
block: '%s',
inline: '%s'
});
`
scrollIntoViewTemplate = `
(el) => {
` + isElementInViewportTemplate + `
if (!isInViewport(el)) {
el.scrollIntoView({
behavior: '%s',
block: '%s',
inline: '%s'
});
}
isElementInViewportFragment = `function isInViewport(i) {
var bounding = i.getBoundingClientRect();
return true;
}
`
return (
bounding.top >= 0 &&
bounding.left >= 0 &&
bounding.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
bounding.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}`
scrollIntoViewBySelectorTemplate = `
const el = document.querySelector('%s');
scroll = `(opts) =>
window.scrollTo({
left: opts.left,
top: opts.top,
behavior: opts.behavior,
block: opts.block,
inline: opts.inline
});
}`
scrollTop = `(opts) => {
window.scrollTo({
left: 0,
top: 0,
behavior: opts.behavior,
block: opts.block,
inline: opts.inline
});
}`
scrollBottom = `(opts) => {
window.scrollTo({
left: 0,
top: window.document.body.scrollHeight,
behavior: opts.behavior,
block: opts.block,
inline: opts.inline
});
}`
)
var (
scrollIntoView = fmt.Sprintf(`(el, opts) => {
%s
if (!isInViewport(el)) {
el.scrollIntoView({
behavior: opts.behavior,
block: opts.block,
inline: opts.inline
});
}
return true;
}`, isElementInViewportFragment)
scrollIntoViewBySelector = fmt.Sprintf(`(parent, selector, opts) => {
const el = parent.querySelector(selector);
if (el == null) {
throw new Error('%s');
throw new Error(%s);
}
` + isElementInViewportTemplate + `
%s
if (!isInViewport(el)) {
el.scrollIntoView({
behavior: '%s',
block: '%s',
inline: '%s'
behavior: opts.behavior,
block: opts.block,
inline: opts.inline
});
}
return true;
`
}`, ParamErr(core.ErrNotFound), isElementInViewportFragment)
)
func Scroll(x, y string, options drivers.ScrollOptions) string {
return fmt.Sprintf(
scrollTemplate,
x,
y,
options.Behavior,
options.Block,
options.Inline,
)
func Scroll(options drivers.ScrollOptions) *eval.Function {
return eval.F(scroll).WithArg(options)
}
func ScrollTop(options drivers.ScrollOptions) string {
return fmt.Sprintf(
scrollTopTemplate,
options.Behavior,
options.Block,
options.Inline,
)
func ScrollTop(options drivers.ScrollOptions) *eval.Function {
return eval.F(scrollTop).WithArg(options)
}
func ScrollBottom(options drivers.ScrollOptions) string {
return fmt.Sprintf(
scrollBottomTemplate,
options.Behavior,
options.Block,
options.Inline,
)
func ScrollBottom(options drivers.ScrollOptions) *eval.Function {
return eval.F(scrollBottom).WithArg(options)
}
func ScrollIntoView(options drivers.ScrollOptions) string {
return fmt.Sprintf(
scrollIntoViewTemplate,
options.Behavior,
options.Block,
options.Inline,
)
func ScrollIntoView(id runtime.RemoteObjectID, options drivers.ScrollOptions) *eval.Function {
return eval.F(scrollIntoView).WithArgRef(id).WithArg(options)
}
func ScrollIntoViewBySelector(selector string, options drivers.ScrollOptions) string {
return fmt.Sprintf(
scrollIntoViewBySelectorTemplate,
selector,
drivers.ErrNotFound,
options.Behavior,
options.Block,
options.Inline,
)
func ScrollIntoViewBySelector(id runtime.RemoteObjectID, selector values.String, options drivers.ScrollOptions) *eval.Function {
return eval.F(scrollIntoViewBySelector).WithArgRef(id).WithArgValue(selector).WithArg(options)
}

View File

@ -2,55 +2,51 @@ 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"
)
func selectBase(values string) string {
return fmt.Sprintf(`
const values = %s;
const selectFragment = `
if (el.nodeName.toLowerCase() !== 'select') {
throw new Error('element is not a <select> element.');
}
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;
}
}
const options = Array.from(el.options);
el.dispatchEvent(new Event('input', { 'bubbles': true }));
el.dispatchEvent(new Event('change', { 'bubbles': true }));
return options.filter(option => option.selected).map(option => option.value);
`
el.value = undefined;
const selec = `(el, values) => {` + selectFragment + `}`
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(id runtime.RemoteObjectID, inputs *values.Array) *eval.Function {
return eval.F(selec).WithArgRef(id).WithArgValue(inputs)
}
func Select(values string) string {
return fmt.Sprintf(`
(el) => {
%s
}
`, selectBase(values),
)
}
var selectBySelector = fmt.Sprintf(`(parent, selector, values) => {
const el = parent.querySelector(selector);
if (el == null) {
throw new Error(%s)
}
func SelectBySelector(selector, values string) string {
return fmt.Sprintf(`
const el = document.querySelector('%s');
if (el == null) {
throw new Error("%s")
}
%s
}`, ParamErr(core.ErrNotFound), selectFragment)
%s
`, selector, drivers.ErrNotFound, selectBase(values),
)
func SelectBySelector(id runtime.RemoteObjectID, selector values.String, inputs *values.Array) *eval.Function {
return eval.F(selectBySelector).WithArgRef(id).WithArgValue(selector).WithArgValue(inputs)
}

View File

@ -1,34 +0,0 @@
package templates
import (
"fmt"
"github.com/MontFerret/ferret/pkg/drivers"
)
const setInnerHTMLTemplate = `
(element, value) => {
element.innerHTML = value;
}
`
func SetInnerHTML() string {
return setInnerHTMLTemplate
}
var setInnerHTMLBySelectorTemplate = fmt.Sprintf(`
(el, selector, value) => {
const found = el.querySelector(selector);
if (found == null) {
throw new Error('%s');
}
found.innerHTML = value;
}
`,
drivers.ErrNotFound,
)
func SetInnerHTMLBySelector() string {
return setInnerHTMLBySelectorTemplate
}

View File

@ -1,34 +0,0 @@
package templates
import (
"fmt"
"github.com/MontFerret/ferret/pkg/drivers"
)
const setInnerTextTemplate = `
(element, value) => {
element.innerText = value;
}
`
func SetInnerText() string {
return setInnerTextTemplate
}
var setInnerTextBySelectorTemplate = fmt.Sprintf(`
(el, selector, value) => {
const found = el.querySelector(selector);
if (found == null) {
throw new Error('%s');
}
found.innerText = value;
}
`,
drivers.ErrNotFound,
)
func SetInnerTextBySelector() string {
return setInnerTextBySelectorTemplate
}

View File

@ -1,12 +1,17 @@
package templates
import (
"github.com/MontFerret/ferret/pkg/drivers/cdp/eval"
"github.com/mafredri/cdp/protocol/runtime"
)
const getPreviousElementSibling = "(el) => el.previousElementSibling"
const getNextElementSibling = "(el) => el.nextElementSibling"
func GetPreviousElementSibling() string {
return getPreviousElementSibling
func GetPreviousElementSibling(id runtime.RemoteObjectID) *eval.Function {
return eval.F(getPreviousElementSibling).WithArgRef(id)
}
func GetNextElementSibling() string {
return getNextElementSibling
func GetNextElementSibling(id runtime.RemoteObjectID) *eval.Function {
return eval.F(getNextElementSibling).WithArgRef(id)
}

View File

@ -1,95 +1,71 @@
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"
)
var getStylesTemplate = `
(el) => {
const out = {};
const styles = window.getComputedStyle(el);
Object.keys(styles).forEach((key) => {
if (!isNaN(parseFloat(key))) {
const name = styles[key];
const value = styles.getPropertyValue(name);
out[name] = value;
}
});
const getStyles = `(el) => {
const out = {};
const styles = window.getComputedStyle(el);
return out;
}
`
func GetStyles() string {
return getStylesTemplate
}
func GetStyle(name string) string {
return fmt.Sprintf(`
(el) => {
const out = {};
const styles = window.getComputedStyle(el);
return styles[%s];
}
`, eval.ParamString(name))
}
func SetStyle(name, value string) string {
return fmt.Sprintf(`
(el) => {
el.style[%s] = %s;
Object.keys(styles).forEach((key) => {
if (!isNaN(parseFloat(key))) {
const name = styles[key];
const value = styles.getPropertyValue(name);
out[name] = value;
}
`, eval.ParamString(name), eval.ParamString(value))
});
return out;
}`
func GetStyles(id runtime.RemoteObjectID) *eval.Function {
return eval.F(getStyles).WithArgRef(id)
}
func SetStyles(pairs *values.Object) string {
return fmt.Sprintf(`
(el) => {
const values = %s;
Object.keys(values).forEach((key) => {
el.style[key] = values[key]
});
}
`, eval.Param(pairs))
const getStyle = `(el, name) => {
const styles = window.getComputedStyle(el);
return styles[name];
}`
func GetStyle(id runtime.RemoteObjectID, name values.String) *eval.Function {
return eval.F(getStyle).WithArgRef(id).WithArgValue(name)
}
func RemoveStyles(names []values.String) string {
return fmt.Sprintf(`
(el) => {
const style = el.style;
[%s].forEach((name) => { style[name] = "" })
}
`,
eval.ParamStringList(names),
)
const setStyle = `(el, name, value) => {
el.style[name] = value;
}`
func SetStyle(id runtime.RemoteObjectID, name, value values.String) *eval.Function {
return eval.F(setStyle).WithArgRef(id).WithArgValue(name).WithArgValue(value)
}
func WaitForStyle(name, value string, when drivers.WaitEvent) string {
return fmt.Sprintf(`
(el) => {
const styles = window.getComputedStyle(el);
const actual = styles[%s];
const expected = %s;
const setStyles = `(el, values) => {
Object.keys(values).forEach((key) => {
el.style[key] = values[key]
});
}`
// null means we need to repeat
return actual %s expected ? true : null ;
}
`, eval.ParamString(name), eval.ParamString(value), WaitEventToEqOperator(when))
func SetStyles(id runtime.RemoteObjectID, values *values.Object) *eval.Function {
return eval.F(setStyles).WithArgRef(id).WithArgValue(values)
}
func StyleRead(name values.String) string {
n := name.String()
return fmt.Sprintf(`
((function() {
const cs = window.getComputedStyle(el);
const currentValue = cs.getPropertyValue(%s);
const removeStyles = `(el, names) => {
const style = el.style;
names.forEach((name) => { style[name] = "" })
}`
return currentValue || null;
})())
`, eval.ParamString(n))
func RemoveStyles(id runtime.RemoteObjectID, names []values.String) *eval.Function {
return eval.F(removeStyles).WithArgRef(id).WithArg(names)
}
const removeStylesAll = `(el) => {
el.removeAttribute("style");
}`
func RemoveStylesAll(id runtime.RemoteObjectID) *eval.Function {
return eval.F(removeStylesAll).WithArgRef(id)
}

View File

@ -1,7 +1,9 @@
package templates
const getURL = `return window.location.toString()`
import "github.com/MontFerret/ferret/pkg/drivers/cdp/eval"
func GetURL() string {
return getURL
const getURL = `() => window.location.toString()`
func GetURL() *eval.Function {
return eval.F(getURL)
}

View File

@ -0,0 +1,23 @@
package templates
import (
"github.com/MontFerret/ferret/pkg/drivers/cdp/eval"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/mafredri/cdp/protocol/runtime"
)
const getValue = `(el) => {
return el.value
}`
func GetValue(id runtime.RemoteObjectID) *eval.Function {
return eval.F(getValue).WithArgRef(id)
}
const setValue = `(el, value) => {
el.value = value
}`
func SetValue(id runtime.RemoteObjectID, value core.Value) *eval.Function {
return eval.F(setValue).WithArgRef(id).WithArgValue(value)
}

View File

@ -0,0 +1,266 @@
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 (
waitExistenceFragment = `(el, op, ...args) => {
const actual = %s; // check
// presence
if (op === 0) {
if (actual != null) {
return true;
}
} else {
if (actual == null) {
return true;
}
}
// null means we need to repeat
return null;
}`
waitEqualityFragment = `(el, expected, op, ...args) => {
const actual = %s; // check
// presence
if (op === 0) {
if (actual === expected) {
return true;
}
} else {
if (actual !== expected) {
return true;
}
}
// null means we need to repeat
return null;
}`
waitExistenceBySelectorFragment = `(parent, selector, op, ...args) => {
const el = parent.querySelector(selector); // selector
if (el == null) {
return false;
}
const actual = %s; // check
// presence
if (op === 0) {
if (actual != null) {
return true;
}
} else {
if (actual == null) {
return true;
}
}
// null means we need to repeat
return null;
}`
waitEqualityBySelectorFragment = `(parent, selector, expected, op, ...args) => {
const el = parent.querySelector(selector); // selector
if (el == null) {
return false;
}
const actual = %s; // check
// presence
if (op === 0) {
if (actual === expected) {
return true;
}
} else {
if (actual !== expected) {
return true;
}
}
// null means we need to repeat
return null;
}`
waitExistenceBySelectorAllFragment = `(parent, selector, op, ...args) => {
const elements = parent.querySelectorAll(selector); // selector
if (elements == null || elements.length === 0) {
return false;
}
let resultCount = 0;
elements.forEach((el) => {
let actual = %s; // check
// when
// presence
if (op === 0) {
if (actual != null) {
resultCount++;
}
} else {
if (actual == null) {
resultCount++;
}
}
});
if (resultCount === elements.length) {
return true;
}
// null means we need to repeat
return null;
}`
waitEqualityBySelectorAllFragment = `(parent, selector, expected, op, ...args) => {
const elements = parent.querySelectorAll(selector); // selector
if (elements == null || elements.length === 0) {
return false;
}
let resultCount = 0;
elements.forEach((el) => {
let actual = %s; // check
// when
// presence
if (op === 0) {
if (actual === expected) {
resultCount++;
}
} else {
if (actual !== expected) {
resultCount++;
}
}
});
if (resultCount === elements.length) {
return true;
}
// null means we need to repeat
return null;
}`
)
func partialWaitExistence(id runtime.RemoteObjectID, when drivers.WaitEvent, fragment string) *eval.Function {
return eval.F(fmt.Sprintf(waitExistenceFragment, fragment)).
WithArgRef(id).
WithArg(int(when))
}
func partialWaitEquality(id runtime.RemoteObjectID, expected core.Value, when drivers.WaitEvent, fragment string) *eval.Function {
return eval.F(fmt.Sprintf(waitEqualityFragment, fragment)).
WithArgRef(id).
WithArgValue(expected).
WithArg(int(when))
}
func partialWaitExistenceBySelector(id runtime.RemoteObjectID, selector values.String, when drivers.WaitEvent, fragment string) *eval.Function {
return eval.F(fmt.Sprintf(waitExistenceBySelectorFragment, fragment)).
WithArgRef(id).
WithArgValue(selector).
WithArg(int(when))
}
func partialWaitEqualityBySelector(id runtime.RemoteObjectID, selector values.String, expected core.Value, when drivers.WaitEvent, fragment string) *eval.Function {
return eval.F(fmt.Sprintf(waitEqualityBySelectorFragment, fragment)).
WithArgRef(id).
WithArgValue(selector).
WithArgValue(expected).
WithArg(int(when))
}
func partialWaitExistenceBySelectorAll(id runtime.RemoteObjectID, selector values.String, when drivers.WaitEvent, fragment string) *eval.Function {
return eval.F(fmt.Sprintf(waitExistenceBySelectorAllFragment, fragment)).
WithArgRef(id).
WithArgValue(selector).
WithArg(int(when))
}
func partialWaitEqualityBySelectorAll(id runtime.RemoteObjectID, selector values.String, expected core.Value, when drivers.WaitEvent, fragment string) *eval.Function {
return eval.F(fmt.Sprintf(waitEqualityBySelectorAllFragment, fragment)).
WithArgRef(id).
WithArgValue(selector).
WithArgValue(expected).
WithArg(int(when))
}
const waitForElementFragment = `el.querySelector(args[0])`
func WaitForElement(id runtime.RemoteObjectID, selector values.String, when drivers.WaitEvent) *eval.Function {
return partialWaitExistence(id, when, waitForElementFragment).WithArgValue(selector)
}
const waitForElementAllFragment = `(function() {
const elements = el.querySelector(args[0]);
return elements.length;
})()`
func WaitForElementAll(id runtime.RemoteObjectID, selector values.String, when drivers.WaitEvent) *eval.Function {
return partialWaitEquality(id, values.ZeroInt, when, waitForElementAllFragment).WithArgValue(selector)
}
const waitForClassFragment = `el.className.split(' ').find(i => i === args[0]);`
func WaitForClass(id runtime.RemoteObjectID, class values.String, when drivers.WaitEvent) *eval.Function {
return partialWaitExistence(id, when, waitForClassFragment).WithArgValue(class)
}
func WaitForClassBySelector(id runtime.RemoteObjectID, selector, class values.String, when drivers.WaitEvent) *eval.Function {
return partialWaitExistenceBySelector(id, selector, when, waitForClassFragment).WithArgValue(class)
}
func WaitForClassBySelectorAll(id runtime.RemoteObjectID, selector, class values.String, when drivers.WaitEvent) *eval.Function {
return partialWaitExistenceBySelectorAll(id, selector, when, waitForClassFragment).WithArgValue(class)
}
const waitForAttributeFragment = `el.getAttribute(args[0])`
func WaitForAttribute(id runtime.RemoteObjectID, name values.String, expected core.Value, when drivers.WaitEvent) *eval.Function {
return partialWaitEquality(id, expected, when, waitForAttributeFragment).WithArgValue(name)
}
func WaitForAttributeBySelector(id runtime.RemoteObjectID, selector values.String, name core.Value, expected core.Value, when drivers.WaitEvent) *eval.Function {
return partialWaitEqualityBySelector(id, selector, expected, when, waitForAttributeFragment).WithArgValue(name)
}
func WaitForAttributeBySelectorAll(id runtime.RemoteObjectID, selector, name values.String, expected core.Value, when drivers.WaitEvent) *eval.Function {
return partialWaitEqualityBySelectorAll(id, selector, expected, when, waitForAttributeFragment).WithArgValue(name)
}
const waitForStyleFragment = `(function getStyles() {
const styles = window.getComputedStyle(el);
return styles[args[0]];
})()`
func WaitForStyle(id runtime.RemoteObjectID, name values.String, expected core.Value, when drivers.WaitEvent) *eval.Function {
return partialWaitEquality(id, expected, when, waitForStyleFragment).WithArgValue(name)
}
func WaitForStyleBySelector(id runtime.RemoteObjectID, selector, name values.String, expected core.Value, when drivers.WaitEvent) *eval.Function {
return partialWaitEqualityBySelector(id, selector, expected, when, waitForStyleFragment).WithArgValue(name)
}
func WaitForStyleBySelectorAll(id runtime.RemoteObjectID, selector, name values.String, expected core.Value, when drivers.WaitEvent) *eval.Function {
return partialWaitEqualityBySelectorAll(id, selector, expected, when, waitForStyleFragment).WithArgValue(name)
}

View File

@ -1,36 +0,0 @@
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"
)
func WaitBySelector(selector values.String, when drivers.WaitEvent, value core.Value, check string) string {
return fmt.Sprintf(
`
const el = document.querySelector(%s); // selector
if (el == null) {
return false;
}
const result = %s; // check
// when value
if (result %s %s) {
return true;
}
// null means we need to repeat
return null;
`,
eval.ParamString(selector.String()),
check,
WaitEventToEqOperator(when),
eval.Param(value),
)
}

View File

@ -1,43 +0,0 @@
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"
)
func WaitBySelectorAll(selector values.String, when drivers.WaitEvent, value core.Value, check string) string {
return fmt.Sprintf(`
var elements = document.querySelectorAll(%s); // selector
if (elements == null || elements.length === 0) {
return false;
}
var resultCount = 0;
elements.forEach((el) => {
var result = %s; // check
// when
if (result %s %s) {
resultCount++;
}
});
if (resultCount === elements.length) {
return true;
}
// null means we need to repeat
return null;
`,
eval.ParamString(selector.String()),
check,
WaitEventToEqOperator(when),
eval.Param(value),
)
}

View File

@ -1,69 +1,74 @@
package templates
const xPathTemplate = `
(element, expression) => {
const unwrap = (item) => {
return item.nodeType != 2 ? item : item.nodeValue;
};
const out = document.evaluate(
expression,
element,
null,
XPathResult.ANY_TYPE
);
let result;
switch (out.resultType) {
case XPathResult.UNORDERED_NODE_ITERATOR_TYPE:
case XPathResult.ORDERED_NODE_ITERATOR_TYPE: {
result = [];
let item;
while ((item = out.iterateNext())) {
result.push(unwrap(item));
}
break;
}
case XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE:
case XPathResult.ORDERED_NODE_SNAPSHOT_TYPE: {
result = [];
for (let i = 0; i < out.snapshotLength; i++) {
const item = out.snapshotItem(i);
if (item != null) {
result.push(unwrap(item));
}
}
break;
}
case XPathResult.NUMBER_TYPE: {
result = out.numberValue;
break;
}
case XPathResult.STRING_TYPE: {
result = out.stringValue;
break;
}
case XPathResult.BOOLEAN_TYPE: {
result = out.booleanValue;
break;
}
case XPathResult.ANY_UNORDERED_NODE_TYPE:
case XPathResult.FIRST_ORDERED_NODE_TYPE: {
result = unwrap(out.singleNodeValue);
break;
}
default: {
break;
import (
"github.com/MontFerret/ferret/pkg/drivers/cdp/eval"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/mafredri/cdp/protocol/runtime"
)
const xpath = `(el, expression) => {
const unwrap = (item) => {
return item.nodeType != 2 ? item : item.nodeValue;
};
const out = document.evaluate(
expression,
el,
null,
XPathResult.ANY_TYPE
);
let result;
switch (out.resultType) {
case XPathResult.UNORDERED_NODE_ITERATOR_TYPE:
case XPathResult.ORDERED_NODE_ITERATOR_TYPE: {
result = [];
let item;
while ((item = out.iterateNext())) {
result.push(unwrap(item));
}
break;
}
case XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE:
case XPathResult.ORDERED_NODE_SNAPSHOT_TYPE: {
result = [];
for (let i = 0; i < out.snapshotLength; i++) {
const item = out.snapshotItem(i);
if (item != null) {
result.push(unwrap(item));
}
}
return result;
break;
}
`
case XPathResult.NUMBER_TYPE: {
result = out.numberValue;
break;
}
case XPathResult.STRING_TYPE: {
result = out.stringValue;
break;
}
case XPathResult.BOOLEAN_TYPE: {
result = out.booleanValue;
break;
}
case XPathResult.ANY_UNORDERED_NODE_TYPE:
case XPathResult.FIRST_ORDERED_NODE_TYPE: {
result = unwrap(out.singleNodeValue);
break;
}
default: {
break;
}
}
func XPath() string {
return xPathTemplate
return result;
}
`
func XPath(id runtime.RemoteObjectID, expression values.String) *eval.Function {
return eval.F(xpath).WithArgRef(id).WithArgValue(expression)
}

View File

@ -231,9 +231,9 @@ func GetInNode(ctx context.Context, node drivers.HTMLNode, path []core.Value) (c
switch segment {
case "nodeType":
return node.GetNodeType(), nil
return node.GetNodeType(ctx)
case "nodeName":
return node.GetNodeName(), nil
return node.GetNodeName(ctx)
case "children":
children, err := node.GetChildNodes(ctx)

View File

@ -142,12 +142,12 @@ func (doc *HTMLDocument) SetIn(ctx context.Context, path []core.Value, value cor
return common.SetInDocument(ctx, doc, path, value)
}
func (doc *HTMLDocument) GetNodeType() values.Int {
return 9
func (doc *HTMLDocument) GetNodeType(_ context.Context) (values.Int, error) {
return 9, nil
}
func (doc *HTMLDocument) GetNodeName() values.String {
return "#document"
func (doc *HTMLDocument) GetNodeName(_ context.Context) (values.String, error) {
return "#document", nil
}
func (doc *HTMLDocument) GetChildNodes(ctx context.Context) (*values.Array, error) {
@ -216,7 +216,7 @@ func (doc *HTMLDocument) ScrollBySelector(_ context.Context, _ values.String, _
return core.ErrNotSupported
}
func (doc *HTMLDocument) ScrollByXY(_ context.Context, _, _ values.Float, _ drivers.ScrollOptions) error {
func (doc *HTMLDocument) Scroll(_ context.Context, _ drivers.ScrollOptions) error {
return core.ErrNotSupported
}
@ -224,34 +224,6 @@ func (doc *HTMLDocument) MoveMouseByXY(_ context.Context, _, _ values.Float) err
return core.ErrNotSupported
}
func (doc *HTMLDocument) WaitForElement(_ context.Context, _ values.String, _ drivers.WaitEvent) error {
return core.ErrNotSupported
}
func (doc *HTMLDocument) WaitForClassBySelector(_ context.Context, _, _ values.String, _ drivers.WaitEvent) error {
return core.ErrNotSupported
}
func (doc *HTMLDocument) WaitForClassBySelectorAll(_ context.Context, _, _ values.String, _ drivers.WaitEvent) error {
return core.ErrNotSupported
}
func (doc *HTMLDocument) WaitForAttributeBySelector(_ context.Context, _, _ values.String, _ core.Value, _ drivers.WaitEvent) error {
return core.ErrNotSupported
}
func (doc *HTMLDocument) WaitForAttributeBySelectorAll(_ context.Context, _, _ values.String, _ core.Value, _ drivers.WaitEvent) error {
return core.ErrNotSupported
}
func (doc *HTMLDocument) WaitForStyleBySelector(_ context.Context, _, _ values.String, _ core.Value, _ drivers.WaitEvent) error {
return core.ErrNotSupported
}
func (doc *HTMLDocument) WaitForStyleBySelectorAll(_ context.Context, _, _ values.String, _ core.Value, _ drivers.WaitEvent) error {
return core.ErrNotSupported
}
func (doc *HTMLDocument) Close() error {
return nil
}

View File

@ -2,6 +2,7 @@ package http_test
import (
"bytes"
"context"
"testing"
"github.com/MontFerret/ferret/pkg/drivers/http"
@ -234,7 +235,10 @@ func TestDocument(t *testing.T) {
So(err, ShouldBeNil)
So(el.GetNodeType(), ShouldEqual, 9)
nt, err := el.GetNodeType(context.Background())
So(err, ShouldBeNil)
So(nt, ShouldEqual, 9)
})
})
}

View File

@ -86,22 +86,22 @@ func (el *HTMLElement) Copy() core.Value {
return c
}
func (el *HTMLElement) GetNodeType() values.Int {
func (el *HTMLElement) GetNodeType(_ context.Context) (values.Int, error) {
nodes := el.selection.Nodes
if len(nodes) == 0 {
return 0
return 0, nil
}
return values.NewInt(common.FromHTMLType(nodes[0].Type))
return values.NewInt(common.FromHTMLType(nodes[0].Type)), nil
}
func (el *HTMLElement) Close() error {
return nil
}
func (el *HTMLElement) GetNodeName() values.String {
return values.NewString(goquery.NodeName(el.selection))
func (el *HTMLElement) GetNodeName(_ context.Context) (values.String, error) {
return values.NewString(goquery.NodeName(el.selection)), nil
}
func (el *HTMLElement) Length() values.Int {
@ -599,14 +599,46 @@ func (el *HTMLElement) WaitForClass(_ context.Context, _ values.String, _ driver
return core.ErrNotSupported
}
func (el *HTMLElement) WaitForElement(_ context.Context, _ values.String, _ drivers.WaitEvent) error {
return core.ErrNotSupported
}
func (el *HTMLElement) WaitForElementAll(_ context.Context, _ values.String, _ drivers.WaitEvent) error {
return core.ErrNotSupported
}
func (el *HTMLElement) WaitForAttribute(_ context.Context, _ values.String, _ core.Value, _ drivers.WaitEvent) error {
return core.ErrNotSupported
}
func (el *HTMLElement) WaitForAttributeBySelector(_ context.Context, _, _ values.String, _ core.Value, _ drivers.WaitEvent) error {
return core.ErrNotSupported
}
func (el *HTMLElement) WaitForAttributeBySelectorAll(_ context.Context, _, _ values.String, _ core.Value, _ drivers.WaitEvent) error {
return core.ErrNotSupported
}
func (el *HTMLElement) WaitForStyle(_ context.Context, _ values.String, _ core.Value, _ drivers.WaitEvent) error {
return core.ErrNotSupported
}
func (el *HTMLElement) WaitForStyleBySelector(_ context.Context, _, _ values.String, _ core.Value, _ drivers.WaitEvent) error {
return core.ErrNotSupported
}
func (el *HTMLElement) WaitForStyleBySelectorAll(_ context.Context, _, _ values.String, _ core.Value, _ drivers.WaitEvent) error {
return core.ErrNotSupported
}
func (el *HTMLElement) WaitForClassBySelector(_ context.Context, _, _ values.String, _ drivers.WaitEvent) error {
return core.ErrNotSupported
}
func (el *HTMLElement) WaitForClassBySelectorAll(_ context.Context, _, _ values.String, _ drivers.WaitEvent) error {
return core.ErrNotSupported
}
func (el *HTMLElement) ensureStyles(ctx context.Context) error {
if el.styles == nil {
styles, err := el.parseStyles(ctx)

View File

@ -257,7 +257,10 @@ func TestElement(t *testing.T) {
So(err, ShouldBeNil)
So(el.GetNodeType(), ShouldEqual, 1)
nt, err := el.GetNodeType(context.Background())
So(err, ShouldBeNil)
So(nt, ShouldEqual, 1)
})
Convey(".GetNodeName", t, func() {
@ -273,7 +276,10 @@ func TestElement(t *testing.T) {
So(err, ShouldBeNil)
So(el.GetNodeName(), ShouldEqual, "body")
nn, err := el.GetNodeName(context.Background())
So(err, ShouldBeNil)
So(nn, ShouldEqual, "body")
})
Convey(".Length", t, func() {
@ -399,10 +405,9 @@ func TestElement(t *testing.T) {
So(err, ShouldBeNil)
So(found, ShouldNotEqual, values.None)
v := found.(drivers.HTMLNode).GetNodeName()
v, err := found.(drivers.HTMLNode).GetNodeName(context.Background())
So(err, ShouldBeNil)
So(v, ShouldEqual, "img")
})

View File

@ -1,6 +1,10 @@
package drivers
import "strings"
import (
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/wI2L/jettison"
"strings"
)
// ScrollBehavior defines the transition animation.
// In HTML specification, default value is auto, but in Ferret it's instant.
@ -24,6 +28,10 @@ func NewScrollBehavior(value string) ScrollBehavior {
}
}
func (b ScrollBehavior) MarshalJSON() ([]byte, error) {
return jettison.MarshalOpts(b.String(), jettison.NoHTMLEscaping())
}
func (b ScrollBehavior) String() string {
switch b {
case ScrollBehaviorInstant:
@ -62,6 +70,10 @@ func NewScrollVerticalAlignment(value string) ScrollVerticalAlignment {
}
}
func (a ScrollVerticalAlignment) MarshalJSON() ([]byte, error) {
return jettison.MarshalOpts(a.String(), jettison.NoHTMLEscaping())
}
func (a ScrollVerticalAlignment) String() string {
switch a {
case ScrollVerticalAlignmentCenter:
@ -104,6 +116,10 @@ func NewScrollHorizontalAlignment(value string) ScrollHorizontalAlignment {
}
}
func (a ScrollHorizontalAlignment) MarshalJSON() ([]byte, error) {
return jettison.MarshalOpts(a.String(), jettison.NoHTMLEscaping())
}
func (a ScrollHorizontalAlignment) String() string {
switch a {
case ScrollHorizontalAlignmentCenter:
@ -121,7 +137,9 @@ func (a ScrollHorizontalAlignment) String() string {
// ScrollOptions defines how scroll animation should be performed.
type ScrollOptions struct {
Behavior ScrollBehavior
Block ScrollVerticalAlignment
Inline ScrollHorizontalAlignment
Top values.Float `json:"top"`
Left values.Float `json:"left"`
Behavior ScrollBehavior `json:"behavior"`
Block ScrollVerticalAlignment `json:"block"`
Inline ScrollHorizontalAlignment `json:"inline"`
}

View File

@ -24,9 +24,9 @@ type (
collections.Measurable
io.Closer
GetNodeType() values.Int
GetNodeType(ctx context.Context) (values.Int, error)
GetNodeName() values.String
GetNodeName(ctx context.Context) (values.String, error)
GetChildNodes(ctx context.Context) (*values.Array, error)
@ -133,11 +133,27 @@ type (
HoverBySelector(ctx context.Context, selector values.String) error
WaitForElement(ctx context.Context, selector values.String, when WaitEvent) error
WaitForElementAll(ctx context.Context, selector values.String, when WaitEvent) error
WaitForAttribute(ctx context.Context, name values.String, value core.Value, when WaitEvent) error
WaitForAttributeBySelector(ctx context.Context, selector, name values.String, value core.Value, when WaitEvent) error
WaitForAttributeBySelectorAll(ctx context.Context, selector, name values.String, value core.Value, when WaitEvent) error
WaitForStyle(ctx context.Context, name values.String, value core.Value, when WaitEvent) error
WaitForStyleBySelector(ctx context.Context, selector, name values.String, value core.Value, when WaitEvent) error
WaitForStyleBySelectorAll(ctx context.Context, selector, name values.String, value core.Value, when WaitEvent) error
WaitForClass(ctx context.Context, class values.String, when WaitEvent) error
WaitForClassBySelector(ctx context.Context, selector, class values.String, when WaitEvent) error
WaitForClassBySelectorAll(ctx context.Context, selector, class values.String, when WaitEvent) error
}
HTMLDocument interface {
@ -155,29 +171,15 @@ type (
GetChildDocuments(ctx context.Context) (*values.Array, error)
Scroll(ctx context.Context, options ScrollOptions) error
ScrollTop(ctx context.Context, options ScrollOptions) error
ScrollBottom(ctx context.Context, options ScrollOptions) error
ScrollBySelector(ctx context.Context, selector values.String, options ScrollOptions) error
ScrollByXY(ctx context.Context, x, y values.Float, options ScrollOptions) error
MoveMouseByXY(ctx context.Context, x, y values.Float) error
WaitForElement(ctx context.Context, selector values.String, when WaitEvent) error
WaitForAttributeBySelector(ctx context.Context, selector, name values.String, value core.Value, when WaitEvent) error
WaitForAttributeBySelectorAll(ctx context.Context, selector, name values.String, value core.Value, when WaitEvent) error
WaitForStyleBySelector(ctx context.Context, selector, name values.String, value core.Value, when WaitEvent) error
WaitForStyleBySelectorAll(ctx context.Context, selector, name values.String, value core.Value, when WaitEvent) error
WaitForClassBySelector(ctx context.Context, selector, class values.String, when WaitEvent) error
WaitForClassBySelectorAll(ctx context.Context, selector, class values.String, when WaitEvent) error
}
// HTMLPage interface represents any web page loaded in the browser

View File

@ -29,5 +29,5 @@ func InputClear(ctx context.Context, args ...core.Value) (core.Value, error) {
return values.None, el.Clear(ctx)
}
return values.None, el.ClearBySelector(ctx, values.ToString(args[1]))
return values.True, el.ClearBySelector(ctx, values.ToString(args[1]))
}

View File

@ -25,8 +25,8 @@ func Focus(ctx context.Context, args ...core.Value) (core.Value, error) {
}
if len(args) == 1 {
return values.None, el.Focus(ctx)
return values.True, el.Focus(ctx)
}
return values.None, el.FocusBySelector(ctx, values.ToString(args[1]))
return values.True, el.FocusBySelector(ctx, values.ToString(args[1]))
}

View File

@ -27,7 +27,7 @@ func Hover(ctx context.Context, args ...core.Value) (core.Value, error) {
}
if len(args) == 1 {
return values.None, el.Hover(ctx)
return values.True, el.Hover(ctx)
}
err = core.ValidateType(args[1], types.String)
@ -36,5 +36,5 @@ func Hover(ctx context.Context, args ...core.Value) (core.Value, error) {
return values.None, err
}
return values.None, el.HoverBySelector(ctx, values.ToString(args[1]))
return values.True, el.HoverBySelector(ctx, values.ToString(args[1]))
}

View File

@ -49,5 +49,5 @@ func Navigate(ctx context.Context, args ...core.Value) (core.Value, error) {
ctx, fn := waitTimeout(ctx, timeout)
defer fn()
return values.None, page.Navigate(ctx, args[1].(values.String))
return values.True, page.Navigate(ctx, args[1].(values.String))
}

View File

@ -39,9 +39,9 @@ func Press(ctx context.Context, args ...core.Value) (core.Value, error) {
switch keys := keysArg.(type) {
case values.String:
return values.None, el.Press(ctx, []values.String{keys}, count)
return values.True, el.Press(ctx, []values.String{keys}, count)
case *values.Array:
return values.None, el.Press(ctx, values.ToStrings2(keys), count)
return values.True, el.Press(ctx, values.ToStrings2(keys), count)
default:
return values.None, core.TypeError(keysArg.Type(), types.String, types.Array)
}

View File

@ -43,9 +43,9 @@ func PressSelector(ctx context.Context, args ...core.Value) (core.Value, error)
switch keys := keysArg.(type) {
case values.String:
return values.None, el.PressBySelector(ctx, selector, []values.String{keys}, count)
return values.True, el.PressBySelector(ctx, selector, []values.String{keys}, count)
case *values.Array:
return values.None, el.PressBySelector(ctx, selector, values.ToStrings2(keys), count)
return values.True, el.PressBySelector(ctx, selector, values.ToStrings2(keys), count)
default:
return values.None, core.TypeError(keysArg.Type(), types.String, types.Array)
}

View File

@ -39,5 +39,5 @@ func ScrollBottom(ctx context.Context, args ...core.Value) (core.Value, error) {
}
}
return values.None, doc.ScrollBottom(ctx, opts)
return values.True, doc.ScrollBottom(ctx, opts)
}

View File

@ -86,14 +86,14 @@ func ScrollInto(ctx context.Context, args ...core.Value) (core.Value, error) {
if doc != nil {
if selector != values.EmptyString {
return values.None, doc.ScrollBySelector(ctx, selector, opts)
return values.True, doc.ScrollBySelector(ctx, selector, opts)
}
return values.None, doc.GetElement().ScrollIntoView(ctx, opts)
return values.True, doc.GetElement().ScrollIntoView(ctx, opts)
}
if el != nil {
return values.None, el.ScrollIntoView(ctx, opts)
return values.True, el.ScrollIntoView(ctx, opts)
}
return values.None, core.TypeError(

View File

@ -39,5 +39,5 @@ func ScrollTop(ctx context.Context, args ...core.Value) (core.Value, error) {
}
}
return values.None, doc.ScrollTop(ctx, opts)
return values.True, doc.ScrollTop(ctx, opts)
}

View File

@ -46,6 +46,8 @@ func ScrollXY(ctx context.Context, args ...core.Value) (core.Value, error) {
y := values.ToFloat(args[2])
var opts drivers.ScrollOptions
opts.Top = x
opts.Left = y
if len(args) > 3 {
opts, err = toScrollOptions(args[3])
@ -53,7 +55,10 @@ func ScrollXY(ctx context.Context, args ...core.Value) (core.Value, error) {
if err != nil {
return values.None, err
}
opts.Top = x
opts.Left = y
}
return values.None, doc.ScrollByXY(ctx, x, y, opts)
return values.True, doc.Scroll(ctx, opts)
}

View File

@ -70,7 +70,7 @@ func waitAttributeWhen(ctx context.Context, args []core.Value, when drivers.Wait
return values.None, err
}
doc, err := drivers.ToDocument(arg1)
el, err := drivers.ToElement(arg1)
if err != nil {
return values.None, err
@ -78,7 +78,7 @@ func waitAttributeWhen(ctx context.Context, args []core.Value, when drivers.Wait
selector := args[1].(values.String)
name := args[2].(values.String)
value := args[3]
value := values.ToString(args[3])
if len(args) == 5 {
err = core.ValidateType(args[4], types.Int)
@ -93,7 +93,7 @@ func waitAttributeWhen(ctx context.Context, args []core.Value, when drivers.Wait
ctx, fn := waitTimeout(ctx, timeout)
defer fn()
return values.None, doc.WaitForAttributeBySelector(ctx, selector, name, value, when)
return values.True, el.WaitForAttributeBySelector(ctx, selector, name, value, when)
}
el := arg1.(drivers.HTMLElement)
@ -113,5 +113,5 @@ func waitAttributeWhen(ctx context.Context, args []core.Value, when drivers.Wait
ctx, fn := waitTimeout(ctx, timeout)
defer fn()
return values.None, el.WaitForAttribute(ctx, name, value, when)
return values.True, el.WaitForAttribute(ctx, name, value, when)
}

View File

@ -36,7 +36,7 @@ func waitAttributeAllWhen(ctx context.Context, args []core.Value, when drivers.W
return values.None, err
}
doc, err := drivers.ToDocument(args[0])
el, err := drivers.ToElement(args[0])
if err != nil {
return values.None, err
@ -74,5 +74,5 @@ func waitAttributeAllWhen(ctx context.Context, args []core.Value, when drivers.W
ctx, fn := waitTimeout(ctx, timeout)
defer fn()
return values.None, doc.WaitForAttributeBySelectorAll(ctx, selector, name, value, when)
return values.True, el.WaitForAttributeBySelectorAll(ctx, selector, name, value, when)
}

View File

@ -69,7 +69,7 @@ func waitClassWhen(ctx context.Context, args []core.Value, when drivers.WaitEven
return values.None, err
}
doc, err := drivers.ToDocument(arg1)
el, err := drivers.ToElement(arg1)
if err != nil {
return values.None, err
@ -91,7 +91,7 @@ func waitClassWhen(ctx context.Context, args []core.Value, when drivers.WaitEven
ctx, fn := waitTimeout(ctx, timeout)
defer fn()
return values.None, doc.WaitForClassBySelector(ctx, selector, class, when)
return values.True, el.WaitForClassBySelector(ctx, selector, class, when)
}
el := arg1.(drivers.HTMLElement)
@ -110,5 +110,5 @@ func waitClassWhen(ctx context.Context, args []core.Value, when drivers.WaitEven
ctx, fn := waitTimeout(ctx, timeout)
defer fn()
return values.None, el.WaitForClass(ctx, class, when)
return values.True, el.WaitForClass(ctx, class, when)
}

View File

@ -36,7 +36,7 @@ func waitClassAllWhen(ctx context.Context, args []core.Value, when drivers.WaitE
return values.None, err
}
doc, err := drivers.ToDocument(args[0])
el, err := drivers.ToElement(args[0])
if err != nil {
return values.None, err
@ -73,5 +73,5 @@ func waitClassAllWhen(ctx context.Context, args []core.Value, when drivers.WaitE
ctx, fn := waitTimeout(ctx, timeout)
defer fn()
return values.None, doc.WaitForClassBySelectorAll(ctx, selector, class, when)
return values.True, el.WaitForClassBySelectorAll(ctx, selector, class, when)
}

View File

@ -34,7 +34,7 @@ func waitElementWhen(ctx context.Context, args []core.Value, when drivers.WaitEv
return values.None, err
}
doc, err := drivers.ToDocument(args[0])
el, err := drivers.ToElement(args[0])
if err != nil {
return values.None, err
@ -56,5 +56,5 @@ func waitElementWhen(ctx context.Context, args []core.Value, when drivers.WaitEv
ctx, fn := waitTimeout(ctx, timeout)
defer fn()
return values.None, doc.WaitForElement(ctx, values.NewString(selector), when)
return values.True, el.WaitForElement(ctx, values.NewString(selector), when)
}

View File

@ -55,10 +55,10 @@ func WaitNavigation(ctx context.Context, args ...core.Value) (core.Value, error)
defer fn()
if params.Frame == nil {
return values.None, doc.WaitForNavigation(ctx, params.TargetURL)
return values.True, doc.WaitForNavigation(ctx, params.TargetURL)
}
return values.None, doc.WaitForFrameNavigation(ctx, params.Frame, params.TargetURL)
return values.True, doc.WaitForFrameNavigation(ctx, params.Frame, params.TargetURL)
}
func parseWaitNavigationParams(arg core.Value) (WaitNavigationParams, error) {

View File

@ -70,7 +70,7 @@ func waitStyleWhen(ctx context.Context, args []core.Value, when drivers.WaitEven
return values.None, err
}
doc, err := drivers.ToDocument(arg1)
el, err := drivers.ToElement(arg1)
if err != nil {
return values.None, err
@ -93,7 +93,7 @@ func waitStyleWhen(ctx context.Context, args []core.Value, when drivers.WaitEven
ctx, fn := waitTimeout(ctx, timeout)
defer fn()
return values.None, doc.WaitForStyleBySelector(ctx, selector, name, value, when)
return values.True, el.WaitForStyleBySelector(ctx, selector, name, value, when)
}
el := arg1.(drivers.HTMLElement)
@ -113,5 +113,5 @@ func waitStyleWhen(ctx context.Context, args []core.Value, when drivers.WaitEven
ctx, fn := waitTimeout(ctx, timeout)
defer fn()
return values.None, el.WaitForStyle(ctx, name, value, when)
return values.True, el.WaitForStyle(ctx, name, value, when)
}

View File

@ -36,7 +36,7 @@ func waitStyleAllWhen(ctx context.Context, args []core.Value, when drivers.WaitE
return values.None, err
}
doc, err := drivers.ToDocument(args[0])
el, err := drivers.ToElement(args[0])
if err != nil {
return values.None, err
@ -74,5 +74,5 @@ func waitStyleAllWhen(ctx context.Context, args []core.Value, when drivers.WaitE
ctx, fn := waitTimeout(ctx, timeout)
defer fn()
return values.None, doc.WaitForStyleBySelectorAll(ctx, selector, name, value, when)
return values.True, el.WaitForStyleBySelectorAll(ctx, selector, name, value, when)
}