mirror of
https://github.com/MontFerret/ferret.git
synced 2025-03-17 21:18:37 +02:00
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
This commit is contained in:
parent
79b8171fd8
commit
e64ad4ec0e
8
examples/wait_class.fql
Normal file
8
examples/wait_class.fql
Normal file
@ -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
|
6
examples/wait_class_doc.fql
Normal file
6
examples/wait_class_doc.fql
Normal file
@ -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
|
@ -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"`)
|
||||
})
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
)
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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) {
|
||||
|
@ -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.
|
||||
|
@ -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])
|
||||
}
|
58
pkg/stdlib/html/click.go
Normal file
58
pkg/stdlib/html/click.go
Normal file
@ -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))
|
||||
}
|
39
pkg/stdlib/html/click_all.go
Normal file
39
pkg/stdlib/html/click_all.go
Normal file
@ -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))
|
||||
}
|
@ -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))
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
39
pkg/stdlib/html/document_parse.go
Normal file
39
pkg/stdlib/html/document_parse.go
Normal file
@ -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)
|
||||
}
|
@ -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-")
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -53,7 +53,6 @@ func (el *HTMLElement) Unwrap() interface{} {
|
||||
return el.selection
|
||||
}
|
||||
|
||||
|
||||
func (el *HTMLElement) Hash() uint64 {
|
||||
str, err := el.selection.Html()
|
||||
|
||||
|
@ -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(`
|
||||
<html>
|
||||
<head></head>
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
24
pkg/stdlib/html/elements.go
Normal file
24
pkg/stdlib/html/elements.go
Normal file
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
38
pkg/stdlib/html/inner_html.go
Normal file
38
pkg/stdlib/html/inner_html.go
Normal file
@ -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
|
||||
}
|
38
pkg/stdlib/html/inner_html_all.go
Normal file
38
pkg/stdlib/html/inner_html_all.go
Normal file
@ -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
|
||||
}
|
38
pkg/stdlib/html/inner_text.go
Normal file
38
pkg/stdlib/html/inner_text.go
Normal file
@ -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
|
||||
}
|
38
pkg/stdlib/html/inner_text_all.go
Normal file
38
pkg/stdlib/html/inner_text_all.go
Normal file
@ -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
|
||||
}
|
72
pkg/stdlib/html/input.go
Normal file
72
pkg/stdlib/html/input.go
Normal file
@ -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])
|
||||
}
|
@ -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,
|
||||
|
43
pkg/stdlib/html/navigate.go
Normal file
43
pkg/stdlib/html/navigate.go
Normal file
@ -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))
|
||||
}
|
87
pkg/stdlib/html/wait_class.go
Normal file
87
pkg/stdlib/html/wait_class.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
67
pkg/stdlib/html/wait_class_all.go
Normal file
67
pkg/stdlib/html/wait_class_all.go
Normal file
@ -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)
|
||||
}
|
51
pkg/stdlib/html/wait_element.go
Normal file
51
pkg/stdlib/html/wait_element.go
Normal file
@ -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)
|
||||
}
|
48
pkg/stdlib/html/wait_navigation.go
Normal file
48
pkg/stdlib/html/wait_navigation.go
Normal file
@ -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)
|
||||
}
|
@ -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
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user