diff --git a/.travis.yml b/.travis.yml index 3272544c..16882b3c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -54,6 +54,7 @@ jobs: script: - make cover - stage: e2e + go: stable before_script: - google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 --disable-setuid-sandbox --no-sandbox about:blank & script: diff --git a/e2e/main.go b/e2e/main.go index 9433b798..55c06caa 100644 --- a/e2e/main.go +++ b/e2e/main.go @@ -1,12 +1,14 @@ package main import ( + "context" "flag" "fmt" "github.com/MontFerret/ferret/e2e/runner" "github.com/MontFerret/ferret/e2e/server" "github.com/rs/zerolog" "os" + "os/signal" "path/filepath" "regexp" ) @@ -101,7 +103,18 @@ func main() { Filter: filterR, }) - err := r.Run() + ctx, cancel := context.WithCancel(context.Background()) + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + + go func() { + for { + <-c + cancel() + } + }() + + err := r.Run(ctx) if err != nil { os.Exit(1) diff --git a/e2e/pages/dynamic/components/pages/events/appearable.js b/e2e/pages/dynamic/components/pages/events/appearable.js new file mode 100644 index 00000000..09820f0f --- /dev/null +++ b/e2e/pages/dynamic/components/pages/events/appearable.js @@ -0,0 +1,42 @@ +import random from "../../../utils/random.js"; + +const e = React.createElement; + +function render(id) { + return e("span", { id: `${id}-content` }, ["Hello world"]); +} + +export default class AppearableComponent extends React.PureComponent { + constructor(props) { + super(props); + + this.state = { + element: props.appear === true ? null : render(props.id) + }; + } + + handleClick() { + setTimeout(() => { + this.setState({ + element: this.props.appear === true ? render(this.props.id) : null + }) + }, random()) + } + + render() { + const btnId = `${this.props.id}-btn`; + + return e("div", {className: "card"}, [ + e("div", { className: "card-header"}, [ + e("button", { + id: btnId, + className: "btn btn-primary", + onClick: this.handleClick.bind(this) + }, [ + this.props.title || "Toggle class" + ]) + ]), + e("div", { className: "card-body"}, this.state.element) + ]); + } +} \ No newline at end of file diff --git a/e2e/pages/dynamic/components/pages/events/clickable.js b/e2e/pages/dynamic/components/pages/events/clickable.js index 426620af..f0d3b48c 100644 --- a/e2e/pages/dynamic/components/pages/events/clickable.js +++ b/e2e/pages/dynamic/components/pages/events/clickable.js @@ -1,3 +1,5 @@ +import random from "../../../utils/random.js"; + const e = React.createElement; export default class ClickableComponent extends React.PureComponent { @@ -5,7 +7,7 @@ export default class ClickableComponent extends React.PureComponent { super(props); this.state = { - clicked: false + show: props.show === true }; } @@ -13,12 +15,12 @@ export default class ClickableComponent extends React.PureComponent { let timeout = 500; if (this.props.randomTimeout) { - timeout = Math.ceil(Math.random() * 1000 * 10); + timeout = random(); } setTimeout(() => { this.setState({ - clicked: !this.state.clicked + show: !this.state.show }) }, timeout) } @@ -28,7 +30,7 @@ export default class ClickableComponent extends React.PureComponent { const contentId = `${this.props.id}-content`; const classNames = ["alert"]; - if (this.state.clicked) { + if (this.state.show === true) { classNames.push("alert-success"); } @@ -39,7 +41,7 @@ export default class ClickableComponent extends React.PureComponent { className: "btn btn-primary", onClick: this.handleClick.bind(this) }, [ - "Toggle class" + this.props.title || "Toggle class" ]) ]), e("div", { className: "card-body"}, [ diff --git a/e2e/pages/dynamic/components/pages/events/index.js b/e2e/pages/dynamic/components/pages/events/index.js index b52945e5..8735891f 100644 --- a/e2e/pages/dynamic/components/pages/events/index.js +++ b/e2e/pages/dynamic/components/pages/events/index.js @@ -1,19 +1,60 @@ import Hoverable from "./hoverable.js"; import Clickable from "./clickable.js"; +import Appearable from "./appearable.js"; const e = React.createElement; export default class EventsPage extends React.Component { render() { - return e("div", { className: "row", id: "page-events" }, [ - e("div", { className: "col-lg-4"}, [ - e(Hoverable), + return e("div", { id: "page-events" }, [ + e("div", { className: "row" }, [ + e("div", { className: "col-lg-4"}, [ + e(Hoverable), + ]), + e("div", { className: "col-lg-4"}, [ + e(Clickable, { + id: "wait-class", + title: "Add class" + }) + ]), + e("div", { className: "col-lg-4"}, [ + e(Clickable, { + id: "wait-class-random", + title: "Add class 2", + randomTimeout: true + }) + ]) ]), - e("div", { className: "col-lg-4"}, [ - e(Clickable, { id: "wait-class" }) - ]), - e("div", { className: "col-lg-4"}, [ - e(Clickable, { id: "wait-class-random", randomTimeout: true }) + e("div", { className: "row" }, [ + e("div", { className: "col-lg-4"}, [ + e(Clickable, { + id: "wait-no-class", + title: "Remove class", + show: true + }) + ]), + e("div", { className: "col-lg-4"}, [ + e(Clickable, { + id: "wait-no-class-random", + title: "Remove class 2", + show: true, + randomTimeout: true + }) + ]), + e("div", { className: "col-lg-4"}, [ + e(Appearable, { + id: "wait-element", + appear: true, + title: "Appearable" + }) + ]), + e("div", { className: "col-lg-4"}, [ + e(Appearable, { + id: "wait-no-element", + appear: false, + title: "Disappearable" + }) + ]) ]) ]) } diff --git a/e2e/pages/dynamic/utils/random.js b/e2e/pages/dynamic/utils/random.js new file mode 100644 index 00000000..2f401403 --- /dev/null +++ b/e2e/pages/dynamic/utils/random.js @@ -0,0 +1,13 @@ +export default function random(min = 1000, max = 5000) { + const val = Math.random() * 1000 * 10; + + if (val < min) { + return min; + } + + if (val > max) { + return max; + } + + return val; +} \ No newline at end of file diff --git a/e2e/runner/runner.go b/e2e/runner/runner.go index fab88fed..389781eb 100644 --- a/e2e/runner/runner.go +++ b/e2e/runner/runner.go @@ -52,9 +52,7 @@ func New(logger zerolog.Logger, settings Settings) *Runner { } } -func (r *Runner) Run() error { - ctx := context.Background() - +func (r *Runner) Run(ctx context.Context) error { ctx = drivers.WithContext( ctx, cdp.NewDriver(cdp.WithAddress(r.settings.CDPAddress)), diff --git a/e2e/tests/doc_wait_class_all_d.fql b/e2e/tests/doc_wait_class_all_d.fql index beb96031..2d1c4901 100644 --- a/e2e/tests/doc_wait_class_all_d.fql +++ b/e2e/tests/doc_wait_class_all_d.fql @@ -3,7 +3,7 @@ LET doc = DOCUMENT(url, true) WAIT_ELEMENT(doc, "#page-events") -CLICK_ALL(doc, ".clickable button") -WAIT_CLASS_ALL(doc, ".clickable .card-body div", "alert-success", 10000) +CLICK_ALL(doc, "#wait-class-btn, #wait-class-random-btn") +WAIT_CLASS_ALL(doc, "#wait-class-content, #wait-class-random-content", "alert-success", 10000) RETURN "" \ No newline at end of file diff --git a/e2e/tests/doc_wait_element_d.fql b/e2e/tests/doc_wait_element_d.fql new file mode 100644 index 00000000..1c61cecf --- /dev/null +++ b/e2e/tests/doc_wait_element_d.fql @@ -0,0 +1,13 @@ +LET url = @dynamic + "?redirect=/events" +LET doc = DOCUMENT(url, true) +LET pageSelector = "#page-events" +LET elemSelector = "#wait-element-content" +LET btnSelector = "#wait-element-btn" + +WAIT_ELEMENT(doc, pageSelector) + +CLICK(doc, btnSelector) + +WAIT_ELEMENT(doc, elemSelector, 10000) + +RETURN ELEMENT_EXISTS(doc, elemSelector) ? "" : "element not found" \ No newline at end of file diff --git a/e2e/tests/doc_wait_no_class_all_d.fql b/e2e/tests/doc_wait_no_class_all_d.fql new file mode 100644 index 00000000..2bdb5062 --- /dev/null +++ b/e2e/tests/doc_wait_no_class_all_d.fql @@ -0,0 +1,9 @@ +LET url = @dynamic + "?redirect=/events" +LET doc = DOCUMENT(url, true) + +WAIT_ELEMENT(doc, "#page-events") + +CLICK_ALL(doc, "#wait-no-class-btn, #wait-no-class-random-btn") +WAIT_NO_CLASS_ALL(doc, "#wait-no-class-content, #wait-no-class-random-content", "alert-success", 10000) + +RETURN "" \ No newline at end of file diff --git a/e2e/tests/doc_wait_no_class_d.fql b/e2e/tests/doc_wait_no_class_d.fql new file mode 100644 index 00000000..510a8a3d --- /dev/null +++ b/e2e/tests/doc_wait_no_class_d.fql @@ -0,0 +1,14 @@ +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_NO_CLASS(doc, "#wait-no-class-content", "alert-success") + +// with random timeout +CLICK(doc, "#wait-no-class-random-btn") +WAIT_NO_CLASS(doc, "#wait-no-class-random-content", "alert-success", 10000) + +RETURN "" \ No newline at end of file diff --git a/e2e/tests/doc_wait_no_element_d.fql b/e2e/tests/doc_wait_no_element_d.fql new file mode 100644 index 00000000..7ca94a6a --- /dev/null +++ b/e2e/tests/doc_wait_no_element_d.fql @@ -0,0 +1,13 @@ +LET url = @dynamic + "?redirect=/events" +LET doc = DOCUMENT(url, true) +LET pageSelector = "#page-events" +LET elemSelector = "#wait-no-element-content" +LET btnSelector = "#wait-no-element-btn" + +WAIT_ELEMENT(doc, pageSelector) + +CLICK(doc, btnSelector) + +WAIT_NO_ELEMENT(doc, elemSelector, 10000) + +RETURN ELEMENT_EXISTS(doc, elemSelector) ? "element should not be found" : "" \ No newline at end of file diff --git a/e2e/tests/el_wait_no_class_d.fql b/e2e/tests/el_wait_no_class_d.fql new file mode 100644 index 00000000..b15136e0 --- /dev/null +++ b/e2e/tests/el_wait_no_class_d.fql @@ -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_CLASS(c1, "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_CLASS(c2, "alert-success", 10000) + +RETURN "" \ No newline at end of file diff --git a/pkg/drivers/cdp/document.go b/pkg/drivers/cdp/document.go index e2b989b3..23568186 100644 --- a/pkg/drivers/cdp/document.go +++ b/pkg/drivers/cdp/document.go @@ -526,44 +526,23 @@ func (doc *HTMLDocument) MoveMouseByXY(ctx context.Context, x, y values.Float) e ) } -func (doc *HTMLDocument) WaitForSelector(ctx context.Context, selector values.String) error { +func (doc *HTMLDocument) WaitForElement(ctx context.Context, selector values.String, when drivers.WaitEvent) error { + task := events.NewEvalWaitTask( doc.client, - fmt.Sprintf(` - var el = document.querySelector(%s); - if (el != null) { - return true; - } - // null means we need to repeat - return null; - `, eval.ParamString(selector.String())), - events.DefaultPolling, - ) - - _, err := task.Run(ctx) - - return err -} - -func (doc *HTMLDocument) WaitForClassBySelector(ctx context.Context, selector, class values.String) 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; - `, + fmt.Sprintf( + ` + var el = document.querySelector(%s); + + if (el %s null) { + return true; + } + + // null means we need to repeat + return null; + `, eval.ParamString(selector.String()), - eval.ParamString(class.String()), + waitEventToEqOperator(when), ), events.DefaultPolling, ) @@ -573,22 +552,58 @@ func (doc *HTMLDocument) WaitForClassBySelector(ctx context.Context, selector, c return err } -func (doc *HTMLDocument) WaitForClassBySelectorAll(ctx context.Context, selector, class values.String) error { +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), + ), + events.DefaultPolling, + ) + + _, err := task.Run(ctx) + + return err +} + +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 != null) { + if (found %s null) { foundCount++; } }); + if (foundCount === elements.length) { return true; } @@ -598,6 +613,7 @@ func (doc *HTMLDocument) WaitForClassBySelectorAll(ctx context.Context, selector `, eval.ParamString(selector.String()), eval.ParamString(class.String()), + waitEventToEqOperator(when), ), events.DefaultPolling, ) diff --git a/pkg/drivers/cdp/element.go b/pkg/drivers/cdp/element.go index 5cfef33a..630be544 100644 --- a/pkg/drivers/cdp/element.go +++ b/pkg/drivers/cdp/element.go @@ -695,7 +695,7 @@ func (el *HTMLElement) ExistsBySelector(ctx context.Context, selector values.Str return values.True } -func (el *HTMLElement) WaitForClass(ctx context.Context, class values.String) error { +func (el *HTMLElement) WaitForClass(ctx context.Context, class values.String, when drivers.WaitEvent) error { task := events.NewWaitTask( func(ctx2 context.Context) (core.Value, error) { current := el.GetAttribute(ctx2, "class") @@ -708,9 +708,28 @@ func (el *HTMLElement) WaitForClass(ctx context.Context, class values.String) er classStr := string(class) classes := strings.Split(string(str), " ") - for _, c := range classes { - if c == classStr { - return values.True, nil + if when != drivers.WaitEventAbsence { + for _, c := range classes { + if c == classStr { + // The value does not really matter if it's not None + // None indicates that operation needs to be repeated + return values.True, nil + } + } + } else { + var found values.Boolean + + for _, c := range classes { + if c == classStr { + found = values.True + break + } + } + + if found == values.False { + // The value does not really matter if it's not None + // None indicates that operation needs to be repeated + return values.False, nil } } diff --git a/pkg/drivers/cdp/helpers.go b/pkg/drivers/cdp/helpers.go index 40685471..ce921eb7 100644 --- a/pkg/drivers/cdp/helpers.go +++ b/pkg/drivers/cdp/helpers.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "errors" + "github.com/MontFerret/ferret/pkg/drivers" "math" "strings" @@ -402,3 +403,11 @@ func createEventBroker(client *cdp.Client) (*events.EventBroker, error) { return broker, nil } + +func waitEventToEqOperator(when drivers.WaitEvent) string { + if when == drivers.WaitEventAbsence { + return "==" + } + + return "!=" +} diff --git a/pkg/drivers/http/document.go b/pkg/drivers/http/document.go index 0cbc75b9..b82a1acf 100644 --- a/pkg/drivers/http/document.go +++ b/pkg/drivers/http/document.go @@ -225,15 +225,15 @@ func (doc *HTMLDocument) WaitForNavigation(_ context.Context) error { return core.ErrNotSupported } -func (doc *HTMLDocument) WaitForSelector(_ context.Context, _ values.String) error { +func (doc *HTMLDocument) WaitForElement(_ context.Context, _ values.String, _ drivers.WaitEvent) error { return core.ErrNotSupported } -func (doc *HTMLDocument) WaitForClassBySelector(_ context.Context, _, _ values.String) error { +func (doc *HTMLDocument) WaitForClassBySelector(_ context.Context, _, _ values.String, _ drivers.WaitEvent) error { return core.ErrNotSupported } -func (doc *HTMLDocument) WaitForClassBySelectorAll(_ context.Context, _, _ values.String) error { +func (doc *HTMLDocument) WaitForClassBySelectorAll(_ context.Context, _, _ values.String, _ drivers.WaitEvent) error { return core.ErrNotSupported } diff --git a/pkg/drivers/http/element.go b/pkg/drivers/http/element.go index 5d0176c9..17f3fb9b 100644 --- a/pkg/drivers/http/element.go +++ b/pkg/drivers/http/element.go @@ -309,7 +309,7 @@ func (nd *HTMLElement) Hover(_ context.Context) error { return core.ErrNotSupported } -func (nd *HTMLElement) WaitForClass(_ context.Context, _ values.String) error { +func (nd *HTMLElement) WaitForClass(_ context.Context, _ values.String, _ drivers.WaitEvent) error { return core.ErrNotSupported } diff --git a/pkg/drivers/value.go b/pkg/drivers/value.go index 8cfd36ff..3bd91192 100644 --- a/pkg/drivers/value.go +++ b/pkg/drivers/value.go @@ -10,6 +10,9 @@ import ( ) type ( + // WaitEvent is an enum that represents what event is needed to wait for + WaitEvent int + // Node is an interface from which a number of DOM API object types inherit. // It allows those types to be treated similarly; // for example, inheriting the same set of methods, or being tested in the same way. @@ -74,7 +77,7 @@ type ( Hover(ctx context.Context) error - WaitForClass(ctx context.Context, class values.String) error + WaitForClass(ctx context.Context, class values.String, when WaitEvent) error } // The Document interface represents any web page loaded in the browser @@ -120,10 +123,18 @@ type ( WaitForNavigation(ctx context.Context) error - WaitForSelector(ctx context.Context, selector values.String) error + WaitForElement(ctx context.Context, selector values.String, when WaitEvent) error - WaitForClassBySelector(ctx context.Context, selector, class values.String) error + WaitForClassBySelector(ctx context.Context, selector, class values.String, when WaitEvent) error - WaitForClassBySelectorAll(ctx context.Context, selector, class values.String) error + WaitForClassBySelectorAll(ctx context.Context, selector, class values.String, when WaitEvent) error } ) + +const ( + // Event indicating to wait for value to appear + WaitEventPresence = 0 + + // Event indicating to wait for value to disappear + WaitEventAbsence = 1 +) diff --git a/pkg/stdlib/html/lib.go b/pkg/stdlib/html/lib.go index a82bcbc1..2a936dd0 100644 --- a/pkg/stdlib/html/lib.go +++ b/pkg/stdlib/html/lib.go @@ -14,36 +14,39 @@ const defaultTimeout = 5000 func NewLib() map[string]core.Function { return map[string]core.Function{ - "CLICK": Click, - "CLICK_ALL": ClickAll, - "DOCUMENT": Document, - "DOWNLOAD": Download, - "ELEMENT": Element, - "ELEMENT_EXISTS": ElementExists, - "ELEMENTS": Elements, - "ELEMENTS_COUNT": ElementsCount, - "HOVER": Hover, - "INNER_HTML": InnerHTML, - "INNER_HTML_ALL": InnerHTMLAll, - "INNER_TEXT": InnerText, - "INNER_TEXT_ALL": InnerTextAll, - "INPUT": Input, - "MOUSE": MouseMoveXY, - "NAVIGATE": Navigate, - "NAVIGATE_BACK": NavigateBack, - "NAVIGATE_FORWARD": NavigateForward, - "PAGINATION": Pagination, - "PDF": PDF, - "SCREENSHOT": Screenshot, - "SCROLL": ScrollXY, - "SCROLL_BOTTOM": ScrollBottom, - "SCROLL_ELEMENT": ScrollInto, - "SCROLL_TOP": ScrollTop, - "SELECT": Select, - "WAIT_ELEMENT": WaitElement, - "WAIT_CLASS": WaitClass, - "WAIT_CLASS_ALL": WaitClassAll, - "WAIT_NAVIGATION": WaitNavigation, + "CLICK": Click, + "CLICK_ALL": ClickAll, + "DOCUMENT": Document, + "DOWNLOAD": Download, + "ELEMENT": Element, + "ELEMENT_EXISTS": ElementExists, + "ELEMENTS": Elements, + "ELEMENTS_COUNT": ElementsCount, + "HOVER": Hover, + "INNER_HTML": InnerHTML, + "INNER_HTML_ALL": InnerHTMLAll, + "INNER_TEXT": InnerText, + "INNER_TEXT_ALL": InnerTextAll, + "INPUT": Input, + "MOUSE": MouseMoveXY, + "NAVIGATE": Navigate, + "NAVIGATE_BACK": NavigateBack, + "NAVIGATE_FORWARD": NavigateForward, + "PAGINATION": Pagination, + "PDF": PDF, + "SCREENSHOT": Screenshot, + "SCROLL": ScrollXY, + "SCROLL_BOTTOM": ScrollBottom, + "SCROLL_ELEMENT": ScrollInto, + "SCROLL_TOP": ScrollTop, + "SELECT": Select, + "WAIT_ELEMENT": WaitElement, + "WAIT_NO_ELEMENT": WaitNoElement, + "WAIT_CLASS": WaitClass, + "WAIT_NO_CLASS": WaitNoClass, + "WAIT_CLASS_ALL": WaitClassAll, + "WAIT_NO_CLASS_ALL": WaitNoClassAll, + "WAIT_NAVIGATION": WaitNavigation, } } diff --git a/pkg/stdlib/html/wait_class.go b/pkg/stdlib/html/wait_class.go index 06c7708a..aae6f662 100644 --- a/pkg/stdlib/html/wait_class.go +++ b/pkg/stdlib/html/wait_class.go @@ -18,6 +18,23 @@ import ( // @param timeout (Int, optional) - If document is passed, this param must represent timeout. // Otherwise not passed. func WaitClass(ctx context.Context, args ...core.Value) (core.Value, error) { + return waitClassWhen(ctx, args, drivers.WaitEventPresence) +} + +// WaitClass waits for a class to disappear 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 WaitNoClass(ctx context.Context, args ...core.Value) (core.Value, error) { + return waitClassWhen(ctx, args, drivers.WaitEventAbsence) +} + +func waitClassWhen(ctx context.Context, args []core.Value, when drivers.WaitEvent) (core.Value, error) { err := core.ValidateArgs(args, 2, 4) if err != nil { @@ -74,7 +91,7 @@ func WaitClass(ctx context.Context, args ...core.Value) (core.Value, error) { ctx, fn := waitTimeout(ctx, timeout) defer fn() - return values.None, doc.WaitForClassBySelector(ctx, selector, class) + return values.None, doc.WaitForClassBySelector(ctx, selector, class, when) } el := arg1.(drivers.HTMLElement) @@ -93,5 +110,5 @@ func WaitClass(ctx context.Context, args ...core.Value) (core.Value, error) { ctx, fn := waitTimeout(ctx, timeout) defer fn() - return values.None, el.WaitForClass(ctx, class) + return values.None, el.WaitForClass(ctx, class, when) } diff --git a/pkg/stdlib/html/wait_class_all.go b/pkg/stdlib/html/wait_class_all.go index 2ea94b1b..f55fdd16 100644 --- a/pkg/stdlib/html/wait_class_all.go +++ b/pkg/stdlib/html/wait_class_all.go @@ -2,6 +2,7 @@ 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" @@ -14,6 +15,20 @@ import ( // @param class (String) - String of target CSS class. // @param timeout (Int, optional) - Optional timeout. func WaitClassAll(ctx context.Context, args ...core.Value) (core.Value, error) { + return waitClassAllWhen(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 WaitNoClassAll(ctx context.Context, args ...core.Value) (core.Value, error) { + return waitClassAllWhen(ctx, args, drivers.WaitEventAbsence) +} + +func waitClassAllWhen(ctx context.Context, args []core.Value, when drivers.WaitEvent) (core.Value, error) { err := core.ValidateArgs(args, 3, 4) if err != nil { @@ -57,5 +72,5 @@ func WaitClassAll(ctx context.Context, args ...core.Value) (core.Value, error) { ctx, fn := waitTimeout(ctx, timeout) defer fn() - return values.None, doc.WaitForClassBySelectorAll(ctx, selector, class) + return values.None, doc.WaitForClassBySelectorAll(ctx, selector, class, when) } diff --git a/pkg/stdlib/html/wait_element.go b/pkg/stdlib/html/wait_element.go index 9a29331d..c34c5177 100644 --- a/pkg/stdlib/html/wait_element.go +++ b/pkg/stdlib/html/wait_element.go @@ -2,6 +2,7 @@ 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" @@ -13,6 +14,19 @@ import ( // @param selector (String) - Target element's selector. // @param timeout (Int, optional) - Optional timeout. Default 5000 ms. func WaitElement(ctx context.Context, args ...core.Value) (core.Value, error) { + return waitElementWhen(ctx, args, drivers.WaitEventPresence) +} + +// WaitNoElements waits for element to disappear in the DOM. +// Stops the execution until it does not find an element or operation times out. +// @param doc (HTMLDocument) - Driver HTMLDocument. +// @param selector (String) - Target element's selector. +// @param timeout (Int, optional) - Optional timeout. Default 5000 ms. +func WaitNoElement(ctx context.Context, args ...core.Value) (core.Value, error) { + return waitElementWhen(ctx, args, drivers.WaitEventAbsence) +} + +func waitElementWhen(ctx context.Context, args []core.Value, when drivers.WaitEvent) (core.Value, error) { err := core.ValidateArgs(args, 2, 3) if err != nil { @@ -41,5 +55,5 @@ func WaitElement(ctx context.Context, args ...core.Value) (core.Value, error) { ctx, fn := waitTimeout(ctx, timeout) defer fn() - return values.None, doc.WaitForSelector(ctx, values.NewString(selector)) + return values.None, doc.WaitForElement(ctx, values.NewString(selector), when) }