Raise an error when there are multiple elements found

This commit is contained in:
Jonas Nicklas 2012-01-31 16:20:35 +01:00
parent 588613a37b
commit cc05b1d63b
16 changed files with 111 additions and 158 deletions

View File

@ -24,7 +24,11 @@ module Capybara
# @raise [Capybara::ElementNotFound] If the element can't be found before time expires
#
def find(*args)
wait_until { first(*args) or raise_find_error(*args) }
wait_until do
results = all(*args)
raise_find_error(*args) unless results.length == 1
results.first
end
end
##

View File

@ -19,9 +19,9 @@ module Capybara
xpath = @selector.call(@locator)
if xpath.respond_to?(:to_xpaths)
@xpaths = xpath.to_xpaths
@xpaths = [xpath.to_xpath(:fuzzy)]
else
@xpaths = [xpath.to_s].flatten
@xpaths = [xpath.to_s]
end
end

View File

@ -51,15 +51,15 @@ shared_examples_for "all" do
context "with visible filter" do
after { Capybara.ignore_hidden_elements = false }
it "should only find visible nodes" do
@session.all("//a[@title='awesome title']").should have(2).elements
@session.all("//a[@title='awesome title']", :visible => true).should have(1).elements
@session.all(:css, "a.simple").should have(2).elements
@session.all(:css, "a.simple", :visible => true).should have(1).elements
Capybara.ignore_hidden_elements = true
@session.all("//a[@title='awesome title']").should have(1).elements
@session.all(:css, "a.simple").should have(1).elements
end
it "should only find invisible nodes" do
Capybara.ignore_hidden_elements = true
@session.all("//a[@title='awesome title']", :visible => false).should have(2).elements
@session.all(:css, "a.simple", :visible => false).should have(2).elements
end
end

View File

@ -24,30 +24,30 @@ shared_examples_for "attach_file" do
context "with multipart form" do
it "should set a file path by id" do
@session.attach_file "form_document", @test_file_path
@session.click_button('Upload')
@session.click_button('Upload Single')
@session.body.should include(File.read(@test_file_path))
end
it "should set a file path by label" do
@session.attach_file "Document", @test_file_path
@session.click_button('Upload')
@session.attach_file "Single Document", @test_file_path
@session.click_button('Upload Single')
@session.body.should include(File.read(@test_file_path))
end
it "should not break if no file is submitted" do
@session.click_button('Upload')
@session.click_button('Upload Single')
@session.body.should include('No file uploaded')
end
it "should send content type text/plain when uploading a text file" do
@session.attach_file "Document", @test_file_path
@session.click_button 'Upload'
@session.attach_file "Single Document", @test_file_path
@session.click_button 'Upload Single'
@session.body.should include('text/plain')
end
it "should send content type image/jpeg when uploading an image" do
@session.attach_file "Document", @test_jpg_file_path
@session.click_button 'Upload'
@session.attach_file "Single Document", @test_jpg_file_path
@session.click_button 'Upload Single'
@session.body.should include('image/jpeg')
end

View File

@ -27,7 +27,7 @@ shared_examples_for "click_button" do
extract_results(@session)['no_action'].should == 'No Action'
end
end
context "with value given on a submit button" do
context "on a form with HTML5 fields" do
before do
@ -138,7 +138,7 @@ shared_examples_for "click_button" do
@session.body.should include('You landed')
end
end
context "with title given on a submit button" do
it "should submit the associated form" do
@session.click_button('What an Awesome Button')
@ -204,11 +204,6 @@ shared_examples_for "click_button" do
@session.click_button('Click')
extract_results(@session)['first_name'].should == 'John'
end
it "should prefer exact matches over partial matches" do
@session.click_button('Just an input')
extract_results(@session)['button'].should == 'button_second'
end
end
context "with id given on a button defined by <button> tag" do
@ -236,11 +231,6 @@ shared_examples_for "click_button" do
@session.click_button('ck_me')
extract_results(@session)['first_name'].should == 'John'
end
it "should prefer exact matches over partial matches" do
@session.click_button('Just a button')
extract_results(@session)['button'].should == 'Just a button'
end
end
context "with title given on a button defined by <button> tag" do
@ -268,7 +258,7 @@ shared_examples_for "click_button" do
@results = extract_results(@session)
@results['no_value'].should_not be_nil
end
it "should not send image buttons that were not clicked" do
@session.click_button('Click me!')
@results = extract_results(@session)

