This commit is contained in:
Jonas Nicklas 2010-08-13 15:34:01 +02:00
parent bbc9dc73d2
commit 34a548efe8
3 changed files with 104 additions and 358 deletions

View File

@ -121,7 +121,7 @@ module Capybara
def all(*args)
options = if args.last.is_a?(Hash) then args.pop else {} end
results = Capybara::XPath.tempwrap(normalize_locator(*args)).map do |path|
results = Capybara::XPath.wrap(normalize_locator(*args)).map do |path|
base.find(path)
end.flatten

View File

@ -14,225 +14,144 @@ module Capybara
#
# This will generate an XPath that matches either a text field or a link.
#
class XPath
module XPath
class << self
include ::XPath
include ::XPath
extend self
def from_css(css)
collection(*Nokogiri::CSS.xpath_for(css).map { |selector| ::XPath::Expression::Literal.new(:".#{selector}") }.flatten)
end
alias_method :for_css, :from_css
def from_css(css)
collection(*Nokogiri::CSS.xpath_for(css).map { |selector| ::XPath::Expression::Literal.new(:".#{selector}") }.flatten)
end
alias_method :for_css, :from_css
def collection(*args)
::XPath::Collection.new(*args)
end
def collection(*args)
::XPath::Collection.new(*args)
end
def link(locator)
link = descendant(:a)[attr(:href)]
link[attr(:id).equals(locator) | text.is(locator) | attr(:title).is(locator) | descendant(:img)[attr(:alt).is(locator)]]
end
def link(locator)
link = descendant(:a)[attr(:href)]
link[attr(:id).equals(locator) | text.is(locator) | attr(:title).is(locator) | descendant(:img)[attr(:alt).is(locator)]]
end
def content(locator)
child(:"descendant-or-self::*")[current.n.contains(locator)]
end
def content(locator)
child(:"descendant-or-self::*")[current.n.contains(locator)]
end
def button(locator)
collection(
descendant(:input)[attr(:type).one_of('submit', 'image', 'button')][attr(:id).equals(locator) | attr(:value).is(locator)],
descendant(:button)[attr(:id).equals(locator) | attr(:value).is(locator) | text.is(locator)],
descendant(:input)[attr(:type).equals('image')][attr(:alt).is(locator)]
)
end
def button(locator)
collection(
descendant(:input)[attr(:type).one_of('submit', 'image', 'button')][attr(:id).equals(locator) | attr(:value).is(locator)],
descendant(:button)[attr(:id).equals(locator) | attr(:value).is(locator) | text.is(locator)],
descendant(:input)[attr(:type).equals('image')][attr(:alt).is(locator)]
)
end
def link_or_button(locator)
collection(link(locator), button(locator))
end
def link_or_button(locator)
collection(link(locator), button(locator))
end
def fieldset(locator)
descendant(:fieldset)[attr(:id).equals(locator) | descendant(:legend)[text.is(locator)]]
end
def fieldset(locator)
descendant(:fieldset)[attr(:id).equals(locator) | descendant(:legend)[text.is(locator)]]
end
def field(locator, options={})
if options[:with]
fillable_field(locator, options)
else
xpath = descendant(:input, :textarea, :select)[attr(:type).one_of('submit', 'image', 'hidden').inverse]
xpath = locate_field(xpath, locator)
xpath = xpath[attr(:checked)] if options[:checked]
xpath = xpath[attr(:checked).inverse] if options[:unchecked]
xpath
end
end
def fillable_field(locator, options={})
xpath = descendant(:input, :textarea)[attr(:type).one_of('submit', 'image', 'radio', 'checkbox', 'hidden', 'file').inverse]
def field(locator, options={})
if options[:with]
fillable_field(locator, options)
else
xpath = descendant(:input, :textarea, :select)[attr(:type).one_of('submit', 'image', 'hidden').inverse]
xpath = locate_field(xpath, locator)
xpath = xpath[field_value(options[:with])] if options.has_key?(:with)
xpath = xpath[attr(:checked)] if options[:checked]
xpath = xpath[attr(:checked).inverse] if options[:unchecked]
xpath
end
def select(locator, options={})
xpath = locate_field(descendant(:select), locator)
options[:options].each do |option|
xpath = xpath[descendant(:option).text.equals(option)]
end if options[:options]
[options[:selected]].flatten.each do |option|
xpath = xpath[descendant(:option)[attr(:selected)].text.equals(option)]
end if options[:selected]
xpath
end
def checkbox(locator, options={})
xpath = locate_field(descendant(:input)[attr(:type).equals('checkbox')], locator)
end
def radio_button(locator, options={})
locate_field(descendant(:input)[attr(:type).equals('radio')], locator)
end
def file_field(locator, options={})
locate_field(descendant(:input)[attr(:type).equals('file')], locator)
end
def field_value(value)
(text.is(value) & name.equals('textarea')) | (attr(:value).equals(value) & name.equals('textarea').inverse)
end
def locate_field(xpath, locator)
collection(
xpath[attr(:id).equals(locator) | attr(:name).equals(locator) | attr(:id).equals(anywhere(:label)[text.is(locator)].attr(:for))],
descendant(:label)[text.is(locator)].descendant(xpath)
)
end
def table(locator, options={})
xpath = descendant(:table)[attr(:id).equals(locator) | descendant(:caption).contains(locator)]
xpath = xpath[table_rows(options[:rows])] if options[:rows]
xpath
end
def table_rows(rows)
row_conditions = descendant(:tr)[table_row(rows.first)]
rows.drop(1).each do |row|
row_conditions = row_conditions.next_sibling(:tr)[table_row(row)]
end
row_conditions
end
def table_row(cells)
cell_conditions = child(:td, :th)[text.equals(cells.first)]
cells.drop(1).each do |cell|
cell_conditions = cell_conditions.next_sibling(:td, :th)[text.equals(cell)]
end
cell_conditions
end
def escape(string)
if string.include?("'")
string = string.split("'", -1).map do |substr|
"'#{substr}'"
end.join(%q{,"'",})
"concat(#{string})"
else
"'#{string}'"
end
end
def wrap(path)
if path.is_a?(self)
path
else
new(path.to_s)
end
end
def tempwrap(path)
if path.is_a?(::XPath::Expression) or path.is_a?(::XPath::Collection)
paths = path.to_xpaths
paths
else
wrap(path).paths
end
end
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
def fillable_field(locator, options={})
xpath = descendant(:input, :textarea)[attr(:type).one_of('submit', 'image', 'radio', 'checkbox', 'hidden', 'file').inverse]
xpath = locate_field(xpath, locator)
xpath = xpath[field_value(options[:with])] if options.has_key?(:with)
xpath
end
def to_s
@paths.join(' | ')
def select(locator, options={})
xpath = locate_field(descendant(:select), locator)
options[:options].each do |option|
xpath = xpath[descendant(:option).text.equals(option)]
end if options[:options]
[options[:selected]].flatten.each do |option|
xpath = xpath[descendant(:option)[attr(:selected)].text.equals(option)]
end if options[:selected]
xpath
end
def append(path)
XPath.new(*[@paths, XPath.wrap(path).paths].flatten)
def checkbox(locator, options={})
xpath = locate_field(descendant(:input)[attr(:type).equals('checkbox')], locator)
end
def prepend(path)
XPath.new(*[XPath.wrap(path).paths, @paths].flatten)
def radio_button(locator, options={})
locate_field(descendant(:input)[attr(:type).equals('radio')], locator)
end
def file_field(locator, options={})
locate_field(descendant(:input)[attr(:type).equals('file')], locator)
end
def field_value(value)
(text.is(value) & name.equals('textarea')) | (attr(:value).equals(value) & name.equals('textarea').inverse)
end
def option(name)
append(".//option[normalize-space(text())=#{s(name)}]").append(".//option[contains(.,#{s(name)})]")
descendant(:option)[text.n.is(name)]
end
protected
def input_field(type, locator, options={})
options = options.merge(:value => options[:with]) if options.has_key?(:with)
add_field(locator, ".//input[@type='#{type}']", options)
def locate_field(xpath, locator)
collection(
xpath[attr(:id).equals(locator) | attr(:name).equals(locator) | attr(:id).equals(anywhere(:label)[text.is(locator)].attr(:for))],
descendant(:label)[text.is(locator)].descendant(xpath)
)
end
# place this between to nodes to indicate that they should be siblings
def sibling
def table(locator, options={})
xpath = descendant(:table)[attr(:id).equals(locator) | descendant(:caption).contains(locator)]
xpath = xpath[table_rows(options[:rows])] if options[:rows]
xpath
end
def add_field(locator, field, options={})
postfix = extract_postfix(options)
xpath = append("#{field}[@id=#{s(locator)}]#{postfix}")
xpath = xpath.append("#{field}[@name=#{s(locator)}]#{postfix}")
xpath = xpath.append("#{field}[@id=//label[contains(.,#{s(locator)})]/@for]#{postfix}")
# FIXME: Label should not be scoped to node, temporary workaround!!!
xpath = xpath.append(".//label[contains(.,#{s(locator)})]/#{field}#{postfix}")
xpath.prepend("#{field}[@id=//label[text()=#{s(locator)}]/@for]#{postfix}")
def table_rows(rows)
row_conditions = descendant(:tr)[table_row(rows.first)]
rows.drop(1).each do |row|
row_conditions = row_conditions.next_sibling(:tr)[table_row(row)]
end
row_conditions
end
def extract_postfix(options)
options.inject("") do |postfix, (key, value)|
case key
when :value then postfix += "[@value=#{s(value)}]"
when :text then postfix += "[text()=#{s(value)}]"
when :checked then postfix += "[@checked]"
when :unchecked then postfix += "[not(@checked)]"
when :options then postfix += value.map { |o| "[.//option/text()=#{s(o)}]" }.join
when :selected then postfix += [value].flatten.map { |o| "[.//option[@selected]/text()=#{s(o)}]" }.join
end
postfix
def table_row(cells)
cell_conditions = child(:td, :th)[text.equals(cells.first)]
cells.drop(1).each do |cell|
cell_conditions = cell_conditions.next_sibling(:td, :th)[text.equals(cell)]
end
cell_conditions
end
def escape(string)
if string.include?("'")
string = string.split("'", -1).map do |substr|
"'#{substr}'"
end.join(%q{,"'",})
"concat(#{string})"
else
"'#{string}'"
end
end
# Sanitize a String for putting it into an xpath query
def s(string)
XPath.escape(string)
def wrap(path)
if path.respond_to?(:to_xpaths)
path.to_xpaths
else
[path.to_s].flatten
end
end
end
end

