From 9c9f3684a9e6ff907f657a95a1a5db7f2aaf2cd4 Mon Sep 17 00:00:00 2001 From: Jonas Nicklas Date: Sat, 12 Dec 2009 21:46:08 +0100 Subject: [PATCH] Moved waiting back into session where it belongs Also expose the new wait_for method As an added bonus, find and all now accept XPath instances and handle them correctly --- lib/capybara/driver/base.rb | 7 +--- lib/capybara/driver/culerity_driver.rb | 9 +---- lib/capybara/driver/selenium_driver.rb | 11 +----- lib/capybara/dsl.rb | 2 +- lib/capybara/session.rb | 45 ++++++++++++--------- lib/capybara/xpath.rb | 12 ++++++ spec/session_spec.rb | 54 ++++++++++++++++++++++++++ spec/xpath_spec.rb | 19 +++++++++ 8 files changed, 117 insertions(+), 42 deletions(-) diff --git a/lib/capybara/driver/base.rb b/lib/capybara/driver/base.rb index cea89c9e..559a6c66 100644 --- a/lib/capybara/driver/base.rb +++ b/lib/capybara/driver/base.rb @@ -11,10 +11,7 @@ class Capybara::Driver::Base raise "Not implemented" end - def fetch(*paths) - paths.find do |path| - result = find(path).first - return result if result - end + def wait? + false end end \ No newline at end of file diff --git a/lib/capybara/driver/culerity_driver.rb b/lib/capybara/driver/culerity_driver.rb index 930ef6bb..de72f761 100644 --- a/lib/capybara/driver/culerity_driver.rb +++ b/lib/capybara/driver/culerity_driver.rb @@ -70,14 +70,7 @@ class Capybara::Driver::Culerity < Capybara::Driver::Base browser.elements_by_xpath(selector).map { |node| Node.new(self, node) } end - def fetch(*paths) - 8.times do - result = super - return result if result - sleep(0.1) - end - nil - end + def wait?; true; end private diff --git a/lib/capybara/driver/selenium_driver.rb b/lib/capybara/driver/selenium_driver.rb index dc191e35..be625a98 100644 --- a/lib/capybara/driver/selenium_driver.rb +++ b/lib/capybara/driver/selenium_driver.rb @@ -83,15 +83,8 @@ class Capybara::Driver::Selenium < Capybara::Driver::Base def find(selector) driver.find_elements(:xpath, selector).map { |node| Node.new(self, node) } end - - def fetch(*paths) - 8.times do - result = super - return result if result - sleep(0.1) - end - nil - end + + def wait?; true; end private diff --git a/lib/capybara/dsl.rb b/lib/capybara/dsl.rb index b86a7b79..2aff1aae 100644 --- a/lib/capybara/dsl.rb +++ b/lib/capybara/dsl.rb @@ -50,7 +50,7 @@ module Capybara :visit, :body, :click_link, :click_button, :fill_in, :choose, :has_xpath?, :has_css?, :check, :uncheck, :attach_file, :select, :has_content?, :within, :within_fieldset, :within_table, :save_and_open_page, :find, :find_field, :find_link, :find_button, - :field_labeled, :all + :field_labeled, :all, :wait_for ] SESSION_METHODS.each do |method| class_eval <<-RUBY, __FILE__, __LINE__+1 diff --git a/lib/capybara/session.rb b/lib/capybara/session.rb index f8bbda1c..3f0e0104 100644 --- a/lib/capybara/session.rb +++ b/lib/capybara/session.rb @@ -26,49 +26,49 @@ module Capybara end def click_link(locator) - link = find_link(locator) + link = wait_for(XPath.link(locator)) raise Capybara::ElementNotFound, "no link with title, id or text '#{locator}' found" unless link link.click end def click_button(locator) - button = find_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 end def fill_in(locator, options={}) - field = fetch_xpath(XPath.fillable_field(locator)) + 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]) end def choose(locator) - field = fetch_xpath(XPath.radio_button(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) end def check(locator) - field = fetch_xpath(XPath.checkbox(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) end def uncheck(locator) - field = fetch_xpath(XPath.checkbox(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) end def select(value, options={}) - field = fetch_xpath(XPath.select(options[:from])) + 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) end def attach_file(locator, path) - field = fetch_xpath(XPath.file_field(locator)) + 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) end @@ -100,7 +100,7 @@ module Capybara def within(kind, scope=nil) kind, scope = Capybara.default_selector, kind unless scope scope = css_to_xpath(scope) if kind == :css - raise Capybara::ElementNotFound, "scope '#{scope}' not found on page" unless find(scope) + raise Capybara::ElementNotFound, "scope '#{scope}' not found on page" unless wait_for(scope) scopes.push(scope) yield scopes.pop @@ -124,32 +124,39 @@ module Capybara end def all(locator) - locator = current_scope.to_s + locator - driver.find(locator) + XPath.wrap(locator).scope(current_scope).paths.map do |path| + driver.find(path) + end.flatten end def find(locator) - all(locator.to_s).first + all(locator).first + end + + def wait_for(locator) + return find(locator) unless driver.wait? + 8.times do + result = find(locator) + return result if result + sleep(0.1) + end + nil end def find_field(locator) - fetch_xpath(XPath.field(locator)) + find(XPath.field(locator)) end alias_method :field_labeled, :find_field def find_link(locator) - fetch_xpath(XPath.link(locator)) + find(XPath.link(locator)) end def find_button(locator) - fetch_xpath(XPath.button(locator)) + find(XPath.button(locator)) end private - - def fetch_xpath(xpath) - driver.fetch(*xpath.paths.map { |path| current_scope.to_s + path }) - end def css_to_xpath(css) Nokogiri::CSS.xpath_for(css).first diff --git a/lib/capybara/xpath.rb b/lib/capybara/xpath.rb index 57145d05..7c079252 100644 --- a/lib/capybara/xpath.rb +++ b/lib/capybara/xpath.rb @@ -4,6 +4,14 @@ module Capybara # this will generate an XPath that matches either a text field or a link class XPath class << self + def wrap(path) + if path.is_a?(self) + path + else + new(path.to_s) + end + end + def respond_to?(method) new.respond_to?(method) end @@ -75,6 +83,10 @@ module Capybara def file_field(locator) add_field(locator) { |id| "//input[@type='file'][@id=#{id}]" } end + + def scope(scope) + XPath.new(*paths.map { |p| scope + p }) + end def to_s @paths.join(' | ') diff --git a/spec/session_spec.rb b/spec/session_spec.rb index a51dcfb8..4045ddf0 100644 --- a/spec/session_spec.rb +++ b/spec/session_spec.rb @@ -607,6 +607,13 @@ shared_examples_for "session" do @session.all('//div').should be_empty end + it "should accept an XPath instance" do + @session.visit('/form') + @xpath = Capybara::XPath.text_field('Name') + @result = @session.all(@xpath) + @result.map(&:value).should include('Smith', 'John', 'John Smith') + end + context "within a scope" do before do @session.visit('/with_scope') @@ -634,6 +641,12 @@ shared_examples_for "session" do @session.find('//div').should be_nil end + it "should accept an XPath instance and respect the order of paths" do + @session.visit('/form') + @xpath = Capybara::XPath.text_field('Name') + @session.find(@xpath).value.should == 'John Smith' + end + context "within a scope" do before do @session.visit('/with_scope') @@ -646,6 +659,39 @@ shared_examples_for "session" do end end end + + describe '#wait_for' 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' + end + + it "should return nil when nothing was found" do + @session.wait_for('//div').should be_nil + 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' + end + + context "within a scope" do + before do + @session.visit('/with_scope') + end + + 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/ + end + end + end + end describe '#within' do before do @@ -791,6 +837,14 @@ shared_examples_for "session" do end shared_examples_for "session with javascript support" do + describe '#wait_for' 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 == '#' + end + end + describe '#click_link' do it "should wait for asynchronous load" do @session.visit('/with_js') diff --git a/spec/xpath_spec.rb b/spec/xpath_spec.rb index 8539c566..f0f39aff 100644 --- a/spec/xpath_spec.rb +++ b/spec/xpath_spec.rb @@ -16,6 +16,25 @@ describe Capybara::XPath do it "should respond to instance methods at the class level" do Capybara::XPath.should respond_to(:fillable_field) end + + describe '.wrap' do + it "should return an XPath unmodified" do + Capybara::XPath.wrap(@xpath).should == @xpath + end + + it "should wrap a string in an xpath object" do + @xpath = Capybara::XPath.wrap('//foo/bar') + @xpath.should be_an_instance_of(Capybara::XPath) + @xpath.paths.should == ['//foo/bar'] + end + end + + describe '#scope' do + it "should prepend the given scope to all paths" do + @xpath = Capybara::XPath.new('//foo/bar', '//test[@blah=foo]').scope('//quox') + @xpath.paths.should include('//quox//foo/bar', '//quox//test[@blah=foo]') + end + end describe '#field' do it "should find any field by id or label" do