From e64ad4ec0e07b7644432dc5fa1e43be32e38e6ee Mon Sep 17 00:00:00 2001 From: Tim Voronov Date: Sat, 6 Oct 2018 22:33:39 -0400 Subject: [PATCH] Feature/#33 wait class function (#63) * #33 Lib cleanup. Added WAIT_CLASS and WAIT_CLASS_ALL functions * #33 Fixed attr update * #33 HTMLElement.WaitForClass * #33 Updated HTMLDocument.WaitForClass --- examples/wait_class.fql | 8 + examples/wait_class_doc.fql | 6 + pkg/compiler/compiler_test.go | 44 ++-- pkg/runtime/core/errors.go | 10 + pkg/runtime/values/html.go | 8 + pkg/stdlib/arrays/append.go | 2 +- pkg/stdlib/arrays/first.go | 2 +- pkg/stdlib/arrays/last.go | 2 +- pkg/stdlib/arrays/nth.go | 2 +- pkg/stdlib/arrays/position.go | 4 +- pkg/stdlib/arrays/push.go | 4 +- pkg/stdlib/arrays/remove_value.go | 2 +- pkg/stdlib/arrays/slice.go | 2 +- pkg/stdlib/arrays/unshift.go | 2 +- pkg/stdlib/html/actions.go | 188 ----------------- pkg/stdlib/html/click.go | 58 ++++++ pkg/stdlib/html/click_all.go | 39 ++++ pkg/stdlib/html/data.go | 132 ------------ pkg/stdlib/html/document.go | 81 +++----- pkg/stdlib/html/document_parse.go | 39 ++++ pkg/stdlib/html/driver/common/attrs.go | 11 +- pkg/stdlib/html/driver/common/lazy.go | 55 ++++- pkg/stdlib/html/driver/driver.go | 12 +- pkg/stdlib/html/driver/dynamic/document.go | 134 ++++++++++-- pkg/stdlib/html/driver/dynamic/dynamic.go | 4 +- pkg/stdlib/html/driver/dynamic/element.go | 195 ++++++++++-------- pkg/stdlib/html/driver/dynamic/events/wait.go | 51 +++-- pkg/stdlib/html/driver/dynamic/helpers.go | 8 +- pkg/stdlib/html/driver/static/document.go | 56 ++++- pkg/stdlib/html/driver/static/element.go | 1 - pkg/stdlib/html/driver/static/element_test.go | 2 +- pkg/stdlib/html/driver/static/static.go | 5 +- pkg/stdlib/html/element.go | 55 +++-- pkg/stdlib/html/elements.go | 24 +++ pkg/stdlib/html/events.go | 74 ------- pkg/stdlib/html/inner_html.go | 38 ++++ pkg/stdlib/html/inner_html_all.go | 38 ++++ pkg/stdlib/html/inner_text.go | 38 ++++ pkg/stdlib/html/inner_text_all.go | 38 ++++ pkg/stdlib/html/input.go | 72 +++++++ pkg/stdlib/html/lib.go | 13 +- pkg/stdlib/html/navigate.go | 43 ++++ pkg/stdlib/html/wait_class.go | 87 ++++++++ pkg/stdlib/html/wait_class_all.go | 67 ++++++ pkg/stdlib/html/wait_element.go | 51 +++++ pkg/stdlib/html/wait_navigation.go | 48 +++++ pkg/stdlib/strings/concat.go | 4 +- pkg/stdlib/strings/json.go | 4 +- 48 files changed, 1193 insertions(+), 670 deletions(-) create mode 100644 examples/wait_class.fql create mode 100644 examples/wait_class_doc.fql delete mode 100644 pkg/stdlib/html/actions.go create mode 100644 pkg/stdlib/html/click.go create mode 100644 pkg/stdlib/html/click_all.go delete mode 100644 pkg/stdlib/html/data.go create mode 100644 pkg/stdlib/html/document_parse.go create mode 100644 pkg/stdlib/html/elements.go delete mode 100644 pkg/stdlib/html/events.go create mode 100644 pkg/stdlib/html/inner_html.go create mode 100644 pkg/stdlib/html/inner_html_all.go create mode 100644 pkg/stdlib/html/inner_text.go create mode 100644 pkg/stdlib/html/inner_text_all.go create mode 100644 pkg/stdlib/html/input.go create mode 100644 pkg/stdlib/html/navigate.go create mode 100644 pkg/stdlib/html/wait_class.go create mode 100644 pkg/stdlib/html/wait_class_all.go create mode 100644 pkg/stdlib/html/wait_element.go create mode 100644 pkg/stdlib/html/wait_navigation.go diff --git a/examples/wait_class.fql b/examples/wait_class.fql new file mode 100644 index 00000000..f179989c --- /dev/null +++ b/examples/wait_class.fql @@ -0,0 +1,8 @@ +LET doc = DOCUMENT("http://getbootstrap.com/docs/4.1/components/collapse/", true) + +LET el = ELEMENT(doc, "#collapseTwo") + +CLICK(doc, "#headingTwo > h5 > button") +WAIT_CLASS(el, "show") + +RETURN TRUE \ No newline at end of file diff --git a/examples/wait_class_doc.fql b/examples/wait_class_doc.fql new file mode 100644 index 00000000..003454f0 --- /dev/null +++ b/examples/wait_class_doc.fql @@ -0,0 +1,6 @@ +LET doc = DOCUMENT("http://getbootstrap.com/docs/4.1/components/collapse/", true) + +CLICK(doc, "#headingTwo > h5 > button") +WAIT_CLASS(doc, "#collapseTwo", "show") + +RETURN TRUE \ No newline at end of file diff --git a/pkg/compiler/compiler_test.go b/pkg/compiler/compiler_test.go index c93a74ad..b76900b5 100644 --- a/pkg/compiler/compiler_test.go +++ b/pkg/compiler/compiler_test.go @@ -1774,29 +1774,21 @@ func TestParam(t *testing.T) { }) } -//func TestHtml(t *testing.T) { -// Convey("Should load a document", t, func() { -// c := compiler.New() -// -// out, err := c.MustCompile(` -//LET doc = DOCUMENT("https://github.com/", true) -//LET main = ELEMENT(doc, '.application-main') -//LET mainTxt = main.innerText -// -//NAVIGATE(doc, "https://github.com/features") -// -//LET features = ELEMENT(doc, '.application-main') -//LET featuresTxt = features.innerText -// -//LOG("featuresTxt:", featuresTxt) -// -//RETURN mainTxt == featuresTxt -// -// -// `).Run(context.Background()) -// -// So(err, ShouldBeNil) -// -// So(string(out), ShouldEqual, `"int"`) -// }) -//} +func TestHtml(t *testing.T) { + Convey("Should load a document", t, func() { + c := compiler.New() + + out, err := c.MustCompile(` +LET doc = DOCUMENT("http://getbootstrap.com/docs/4.1/components/collapse/", true) + +CLICK(doc, "#headingTwo > h5 > button") +WAIT_CLASS(doc, "#collapseTwo", "bar") + +RETURN TRUE + `).Run(context.Background()) + + So(err, ShouldBeNil) + + So(string(out), ShouldEqual, `"int"`) + }) +} diff --git a/pkg/runtime/core/errors.go b/pkg/runtime/core/errors.go index 34558ae2..54430945 100644 --- a/pkg/runtime/core/errors.go +++ b/pkg/runtime/core/errors.go @@ -49,3 +49,13 @@ func TypeError(actual Type, expected ...Type) error { func Error(err error, msg string) error { return errors.Errorf("%s: %s", err.Error(), msg) } + +func Errors(err ...error) error { + message := "" + + for _, e := range err { + message += ": " + e.Error() + } + + return errors.New(message) +} diff --git a/pkg/runtime/values/html.go b/pkg/runtime/values/html.go index 588618eb..dbded7e9 100644 --- a/pkg/runtime/values/html.go +++ b/pkg/runtime/values/html.go @@ -35,5 +35,13 @@ type ( HTMLNode URL() core.Value + + InnerHTMLBySelector(selector String) String + + InnerHTMLBySelectorAll(selector String) *Array + + InnerTextBySelector(selector String) String + + InnerTextBySelectorAll(selector String) *Array } ) diff --git a/pkg/stdlib/arrays/append.go b/pkg/stdlib/arrays/append.go index 7fea1d70..821dea22 100644 --- a/pkg/stdlib/arrays/append.go +++ b/pkg/stdlib/arrays/append.go @@ -10,7 +10,7 @@ import ( * Appends a new item to an array and returns a new array with a given element. * If ``uniqueOnly`` is set to true, then will add the item only if it's unique. * @param arr (Array) - Target array. - * @param item (Value) - Target value to add. + * @param item (Read) - Target value to add. * @returns arr (Array) - New array. */ func Append(_ context.Context, args ...core.Value) (core.Value, error) { diff --git a/pkg/stdlib/arrays/first.go b/pkg/stdlib/arrays/first.go index dac5827e..07f4a6d5 100644 --- a/pkg/stdlib/arrays/first.go +++ b/pkg/stdlib/arrays/first.go @@ -9,7 +9,7 @@ import ( /* * Returns a first element from a given array. * @param arr (Array) - Target array. - * @returns element (Value) - First element in a given array. + * @returns element (Read) - First element in a given array. */ func First(_ context.Context, args ...core.Value) (core.Value, error) { err := core.ValidateArgs(args, 1, 1) diff --git a/pkg/stdlib/arrays/last.go b/pkg/stdlib/arrays/last.go index d579388e..b56c38c9 100644 --- a/pkg/stdlib/arrays/last.go +++ b/pkg/stdlib/arrays/last.go @@ -9,7 +9,7 @@ import ( /* * Returns the last element of an array. * @param array (Array) - The target array. - * @returns (Value) - Last element of an array. + * @returns (Read) - Last element of an array. */ func Last(_ context.Context, args ...core.Value) (core.Value, error) { err := core.ValidateArgs(args, 1, 1) diff --git a/pkg/stdlib/arrays/nth.go b/pkg/stdlib/arrays/nth.go index 0d39167e..f8cf2bbb 100644 --- a/pkg/stdlib/arrays/nth.go +++ b/pkg/stdlib/arrays/nth.go @@ -11,7 +11,7 @@ import ( * It is the same as anyArray[position] for positive positions, but does not support negative positions. * @param array (Array) - An array with elements of arbitrary type. * @param index (Int) - Position of desired element in array, positions start at 0. - * @returns (Value) - The array element at the given position. + * @returns (Read) - The array element at the given position. * If position is negative or beyond the upper bound of the array, then NONE will be returned. */ func Nth(_ context.Context, args ...core.Value) (core.Value, error) { diff --git a/pkg/stdlib/arrays/position.go b/pkg/stdlib/arrays/position.go index 3ef14697..ef8c71e7 100644 --- a/pkg/stdlib/arrays/position.go +++ b/pkg/stdlib/arrays/position.go @@ -9,8 +9,8 @@ import ( /* * Returns a value indicating whether an element is contained in array. Optionally returns its position. * @param array (Array) - The source array. - * @param value (Value) - The target value. - * @param returnIndex (Boolean, optional) - Value which indicates whether to return item's position. + * @param value (Read) - The target value. + * @param returnIndex (Boolean, optional) - Read which indicates whether to return item's position. */ func Position(_ context.Context, args ...core.Value) (core.Value, error) { err := core.ValidateArgs(args, 2, 3) diff --git a/pkg/stdlib/arrays/push.go b/pkg/stdlib/arrays/push.go index ee59f139..d7f1e92c 100644 --- a/pkg/stdlib/arrays/push.go +++ b/pkg/stdlib/arrays/push.go @@ -9,8 +9,8 @@ import ( /* * Create a new array with appended value. * @param array (Array) - Source array. - * @param value (Value) - Target value. - * @param unique (Boolean, optional) - Value indicating whether to do uniqueness check. + * @param value (Read) - Target value. + * @param unique (Boolean, optional) - Read indicating whether to do uniqueness check. * @returns (Array) - A new array with appended value. */ func Push(_ context.Context, args ...core.Value) (core.Value, error) { diff --git a/pkg/stdlib/arrays/remove_value.go b/pkg/stdlib/arrays/remove_value.go index c0674585..96b8f779 100644 --- a/pkg/stdlib/arrays/remove_value.go +++ b/pkg/stdlib/arrays/remove_value.go @@ -10,7 +10,7 @@ import ( * Returns a new array with removed all occurrences of value in a given array. * Optionally with a limit to the number of removals. * @param array (Array) - Source array. - * @param value (Value) - Target value. + * @param value (Read) - Target value. * @param limit (Int, optional) - A limit to the number of removals. * @returns (Array) - A new array with removed all occurrences of value in a given array. */ diff --git a/pkg/stdlib/arrays/slice.go b/pkg/stdlib/arrays/slice.go index cff981ec..fdda740a 100644 --- a/pkg/stdlib/arrays/slice.go +++ b/pkg/stdlib/arrays/slice.go @@ -10,7 +10,7 @@ import ( * Returns a new sliced array. * @param array (Array) - Source array. * @param start (Int) - Start position of extraction. - * @param length (Int, optional) - Value indicating how many elements to extract. + * @param length (Int, optional) - Read indicating how many elements to extract. * @returns (Array) - Sliced array. */ func Slice(_ context.Context, args ...core.Value) (core.Value, error) { diff --git a/pkg/stdlib/arrays/unshift.go b/pkg/stdlib/arrays/unshift.go index 7bdf7b2a..f6ede184 100644 --- a/pkg/stdlib/arrays/unshift.go +++ b/pkg/stdlib/arrays/unshift.go @@ -9,7 +9,7 @@ import ( /* * Prepends value to a given array. * @param array (Array) - Target array. - * @param value (Value) - Target value to prepend. + * @param value (Read) - Target value to prepend. * @param unique (Boolean, optional) - Optional value indicating whether a value must be unique to be prepended. * Default is false. * @returns (Array) - New array with prepended value. diff --git a/pkg/stdlib/html/actions.go b/pkg/stdlib/html/actions.go deleted file mode 100644 index 2a2f78f6..00000000 --- a/pkg/stdlib/html/actions.go +++ /dev/null @@ -1,188 +0,0 @@ -package html - -import ( - "context" - "github.com/MontFerret/ferret/pkg/runtime/core" - "github.com/MontFerret/ferret/pkg/runtime/values" - "github.com/MontFerret/ferret/pkg/stdlib/html/driver/dynamic" -) - -/* - * 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) - - if err != nil { - return values.False, err - } - - // CLICK(el) - if len(args) == 1 { - arg1 := args[0] - - err := core.ValidateType(arg1, core.HTMLElementType) - - if err != nil { - return values.False, err - } - - el, ok := arg1.(*dynamic.HTMLElement) - - if !ok { - return values.False, core.Error(core.ErrInvalidType, "expected dynamic element") - } - - return el.Click() - } - - // CLICK(doc, selector) - arg1 := args[0] - selector := args[1].String() - - err = core.ValidateType(arg1, core.HTMLDocumentType) - - if err != nil { - return values.None, err - } - - doc, ok := arg1.(*dynamic.HTMLDocument) - - if !ok { - return values.False, core.Error(core.ErrInvalidType, "expected dynamic document") - } - - return doc.ClickBySelector(values.NewString(selector)) -} - -/* - * Dispatches click event on all matched element - * @param source (Document) - Document. - * @param selector (String) - Selector. - * @returns (Boolean) - Returns true if matched at least one element. - */ -func ClickAll(_ context.Context, args ...core.Value) (core.Value, error) { - err := core.ValidateArgs(args, 2, 2) - - if err != nil { - return values.False, err - } - - arg1 := args[0] - selector := args[1].String() - - err = core.ValidateType(arg1, core.HTMLDocumentType) - - if err != nil { - return values.None, err - } - - doc, ok := arg1.(*dynamic.HTMLDocument) - - if !ok { - return values.False, core.Error(core.ErrInvalidType, "expected dynamic document") - } - - return doc.ClickBySelectorAll(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].(*dynamic.HTMLDocument) - - if !ok { - return values.False, core.Error(core.ErrInvalidType, "expected dynamic document") - } - - return values.None, doc.Navigate(args[1].(values.String)) -} - -/* - * Sends a value to an underlying input element. - * @param source (Document | Element) - Event target. - * @param valueOrSelector (String) - Selector or a value. - * @param value (String) - Target value. - * @returns (Boolean) - Returns true if an element was found. - */ -func Input(_ context.Context, args ...core.Value) (core.Value, error) { - err := core.ValidateArgs(args, 2, 3) - - if err != nil { - return values.None, err - } - - // TYPE(el, "foobar") - if len(args) == 2 { - arg1 := args[0] - - err := core.ValidateType(arg1, core.HTMLElementType) - - if err != nil { - return values.False, err - } - - el, ok := arg1.(*dynamic.HTMLElement) - - if !ok { - return values.False, core.Error(core.ErrInvalidType, "expected dynamic element") - } - - err = el.Input(args[1]) - - if err != nil { - return values.False, err - } - - return values.True, nil - } - - arg1 := args[0] - - err = core.ValidateType(arg1, core.HTMLDocumentType) - - if err != nil { - return values.False, err - } - - arg2 := args[1] - - err = core.ValidateType(arg2, core.StringType) - - if err != nil { - return values.False, err - } - - doc, ok := arg1.(*dynamic.HTMLDocument) - - if !ok { - return values.False, core.Error(core.ErrInvalidType, "expected dynamic document") - } - - return doc.InputBySelector(arg2.(values.String), args[2]) -} diff --git a/pkg/stdlib/html/click.go b/pkg/stdlib/html/click.go new file mode 100644 index 00000000..5907833d --- /dev/null +++ b/pkg/stdlib/html/click.go @@ -0,0 +1,58 @@ +package html + +import ( + "context" + "github.com/MontFerret/ferret/pkg/runtime/core" + "github.com/MontFerret/ferret/pkg/runtime/values" + "github.com/MontFerret/ferret/pkg/stdlib/html/driver/dynamic" +) + +/* + * 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) + + if err != nil { + return values.False, err + } + + // CLICK(el) + if len(args) == 1 { + arg1 := args[0] + + err := core.ValidateType(arg1, core.HTMLElementType) + + if err != nil { + return values.False, err + } + + el, ok := arg1.(*dynamic.HTMLElement) + + if !ok { + return values.False, core.Errors(core.ErrInvalidType, ErrNotDynamic) + } + + return el.Click() + } + + // CLICK(doc, selector) + arg1 := args[0] + selector := args[1].String() + + err = core.ValidateType(arg1, core.HTMLDocumentType) + + if err != nil { + return values.None, err + } + + doc, ok := arg1.(*dynamic.HTMLDocument) + + if !ok { + return values.False, core.Errors(core.ErrInvalidType, ErrNotDynamic) + } + + return doc.ClickBySelector(values.NewString(selector)) +} diff --git a/pkg/stdlib/html/click_all.go b/pkg/stdlib/html/click_all.go new file mode 100644 index 00000000..49488366 --- /dev/null +++ b/pkg/stdlib/html/click_all.go @@ -0,0 +1,39 @@ +package html + +import ( + "context" + "github.com/MontFerret/ferret/pkg/runtime/core" + "github.com/MontFerret/ferret/pkg/runtime/values" + "github.com/MontFerret/ferret/pkg/stdlib/html/driver/dynamic" +) + +/* + * Dispatches click event on all matched element + * @param source (Document) - Document. + * @param selector (String) - Selector. + * @returns (Boolean) - Returns true if matched at least one element. + */ +func ClickAll(_ context.Context, args ...core.Value) (core.Value, error) { + err := core.ValidateArgs(args, 2, 2) + + if err != nil { + return values.False, err + } + + arg1 := args[0] + selector := args[1].String() + + err = core.ValidateType(arg1, core.HTMLDocumentType) + + if err != nil { + return values.None, err + } + + doc, ok := arg1.(*dynamic.HTMLDocument) + + if !ok { + return values.False, core.Errors(core.ErrInvalidType, ErrNotDynamic) + } + + return doc.ClickBySelectorAll(values.NewString(selector)) +} diff --git a/pkg/stdlib/html/data.go b/pkg/stdlib/html/data.go deleted file mode 100644 index 1ee0db37..00000000 --- a/pkg/stdlib/html/data.go +++ /dev/null @@ -1,132 +0,0 @@ -package html - -import ( - "context" - "github.com/MontFerret/ferret/pkg/runtime/core" - "github.com/MontFerret/ferret/pkg/runtime/values" - "github.com/MontFerret/ferret/pkg/stdlib/html/driver/dynamic" -) - -/* - * Returns inner html of a matched element - * @param doc (Document) - Document - * @param selector (String) - Selector - * @returns str (String) - String value of inner html. - */ -func InnerHTML(_ context.Context, args ...core.Value) (core.Value, error) { - err := core.ValidateArgs(args, 2, 2) - - if err != nil { - return values.EmptyString, err - } - - arg1 := args[0] - selector := args[1].String() - - err = core.ValidateType(arg1, core.HTMLDocumentType) - - if err != nil { - return values.None, err - } - - doc, ok := arg1.(*dynamic.HTMLDocument) - - if !ok { - return values.EmptyString, core.Error(core.ErrInvalidType, "expected dynamic document") - } - - return doc.InnerHTMLBySelector(values.NewString(selector)) -} - -/* - * Returns inner html of all matched elements. - * @param doc (Document) - Document - * @param selector (String) - Selector - * @returns array (Array) - Array of string values. - */ -func InnerHTMLAll(_ context.Context, args ...core.Value) (core.Value, error) { - err := core.ValidateArgs(args, 2, 2) - - if err != nil { - return values.EmptyString, err - } - - arg1 := args[0] - selector := args[1].String() - - err = core.ValidateType(arg1, core.HTMLDocumentType) - - if err != nil { - return values.None, err - } - - doc, ok := arg1.(*dynamic.HTMLDocument) - - if !ok { - return values.EmptyString, core.Error(core.ErrInvalidType, "expected dynamic document") - } - - return doc.InnerHTMLBySelectorAll(values.NewString(selector)) -} - -/* - * Returns inner text of a matched element - * @param doc (Document) - Document - * @param selector (String) - Selector - * @returns str (String) - String value of inner text. - */ -func InnerText(_ context.Context, args ...core.Value) (core.Value, error) { - err := core.ValidateArgs(args, 2, 2) - - if err != nil { - return values.EmptyString, err - } - - arg1 := args[0] - selector := args[1].String() - - err = core.ValidateType(arg1, core.HTMLDocumentType) - - if err != nil { - return values.None, err - } - - doc, ok := arg1.(*dynamic.HTMLDocument) - - if !ok { - return values.EmptyString, core.Error(core.ErrInvalidType, "expected dynamic document") - } - - return doc.InnerHTMLBySelector(values.NewString(selector)) -} - -/* - * Returns inner text of all matched elements. - * @param doc (Document) - Document - * @param selector (String) - Selector - * @returns array (Array) - Array of string values. - */ -func InnerTextAll(_ context.Context, args ...core.Value) (core.Value, error) { - err := core.ValidateArgs(args, 2, 2) - - if err != nil { - return values.EmptyString, err - } - - arg1 := args[0] - selector := args[1].String() - - err = core.ValidateType(arg1, core.HTMLDocumentType) - - if err != nil { - return values.None, err - } - - doc, ok := arg1.(*dynamic.HTMLDocument) - - if !ok { - return values.EmptyString, core.Error(core.ErrInvalidType, "expected dynamic document") - } - - return doc.InnerHTMLBySelectorAll(values.NewString(selector)) -} diff --git a/pkg/stdlib/html/document.go b/pkg/stdlib/html/document.go index fa17ce69..04a213d9 100644 --- a/pkg/stdlib/html/document.go +++ b/pkg/stdlib/html/document.go @@ -5,16 +5,38 @@ import ( "github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime/values" "github.com/MontFerret/ferret/pkg/stdlib/html/driver" - "github.com/MontFerret/ferret/pkg/stdlib/html/driver/static" ) -func Document(ctx context.Context, inputs ...core.Value) (core.Value, error) { - url, dynamic, err := documentArgs(inputs) +/* + * Loads a document by a given url. + * By default, loads a document by http call - resulted document does not support any interactions. + * If passed "true" as a second argument, headless browser is used for loading the document which support interactions. + * @param url (String) - Target url string. If passed "about:blank" for dynamic document - it will open an empty page. + * @param dynamic (Boolean) - Optional boolean value indicating whether to use dynamic document. + * @returns (HTMLDocument) - Returns loaded HTML document. + */ +func Document(ctx context.Context, args ...core.Value) (core.Value, error) { + err := core.ValidateArgs(args, 1, 2) if err != nil { return values.None, err } + err = core.ValidateType(args[0], core.StringType) + + url := args[0].(values.String) + dynamic := values.False + + if len(args) == 2 { + err = core.ValidateType(args[1], core.BooleanType) + + if err != nil { + return values.None, err + } + + dynamic = args[1].(values.Boolean) + } + var drv driver.Driver if !dynamic { @@ -27,56 +49,5 @@ func Document(ctx context.Context, inputs ...core.Value) (core.Value, error) { return values.None, err } - return drv.GetDocument(ctx, url.String()) -} - -func DocumentParse(ctx context.Context, inputs ...core.Value) (core.Value, error) { - arg1 := values.EmptyString - - if len(inputs) == 0 { - return arg1, core.Error(core.ErrMissedArgument, "document string") - } - - a1 := inputs[0] - - if a1.Type() != core.StringType { - return arg1, core.Error(core.TypeError(a1.Type(), core.StringType), "arg 1") - } - - drv, err := driver.FromContext(ctx, driver.Static) - - if err != nil { - return values.None, err - } - - return drv.(*static.Driver).ParseDocument(ctx, arg1.String()) -} - -func documentArgs(inputs []core.Value) (values.String, values.Boolean, error) { - arg1 := values.EmptyString - arg2 := values.False - - if len(inputs) == 0 { - return arg1, arg2, core.Error(core.ErrMissedArgument, "element and useJs") - } - - a1 := inputs[0] - - if a1.Type() != core.StringType { - return arg1, arg2, core.Error(core.TypeError(a1.Type(), core.StringType), "arg 1") - } - - arg1 = a1.(values.String) - - if len(inputs) == 2 { - a2 := inputs[1] - - if a2.Type() != core.BooleanType { - return arg1, arg2, core.Error(core.TypeError(a2.Type(), core.BooleanType), "arg 2") - } - - arg2 = a2.(values.Boolean) - } - - return arg1, arg2, nil + return drv.GetDocument(ctx, url) } diff --git a/pkg/stdlib/html/document_parse.go b/pkg/stdlib/html/document_parse.go new file mode 100644 index 00000000..1823af3d --- /dev/null +++ b/pkg/stdlib/html/document_parse.go @@ -0,0 +1,39 @@ +package html + +import ( + "context" + "github.com/MontFerret/ferret/pkg/runtime/core" + "github.com/MontFerret/ferret/pkg/runtime/values" + "github.com/MontFerret/ferret/pkg/stdlib/html/driver" + "github.com/MontFerret/ferret/pkg/stdlib/html/driver/static" +) + +/* + * Parses a given HTML string and returns a HTML document. + * Returned HTML document is always static. + * @param html (String) - Target HTML string. + * @returns (HTMLDocument) - Parsed HTML static document. + */ +func DocumentParse(ctx context.Context, args ...core.Value) (core.Value, error) { + err := core.ValidateArgs(args, 1, 1) + + if err != nil { + return values.None, err + } + + err = core.ValidateType(args[0], core.StringType) + + if err != nil { + return values.None, err + } + + drv, err := driver.FromContext(ctx, driver.Static) + + if err != nil { + return values.None, err + } + + str := args[0].(values.String) + + return drv.(*static.Driver).ParseDocument(ctx, str) +} diff --git a/pkg/stdlib/html/driver/common/attrs.go b/pkg/stdlib/html/driver/common/attrs.go index 9decd158..03970c60 100644 --- a/pkg/stdlib/html/driver/common/attrs.go +++ b/pkg/stdlib/html/driver/common/attrs.go @@ -1,5 +1,7 @@ package common +import "strings" + var Attributes = []string{ "abbr", "accept", @@ -147,7 +149,12 @@ func init() { } func IsAttribute(name string) bool { - _, yes := attrMap[name] + _, isDefault := attrMap[name] - return yes + if isDefault { + return true + } + + return strings.HasPrefix(name, "data-") || + strings.HasPrefix(name, "aria-") } diff --git a/pkg/stdlib/html/driver/common/lazy.go b/pkg/stdlib/html/driver/common/lazy.go index 296636c8..4727dc90 100644 --- a/pkg/stdlib/html/driver/common/lazy.go +++ b/pkg/stdlib/html/driver/common/lazy.go @@ -27,6 +27,10 @@ func NewLazyValue(factory LazyFactory) *LazyValue { return lz } +/* + * Indicates whether the value is ready. + * @returns (Boolean) - Boolean value indicating whether the value is ready. + */ func (lv *LazyValue) Ready() bool { lv.Lock() defer lv.Unlock() @@ -34,25 +38,42 @@ func (lv *LazyValue) Ready() bool { return lv.ready } -func (lv *LazyValue) Value() (core.Value, error) { +/* + * Returns an underlying value. + * Not thread safe. Should not mutated. + * @returns (Value) - Underlying value if successfully loaded, otherwise error + */ +func (lv *LazyValue) Read() (core.Value, error) { lv.Lock() defer lv.Unlock() if !lv.ready { - val, err := lv.factory() - - if err == nil { - lv.value = val - lv.err = nil - } else { - lv.value = values.None - lv.err = err - } + lv.load() } return lv.value, lv.err } +/* + * Safely mutates an underlying value. + * Loads a value if it's not ready. + * Thread safe. + */ +func (lv *LazyValue) Write(writer func(v core.Value, err error)) { + lv.Lock() + defer lv.Unlock() + + if !lv.ready { + lv.load() + } + + writer(lv.value, lv.err) +} + +/* + * Resets the storage. + * Next call of Read will trigger the factory function again. + */ func (lv *LazyValue) Reset() { lv.Lock() defer lv.Unlock() @@ -61,3 +82,17 @@ func (lv *LazyValue) Reset() { lv.value = values.None lv.err = nil } + +func (lv *LazyValue) load() { + val, err := lv.factory() + + if err == nil { + lv.value = val + lv.err = nil + } else { + lv.value = values.None + lv.err = err + } + + lv.ready = true +} diff --git a/pkg/stdlib/html/driver/driver.go b/pkg/stdlib/html/driver/driver.go index 21afbf9c..9725027f 100644 --- a/pkg/stdlib/html/driver/driver.go +++ b/pkg/stdlib/html/driver/driver.go @@ -9,23 +9,23 @@ import ( "github.com/MontFerret/ferret/pkg/stdlib/html/driver/static" ) -type DriverName string +type Name string const ( - Dynamic DriverName = "dynamic" - Static DriverName = "static" + Dynamic Name = "dynamic" + Static Name = "static" ) type Driver interface { - GetDocument(ctx context.Context, url string) (values.HTMLNode, error) + GetDocument(ctx context.Context, url values.String) (values.HTMLNode, error) Close() error } -func ToContext(ctx context.Context, name DriverName, drv Driver) context.Context { +func ToContext(ctx context.Context, name Name, drv Driver) context.Context { return context.WithValue(ctx, name, drv) } -func FromContext(ctx context.Context, name DriverName) (Driver, error) { +func FromContext(ctx context.Context, name Name) (Driver, error) { val := ctx.Value(name) drv, ok := val.(Driver) diff --git a/pkg/stdlib/html/driver/dynamic/document.go b/pkg/stdlib/html/driver/dynamic/document.go index 12020066..6a858a25 100644 --- a/pkg/stdlib/html/driver/dynamic/document.go +++ b/pkg/stdlib/html/driver/dynamic/document.go @@ -21,7 +21,6 @@ import ( "time" ) - const BlankPageURL = "about:blank" type HTMLDocument struct { @@ -378,7 +377,7 @@ func (doc *HTMLDocument) URL() core.Value { return doc.url } -func (doc *HTMLDocument) InnerHTMLBySelector(selector values.String) (values.String, error) { +func (doc *HTMLDocument) InnerHTMLBySelector(selector values.String) values.String { res, err := eval.Eval( doc.client, fmt.Sprintf(` @@ -395,17 +394,23 @@ func (doc *HTMLDocument) InnerHTMLBySelector(selector values.String) (values.Str ) if err != nil { - return values.EmptyString, err + doc.logger.Error(). + Timestamp(). + Err(err). + Str("selector", selector.String()). + Msg("failed to get inner HTML by selector") + + return values.EmptyString } if res.Type() == core.StringType { - return res.(values.String), nil + return res.(values.String) } - return values.EmptyString, nil + return values.EmptyString } -func (doc *HTMLDocument) InnerHTMLBySelectorAll(selector values.String) (*values.Array, error) { +func (doc *HTMLDocument) InnerHTMLBySelectorAll(selector values.String) *values.Array { res, err := eval.Eval( doc.client, fmt.Sprintf(` @@ -427,17 +432,23 @@ func (doc *HTMLDocument) InnerHTMLBySelectorAll(selector values.String) (*values ) if err != nil { - return values.NewArray(0), err + doc.logger.Error(). + Timestamp(). + Err(err). + Str("selector", selector.String()). + Msg("failed to get an array of inner HTML by selector") + + return values.NewArray(0) } if res.Type() == core.ArrayType { - return res.(*values.Array), nil + return res.(*values.Array) } - return values.NewArray(0), nil + return values.NewArray(0) } -func (doc *HTMLDocument) InnerTextBySelector(selector values.String) (values.String, error) { +func (doc *HTMLDocument) InnerTextBySelector(selector values.String) values.String { res, err := eval.Eval( doc.client, fmt.Sprintf(` @@ -454,17 +465,23 @@ func (doc *HTMLDocument) InnerTextBySelector(selector values.String) (values.Str ) if err != nil { - return values.EmptyString, err + doc.logger.Error(). + Timestamp(). + Err(err). + Str("selector", selector.String()). + Msg("failed to get inner text by selector") + + return values.EmptyString } if res.Type() == core.StringType { - return res.(values.String), nil + return res.(values.String) } - return values.EmptyString, nil + return values.EmptyString } -func (doc *HTMLDocument) InnerTextBySelectorAll(selector values.String) (*values.Array, error) { +func (doc *HTMLDocument) InnerTextBySelectorAll(selector values.String) *values.Array { res, err := eval.Eval( doc.client, fmt.Sprintf(` @@ -486,14 +503,20 @@ func (doc *HTMLDocument) InnerTextBySelectorAll(selector values.String) (*values ) if err != nil { - return values.NewArray(0), err + doc.logger.Error(). + Timestamp(). + Err(err). + Str("selector", selector.String()). + Msg("failed to get an array inner text by selector") + + return values.NewArray(0) } if res.Type() == core.ArrayType { - return res.(*values.Array), nil + return res.(*values.Array) } - return values.NewArray(0), nil + return values.NewArray(0) } func (doc *HTMLDocument) ClickBySelector(selector values.String) (values.Boolean, error) { @@ -595,15 +618,16 @@ func (doc *HTMLDocument) InputBySelector(selector values.String, value core.Valu } func (doc *HTMLDocument) WaitForSelector(selector values.String, timeout values.Int) error { - task := events.NewWaitTask( + task := events.NewEvalWaitTask( doc.client, fmt.Sprintf(` - el = document.querySelector(%s); + var el = document.querySelector(%s); if (el != null) { return true; } + // null means we need to repeat return null; `, eval.ParamString(selector.String())), time.Millisecond*time.Duration(timeout), @@ -615,6 +639,78 @@ func (doc *HTMLDocument) WaitForSelector(selector values.String, timeout values. return err } +func (doc *HTMLDocument) WaitForClass(selector, class values.String, timeout values.Int) error { + task := events.NewEvalWaitTask( + doc.client, + fmt.Sprintf(` + var el = document.querySelector(%s); + + if (el == null) { + return false; + } + + var className = %s; + var found = el.className.split(' ').find(i => i === className); + + if (found != null) { + return true; + } + + // null means we need to repeat + return null; + `, + eval.ParamString(selector.String()), + eval.ParamString(class.String()), + ), + time.Millisecond*time.Duration(timeout), + events.DefaultPolling, + ) + + _, err := task.Run() + + return err +} + +func (doc *HTMLDocument) WaitForClassAll(selector, class values.String, timeout values.Int) error { + task := events.NewEvalWaitTask( + doc.client, + fmt.Sprintf(` + var elements = document.querySelectorAll(%s); + + if (elements == null || elements.length === 0) { + return false; + } + + var className = %s; + var foundCount = 0; + + elements.forEach((el) => { + var found = el.className.split(' ').find(i => i === className); + + if (found != null) { + foundCount++; + } + }); + + if (foundCount === elements.length) { + return true; + } + + // null means we need to repeat + return null; + `, + eval.ParamString(selector.String()), + eval.ParamString(class.String()), + ), + time.Millisecond*time.Duration(timeout), + events.DefaultPolling, + ) + + _, err := task.Run() + + return err +} + func (doc *HTMLDocument) WaitForNavigation(timeout values.Int) error { timer := time.NewTimer(time.Millisecond * time.Duration(timeout)) onEvent := make(chan bool) diff --git a/pkg/stdlib/html/driver/dynamic/dynamic.go b/pkg/stdlib/html/driver/dynamic/dynamic.go index 65c81813..6d731c0e 100644 --- a/pkg/stdlib/html/driver/dynamic/dynamic.go +++ b/pkg/stdlib/html/driver/dynamic/dynamic.go @@ -28,7 +28,7 @@ func NewDriver(address string) *Driver { return drv } -func (drv *Driver) GetDocument(ctx context.Context, url string) (values.HTMLNode, error) { +func (drv *Driver) GetDocument(ctx context.Context, targetURL values.String) (values.HTMLNode, error) { err := drv.init(ctx) if err != nil { @@ -38,6 +38,8 @@ func (drv *Driver) GetDocument(ctx context.Context, url string) (values.HTMLNode ctx, cancel := context.WithTimeout(ctx, DefaultTimeout) defer cancel() + url := targetURL.String() + if url == "" { url = BlankPageURL } diff --git a/pkg/stdlib/html/driver/dynamic/element.go b/pkg/stdlib/html/driver/dynamic/element.go index ada5b9f1..470c9bb9 100644 --- a/pkg/stdlib/html/driver/dynamic/element.go +++ b/pkg/stdlib/html/driver/dynamic/element.go @@ -15,6 +15,7 @@ import ( "github.com/rs/zerolog" "hash/fnv" "strconv" + "strings" "sync" "time" ) @@ -145,7 +146,7 @@ func (el *HTMLElement) Type() core.Type { } func (el *HTMLElement) MarshalJSON() ([]byte, error) { - val, err := el.innerText.Value() + val, err := el.innerText.Read() if err != nil { return nil, err @@ -188,7 +189,6 @@ func (el *HTMLElement) Unwrap() interface{} { return el } - func (el *HTMLElement) Hash() uint64 { el.Lock() defer el.Unlock() @@ -243,7 +243,7 @@ func (el *HTMLElement) NodeName() values.String { } func (el *HTMLElement) GetAttributes() core.Value { - val, err := el.attributes.Value() + val, err := el.attributes.Read() if err != nil { return values.None @@ -254,7 +254,7 @@ func (el *HTMLElement) GetAttributes() core.Value { } func (el *HTMLElement) GetAttribute(name values.String) core.Value { - attrs, err := el.attributes.Value() + attrs, err := el.attributes.Read() if err != nil { return values.None @@ -270,7 +270,7 @@ func (el *HTMLElement) GetAttribute(name values.String) core.Value { } func (el *HTMLElement) GetChildNodes() core.Value { - val, err := el.loadedChildren.Value() + val, err := el.loadedChildren.Read() if err != nil { return values.NewArray(0) @@ -280,7 +280,7 @@ func (el *HTMLElement) GetChildNodes() core.Value { } func (el *HTMLElement) GetChildNode(idx values.Int) core.Value { - val, err := el.loadedChildren.Value() + val, err := el.loadedChildren.Read() if err != nil { return values.None @@ -365,8 +365,38 @@ func (el *HTMLElement) QuerySelectorAll(selector values.String) core.Value { return arr } +func (el *HTMLElement) WaitForClass(class values.String, timeout values.Int) error { + task := events.NewWaitTask( + func() (core.Value, error) { + current := el.GetAttribute("class") + + if current.Type() != core.StringType { + return values.None, nil + } + + str := current.(values.String) + classStr := string(class) + classes := strings.Split(string(str), " ") + + for _, c := range classes { + if c == classStr { + return values.True, nil + } + } + + return values.None, nil + }, + time.Millisecond*time.Duration(timeout), + events.DefaultPolling, + ) + + _, err := task.Run() + + return err +} + func (el *HTMLElement) InnerText() values.String { - val, err := el.innerText.Value() + val, err := el.innerText.Read() if err != nil { return values.EmptyString @@ -469,29 +499,25 @@ func (el *HTMLElement) handleAttrModified(message interface{}) { return } - // they are not event loaded - // just ignore the event - if !el.attributes.Ready() { - return - } + el.attributes.Write(func(v core.Value, err error) { + if err != nil { + el.logger.Error(). + Timestamp(). + Err(err). + Int("id", int(el.id)). + Msg("failed to update node") - val, err := el.attributes.Value() + return + } - // failed to load - if err != nil { - return - } + attrs, ok := v.(*values.Object) - el.Lock() - defer el.Unlock() + if !ok { + return + } - attrs, ok := val.(*values.Object) - - if !ok { - return - } - - attrs.Set(values.NewString(reply.Name), values.NewString(reply.Value)) + attrs.Set(values.NewString(reply.Name), values.NewString(reply.Value)) + }) } func (el *HTMLElement) handleAttrRemoved(message interface{}) { @@ -513,24 +539,25 @@ func (el *HTMLElement) handleAttrRemoved(message interface{}) { return } - val, err := el.attributes.Value() + el.attributes.Write(func(v core.Value, err error) { + if err != nil { + el.logger.Error(). + Timestamp(). + Err(err). + Int("id", int(el.id)). + Msg("failed to update node") - // failed to load - if err != nil { - return - } + return + } - el.Lock() - defer el.Unlock() + attrs, ok := v.(*values.Object) - attrs, ok := val.(*values.Object) + if !ok { + return + } - if !ok { - return - } - - // TODO: actually, we need to sync it too... - attrs.Remove(values.NewString(reply.Name)) + attrs.Remove(values.NewString(reply.Name)) + }) } func (el *HTMLElement) handleChildrenCountChanged(message interface{}) { @@ -598,40 +625,36 @@ func (el *HTMLElement) handleChildInserted(message interface{}) { return } - loaded, err := el.loadedChildren.Value() + el.loadedChildren.Write(func(v core.Value, err error) { + loadedArr := v.(*values.Array) + loadedEl, err := LoadElement(el.logger, el.client, el.broker, nextID) - if err != nil { - return - } + if err != nil { + el.logger.Error(). + Timestamp(). + Err(err). + Int("id", int(el.id)). + Msg("failed to load an inserted node") - loadedArr := loaded.(*values.Array) - loadedEl, err := LoadElement(el.logger, el.client, el.broker, nextID) + return + } - if err != nil { - el.logger.Error(). - Timestamp(). - Err(err). - Int("id", int(el.id)). - Msg("failed to load an inserted node") + loadedArr.Insert(values.NewInt(targetIDx), loadedEl) - return - } + newInnerHTML, err := loadInnerHTML(el.client, el.id) - loadedArr.Insert(values.NewInt(targetIDx), loadedEl) + if err != nil { + el.logger.Error(). + Timestamp(). + Err(err). + Int("id", int(el.id)). + Msg("failed to update node") - newInnerHTML, err := loadInnerHTML(el.client, el.id) + return + } - if err != nil { - el.logger.Error(). - Timestamp(). - Err(err). - Int("id", int(el.id)). - Msg("failed to update node") - - return - } - - el.innerHTML = newInnerHTML + el.innerHTML = newInnerHTML + }) } func (el *HTMLElement) handleChildDeleted(message interface{}) { @@ -669,26 +692,32 @@ func (el *HTMLElement) handleChildDeleted(message interface{}) { return } - loaded, err := el.loadedChildren.Value() + el.loadedChildren.Write(func(v core.Value, err error) { + if err != nil { + el.logger.Error(). + Timestamp(). + Err(err). + Int("id", int(el.id)). + Msg("failed to update node") - if err != nil { - return - } + return + } - loadedArr := loaded.(*values.Array) - loadedArr.RemoveAt(values.NewInt(targetIDx)) + loadedArr := v.(*values.Array) + loadedArr.RemoveAt(values.NewInt(targetIDx)) - newInnerHTML, err := loadInnerHTML(el.client, el.id) + newInnerHTML, err := loadInnerHTML(el.client, el.id) - if err != nil { - el.logger.Error(). - Timestamp(). - Err(err). - Int("id", int(el.id)). - Msg("failed to update node") + if err != nil { + el.logger.Error(). + Timestamp(). + Err(err). + Int("id", int(el.id)). + Msg("failed to update node") - return - } + return + } - el.innerHTML = newInnerHTML + el.innerHTML = newInnerHTML + }) } diff --git a/pkg/stdlib/html/driver/dynamic/events/wait.go b/pkg/stdlib/html/driver/dynamic/events/wait.go index aa709ac9..0f1703ff 100644 --- a/pkg/stdlib/html/driver/dynamic/events/wait.go +++ b/pkg/stdlib/html/driver/dynamic/events/wait.go @@ -8,24 +8,24 @@ import ( "time" ) -type WaitTask struct { - client *cdp.Client - predicate string - timeout time.Duration - polling time.Duration -} +type ( + Function func() (core.Value, error) + WaitTask struct { + fun Function + timeout time.Duration + polling time.Duration + } +) const DefaultPolling = time.Millisecond * time.Duration(200) func NewWaitTask( - client *cdp.Client, - predicate string, + fun Function, timeout time.Duration, polling time.Duration, ) *WaitTask { return &WaitTask{ - client, - predicate, + fun, timeout, polling, } @@ -39,14 +39,9 @@ func (task *WaitTask) Run() (core.Value, error) { case <-timer.C: return values.None, core.ErrTimeout default: - out, err := eval.Eval( - task.client, - task.predicate, - true, - false, - ) + out, err := task.fun() - // JS expression failed + // expression failed // terminating if err != nil { timer.Stop() @@ -54,7 +49,7 @@ func (task *WaitTask) Run() (core.Value, error) { return values.None, err } - // JS output is not empty + // output is not empty // terminating if out != values.None { timer.Stop() @@ -67,3 +62,23 @@ func (task *WaitTask) Run() (core.Value, error) { } } } + +func NewEvalWaitTask( + client *cdp.Client, + predicate string, + timeout time.Duration, + polling time.Duration, +) *WaitTask { + return NewWaitTask( + func() (core.Value, error) { + return eval.Eval( + client, + predicate, + true, + false, + ) + }, + timeout, + polling, + ) +} diff --git a/pkg/stdlib/html/driver/dynamic/helpers.go b/pkg/stdlib/html/driver/dynamic/helpers.go index d286d792..b6796010 100644 --- a/pkg/stdlib/html/driver/dynamic/helpers.go +++ b/pkg/stdlib/html/driver/dynamic/helpers.go @@ -10,6 +10,7 @@ import ( "github.com/mafredri/cdp/protocol/page" "github.com/rs/zerolog" "golang.org/x/sync/errgroup" + "strings" ) func pointerInt(input int) *int { @@ -34,6 +35,7 @@ func parseAttrs(attrs []string) *values.Object { res := values.NewObject() for _, el := range attrs { + el = strings.TrimSpace(el) str := values.NewString(el) if common.IsAttribute(el) { @@ -43,7 +45,11 @@ func parseAttrs(attrs []string) *values.Object { current, ok := res.Get(attr) if ok { - res.Set(attr, current.(values.String).Concat(values.SpaceString).Concat(str)) + if current.String() != "" { + res.Set(attr, current.(values.String).Concat(values.SpaceString).Concat(str)) + } else { + res.Set(attr, str) + } } } } diff --git a/pkg/stdlib/html/driver/static/document.go b/pkg/stdlib/html/driver/static/document.go index b4ee5394..6c7b7025 100644 --- a/pkg/stdlib/html/driver/static/document.go +++ b/pkg/stdlib/html/driver/static/document.go @@ -32,16 +32,16 @@ func NewHTMLDocument( return &HTMLDocument{el, values.NewString(url)}, nil } -func (el *HTMLDocument) Type() core.Type { +func (doc *HTMLDocument) Type() core.Type { return core.HTMLDocumentType } -func (el *HTMLDocument) Compare(other core.Value) int { +func (doc *HTMLDocument) Compare(other core.Value) int { switch other.Type() { case core.HTMLDocumentType: otherDoc := other.(values.HTMLDocument) - return el.url.Compare(otherDoc.URL()) + return doc.url.Compare(otherDoc.URL()) default: if other.Type() > core.HTMLDocumentType { return -1 @@ -51,6 +51,52 @@ func (el *HTMLDocument) Compare(other core.Value) int { } } -func (el *HTMLDocument) URL() core.Value { - return el.url +func (doc *HTMLDocument) URL() core.Value { + return doc.url +} + +func (doc *HTMLDocument) InnerHTMLBySelector(selector values.String) values.String { + selection := doc.selection.Find(selector.String()) + + str, err := selection.Html() + + // TODO: log error + if err != nil { + return values.EmptyString + } + + return values.NewString(str) +} + +func (doc *HTMLDocument) InnerHTMLBySelectorAll(selector values.String) *values.Array { + selection := doc.selection.Find(selector.String()) + arr := values.NewArray(selection.Length()) + + selection.Each(func(_ int, selection *goquery.Selection) { + str, err := selection.Html() + + // TODO: log error + if err == nil { + arr.Push(values.NewString(str)) + } + }) + + return arr +} + +func (doc *HTMLDocument) InnerTextBySelector(selector values.String) values.String { + selection := doc.selection.Find(selector.String()) + + return values.NewString(selection.Text()) +} + +func (doc *HTMLDocument) InnerTextBySelectorAll(selector values.String) *values.Array { + selection := doc.selection.Find(selector.String()) + arr := values.NewArray(selection.Length()) + + selection.Each(func(_ int, selection *goquery.Selection) { + arr.Push(values.NewString(selection.Text())) + }) + + return arr } diff --git a/pkg/stdlib/html/driver/static/element.go b/pkg/stdlib/html/driver/static/element.go index 87571ed4..176e823c 100644 --- a/pkg/stdlib/html/driver/static/element.go +++ b/pkg/stdlib/html/driver/static/element.go @@ -53,7 +53,6 @@ func (el *HTMLElement) Unwrap() interface{} { return el.selection } - func (el *HTMLElement) Hash() uint64 { str, err := el.selection.Html() diff --git a/pkg/stdlib/html/driver/static/element_test.go b/pkg/stdlib/html/driver/static/element_test.go index 8d49a33b..b8e4ff2f 100644 --- a/pkg/stdlib/html/driver/static/element_test.go +++ b/pkg/stdlib/html/driver/static/element_test.go @@ -297,7 +297,7 @@ func TestElement(t *testing.T) { So(el.Length(), ShouldEqual, 4) }) - Convey(".Value", t, func() { + Convey(".Read", t, func() { buff := bytes.NewBuffer([]byte(` diff --git a/pkg/stdlib/html/driver/static/static.go b/pkg/stdlib/html/driver/static/static.go index 3e570070..df81d1e9 100644 --- a/pkg/stdlib/html/driver/static/static.go +++ b/pkg/stdlib/html/driver/static/static.go @@ -28,7 +28,8 @@ func NewDriver(setters ...Option) *Driver { return &Driver{client} } -func (d *Driver) GetDocument(_ context.Context, url string) (values.HTMLNode, error) { +func (d *Driver) GetDocument(_ context.Context, targetURL values.String) (values.HTMLNode, error) { + url := targetURL.String() req, err := httpx.NewRequest(httpx.MethodGet, url, nil) if err != nil { @@ -58,7 +59,7 @@ func (d *Driver) GetDocument(_ context.Context, url string) (values.HTMLNode, er return NewHTMLDocument(url, doc) } -func (d *Driver) ParseDocument(_ context.Context, str string) (values.HTMLNode, error) { +func (d *Driver) ParseDocument(_ context.Context, str values.String) (values.HTMLNode, error) { buf := bytes.NewBuffer([]byte(str)) doc, err := goquery.NewDocumentFromReader(buf) diff --git a/pkg/stdlib/html/element.go b/pkg/stdlib/html/element.go index 2491e962..a29c99a3 100644 --- a/pkg/stdlib/html/element.go +++ b/pkg/stdlib/html/element.go @@ -6,8 +6,15 @@ import ( "github.com/MontFerret/ferret/pkg/runtime/values" ) -func Element(_ context.Context, inputs ...core.Value) (core.Value, error) { - el, selector, err := elementArgs(inputs) +/* + * Finds an element by a given CSS selector. + * Returns NONE if element not found. + * @param docOrEl (HTMLDocument|HTMLElement) - Parent document or element. + * @param selector (String) - CSS selector. + * @returns (HTMLElement | None) - Returns an HTMLElement if found, otherwise NONE. + */ +func Element(_ context.Context, args ...core.Value) (core.Value, error) { + el, selector, err := queryArgs(args) if err != nil { return values.None, err @@ -16,36 +23,24 @@ func Element(_ context.Context, inputs ...core.Value) (core.Value, error) { return el.QuerySelector(selector), nil } -func Elements(_ context.Context, inputs ...core.Value) (core.Value, error) { - el, selector, err := elementArgs(inputs) +func queryArgs(args []core.Value) (values.HTMLNode, values.String, error) { + err := core.ValidateArgs(args, 2, 2) if err != nil { - return values.None, err + return nil, values.EmptyString, err } - return el.QuerySelectorAll(selector), nil -} - -func elementArgs(inputs []core.Value) (values.HTMLNode, values.String, error) { - if len(inputs) == 0 { - return nil, values.EmptyString, core.Error(core.ErrMissedArgument, "element and arg2") - } - - if len(inputs) == 1 { - return nil, values.EmptyString, core.Error(core.ErrMissedArgument, "arg2") - } - - arg1 := inputs[0] - arg2 := inputs[1] - - if arg1.Type() != core.HTMLDocumentType && - arg1.Type() != core.HTMLElementType { - return nil, values.EmptyString, core.TypeError(arg1.Type(), core.HTMLDocumentType, core.HTMLElementType) - } - - if arg2.Type() != core.StringType { - return nil, values.EmptyString, core.TypeError(arg2.Type(), core.StringType) - } - - return arg1.(values.HTMLNode), arg2.(values.String), nil + err = core.ValidateType(args[0], core.HTMLDocumentType, core.HTMLElementType) + + if err != nil { + return nil, values.EmptyString, err + } + + err = core.ValidateType(args[1], core.StringType) + + if err != nil { + return nil, values.EmptyString, err + } + + return args[0].(values.HTMLNode), args[1].(values.String), nil } diff --git a/pkg/stdlib/html/elements.go b/pkg/stdlib/html/elements.go new file mode 100644 index 00000000..433871e7 --- /dev/null +++ b/pkg/stdlib/html/elements.go @@ -0,0 +1,24 @@ +package html + +import ( + "context" + "github.com/MontFerret/ferret/pkg/runtime/core" + "github.com/MontFerret/ferret/pkg/runtime/values" +) + +/* + * Finds HTML elements by a given CSS selector. + * Returns an empty array if element not found. + * @param docOrEl (HTMLDocument|HTMLElement) - Parent document or element. + * @param selector (String) - CSS selector. + * @returns (Array) - Returns an array of found HTML element. + */ +func Elements(_ context.Context, args ...core.Value) (core.Value, error) { + el, selector, err := queryArgs(args) + + if err != nil { + return values.None, err + } + + return el.QuerySelectorAll(selector), nil +} diff --git a/pkg/stdlib/html/events.go b/pkg/stdlib/html/events.go deleted file mode 100644 index 782dd1f4..00000000 --- a/pkg/stdlib/html/events.go +++ /dev/null @@ -1,74 +0,0 @@ -package html - -import ( - "context" - "github.com/MontFerret/ferret/pkg/runtime/core" - "github.com/MontFerret/ferret/pkg/runtime/values" - "github.com/MontFerret/ferret/pkg/stdlib/html/driver/dynamic" -) - -func WaitElement(_ context.Context, args ...core.Value) (core.Value, error) { - err := core.ValidateArgs(args, 2, 3) - - if err != nil { - return values.None, err - } - - arg := args[0] - selector := args[1].String() - timeout := values.NewInt(5000) - - if len(args) > 2 { - if args[2].Type() == core.IntType { - timeout = args[2].(values.Int) - } - } - - err = core.ValidateType(arg, core.HTMLDocumentType) - - if err != nil { - return values.None, err - } - - doc, ok := arg.(*dynamic.HTMLDocument) - - if !ok { - return values.False, core.Error(core.ErrInvalidType, "expected dynamic document") - } - - return values.None, doc.WaitForSelector(values.NewString(selector), timeout) -} - -func WaitNavigation(_ context.Context, args ...core.Value) (core.Value, error) { - err := core.ValidateArgs(args, 1, 2) - - if err != nil { - return values.None, err - } - - err = core.ValidateType(args[0], core.HTMLDocumentType) - - if err != nil { - return values.None, err - } - - doc, ok := args[0].(*dynamic.HTMLDocument) - - if !ok { - return values.None, core.Error(core.ErrInvalidType, "expected dynamic document") - } - - timeout := values.NewInt(5000) - - if len(args) > 1 { - err = core.ValidateType(args[1], core.IntType) - - if err != nil { - return values.None, err - } - - timeout = args[1].(values.Int) - } - - return values.None, doc.WaitForNavigation(timeout) -} diff --git a/pkg/stdlib/html/inner_html.go b/pkg/stdlib/html/inner_html.go new file mode 100644 index 00000000..1ff3cba2 --- /dev/null +++ b/pkg/stdlib/html/inner_html.go @@ -0,0 +1,38 @@ +package html + +import ( + "context" + "github.com/MontFerret/ferret/pkg/runtime/core" + "github.com/MontFerret/ferret/pkg/runtime/values" +) + +/* + * Returns inner HTML string of a matched element + * @param doc (Document) - Parent document. + * @param selector (String) - String of CSS selector. + * @returns (String) - Inner HTML string if an element found, otherwise NONE. + */ +func InnerHTML(_ context.Context, args ...core.Value) (core.Value, error) { + err := core.ValidateArgs(args, 2, 2) + + if err != nil { + return values.EmptyString, 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 := args[0].(values.HTMLDocument) + selector := args[1].(values.String) + + return doc.InnerHTMLBySelector(selector), nil +} diff --git a/pkg/stdlib/html/inner_html_all.go b/pkg/stdlib/html/inner_html_all.go new file mode 100644 index 00000000..21b01130 --- /dev/null +++ b/pkg/stdlib/html/inner_html_all.go @@ -0,0 +1,38 @@ +package html + +import ( + "context" + "github.com/MontFerret/ferret/pkg/runtime/core" + "github.com/MontFerret/ferret/pkg/runtime/values" +) + +/* + * Returns an array of inner HTML strings of matched elements. + * @param doc (Document) - Parent document. + * @param selector (String) - String of CSS selector. + * @returns (String) - An array of inner HTML strings if any element found, otherwise empty array. + */ +func InnerHTMLAll(_ 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 := args[0].(values.HTMLDocument) + selector := args[1].(values.String) + + return doc.InnerHTMLBySelectorAll(selector), nil +} diff --git a/pkg/stdlib/html/inner_text.go b/pkg/stdlib/html/inner_text.go new file mode 100644 index 00000000..ceda386e --- /dev/null +++ b/pkg/stdlib/html/inner_text.go @@ -0,0 +1,38 @@ +package html + +import ( + "context" + "github.com/MontFerret/ferret/pkg/runtime/core" + "github.com/MontFerret/ferret/pkg/runtime/values" +) + +/* + * Returns inner text of a matched element + * @param doc (Document) - Parent document. + * @param selector (String) - String of CSS selector. + * @returns (String) - Inner text if an element found, otherwise NONE. + */ +func InnerText(_ context.Context, args ...core.Value) (core.Value, error) { + err := core.ValidateArgs(args, 2, 2) + + if err != nil { + return values.EmptyString, 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 := args[0].(values.HTMLDocument) + selector := args[1].(values.String) + + return doc.InnerTextBySelector(selector), nil +} diff --git a/pkg/stdlib/html/inner_text_all.go b/pkg/stdlib/html/inner_text_all.go new file mode 100644 index 00000000..31693283 --- /dev/null +++ b/pkg/stdlib/html/inner_text_all.go @@ -0,0 +1,38 @@ +package html + +import ( + "context" + "github.com/MontFerret/ferret/pkg/runtime/core" + "github.com/MontFerret/ferret/pkg/runtime/values" +) + +/* + * Returns an array of inner text of matched elements. + * @param doc (Document) - Parent document. + * @param selector (String) - String of CSS selector. + * @returns (String) - An array of inner text if any element found, otherwise empty array. + */ +func InnerTextAll(_ 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 := args[0].(values.HTMLDocument) + selector := args[1].(values.String) + + return doc.InnerTextBySelectorAll(selector), nil +} diff --git a/pkg/stdlib/html/input.go b/pkg/stdlib/html/input.go new file mode 100644 index 00000000..5c9f6ed7 --- /dev/null +++ b/pkg/stdlib/html/input.go @@ -0,0 +1,72 @@ +package html + +import ( + "context" + "github.com/MontFerret/ferret/pkg/runtime/core" + "github.com/MontFerret/ferret/pkg/runtime/values" + "github.com/MontFerret/ferret/pkg/stdlib/html/driver/dynamic" +) + +/* + * Sends a value to an underlying input element. + * @param source (Document | Element) - Event target. + * @param valueOrSelector (String) - Selector or a value. + * @param value (String) - Target value. + * @returns (Boolean) - Returns true if an element was found. + */ +func Input(_ context.Context, args ...core.Value) (core.Value, error) { + err := core.ValidateArgs(args, 2, 3) + + if err != nil { + return values.None, err + } + + // TYPE(el, "foobar") + if len(args) == 2 { + arg1 := args[0] + + err := core.ValidateType(arg1, core.HTMLElementType) + + if err != nil { + return values.False, err + } + + el, ok := arg1.(*dynamic.HTMLElement) + + if !ok { + return values.False, core.Errors(core.ErrInvalidType, ErrNotDynamic) + } + + err = el.Input(args[1]) + + if err != nil { + return values.False, err + } + + return values.True, nil + } + + arg1 := args[0] + + err = core.ValidateType(arg1, core.HTMLDocumentType) + + if err != nil { + return values.False, err + } + + arg2 := args[1] + + err = core.ValidateType(arg2, core.StringType) + + if err != nil { + return values.False, err + } + + doc, ok := arg1.(*dynamic.HTMLDocument) + + if !ok { + return values.False, core.Errors(core.ErrInvalidType, ErrNotDynamic) + } + + return doc.InputBySelector(arg2.(values.String), args[2]) +} diff --git a/pkg/stdlib/html/lib.go b/pkg/stdlib/html/lib.go index bd3195db..2d7c82d2 100644 --- a/pkg/stdlib/html/lib.go +++ b/pkg/stdlib/html/lib.go @@ -1,6 +1,15 @@ package html -import "github.com/MontFerret/ferret/pkg/runtime/core" +import ( + "github.com/MontFerret/ferret/pkg/runtime/core" + "github.com/pkg/errors" +) + +const defaultTimeout = 5000 + +var ( + ErrNotDynamic = errors.New("expected dynamic document or element") +) func NewLib() map[string]core.Function { return map[string]core.Function{ @@ -10,6 +19,8 @@ func NewLib() map[string]core.Function { "ELEMENTS": Elements, "WAIT_ELEMENT": WaitElement, "WAIT_NAVIGATION": WaitNavigation, + "WAIT_CLASS": WaitClass, + "WAIT_CLASS_ALL": WaitClassAll, "CLICK": Click, "CLICK_ALL": ClickAll, "NAVIGATE": Navigate, diff --git a/pkg/stdlib/html/navigate.go b/pkg/stdlib/html/navigate.go new file mode 100644 index 00000000..c270f7b0 --- /dev/null +++ b/pkg/stdlib/html/navigate.go @@ -0,0 +1,43 @@ +package html + +import ( + "context" + "github.com/MontFerret/ferret/pkg/runtime/core" + "github.com/MontFerret/ferret/pkg/runtime/values" + "github.com/MontFerret/ferret/pkg/stdlib/html/driver/dynamic" +) + +/* + * 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].(*dynamic.HTMLDocument) + + if !ok { + return values.False, core.Errors(core.ErrInvalidType, ErrNotDynamic) + } + + return values.None, doc.Navigate(args[1].(values.String)) +} diff --git a/pkg/stdlib/html/wait_class.go b/pkg/stdlib/html/wait_class.go new file mode 100644 index 00000000..49c4c373 --- /dev/null +++ b/pkg/stdlib/html/wait_class.go @@ -0,0 +1,87 @@ +package html + +import ( + "context" + "github.com/MontFerret/ferret/pkg/runtime/core" + "github.com/MontFerret/ferret/pkg/runtime/values" + "github.com/MontFerret/ferret/pkg/stdlib/html/driver/dynamic" +) + +/* + * Waits for a class to appear on a given element. + * Stops the execution until the navigation ends or operation times out. + * @param docOrEl (HTMLDocument|HTMLElement) - Target document or element. + * @param selectorOrClass (String) - If document is passed, this param must represent an element selector. + * Otherwise target class. + * @param classOrTimeout (String|Int, optional) - If document is passed, this param must represent target class name. + * Otherwise timeout. + * @param timeout (Int, optional) - If document is passed, this param must represent timeout. + * Otherwise not passed. + */ +func WaitClass(_ context.Context, args ...core.Value) (core.Value, error) { + err := core.ValidateArgs(args, 2, 4) + + if err != nil { + return values.None, err + } + + // document or element + err = core.ValidateType(args[0], core.HTMLDocumentType, core.HTMLElementType) + + if err != nil { + return values.None, err + } + + // selector or class + err = core.ValidateType(args[1], core.StringType) + + if err != nil { + return values.None, err + } + + timeout := values.NewInt(defaultTimeout) + + // lets figure out what is passed as 1st argument + switch args[0].(type) { + case *dynamic.HTMLDocument: + // class + err = core.ValidateType(args[2], core.StringType) + + if err != nil { + return values.None, err + } + + doc := args[0].(*dynamic.HTMLDocument) + selector := args[1].(values.String) + class := args[2].(values.String) + + if len(args) == 4 { + err = core.ValidateType(args[3], core.IntType) + + if err != nil { + return values.None, err + } + + timeout = args[3].(values.Int) + } + + return values.None, doc.WaitForClass(selector, class, timeout) + case *dynamic.HTMLElement: + el := args[0].(*dynamic.HTMLElement) + class := args[1].(values.String) + + if len(args) == 3 { + err = core.ValidateType(args[2], core.IntType) + + if err != nil { + return values.None, err + } + + timeout = args[3].(values.Int) + } + + return values.None, el.WaitForClass(class, timeout) + default: + return values.None, core.Errors(core.ErrInvalidType, ErrNotDynamic) + } +} diff --git a/pkg/stdlib/html/wait_class_all.go b/pkg/stdlib/html/wait_class_all.go new file mode 100644 index 00000000..8a218ff3 --- /dev/null +++ b/pkg/stdlib/html/wait_class_all.go @@ -0,0 +1,67 @@ +package html + +import ( + "context" + "github.com/MontFerret/ferret/pkg/runtime/core" + "github.com/MontFerret/ferret/pkg/runtime/values" + "github.com/MontFerret/ferret/pkg/stdlib/html/driver/dynamic" +) + +/* + * Waits for a class to appear on all matched elements. + * Stops the execution until the navigation ends or operation times out. + * @param doc (HTMLDocument) - Parent document. + * @param selector (String) - String of CSS selector. + * @param class (String) - String of target CSS class. + * @param timeout (Int, optional) - Optional timeout. + */ +func WaitClassAll(_ context.Context, args ...core.Value) (core.Value, error) { + err := core.ValidateArgs(args, 3, 4) + + if err != nil { + return values.None, err + } + + // document only + err = core.ValidateType(args[0], core.HTMLDocumentType) + + if err != nil { + return values.None, err + } + + // selector + err = core.ValidateType(args[1], core.StringType) + + if err != nil { + return values.None, err + } + + // class + err = core.ValidateType(args[2], core.StringType) + + if err != nil { + return values.None, err + } + + doc, ok := args[0].(*dynamic.HTMLDocument) + + if !ok { + return values.None, core.Errors(core.ErrInvalidType, ErrNotDynamic) + } + + selector := args[1].(values.String) + class := args[2].(values.String) + timeout := values.NewInt(defaultTimeout) + + if len(args) == 4 { + err = core.ValidateType(args[3], core.IntType) + + if err != nil { + return values.None, err + } + + timeout = args[3].(values.Int) + } + + return values.None, doc.WaitForClassAll(selector, class, timeout) +} diff --git a/pkg/stdlib/html/wait_element.go b/pkg/stdlib/html/wait_element.go new file mode 100644 index 00000000..4d8395a6 --- /dev/null +++ b/pkg/stdlib/html/wait_element.go @@ -0,0 +1,51 @@ +package html + +import ( + "context" + "github.com/MontFerret/ferret/pkg/runtime/core" + "github.com/MontFerret/ferret/pkg/runtime/values" + "github.com/MontFerret/ferret/pkg/stdlib/html/driver/dynamic" +) + +/* + * Waits for element to appear in the DOM. + * Stops the execution until it finds an element or operation times out. + * @param doc (HTMLDocument) - Dynamic HTMLDocument. + * @param selector (String) - Target element's selector. + * @param timeout (Int, optional) - Optional timeout. Default 5000 ms. + */ +func WaitElement(_ context.Context, args ...core.Value) (core.Value, error) { + err := core.ValidateArgs(args, 2, 3) + + if err != nil { + return values.None, err + } + + arg := args[0] + selector := args[1].String() + timeout := values.NewInt(defaultTimeout) + + if len(args) > 2 { + err = core.ValidateType(args[2], core.IntType) + + if err != nil { + return values.None, err + } + + timeout = args[2].(values.Int) + } + + err = core.ValidateType(arg, core.HTMLDocumentType) + + if err != nil { + return values.None, err + } + + doc, ok := arg.(*dynamic.HTMLDocument) + + if !ok { + return values.None, core.Errors(core.ErrInvalidType, ErrNotDynamic) + } + + return values.None, doc.WaitForSelector(values.NewString(selector), timeout) +} diff --git a/pkg/stdlib/html/wait_navigation.go b/pkg/stdlib/html/wait_navigation.go new file mode 100644 index 00000000..91d4fe59 --- /dev/null +++ b/pkg/stdlib/html/wait_navigation.go @@ -0,0 +1,48 @@ +package html + +import ( + "context" + "github.com/MontFerret/ferret/pkg/runtime/core" + "github.com/MontFerret/ferret/pkg/runtime/values" + "github.com/MontFerret/ferret/pkg/stdlib/html/driver/dynamic" +) + +/* + * Waits for document to navigate to a new url. + * Stops the execution until the navigation ends or operation times out. + * @param doc (HTMLDocument) - Dynamic HTMLDocument. + * @param timeout (Int, optional) - Optional timeout. Default 5000 ms. + */ +func WaitNavigation(_ context.Context, args ...core.Value) (core.Value, error) { + err := core.ValidateArgs(args, 1, 2) + + if err != nil { + return values.None, err + } + + err = core.ValidateType(args[0], core.HTMLDocumentType) + + if err != nil { + return values.None, err + } + + doc, ok := args[0].(*dynamic.HTMLDocument) + + if !ok { + return values.None, core.Errors(core.ErrInvalidType, ErrNotDynamic) + } + + timeout := values.NewInt(defaultTimeout) + + if len(args) > 1 { + err = core.ValidateType(args[1], core.IntType) + + if err != nil { + return values.None, err + } + + timeout = args[1].(values.Int) + } + + return values.None, doc.WaitForNavigation(timeout) +} diff --git a/pkg/stdlib/strings/concat.go b/pkg/stdlib/strings/concat.go index f611141b..71782734 100644 --- a/pkg/stdlib/strings/concat.go +++ b/pkg/stdlib/strings/concat.go @@ -7,7 +7,7 @@ import ( ) /* - * Concatenates one or more instances of Value, or an Array. + * Concatenates one or more instances of Read, or an Array. * @params src (String...|Array) - The source string / array. * @returns String */ @@ -42,7 +42,7 @@ func Concat(_ context.Context, args ...core.Value) (core.Value, error) { } /* - * Concatenates one or more instances of Value, or an Array with a given separator. + * Concatenates one or more instances of Read, or an Array with a given separator. * @params separator (string) - The separator string. * @params src (string...|array) - The source string / array. * @returns string diff --git a/pkg/stdlib/strings/json.go b/pkg/stdlib/strings/json.go index 074cbfce..512f82a4 100644 --- a/pkg/stdlib/strings/json.go +++ b/pkg/stdlib/strings/json.go @@ -10,7 +10,7 @@ import ( /* * Returns a FQL value described by the JSON-encoded input string. * @params text (String) - The string to parse as JSON. - * @returns FQL value (Value) + * @returns FQL value (Read) */ func JSONParse(_ context.Context, args ...core.Value) (core.Value, error) { err := core.ValidateArgs(args, 1, 1) @@ -32,7 +32,7 @@ func JSONParse(_ context.Context, args ...core.Value) (core.Value, error) { /* * Returns a JSON string representation of the input value. - * @params value (Value) - The input value to serialize. + * @params value (Read) - The input value to serialize. * @returns json (String) */ func JSONStringify(_ context.Context, args ...core.Value) (core.Value, error) {