View File

@ -21,11 +21,6 @@ shared_examples_for "click_link" do
@session.click_link('abo')
@session.body.should include('Bar')
end
it "should prefer exact matches over partial matches" do
@session.click_link('A link')
@session.body.should include('Bar')
end
end
context "with title given" do
@ -38,11 +33,6 @@ shared_examples_for "click_link" do
@session.click_link('some tit')
@session.body.should include('Bar')
end
it "should prefer exact matches over partial matches" do
@session.click_link('a fine link')
@session.body.should include('Bar')
end
end
context "with alternative text given to a contained image" do
@ -55,11 +45,6 @@ shared_examples_for "click_link" do
@session.click_link('some imag')
@session.body.should include('Bar')
end
it "should prefer exact matches over partial matches" do
@session.click_link('fine image')
@session.body.should include('Bar')
end
end
context "with a locator that doesn't exist" do
@ -94,7 +79,7 @@ shared_examples_for "click_link" do
it "should do nothing on anchor links" do
@session.fill_in("test_field", :with => 'blah')
@session.click_link('Anchor')
@session.click_link('Normal Anchor')
@session.find_field("test_field").value.should == 'blah'
@session.click_link('Blank Anchor')
@session.find_field("test_field").value.should == 'blah'

View File

@ -17,9 +17,9 @@ shared_examples_for "fill_in" do
end
it "should fill in a text field by label without for" do
@session.fill_in('Street', :with => 'Avenue Q')
@session.fill_in('First Name', :with => 'Harry')
@session.click_button('awesome')
extract_results(@session)['street'].should == 'Avenue Q'
extract_results(@session)['first_name'].should == 'Harry'
end
it "should fill in a url field by label without for" do
@ -28,12 +28,6 @@ shared_examples_for "fill_in" do
extract_results(@session)['html5_url'].should == 'http://www.avenueq.com'
end
it "should favour exact label matches over partial matches" do
@session.fill_in('Name', :with => 'Harry Jones')
@session.click_button('awesome')
extract_results(@session)['name'].should == 'Harry Jones'
end
it "should fill in a textarea by id" do
@session.fill_in('form_description', :with => 'Texty text')
@session.click_button('awesome')
@ -93,17 +87,11 @@ shared_examples_for "fill_in" do
@session.click_button('awesome')
extract_results(@session)['password'].should == 'supasikrit'
end
it "should prefer exact matches over partial matches" do
@session.fill_in('Name', :with => 'Ford Prefect')
@session.click_button('awesome')
extract_results(@session)['name'].should == 'Ford Prefect'
end
it "should throw an exception if a hash containing 'with' is not provided" do
lambda{@session.fill_in 'Name', 'ignu'}.should raise_error
end
context "with ignore_hidden_fields" do
before { Capybara.ignore_hidden_elements = true }
after { Capybara.ignore_hidden_elements = false }

View File

@ -1,5 +1,5 @@
shared_examples_for "find" do
describe '#find' do
describe '#find', :focus => true do
before do
@session.visit('/with_html')
end
@ -15,7 +15,11 @@ shared_examples_for "find" do
it "should find the first element using the given locator and options" do
@session.find('//a', :text => 'Redirect')[:id].should == 'red'
@session.find(:css, 'a', :text => 'A link')[:title].should == 'twas a fine link'
@session.find(:css, 'a', :text => 'A link came first')[:title].should == 'twas a fine link'
end
it "should raise an error if there are multiple matches" do
expect { @session.find('//a') }.to raise_error(Capybara::ElementNotFound)
end
describe 'the returned node' do
@ -36,7 +40,7 @@ shared_examples_for "find" do
it "should have a reference to its parent if there is one" do
@node = @session.find(:css, '#first')
@node.parent.should == @node.session.document
@node.find('a').parent.should == @node
@node.find(:css, '#foo').parent.should == @node
end
end
@ -147,10 +151,10 @@ shared_examples_for "find" do
end.should raise_error(Capybara::ElementNotFound, "Unable to find xpath \"//div[@id=\\\"nosuchthing\\\"]\"")
end
it "should accept an XPath instance and respect the order of paths" do
it "should accept an XPath instance" do
@session.visit('/form')
@xpath = XPath::HTML.fillable_field('Name')
@session.find(@xpath).value.should == 'John Smith'
@xpath = XPath::HTML.fillable_field('First Name')
@session.find(@xpath).value.should == 'John'
end
context "within a scope" do
@ -158,9 +162,9 @@ shared_examples_for "find" do
@session.visit('/with_scope')
end
it "should find the first element using the given locator" do
it "should find the an element using the given locator" do
@session.within(:xpath, "//div[@id='for_bar']") do
@session.find('.//li').text.should =~ /With Simple HTML/
@session.find('.//li[1]').text.should =~ /With Simple HTML/
end
end
end

