diff --git a/spec/features/wait_spec.cr b/spec/features/wait_spec.cr new file mode 100644 index 0000000..c6bbbea --- /dev/null +++ b/spec/features/wait_spec.cr @@ -0,0 +1,51 @@ +require "../spec_helper" + +describe Selenium::Helpers::Wait do + describe ".wait" do + it "success if true" do + TestServer.route "/home", <<-HTML +
+ HTML + + with_session do |session| + session.navigate_to("http://localhost:3002/home") + + wait = Selenium::Helpers::Wait.new(timeout: 2.seconds, interval: 1.second) + + wait.until { session.find_element(:css, "#div") } + end + end + + it "raises an error if false" do + TestServer.route "/home", <<-HTML +
+ HTML + + with_session do |session| + session.navigate_to("http://localhost:3002/home") + + wait = Selenium::Helpers::Wait.new(timeout: 2.seconds, interval: 1.second) + + expect_raises(IO::TimeoutError, /timed out after 2 seconds \(no such element.*/) do + wait.until { session.find_element(:css, "#div1") } + end + end + end + + it "raises an error with custom message" do + TestServer.route "/home", <<-HTML +
+ HTML + + with_session do |session| + session.navigate_to("http://localhost:3002/home") + + wait = Selenium::Helpers::Wait.new(timeout: 2.seconds, interval: 1.second, message: "custom message") + + expect_raises(IO::TimeoutError, /custom message \(no such element.*/) do + wait.until { session.find_element(:css, "#div1") } + end + end + end + end +end diff --git a/src/selenium/helpers/wait.cr b/src/selenium/helpers/wait.cr new file mode 100644 index 0000000..e558327 --- /dev/null +++ b/src/selenium/helpers/wait.cr @@ -0,0 +1,50 @@ +class Selenium::Helpers::Wait + @timeout : Time::Span + @interval : Time::Span + @ignored = [] of Exception + + # Create a new `Wait` instance. + # + # `timeout` - seconds to wait before timing out + # `interval` - seconds to sleep between polls + # `message` - exception mesage if timed out + # `ignored` - exceptions to ignore while polling + def initialize(@timeout = 5.seconds, @interval = 0.2.seconds, @ignored = [Selenium::Error], @message : String? = nil) + end + + # Wait until the given block returns a `true` value. + # Otherwise raise an exception `IO::TimeoutError`. + # + # ``` + # wait = Selenium::Helpers::Wait.new(timeout: 10.seconds, interval: 1.second) + # wait.until { session.document_manager.execute_script("return document.readyState;") == "complete" } + # ``` + def until(&) : Nil + end_time = current_time + @timeout + last_error : Exception? = nil + + until current_time > end_time + begin + result = yield + + return result if result + rescue ex + last_error = ex + + raise ex unless @ignored.includes?(ex.class) + end + + sleep @interval + end + + message = @message || "timed out after #{@timeout.seconds} seconds" + + message = message + " (#{last_error.message})" if last_error + + raise IO::TimeoutError.new(message) + end + + private def current_time + Time.utc + end +end