mirror of
				https://github.com/MontFerret/ferret.git
				synced 2025-10-30 23:37:40 +02:00 
			
		
		
		
	Feature/#229 wait no element (#249)
* Added possibility to wait for an element or a class absence
This commit is contained in:
		| @@ -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: | ||||
|   | ||||
							
								
								
									
										15
									
								
								e2e/main.go
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								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) | ||||
|   | ||||
							
								
								
									
										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; | ||||
|  | ||||
| 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"}, [ | ||||
|   | ||||
| @@ -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" | ||||
|                     }) | ||||
|                 ]) | ||||
|             ]) | ||||
|         ]) | ||||
|     } | ||||
|   | ||||
							
								
								
									
										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 { | ||||
| 	ctx := context.Background() | ||||
|  | ||||
| func (r *Runner) Run(ctx context.Context) error { | ||||
| 	ctx = drivers.WithContext( | ||||
| 		ctx, | ||||
| 		cdp.NewDriver(cdp.WithAddress(r.settings.CDPAddress)), | ||||
|   | ||||
| @@ -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 "" | ||||
							
								
								
									
										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( | ||||
| 		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, | ||||
| 	) | ||||
|   | ||||
| @@ -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 | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
|   | ||||
| @@ -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 "!=" | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
| ) | ||||
|   | ||||
| @@ -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, | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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) | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user