View File

@ -1,173 +0,0 @@
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 '.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 '#append' do
it "should append an XPath's paths" do
@xpath = Capybara::XPath.wrap('//test')
@xpath = @xpath.append(Capybara::XPath.wrap('//foo/bar'))
@xpath.paths.should == ['//test', '//foo/bar']
end
it "should append an String as a new path" do
@xpath = Capybara::XPath.wrap('//test')
@xpath = @xpath.append('//foo/bar')
@xpath.paths.should == ['//test', '//foo/bar']
end
end
describe '#prepend' do
it "should prepend an XPath's paths" do
@xpath = Capybara::XPath.wrap('//test')
@xpath = @xpath.prepend(Capybara::XPath.wrap('//foo/bar'))
@xpath.paths.should == ['//foo/bar', '//test']
end
it "should prepend an String as a new path" do
@xpath = Capybara::XPath.wrap('//test')
@xpath = @xpath.prepend('//foo/bar')
@xpath.paths.should == ['//foo/bar', '//test']
end
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').button('Click me!').to_s
@driver.find(@query).first.value.should == 'John'
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').button('Click me!').to_s
@driver.find(@query).first.value.should == 'John'
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').button('Click me!').to_s
@driver.find(@query).first.text.should == 'Descriptive text goes here'
end
end
describe '#button' do
it "should find a button by id or content" do
@query = @xpath.button('awe123').to_s
@driver.find(@query).first.value.should == 'awesome'
@query = @xpath.button('okay556').to_s
@driver.find(@query).first.value.should == 'okay'
@query = @xpath.button('click_me_123').to_s
@driver.find(@query).first.value.should == 'click_me'
@query = @xpath.button('Click me!').to_s
@driver.find(@query).first.value.should == 'click_me'
@query = @xpath.button('fresh_btn').to_s
@driver.find(@query).first.value.should == 'i am fresh'
@query = @xpath.button('i am fresh').to_s
@driver.find(@query).first[:name].should == 'form[fresh]'
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').button('Click me!').to_s
@driver.find(@query).first.value.should == 'male'
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').button('Click me!').to_s
@driver.find(@query).first.value.should == 'cat'
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').button('Click me!').to_s
@driver.find(@query).first[:name].should == 'form[region]'
end
end
end