View File

@ -15,8 +15,8 @@ shared_examples_for "first" do
it "should accept an XPath instance" do
@session.visit('/form')
@xpath = XPath::HTML.fillable_field('Name')
@session.first(@xpath).value.should == 'John Smith'
@xpath = XPath::HTML.fillable_field('First Name')
@session.first(@xpath).value.should == 'John'
end
context "with css selectors" do

View File

@ -1,4 +1,4 @@
shared_examples_for "has_css" do
shared_examples_for "has_css" do
describe '#has_css?' do
before do
@session.visit('/with_html')

View File

@ -30,15 +30,9 @@ shared_examples_for "select" do
end
it "should select an option without giving a select box" do
@session.select("Mr")
@session.select("Swedish")
@session.click_button('awesome')
extract_results(@session)['title'].should == 'Mr'
end
it "should favour exact matches to option labels" do
@session.select("Mr", :from => 'Title')
@session.click_button('awesome')
extract_results(@session)['title'].should == 'Mr'
extract_results(@session)['locale'].should == 'sv'
end
it "should escape quotes" do
@ -102,7 +96,7 @@ shared_examples_for "select" do
@session.click_button('awesome')
extract_results(@session)['languages'].should include('Ruby', 'Javascript')
end
it "should remain selected if already selected" do
@session.select("Ruby", :from => 'Language')
@session.select("Javascript", :from => 'Language')
@ -110,7 +104,7 @@ shared_examples_for "select" do
@session.click_button('awesome')
extract_results(@session)['languages'].should include('Ruby', 'Javascript')
end
it "should return value attribute rather than content if present" do
@session.find_field('Underwear').value.should include('thermal')
end

View File

@ -8,28 +8,28 @@ shared_examples_for "unselect" do
it "should unselect an option from a select box by id" do
@session.unselect('Commando', :from => 'form_underwear')
@session.click_button('awesome')
extract_results(@session)['underwear'].should include('Briefs', 'Boxer Briefs')
extract_results(@session)['underwear'].should include('Briefs', 'Boxerbriefs')
extract_results(@session)['underwear'].should_not include('Commando')
end
it "should unselect an option without a select box" do
@session.unselect('Commando')
@session.click_button('awesome')
extract_results(@session)['underwear'].should include('Briefs', 'Boxer Briefs')
extract_results(@session)['underwear'].should include('Briefs', 'Boxerbriefs')
extract_results(@session)['underwear'].should_not include('Commando')
end
it "should unselect an option from a select box by label" do
@session.unselect('Commando', :from => 'Underwear')
@session.click_button('awesome')
extract_results(@session)['underwear'].should include('Briefs', 'Boxer Briefs')
extract_results(@session)['underwear'].should include('Briefs', 'Boxerbriefs')
extract_results(@session)['underwear'].should_not include('Commando')
end
it "should favour exact matches to option labels" do
@session.unselect("Briefs", :from => 'Underwear')
@session.click_button('awesome')
extract_results(@session)['underwear'].should include('Commando', 'Boxer Briefs')
extract_results(@session)['underwear'].should include('Commando', 'Boxerbriefs')
extract_results(@session)['underwear'].should_not include('Briefs')
end

View File

