1
0
mirror of https://github.com/MontFerret/ferret.git synced 2024-12-12 11:15:14 +02:00

Feature/#250 wait style (#257)

* Added WAIT_ATTR functions
This commit is contained in:
Tim Voronov 2019-03-13 22:50:29 -04:00 committed by GitHub
parent 376ad77404
commit 82f7317ebe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 539 additions and 66 deletions

View File

@ -0,0 +1,9 @@
LET url = @dynamic + "?redirect=/events"
LET doc = DOCUMENT(url, true)
WAIT_ELEMENT(doc, "#page-events")
CLICK_ALL(doc, "#wait-class-btn, #wait-class-random-btn")
WAIT_ATTR_ALL(doc, "#wait-class-content, #wait-class-random-content", "class", "alert alert-success", 10000)
RETURN ""

View File

@ -0,0 +1,18 @@
LET url = @dynamic + "?redirect=/events"
LET doc = DOCUMENT(url, true)
LET selector = "#wait-class-btn"
LET attrName = "data-ferret-x"
LET attrVal = "foobar"
WAIT_ELEMENT(doc, "#page-events")
LET el = ELEMENT(doc, selector)
LET prev = el.attributes
ATTR_SET(el, attrName, attrVal)
WAIT_ATTR(doc, selector, attrName, attrVal, 30000)
//WAIT_ATTR(el, attrName, attrVal)
LET curr = el.attributes
RETURN prev[attrName] == NONE && curr[attrName] == attrVal ? "" : "attributes should be updated"

View File

@ -0,0 +1,16 @@
LET url = @dynamic + "?redirect=/events"
LET doc = DOCUMENT(url, true)
WAIT_ELEMENT(doc, "#page-events")
// with fixed timeout
CLICK(doc, "#wait-no-class-btn")
WAIT(1000)
PRINT(ATTR_GET(ELEMENT(doc, "#wait-no-class-content"), "class"))
WAIT_NO_ATTR(doc, "#wait-no-class-content", "class", "alert alert-success")
// with random timeout
CLICK(doc, "#wait-no-class-random-btn")
WAIT_NO_ATTR(doc, "#wait-no-class-random-content", "class", "alert alert-success", 10000)
RETURN ""

View File

@ -0,0 +1,14 @@
LET url = @dynamic + "?redirect=/events"
LET doc = DOCUMENT(url, true)
WAIT_ELEMENT(doc, "#page-events")
LET el = ELEMENT(doc, "#wait-class-content")
ATTR_SET(el, "data-test", "test")
WAIT_ATTR(el, "data-test", "test")
ATTR_REMOVE(el, "class")
WAIT_ATTR(el, "class", NONE)
RETURN el.attributes.class == NONE ? "" : "attribute should be removed"

View File

@ -0,0 +1,20 @@
LET url = @dynamic + "?redirect=/events"
LET doc = DOCUMENT(url, true)
WAIT_ELEMENT(doc, "#page-events")
// with fixed timeout
LET b1 = ELEMENT(doc, "#wait-class-btn")
LET c1 = ELEMENT(doc, "#wait-class-content")
CLICK(b1)
WAIT_ATTR(c1, "class", "alert alert-success")
// with random timeout
LET b2 = ELEMENT(doc, "#wait-class-random-btn")
LET c2 = ELEMENT(doc, "#wait-class-random-content")
CLICK(b2)
WAIT_ATTR(c2, "class", "alert alert-success", 10000)
RETURN ""

View File

@ -0,0 +1,20 @@
LET url = @dynamic + "?redirect=/events"
LET doc = DOCUMENT(url, true)
WAIT_ELEMENT(doc, "#page-events")
// with fixed timeout
LET b1 = ELEMENT(doc, "#wait-no-class-btn")
LET c1 = ELEMENT(doc, "#wait-no-class-content")
CLICK(b1)
WAIT_NO_ATTR(c1, "class", "alert alert-success")
// with random timeout
LET b2 = ELEMENT(doc, "#wait-no-class-random-btn")
LET c2 = ELEMENT(doc, "#wait-no-class-random-content")
CLICK(b2)
WAIT_NO_ATTR(c2, "class", "alert alert-success", 10000)
RETURN ""

View File

