locator 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/README.textile +175 -4
  2. data/TODO +1 -1
  3. data/lib/core_ext/hash/except.rb +11 -0
  4. data/lib/core_ext/hash/slice.rb +14 -0
  5. data/lib/locator.rb +2 -1
  6. data/lib/locator/dom.rb +1 -0
  7. data/lib/locator/dom/htmlunit.rb +10 -0
  8. data/lib/locator/dom/htmlunit/element.rb +80 -0
  9. data/lib/locator/dom/htmlunit/page.rb +11 -0
  10. data/lib/locator/dom/nokogiri/element.rb +15 -15
  11. data/lib/locator/dom/nokogiri/page.rb +3 -2
  12. data/lib/locator/element.rb +9 -4
  13. data/lib/locator/element/area.rb +1 -1
  14. data/lib/locator/element/button.rb +2 -2
  15. data/lib/locator/element/form.rb +1 -1
  16. data/lib/locator/element/form_element.rb +7 -1
  17. data/lib/locator/element/input.rb +2 -3
  18. data/lib/locator/element/label.rb +1 -1
  19. data/lib/locator/element/link.rb +1 -1
  20. data/lib/locator/element/select.rb +1 -1
  21. data/lib/locator/element/select_option.rb +1 -1
  22. data/lib/locator/element/text_area.rb +1 -1
  23. data/lib/locator/result.rb +22 -6
  24. data/lib/locator/version.rb +1 -1
  25. data/lib/locator/xpath.rb +4 -0
  26. data/test/locator/dom/htmlunit_test.rb +77 -0
  27. data/test/locator/dom/nokogiri_test.rb +16 -0
  28. data/test/locator/element/button_test.rb +4 -4
  29. data/test/locator/element/field_test.rb +12 -12
  30. data/test/locator/element/form_test.rb +4 -4
  31. data/test/locator/element/hidden_field_test.rb +36 -0
  32. data/test/locator/element/label_test.rb +3 -3
  33. data/test/locator/element/select_option_test.rb +5 -5
  34. data/test/locator/element/select_test.rb +3 -3
  35. data/test/locator/element/text_area_test.rb +3 -3
  36. data/test/locator/element_test.rb +17 -7
  37. data/test/locator_test.rb +8 -8
  38. metadata +13 -2
data/README.textile CHANGED
@@ -1,13 +1,184 @@
1
+ <a name="readme"></a>
2
+
1
3
  h1. Locator
2
4
 
3
- This library aims to extract common html element selection from testing tools such as "Webrat":http://github.com/brynary/webrat/blob/master/lib/webrat/core/locators.rb, "Capybara":http://github.com/jnicklas/capybara/blob/master/lib/capybara/xpath.rb or "Steam":http://github.com/svenfuchs/steam/blob/master/lib/steam/locators.rb. At its core it constructs "XPath":http://github.com/svenfuchs/locator/blob/master/lib/locator/xpath.rb objects using a simple "boolean expression engine":http://github.com/svenfuchs/locator/blob/master/lib/locator/boolean.rb in order to locate elements in a Nokogiri DOM. It provides a bunch of "Element classes":http://github.com/svenfuchs/locator/blob/master/lib/locator/element.rb that are targeted at implementing a Webrat-style DSL for convenience.
5
+ This Gem aims to extract common html element selection from testing tools such as "Webrat":http://github.com/brynary/webrat/blob/master/lib/webrat/core/locators.rb, "Capybara":http://github.com/jnicklas/capybara/blob/master/lib/capybara/xpath.rb or "Steam":http://github.com/svenfuchs/steam/blob/master/lib/steam/locators.rb. At its core it constructs "XPath":http://github.com/svenfuchs/locator/blob/master/lib/locator/xpath.rb objects using a simple "boolean expression engine":http://github.com/svenfuchs/locator/blob/master/lib/locator/boolean.rb in order to locate elements in a Nokogiri DOM. It provides a bunch of "Element classes":http://github.com/svenfuchs/locator/blob/master/lib/locator/element.rb that are targeted at implementing a Webrat-style DSL for convenience.
6
+
7
+ "See below":#why-another-library for why I strongly believe that this behavior should be implemented as a stand-alone library.
4
8
 
5
9
  h2. Usage
6
10
 
7
- Of course you can use the underlying implementation, too, but there are three main public methods which are supposed to give you access to all you need.
11
+ Of course you can use the underlying implementation, too, but there are three main public methods which are supposed to give you access to all you need:
12
+
13
+ * @Locator.locate(html, *args)@
14
+ * @Locator.within(*args)@
15
+ * @Locator.xpath(*args)@
16
+
17
+ The following code examples all assume that you include the Locator module like so:
18
+
19
+ pre. include Locator
8
20
 
9
- h3. Building an XPath
21
+ All the examples should work without doing so though. You should be able to statically call the same methods on the Locator module instead.
10
22
 
11
23
  h3. Locating an element
12
24
 