@ -6,7 +6,7 @@ shared_examples_for "within" do
context "with CSS selector" do
it "should click links in the given scope" do
@session.within(:css, "ul li[contains('With Simple HTML')]") do
@session.within(:css, "#for_bar li[contains('With Simple HTML')]") do
@session.click_link('Go')
end
@session.body.should include('Bar')
@ -20,7 +20,7 @@ shared_examples_for "within" do
end
it "should accept additional options" do
@session.within(:css, "ul li", :text => 'With Simple HTML') do
@session.within(:css, "#for_bar li", :text => 'With Simple HTML') do
@session.click_link('Go')
end
@session.body.should include('Bar')
@ -29,7 +29,7 @@ shared_examples_for "within" do
context "with XPath selector" do
it "should click links in the given scope" do
@session.within(:xpath, "//li[contains(.,'With Simple HTML')]") do
@session.within(:xpath, "//div[@id='for_bar']//li[contains(.,'With Simple HTML')]") do
@session.click_link('Go')
end
@session.body.should include('Bar')
@ -38,7 +38,7 @@ shared_examples_for "within" do
context "with the default selector" do
it "should use XPath" do
@session.within("//li[contains(., 'With Simple HTML')]") do
@session.within("//div[@id='for_bar']//li[contains(.,'With Simple HTML')]") do
@session.click_link('Go')
end
@session.body.should include('Bar')
@ -47,9 +47,9 @@ shared_examples_for "within" do
context "with Node rather than selector" do
it "should click links in the given scope" do
node_of_interest = @session.find(:css, "ul li[contains('With Simple HTML')]")
node_of_interest = @session.find(:css, "#for_bar li[contains('With Simple HTML')]")
@session.within(node_of_interest) do
@session.within(node_of_interest) do
@session.click_link('Go')
end
@session.body.should include('Bar')
@ -59,7 +59,7 @@ shared_examples_for "within" do
context "with the default selector set to CSS" do
before { Capybara.default_selector = :css }
it "should use CSS" do
@session.within("ul li[contains('With Simple HTML')]") do
@session.within("#for_bar li[contains('With Simple HTML')]") do
@session.click_link('Go')
end
@session.body.should include('Bar')
@ -67,68 +67,57 @@ shared_examples_for "within" do
after { Capybara.default_selector = :xpath }
end
context "with click_link" do
it "should click links in the given scope" do
@session.within("//li[contains(.,'With Simple HTML')]") do
@session.click_link('Go')
context "with nested scopes" do
it "should respect the inner scope" do
@session.within("//div[@id='for_bar']") do
@session.within(".//li[contains(.,'Bar')]") do
@session.click_link('Go')
end
end
@session.body.should include('Bar')
@session.body.should include('Another World')
end
context "with nested scopes" do
it "should respect the inner scope" do
@session.within("//div[@id='for_bar']") do
@session.within(".//li[contains(.,'Bar')]") do
@session.click_link('Go')
end
it "should respect the outer scope" do
@session.within("//div[@id='another_foo']") do
@session.within(".//li[contains(.,'With Simple HTML')]") do
@session.click_link('Go')
end
@session.body.should include('Another World')
end
it "should respect the outer scope" do
@session.within("//div[@id='another_foo']") do
@session.within(".//li[contains(.,'With Simple HTML')]") do
@session.click_link('Go')
end
end
@session.body.should include('Hello world')
end
end
it "should raise an error if the scope is not found on the page" do
running do
@session.within("//div[@id='doesnotexist']") do
end
end.should raise_error(Capybara::ElementNotFound)
end
it "should restore the scope when an error is raised" do
running do
@session.within("//div[@id='for_bar']") do
running do
running do
@session.within(".//div[@id='doesnotexist']") do
end
end.should raise_error(Capybara::ElementNotFound)
end.should_not change { @session.has_xpath?(".//div[@id='another_foo']") }.from(false)
end
end.should_not change { @session.has_xpath?(".//div[@id='another_foo']") }.from(true)
@session.body.should include('Hello world')
end
end
context "with forms" do
it "should fill in a field and click a button" do
@session.within("//li[contains(.,'Bar')]") do
@session.click_button('Go')
it "should raise an error if the scope is not found on the page" do
running do
@session.within("//div[@id='doesnotexist']") do
end
extract_results(@session)['first_name'].should == 'Peter'
@session.visit('/with_scope')
@session.within("//li[contains(.,'Bar')]") do
@session.fill_in('First Name', :with => 'Dagobert')
@session.click_button('Go')
end.should raise_error(Capybara::ElementNotFound)
end
it "should restore the scope when an error is raised" do
running do
@session.within("//div[@id='for_bar']") do
running do
running do
@session.within(".//div[@id='doesnotexist']") do
end
end.should raise_error(Capybara::ElementNotFound)
end.should_not change { @session.has_xpath?(".//div[@id='another_foo']") }.from(false)
end
extract_results(@session)['first_name'].should == 'Dagobert'
end.should_not change { @session.has_xpath?(".//div[@id='another_foo']") }.from(true)
end
it "should fill in a field and click a button" do
@session.within("//li[contains(.,'Bar')]") do
@session.click_button('Go')
end
extract_results(@session)['first_name'].should == 'Peter'
@session.visit('/with_scope')
@session.within("//li[contains(.,'Bar')]") do
@session.fill_in('First Name', :with => 'Dagobert')
@session.click_button('Go')
end
extract_results(@session)['first_name'].should == 'Dagobert'
end
end

