2009-12-09 13:03:55 -05:00
|
|
|
module Capybara
|
2010-07-16 16:08:39 -04:00
|
|
|
|
|
|
|
##
|
|
|
|
#
|
|
|
|
# This is a class for generating XPath queries, use it like this:
|
|
|
|
#
|
2009-12-09 13:03:55 -05:00
|
|
|
# Xpath.text_field('foo').link('blah').to_s
|
2010-07-16 16:08:39 -04:00
|
|
|
#
|
|
|
|
# This will generate an XPath that matches either a text field or a link.
|
|
|
|
#
|
2009-12-09 13:03:55 -05:00
|
|
|
class XPath
|
2009-12-27 03:11:22 -05:00
|
|
|
|
2009-12-09 13:03:55 -05:00
|
|
|
class << self
|
2010-04-26 11:23:34 -04:00
|
|
|
def escape(string)
|
|
|
|
if string.include?("'")
|
|
|
|
string = string.split("'", -1).map do |substr|
|
|
|
|
"'#{substr}'"
|
|
|
|
end.join(%q{,"'",})
|
|
|
|
"concat(#{string})"
|
|
|
|
else
|
|
|
|
"'#{string}'"
|
|
|
|
end
|
2009-12-24 00:41:34 -05:00
|
|
|
end
|
2010-04-26 11:23:34 -04:00
|
|
|
|
2009-12-12 15:46:08 -05:00
|
|
|
def wrap(path)
|
|
|
|
if path.is_a?(self)
|
|
|
|
path
|
|
|
|
else
|
|
|
|
new(path.to_s)
|
|
|
|
end
|
|
|
|
end
|
2010-01-01 11:48:39 -05:00
|
|
|
|
2009-12-09 13:03:55 -05:00
|
|
|
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
|
2009-12-24 00:41:34 -05:00
|
|
|
|
2010-03-12 14:13:10 -05:00
|
|
|
def to_s
|
|
|
|
@paths.join(' | ')
|
|
|
|
end
|
|
|
|
|
|
|
|
def append(path)
|
|
|
|
XPath.new(*[@paths, XPath.wrap(path).paths].flatten)
|
|
|
|
end
|
|
|
|
|
|
|
|
def prepend(path)
|
|
|
|
XPath.new(*[XPath.wrap(path).paths, @paths].flatten)
|
|
|
|
end
|
|
|
|
|
2010-03-12 13:16:09 -05:00
|
|
|
def from_css(css)
|
2010-07-09 19:19:09 -04:00
|
|
|
XPath.new(*[@paths, Nokogiri::CSS.xpath_for(css).map { |selector| '.' + selector }].flatten)
|
2010-03-12 13:16:09 -05:00
|
|
|
end
|
|
|
|
alias_method :for_css, :from_css
|
|
|
|
|
2010-01-18 15:28:06 -05:00
|
|
|
def field(locator, options={})
|
|
|
|
if options[:with]
|
|
|
|
fillable_field(locator, options)
|
|
|
|
else
|
2010-01-18 16:31:22 -05:00
|
|
|
xpath = fillable_field(locator)
|
|
|
|
xpath = xpath.input_field(:file, locator, options)
|
|
|
|
xpath = xpath.checkbox(locator, options)
|
|
|
|
xpath = xpath.radio_button(locator, options)
|
|
|
|
xpath.select(locator, options)
|
2010-01-18 15:28:06 -05:00
|
|
|
end
|
2009-12-09 13:03:55 -05:00
|
|
|
end
|
|
|
|
|
2010-01-18 15:28:06 -05:00
|
|
|
def fillable_field(locator, options={})
|
2010-03-12 14:11:21 -05:00
|
|
|
text_area(locator, options).text_field(locator, options)
|
2009-12-09 13:03:55 -05:00
|
|
|
end
|
2010-01-01 11:48:39 -05:00
|
|
|
|
2009-12-09 13:11:52 -05:00
|
|
|
def content(locator)
|
2010-07-09 19:19:09 -04:00
|
|
|
append("./descendant-or-self::*[contains(normalize-space(.),#{s(locator)})]")
|
2009-12-09 13:11:52 -05:00
|
|
|
end
|
2010-01-01 11:48:39 -05:00
|
|
|
|
2010-02-20 16:41:53 -05:00
|
|
|
def table(locator, options={})
|
|
|
|
conditions = ""
|
|
|
|
if options[:rows]
|
|
|
|
row_conditions = options[:rows].map do |row|
|
|
|
|
row = row.map { |column| "*[self::td or self::th][text()=#{s(column)}]" }.join(sibling)
|
|
|
|
"tr[./#{row}]"
|
|
|
|
end.join(sibling)
|
|
|
|
conditions << "[.//#{row_conditions}]"
|
|
|
|
end
|
2010-07-09 19:19:09 -04:00
|
|
|
append(".//table[@id=#{s(locator)} or contains(caption,#{s(locator)})]#{conditions}")
|
2009-12-09 13:11:52 -05:00
|
|
|
end
|
2010-01-01 11:48:39 -05:00
|
|
|
|
2009-12-09 13:11:52 -05:00
|
|
|
def fieldset(locator)
|
2010-07-09 19:19:09 -04:00
|
|
|
append(".//fieldset[@id=#{s(locator)} or contains(legend,#{s(locator)})]")
|
2009-12-09 13:11:52 -05:00
|
|
|
end
|
2010-01-01 11:48:39 -05:00
|
|
|
|
2009-12-09 13:17:01 -05:00
|
|
|
def link(locator)
|
2010-07-09 19:19:09 -04:00
|
|
|
xpath = append(".//a[@href][@id=#{s(locator)} or contains(.,#{s(locator)}) or contains(@title,#{s(locator)}) or img[contains(@alt,#{s(locator)})]]")
|
|
|
|
xpath.prepend(".//a[@href][text()=#{s(locator)} or @title=#{s(locator)} or img[@alt=#{s(locator)}]]")
|
2009-12-09 13:17:01 -05:00
|
|
|
end
|
2010-01-01 11:48:39 -05:00
|
|
|
|
2009-12-09 13:17:01 -05:00
|
|
|
def button(locator)
|
2010-07-09 19:19:09 -04:00
|
|
|
xpath = append(".//input[@type='submit' or @type='image' or @type='button'][@id=#{s(locator)} or contains(@value,#{s(locator)})]")
|
|
|
|
xpath = xpath.append(".//button[@id=#{s(locator)} or contains(@value,#{s(locator)}) or contains(.,#{s(locator)})]")
|
|
|
|
xpath = xpath.prepend(".//input[@type='submit' or @type='image' or @type='button'][@value=#{s(locator)}]")
|
|
|
|
xpath = xpath.prepend(".//input[@type='image'][@alt=#{s(locator)} or contains(@alt,#{s(locator)})]")
|
|
|
|
xpath = xpath.prepend(".//button[@value=#{s(locator)} or text()=#{s(locator)}]")
|
2009-12-09 13:17:01 -05:00
|
|
|
end
|
2009-12-09 13:03:55 -05:00
|
|
|
|
2010-03-12 14:11:21 -05:00
|
|
|
def text_field(locator, options={})
|
|
|
|
options = options.merge(:value => options[:with]) if options.has_key?(:with)
|
2010-07-09 19:19:09 -04:00
|
|
|
add_field(locator, ".//input[not(@type) or (@type!='radio' and @type!='checkbox' and @type!='hidden')]", options)
|
2010-03-12 14:11:21 -05:00
|
|
|
end
|
|
|
|
|
2010-01-30 14:28:45 -05:00
|
|
|
def text_area(locator, options={})
|
2010-02-05 03:04:47 -05:00
|
|
|
options = options.merge(:text => options[:with]) if options.has_key?(:with)
|
2010-07-09 19:19:09 -04:00
|
|
|
add_field(locator, ".//textarea", options)
|
2009-12-09 13:03:55 -05:00
|
|
|
end
|
|
|
|
|
2010-01-18 16:31:22 -05:00
|
|
|
def select(locator, options={})
|
2010-07-09 19:19:09 -04:00
|
|
|
add_field(locator, ".//select", options)
|
2009-12-09 13:03:55 -05:00
|
|
|
end
|
|
|
|
|
2010-01-18 16:31:22 -05:00
|
|
|
def checkbox(locator, options={})
|
|
|
|
input_field(:checkbox, locator, options)
|
2010-01-11 14:36:05 -05:00
|
|
|
end
|
|
|
|
|
2010-01-18 16:31:22 -05:00
|
|
|
def radio_button(locator, options={})
|
|
|
|
input_field(:radio, locator, options)
|
2010-01-11 14:36:05 -05:00
|
|
|
end
|
|
|
|
|
2010-03-12 14:11:21 -05:00
|
|
|
def file_field(locator, options={})
|
|
|
|
input_field(:file, locator, options)
|
2010-01-11 14:36:05 -05:00
|
|
|
end
|
|
|
|
|
2009-12-09 13:17:01 -05:00
|
|
|
protected
|
2010-01-01 11:48:39 -05:00
|
|
|
|
2010-03-12 14:11:21 -05:00
|
|
|
def input_field(type, locator, options={})
|
|
|
|
options = options.merge(:value => options[:with]) if options.has_key?(:with)
|
2010-07-09 19:19:09 -04:00
|
|
|
add_field(locator, ".//input[@type='#{type}']", options)
|
2010-03-12 14:11:21 -05:00
|
|
|
end
|
|
|
|
|
2010-02-20 16:41:53 -05:00
|
|
|
# place this between to nodes to indicate that they should be siblings
|
|
|
|
def sibling
|
|
|
|
'/following-sibling::*[1]/self::'
|
|
|
|
end
|
|
|
|
|
2010-01-18 15:28:06 -05:00
|
|
|
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}")
|
2010-07-09 19:19:09 -04:00
|
|
|
# FIXME: Label should not be scoped to node, temporary workaround!!!
|
|
|
|
xpath = xpath.append(".//label[contains(.,#{s(locator)})]/#{field}#{postfix}")
|
2010-01-18 15:28:06 -05:00
|
|
|
xpath.prepend("#{field}[@id=//label[text()=#{s(locator)}]/@for]#{postfix}")
|
|
|
|
end
|
|
|
|
|
|
|
|
def extract_postfix(options)
|
|
|
|
options.inject("") do |postfix, (key, value)|
|
|
|
|
case key
|
2010-02-05 03:04:47 -05:00
|
|
|
when :value then postfix += "[@value=#{s(value)}]"
|
|
|
|
when :text then postfix += "[text()=#{s(value)}]"
|
2010-01-18 16:31:22 -05:00
|
|
|
when :checked then postfix += "[@checked]"
|
|
|
|
when :unchecked then postfix += "[not(@checked)]"
|
2010-06-03 02:55:10 -04:00
|
|
|
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
|
2010-01-18 15:28:06 -05:00
|
|
|
end
|
|
|
|
postfix
|
|
|
|
end
|
2009-12-09 14:20:40 -05:00
|
|
|
end
|
2010-01-01 11:48:39 -05:00
|
|
|
|
2009-12-09 13:11:52 -05:00
|
|
|
# Sanitize a String for putting it into an xpath query
|
|
|
|
def s(string)
|
2010-04-26 11:23:34 -04:00
|
|
|
XPath.escape(string)
|
2009-12-09 13:11:52 -05:00
|
|
|
end
|
2009-12-09 13:03:55 -05:00
|
|
|
|
|
|
|
end
|
|
|
|
end
|