13
- h3. Scoping to an element
25
+ There are three APIs for locating elements:
26
+
27
+ * using a locator type (e.g. @:link@) and/or required node attributes (e.g. @:id => 'foo'@) and/or a selector (e.g. @"The link"@)
28
+ * using an xpath
29
+ * using a css selector
30
+
31
+ In all cases you can use the Locator.locate method which wants you to pass the HTML code as the first argument:
32
+
33
+ pre. locate(html, *args)
34
+
35
+ h4. Locating elements using a locator type
36
+
37
+ If you pass a *Symbol* as a first argument, then Locator will interpret this as a *locator type*. For a list of "supported locator types":#locator-types see below, these sometimes but do not always equal HTML tag node names.
38
+
39
+ E.g. the @:form@ tag will simply locate HTML form tags. Locator types like @:button@, @:field@ and @:link@ bake in some more knowledge about how we want to build a DSL (e.g. a helper locator_button could use the Button locator type and match both HTML button tags and clickable input tags such as submit input tags).
40
+
41
+ So this will simply return the first link tag on the page:
42
+
43
+ pre. locate(html, :link)
44
+
45
+ h4. Locating elements using a selector
46
+
47
+ You can (alternatively or additionally) specify a *selector*. Selectors will be matched against matchable values depending on the locator type, "see below":#matchable-values for details.
48
+
49
+ E.g. to find a link that has the text @"Click here!"@ you could use any of the following calls:
50
+
51
+ <pre>locate(html, :link, "Click here!")
52
+ locate(html, :link, "Click")
53
+ locate(html, :link, "here!")
54
+ locate(html, "Click")</pre>
55
+
56
+ And so on. Note that Locator will pick the *outermost* element with the *shortest matching value*, see below for details about "matchable values":#matchable-values.
57
+
58
+ h4. Locating elements using attributes
59
+
60
+ You can (alternatively or additionally) specify required attributes. E.g. to find a link with the id @"foo"@ you can use:
61
+
62
+ pre. locate(html, :link, :id => "foo")
63
+
64
+ You can combine that with a selector. E.g. this will locate a link that has a text "click" and a class "foo":
65
+
66
+ pre. locate(html, :link, "click", :class => "foo")
67
+
68
+ All attributes are matched by equality. I.e. when you pass @:id => "foo"@ then only elements are matched that have the exact id attribute "foo". The only *exception* from this is the @:class@ attribute. When you pass @:class => "foo"@ then this is semantically equivalent to saying "an element that has the class 'foo'".
69
+
70
+ (Technically the class attribute is matched using an xpath @*[contains(concat(' ', @class, ' '), concat(' ', "foo", ' '))]@, i.e. leading and tailing spaces are added to the element's class attribute value and the requested value, and then it checks that the requested value is contained in the actual class attribute value.)
71
+
72
+ h4. Locating elements using an XPath or CSS selector
73
+
74
+ Instead using the API described above you can also specify an XPath or CSS selector like so:
75
+
76
+ <pre>locate(html, :xpath => "//div[@id='foo']")
77
+ locate(html, :xpath => "div#foo")</pre>
78
+
79
+ These both would lookup the same div element with the id "foo".
80
+
81
+ h3. Scoping to an element
82
+
83
+ You can scope the element lookup to certain parts of your HTML Dom. E.g. this is useful when you have a couple of forms with the same input elements and you want to fill in a particular one.
84
+
85
+ You can use both the Locator.locate and Locator.within methods for this:
86
+
87
+ pre. within(:form, "login_form") { locate(html, :field, "password") }
88
+
89
+ Or:
90
+
91
+ pre. locate(html, :form, "login_form") { locate(html, :field, "password") }
92
+
93
+ h2. Appendix
94
+
95
+ <a name="locator-types"></a>
96
+
97
+ h3. Supported locator types
98
+
99
+ All types will match the id attribute, all form elements (input, textarea etc.) additionally match the name attribute. Some locators additionally match the value attribute and/or the node content (inner_text). Also see below for "matchable values":#matchable-values.
100
+
101
+ All form element locator types (like :field, :checkbox, :file etc.) can additionally be located through their label tag. E.g. when you have a label tag "Name" that points to a text input tag then you can locate the text input using the text selector "Name". E.g.:
102
+
103
+ pre. locate(html, :field, "Name")
104
+
105
+ Here's a list of supported locator types and any additional matchable values:
106
+
107
+ | *Type* | *Locates* | *Extra matchable* |
108
+ | :button | a button element or a submit, button or image input tag | content (for buttons), value (for inputs) |
109
+ | :check_box | a checkbox input tag | |
110
+ | :field | an input element (see below) or a textarea | |
111
+ | :file | a file input tag | |
112
+ | :form | a form tag | |
113
+ | :hidden_field | a hidden input tag | |
114
+ | :input | an input tag with a type of text, password , email, url, search, tel or color | |
115
+ | :label | a label tag | content |
116
+ | :link | a link (i.e. an :a tag with an href attribute) | content |
117
+ | :radio_button | a radio input tag | |
118
+ | :select | a select box | |
119
+ | :select_option | a select option tag | value, content |
120
+ | :text_area | a textarea tag | |
121
+
122
+ <a name="matchable-values"></a>
123
+
124
+ h3. Matchable values
125
+
126
+ When given a *selector* Locator (as in @locate("The link")@) will match it against different node values depending on the locator type. Node attributes like id, name and value need to equal the given selector. The node content needs to include the selector text (case sensitive).
127
+
128
+ Locator will then pick the *outermost* element with the *shortest* matching value.
129
+
130
+ | *Matchable* | *Looks at* | *Match type* |
131
+ | content | the element's full inner text | contained in content |
132
+ | id | the elements id attribute | equals |
133
+ | name | the elements name attribute | equals |
134
+ | value | the elements value attribute | equals |
135
+
136
+ E.g. @locate(html, :link, "link")@ will find both a link with the text @"link"@ as well as @"The link"@ but it will select and return the first one because it has the shorter matching value (i.e. @"link"@).
137
+
138
+ Locator regards the inner text of an element as its content. That means that the given HTML structure looks like this:
139
+
140
+ <pre><div>
141
+ <div>
142
+ Some <span>text</span>
143
+ </div>
144
+ </div></pre>
145
+
146
+ ... then @locate(:div, "Some text")@ will locate the outer div because it a) ignores the nested span so that the content (inner text) is @"Some text"@ and b) the contents of both div elements are the same.
147
+
148
+ The same locator will return the inner div though when given the following HTML structure:
149
+
150
+ <pre><div>
151
+ <div>
152
+ Some <span>text</span>
153
+ </div>
154
+ here
155
+ </div></pre>
156
+
157
+ ... because the content of the outer div now is @"Some text here"@ which is longer than @"Some text"@ and Locator returns the *outermost* element with the *shortest*@ matching value.
158
+
159
+ This might seem complicated at first but it is required and quite consistent if you think about it.
160
+
161
+ One obvious reason for returning the outermost element is that one wants @locate@ and @within@ behave the same way. If within would refer to the innermost element though then the following locator would not find any element in the HTML above which would be highly confusing:
162
+
163
+ <pre>within(:div) { within(:div) { locate(:span) } }</pre>
164
+
165
+ And an obvious reason for returning an element with the shortest matching value is that in the following case one will want @locate("The Link")@ to return the second element, not the first one:
166
+
167
+ <pre><a href="#">The link with extra text</a>
168
+ <a href="http://www.some-very-long-url.com">The link!</a></pre>
169
+
170
+ <a name="why-another-library"></a>
171
+
172
+ h3. Why another library?
173
+
174
+ In my opinion each of the libraries mentioned above do way to much. Amongst many other things they all implement tools for locating HTML elements from a Dom in some way. When we look at the details of the implementation they all do it differently though. So moving a test suite from Webrat to Capybara or Steam might be easy if only the simplest and most common helpers are used. But it can also be a huge pita because of all the tiny differences in those libraries: the APIs are simply not the same even if they seem to be at the first glimpse.
175
+
176
+ Thus, having a library that does nothing but locating elements but does this one thing well and can then be used by other solutions that add other features is the way to go.
177
+
178
+ By now Locator has been integrated to Steam (which already very much streamlined Steam's API). Obviously I would be humbled if other solutions would pick up Locator, too, and I do offer my help for working on this.
179
+
180
+
181
+ TODO
182
+
183
+ * add notes about using Locator::Element classes directly
184
+
data/TODO CHANGED
@@ -1,4 +1,4 @@
1
- somehow allow these?
1
+ Somehow allow these?
2
2
 
3
3
  locate(html, :foo) { locate(:bar) }
4
4
  within(html, :foo) { locate(:bar) }
@@ -0,0 +1,11 @@
1
+ class Hash
2
+ def except!(*keys)
3
+ keys.map! { |key| convert_key(key) } if respond_to?(:convert_key)
4
+ keys.each { |key| delete(key) }
5
+ self
6
+ end
7
+
8
+ def except(*keys)
9
+ dup.except!(*keys)
10
+ end
11
+ end unless Hash.method_defined?(:slice)
@@ -0,0 +1,14 @@
1
+ class Hash
2
+ def slice!(*keys)
3
+ omit = slice(*self.keys - keys)
4
+ hash = slice(*keys)
5
+ replace(hash)
6
+ omit
7
+ end
8
+
9
+ def slice(*keys)
10
+ hash = self.class.new
11
+ keys.each { |k| hash[k] = self[k] if has_key?(k) }
12
+ hash
13
+ end
14
+ end unless Hash.method_defined?(:slice)
data/lib/locator.rb CHANGED
@@ -26,7 +26,8 @@ module Locator
26
26
  end
27
27
  end
28
28
 
29
- def xpath(type, *args)
29
+ def xpath(*args)
30
+ type = args.shift if args.first.is_a?(Symbol)
30
31
  Locator[type].new.xpath(*args)
31
32
  end
32
33
 
data/lib/locator/dom.rb CHANGED
@@ -2,6 +2,7 @@ require 'nokogiri'
2
2
 
3
3
  module Locator
4
4
  module Dom
5
+ autoload :Htmlunit, 'locator/dom/htmlunit'
5
6
  autoload :Nokogiri, 'locator/dom/nokogiri'
6
7
 
7
8
  class << self
@@ -0,0 +1,10 @@
1
+ require 'nokogiri'
2
+
3
+ module Locator
4
+ module Dom
5
+ module Htmlunit
6
+ autoload :Element, 'locator/dom/htmlunit/element'
7
+ autoload :Page, 'locator/dom/htmlunit/page'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,80 @@
1
+ module Locator
2
+ module Dom
3
+ module Htmlunit
4
+ class Element
5
+ attr_reader :element, :matches
6
+
7
+ def initialize(element)
8
+ @element = element || raise('nil passed as an element')
9
+ @matches = []
10
+ end
11
+
12
+ def <=>(other)
13
+ to_s.length <=> other.to_s.length
14
+ end
15
+
16
+ def name
17
+ element.getNodeName
18
+ end
19
+
20
+ def xpath
21
+ element.getCanonicalXPath
22
+ end
23
+
24
+ def css_path
25
+ raise 'not implemented'
26
+ end
27
+
28
+ def content
29
+ # TODO HtmlUnit has asText and getTextContent
30
+ element.getTextContent # Celerity normalizes this
31
+ end
32
+
33
+ # def inner_html
34
+ # element.inner_html
35
+ # end
36
+
37
+ def to_s
38
+ element.asXml
39
+ end
40
+
41
+ def attribute(name)
42
+ element.getAttribute(name)
43
+ end
44
+
45
+ def element_by_id(id)
46
+ element_by_xpath("//*[@id='#{id}']")
47
+ end
48
+
49
+ def element_by_xpath(xpath)
50
+ if element = self.element.getFirstByXPath(xpath)
51
+ Element.new(element)
52
+ end
53
+ end
54
+
55
+ def elements_by_xpath(*xpaths)
56
+ xpaths.map do |xpath|
57
+ element.getByXPath(xpath).toArray.map { |e| Element.new(e) }
58
+ end.flatten
59
+ end
60
+
61
+ def elements_by_css(*rules)
62
+ elements_by_xpath(*::Nokogiri::CSS.xpath_for(*rules))
63
+ end
64
+
65
+ def ancestors
66
+ ancestors, node = [], element.getParentNode
67
+ until node.getNodeName == '#document'
68
+ ancestors.unshift(Element.new(node))
69
+ node = node.getParentNode
70
+ end
71
+ ancestors
72
+ end
73
+
74
+ def ancestor_of?(other)
75
+ other.element.ancestors.include?(element)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,11 @@
1
+ module Locator
2
+ module Dom
3
+ module Htmlunit
4
+ class Page < Element
5
+ def dom
6
+ element
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -14,7 +14,7 @@ module Locator
14
14
  end
15
15
 
16
16
  def name
17
- @element.name
17
+ element.name
18
18
  end
19
19
 
20
20
  def xpath
@@ -37,32 +37,32 @@ module Locator
37
37
  element.to_s
38
38
  end
39
39
 
40
- def tag_name
41
- element.description.name
42
- end
43
-
44
- def ancestor_of?(other)
45
- other.element.ancestors.include?(element)
46
- end
47
-
48
40
  def attribute(name)
49
41
  element.attribute(name).to_s
50
42
  end
51
43
 
52
- def attributes(names)
53
- names.map { |name| attribute(name) }
54
- end
55
-
56
44
  def element_by_id(id)
57
45
  elements_by_xpath("//*[@id='#{id}']").first
58
46
  end
59
47
 
48
+ def element_by_xpath(xpath)
49
+ elements_by_xpath(xpath).first
50
+ end
51
+
52
+ def elements_by_xpath(*xpaths)
53
+ element.xpath(*xpaths).map { |element| Element.new(element) }
54
+ end
55
+
60
56
  def elements_by_css(*rules)
61
57
  element.css(*rules).map { |element| Element.new(element) }
62
58
  end
63
59
 
64
- def elements_by_xpath(*xpaths)
65
- element.xpath(*xpaths).map { |element| Element.new(element) }
60
+ def ancestors
61
+ element.ancestors
62
+ end
63
+
64
+ def ancestor_of?(other)
65
+ other.ancestors.include?(element)
66
66
  end
67
67
  end
68
68
  end
@@ -2,8 +2,9 @@ module Locator
2
2
  module Dom
3
3
  module Nokogiri
4
4
  class Page < Element
5
- def initialize(html)
6
- super(::Nokogiri::HTML::Document.parse(html))
5
+ def initialize(dom)
6
+ dom = ::Nokogiri::HTML::Document.parse(dom) if dom.is_a?(String)
7
+ super
7
8
  end
8
9
 
9
10
  def dom
@@ -1,3 +1,6 @@
1
+ require 'core_ext/hash/except'
2
+ require 'core_ext/hash/slice'
3
+
1
4
  module Locator
2
5
  class Element
3
6
  autoload :Area, 'locator/element/area'
@@ -20,10 +23,11 @@ module Locator
20
23
 
21
24
  attr_reader :name, :css, :locatables, :attributes
22
25
 
23
- def initialize(name = nil, locatables = nil, attributes = nil)
26
+ def initialize(*args)
27
+ attributes, name = args.last.is_a?(Hash) ? args.pop : {}, args.pop
24
28
  @name = name
25
- @locatables = { :equals => :id, :matches => :content }.merge(locatables || {})
26
- @attributes = attributes || {}
29
+ @attributes = attributes
30
+ @locatables = ((attributes.delete(:matches) || [:content]) + [:id]).uniq
27
31
  end
28
32
 
29
33
  def locate(*args)
@@ -46,7 +50,8 @@ module Locator
46
50
  def lookup(scope, selector, attributes = {})
47
51
  scope = scope.respond_to?(:elements_by_xpath) ? scope : Locator::Dom.page(scope)
48
52
  xpath, css = attributes.delete(:xpath), attributes.delete(:css)
49
- elements = css ? scope.elements_by_css(css) : scope.elements_by_xpath(xpath || xpath(attributes))
53
+ xpath = ::Nokogiri::CSS.xpath_for(*css).first if css
54
+ elements = scope.elements_by_xpath(xpath || xpath(attributes))
50
55
  Result.new(elements).filter!(selector, locatables)
51
56
  end
52
57
  end
@@ -2,7 +2,7 @@ module Locator
2
2
  class Element
3
3
  class Area < Element
4
4
  def initialize
5
- super(:area, :equals => [:id], :matches => [:alt])
5
+ super(:area, :matches => [:alt])
6
6
  end
7
7
  end
8
8
  end
@@ -2,8 +2,8 @@ module Locator
2
2
  class Element
3
3
  class Button < ElementsList
4
4
  def initialize
5
- input = Element.new(:input, { :equals => [:id, :name], :matches => [:value] }, :type => %w(submit button image))
6
- button = Element.new(:button, :equals => [:id, :name], :matches => [:content])
5
+ input = Element.new(:input, :matches => [:value], :type => %w(submit button image))
6
+ button = Element.new(:button, :matches => [:content])
7
7
  super(input, button)
8
8
  end
9
9
  end
@@ -2,7 +2,7 @@ module Locator
2
2
  class Element
3
3
  class Form < Element
4
4
  def initialize
5
- super(:form, :equals => [:id, :name])
5
+ super(:form)
6
6
  end
7
7
  end
8
8
  end
@@ -1,8 +1,14 @@
1
1
  module Locator
2
2
  class Element
3
3
  class FormElement < Element
4
+ def initialize(name, attributes = {})
5
+ attributes[:matches] ||= []
6
+ attributes[:matches] << :name
7
+ super
8
+ end
9
+
4
10
  def lookup(dom, selector, attributes)
5
- super(dom, selector, attributes) + LabeledElement.new(name).send(:lookup, dom, selector, attributes)
11
+ super(dom, selector, attributes) + LabeledElement.new(name).send(:lookup, dom, selector, attributes.dup)
6
12
  end
7
13
  end
8
14
  end
@@ -2,9 +2,8 @@ module Locator
2
2
  class Element
3
3
  class Input < FormElement
4
4
  def initialize(attributes = {})
5
- attributes = { :type => [:text, :password , :email, :url, :search, :tel, :color] }.merge(attributes)
6
- matchables = { :equals => [:id, :name] }
7
- super(:input, { :equals => [:id, :name] }, attributes)
5
+ defaults = { :type => [:text, :password , :email, :url, :search, :tel, :color] }
6
+ super(:input, defaults.merge(attributes))
8
7
  end
9
8
  end
10
9
  end
@@ -2,7 +2,7 @@ module Locator
2
2
  class Element
3
3
  class Label < Element
4
4
  def initialize
5
- super(:label, :equals => [:id, :content])
5
+ super(:label, :matches => [:content])
6
6
  end
7
7
  end
8
8
  end
@@ -2,7 +2,7 @@ module Locator
2
2
  class Element
3
3
  class Link < Element
4
4
  def initialize
5
- super(:a, nil, :href => true)
5
+ super(:a, :href => true)
6
6
  end
7
7
  end
8
8
  end
@@ -2,7 +2,7 @@ module Locator
2
2
  class Element
3
3
  class Select < FormElement
4
4
  def initialize
5
- super(:select, :equals => [:id, :name])
5
+ super(:select)
6
6
  end
7
7
  end
8
8
  end
@@ -2,7 +2,7 @@ module Locator
2
2
  class Element
3
3
  class SelectOption < Element
4
4
  def initialize
5
- super(:option, :equals => [:id, :value, :content])
5
+ super(:option, :matches => [:value, :content])
6
6
  end
7
7
  end
8
8
  end
@@ -2,7 +2,7 @@ module Locator
2
2
  class Element
3
3
  class TextArea < FormElement
4
4
  def initialize
5
- super(:textarea, :equals => [:id, :name])
5
+ super(:textarea)
6
6
  end
7
7
  end
8
8
  end
@@ -1,13 +1,29 @@
1
1
  module Locator
2
2
  class Result < Array
3
+ MATCH_TYPES = {
4
+ :alt => :contains,
5
+ :title => :contains,
6
+ :content => :contains
7
+ }
8
+
3
9
  class << self
10
+ def matches?(name, value, selector)
11
+ value = normalize_whitespace(value)
12
+ case selector
13
+ when Regexp
14
+ value =~ selector
15
+ else
16
+ type = MATCH_TYPES[name] || :equals
17
+ send("#{type}?", value, selector)
18
+ end
19
+ end
20
+
4
21
  def equals?(value, selector)
5
22
  value == selector
6
23
  end
7
24
 
8
- def matches?(value, selector)
9
- value = normalize_whitespace(value)
10
- Regexp === selector ? value =~ selector : value.include?(selector)
25
+ def contains?(value, selector)
26
+ value.include?(selector)
11
27
  end
12
28
 
13
29
  def normalize_whitespace(value)
@@ -20,7 +36,7 @@ module Locator
20
36
  end
21
37
 
22
38
  def filter(selector, locatables)
23
- selector ? locatables.map { |(type, attrs)| filter_by(type, selector, attrs) }.flatten : self
39
+ selector ? filter_by(selector, locatables) : self
24
40
  end
25
41
 
26
42
  def sort!
@@ -34,11 +50,11 @@ module Locator
34
50
 
35
51
  protected
36
52
 
37
- def filter_by(type, selector, attributes)
53
+ def filter_by(selector, attributes)
38
54
  select do |element|
39
55
  Array(attributes).any? do |name|
40
56
  value = name == :content ? element.content : element.attribute(name.to_s)
41
- element.matches << value if self.class.send("#{type}?", value, selector)
57
+ element.matches << value if self.class.matches?(name, value, selector)
42
58
  end
43
59
  end
44
60
  end
@@ -1,3 +1,3 @@
1
1
  module Locator
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
data/lib/locator/xpath.rb CHANGED
@@ -46,6 +46,10 @@ module Locator
46
46
  replace(self.map { |l| other.map { |r| "#{l}#{r}" } })
47
47
  end
48
48
 
49
+ def or!(other)
50
+ other.empty? ? self : replace([self.dup, other])
51
+ end
52
+
49
53
  def to_s
50
54
  flatten.join(' | ')
51
55
  end
@@ -0,0 +1,77 @@
1
+ require File.expand_path('../../../test_helper', __FILE__)
2
+
3
+ $: << '~/Development/projects/steam/lib'
4
+ require 'steam'
5
+ include Steam
6
+
7
+ Java.load(Dir["#{Steam.config[:html_unit][:java_path]}/*.jar"].join(':'))
8
+ Java.import 'com.gargoylesoftware.htmlunit.WebClient'
9
+ Java.import 'com.gargoylesoftware.htmlunit.BrowserVersion'
10
+
11
+ class HtmlunitElementTest < Test::Unit::TestCase
12
+ include Locator, Java::Com::Gargoylesoftware::Htmlunit
13
+
14
+ def setup
15
+ connection = Connection::Mock.new
16
+ client = WebClient.new(BrowserVersion.FIREFOX_3)
17
+ client.setWebConnection(Rjb::bind(Browser::HtmlUnit::Connection.new(connection), 'com.gargoylesoftware.htmlunit.WebConnection'))
18
+
19
+ connection.mock(:get, 'http://localhost:3000/', '<a href="#" class="foo">the link</a><div id="bar"></div>')
20
+ @dom = Dom::Htmlunit::Page.new(client.getPage('http://localhost:3000/'))
21
+ end
22
+
23
+ test "Element takes a Htmlunit::Page adapter" do
24
+ element = Element::Link.new.locate(@dom, 'the link', :class => 'foo')
25
+ assert element.name
26
+ end
27
+
28
+ test "responds to name" do
29
+ element = Element::Link.new.locate(@dom, 'the link')
30
+ assert_equal 'a', element.name
31
+ end
32
+
33
+ test "responds to xpath" do
34
+ element = Element::Link.new.locate(@dom, 'the link')
35
+ assert_equal '/html/body/a', element.xpath
36
+ end
37
+
38
+ test "responds to to_s" do
39
+ element = Element::Link.new.locate(@dom, 'the link')
40
+ assert_equal "<a href=\"#\" class=\"foo\">\n the link\n</a>\n", element.to_s
41
+ end
42
+
43
+ test "responds to content" do
44
+ element = Element::Link.new.locate(@dom, 'the link')
45
+ assert_equal "the link", element.content
46
+ end
47
+
48
+ test "responds to attribute" do
49
+ element = Element::Link.new.locate(@dom, 'the link')
50
+ assert_equal 'foo', element.attribute('class')
51
+ end
52
+
53
+ test "responds to element_by_id" do
54
+ element = Element.new(:html).locate(@dom)
55
+ assert_equal 'bar', element.element_by_id('bar').attribute('id')
56
+ end
57
+
58
+ test "responds to element_by_xpath" do
59
+ element = Element.new(:html).locate(@dom)
60
+ assert_equal 'bar', element.element_by_xpath('//html/body/div[@id="bar"]').attribute('id')
61
+ end
62
+
63
+ test "responds to elements_by_xpath" do
64
+ element = Element::Link.new.locate(@dom, 'the link')
65
+ assert_equal 'foo', element.attribute('class')
66
+ end
67
+
68
+ test "responds to ancestors" do
69
+ element = Element.new(:a).locate(@dom)
70
+ assert_equal %w(html body), element.ancestors.map { |e| e.name }
71
+ end
72
+
73
+ test "responds to ancestor_of?" do
74
+ element = Element.new(:a).locate(@dom)
75
+ assert_equal %w(html body), element.ancestors.map { |e| e.name }
76
+ end
77
+ end
@@ -0,0 +1,16 @@
1
+ require File.expand_path('../../../test_helper', __FILE__)
2
+ require 'nokogiri'
3
+
4
+ class NokogiriTest < Test::Unit::TestCase
5
+ include Locator
6
+
7
+ # test "Element takes a Nokogiri dom" do
8
+ # dom = Nokogiri::HTML::Document.parse('<a href="#">the link</a>')
9
+ # assert Element.new.locate(dom, 'the link')
10
+ # end
11
+
12
+ test "Element takes a Nokogiri page adapter" do
13
+ dom = Locator::Dom::Nokogiri::Page.new('<a href="#">the link</a>')
14
+ assert Element.new.locate(dom, 'the link')
15
+ end
16
+ end
@@ -6,22 +6,22 @@ class ElementButtonTest < Test::Unit::TestCase
6
6
 
7
7
  test "finds a button" do
8
8
  html = '<button></button>'
9
- assert_equal 'button', Button.new.locate(html).tag_name
9
+ assert_equal 'button', Button.new.locate(html).name
10
10
  end
11
11
 
12
12
  test "finds a submit input" do
13
13
  html = '<input type="submit">'
14
- assert_equal 'input', Button.new.locate(html).tag_name
14
+ assert_equal 'input', Button.new.locate(html).name
15
15
  end
16
16
 
17
17
  test "finds a button input" do
18
18
  html = '<input type="button">'
19
- assert_equal 'input', Button.new.locate(html).tag_name
19
+ assert_equal 'input', Button.new.locate(html).name
20
20
  end
21
21
 
22
22
  test "finds an image input" do
23
23
  html = '<input type="image">'
24
- assert_equal 'input', Button.new.locate(html).tag_name
24
+ assert_equal 'input', Button.new.locate(html).name
25
25
  end
26
26
 
27
27
  test "does not find a checkbox input" do
@@ -6,62 +6,62 @@ class ElementFieldTest < Test::Unit::TestCase
6
6
 
7
7
  test "finds a textarea" do
8
8
  html = '<textarea></textarea>'
9
- assert_equal 'textarea', Field.new.locate(html).tag_name
9
+ assert_equal 'textarea', Field.new.locate(html).name
10
10
  end
11
11
 
12
12
  test "finds a text input" do
13
13
  html = '<input type="text">'
14
- assert_equal 'input', Field.new.locate(html).tag_name
14
+ assert_equal 'input', Field.new.locate(html).name
15
15
  end
16
16
 
17
17
  test "finds a password input" do
18
18
  html = '<input type="password">'
19
- assert_equal 'input', Field.new.locate(html).tag_name
19
+ assert_equal 'input', Field.new.locate(html).name
20
20
  end
21
21
 
22
22
  test "finds an email input" do
23
23
  html = '<input type="email">'
24
- assert_equal 'input', Field.new.locate(html).tag_name
24
+ assert_equal 'input', Field.new.locate(html).name
25
25
  end
26
26
 
27
27
  test "finds a url input" do
28
28
  html = '<input type="url">'
29
- assert_equal 'input', Field.new.locate(html).tag_name
29
+ assert_equal 'input', Field.new.locate(html).name
30
30
  end
31
31
 
32
32
  test "finds a search input" do
33
33
  html = '<input type="search">'
34
- assert_equal 'input', Field.new.locate(html).tag_name
34
+ assert_equal 'input', Field.new.locate(html).name
35
35
  end
36
36
 
37
37
  test "finds a tel input" do
38
38
  html = '<input type="tel">'
39
- assert_equal 'input', Field.new.locate(html).tag_name
39
+ assert_equal 'input', Field.new.locate(html).name
40
40
  end
41
41
 
42
42
  test "finds a color input" do
43
43
  html = '<input type="color">'
44
- assert_equal 'input', Field.new.locate(html).tag_name
44
+ assert_equal 'input', Field.new.locate(html).name
45
45
  end
46
46
 
47
47
  test "finds a text input by name" do
48
48
  html = '<input type="text" name="foo">'
49
- assert_equal 'input', Field.new.locate(html, 'foo').tag_name
49
+ assert_equal 'input', Field.new.locate(html, 'foo').name
50
50
  end
51
51
 
52
52
  test "finds a text input by class attribute" do
53
53
  html = '<input type="text" class="foo">'
54
- assert_equal 'input', Field.new.locate(html, :class => 'foo').tag_name
54
+ assert_equal 'input', Field.new.locate(html, :class => 'foo').name
55
55
  end
56
56
 
57
57
  test "finds an input by label content" do
58
58
  html = '<label for="bar">foo</label><input type="text" id="bar">'
59
- assert_equal 'input', Field.new.locate(html, 'foo').tag_name
59
+ assert_equal 'input', Field.new.locate(html, 'foo').name
60
60
  end
61
61
 
62
62
  test "finds an input by label content and input class" do
63
63
  html = '<label for="bar">foo</label><input type="text" id="bar" class="baz">'
64
- assert_equal 'input', Field.new.locate(html, 'foo', :class => 'baz').tag_name
64
+ assert_equal 'input', Field.new.locate(html, 'foo', :class => 'baz').name
65
65
  end
66
66
 
67
67
  test "does not find a checkbox input" do
@@ -6,22 +6,22 @@ class ElementFormTest < Test::Unit::TestCase
6
6
 
7
7
  test "finds a form" do
8
8
  html = '<form></form>'
9
- assert_equal 'form', Form.new.locate(html).tag_name
9
+ assert_equal 'form', Form.new.locate(html).name
10
10
  end
11
11
 
12
12
  test "finds a form by id" do
13
13
  html = '<form id="foo"></form>'
14
- assert_equal 'form', Form.new.locate(html, 'foo').tag_name
14
+ assert_equal 'form', Form.new.locate(html, 'foo').name
15
15
  end
16
16
 
17
17
  test "finds a form by name" do
18
18
  html = '<form name="foo"></form>'
19
- assert_equal 'form', Form.new.locate(html, 'foo').tag_name
19
+ assert_equal 'form', Form.new.locate(html, 'foo').name
20
20
  end
21
21
 
22
22
  test "finds a form by class" do
23
23
  html = '<form class="foo"></form>'
24
- assert_equal 'form', Form.new.locate(html, :class => 'foo').tag_name
24
+ assert_equal 'form', Form.new.locate(html, :class => 'foo').name
25
25
  end
26
26
 
27
27
  test "does not find a form when id does not match" do
@@ -0,0 +1,36 @@
1
+ require File.expand_path('../../../test_helper', __FILE__)
2
+ require 'locator/element'
3
+
4
+ class ElementHiddenFieldTest < Test::Unit::TestCase
5
+ HiddenField = Locator::Element::HiddenField
6
+
7
+ test "finds a hidden input" do
8
+ html = '<input type="hidden" />'
9
+ assert_equal 'input', HiddenField.new.locate(html).name
10
+ end
11
+
12
+ test "finds a hidden input by id" do
13
+ html = '<input type="hidden" id="foo" />'
14
+ assert_equal 'input', HiddenField.new.locate(html, 'foo').name
15
+ end
16
+
17
+ test "finds a hidden input by name" do
18
+ html = '<input type="hidden" name="foo" />'
19
+ assert_equal 'input', HiddenField.new.locate(html, 'foo').name
20
+ end
21
+
22
+ test "finds a hidden input by class" do
23
+ html = '<input type="hidden" class="foo" />'
24
+ assert_equal 'input', HiddenField.new.locate(html, :class => 'foo').name
25
+ end
26
+
27
+ test "does not find a hidden input when id does not match" do
28
+ html = '<input type="hidden" id="bar" />'
29
+ assert_nil HiddenField.new.locate(html, :class => 'foo')
30
+ end
31
+
32
+ test "does not find a hidden input when class does not match" do
33
+ html = '<input type="hidden" class="bar" />'
34
+ assert_nil HiddenField.new.locate(html, :class => 'foo')
35
+ end
36
+ end
@@ -5,17 +5,17 @@ class ElementLabelTest < Test::Unit::TestCase
5
5
 
6
6
  test "finds a label" do
7
7
  html = '<label></label>'
8
- assert_equal 'label', Label.new.locate(html).tag_name
8
+ assert_equal 'label', Label.new.locate(html).name
9
9
  end
10
10
 
11
11
  test "finds a label by id" do
12
12
  html = '<label id="foo"></label>'
13
- assert_equal 'label', Label.new.locate(html, 'foo').tag_name
13
+ assert_equal 'label', Label.new.locate(html, 'foo').name
14
14
  end
15
15
 
16
16
  test "finds a label by class" do
17
17
  html = '<label class="foo"></label>'
18
- assert_equal 'label', Label.new.locate(html, :class => 'foo').tag_name
18
+ assert_equal 'label', Label.new.locate(html, :class => 'foo').name
19
19
  end
20
20
 
21
21
  test "does not find a label when id does not match" do
@@ -6,27 +6,27 @@ class ElementSelectOptionOptionTest < Test::Unit::TestCase
6
6
 
7
7
  test "finds an option" do
8
8
  html = '<option></option>'
9
- assert_equal 'option', SelectOption.new.locate(html).tag_name
9
+ assert_equal 'option', SelectOption.new.locate(html).name
10
10
  end
11
11
 
12
12
  test "finds an option by id" do
13
13
  html = '<option id="foo"></option>'
14
- assert_equal 'option', SelectOption.new.locate(html, 'foo').tag_name
14
+ assert_equal 'option', SelectOption.new.locate(html, 'foo').name
15
15
  end
16
16
 
17
17
  test "finds an option by value" do
18
18
  html = '<option value="foo"></option>'
19
- assert_equal 'option', SelectOption.new.locate(html, 'foo').tag_name
19
+ assert_equal 'option', SelectOption.new.locate(html, 'foo').name
20
20
  end
21
21
 
22
22
  test "finds an option by content" do
23
23
  html = '<option>foo</option>'
24
- assert_equal 'option', SelectOption.new.locate(html, 'foo').tag_name
24
+ assert_equal 'option', SelectOption.new.locate(html, 'foo').name
25
25
  end
26
26
 
27
27
  test "finds an option by class attribute" do
28
28
  html = '<option class="foo"></option>'
29
- assert_equal 'option', SelectOption.new.locate(html, :class => 'foo').tag_name
29
+ assert_equal 'option', SelectOption.new.locate(html, :class => 'foo').name
30
30
  end
31
31
 
32
32
  test "does not find an option when id does not match" do
@@ -5,17 +5,17 @@ class ElementSelectTest < Test::Unit::TestCase
5
5
 
6
6
  test "finds a select" do
7
7
  html = '<select id="foo" class="foo"></select><select id="bar" class="bar"></select>'
8
- assert_equal 'select', Select.new.locate(html).tag_name
8
+ assert_equal 'select', Select.new.locate(html).name
9
9
  end
10
10
 
11
11
  test "finds a select by id" do
12
12
  html = '<select id="foo"></select>'
13
- assert_equal 'select', Select.new.locate(html, 'foo').tag_name
13
+ assert_equal 'select', Select.new.locate(html, 'foo').name
14
14
  end
15
15
 
16
16
  test "finds a select by class" do
17
17
  html = '<select class="foo"></select>'
18
- assert_equal 'select', Select.new.locate(html, :class => 'foo').tag_name
18
+ assert_equal 'select', Select.new.locate(html, :class => 'foo').name
19
19
  end
20
20
 
21
21
  test "does not find a select when id does not match" do
@@ -6,17 +6,17 @@ class ElementTextAreaTest < Test::Unit::TestCase
6
6
 
7
7
  test "finds a textarea" do
8
8
  html = '<textarea></textarea>'
9
- assert_equal 'textarea', TextArea.new.locate(html).tag_name
9
+ assert_equal 'textarea', TextArea.new.locate(html).name
10
10
  end
11
11
 
12
12
  test "finds a textarea by id" do
13
13
  html = '<textarea id="foo"></textarea>'
14
- assert_equal 'textarea', TextArea.new.locate(html, 'foo').tag_name
14
+ assert_equal 'textarea', TextArea.new.locate(html, 'foo').name
15
15
  end
16
16
 
17
17
  test "finds a textarea by class" do
18
18
  html = '<textarea class="foo"></textarea>'
19
- assert_equal 'textarea', TextArea.new.locate(html, :class => 'foo').tag_name
19
+ assert_equal 'textarea', TextArea.new.locate(html, :class => 'foo').name
20
20
  end
21
21
 
22
22
  test "does not find a textarea when id does not match" do
@@ -15,39 +15,49 @@ class LocatorElementTest < Test::Unit::TestCase
15
15
  end
16
16
 
17
17
  test "xpath with attributes" do
18
- xpath = Element.new(nil, nil, :type => 'type', :class => 'class').xpath
18
+ xpath = Element.new(nil, :type => 'type', :class => 'class').xpath
19
19
  assert_equal ".//*[@type=\"type\"][contains(concat(' ', @class, ' '), concat(' ', \"class\", ' '))]", xpath
20
20
  end
21
21
 
22
22
  test "xpath with node name and attributes" do
23
- xpath = Element.new(:div, nil, :type => 'type', :class => 'class').xpath
23
+ xpath = Element.new(:div, :type => 'type', :class => 'class').xpath
24
24
  assert_equal ".//div[@type=\"type\"][contains(concat(' ', @class, ' '), concat(' ', \"class\", ' '))]", xpath
25
25
  end
26
26
 
27
+ test "xpath with multiple node name and attributes" do
28
+ xpath = Element.new([:div, :p], :type => 'type').xpath
29
+ assert_equal ".//div[@type=\"type\"] | .//p[@type=\"type\"]", xpath
30
+ end
31
+
32
+ test "xpath merges given attributes with element attributes" do
33
+ xpath = Element.new(:div, :foo => 'foo').xpath(:bar => 'bar')
34
+ assert_equal ".//div[@foo=\"foo\"][@bar=\"bar\"]", xpath
35
+ end
36
+
27
37
  # all
28
38
 
29
39
  test "all selects all elements when given no attributes" do
30
40
  html = '<a class="foo"></a><p class="bar"></p>'
31
41
  elements = Element.new.all(html)
32
- assert_equal %w(html body a p), elements.map { |element| element.tag_name }
42
+ assert_equal %w(html body a p), elements.map { |element| element.name }
33
43
  end
34
44
 
35
45
  test "all selects all nodes with given node name" do
36
46
  html = '<a class="foo"></a><p class="bar"></p>'
37
47
  elements = Element.new('a').all(html)
38
- assert_equal %w(a), elements.map { |element| element.tag_name }
48
+ assert_equal %w(a), elements.map { |element| element.name }
39
49
  end
40
50
 
41
51
  test "all selects all nodes with attribute given to initialize" do
42
52
  html = '<a class="foo"></a><p class="bar"></p>'
43
- elements = Element.new(nil, nil, :class => 'foo').all(html)
44
- assert_equal %w(a), elements.map { |element| element.tag_name }
53
+ elements = Element.new(:class => 'foo').all(html)
54
+ assert_equal %w(a), elements.map { |element| element.name }
45
55
  end
46
56
 
47
57
  test "all selects all nodes with attribute given to all" do
48
58
  html = '<a class="foo"></a><p class="bar"></p>'
49
59
  elements = Element.new.all(html, :class => 'foo')
50
- assert_equal %w(a), elements.map { |element| element.tag_name }
60
+ assert_equal %w(a), elements.map { |element| element.name }
51
61
  end
52
62
 
53
63
  # locate
data/test/locator_test.rb CHANGED
@@ -20,16 +20,16 @@ class LocatorTest < Test::Unit::TestCase
20
20
 
21
21
  # locate
22
22
 
23
- test "locates an element by node name" do
24
- html = '<html><body><form></form></body></html>'
23
+ test "locates the first element by node name" do
24
+ html = '<html><body><form id="foo"></form><form id="bar"></form></body></html>'
25
25
  element = Locator.locate(html, :form)
26
- assert_equal 'form', element.tag_name
26
+ assert_equal 'foo', element.attribute('id')
27
27
  end
28
28
 
29
- test "locates an element by xpath" do
30
- html = '<html><body><form></form></body></html>'
29
+ test "locates the first element by xpath" do
30
+ html = '<html><body><form id="foo"></form><form id="bar"></form></body></html>'
31
31
  element = Locator.locate(html, :xpath => '//form')
32
- assert_equal 'form', element.tag_name
32
+ assert_equal 'foo', element.attribute('id')
33
33
  end
34
34
 
35
35
  # within
@@ -77,9 +77,9 @@ class LocatorTest < Test::Unit::TestCase
77
77
  end
78
78
 
79
79
  test "locate when given a block scopes the block to the located element" do
80
- html = '<form></form><div><form><p id="foo"><p></form></div>'
80
+ html = '<p id="foo"><p><form></form><div><form><p id="bar"><p></form></div>'
81
81
  element = locate(html, :div) { locate(html, :form) { locate(html, :p) } }
82
- assert_equal 'foo', element.attribute('id')
82
+ assert_equal 'bar', element.attribute('id')
83
83
  end
84
84
 
85
85
  test "locate does not yield the block when no element was found (would otherwise locate in global scope)" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: locator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sven Fuchs
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-02-13 00:00:00 +01:00
12
+ date: 2010-02-21 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -26,10 +26,15 @@ files:
26
26
  - README.textile
27
27
  - Rakefile
28
28
  - TODO
29
+ - lib/core_ext/hash/except.rb
30
+ - lib/core_ext/hash/slice.rb
29
31
  - lib/core_ext/string/underscore.rb
30
32
  - lib/locator.rb
31
33
  - lib/locator/boolean.rb
32
34
  - lib/locator/dom.rb
35
+ - lib/locator/dom/htmlunit.rb
36
+ - lib/locator/dom/htmlunit/element.rb
37
+ - lib/locator/dom/htmlunit/page.rb
33
38
  - lib/locator/dom/nokogiri.rb
34
39
  - lib/locator/dom/nokogiri/element.rb
35
40
  - lib/locator/dom/nokogiri/page.rb
@@ -56,9 +61,12 @@ files:
56
61
  - lib/locator/xpath.rb
57
62
  - test/all.rb
58
63
  - test/locator/boolean_test.rb
64
+ - test/locator/dom/htmlunit_test.rb
65
+ - test/locator/dom/nokogiri_test.rb
59
66
  - test/locator/element/button_test.rb
60
67
  - test/locator/element/field_test.rb
61
68
  - test/locator/element/form_test.rb
69
+ - test/locator/element/hidden_field_test.rb
62
70
  - test/locator/element/label_test.rb
63
71
  - test/locator/element/link_test.rb
64
72
  - test/locator/element/select_option_test.rb
@@ -99,9 +107,12 @@ summary: Generic html element locators for integration testing
99
107
  test_files:
100
108
  - test/all.rb
101
109
  - test/locator/boolean_test.rb
110
+ - test/locator/dom/htmlunit_test.rb
111
+ - test/locator/dom/nokogiri_test.rb
102
112
  - test/locator/element/button_test.rb
103
113
  - test/locator/element/field_test.rb
104
114
  - test/locator/element/form_test.rb
115
+ - test/locator/element/hidden_field_test.rb
105
116
  - test/locator/element/label_test.rb
106
117
  - test/locator/element/link_test.rb
107
118
  - test/locator/element/select_option_test.rb