From a6b51a1f40fe8e61483f8993cbcc10dd436af120 Mon Sep 17 00:00:00 2001 From: Tim Voronov Date: Tue, 25 Sep 2018 19:04:07 -0400 Subject: [PATCH] #23 Added NAVIGATE --- docs/examples/navigate.fql | 13 ++++ pkg/runtime/values/helpers.go | 13 ++++ pkg/stdlib/html/actions.go | 39 +++++++++- pkg/stdlib/html/driver/browser/document.go | 36 +++++----- pkg/stdlib/html/driver/browser/eval/eval.go | 79 +++++++++++++++++++-- pkg/stdlib/html/driver/browser/helpers.go | 28 +++++++- pkg/stdlib/html/lib.go | 1 + 7 files changed, 181 insertions(+), 28 deletions(-) create mode 100644 docs/examples/navigate.fql diff --git a/docs/examples/navigate.fql b/docs/examples/navigate.fql new file mode 100644 index 00000000..70f4d53c --- /dev/null +++ b/docs/examples/navigate.fql @@ -0,0 +1,13 @@ +LET doc = DOCUMENT("https://github.com/", true) + +LET main = ELEMENT(doc, '.application-main') +LOG('innerText:start') +LET mainTxt = main.innerText +LOG('innerText:end') + +NAVIGATE(doc, "https://github.com/features") + +LET features = ELEMENT(doc, '.application-main') +LET featuresTxt = features.innerText + +RETURN mainTxt == featuresTxt diff --git a/pkg/runtime/values/helpers.go b/pkg/runtime/values/helpers.go index 8d34c05c..8eca5f16 100644 --- a/pkg/runtime/values/helpers.go +++ b/pkg/runtime/values/helpers.go @@ -1,6 +1,7 @@ package values import ( + "encoding/json" "github.com/MontFerret/ferret/pkg/runtime/core" "time" ) @@ -210,6 +211,18 @@ func Parse(input interface{}) core.Value { return None } +func Unmarshal(value json.RawMessage) (core.Value, error) { + var o interface{} + + err := json.Unmarshal(value, &o) + + if err != nil { + return None, err + } + + return Parse(o), nil +} + func ToBoolean(input core.Value) core.Value { switch input.Type() { case core.BooleanType: diff --git a/pkg/stdlib/html/actions.go b/pkg/stdlib/html/actions.go index 418da28c..7a7e5b39 100644 --- a/pkg/stdlib/html/actions.go +++ b/pkg/stdlib/html/actions.go @@ -8,7 +8,9 @@ import ( ) /* - * + * Dispatches click event on a given element + * @param source (Document | Element) - Event source. + * @param selector (String, optional) - Optional selector. Only used when a document instance is passed. */ func Click(_ context.Context, args ...core.Value) (core.Value, error) { err := core.ValidateArgs(args, 1, 2) @@ -54,3 +56,38 @@ func Click(_ context.Context, args ...core.Value) (core.Value, error) { return doc.ClickBySelector(values.NewString(selector)) } } + +/* + * Navigates a document to a new resource. + * The operation blocks the execution until the page gets loaded. + * Which means there is no need in WAIT_NAVIGATION function. + * @param doc (Document) - Target document. + * @param url (String) - Target url to navigate. + */ +func Navigate(_ context.Context, args ...core.Value) (core.Value, error) { + err := core.ValidateArgs(args, 2, 2) + + if err != nil { + return values.None, err + } + + err = core.ValidateType(args[0], core.HtmlDocumentType) + + if err != nil { + return values.None, err + } + + err = core.ValidateType(args[1], core.StringType) + + if err != nil { + return values.None, err + } + + doc, ok := args[0].(*browser.HtmlDocument) + + if !ok { + return values.False, core.Error(core.ErrInvalidType, "expected dynamic document") + } + + return values.None, doc.Navigate(args[1].(values.String)) +} diff --git a/pkg/stdlib/html/driver/browser/document.go b/pkg/stdlib/html/driver/browser/document.go index af0aa198..4911c653 100644 --- a/pkg/stdlib/html/driver/browser/document.go +++ b/pkg/stdlib/html/driver/browser/document.go @@ -14,6 +14,7 @@ import ( "github.com/mafredri/cdp/protocol/emulation" "github.com/mafredri/cdp/protocol/page" "github.com/mafredri/cdp/rpcc" + "github.com/pkg/errors" "strings" "sync" "time" @@ -43,7 +44,7 @@ func LoadHtmlDocument( client := cdp.NewClient(conn) - err := RunBatch( + err := runBatch( func() error { return client.Page.Enable(ctx) }, @@ -96,25 +97,9 @@ func LoadHtmlDocument( return NewHtmlDocument(conn, client, root, broker), nil } -func waitForLoadEvent(ctx context.Context, client *cdp.Client) error { - loadEventFired, err := client.Page.LoadEventFired(ctx) - - if err != nil { - return err - } - - _, err = loadEventFired.Recv() - - if err != nil { - return err - } - - return loadEventFired.Close() -} - func getRootElement(client *cdp.Client) (dom.Node, error) { args := dom.NewGetDocumentArgs() - args.Depth = PointerInt(1) // lets load the entire document + args.Depth = pointerInt(1) // lets load the entire document d, err := client.DOM.GetDocument(context.Background(), args) @@ -419,3 +404,18 @@ func (doc *HtmlDocument) WaitForNavigation(timeout values.Int) error { } } } + +func (doc *HtmlDocument) Navigate(url values.String) error { + ctx := context.Background() + repl, err := doc.client.Page.Navigate(ctx, page.NewNavigateArgs(url.String())) + + if err != nil { + return err + } + + if repl.ErrorText != nil { + return errors.New(*repl.ErrorText) + } + + return waitForLoadEvent(ctx, doc.client) +} diff --git a/pkg/stdlib/html/driver/browser/eval/eval.go b/pkg/stdlib/html/driver/browser/eval/eval.go index d7754749..a3e42c52 100644 --- a/pkg/stdlib/html/driver/browser/eval/eval.go +++ b/pkg/stdlib/html/driver/browser/eval/eval.go @@ -2,11 +2,11 @@ package eval import ( "context" - "encoding/json" "fmt" "github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime/values" "github.com/mafredri/cdp" + "github.com/mafredri/cdp/protocol/dom" "github.com/mafredri/cdp/protocol/runtime" ) @@ -36,15 +36,82 @@ func Eval(client *cdp.Client, exp string, ret bool, async bool) (core.Value, err } if out.Result.Type != "undefined" { - var o interface{} + return values.Unmarshal(out.Result.Value) + } - err := json.Unmarshal(out.Result.Value, &o) + return Unmarshal(&out.Result) +} - if err != nil { - return values.None, core.Error(core.ErrUnexpected, err.Error()) +func Property( + ctx context.Context, + client *cdp.Client, + id dom.NodeID, + propName string, +) (core.Value, error) { + // get a ref to remote object representing the node + obj, err := client.DOM.ResolveNode( + ctx, + dom.NewResolveNodeArgs(). + SetNodeID(id), + ) + + if err != nil { + return values.None, err + } + + if obj.Object.ObjectID == nil { + return values.None, core.Error(core.ErrNotFound, fmt.Sprintf("element %d", id)) + } + + defer client.Runtime.ReleaseObject(ctx, runtime.NewReleaseObjectArgs(*obj.Object.ObjectID)) + + res, err := client.Runtime.GetProperties( + ctx, + runtime.NewGetPropertiesArgs(*obj.Object.ObjectID), + ) + + if err != nil { + return values.None, err + } + + if res.ExceptionDetails != nil { + return values.None, res.ExceptionDetails + } + + // all props + if propName == "" { + var arr *values.Array + arr = values.NewArray(len(res.Result)) + + for _, prop := range res.Result { + val, err := Unmarshal(prop.Value) + + if err != nil { + return values.None, err + } + + arr.Push(val) } - return values.Parse(o), nil + return arr, nil + } + + for _, prop := range res.Result { + if prop.Name == propName { + return Unmarshal(prop.Value) + } + } + + return values.None, nil +} + +func Unmarshal(obj *runtime.RemoteObject) (core.Value, error) { + if obj == nil { + return values.None, nil + } + + if obj.Type != "undefined" { + return values.Unmarshal(obj.Value) } return values.None, nil diff --git a/pkg/stdlib/html/driver/browser/helpers.go b/pkg/stdlib/html/driver/browser/helpers.go index 2bb9ae9a..9e6eaf94 100644 --- a/pkg/stdlib/html/driver/browser/helpers.go +++ b/pkg/stdlib/html/driver/browser/helpers.go @@ -1,16 +1,18 @@ package browser import ( + "context" + "github.com/mafredri/cdp" "golang.org/x/sync/errgroup" ) -func PointerInt(input int) *int { +func pointerInt(input int) *int { return &input } -type BatchFunc = func() error +type batchFunc = func() error -func RunBatch(funcs ...BatchFunc) error { +func runBatch(funcs ...batchFunc) error { eg := errgroup.Group{} for _, f := range funcs { @@ -19,3 +21,23 @@ func RunBatch(funcs ...BatchFunc) error { return eg.Wait() } + +func contextWithTimeout() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.Background(), DefaultTimeout) +} + +func waitForLoadEvent(ctx context.Context, client *cdp.Client) error { + loadEventFired, err := client.Page.LoadEventFired(ctx) + + if err != nil { + return err + } + + _, err = loadEventFired.Recv() + + if err != nil { + return err + } + + return loadEventFired.Close() +} diff --git a/pkg/stdlib/html/lib.go b/pkg/stdlib/html/lib.go index bb8dd6c3..84b9593b 100644 --- a/pkg/stdlib/html/lib.go +++ b/pkg/stdlib/html/lib.go @@ -11,5 +11,6 @@ func NewLib() map[string]core.Function { "WAIT_ELEMENT": WaitElement, "WAIT_NAVIGATION": WaitNavigation, "CLICK": Click, + "NAVIGATE": Navigate, } }