mirror of
https://github.com/MontFerret/ferret.git
synced 2025-06-04 23:27:31 +02:00
Feature/#229 wait no element (#249)
* Added possibility to wait for an element or a class absence
This commit is contained in:
parent
5188f9e71b
commit
d0caef8be7
@ -54,6 +54,7 @@ jobs:
|
|||||||
script:
|
script:
|
||||||
- make cover
|
- make cover
|
||||||
- stage: e2e
|
- stage: e2e
|
||||||
|
go: stable
|
||||||
before_script:
|
before_script:
|
||||||
- google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 --disable-setuid-sandbox --no-sandbox about:blank &
|
- google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 --disable-setuid-sandbox --no-sandbox about:blank &
|
||||||
script:
|
script:
|
||||||
|
15
e2e/main.go
15
e2e/main.go
@ -1,12 +1,14 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/MontFerret/ferret/e2e/runner"
|
"github.com/MontFerret/ferret/e2e/runner"
|
||||||
"github.com/MontFerret/ferret/e2e/server"
|
"github.com/MontFerret/ferret/e2e/server"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
)
|
)
|
||||||
@ -101,7 +103,18 @@ func main() {
|
|||||||
Filter: filterR,
|
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 {
|
if err != nil {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
42
e2e/pages/dynamic/components/pages/events/appearable.js
Normal file
42
e2e/pages/dynamic/components/pages/events/appearable.js
Normal file
@ -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)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
import random from "../../../utils/random.js";
|
||||||
|
|
||||||
const e = React.createElement;
|
const e = React.createElement;
|
||||||
|
|
||||||
export default class ClickableComponent extends React.PureComponent {
|
export default class ClickableComponent extends React.PureComponent {
|
||||||
@ -5,7 +7,7 @@ export default class ClickableComponent extends React.PureComponent {
|
|||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
clicked: false
|
show: props.show === true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -13,12 +15,12 @@ export default class ClickableComponent extends React.PureComponent {
|
|||||||
let timeout = 500;
|
let timeout = 500;
|
||||||
|
|
||||||
if (this.props.randomTimeout) {
|
if (this.props.randomTimeout) {
|
||||||
timeout = Math.ceil(Math.random() * 1000 * 10);
|
timeout = random();
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
clicked: !this.state.clicked
|
show: !this.state.show
|
||||||
})
|
})
|
||||||
}, timeout)
|
}, timeout)
|
||||||
}
|
}
|
||||||
@ -28,7 +30,7 @@ export default class ClickableComponent extends React.PureComponent {
|
|||||||
const contentId = `${this.props.id}-content`;
|
const contentId = `${this.props.id}-content`;
|
||||||
const classNames = ["alert"];
|
const classNames = ["alert"];
|
||||||
|
|
||||||
if (this.state.clicked) {
|
if (this.state.show === true) {
|
||||||
classNames.push("alert-success");
|
classNames.push("alert-success");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,7 +41,7 @@ export default class ClickableComponent extends React.PureComponent {
|
|||||||
className: "btn btn-primary",
|
className: "btn btn-primary",
|
||||||
onClick: this.handleClick.bind(this)
|
onClick: this.handleClick.bind(this)
|
||||||
}, [
|
}, [
|
||||||
"Toggle class"
|
this.props.title || "Toggle class"
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
e("div", { className: "card-body"}, [
|
e("div", { className: "card-body"}, [
|
||||||
|
@ -1,19 +1,60 @@
|
|||||||
import Hoverable from "./hoverable.js";
|
import Hoverable from "./hoverable.js";
|
||||||
import Clickable from "./clickable.js";
|
import Clickable from "./clickable.js";
|
||||||
|
import Appearable from "./appearable.js";
|
||||||
|
|
||||||
const e = React.createElement;
|
const e = React.createElement;
|
||||||
|
|
||||||
export default class EventsPage extends React.Component {
|
export default class EventsPage extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
return e("div", { className: "row", id: "page-events" }, [
|
return e("div", { id: "page-events" }, [
|
||||||
e("div", { className: "col-lg-4"}, [
|
e("div", { className: "row" }, [
|
||||||
e(Hoverable),
|
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("div", { className: "row" }, [
|
||||||
e(Clickable, { id: "wait-class" })
|
e("div", { className: "col-lg-4"}, [
|
||||||
]),
|
e(Clickable, {
|
||||||
e("div", { className: "col-lg-4"}, [
|
id: "wait-no-class",
|
||||||
e(Clickable, { id: "wait-class-random", randomTimeout: true })
|
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"
|
||||||
|
})
|
||||||
|
])
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
13
e2e/pages/dynamic/utils/random.js
Normal file
13
e2e/pages/dynamic/utils/random.js
Normal file
@ -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;
|
||||||
|
}
|
@ -52,9 +52,7 @@ func New(logger zerolog.Logger, settings Settings) *Runner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Runner) Run() error {
|
func (r *Runner) Run(ctx context.Context) error {
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
ctx = drivers.WithContext(
|
ctx = drivers.WithContext(
|
||||||
ctx,
|
ctx,
|
||||||
cdp.NewDriver(cdp.WithAddress(r.settings.CDPAddress)),
|
cdp.NewDriver(cdp.WithAddress(r.settings.CDPAddress)),
|
||||||
|
@ -3,7 +3,7 @@ LET doc = DOCUMENT(url, true)
|
|||||||
|
|
||||||
WAIT_ELEMENT(doc, "#page-events")
|
WAIT_ELEMENT(doc, "#page-events")
|
||||||
|
|
||||||
CLICK_ALL(doc, ".clickable button")
|
CLICK_ALL(doc, "#wait-class-btn, #wait-class-random-btn")
|
||||||
WAIT_CLASS_ALL(doc, ".clickable .card-body div", "alert-success", 10000)
|
WAIT_CLASS_ALL(doc, "#wait-class-content, #wait-class-random-content", "alert-success", 10000)
|
||||||
|
|
||||||
RETURN ""
|
RETURN ""
|
13
e2e/tests/doc_wait_element_d.fql
Normal file
13
e2e/tests/doc_wait_element_d.fql
Normal file
@ -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"
|
9
e2e/tests/doc_wait_no_class_all_d.fql
Normal file
9
e2e/tests/doc_wait_no_class_all_d.fql
Normal file
@ -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 ""
|
14
e2e/tests/doc_wait_no_class_d.fql
Normal file
14
e2e/tests/doc_wait_no_class_d.fql
Normal file
@ -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 ""
|
13
e2e/tests/doc_wait_no_element_d.fql
Normal file
13
e2e/tests/doc_wait_no_element_d.fql
Normal file
@ -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" : ""
|
20
e2e/tests/el_wait_no_class_d.fql
Normal file
20
e2e/tests/el_wait_no_class_d.fql
Normal 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_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 ""
|
@ -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(
|
task := events.NewEvalWaitTask(
|
||||||
doc.client,
|
doc.client,
|
||||||
fmt.Sprintf(`
|
fmt.Sprintf(
|
||||||
var el = document.querySelector(%s);
|
`
|
||||||
if (el != null) {
|
var el = document.querySelector(%s);
|
||||||
return true;
|
|
||||||
}
|
if (el %s null) {
|
||||||
// null means we need to repeat
|
return true;
|
||||||
return null;
|
}
|
||||||
`, eval.ParamString(selector.String())),
|
|
||||||
events.DefaultPolling,
|
// null means we need to repeat
|
||||||
)
|
return null;
|
||||||
|
`,
|
||||||
_, 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;
|
|
||||||
`,
|
|
||||||
eval.ParamString(selector.String()),
|
eval.ParamString(selector.String()),
|
||||||
eval.ParamString(class.String()),
|
waitEventToEqOperator(when),
|
||||||
),
|
),
|
||||||
events.DefaultPolling,
|
events.DefaultPolling,
|
||||||
)
|
)
|
||||||
@ -573,22 +552,58 @@ func (doc *HTMLDocument) WaitForClassBySelector(ctx context.Context, selector, c
|
|||||||
return err
|
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(
|
task := events.NewEvalWaitTask(
|
||||||
doc.client,
|
doc.client,
|
||||||
fmt.Sprintf(`
|
fmt.Sprintf(`
|
||||||
var elements = document.querySelectorAll(%s);
|
var elements = document.querySelectorAll(%s);
|
||||||
|
|
||||||
if (elements == null || elements.length === 0) {
|
if (elements == null || elements.length === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var className = %s;
|
var className = %s;
|
||||||
var foundCount = 0;
|
var foundCount = 0;
|
||||||
|
|
||||||
elements.forEach((el) => {
|
elements.forEach((el) => {
|
||||||
var found = el.className.split(' ').find(i => i === className);
|
var found = el.className.split(' ').find(i => i === className);
|
||||||
if (found != null) {
|
if (found %s null) {
|
||||||
foundCount++;
|
foundCount++;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (foundCount === elements.length) {
|
if (foundCount === elements.length) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -598,6 +613,7 @@ func (doc *HTMLDocument) WaitForClassBySelectorAll(ctx context.Context, selector
|
|||||||
`,
|
`,
|
||||||
eval.ParamString(selector.String()),
|
eval.ParamString(selector.String()),
|
||||||
eval.ParamString(class.String()),
|
eval.ParamString(class.String()),
|
||||||
|
waitEventToEqOperator(when),
|
||||||
),
|
),
|
||||||
events.DefaultPolling,
|
events.DefaultPolling,
|
||||||
)
|
)
|
||||||
|
@ -695,7 +695,7 @@ func (el *HTMLElement) ExistsBySelector(ctx context.Context, selector values.Str
|
|||||||
return values.True
|
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(
|
task := events.NewWaitTask(
|
||||||
func(ctx2 context.Context) (core.Value, error) {
|
func(ctx2 context.Context) (core.Value, error) {
|
||||||
current := el.GetAttribute(ctx2, "class")
|
current := el.GetAttribute(ctx2, "class")
|
||||||
@ -708,9 +708,28 @@ func (el *HTMLElement) WaitForClass(ctx context.Context, class values.String) er
|
|||||||
classStr := string(class)
|
classStr := string(class)
|
||||||
classes := strings.Split(string(str), " ")
|
classes := strings.Split(string(str), " ")
|
||||||
|
|
||||||
for _, c := range classes {
|
if when != drivers.WaitEventAbsence {
|
||||||
if c == classStr {
|
for _, c := range classes {
|
||||||
return values.True, nil
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/MontFerret/ferret/pkg/drivers"
|
||||||
"math"
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -402,3 +403,11 @@ func createEventBroker(client *cdp.Client) (*events.EventBroker, error) {
|
|||||||
|
|
||||||
return broker, nil
|
return broker, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func waitEventToEqOperator(when drivers.WaitEvent) string {
|
||||||
|
if when == drivers.WaitEventAbsence {
|
||||||
|
return "=="
|
||||||
|
}
|
||||||
|
|
||||||
|
return "!="
|
||||||
|
}
|
||||||
|
@ -225,15 +225,15 @@ func (doc *HTMLDocument) WaitForNavigation(_ context.Context) error {
|
|||||||
return core.ErrNotSupported
|
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
|
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
|
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
|
return core.ErrNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,7 +309,7 @@ func (nd *HTMLElement) Hover(_ context.Context) error {
|
|||||||
return core.ErrNotSupported
|
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
|
return core.ErrNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type (
|
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.
|
// Node is an interface from which a number of DOM API object types inherit.
|
||||||
// It allows those types to be treated similarly;
|
// It allows those types to be treated similarly;
|
||||||
// for example, inheriting the same set of methods, or being tested in the same way.
|
// 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
|
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
|
// The Document interface represents any web page loaded in the browser
|
||||||
@ -120,10 +123,18 @@ type (
|
|||||||
|
|
||||||
WaitForNavigation(ctx context.Context) error
|
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
|
||||||
|
)
|
||||||
|
@ -14,36 +14,39 @@ const defaultTimeout = 5000
|
|||||||
|
|
||||||
func NewLib() map[string]core.Function {
|
func NewLib() map[string]core.Function {
|
||||||
return map[string]core.Function{
|
return map[string]core.Function{
|
||||||
"CLICK": Click,
|
"CLICK": Click,
|
||||||
"CLICK_ALL": ClickAll,
|
"CLICK_ALL": ClickAll,
|
||||||
"DOCUMENT": Document,
|
"DOCUMENT": Document,
|
||||||
"DOWNLOAD": Download,
|
"DOWNLOAD": Download,
|
||||||
"ELEMENT": Element,
|
"ELEMENT": Element,
|
||||||
"ELEMENT_EXISTS": ElementExists,
|
"ELEMENT_EXISTS": ElementExists,
|
||||||
"ELEMENTS": Elements,
|
"ELEMENTS": Elements,
|
||||||
"ELEMENTS_COUNT": ElementsCount,
|
"ELEMENTS_COUNT": ElementsCount,
|
||||||
"HOVER": Hover,
|
"HOVER": Hover,
|
||||||
"INNER_HTML": InnerHTML,
|
"INNER_HTML": InnerHTML,
|
||||||
"INNER_HTML_ALL": InnerHTMLAll,
|
"INNER_HTML_ALL": InnerHTMLAll,
|
||||||
"INNER_TEXT": InnerText,
|
"INNER_TEXT": InnerText,
|
||||||
"INNER_TEXT_ALL": InnerTextAll,
|
"INNER_TEXT_ALL": InnerTextAll,
|
||||||
"INPUT": Input,
|
"INPUT": Input,
|
||||||
"MOUSE": MouseMoveXY,
|
"MOUSE": MouseMoveXY,
|
||||||
"NAVIGATE": Navigate,
|
"NAVIGATE": Navigate,
|
||||||
"NAVIGATE_BACK": NavigateBack,
|
"NAVIGATE_BACK": NavigateBack,
|
||||||
"NAVIGATE_FORWARD": NavigateForward,
|
"NAVIGATE_FORWARD": NavigateForward,
|
||||||
"PAGINATION": Pagination,
|
"PAGINATION": Pagination,
|
||||||
"PDF": PDF,
|
"PDF": PDF,
|
||||||
"SCREENSHOT": Screenshot,
|
"SCREENSHOT": Screenshot,
|
||||||
"SCROLL": ScrollXY,
|
"SCROLL": ScrollXY,
|
||||||
"SCROLL_BOTTOM": ScrollBottom,
|
"SCROLL_BOTTOM": ScrollBottom,
|
||||||
"SCROLL_ELEMENT": ScrollInto,
|
"SCROLL_ELEMENT": ScrollInto,
|
||||||
"SCROLL_TOP": ScrollTop,
|
"SCROLL_TOP": ScrollTop,
|
||||||
"SELECT": Select,
|
"SELECT": Select,
|
||||||
"WAIT_ELEMENT": WaitElement,
|
"WAIT_ELEMENT": WaitElement,
|
||||||
"WAIT_CLASS": WaitClass,
|
"WAIT_NO_ELEMENT": WaitNoElement,
|
||||||
"WAIT_CLASS_ALL": WaitClassAll,
|
"WAIT_CLASS": WaitClass,
|
||||||
"WAIT_NAVIGATION": WaitNavigation,
|
"WAIT_NO_CLASS": WaitNoClass,
|
||||||
|
"WAIT_CLASS_ALL": WaitClassAll,
|
||||||
|
"WAIT_NO_CLASS_ALL": WaitNoClassAll,
|
||||||
|
"WAIT_NAVIGATION": WaitNavigation,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,23 @@ import (
|
|||||||
// @param timeout (Int, optional) - If document is passed, this param must represent timeout.
|
// @param timeout (Int, optional) - If document is passed, this param must represent timeout.
|
||||||
// Otherwise not passed.
|
// Otherwise not passed.
|
||||||
func WaitClass(ctx context.Context, args ...core.Value) (core.Value, error) {
|
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)
|
err := core.ValidateArgs(args, 2, 4)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -74,7 +91,7 @@ func WaitClass(ctx context.Context, args ...core.Value) (core.Value, error) {
|
|||||||
ctx, fn := waitTimeout(ctx, timeout)
|
ctx, fn := waitTimeout(ctx, timeout)
|
||||||
defer fn()
|
defer fn()
|
||||||
|
|
||||||
return values.None, doc.WaitForClassBySelector(ctx, selector, class)
|
return values.None, doc.WaitForClassBySelector(ctx, selector, class, when)
|
||||||
}
|
}
|
||||||
|
|
||||||
el := arg1.(drivers.HTMLElement)
|
el := arg1.(drivers.HTMLElement)
|
||||||
@ -93,5 +110,5 @@ func WaitClass(ctx context.Context, args ...core.Value) (core.Value, error) {
|
|||||||
ctx, fn := waitTimeout(ctx, timeout)
|
ctx, fn := waitTimeout(ctx, timeout)
|
||||||
defer fn()
|
defer fn()
|
||||||
|
|
||||||
return values.None, el.WaitForClass(ctx, class)
|
return values.None, el.WaitForClass(ctx, class, when)
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package html
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/drivers"
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/values/types"
|
"github.com/MontFerret/ferret/pkg/runtime/values/types"
|
||||||
@ -14,6 +15,20 @@ import (
|
|||||||
// @param class (String) - String of target CSS class.
|
// @param class (String) - String of target CSS class.
|
||||||
// @param timeout (Int, optional) - Optional timeout.
|
// @param timeout (Int, optional) - Optional timeout.
|
||||||
func WaitClassAll(ctx context.Context, args ...core.Value) (core.Value, error) {
|
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)
|
err := core.ValidateArgs(args, 3, 4)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -57,5 +72,5 @@ func WaitClassAll(ctx context.Context, args ...core.Value) (core.Value, error) {
|
|||||||
ctx, fn := waitTimeout(ctx, timeout)
|
ctx, fn := waitTimeout(ctx, timeout)
|
||||||
defer fn()
|
defer fn()
|
||||||
|
|
||||||
return values.None, doc.WaitForClassBySelectorAll(ctx, selector, class)
|
return values.None, doc.WaitForClassBySelectorAll(ctx, selector, class, when)
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package html
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/drivers"
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/values/types"
|
"github.com/MontFerret/ferret/pkg/runtime/values/types"
|
||||||
@ -13,6 +14,19 @@ import (
|
|||||||
// @param selector (String) - Target element's selector.
|
// @param selector (String) - Target element's selector.
|
||||||
// @param timeout (Int, optional) - Optional timeout. Default 5000 ms.
|
// @param timeout (Int, optional) - Optional timeout. Default 5000 ms.
|
||||||
func WaitElement(ctx context.Context, args ...core.Value) (core.Value, error) {
|
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)
|
err := core.ValidateArgs(args, 2, 3)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -41,5 +55,5 @@ func WaitElement(ctx context.Context, args ...core.Value) (core.Value, error) {
|
|||||||
ctx, fn := waitTimeout(ctx, timeout)
|
ctx, fn := waitTimeout(ctx, timeout)
|
||||||
defer fn()
|
defer fn()
|
||||||
|
|
||||||
return values.None, doc.WaitForSelector(ctx, values.NewString(selector))
|
return values.None, doc.WaitForElement(ctx, values.NewString(selector), when)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user