From 8d44e8cb74536abba6ae75fae0aedd65012a6c55 Mon Sep 17 00:00:00 2001 From: Jonas Nicklas Date: Wed, 9 Dec 2009 19:03:55 +0100 Subject: [PATCH] Added fancy XPath generation class --- lib/capybara/session.rb | 28 ++----- lib/capybara/xpath.rb | 69 ++++++++++++++++ spec/session_spec.rb | 7 +- spec/xpath_spec.rb | 176 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 254 insertions(+), 26 deletions(-) create mode 100644 lib/capybara/xpath.rb create mode 100644 spec/xpath_spec.rb diff --git a/lib/capybara/session.rb b/lib/capybara/session.rb index 7bd6a6ab..e54d1327 100644 --- a/lib/capybara/session.rb +++ b/lib/capybara/session.rb @@ -1,17 +1,6 @@ module Capybara class Session - FIELDS_PATHS = { - :text_field => proc { |id| "//input[@type='text'][@id='#{id}' or @id=//label[contains(.,'#{id}')]/@for]" }, - :text_area => proc { |id| "//textarea[@id='#{id}' or @id=//label[contains(.,'#{id}')]/@for]" }, - :password_field => proc { |id| "//input[@type='password'][@id='#{id}' or @id=//label[contains(.,'#{id}')]/@for]" }, - :radio => proc { |id| "//input[@type='radio'][@id='#{id}' or @id=//label[contains(.,'#{id}')]/@for]" }, - :hidden_field => proc { |id| "//input[@type='hidden'][@id='#{id}' or @id=//label[contains(.,'#{id}')]/@for]" }, - :checkbox => proc { |id| "//input[@type='checkbox'][@id='#{id}' or @id=//label[contains(.,'#{id}')]/@for]" }, - :select => proc { |id| "//select[@id='#{id}' or @id=//label[contains(.,'#{id}')]/@for]" }, - :file_field => proc { |id| "//input[@type='file'][@id='#{id}' or @id=//label[contains(.,'#{id}')]/@for]" } - } - attr_reader :mode, :app def initialize(mode, app) @@ -49,37 +38,37 @@ module Capybara end def fill_in(locator, options={}) - field = find_field(locator, :text_field, :text_area, :password_field) + field = find(XPath.fillable_field(locator).to_s) 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 = find_field(locator, :radio) + field = find(XPath.radio_button(locator).to_s) 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 = find_field(locator, :checkbox) + field = find(XPath.checkbox(locator).to_s) raise Capybara::ElementNotFound, "cannot check field, no checkbox with id or label '#{locator}' found" unless field field.set(true) end def uncheck(locator) - field = find_field(locator, :checkbox) + field = find(XPath.checkbox(locator).to_s) 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 = find_field(options[:from], :select) + field = find(XPath.select(options[:from]).to_s) 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 = find_field(locator, :file_field) + field = find(XPath.file_field(locator).to_s) raise Capybara::ElementNotFound, "cannot attach file, no file field with id or label '#{locator}' found" unless field field.set(path) end @@ -143,9 +132,8 @@ module Capybara all(locator).first end - def find_field(locator, *kinds) - kinds = FIELDS_PATHS.keys if kinds.empty? - find(kinds.map { |kind| FIELDS_PATHS[kind].call(locator) }.join("|")) + def find_field(locator) + find(XPath.field(locator).to_s) end alias_method :field_labeled, :find_field diff --git a/lib/capybara/xpath.rb b/lib/capybara/xpath.rb new file mode 100644 index 00000000..959bacfc --- /dev/null +++ b/lib/capybara/xpath.rb @@ -0,0 +1,69 @@ +module Capybara + # this is a class for generating XPath queries, use it like this: + # Xpath.text_field('foo').link('blah').to_s + # this will generate an XPath that matches either a text field or a link + class XPath + class << self + def respond_to?(method) + new.respond_to?(method) + end + + def method_missing(*args) + new.send(*args) + end + end + + attr_reader :paths + + def initialize(*paths) + @paths = paths + end + + def field(locator) + fillable_field(locator).file_field(locator).checkbox(locator).radio_button(locator).select(locator) + end + + def fillable_field(locator) + text_field(locator).password_field(locator).text_area(locator) + end + + def text_field(locator) + append("//input[@type='text'][@id='#{locator}' or @id=//label[contains(.,'#{locator}')]/@for]") + end + + def password_field(locator) + append("//input[@type='password'][@id='#{locator}' or @id=//label[contains(.,'#{locator}')]/@for]") + end + + def text_area(locator) + append("//textarea[@id='#{locator}' or @id=//label[contains(.,'#{locator}')]/@for]") + end + + def radio_button(locator) + append("//input[@type='radio'][@id='#{locator}' or @id=//label[contains(.,'#{locator}')]/@for]") + end + + def checkbox(locator) + append("//input[@type='checkbox'][@id='#{locator}' or @id=//label[contains(.,'#{locator}')]/@for]") + end + + def select(locator) + append("//select[@id='#{locator}' or @id=//label[contains(.,'#{locator}')]/@for]") + end + + def file_field(locator) + append("//input[@type='file'][@id='#{locator}' or @id=//label[contains(.,'#{locator}')]/@for]") + end + + def to_s + @paths.join(' | ') + end + + private + + def append(path) + XPath.new(*[@paths, path].flatten) + end + + end +end diff --git a/spec/session_spec.rb b/spec/session_spec.rb index 2b3d5910..1cdf196f 100644 --- a/spec/session_spec.rb +++ b/spec/session_spec.rb @@ -552,15 +552,10 @@ shared_examples_for "session" do @session.find_field('Region')[:name].should == 'form[region]' end - it "should raise an error if the field doesn't exist" do + it "should be nil if the field doesn't exist" do @session.find_field('Does not exist').should be_nil end - it "should find only given kind of field" do - @session.find_field('form_description', :text_field, :text_area).text.should == 'Descriptive text goes here' - @session.find_field('form_description', :password_field).should be_nil - end - it "should be aliased as 'field_labeled' for webrat compatibility" do @session.field_labeled('Dog').value.should == 'dog' @session.field_labeled('Does not exist').should be_nil diff --git a/spec/xpath_spec.rb b/spec/xpath_spec.rb new file mode 100644 index 00000000..8539c566 --- /dev/null +++ b/spec/xpath_spec.rb @@ -0,0 +1,176 @@ +require File.expand_path('spec_helper', File.dirname(__FILE__)) + +describe Capybara::XPath do + + before do + @driver = Capybara::Driver::RackTest.new(TestApp) + @driver.visit('/form') + @xpath = Capybara::XPath.new + end + + it "should proxy any class method calls to a new instance" do + @query = Capybara::XPath.fillable_field('First Name').to_s + @driver.find(@query).first.value.should == 'John' + end + + it "should respond to instance methods at the class level" do + Capybara::XPath.should respond_to(:fillable_field) + end + + describe '#field' do + it "should find any field by id or label" do + @query = @xpath.field('First Name').to_s + @driver.find(@query).first.value.should == 'John' + @query = @xpath.field('Password').to_s + @driver.find(@query).first.value.should == 'seeekrit' + @query = @xpath.field('Description').to_s + @driver.find(@query).first.text.should == 'Descriptive text goes here' + @query = @xpath.field('Document').to_s + @driver.find(@query).first[:name].should == 'form[document]' + @query = @xpath.field('Cat').to_s + @driver.find(@query).first.value.should == 'cat' + @query = @xpath.field('Male').to_s + @driver.find(@query).first.value.should == 'male' + @query = @xpath.field('Region').to_s + @driver.find(@query).first[:name].should == 'form[region]' + end + + it "should be chainable" do + @query = @xpath.field('First Name').password_field('First Name').to_s + @driver.find(@query).first.value.should == 'John' + @query = @xpath.field('Password').password_field('Password').to_s + @driver.find(@query).first.value.should == 'seeekrit' + end + end + + describe '#fillable_field' do + it "should find a text field, password field, or text area by id or label" do + @query = @xpath.fillable_field('First Name').to_s + @driver.find(@query).first.value.should == 'John' + @query = @xpath.fillable_field('Password').to_s + @driver.find(@query).first.value.should == 'seeekrit' + @query = @xpath.fillable_field('Description').to_s + @driver.find(@query).first.text.should == 'Descriptive text goes here' + end + + it "should be chainable" do + @query = @xpath.fillable_field('First Name').password_field('First Name').to_s + @driver.find(@query).first.value.should == 'John' + @query = @xpath.fillable_field('Password').password_field('Password').to_s + @driver.find(@query).first.value.should == 'seeekrit' + end + end + + describe '#text_field' do + it "should find a text field by id or label" do + @query = @xpath.text_field('form_first_name').to_s + @driver.find(@query).first.value.should == 'John' + @query = @xpath.text_field('First Name').to_s + @driver.find(@query).first.value.should == 'John' + end + + it "should be chainable" do + @query = @xpath.text_field('First Name').password_field('First Name').to_s + @driver.find(@query).first.value.should == 'John' + @query = @xpath.text_field('Password').password_field('Password').to_s + @driver.find(@query).first.value.should == 'seeekrit' + end + end + + describe '#password_field' do + it "should find a password field by id or label" do + @query = @xpath.password_field('form_password').to_s + @driver.find(@query).first.value.should == 'seeekrit' + @query = @xpath.password_field('Password').to_s + @driver.find(@query).first.value.should == 'seeekrit' + end + + it "should be chainable" do + @query = @xpath.password_field('First Name').text_field('First Name').to_s + @driver.find(@query).first.value.should == 'John' + @query = @xpath.password_field('Password').text_field('Password').to_s + @driver.find(@query).first.value.should == 'seeekrit' + end + end + + describe '#text_area' do + it "should find a text area by id or label" do + @query = @xpath.text_area('form_description').to_s + @driver.find(@query).first.text.should == 'Descriptive text goes here' + @query = @xpath.text_area('Description').to_s + @driver.find(@query).first.text.should == 'Descriptive text goes here' + end + + it "should be chainable" do + @query = @xpath.text_area('Description').password_field('Description').to_s + @driver.find(@query).first.text.should == 'Descriptive text goes here' + @query = @xpath.text_area('Password').password_field('Password').to_s + @driver.find(@query).first.value.should == 'seeekrit' + end + end + + describe '#radio_button' do + it "should find a radio button by id or label" do + @query = @xpath.radio_button('Male').to_s + @driver.find(@query).first.value.should == 'male' + @query = @xpath.radio_button('gender_male').to_s + @driver.find(@query).first.value.should == 'male' + end + + it "should be chainable" do + @query = @xpath.radio_button('Male').password_field('Male').to_s + @driver.find(@query).first.value.should == 'male' + @query = @xpath.radio_button('Password').password_field('Password').to_s + @driver.find(@query).first.value.should == 'seeekrit' + end + end + + describe '#checkbox' do + it "should find a checkbox by id or label" do + @query = @xpath.checkbox('Cat').to_s + @driver.find(@query).first.value.should == 'cat' + @query = @xpath.checkbox('form_pets_cat').to_s + @driver.find(@query).first.value.should == 'cat' + end + + it "should be chainable" do + @query = @xpath.checkbox('Cat').password_field('Cat').to_s + @driver.find(@query).first.value.should == 'cat' + @query = @xpath.checkbox('Password').password_field('Password').to_s + @driver.find(@query).first.value.should == 'seeekrit' + end + end + + describe '#select' do + it "should find a select by id or label" do + @query = @xpath.select('Region').to_s + @driver.find(@query).first[:name].should == 'form[region]' + @query = @xpath.select('form_region').to_s + @driver.find(@query).first[:name].should == 'form[region]' + end + + it "should be chainable" do + @query = @xpath.select('Region').password_field('Region').to_s + @driver.find(@query).first[:name].should == 'form[region]' + @query = @xpath.select('Password').password_field('Password').to_s + @driver.find(@query).first.value.should == 'seeekrit' + end + end + + describe '#file_field' do + it "should find a file field by id or label" do + @query = @xpath.file_field('Document').to_s + @driver.find(@query).first[:name].should == 'form[document]' + @query = @xpath.file_field('form_document').to_s + @driver.find(@query).first[:name].should == 'form[document]' + end + + it "should be chainable" do + @query = @xpath.file_field('Document').password_field('Document').to_s + @driver.find(@query).first[:name].should == 'form[document]' + @query = @xpath.file_field('Password').password_field('Password').to_s + @driver.find(@query).first.value.should == 'seeekrit' + end + end + +end