@ -10,6 +10,7 @@ import (
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/drivers/cdp/eval"
"github.com/MontFerret/ferret/pkg/drivers/cdp/events"
"github.com/MontFerret/ferret/pkg/drivers/cdp/templates"
"github.com/MontFerret/ferret/pkg/drivers/common"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/logging"
@ -527,6 +528,13 @@ func (doc *HTMLDocument) MoveMouseByXY(ctx context.Context, x, y values.Float) e
}
func (doc *HTMLDocument) WaitForElement(ctx context.Context, selector values.String, when drivers.WaitEvent) error {
var operator string
if when == drivers.WaitEventPresence {
operator = "!="
} else {
operator = "=="
}
task := events.NewEvalWaitTask(
doc.client,
@ -542,7 +550,7 @@ func (doc *HTMLDocument) WaitForElement(ctx context.Context, selector values.Str
return null;
`,
eval.ParamString(selector.String()),
waitEventToEqOperator(when),
operator,
),
events.DefaultPolling,
)
@ -555,26 +563,11 @@ func (doc *HTMLDocument) WaitForElement(ctx context.Context, selector values.Str
func (doc *HTMLDocument) WaitForClassBySelector(ctx context.Context, selector, class values.String, when drivers.WaitEvent) error {
task := events.NewEvalWaitTask(
doc.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 %s null) {
return true;
}
// null means we need to repeat
return null;
`,
eval.ParamString(selector.String()),
eval.ParamString(class.String()),
waitEventToEqOperator(when),
templates.WaitBySelector(
selector,
when,
class,
fmt.Sprintf("el.className.split(' ').find(i => i === %s)", eval.ParamString(class.String())),
),
events.DefaultPolling,
)
@ -587,33 +580,11 @@ func (doc *HTMLDocument) WaitForClassBySelector(ctx context.Context, selector, c
func (doc *HTMLDocument) WaitForClassBySelectorAll(ctx context.Context, selector, class values.String, when drivers.WaitEvent) error {
task := events.NewEvalWaitTask(
doc.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 %s null) {
foundCount++;
}
});
if (foundCount === elements.length) {
return true;
}
// null means we need to repeat
return null;
`,
eval.ParamString(selector.String()),
eval.ParamString(class.String()),
waitEventToEqOperator(when),
templates.WaitBySelectorAll(
selector,
when,
class,
fmt.Sprintf("el.className.split(' ').find(i => i === %s)", eval.ParamString(class.String())),
),
events.DefaultPolling,
)
@ -641,6 +612,52 @@ func (doc *HTMLDocument) WaitForNavigation(ctx context.Context) error {
}
}
func (doc *HTMLDocument) WaitForAttributeBySelector(
ctx context.Context,
selector,
name values.String,
value core.Value,
when drivers.WaitEvent,
) error {
task := events.NewEvalWaitTask(
doc.client,
templates.WaitBySelector(
selector,
when,
value,
templates.AttributeRead(name),
),
events.DefaultPolling,
)
_, err := task.Run(ctx)
return err
}
func (doc *HTMLDocument) WaitForAttributeBySelectorAll(
ctx context.Context,
selector,
name values.String,
value core.Value,
when drivers.WaitEvent,
) error {
task := events.NewEvalWaitTask(
doc.client,
templates.WaitBySelectorAll(
selector,
when,
value,
templates.AttributeRead(name),
),
events.DefaultPolling,
)
_, err := task.Run(ctx)
return err
}
func (doc *HTMLDocument) Navigate(ctx context.Context, url values.String) error {
if url == "" {
url = BlankPageURL

View File

@ -861,6 +861,42 @@ func (el *HTMLElement) WaitForClass(ctx context.Context, class values.String, wh
return err
}
func (el *HTMLElement) WaitForAttribute(
ctx context.Context,
name values.String,
value core.Value,
when drivers.WaitEvent,
) error {
task := events.NewWaitTask(
func(ctx2 context.Context) (core.Value, error) {
current := el.GetAttribute(ctx2, name)
if when == drivers.WaitEventPresence {
// Values appeared, exit
if current.Compare(value) == 0 {
// The value does not really matter if it's not None
// None indicates that operation needs to be repeated
return values.True, nil
}
} else {
// Value disappeared, exit
if current.Compare(value) != 0 {
// The value does not really matter if it's not None
// None indicates that operation needs to be repeated
return values.True, nil
}
}
return values.None, nil
},
events.DefaultPolling,
)
_, err := task.Run(ctx)
return err
}
func (el *HTMLElement) Click(ctx context.Context) (values.Boolean, error) {
return events.DispatchEvent(ctx, el.client, el.id.objectID, "click")
}

View File

@ -4,7 +4,6 @@ import (
"context"
"encoding/json"
"fmt"
"strconv"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
@ -16,14 +15,6 @@ func PrepareEval(exp string) string {
return fmt.Sprintf("((function () {%s})())", exp)
}
func ParamString(param string) string {
return "`" + param + "`"
}
func ParamFloat(param float64) string {
return strconv.FormatFloat(param, 'f', 6, 64)
}
func Eval(ctx context.Context, client *cdp.Client, exp string, ret bool, async bool) (core.Value, error) {
args := runtime.
NewEvaluateArgs(PrepareEval(exp)).

View File

@ -0,0 +1,36 @@
package eval
import (
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"strconv"
)
func Param(input core.Value) string {
switch value := input.(type) {
case values.String:
return ParamString(string(value))
case values.Float:
return ParamFloat(float64(value))
case values.Int:
return ParamInt(int64(value))
default:
if input == values.None {
return "null"
}
return value.String()
}
}
func ParamString(param string) string {
return `"` + param + `"`
}
func ParamFloat(param float64) string {
return strconv.FormatFloat(param, 'f', 6, 64)
}
func ParamInt(param int64) string {
return strconv.FormatInt(param, 64)
}

View File

@ -7,7 +7,6 @@ import (
"math"
"strings"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/drivers/cdp/eval"
"github.com/MontFerret/ferret/pkg/drivers/cdp/events"
"github.com/MontFerret/ferret/pkg/drivers/common"
@ -403,11 +402,3 @@ func createEventBroker(client *cdp.Client) (*events.EventBroker, error) {
return broker, nil
}
func waitEventToEqOperator(when drivers.WaitEvent) string {
if when == drivers.WaitEventAbsence {
return "=="
}
return "!="
}

View File

@ -0,0 +1,14 @@
package templates
import (
"fmt"
"github.com/MontFerret/ferret/pkg/drivers/cdp/eval"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
func AttributeRead(name values.String) string {
n := name.String()
return fmt.Sprintf(`
el.attributes[%s] != null ? el.attributes[%s].value : null
`, eval.ParamString(n), eval.ParamString(n))
}

View File

@ -0,0 +1,13 @@
package templates
import (
"github.com/MontFerret/ferret/pkg/drivers"
)
func WaitEventToEqOperator(when drivers.WaitEvent) string {
if when == drivers.WaitEventPresence {
return "=="
}
return "!="
}

View File

@ -0,0 +1,36 @@
package templates
import (
"fmt"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/drivers/cdp/eval"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
)
func WaitBySelector(selector values.String, when drivers.WaitEvent, value core.Value, check string) string {
return fmt.Sprintf(
`
var el = document.querySelector(%s); // selector
if (el == null) {
return false;
}
var result = %s; // check
// when value
if (result %s %s) {
return true;
}
// null means we need to repeat
return null;
`,
eval.ParamString(selector.String()),
check,
WaitEventToEqOperator(when),
eval.Param(value),
)
}

View File

@ -0,0 +1,43 @@
package templates
import (
"fmt"
"github.com/MontFerret/ferret/pkg/drivers/cdp/eval"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/drivers"
)
func WaitBySelectorAll(selector values.String, when drivers.WaitEvent, value core.Value, check string) string {
return fmt.Sprintf(`
var elements = document.querySelectorAll(%s); // selector
if (elements == null || elements.length === 0) {
return false;
}
var resultCount = 0;
elements.forEach((el) => {
var result = %s; // check
// when
if (result %s %s) {
resultCount++;
}
});
if (resultCount === elements.length) {
return true;
}
// null means we need to repeat
return null;
`,
eval.ParamString(selector.String()),
check,
WaitEventToEqOperator(when),
eval.Param(value),
)
}

View File

@ -237,6 +237,14 @@ func (doc *HTMLDocument) WaitForClassBySelectorAll(_ context.Context, _, _ value
return core.ErrNotSupported
}
func (doc *HTMLDocument) WaitForAttributeBySelector(_ context.Context, _, _ values.String, _ core.Value, _ drivers.WaitEvent) error {
return core.ErrNotSupported
}
func (doc *HTMLDocument) WaitForAttributeBySelectorAll(_ context.Context, _, _ values.String, _ core.Value, _ drivers.WaitEvent) error {
return core.ErrNotSupported
}
func (doc *HTMLDocument) Close() error {
return nil
}

View File

@ -406,6 +406,10 @@ func (el *HTMLElement) WaitForClass(_ context.Context, _ values.String, _ driver
return core.ErrNotSupported
}
func (el *HTMLElement) WaitForAttribute(_ context.Context, _ values.String, _ core.Value, _ drivers.WaitEvent) error {
return core.ErrNotSupported
}
func (el *HTMLElement) ensureStyles(ctx context.Context) error {
if el.styles == nil {
styles, err := el.parseStyles(ctx)

View File

@ -92,6 +92,8 @@ type (
Hover(ctx context.Context) error
WaitForClass(ctx context.Context, class values.String, when WaitEvent) error
WaitForAttribute(ctx context.Context, name values.String, value core.Value, when WaitEvent) error
}
// The Document interface represents any web page loaded in the browser
@ -142,6 +144,10 @@ type (
WaitForClassBySelector(ctx context.Context, selector, class values.String, when WaitEvent) error
WaitForClassBySelectorAll(ctx context.Context, selector, class values.String, when WaitEvent) error
WaitForAttributeBySelector(ctx context.Context, selector, name values.String, value core.Value, when WaitEvent) error
WaitForAttributeBySelectorAll(ctx context.Context, selector, name values.String, value core.Value, when WaitEvent) error
}
)

View File

@ -46,6 +46,10 @@ func NewLib() map[string]core.Function {
"STYLE_GET": StyleGet,
"STYLE_REMOVE": StyleRemove,
"STYLE_SET": StyleSet,
"WAIT_ATTR": WaitAttribute,
"WAIT_NO_ATTR": WaitNoAttribute,
"WAIT_ATTR_ALL": WaitAttributeAll,
"WAIT_NO_ATTR_ALL": WaitNoAttributeAll,
"WAIT_ELEMENT": WaitElement,
"WAIT_NO_ELEMENT": WaitNoElement,
"WAIT_CLASS": WaitClass,

View File

@ -0,0 +1,100 @@
package html
import (
"context"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
func WaitAttribute(ctx context.Context, args ...core.Value) (core.Value, error) {
return waitAttributeWhen(ctx, args, drivers.WaitEventPresence)
}
func WaitNoAttribute(ctx context.Context, args ...core.Value) (core.Value, error) {
return waitAttributeWhen(ctx, args, drivers.WaitEventAbsence)
}
func waitAttributeWhen(ctx context.Context, args []core.Value, when drivers.WaitEvent) (core.Value, error) {
err := core.ValidateArgs(args, 3, 5)
if err != nil {
return values.None, err
}
// document or element
arg1 := args[0]
err = core.ValidateType(arg1, drivers.HTMLDocumentType, drivers.HTMLElementType)
if err != nil {
return values.None, err
}
// selector or attr name
err = core.ValidateType(args[1], types.String)
if err != nil {
return values.None, err
}
timeout := values.NewInt(defaultTimeout)
// if a document is passed
// WAIT_ATTR(doc, selector, attrName, attrValue, timeout)
if arg1.Type() == drivers.HTMLDocumentType {
// revalidate args with more accurate amount
err := core.ValidateArgs(args, 4, 5)
if err != nil {
return values.None, err
}
// attr name
err = core.ValidateType(args[2], types.String)
if err != nil {
return values.None, err
}
doc := arg1.(drivers.HTMLDocument)
selector := args[1].(values.String)
name := args[2].(values.String)
value := args[3]
if len(args) == 5 {
err = core.ValidateType(args[4], types.Int)
if err != nil {
return values.None, err
}
timeout = args[4].(values.Int)
}
ctx, fn := waitTimeout(ctx, timeout)
defer fn()
return values.None, doc.WaitForAttributeBySelector(ctx, selector, name, value, when)
}
el := arg1.(drivers.HTMLElement)
name := args[1].(values.String)
value := args[2]
if len(args) == 4 {
err = core.ValidateType(args[3], types.Int)
if err != nil {
return values.None, err
}
timeout = args[3].(values.Int)
}
ctx, fn := waitTimeout(ctx, timeout)
defer fn()
return values.None, el.WaitForAttribute(ctx, name, value, when)
}

View File

@ -0,0 +1,77 @@
package html
import (
"context"
"github.com/MontFerret/ferret/pkg/drivers"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// WaitClassAll 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 WaitAttributeAll(ctx context.Context, args ...core.Value) (core.Value, error) {
return waitAttributeAllWhen(ctx, args, drivers.WaitEventPresence)
}
// WaitClassAll waits for a class to disappear 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 WaitNoAttributeAll(ctx context.Context, args ...core.Value) (core.Value, error) {
return waitAttributeAllWhen(ctx, args, drivers.WaitEventAbsence)
}
func waitAttributeAllWhen(ctx context.Context, args []core.Value, when drivers.WaitEvent) (core.Value, error) {
err := core.ValidateArgs(args, 4, 5)
if err != nil {
return values.None, err
}
doc, err := toDocument(args[0])
if err != nil {
return values.None, err
}
// selector
err = core.ValidateType(args[1], types.String)
if err != nil {
return values.None, err
}
// attr name
err = core.ValidateType(args[2], types.String)
if err != nil {
return values.None, err
}
selector := args[1].(values.String)
name := args[2].(values.String)
value := args[3]
timeout := values.NewInt(defaultTimeout)
if len(args) == 5 {
err = core.ValidateType(args[4], types.Int)
if err != nil {
return values.None, err
}
timeout = args[4].(values.Int)
}
ctx, fn := waitTimeout(ctx, timeout)
defer fn()
return values.None, doc.WaitForAttributeBySelectorAll(ctx, selector, name, value, when)
}