diff --git a/e2e/cli.go b/e2e/cli.go index 25a0b38e..798c01ee 100644 --- a/e2e/cli.go +++ b/e2e/cli.go @@ -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 diff --git a/e2e/tests/dynamic/doc/wait/attr.fql b/e2e/tests/dynamic/doc/wait/attr.fql index 88634851..ac6cb9ea 100644 --- a/e2e/tests/dynamic/doc/wait/attr.fql +++ b/e2e/tests/dynamic/doc/wait/attr.fql @@ -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 \ No newline at end of file +RETURN TRUE \ No newline at end of file diff --git a/e2e/tests/dynamic/doc/wait/attr_all.fql b/e2e/tests/dynamic/doc/wait/attr_all.fql index 9d21dce5..8d915629 100644 --- a/e2e/tests/dynamic/doc/wait/attr_all.fql +++ b/e2e/tests/dynamic/doc/wait/attr_all.fql @@ -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 \ No newline at end of file +RETURN TRUE \ No newline at end of file diff --git a/e2e/tests/dynamic/element/attrs/get_2.fql b/e2e/tests/dynamic/element/attrs/get_2.fql index 341fa7fa..dd5521e4 100644 --- a/e2e/tests/dynamic/element/attrs/get_2.fql +++ b/e2e/tests/dynamic/element/attrs/get_2.fql @@ -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 \ No newline at end of file +RETURN TRUE \ No newline at end of file diff --git a/e2e/tests/dynamic/element/attrs/remove.fql b/e2e/tests/dynamic/element/attrs/remove.fql index 3cc0edd7..cddd56c2 100644 --- a/e2e/tests/dynamic/element/attrs/remove.fql +++ b/e2e/tests/dynamic/element/attrs/remove.fql @@ -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 \ No newline at end of file +RETURN TRUE \ No newline at end of file diff --git a/e2e/tests/dynamic/element/attrs/remove_style.fql b/e2e/tests/dynamic/element/attrs/remove_style.fql new file mode 100644 index 00000000..5c0df57b --- /dev/null +++ b/e2e/tests/dynamic/element/attrs/remove_style.fql @@ -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 \ No newline at end of file diff --git a/e2e/tests/dynamic/element/attrs/set.fql b/e2e/tests/dynamic/element/attrs/set.fql index ad99271e..36475ac3 100644 --- a/e2e/tests/dynamic/element/attrs/set.fql +++ b/e2e/tests/dynamic/element/attrs/set.fql @@ -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 \ No newline at end of file +RETURN TRUE \ No newline at end of file diff --git a/e2e/tests/dynamic/element/attrs/set_many.fql b/e2e/tests/dynamic/element/attrs/set_many.fql index 4d364198..e40c57f5 100644 --- a/e2e/tests/dynamic/element/attrs/set_many.fql +++ b/e2e/tests/dynamic/element/attrs/set_many.fql @@ -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 \ No newline at end of file +RETURN TRUE \ No newline at end of file diff --git a/e2e/tests/dynamic/element/styles/remove.fql b/e2e/tests/dynamic/element/styles/remove.fql index 0952bc5e..67df0aee 100644 --- a/e2e/tests/dynamic/element/styles/remove.fql +++ b/e2e/tests/dynamic/element/styles/remove.fql @@ -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 \ No newline at end of file +RETURN TRUE \ No newline at end of file diff --git a/e2e/tests/dynamic/element/wait/attr_2.fql b/e2e/tests/dynamic/element/wait/attr_2.fql index 6f9366ff..aa35455b 100644 --- a/e2e/tests/dynamic/element/wait/attr_2.fql +++ b/e2e/tests/dynamic/element/wait/attr_2.fql @@ -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") diff --git a/e2e/tests/examples/pagination.yaml b/e2e/tests/examples/pagination.yaml index 62e40351..ba0475b3 100644 --- a/e2e/tests/examples/pagination.yaml +++ b/e2e/tests/examples/pagination.yaml @@ -5,4 +5,4 @@ query: criteria: "scraper" pages: 2 assert: - text: RETURN T::NOT::EMPTY(@lab.data.query.result) \ No newline at end of file + text: RETURN T::NOT::NONE(@lab.data.query.result) \ No newline at end of file diff --git a/examples/pagination.fql b/examples/pagination.fql index 20a07e6b..350f04b5 100644 --- a/examples/pagination.fql +++ b/examples/pagination.fql @@ -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) + + diff --git a/pkg/drivers/cdp/dom/document.go b/pkg/drivers/cdp/dom/document.go index 03ab4524..fcf8b6e5 100644 --- a/pkg/drivers/cdp/dom/document.go +++ b/pkg/drivers/cdp/dom/document.go @@ -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 { diff --git a/pkg/drivers/cdp/dom/element.go b/pkg/drivers/cdp/dom/element.go index 38964553..2b4eebfb 100644 --- a/pkg/drivers/cdp/dom/element.go +++ b/pkg/drivers/cdp/dom/element.go @@ -4,9 +4,7 @@ import ( "context" "encoding/json" "fmt" - "github.com/MontFerret/ferret/pkg/runtime/logging" "hash/fnv" - "strconv" "strings" "time" @@ -16,7 +14,6 @@ import ( "github.com/pkg/errors" "github.com/rs/zerolog" "github.com/wI2L/jettison" - "golang.org/x/net/html" "github.com/MontFerret/ferret/pkg/drivers" "github.com/MontFerret/ferret/pkg/drivers/cdp/eval" @@ -25,29 +22,20 @@ 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" - "github.com/MontFerret/ferret/pkg/runtime/values/types" ) -var emptyNodeID = dom.NodeID(0) - -type ( - HTMLElementIdentity struct { - NodeID dom.NodeID - ObjectID runtime.RemoteObjectID - } - - HTMLElement struct { - logger zerolog.Logger - client *cdp.Client - dom *Manager - input *input.Manager - exec *eval.Runtime - id HTMLElementIdentity - nodeType html.NodeType - nodeName values.String - } -) +type HTMLElement struct { + logger zerolog.Logger + client *cdp.Client + dom *Manager + input *input.Manager + exec *eval.Runtime + id runtime.RemoteObjectID + nodeType *common.LazyValue + nodeName *common.LazyValue +} func LoadHTMLElement( ctx context.Context, @@ -65,51 +53,40 @@ func LoadHTMLElement( // getting a remote object that represents the current DOM Node args := dom.NewResolveNodeArgs().SetNodeID(nodeID).SetExecutionContextID(exec.ContextID()) - obj, err := client.DOM.ResolveNode(ctx, args) + ref, err := client.DOM.ResolveNode(ctx, args) if err != nil { return nil, err } - if obj.Object.ObjectID == nil { - return nil, core.Error(core.ErrNotFound, fmt.Sprintf("element %d", nodeID)) + if ref.Object.ObjectID == nil { + return nil, core.Error(core.ErrNotFound, fmt.Sprintf("element %s", ref.Object.Value)) } - return LoadHTMLElementWithID( - ctx, + return ResolveHTMLElement( logger, client, domManager, input, exec, - HTMLElementIdentity{ - NodeID: nodeID, - ObjectID: *obj.Object.ObjectID, - }, + ref.Object, ) } -func LoadHTMLElementWithID( - ctx context.Context, +func ResolveHTMLElement( logger zerolog.Logger, client *cdp.Client, domManager *Manager, input *input.Manager, exec *eval.Runtime, - id HTMLElementIdentity, + ref runtime.RemoteObject, ) (*HTMLElement, error) { - node, err := client.DOM.DescribeNode( - ctx, - dom. - NewDescribeNodeArgs(). - SetObjectID(id.ObjectID). - SetDepth(0), - ) - - if err != nil { - return nil, core.Error(err, strconv.Itoa(int(id.NodeID))) + if ref.ObjectID == nil { + return nil, core.Error(core.ErrNotFound, fmt.Sprintf("element %s", ref.Value)) } + id := *ref.ObjectID + return NewHTMLElement( logger, client, @@ -117,8 +94,6 @@ func LoadHTMLElementWithID( input, exec, id, - node.Node.NodeType, - node.Node.NodeName, ), nil } @@ -128,24 +103,24 @@ func NewHTMLElement( domManager *Manager, input *input.Manager, exec *eval.Runtime, - id HTMLElementIdentity, - nodeType int, - nodeName string, + id runtime.RemoteObjectID, ) *HTMLElement { el := new(HTMLElement) el.logger = logging. WithName(logger.With(), "dom_element"). - Int("node_id", int(id.NodeID)). - Str("object_id", string(id.ObjectID)). - Str("node_name", nodeName). + Str("object_id", string(id)). Logger() el.client = client el.dom = domManager el.input = input el.exec = exec el.id = id - el.nodeType = common.ToHTMLType(nodeType) - el.nodeName = values.NewString(nodeName) + el.nodeType = common.NewLazyValue(func(ctx context.Context) (core.Value, error) { + return el.exec.EvalValue(ctx, templates.GetNodeType(el.id)) + }) + el.nodeName = common.NewLazyValue(func(ctx context.Context) (core.Value, error) { + return el.exec.EvalValue(ctx, templates.GetNodeName(el.id)) + }) return el } @@ -197,7 +172,7 @@ func (el *HTMLElement) Hash() uint64 { h.Write([]byte(el.Type().String())) h.Write([]byte(":")) - h.Write([]byte(strconv.Itoa(int(el.id.NodeID)))) + h.Write([]byte(el.id)) return h.Sum64() } @@ -219,27 +194,35 @@ func (el *HTMLElement) SetIn(ctx context.Context, path []core.Value, value core. } func (el *HTMLElement) GetValue(ctx context.Context) (core.Value, error) { - return el.exec.ReadProperty(ctx, el.id.ObjectID, "value") + return el.exec.EvalValue(ctx, templates.GetValue(el.id)) } func (el *HTMLElement) SetValue(ctx context.Context, value core.Value) error { - return el.client.DOM.SetNodeValue(ctx, dom.NewSetNodeValueArgs(el.id.NodeID, value.String())) + return el.exec.Eval(ctx, templates.SetValue(el.id, value)) } -func (el *HTMLElement) GetNodeType() values.Int { - return values.NewInt(common.FromHTMLType(el.nodeType)) +func (el *HTMLElement) GetNodeType(ctx context.Context) (values.Int, error) { + out, err := el.nodeType.Read(ctx) + + if err != nil { + return values.ZeroInt, err + } + + return values.ToInt(out), nil } -func (el *HTMLElement) GetNodeName() values.String { - return el.nodeName +func (el *HTMLElement) GetNodeName(ctx context.Context) (values.String, error) { + out, err := el.nodeName.Read(ctx) + + if err != nil { + return values.EmptyString, err + } + + return values.ToString(out), nil } func (el *HTMLElement) Length() values.Int { - value, err := el.exec.EvalValue( - context.Background(), - templates.GetChildrenCount(), - eval.WithArgRef(el.id.ObjectID), - ) + value, err := el.exec.EvalValue(context.Background(), templates.GetChildrenCount(el.id)) if err != nil { el.logError(err) @@ -251,494 +234,267 @@ func (el *HTMLElement) Length() values.Int { } func (el *HTMLElement) GetStyles(ctx context.Context) (*values.Object, error) { - value, err := el.exec.EvalValue( - ctx, - templates.GetStyles(), - eval.WithArgRef(el.id.ObjectID), - ) + out, err := el.exec.EvalValue(ctx, templates.GetStyles(el.id)) if err != nil { return values.NewObject(), err } - if value.Type() == types.Object { - return value.(*values.Object), err - } - - return values.NewObject(), core.TypeError(value.Type(), types.Object) + return values.ToObject(ctx, out), nil } func (el *HTMLElement) GetStyle(ctx context.Context, name values.String) (core.Value, error) { - return el.exec.EvalValue( - ctx, - templates.GetStyle(name.String()), - eval.WithArgRef(el.id.ObjectID), - ) + return el.exec.EvalValue(ctx, templates.GetStyle(el.id, name)) } func (el *HTMLElement) SetStyles(ctx context.Context, styles *values.Object) error { - return el.exec.Eval( - ctx, - templates.SetStyles(styles), - eval.WithArgRef(el.id.ObjectID), - ) + return el.exec.Eval(ctx, templates.SetStyles(el.id, styles)) } func (el *HTMLElement) SetStyle(ctx context.Context, name, value values.String) error { - return el.exec.Eval( - ctx, - templates.SetStyle(name.String(), value.String()), - eval.WithArgRef(el.id.ObjectID), - ) + return el.exec.Eval(ctx, templates.SetStyle(el.id, name, value)) } func (el *HTMLElement) RemoveStyle(ctx context.Context, names ...values.String) error { - if len(names) == 0 { - return nil - } - - return el.exec.Eval( - ctx, - templates.RemoveStyles(names), - eval.WithArgRef(el.id.ObjectID), - ) + return el.exec.Eval(ctx, templates.RemoveStyles(el.id, names)) } func (el *HTMLElement) GetAttributes(ctx context.Context) (*values.Object, error) { - repl, err := el.client.DOM.GetAttributes(ctx, dom.NewGetAttributesArgs(el.id.NodeID)) + out, err := el.exec.EvalValue(ctx, templates.GetAttributes(el.id)) if err != nil { return values.NewObject(), err } - attrs := values.NewObject() - - traverseAttrs(repl.Attributes, func(name, value string) bool { - key := values.NewString(name) - var val core.Value = values.None - - if name != common.AttrNameStyle { - val = values.NewString(value) - } else { - parsed, err := common.DeserializeStyles(values.NewString(value)) - - if err == nil { - val = parsed - } else { - val = values.NewObject() - } - } - - attrs.Set(key, val) - - return true - }) - - return attrs, nil + return values.ToObject(ctx, out), nil } func (el *HTMLElement) GetAttribute(ctx context.Context, name values.String) (core.Value, error) { - repl, err := el.client.DOM.GetAttributes(ctx, dom.NewGetAttributesArgs(el.id.NodeID)) - - if err != nil { - return values.None, err - } - - var result core.Value = values.None - targetName := strings.ToLower(name.String()) - - traverseAttrs(repl.Attributes, func(name, value string) bool { - if name == targetName { - if name != common.AttrNameStyle { - result = values.NewString(value) - } else { - parsed, err := common.DeserializeStyles(values.NewString(value)) - - if err == nil { - result = parsed - } else { - result = values.NewObject() - } - } - - return false - } - - return true - }) - - return result, nil + return el.exec.EvalValue(ctx, templates.GetAttribute(el.id, name)) } func (el *HTMLElement) SetAttributes(ctx context.Context, attrs *values.Object) error { - var err error - - attrs.ForEach(func(value core.Value, key string) bool { - err = el.SetAttribute(ctx, values.NewString(key), values.NewString(value.String())) - - return err == nil - }) - - return err + return el.exec.Eval(ctx, templates.SetAttributes(el.id, attrs)) } func (el *HTMLElement) SetAttribute(ctx context.Context, name, value values.String) error { - return el.client.DOM.SetAttributeValue( - ctx, - dom.NewSetAttributeValueArgs(el.id.NodeID, string(name), string(value)), - ) + return el.exec.Eval(ctx, templates.SetAttribute(el.id, name, value)) } func (el *HTMLElement) RemoveAttribute(ctx context.Context, names ...values.String) error { - for _, name := range names { - err := el.client.DOM.RemoveAttribute( - ctx, - dom.NewRemoveAttributeArgs(el.id.NodeID, name.String()), - ) - - if err != nil { - return err - } - } - - return nil + return el.exec.Eval(ctx, templates.RemoveAttributes(el.id, names)) } func (el *HTMLElement) GetChildNodes(ctx context.Context) (*values.Array, error) { - out, err := el.exec.EvalRef( - ctx, - templates.GetChildren(), - eval.WithArgRef(el.id.ObjectID), - ) + out, err := el.evalTo(ctx, templates.GetChildren(el.id)) if err != nil { return values.EmptyArray(), err } - val, err := el.convertEvalResult(ctx, out) - - if err != nil { - el.logError(err) - - return values.EmptyArray(), err - } - - arr, ok := val.(*values.Array) - - if ok { - return arr, nil - } - - return values.EmptyArray(), nil + return values.ToArray(ctx, out), nil } func (el *HTMLElement) GetChildNode(ctx context.Context, idx values.Int) (core.Value, error) { - out, err := el.exec.EvalRef( - ctx, - templates.GetChildByIndex(int64(idx)), - eval.WithArgRef(el.id.ObjectID), - ) - - if err != nil { - return values.None, err - } - - return el.convertEvalResult(ctx, out) + return el.evalToElement(ctx, templates.GetChildByIndex(el.id, idx)) } func (el *HTMLElement) GetParentElement(ctx context.Context) (core.Value, error) { - return el.evalAndGetElement(ctx, templates.GetParent()) + return el.evalToElement(ctx, templates.GetParent(el.id)) } func (el *HTMLElement) GetPreviousElementSibling(ctx context.Context) (core.Value, error) { - return el.evalAndGetElement(ctx, templates.GetPreviousElementSibling()) + return el.evalToElement(ctx, templates.GetPreviousElementSibling(el.id)) } func (el *HTMLElement) GetNextElementSibling(ctx context.Context) (core.Value, error) { - return el.evalAndGetElement(ctx, templates.GetNextElementSibling()) -} - -func (el *HTMLElement) evalAndGetElement(ctx context.Context, expr string) (core.Value, error) { - obj, err := el.exec.EvalRef( - ctx, - expr, - eval.WithArgRef(el.id.ObjectID), - ) - - if err != nil { - return values.None, err - } - - if obj.Type != "object" || obj.ObjectID == nil { - return values.None, nil - } - - repl, err := el.client.DOM.RequestNode(ctx, dom.NewRequestNodeArgs(*obj.ObjectID)) - - if err != nil { - return values.None, err - } - - return LoadHTMLElementWithID( - ctx, - el.logger, - el.client, - el.dom, - el.input, - el.exec, - HTMLElementIdentity{ - NodeID: repl.NodeID, - ObjectID: *obj.ObjectID, - }, - ) + return el.evalToElement(ctx, templates.GetNextElementSibling(el.id)) } func (el *HTMLElement) QuerySelector(ctx context.Context, selector values.String) (core.Value, error) { - obj, err := el.exec.EvalRef( - ctx, - templates.QuerySelector(selector.String()), - eval.WithArgRef(el.id.ObjectID), - ) - - if err != nil { - return values.None, err - } - - return el.convertEvalResult(ctx, obj) + return el.evalToElement(ctx, templates.QuerySelector(el.id, selector)) } func (el *HTMLElement) QuerySelectorAll(ctx context.Context, selector values.String) (*values.Array, error) { - selectorArgs := dom.NewQuerySelectorAllArgs(el.id.NodeID, selector.String()) - res, err := el.client.DOM.QuerySelectorAll(ctx, selectorArgs) + out, err := el.exec.EvalRef(ctx, templates.QuerySelectorAll(el.id, selector)) if err != nil { return values.EmptyArray(), err } - arr := values.NewArray(len(res.NodeIDs)) + res, err := el.fromEvalRef(ctx, out) - for _, id := range res.NodeIDs { - if id == emptyNodeID { - el.logError(err). - Str("selector", selector.String()). - Msg("failed to find a node by selector. returned 0 NodeID") - - continue - } - - childEl, err := LoadHTMLElement( - ctx, - el.logger, - el.client, - el.dom, - el.input, - el.exec, - id, - ) - - if err != nil { - return values.EmptyArray(), err - } - - arr.Push(childEl) + if err != nil { + return values.EmptyArray(), err } - return arr, nil + return values.ToArray(ctx, res), nil } func (el *HTMLElement) XPath(ctx context.Context, expression values.String) (result core.Value, err error) { - out, err := el.exec.EvalRef( - ctx, - templates.XPath(), - eval.WithArgRef(el.id.ObjectID), - eval.WithArgValue(expression), - ) + out, err := el.exec.EvalRef(ctx, templates.XPath(el.id, expression)) if err != nil { return values.None, err } - return el.convertEvalResult(ctx, out) + return el.fromEvalRef(ctx, out) } func (el *HTMLElement) GetInnerText(ctx context.Context) (values.String, error) { - return getInnerText(ctx, el.client, el.exec, el.id, el.nodeType) -} - -func (el *HTMLElement) SetInnerText(ctx context.Context, innerText values.String) error { - return setInnerText(ctx, el.client, el.exec, el.id, innerText) -} - -func (el *HTMLElement) GetInnerTextBySelector(ctx context.Context, selector values.String) (values.String, error) { - out, err := el.exec.EvalValue( - ctx, - templates.GetInnerTextBySelector(), - eval.WithArgRef(el.id.ObjectID), - eval.WithArgValue(selector), - ) + out, err := el.exec.EvalValue(ctx, templates.GetInnerText(el.id)) if err != nil { return values.EmptyString, err } - return values.NewString(out.String()), nil + return values.ToString(out), nil +} + +func (el *HTMLElement) SetInnerText(ctx context.Context, innerText values.String) error { + return el.exec.Eval( + ctx, + templates.SetInnerText(el.id, innerText), + ) +} + +func (el *HTMLElement) GetInnerTextBySelector(ctx context.Context, selector values.String) (values.String, error) { + out, err := el.exec.EvalValue(ctx, templates.GetInnerTextBySelector(el.id, selector)) + + if err != nil { + return values.EmptyString, err + } + + return values.ToString(out), nil } func (el *HTMLElement) SetInnerTextBySelector(ctx context.Context, selector, innerText values.String) error { return el.exec.Eval( ctx, - templates.SetInnerTextBySelector(), - eval.WithArgRef(el.id.ObjectID), - eval.WithArgValue(selector), - eval.WithArgValue(innerText), + templates.SetInnerTextBySelector(el.id, selector, innerText), ) } func (el *HTMLElement) GetInnerTextBySelectorAll(ctx context.Context, selector values.String) (*values.Array, error) { - out, err := el.exec.EvalValue( - ctx, - templates.GetInnerTextBySelectorAll(), - eval.WithArgRef(el.id.ObjectID), - eval.WithArgValue(selector), - ) + out, err := el.exec.EvalValue(ctx, templates.GetInnerTextBySelectorAll(el.id, selector)) if err != nil { return values.EmptyArray(), err } - arr, ok := out.(*values.Array) - - if !ok { - return values.EmptyArray(), errors.New("unexpected output") - } - - return arr, nil + return values.ToArray(ctx, out), nil } func (el *HTMLElement) GetInnerHTML(ctx context.Context) (values.String, error) { - return getInnerHTML(ctx, el.client, el.exec, el.id, el.nodeType) -} - -func (el *HTMLElement) SetInnerHTML(ctx context.Context, innerHTML values.String) error { - return setInnerHTML(ctx, el.client, el.exec, el.id, innerHTML) -} - -func (el *HTMLElement) GetInnerHTMLBySelector(ctx context.Context, selector values.String) (values.String, error) { - out, err := el.exec.EvalValue( - ctx, - templates.GetInnerHTMLBySelector(), - eval.WithArgRef(el.id.ObjectID), - eval.WithArgValue(selector), - ) + out, err := el.exec.EvalValue(ctx, templates.GetInnerHTML(el.id)) if err != nil { return values.EmptyString, err } - return values.NewString(out.String()), nil + return values.ToString(out), nil +} + +func (el *HTMLElement) SetInnerHTML(ctx context.Context, innerHTML values.String) error { + return el.exec.Eval(ctx, templates.SetInnerHTML(el.id, innerHTML)) +} + +func (el *HTMLElement) GetInnerHTMLBySelector(ctx context.Context, selector values.String) (values.String, error) { + out, err := el.exec.EvalValue(ctx, templates.GetInnerHTMLBySelector(el.id, selector)) + + if err != nil { + return values.EmptyString, err + } + + return values.ToString(out), nil } func (el *HTMLElement) SetInnerHTMLBySelector(ctx context.Context, selector, innerHTML values.String) error { - return el.exec.Eval( - ctx, - templates.SetInnerHTMLBySelector(), - eval.WithArgRef(el.id.ObjectID), - eval.WithArgValue(selector), - eval.WithArgValue(innerHTML), - ) + return el.exec.Eval(ctx, templates.SetInnerHTMLBySelector(el.id, selector, innerHTML)) } func (el *HTMLElement) GetInnerHTMLBySelectorAll(ctx context.Context, selector values.String) (*values.Array, error) { - out, err := el.exec.EvalValue( - ctx, - templates.GetInnerHTMLBySelectorAll(), - eval.WithArgRef(el.id.ObjectID), - eval.WithArgValue(selector), - ) + out, err := el.exec.EvalValue(ctx, templates.GetInnerHTMLBySelectorAll(el.id, selector)) if err != nil { return values.EmptyArray(), err } - arr, ok := out.(*values.Array) - - if !ok { - return values.EmptyArray(), errors.New("unexpected output") - } - - return arr, nil + return values.ToArray(ctx, out), nil } func (el *HTMLElement) CountBySelector(ctx context.Context, selector values.String) (values.Int, error) { - selectorArgs := dom.NewQuerySelectorAllArgs(el.id.NodeID, selector.String()) - res, err := el.client.DOM.QuerySelectorAll(ctx, selectorArgs) + out, err := el.exec.EvalValue(ctx, templates.CountBySelector(el.id, selector)) if err != nil { return values.ZeroInt, err } - return values.NewInt(len(res.NodeIDs)), nil + return values.ToInt(out), nil } func (el *HTMLElement) ExistsBySelector(ctx context.Context, selector values.String) (values.Boolean, error) { - selectorArgs := dom.NewQuerySelectorArgs(el.id.NodeID, selector.String()) - res, err := el.client.DOM.QuerySelector(ctx, selectorArgs) + out, err := el.exec.EvalValue(ctx, templates.ExistsBySelector(el.id, selector)) if err != nil { return values.False, err } - if res.NodeID == 0 { - return values.False, nil - } + return values.ToBoolean(out), nil +} - return values.True, nil +func (el *HTMLElement) WaitForElement(ctx context.Context, selector values.String, when drivers.WaitEvent) error { + task := events.NewEvalWaitTask( + el.exec, + templates.WaitForElement(el.id, selector, when), + events.DefaultPolling, + ) + + _, err := task.Run(ctx) + + return err +} + +func (el *HTMLElement) WaitForElementAll(ctx context.Context, selector values.String, when drivers.WaitEvent) error { + task := events.NewEvalWaitTask( + el.exec, + templates.WaitForElementAll(el.id, selector, when), + events.DefaultPolling, + ) + + _, err := task.Run(ctx) + + return err } func (el *HTMLElement) WaitForClass(ctx context.Context, class values.String, when drivers.WaitEvent) error { - task := events.NewWaitTask( - func(ctx2 context.Context) (core.Value, error) { - current, err := el.GetAttribute(ctx2, "class") + task := events.NewEvalWaitTask( + el.exec, + templates.WaitForClass(el.id, class, when), + events.DefaultPolling, + ) - if err != nil { - return values.None, nil - } + _, err := task.Run(ctx) - if current.Type() != types.String { - return values.None, nil - } + return err +} - str := current.(values.String) - classStr := string(class) - classes := strings.Split(string(str), " ") +func (el *HTMLElement) WaitForClassBySelector(ctx context.Context, selector, class values.String, when drivers.WaitEvent) error { + task := events.NewEvalWaitTask( + el.exec, + templates.WaitForClassBySelector(el.id, selector, class, when), + events.DefaultPolling, + ) - if when != drivers.WaitEventAbsence { - for _, c := range classes { - if c == classStr { - // The value does not really matter if it's not None - // None indicates that operation needs to be repeated - return values.True, nil - } - } - } else { - var found values.Boolean + _, err := task.Run(ctx) - for _, c := range classes { - if c == classStr { - found = values.True - break - } - } + return err +} - if found == values.False { - // The value does not really matter if it's not None - // None indicates that operation needs to be repeated - return values.False, nil - } - } - - return values.None, nil - }, +func (el *HTMLElement) WaitForClassBySelectorAll(ctx context.Context, selector, class values.String, when drivers.WaitEvent) error { + task := events.NewEvalWaitTask( + el.exec, + templates.WaitForClassBySelectorAll(el.id, selector, class, when), events.DefaultPolling, ) @@ -753,9 +509,35 @@ func (el *HTMLElement) WaitForAttribute( value core.Value, when drivers.WaitEvent, ) error { - task := events.NewValueWaitTask(when, value, func(ctx context.Context) (core.Value, error) { - return el.GetAttribute(ctx, name) - }, events.DefaultPolling) + task := events.NewEvalWaitTask( + el.exec, + templates.WaitForAttribute(el.id, name, value, when), + events.DefaultPolling, + ) + + _, err := task.Run(ctx) + + return err +} + +func (el *HTMLElement) WaitForAttributeBySelector(ctx context.Context, selector, name values.String, value core.Value, when drivers.WaitEvent) error { + task := events.NewEvalWaitTask( + el.exec, + templates.WaitForAttributeBySelector(el.id, selector, name, value, when), + events.DefaultPolling, + ) + + _, err := task.Run(ctx) + + return err +} + +func (el *HTMLElement) WaitForAttributeBySelectorAll(ctx context.Context, selector, name values.String, value core.Value, when drivers.WaitEvent) error { + task := events.NewEvalWaitTask( + el.exec, + templates.WaitForAttributeBySelectorAll(el.id, selector, name, value, when), + events.DefaultPolling, + ) _, err := task.Run(ctx) @@ -765,13 +547,32 @@ func (el *HTMLElement) WaitForAttribute( func (el *HTMLElement) WaitForStyle(ctx context.Context, name values.String, value core.Value, when drivers.WaitEvent) error { task := events.NewEvalWaitTask( el.exec, - templates.WaitForStyle( - name.String(), - value.String(), - when, - ), + templates.WaitForStyle(el.id, name, value, when), + events.DefaultPolling, + ) + + _, err := task.Run(ctx) + + return err +} + +func (el *HTMLElement) WaitForStyleBySelector(ctx context.Context, selector, name values.String, value core.Value, when drivers.WaitEvent) error { + task := events.NewEvalWaitTask( + el.exec, + templates.WaitForStyleBySelector(el.id, selector, name, value, when), + events.DefaultPolling, + ) + + _, err := task.Run(ctx) + + return err +} + +func (el *HTMLElement) WaitForStyleBySelectorAll(ctx context.Context, selector, name values.String, value core.Value, when drivers.WaitEvent) error { + task := events.NewEvalWaitTask( + el.exec, + templates.WaitForStyleBySelectorAll(el.id, selector, name, value, when), events.DefaultPolling, - eval.WithArgRef(el.id.ObjectID), ) _, err := task.Run(ctx) @@ -780,23 +581,46 @@ func (el *HTMLElement) WaitForStyle(ctx context.Context, name values.String, val } func (el *HTMLElement) Click(ctx context.Context, count values.Int) error { - return el.input.Click(ctx, el.id.ObjectID, int(count)) + return el.input.Click(ctx, el.id, int(count)) } func (el *HTMLElement) ClickBySelector(ctx context.Context, selector values.String, count values.Int) error { - return el.input.ClickBySelector(ctx, el.id.NodeID, selector.String(), int(count)) + return el.input.ClickBySelector(ctx, el.id, selector, count) } func (el *HTMLElement) ClickBySelectorAll(ctx context.Context, selector values.String, count values.Int) error { - return el.input.ClickBySelectorAll(ctx, el.id.NodeID, selector.String(), int(count)) + elements, err := el.QuerySelectorAll(ctx, selector) + + if err != nil { + return err + } + + elements.ForEach(func(value core.Value, idx int) bool { + found := value.(*HTMLElement) + + if e := found.Click(ctx, count); e != nil { + err = e + return false + } + + return true + }) + + return err } func (el *HTMLElement) Input(ctx context.Context, value core.Value, delay values.Int) error { - if el.GetNodeName() != "INPUT" { + name, err := el.GetNodeName(ctx) + + if err != nil { + return err + } + + if strings.ToLower(string(name)) != "input" { return core.Error(core.ErrInvalidOperation, "element is not an element.") } - return el.input.Type(ctx, el.id.ObjectID, input.TypeParams{ + return el.input.Type(ctx, el.id, input.TypeParams{ Text: value.String(), Clear: false, Delay: time.Duration(delay) * time.Millisecond, @@ -804,7 +628,7 @@ func (el *HTMLElement) Input(ctx context.Context, value core.Value, delay values } func (el *HTMLElement) InputBySelector(ctx context.Context, selector values.String, value core.Value, delay values.Int) error { - return el.input.TypeBySelector(ctx, el.id.NodeID, selector.String(), input.TypeParams{ + return el.input.TypeBySelector(ctx, el.id, selector, input.TypeParams{ Text: value.String(), Clear: false, Delay: time.Duration(delay) * time.Millisecond, @@ -816,72 +640,97 @@ func (el *HTMLElement) Press(ctx context.Context, keys []values.String, count va } func (el *HTMLElement) PressBySelector(ctx context.Context, selector values.String, keys []values.String, count values.Int) error { - return el.input.PressBySelector(ctx, el.id.NodeID, selector.String(), values.UnwrapStrings(keys), int(count)) + return el.input.PressBySelector(ctx, el.id, selector, values.UnwrapStrings(keys), int(count)) } func (el *HTMLElement) Clear(ctx context.Context) error { - return el.input.Clear(ctx, el.id.ObjectID) + return el.input.Clear(ctx, el.id) } func (el *HTMLElement) ClearBySelector(ctx context.Context, selector values.String) error { - return el.input.ClearBySelector(ctx, el.id.NodeID, selector.String()) + return el.input.ClearBySelector(ctx, el.id, selector) } func (el *HTMLElement) Select(ctx context.Context, value *values.Array) (*values.Array, error) { - return el.input.Select(ctx, el.id.ObjectID, value) + return el.input.Select(ctx, el.id, value) } func (el *HTMLElement) SelectBySelector(ctx context.Context, selector values.String, value *values.Array) (*values.Array, error) { - return el.input.SelectBySelector(ctx, el.id.NodeID, selector.String(), value) + return el.input.SelectBySelector(ctx, el.id, selector, value) } func (el *HTMLElement) ScrollIntoView(ctx context.Context, options drivers.ScrollOptions) error { - return el.input.ScrollIntoView(ctx, el.id.ObjectID, options) + return el.input.ScrollIntoView(ctx, el.id, options) } func (el *HTMLElement) Focus(ctx context.Context) error { - return el.input.Focus(ctx, el.id.ObjectID) + return el.input.Focus(ctx, el.id) } func (el *HTMLElement) FocusBySelector(ctx context.Context, selector values.String) error { - return el.input.FocusBySelector(ctx, el.id.NodeID, selector.String()) + return el.input.FocusBySelector(ctx, el.id, selector) } func (el *HTMLElement) Blur(ctx context.Context) error { - return el.input.Blur(ctx, el.id.ObjectID) + return el.input.Blur(ctx, el.id) } func (el *HTMLElement) BlurBySelector(ctx context.Context, selector values.String) error { - return el.input.BlurBySelector(ctx, el.id.ObjectID, selector.String()) + return el.input.BlurBySelector(ctx, el.id, selector) } func (el *HTMLElement) Hover(ctx context.Context) error { - return el.input.MoveMouse(ctx, el.id.ObjectID) + return el.input.MoveMouse(ctx, el.id) } func (el *HTMLElement) HoverBySelector(ctx context.Context, selector values.String) error { - return el.input.MoveMouseBySelector(ctx, el.id.NodeID, selector.String()) + return el.input.MoveMouseBySelector(ctx, el.id, selector) } -func (el *HTMLElement) convertEvalResult(ctx context.Context, out runtime.RemoteObject) (core.Value, error) { - typeName := out.Type +func (el *HTMLElement) evalTo(ctx context.Context, fn *eval.Function) (core.Value, error) { + out, err := el.exec.EvalRef(ctx, fn) - // checking whether it's actually an array - if typeName == "object" { - if out.ClassName != nil { - switch *out.ClassName { - case "Array": - typeName = "array" - case "HTMLCollection": - typeName = "HTMLCollection" - default: - break - } - } + if err != nil { + return values.None, err } - switch typeName { - case "array", "HTMLCollection": + return el.fromEvalRef(ctx, out) +} + +func (el *HTMLElement) evalToElement(ctx context.Context, fn *eval.Function) (core.Value, error) { + obj, err := el.exec.EvalRef(ctx, fn) + + if err != nil { + return values.None, err + } + + if obj.Type != "object" || obj.ObjectID == nil { + return values.None, nil + } + + return ResolveHTMLElement( + el.logger, + el.client, + el.dom, + el.input, + el.exec, + obj, + ) +} + +func (el *HTMLElement) fromEvalRef(ctx context.Context, out runtime.RemoteObject) (core.Value, error) { + var subtype string + + if out.Subtype != nil { + subtype = *out.Subtype + } + + if subtype == "null" || subtype == "undefined" { + return values.None, nil + } + + switch subtype { + case "array", "HTMLCollection", "NodeList": if out.ObjectID == nil { return values.None, nil } @@ -922,23 +771,13 @@ func (el *HTMLElement) convertEvalResult(ctx context.Context, out runtime.Remote continue } - repl, err := el.client.DOM.RequestNode(ctx, dom.NewRequestNodeArgs(*descr.Value.ObjectID)) - - if err != nil { - return values.None, err - } - - el, err := LoadHTMLElementWithID( - ctx, + el, err := ResolveHTMLElement( el.logger, el.client, el.dom, el.input, el.exec, - HTMLElementIdentity{ - NodeID: repl.NodeID, - ObjectID: *descr.Value.ObjectID, - }, + *descr.Value, ) if err != nil { @@ -949,7 +788,7 @@ func (el *HTMLElement) convertEvalResult(ctx context.Context, out runtime.Remote } return result, nil - case "object": + case "node": if out.ObjectID == nil { var value interface{} @@ -960,28 +799,16 @@ func (el *HTMLElement) convertEvalResult(ctx context.Context, out runtime.Remote return values.Parse(value), nil } - repl, err := el.client.DOM.RequestNode(ctx, dom.NewRequestNodeArgs(*out.ObjectID)) - - if err != nil { - return values.None, err - } - - return LoadHTMLElementWithID( - ctx, + return ResolveHTMLElement( el.logger, el.client, el.dom, el.input, el.exec, - HTMLElementIdentity{ - NodeID: repl.NodeID, - ObjectID: *out.ObjectID, - }, + out, ) - case "string", "number", "boolean": - return eval.Unmarshal(&out) default: - return values.None, nil + return eval.Unmarshal(out) } } @@ -989,7 +816,6 @@ func (el *HTMLElement) logError(err error) *zerolog.Event { return el.logger. Error(). Timestamp(). - Int("nodeID", int(el.id.NodeID)). - Str("objectID", string(el.id.ObjectID)). + Str("objectID", string(el.id)). Err(err) } diff --git a/pkg/drivers/cdp/dom/helpers.go b/pkg/drivers/cdp/dom/helpers.go index acf8b0ad..6d210756 100644 --- a/pkg/drivers/cdp/dom/helpers.go +++ b/pkg/drivers/cdp/dom/helpers.go @@ -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 diff --git a/pkg/drivers/cdp/dom/manager.go b/pkg/drivers/cdp/dom/manager.go index e695eac9..98c2c2ce 100644 --- a/pkg/drivers/cdp/dom/manager.go +++ b/pkg/drivers/cdp/dom/manager.go @@ -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) diff --git a/pkg/drivers/cdp/eval/function.go b/pkg/drivers/cdp/eval/function.go index 56be5485..2f6dc88f 100644 --- a/pkg/drivers/cdp/eval/function.go +++ b/pkg/drivers/cdp/eval/function.go @@ -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 - } -} diff --git a/pkg/drivers/cdp/eval/helpers.go b/pkg/drivers/cdp/eval/helpers.go index 69713071..1cd58174 100644 --- a/pkg/drivers/cdp/eval/helpers.go +++ b/pkg/drivers/cdp/eval/helpers.go @@ -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) } diff --git a/pkg/drivers/cdp/eval/helpers_test.go b/pkg/drivers/cdp/eval/helpers_test.go new file mode 100644 index 00000000..65a3461a --- /dev/null +++ b/pkg/drivers/cdp/eval/helpers_test.go @@ -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}") + }) + }) +} diff --git a/pkg/drivers/cdp/eval/param.go b/pkg/drivers/cdp/eval/param.go deleted file mode 100644 index b41925f4..00000000 --- a/pkg/drivers/cdp/eval/param.go +++ /dev/null @@ -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) -} diff --git a/pkg/drivers/cdp/eval/runtime.go b/pkg/drivers/cdp/eval/runtime.go index 29b6ebf6..9b3f1492 100644 --- a/pkg/drivers/cdp/eval/runtime.go +++ b/pkg/drivers/cdp/eval/runtime.go @@ -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: diff --git a/pkg/drivers/cdp/events/loop_test.go b/pkg/drivers/cdp/events/loop_test.go index d0a5fb23..0b8184cc 100644 --- a/pkg/drivers/cdp/events/loop_test.go +++ b/pkg/drivers/cdp/events/loop_test.go @@ -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() diff --git a/pkg/drivers/cdp/events/wait.go b/pkg/drivers/cdp/events/wait.go index 78b40620..549a5c86 100644 --- a/pkg/drivers/cdp/events/wait.go +++ b/pkg/drivers/cdp/events/wait.go @@ -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, ) diff --git a/pkg/drivers/cdp/input/manager.go b/pkg/drivers/cdp/input/manager.go index e5c0fa7e..2d744850 100644 --- a/pkg/drivers/cdp/input/manager.go +++ b/pkg/drivers/cdp/input/manager.go @@ -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") diff --git a/pkg/drivers/cdp/network/manager.go b/pkg/drivers/cdp/network/manager.go index c19bee51..cd02fcc8 100644 --- a/pkg/drivers/cdp/network/manager.go +++ b/pkg/drivers/cdp/network/manager.go @@ -605,7 +605,7 @@ func (m *Manager) WaitForFrameNavigation(ctx context.Context, frameID page.Frame if ctx.Err() == nil { log.Trace().Msg("creating frame execution context") - ec, err := eval.New(ctx, m.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") diff --git a/pkg/drivers/cdp/page.go b/pkg/drivers/cdp/page.go index e6095550..a77f866a 100644 --- a/pkg/drivers/cdp/page.go +++ b/pkg/drivers/cdp/page.go @@ -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) diff --git a/pkg/drivers/cdp/templates/attributes.go b/pkg/drivers/cdp/templates/attributes.go index 61a51e40..6bb8eee3 100644 --- a/pkg/drivers/cdp/templates/attributes.go +++ b/pkg/drivers/cdp/templates/attributes.go @@ -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) } diff --git a/pkg/drivers/cdp/templates/blur.go b/pkg/drivers/cdp/templates/blur.go index 8f22294b..670eb6f7 100644 --- a/pkg/drivers/cdp/templates/blur.go +++ b/pkg/drivers/cdp/templates/blur.go @@ -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) } diff --git a/pkg/drivers/cdp/templates/children.go b/pkg/drivers/cdp/templates/children.go index 92c797bb..4b7a99b9 100644 --- a/pkg/drivers/cdp/templates/children.go +++ b/pkg/drivers/cdp/templates/children.go @@ -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) } diff --git a/pkg/drivers/cdp/templates/dom_ready.go b/pkg/drivers/cdp/templates/dom_ready.go deleted file mode 100644 index a33bc01a..00000000 --- a/pkg/drivers/cdp/templates/dom_ready.go +++ /dev/null @@ -1,13 +0,0 @@ -package templates - -const domReadyTemplate = ` -if (document.readyState === 'complete') { - return true; -} - -return null; -` - -func DOMReady() string { - return domReadyTemplate -} diff --git a/pkg/drivers/cdp/templates/get_inner_html.go b/pkg/drivers/cdp/templates/get_inner_html.go deleted file mode 100644 index 93914229..00000000 --- a/pkg/drivers/cdp/templates/get_inner_html.go +++ /dev/null @@ -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 -} diff --git a/pkg/drivers/cdp/templates/get_inner_text.go b/pkg/drivers/cdp/templates/get_inner_text.go deleted file mode 100644 index 19b739ed..00000000 --- a/pkg/drivers/cdp/templates/get_inner_text.go +++ /dev/null @@ -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 -} diff --git a/pkg/drivers/cdp/templates/global.go b/pkg/drivers/cdp/templates/global.go new file mode 100644 index 00000000..c33cb032 --- /dev/null +++ b/pkg/drivers/cdp/templates/global.go @@ -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) +} diff --git a/pkg/drivers/cdp/templates/helpers.go b/pkg/drivers/cdp/templates/helpers.go index aeb0e8f8..e1193f0e 100644 --- a/pkg/drivers/cdp/templates/helpers.go +++ b/pkg/drivers/cdp/templates/helpers.go @@ -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) } diff --git a/pkg/drivers/cdp/templates/inner_html.go b/pkg/drivers/cdp/templates/inner_html.go new file mode 100644 index 00000000..702bfd55 --- /dev/null +++ b/pkg/drivers/cdp/templates/inner_html.go @@ -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) +} diff --git a/pkg/drivers/cdp/templates/inner_text.go b/pkg/drivers/cdp/templates/inner_text.go new file mode 100644 index 00000000..b591720f --- /dev/null +++ b/pkg/drivers/cdp/templates/inner_text.go @@ -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) +} diff --git a/pkg/drivers/cdp/templates/parent.go b/pkg/drivers/cdp/templates/parent.go index ee466d1a..826263d7 100644 --- a/pkg/drivers/cdp/templates/parent.go +++ b/pkg/drivers/cdp/templates/parent.go @@ -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) } diff --git a/pkg/drivers/cdp/templates/query.go b/pkg/drivers/cdp/templates/query.go index 9b2cdae4..533d7072 100644 --- a/pkg/drivers/cdp/templates/query.go +++ b/pkg/drivers/cdp/templates/query.go @@ -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) } diff --git a/pkg/drivers/cdp/templates/scroll.go b/pkg/drivers/cdp/templates/scroll.go index dcb014b1..d3be7582 100644 --- a/pkg/drivers/cdp/templates/scroll.go +++ b/pkg/drivers/cdp/templates/scroll.go @@ -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) } diff --git a/pkg/drivers/cdp/templates/select.go b/pkg/drivers/cdp/templates/select.go index 390e4e9c..2403db66 100644 --- a/pkg/drivers/cdp/templates/select.go +++ b/pkg/drivers/cdp/templates/select.go @@ -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 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) } diff --git a/pkg/drivers/cdp/templates/set_inner_html.go b/pkg/drivers/cdp/templates/set_inner_html.go deleted file mode 100644 index eb2b2995..00000000 --- a/pkg/drivers/cdp/templates/set_inner_html.go +++ /dev/null @@ -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 -} diff --git a/pkg/drivers/cdp/templates/set_inner_text.go b/pkg/drivers/cdp/templates/set_inner_text.go deleted file mode 100644 index 5ba0023f..00000000 --- a/pkg/drivers/cdp/templates/set_inner_text.go +++ /dev/null @@ -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 -} diff --git a/pkg/drivers/cdp/templates/siblings.go b/pkg/drivers/cdp/templates/siblings.go index 20152363..1c70c77f 100644 --- a/pkg/drivers/cdp/templates/siblings.go +++ b/pkg/drivers/cdp/templates/siblings.go @@ -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) } diff --git a/pkg/drivers/cdp/templates/styles.go b/pkg/drivers/cdp/templates/styles.go index c0830548..cb68057b 100644 --- a/pkg/drivers/cdp/templates/styles.go +++ b/pkg/drivers/cdp/templates/styles.go @@ -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) } diff --git a/pkg/drivers/cdp/templates/url.go b/pkg/drivers/cdp/templates/url.go index 5ce1562d..d3ee201f 100644 --- a/pkg/drivers/cdp/templates/url.go +++ b/pkg/drivers/cdp/templates/url.go @@ -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) } diff --git a/pkg/drivers/cdp/templates/value.go b/pkg/drivers/cdp/templates/value.go new file mode 100644 index 00000000..778e5963 --- /dev/null +++ b/pkg/drivers/cdp/templates/value.go @@ -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) +} diff --git a/pkg/drivers/cdp/templates/wait.go b/pkg/drivers/cdp/templates/wait.go new file mode 100644 index 00000000..97b813ec --- /dev/null +++ b/pkg/drivers/cdp/templates/wait.go @@ -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) +} diff --git a/pkg/drivers/cdp/templates/wait_by_selector.go b/pkg/drivers/cdp/templates/wait_by_selector.go deleted file mode 100644 index 2cb95c1e..00000000 --- a/pkg/drivers/cdp/templates/wait_by_selector.go +++ /dev/null @@ -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), - ) -} diff --git a/pkg/drivers/cdp/templates/wait_by_selector_all.go b/pkg/drivers/cdp/templates/wait_by_selector_all.go deleted file mode 100644 index ec770580..00000000 --- a/pkg/drivers/cdp/templates/wait_by_selector_all.go +++ /dev/null @@ -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), - ) -} diff --git a/pkg/drivers/cdp/templates/xpath.go b/pkg/drivers/cdp/templates/xpath.go index 965780f5..f208d65a 100644 --- a/pkg/drivers/cdp/templates/xpath.go +++ b/pkg/drivers/cdp/templates/xpath.go @@ -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) } diff --git a/pkg/drivers/common/getter.go b/pkg/drivers/common/getter.go index fa4a5462..cded8a2b 100644 --- a/pkg/drivers/common/getter.go +++ b/pkg/drivers/common/getter.go @@ -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) diff --git a/pkg/drivers/http/document.go b/pkg/drivers/http/document.go index 5578a6e3..30710949 100644 --- a/pkg/drivers/http/document.go +++ b/pkg/drivers/http/document.go @@ -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 } diff --git a/pkg/drivers/http/document_test.go b/pkg/drivers/http/document_test.go index 8c00bb2d..b73177e7 100644 --- a/pkg/drivers/http/document_test.go +++ b/pkg/drivers/http/document_test.go @@ -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) }) }) } diff --git a/pkg/drivers/http/element.go b/pkg/drivers/http/element.go index db361a32..c5a9bb52 100644 --- a/pkg/drivers/http/element.go +++ b/pkg/drivers/http/element.go @@ -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) diff --git a/pkg/drivers/http/element_test.go b/pkg/drivers/http/element_test.go index f1c65737..ba8a658d 100644 --- a/pkg/drivers/http/element_test.go +++ b/pkg/drivers/http/element_test.go @@ -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") }) diff --git a/pkg/drivers/scroll.go b/pkg/drivers/scroll.go index 7f031206..d22fc6f3 100644 --- a/pkg/drivers/scroll.go +++ b/pkg/drivers/scroll.go @@ -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"` } diff --git a/pkg/drivers/value.go b/pkg/drivers/value.go index 1ecd72a0..5dafb6dd 100644 --- a/pkg/drivers/value.go +++ b/pkg/drivers/value.go @@ -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 diff --git a/pkg/stdlib/html/clear.go b/pkg/stdlib/html/clear.go index 204d9f3e..2994090f 100644 --- a/pkg/stdlib/html/clear.go +++ b/pkg/stdlib/html/clear.go @@ -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])) } diff --git a/pkg/stdlib/html/focus.go b/pkg/stdlib/html/focus.go index f96a6ba2..923853c1 100644 --- a/pkg/stdlib/html/focus.go +++ b/pkg/stdlib/html/focus.go @@ -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])) } diff --git a/pkg/stdlib/html/hover.go b/pkg/stdlib/html/hover.go index c6e45a67..85616ff8 100644 --- a/pkg/stdlib/html/hover.go +++ b/pkg/stdlib/html/hover.go @@ -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])) } diff --git a/pkg/stdlib/html/navigate.go b/pkg/stdlib/html/navigate.go index 7f1148ae..6bc86fdd 100644 --- a/pkg/stdlib/html/navigate.go +++ b/pkg/stdlib/html/navigate.go @@ -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)) } diff --git a/pkg/stdlib/html/press.go b/pkg/stdlib/html/press.go index 68317ff1..500468ea 100644 --- a/pkg/stdlib/html/press.go +++ b/pkg/stdlib/html/press.go @@ -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) } diff --git a/pkg/stdlib/html/press_selector.go b/pkg/stdlib/html/press_selector.go index 625fbae3..b917b6a1 100644 --- a/pkg/stdlib/html/press_selector.go +++ b/pkg/stdlib/html/press_selector.go @@ -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) } diff --git a/pkg/stdlib/html/scroll_bottom.go b/pkg/stdlib/html/scroll_bottom.go index f2b946e5..98905bad 100644 --- a/pkg/stdlib/html/scroll_bottom.go +++ b/pkg/stdlib/html/scroll_bottom.go @@ -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) } diff --git a/pkg/stdlib/html/scroll_element.go b/pkg/stdlib/html/scroll_element.go index b802bd76..cde278c7 100644 --- a/pkg/stdlib/html/scroll_element.go +++ b/pkg/stdlib/html/scroll_element.go @@ -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( diff --git a/pkg/stdlib/html/scroll_top.go b/pkg/stdlib/html/scroll_top.go index c3b79bb1..c9d24f59 100644 --- a/pkg/stdlib/html/scroll_top.go +++ b/pkg/stdlib/html/scroll_top.go @@ -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) } diff --git a/pkg/stdlib/html/scroll_xy.go b/pkg/stdlib/html/scroll_xy.go index b1419168..6f0de6ec 100644 --- a/pkg/stdlib/html/scroll_xy.go +++ b/pkg/stdlib/html/scroll_xy.go @@ -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) } diff --git a/pkg/stdlib/html/wait_attr.go b/pkg/stdlib/html/wait_attr.go index 57c8364d..9f0d208e 100644 --- a/pkg/stdlib/html/wait_attr.go +++ b/pkg/stdlib/html/wait_attr.go @@ -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) } diff --git a/pkg/stdlib/html/wait_attr_all.go b/pkg/stdlib/html/wait_attr_all.go index bf3d4d1f..b811434a 100644 --- a/pkg/stdlib/html/wait_attr_all.go +++ b/pkg/stdlib/html/wait_attr_all.go @@ -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) } diff --git a/pkg/stdlib/html/wait_class.go b/pkg/stdlib/html/wait_class.go index 39d75ee1..1d396ff9 100644 --- a/pkg/stdlib/html/wait_class.go +++ b/pkg/stdlib/html/wait_class.go @@ -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) } diff --git a/pkg/stdlib/html/wait_class_all.go b/pkg/stdlib/html/wait_class_all.go index b98deeb8..a35f858c 100644 --- a/pkg/stdlib/html/wait_class_all.go +++ b/pkg/stdlib/html/wait_class_all.go @@ -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) } diff --git a/pkg/stdlib/html/wait_element.go b/pkg/stdlib/html/wait_element.go index 736e1f58..e2d12e34 100644 --- a/pkg/stdlib/html/wait_element.go +++ b/pkg/stdlib/html/wait_element.go @@ -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) } diff --git a/pkg/stdlib/html/wait_navigation.go b/pkg/stdlib/html/wait_navigation.go index af43f0b5..b0cbe9ca 100644 --- a/pkg/stdlib/html/wait_navigation.go +++ b/pkg/stdlib/html/wait_navigation.go @@ -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) { diff --git a/pkg/stdlib/html/wait_style.go b/pkg/stdlib/html/wait_style.go index 2accea3f..b05c2f7e 100644 --- a/pkg/stdlib/html/wait_style.go +++ b/pkg/stdlib/html/wait_style.go @@ -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) } diff --git a/pkg/stdlib/html/wait_style_all.go b/pkg/stdlib/html/wait_style_all.go index ad84bad5..02964c3e 100644 --- a/pkg/stdlib/html/wait_style_all.go +++ b/pkg/stdlib/html/wait_style_all.go @@ -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) }