diff --git a/lib/capybara/session.rb b/lib/capybara/session.rb index d79e5741..b32dd135 100644 --- a/lib/capybara/session.rb +++ b/lib/capybara/session.rb @@ -1,3 +1,5 @@ +require 'capybara/wait_until' + module Capybara class Session include Searchable @@ -38,65 +40,56 @@ module Capybara end def click(locator) - link = wait_for(XPath.link(locator).button(locator)) - raise Capybara::ElementNotFound, "no link or button '#{locator}' found" unless link - link.click + msg = "no link or button '#{locator}' found" + locate(XPath.link(locator).button(locator), msg).click end def click_link(locator) - link = wait_for(XPath.link(locator)) - raise Capybara::ElementNotFound, "no link with title, id or text '#{locator}' found" unless link - link.click + msg = "no link with title, id or text '#{locator}' found" + locate(XPath.link(locator), msg).click end def click_button(locator) - button = wait_for(XPath.button(locator)) - raise Capybara::ElementNotFound, "no button with value or id or text '#{locator}' found" unless button - button.click + msg = "no button with value or id or text '#{locator}' found" + locate(XPath.button(locator)).click end def drag(source_locator, target_locator) - source = wait_for(source_locator) - raise Capybara::ElementNotFound, "drag source '#{source_locator}' not found on page" unless source - target = wait_for(target_locator) - raise Capybara::ElementNotFound, "drag target '#{target_locator}' not found on page" unless target + source = locate(source_locator, "drag source '#{source_locator}' not found on page") + + target = locate(target_locator, "drag target '#{target_locator}' not found on page") + source.drag_to(target) end def fill_in(locator, options={}) - field = wait_for(XPath.fillable_field(locator)) - raise Capybara::ElementNotFound, "cannot fill in, no text field, text area or password field with id or label '#{locator}' found" unless field - field.set(options[:with]) + msg = "cannot fill in, no text field, text area or password field with id or label '#{locator}' found" + locate(XPath.fillable_field(locator), msg).set(options[:with]) end def choose(locator) - field = wait_for(XPath.radio_button(locator)) - raise Capybara::ElementNotFound, "cannot choose field, no radio button with id or label '#{locator}' found" unless field - field.set(true) + msg = "cannot choose field, no radio button with id or label '#{locator}' found" + locate(XPath.radio_button(locator), msg).set(true) end def check(locator) - field = wait_for(XPath.checkbox(locator)) - raise Capybara::ElementNotFound, "cannot check field, no checkbox with id or label '#{locator}' found" unless field - field.set(true) + msg = "cannot check field, no checkbox with id or label '#{locator}' found" + locate(XPath.checkbox(locator), msg).set(true) end def uncheck(locator) - field = wait_for(XPath.checkbox(locator)) - raise Capybara::ElementNotFound, "cannot uncheck field, no checkbox with id or label '#{locator}' found" unless field - field.set(false) + msg = "cannot uncheck field, no checkbox with id or label '#{locator}' found" + locate(XPath.checkbox(locator), msg).set(false) end def select(value, options={}) - field = wait_for(XPath.select(options[:from])) - raise Capybara::ElementNotFound, "cannot select option, no select box with id or label '#{options[:from]}' found" unless field - field.select(value) + msg = "cannot select option, no select box with id or label '#{options[:from]}' found" + locate(XPath.select(options[:from])).select(value) end def attach_file(locator, path) - field = wait_for(XPath.file_field(locator)) - raise Capybara::ElementNotFound, "cannot attach file, no file field with id or label '#{locator}' found" unless field - field.set(path) + msg = "cannot attach file, no file field with id or label '#{locator}' found" + locate(XPath.file_field(locator)).set(path) end def body @@ -106,7 +99,7 @@ module Capybara def within(kind, scope=nil) kind, scope = Capybara.default_selector, kind unless scope scope = XPath.from_css(scope) if kind == :css - raise Capybara::ElementNotFound, "scope '#{scope}' not found on page" unless wait_for(scope) + locate(scope, "scope '#{scope}' not found on page") scopes.push(scope) yield scopes.pop @@ -129,25 +122,29 @@ module Capybara Capybara::SaveAndOpenPage.save_and_open_page(body) end - def wait_for(locator) - wait_until { find(locator) } + #return node identified by locator or raise ElementNotFound(using desc) + def locate(locator, fail_msg = nil) + + fail_msg ||= "Unable to locate '#{locator}'" + + node = nil + begin + if driver.wait? + node = wait_until { find(locator) } + else + node = find(locator) + end + rescue Capybara::TimeoutError; end + + raise Capybara::ElementNotFound, fail_msg unless node + + node end - def wait_for_condition(script) - wait_until { evaluate_script(script) } + def wait_until(timeout = Capybara.default_wait_time) + WaitUntil.timeout(timeout) { yield } end - - def wait_until - return yield unless driver.wait? - 10.times do - if result = yield - return result - end - sleep(0.1) - end - nil - end - + def evaluate_script(script) driver.evaluate_script(script) end diff --git a/spec/dsl/wait_for_spec.rb b/spec/dsl/locate_spec.rb similarity index 50% rename from spec/dsl/wait_for_spec.rb rename to spec/dsl/locate_spec.rb index 8c57b793..b796b52a 100644 --- a/spec/dsl/wait_for_spec.rb +++ b/spec/dsl/locate_spec.rb @@ -1,23 +1,25 @@ -module WaitForSpec - shared_examples_for "wait_for" do - describe '#wait_for' do +module LocateSpec + shared_examples_for "locate" do + describe '#locate' do before do @session.visit('/with_html') end it "should find the first element using the given locator" do - @session.wait_for('//h1').text.should == 'This is a test' - @session.wait_for("//input[@id='test_field']")[:value].should == 'monkey' + @session.locate('//h1').text.should == 'This is a test' + @session.locate("//input[@id='test_field']")[:value].should == 'monkey' end - it "should return nil when nothing was found" do - @session.wait_for('//div[@id="nosuchthing"]').should be_nil + it "should raise ElementNotFound with specified fail message if nothing was found" do + running do + @session.locate('//div[@id="nosuchthing"]', 'arghh').should be_nil + end.should raise_error(Capybara::ElementNotFound, "arghh") end it "should accept an XPath instance and respect the order of paths" do @session.visit('/form') @xpath = Capybara::XPath.text_field('Name') - @session.wait_for(@xpath).value.should == 'John Smith' + @session.locate(@xpath).value.should == 'John Smith' end context "within a scope" do @@ -27,7 +29,7 @@ module WaitForSpec it "should find the first element using the given locator" do @session.within(:xpath, "//div[@id='for_bar']") do - @session.wait_for('//li').text.should =~ /With Simple HTML/ + @session.locate('//li').text.should =~ /With Simple HTML/ end end end diff --git a/spec/session_spec.rb b/spec/session_spec.rb index d63de5f2..9f689f54 100644 --- a/spec/session_spec.rb +++ b/spec/session_spec.rb @@ -39,7 +39,7 @@ shared_examples_for "session" do it_should_behave_like "has_xpath" it_should_behave_like "select" it_should_behave_like "uncheck" - it_should_behave_like "wait_for" + it_should_behave_like "locate" it_should_behave_like "within" it_should_behave_like "current_url" end diff --git a/spec/session_with_javascript_support_spec.rb b/spec/session_with_javascript_support_spec.rb index 8e034fae..d46ecaac 100644 --- a/spec/session_with_javascript_support_spec.rb +++ b/spec/session_with_javascript_support_spec.rb @@ -10,17 +10,24 @@ shared_examples_for "session with javascript support" do end end - describe '#wait_for' do + describe '#locate' do it "should wait for asynchronous load" do @session.visit('/with_js') @session.click_link('Click me') - @session.wait_for("//a[contains(.,'Has been clicked')]")[:href].should == '#' + @session.locate("//a[contains(.,'Has been clicked')]")[:href].should == '#' end end describe '#wait_until' do + before do + @default_timeout = Capybara.default_wait_time + end - it "should wait for block to return true" do + after do + Capybara.default_wait_time = @default_wait_time + end + + it "should wait for block to return true" do @session.visit('/with_js') @session.select('My Waiting Option', :from => 'waiter') @session.evaluate_script('activeRequests == 1').should be_true @@ -30,21 +37,31 @@ shared_examples_for "session with javascript support" do @session.evaluate_script('activeRequests == 0').should be_true end - it "should return false if block doesn't return true within timeout" do + it "should raise Capybara::TimeoutError if block doesn't return true within timeout" do @session.visit('/with_html') - - @session.wait_until { false }.should be_nil + Proc.new do + @session.wait_until(0.1) do + @session.find('//div[@id="nosuchthing"]') + end + end.should raise_error(::Capybara::TimeoutError) + end + + it "should accept custom timeout in seconds" do + start = Time.now + Capybara.default_wait_time = 5 + begin + @session.wait_until(0.1) { false } + rescue Capybara::TimeoutError; end + (Time.now - start).should be_close(0.1, 0.1) end - end - - describe '#wait_for_condition' do - it "should wait for condition to be true" do - @session.visit('/with_js') - @session.select('My Waiting Option', :from => 'waiter') - @session.evaluate_script('activeRequests == 1').should be_true - @session.wait_for_condition('activeRequests == 0').should be_true - @session.evaluate_script('activeRequests == 0').should be_true + it "should default to Capybara.default_wait_time before timeout" do + start = Time.now + Capybara.default_wait_time = 0.2 + begin + @session.wait_until { false } + rescue Capybara::TimeoutError; end + (Time.now - start).should be_close(0.2, 0.1) end end diff --git a/spec/session_without_javascript_support_spec.rb b/spec/session_without_javascript_support_spec.rb index 99d763eb..af54d775 100644 --- a/spec/session_without_javascript_support_spec.rb +++ b/spec/session_without_javascript_support_spec.rb @@ -12,18 +12,4 @@ shared_examples_for "session without javascript support" do end end - describe '#wait_until' do - it "should not wait for asynchronous load" do - @session.visit('/with_html') - - before_time = Time.new - - @session.wait_until do - @session.find('//div[@id="nosuchthing"]') - end.should be_nil - - (Time.now - before_time).should be < 2 - end - - end end