View File

@ -32,7 +32,7 @@
<label for="form_last_name">Last Name</label>
<input type="text" name="form[last_name]" value="Smith" id="form_last_name"/>
</p>
<p>
<label for="form_name_explanation">Explanation of Name</label>
<textarea name="form[name_explanation]" id="form_name_explanation"></textarea>
@ -47,7 +47,7 @@
<label for="form_schmooo">Schmooo</label>
<input type="schmooo" name="form[schmooo]" value="This is Schmooo!" id="form_schmooo"/>
</p>
<p>
<label>Street<br/>
<input type="text" maxlength="" name="form[street]" value="Sesame street 66"/>
@ -74,7 +74,7 @@
<label for="form_image">Image</label>
<input type="file" name="form[image]" id="form_image"/>
</p>
<p>
<input type="hidden" name="form[token]" value="12345" id="form_token"/>
</p>
@ -155,7 +155,7 @@
<p>
<label for="form_underwear">Underwear</label>
<select name="form[underwear][]" id="form_underwear" multiple="multiple">
<option selected="selected">Boxer Briefs</option>
<option selected="selected">Boxerbriefs</option>
<option>Boxers</option>
<option selected="selected">Briefs</option>
<option selected="selected">Commando</option>
@ -163,7 +163,7 @@
<option selected="selected" value="thermal">Long Johns</option>
</select>
</p>
<p>
<span>First address<span>
<label for='address1_street'>Street</label>
@ -185,7 +185,7 @@
<input type="text" name="form[addresses][][street]" value="" id="address2_street">
<label for='address2_city'>City</label>
<input type="text" name="form[addresses][][city]" value="" id="address2_city">
<input type="text" name="form[addresses][][city]" value="" id="address2_city">
<label for='address2_country'>Country</label>
<select name="form[addresses][][country]" id="address2_country">
@ -286,12 +286,12 @@
</p>
<p>
<label for="form_document">Document</label>
<label for="form_document">Single Document</label>
<input type="file" name="form[document]" id="form_document"/>
</p>
<p>
<input type="submit" value="Upload"/>
<input type="submit" value="Upload Single"/>
<p>
</form>
@ -338,7 +338,7 @@
<label for="html5_color">Html5 Color</label>
<input type="color" name="form[html5_color]" value="#FFFFFF" id="html5_color"/>
</p>
<p>
<input type="submit" name="form[html5_submit]" value="html5_submit"/>
</p>

View File

@ -33,15 +33,14 @@
<a title="twas a fine link" href="/redirect">A link came first</a>
<a title="a fine link" href="/with_simple_html">A link</a>
<a title="a fine link with data method" data-method="delete" href="/delete">A link with data-method</a>
<a title="a fine link with data method capitalized" data-method="DELETE" href="/delete">A link with data-method capitalized</a>
<a title="a fine link with capitalized data method" data-method="DELETE" href="/delete">A link with capitalized data-method</a>
<a>No Href</a>
<a href="">Blank Href</a>
<a href="#">Blank Anchor</a>
<a href="#anchor">Anchor</a>
<a href="#anchor">Normal Anchor</a>
<a href="/with_simple_html#anchor">Anchor on different page</a>
<a href="/with_html#anchor">Anchor on same page</a>
<a href="with_html">Relative</a>
<input type="text" value="" id="test_field">
<input type="text" checked="checked" id="checked_field">
<a href="/redirect"><img width="20" height="20" alt="very fine image" /></a>
<a href="/with_simple_html"><img width="20" height="20" alt="fine image" /></a>
@ -54,7 +53,7 @@
<div id="hidden" style="display: none;">
<div id="hidden_via_ancestor">Inside element with hidden ancestor</div>
<a href="/with_simple_html" title="awesome title" class="simple">hidden link</a>
<a href="/with_simple_html" title="hidden link" class="simple">hidden link</a>
</div>
<div style="display: none;">

View File

@ -34,7 +34,7 @@ describe Capybara::Session do
it "should use data-method if available even if it's capitalized" do
@session.visit "/with_html"
@session.click_link "A link with data-method capitalized"
@session.click_link "A link with capitalized data-method"
@session.body.should include('The requested object was deleted')
end