jarib-celerity 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +42 -0
- data/License.txt +621 -0
- data/README.txt +64 -0
- data/Rakefile +12 -0
- data/lib/celerity.rb +59 -0
- data/lib/celerity/browser.rb +410 -0
- data/lib/celerity/clickable_element.rb +26 -0
- data/lib/celerity/collections.rb +148 -0
- data/lib/celerity/container.rb +488 -0
- data/lib/celerity/default_viewer.rb +10 -0
- data/lib/celerity/disabled_element.rb +27 -0
- data/lib/celerity/element.rb +241 -0
- data/lib/celerity/element_collections.rb +68 -0
- data/lib/celerity/element_locator.rb +167 -0
- data/lib/celerity/elements/button.rb +34 -0
- data/lib/celerity/elements/file_field.rb +17 -0
- data/lib/celerity/elements/form.rb +16 -0
- data/lib/celerity/elements/frame.rb +53 -0
- data/lib/celerity/elements/image.rb +57 -0
- data/lib/celerity/elements/label.rb +9 -0
- data/lib/celerity/elements/link.rb +12 -0
- data/lib/celerity/elements/meta.rb +6 -0
- data/lib/celerity/elements/non_control_elements.rb +93 -0
- data/lib/celerity/elements/option.rb +18 -0
- data/lib/celerity/elements/radio_check.rb +85 -0
- data/lib/celerity/elements/select_list.rb +81 -0
- data/lib/celerity/elements/table.rb +117 -0
- data/lib/celerity/elements/table_cell.rb +28 -0
- data/lib/celerity/elements/table_elements.rb +41 -0
- data/lib/celerity/elements/table_row.rb +36 -0
- data/lib/celerity/elements/text_field.rb +127 -0
- data/lib/celerity/exception.rb +40 -0
- data/lib/celerity/extra/method_generator.rb +158 -0
- data/lib/celerity/htmlunit.rb +41 -0
- data/lib/celerity/htmlunit/commons-codec-1.3.jar +0 -0
- data/lib/celerity/htmlunit/commons-collections-3.2.1.jar +0 -0
- data/lib/celerity/htmlunit/commons-httpclient-3.1.jar +0 -0
- data/lib/celerity/htmlunit/commons-io-1.4.jar +0 -0
- data/lib/celerity/htmlunit/commons-lang-2.4.jar +0 -0
- data/lib/celerity/htmlunit/commons-logging-1.1.1.jar +0 -0
- data/lib/celerity/htmlunit/cssparser-0.9.5.jar +0 -0
- data/lib/celerity/htmlunit/htmlunit-2.4-SNAPSHOT.jar +0 -0
- data/lib/celerity/htmlunit/htmlunit-core-js-2.4-SNAPSHOT.jar +0 -0
- data/lib/celerity/htmlunit/nekohtml-1.9.10-20081209.100757-4.jar +0 -0
- data/lib/celerity/htmlunit/sac-1.3.jar +0 -0
- data/lib/celerity/htmlunit/serializer-2.7.1.jar +0 -0
- data/lib/celerity/htmlunit/xalan-2.7.1.jar +0 -0
- data/lib/celerity/htmlunit/xercesImpl-2.8.1.jar +0 -0
- data/lib/celerity/htmlunit/xml-apis-1.3.04.jar +0 -0
- data/lib/celerity/identifier.rb +11 -0
- data/lib/celerity/input_element.rb +25 -0
- data/lib/celerity/listener.rb +106 -0
- data/lib/celerity/resources/no_viewer.png +0 -0
- data/lib/celerity/util.rb +79 -0
- data/lib/celerity/version.rb +9 -0
- data/lib/celerity/watir_compatibility.rb +85 -0
- data/tasks/benchmark.rake +4 -0
- data/tasks/deployment.rake +43 -0
- data/tasks/environment.rake +7 -0
- data/tasks/fix.rake +25 -0
- data/tasks/jar.rake +57 -0
- data/tasks/rdoc.rake +4 -0
- data/tasks/rspec.rake +30 -0
- data/tasks/simple_ci.rake +94 -0
- data/tasks/snapshot.rake +26 -0
- data/tasks/specserver.rake +21 -0
- data/tasks/website.rake +17 -0
- data/tasks/yard.rake +5 -0
- metadata +129 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
module Celerity
|
2
|
+
# Module mixed in to all elements that can have the 'disabled' attribute.
|
3
|
+
module DisabledElement
|
4
|
+
include Celerity::Exception
|
5
|
+
|
6
|
+
# Returns false if the element is disabled.
|
7
|
+
def enabled?
|
8
|
+
!disabled?
|
9
|
+
end
|
10
|
+
|
11
|
+
# Returns true if the element is disabled.
|
12
|
+
def disabled?
|
13
|
+
assert_exists unless defined?(@object) && @object
|
14
|
+
@object.isDisabled
|
15
|
+
end
|
16
|
+
alias_method :disabled, :disabled?
|
17
|
+
|
18
|
+
# Used internally.
|
19
|
+
# @api private
|
20
|
+
def assert_enabled
|
21
|
+
if disabled?
|
22
|
+
raise ObjectDisabledException, "Object #{identifier_string} is disabled"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,241 @@
|
|
1
|
+
module Celerity
|
2
|
+
|
3
|
+
# Superclass for all HTML elements.
|
4
|
+
class Element
|
5
|
+
include Exception
|
6
|
+
include Container
|
7
|
+
|
8
|
+
attr_reader :container, :object
|
9
|
+
|
10
|
+
# number of spaces that separate the property from the value in the create_string method
|
11
|
+
TO_S_SIZE = 14
|
12
|
+
|
13
|
+
# HTML 4.01 Transitional DTD
|
14
|
+
HTML_401_TRANSITIONAL = {
|
15
|
+
:core => [:class, :id, :style, :title],
|
16
|
+
:cell_halign => [:align, :char, :charoff],
|
17
|
+
:cell_valign => [:valign],
|
18
|
+
:i18n => [:dir, :lang],
|
19
|
+
:event => [:onclick, :ondblclick, :onmousedown, :onmouseup, :onmouseover,
|
20
|
+
:onmousemove, :onmouseout, :onkeypress, :onkeydown, :onkeyup],
|
21
|
+
:sloppy => [:name, :value]
|
22
|
+
}
|
23
|
+
|
24
|
+
CELLHALIGN_ATTRIBUTES = HTML_401_TRANSITIONAL[:cell_halign]
|
25
|
+
CELLVALIGN_ATTRIBUTES = HTML_401_TRANSITIONAL[:cell_valign]
|
26
|
+
BASE_ATTRIBUTES = HTML_401_TRANSITIONAL.values_at(:core, :i18n, :event, :sloppy).flatten
|
27
|
+
ATTRIBUTES = BASE_ATTRIBUTES
|
28
|
+
TAGS = []
|
29
|
+
|
30
|
+
DEFAULT_HOW = nil
|
31
|
+
|
32
|
+
# @api private
|
33
|
+
def initialize(container, *args)
|
34
|
+
self.container = container
|
35
|
+
|
36
|
+
case args.size
|
37
|
+
when 2
|
38
|
+
@conditions = { args[0] => args[1] }
|
39
|
+
when 1
|
40
|
+
if args.first.is_a? Hash
|
41
|
+
@conditions = args.first
|
42
|
+
elsif (how = self.class::DEFAULT_HOW)
|
43
|
+
@conditions = { how => args.first }
|
44
|
+
else
|
45
|
+
raise ArgumentError, "wrong number of arguments (1 for 2)"
|
46
|
+
end
|
47
|
+
else
|
48
|
+
raise ArgumentError, "wrong number of arguments (#{args.size} for 2)"
|
49
|
+
end
|
50
|
+
|
51
|
+
@conditions.freeze
|
52
|
+
end
|
53
|
+
|
54
|
+
# Get the parent element
|
55
|
+
# @return [Celerity::Element, nil] subclass of Celerity::Element, or nil if no parent was found
|
56
|
+
def parent
|
57
|
+
assert_exists
|
58
|
+
|
59
|
+
obj = @object.parentNode
|
60
|
+
until element_class = Celerity::Util.htmlunit2celerity(obj.class)
|
61
|
+
return nil if obj.nil?
|
62
|
+
obj = obj.parentNode
|
63
|
+
end
|
64
|
+
|
65
|
+
element_class.new(@container, :object, obj)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Sets the focus to this element.
|
69
|
+
def focus
|
70
|
+
assert_exists
|
71
|
+
@object.focus
|
72
|
+
end
|
73
|
+
|
74
|
+
# Used internally. Find the element on the page.
|
75
|
+
# @api private
|
76
|
+
def locate
|
77
|
+
@object = ElementLocator.new(@container, self.class).find_by_conditions(@conditions)
|
78
|
+
end
|
79
|
+
|
80
|
+
# @return [String] A string representation of the element.
|
81
|
+
def to_s
|
82
|
+
assert_exists
|
83
|
+
create_string(@object)
|
84
|
+
end
|
85
|
+
|
86
|
+
# @param [String, #to_s] The attribute.
|
87
|
+
# @return [String] The value of the given attribute.
|
88
|
+
def attribute_value(attribute)
|
89
|
+
assert_exists
|
90
|
+
@object.getAttribute(attribute.to_s)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Check if the element is visible to the user or not.
|
94
|
+
# Note that this only takes the _style attribute_ of the element (and
|
95
|
+
# its parents) into account - styles from applied CSS is not considered.
|
96
|
+
#
|
97
|
+
# @return [boolean]
|
98
|
+
def visible?
|
99
|
+
obj = self
|
100
|
+
while obj
|
101
|
+
return false if obj.respond_to?(:type) && obj.type == 'hidden'
|
102
|
+
return false if obj.style =~ /display\s*:\s*none|visibility\s*:\s*hidden/
|
103
|
+
obj = obj.parent
|
104
|
+
end
|
105
|
+
|
106
|
+
return true
|
107
|
+
end
|
108
|
+
|
109
|
+
# Used internally to ensure the element actually exists.
|
110
|
+
#
|
111
|
+
# @raise [Celerity::Exception::UnknownObjectException] if the element can't be found.
|
112
|
+
# @api private
|
113
|
+
def assert_exists
|
114
|
+
locate
|
115
|
+
unless @object
|
116
|
+
raise UnknownObjectException, "Unable to locate #{self.class.name[/::(.*)$/, 1]}, using #{identifier_string}"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Checks if the element exists.
|
121
|
+
# @return [true, false]
|
122
|
+
def exists?
|
123
|
+
assert_exists
|
124
|
+
true
|
125
|
+
rescue UnknownObjectException, UnknownFrameException
|
126
|
+
false
|
127
|
+
end
|
128
|
+
alias_method :exist?, :exists?
|
129
|
+
|
130
|
+
# Return a text representation of the element as it would appear in a browser.
|
131
|
+
#
|
132
|
+
# @see inner_text
|
133
|
+
# @return [String]
|
134
|
+
def text
|
135
|
+
assert_exists
|
136
|
+
@object.asText.strip # this must behave like ElementLocator
|
137
|
+
end
|
138
|
+
|
139
|
+
# Return the text content of this DOM node, disregarding its visibility.
|
140
|
+
#
|
141
|
+
# (Celerity-specific?)
|
142
|
+
#
|
143
|
+
# @see text
|
144
|
+
# @return [String]
|
145
|
+
def inner_text
|
146
|
+
assert_exists
|
147
|
+
Celerity::Util.normalize_text @object.getTextContent
|
148
|
+
end
|
149
|
+
|
150
|
+
# @return [String] The normative XML representation of the element (including children).
|
151
|
+
def to_xml
|
152
|
+
assert_exists
|
153
|
+
@object.asXml
|
154
|
+
end
|
155
|
+
alias_method :asXml, :to_xml
|
156
|
+
alias_method :as_xml, :to_xml
|
157
|
+
alias_method :html, :to_xml
|
158
|
+
|
159
|
+
# @return [String] A string representation of the element's attributes.
|
160
|
+
def attribute_string
|
161
|
+
assert_exists
|
162
|
+
|
163
|
+
result = ''
|
164
|
+
@object.getAttributes.each do |attribute|
|
165
|
+
result << %Q{#{attribute.getName}="#{attribute.getHtmlValue.to_s}"}
|
166
|
+
end
|
167
|
+
|
168
|
+
result
|
169
|
+
end
|
170
|
+
|
171
|
+
# return the canonical xpath for this element (Celerity-specific)
|
172
|
+
def xpath
|
173
|
+
assert_exists
|
174
|
+
@object.getCanonicalXPath
|
175
|
+
end
|
176
|
+
|
177
|
+
# Dynamically get element attributes.
|
178
|
+
#
|
179
|
+
# @see ATTRIBUTES constant for a list of valid methods for a given element.
|
180
|
+
#
|
181
|
+
# @return [String] The resulting attribute.
|
182
|
+
# @raise [NoMethodError] if the element doesn't support this attribute.
|
183
|
+
def method_missing(meth, *args, &blk)
|
184
|
+
assert_exists
|
185
|
+
|
186
|
+
meth = selector_to_attribute(meth)
|
187
|
+
|
188
|
+
if self.class::ATTRIBUTES.include?(meth)
|
189
|
+
return @object.getAttributeValue(meth.to_s)
|
190
|
+
end
|
191
|
+
|
192
|
+
Log.warn "Element\#method_missing calling super with #{meth.inspect}"
|
193
|
+
super
|
194
|
+
end
|
195
|
+
|
196
|
+
def respond_to?(meth, include_private = false)
|
197
|
+
meth = selector_to_attribute(meth)
|
198
|
+
return true if self.class::ATTRIBUTES.include?(meth)
|
199
|
+
super
|
200
|
+
end
|
201
|
+
|
202
|
+
private
|
203
|
+
|
204
|
+
def create_string(element)
|
205
|
+
ret = []
|
206
|
+
|
207
|
+
unless (tag = element.getTagName).empty?
|
208
|
+
ret << "tag:".ljust(TO_S_SIZE) + tag
|
209
|
+
end
|
210
|
+
|
211
|
+
element.getAttributes.each do |attribute|
|
212
|
+
ret << " #{attribute.getName}:".ljust(TO_S_SIZE+2) + attribute.getHtmlValue.to_s
|
213
|
+
end
|
214
|
+
|
215
|
+
unless (text = element.asText).empty?
|
216
|
+
ret << " text:".ljust(TO_S_SIZE+2) + element.asText
|
217
|
+
end
|
218
|
+
|
219
|
+
ret.join("\n")
|
220
|
+
end
|
221
|
+
|
222
|
+
def identifier_string
|
223
|
+
if @conditions.size == 1
|
224
|
+
how, what = @conditions.to_a.first
|
225
|
+
"#{how.inspect} and #{what.inspect}"
|
226
|
+
else
|
227
|
+
@conditions.inspect
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def selector_to_attribute(meth)
|
232
|
+
case meth
|
233
|
+
when :class_name then :class
|
234
|
+
when :caption then :value
|
235
|
+
when :url then :href
|
236
|
+
else meth
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
end # Element
|
241
|
+
end # Celerity
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Celerity
|
2
|
+
# This class is the superclass for the iterator classes (Buttons, Links, Spans etc.)
|
3
|
+
# It would normally only be accessed by the iterator methods (Browser#spans, Browser#links, ...).
|
4
|
+
class ElementCollections
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
# @api private
|
8
|
+
def initialize(container, how = nil, what = nil)
|
9
|
+
@container = container
|
10
|
+
@object = (how == :object ? what : nil)
|
11
|
+
@length = length
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [Fixnum] The number of elements in this collection.
|
15
|
+
def length
|
16
|
+
if @object
|
17
|
+
@object.length
|
18
|
+
else
|
19
|
+
@elements ||= ElementLocator.new(@container, element_class).elements_by_idents
|
20
|
+
@elements.size
|
21
|
+
end
|
22
|
+
end
|
23
|
+
alias_method :size, :length
|
24
|
+
|
25
|
+
# @yieldparam [Celerity::Element] element Iterate through the elements in this collection.
|
26
|
+
#
|
27
|
+
def each
|
28
|
+
if @elements
|
29
|
+
@elements.each { |e| yield(element_class.new(@container, :object, e)) }
|
30
|
+
else
|
31
|
+
0.upto(@length - 1) { |i| yield iterator_object(i) }
|
32
|
+
end
|
33
|
+
|
34
|
+
@length
|
35
|
+
end
|
36
|
+
|
37
|
+
# Get the element at the given index.
|
38
|
+
# This is 1-indexed to keep compatibility with Watir - subject to change.
|
39
|
+
# Also note that because of Watir's lazy loading, this will return an Element
|
40
|
+
# instance even if the index is out of bounds.
|
41
|
+
#
|
42
|
+
# @param [Fixnum] n Index of wanted element, 1-indexed.
|
43
|
+
# @return [Celerity::Element] Returns a subclass of Celerity::Element
|
44
|
+
def [](n)
|
45
|
+
if @elements && @elements[n - INDEX_OFFSET]
|
46
|
+
element_class.new(@container, :object, @elements[n - INDEX_OFFSET])
|
47
|
+
else
|
48
|
+
iterator_object(n - INDEX_OFFSET)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Note: This can be quite useful in irb:
|
53
|
+
#
|
54
|
+
# puts browser.text_fields
|
55
|
+
#
|
56
|
+
# @return [String] A string representation of all elements in this collection.
|
57
|
+
def to_s
|
58
|
+
map { |e| e.to_s }.join("\n")
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def iterator_object(i)
|
64
|
+
element_class.new(@container, :index, i+1)
|
65
|
+
end
|
66
|
+
|
67
|
+
end # ElementCollections
|
68
|
+
end # Celerity
|
@@ -0,0 +1,167 @@
|
|
1
|
+
module Celerity
|
2
|
+
|
3
|
+
# Used internally to locate elements on the page.
|
4
|
+
class ElementLocator
|
5
|
+
include Celerity::Exception
|
6
|
+
attr_accessor :idents
|
7
|
+
|
8
|
+
|
9
|
+
def initialize(container, element_class)
|
10
|
+
container.assert_exists
|
11
|
+
|
12
|
+
@container = container
|
13
|
+
@object = container.object
|
14
|
+
@element_class = element_class
|
15
|
+
@attributes = @element_class::ATTRIBUTES # could check for 'strict' here?
|
16
|
+
@idents = @element_class::TAGS
|
17
|
+
@tags = @idents.map { |e| e.tag.downcase }
|
18
|
+
end
|
19
|
+
|
20
|
+
def find_by_conditions(conditions) # TODO: refactor without performance hit
|
21
|
+
return nil unless @object # probably means we're on a TextPage (content-type is "text/plain")
|
22
|
+
|
23
|
+
@condition_idents = []
|
24
|
+
attributes = Hash.new { |h, k| h[k] = [] }
|
25
|
+
index = 0 # by default, return the first matching element
|
26
|
+
text = nil
|
27
|
+
|
28
|
+
conditions.each do |how, what|
|
29
|
+
case how
|
30
|
+
when :object
|
31
|
+
unless what.is_a?(HtmlUnit::Html::HtmlElement) || what.nil?
|
32
|
+
raise ArgumentError, "expected an HtmlUnit::Html::HtmlElement subclass, got #{what.inspect}:#{what.class}"
|
33
|
+
end
|
34
|
+
return what
|
35
|
+
when :id
|
36
|
+
return find_by_id(what)
|
37
|
+
when :xpath
|
38
|
+
return find_by_xpath(what)
|
39
|
+
when :label
|
40
|
+
return find_by_label(what)
|
41
|
+
when :class_name
|
42
|
+
how = :class
|
43
|
+
when :url
|
44
|
+
how = :href
|
45
|
+
when :caption
|
46
|
+
how = :text
|
47
|
+
end
|
48
|
+
|
49
|
+
if @attributes.include?(how = how.to_sym)
|
50
|
+
attributes[how] << what
|
51
|
+
elsif how == :index
|
52
|
+
index = what.to_i - INDEX_OFFSET
|
53
|
+
elsif how == :text
|
54
|
+
text = what
|
55
|
+
else
|
56
|
+
raise MissingWayOfFindingObjectException, "No how #{how.inspect}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
@idents.each do |ident|
|
61
|
+
merged = attributes.merge(ident.attributes) { |key, v1, v2| v1 | v2 }
|
62
|
+
id = Identifier.new(ident.tag, merged)
|
63
|
+
id.text = ident.text || text # «original» identifier takes precedence for :text
|
64
|
+
@condition_idents << id
|
65
|
+
end
|
66
|
+
|
67
|
+
if index == 0
|
68
|
+
element_by_idents(@condition_idents)
|
69
|
+
else
|
70
|
+
elements_by_idents(@condition_idents)[index]
|
71
|
+
end
|
72
|
+
|
73
|
+
rescue HtmlUnit::ElementNotFoundException
|
74
|
+
nil # for rcov
|
75
|
+
end
|
76
|
+
|
77
|
+
def find_by_id(what)
|
78
|
+
case what
|
79
|
+
when Regexp
|
80
|
+
elements_by_tag_names.find { |elem| elem.getIdAttribute =~ what }
|
81
|
+
when String
|
82
|
+
obj = @object.getHtmlElementById(what)
|
83
|
+
return obj if @tags.include?(obj.getTagName)
|
84
|
+
|
85
|
+
$stderr.puts "warning: multiple elements with identical id? (#{what.inspect})" if $VERBOSE
|
86
|
+
elements_by_tag_names.find { |elem| elem.getIdAttribute == what }
|
87
|
+
else
|
88
|
+
raise TypeError, "expected String or Regexp, got #{what.inspect}:#{what.class}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def find_by_xpath(what)
|
93
|
+
what = ".#{what}" if what[0].chr == "/"
|
94
|
+
@object.getByXPath(what).to_a.first
|
95
|
+
end
|
96
|
+
|
97
|
+
def find_by_label(what)
|
98
|
+
obj = elements_by_tag_names(%w[label]).find { |e| matches?(e.asText, what) }
|
99
|
+
|
100
|
+
return nil unless obj && (ref = obj.getReferencedElement)
|
101
|
+
return ref if @tags.include?(ref.getTagName)
|
102
|
+
|
103
|
+
find_by_id obj.getForAttribute
|
104
|
+
end
|
105
|
+
|
106
|
+
def elements_by_idents(idents = nil)
|
107
|
+
get_by_idents(:select, idents || @idents)
|
108
|
+
end
|
109
|
+
|
110
|
+
def element_by_idents(idents = nil)
|
111
|
+
get_by_idents(:find, idents || @idents)
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def get_by_idents(meth, idents)
|
117
|
+
with_nullpointer_retry do
|
118
|
+
@object.getAllHtmlChildElements.send(meth) do |e|
|
119
|
+
next unless @tags.include?(e.getTagName)
|
120
|
+
idents.any? { |id| element_matches_ident?(e, id) }
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def element_matches_ident?(element, ident)
|
126
|
+
return false unless ident.tag == element.getTagName
|
127
|
+
|
128
|
+
attr_result = ident.attributes.all? do |key, values|
|
129
|
+
values.any? { |val| matches?(element.getAttributeValue(key.to_s), val) }
|
130
|
+
end
|
131
|
+
|
132
|
+
if ident.text
|
133
|
+
attr_result && matches?(element.asText.strip, ident.text)
|
134
|
+
else
|
135
|
+
attr_result
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def elements_by_tag_names(tags = @tags)
|
140
|
+
with_nullpointer_retry do
|
141
|
+
# HtmlUnit's getHtmlElementsByTagNames won't get elements in the correct
|
142
|
+
# order (making :index fail), so we're using getAllHtmlChildElements instead.
|
143
|
+
@object.getAllHtmlChildElements.select do |elem|
|
144
|
+
tags.include?(elem.getTagName)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# HtmlUnit throws NPEs sometimes when we're locating elements
|
150
|
+
# Retry seems to work fine.
|
151
|
+
def with_nullpointer_retry(max_retries = 3)
|
152
|
+
tries = 0
|
153
|
+
yield
|
154
|
+
rescue java.lang.NullPointerException => e
|
155
|
+
raise e if tries >= max_retries
|
156
|
+
|
157
|
+
tries += 1
|
158
|
+
$stderr.puts "warning: celerity caught #{e} - retry ##{tries}"
|
159
|
+
retry
|
160
|
+
end
|
161
|
+
|
162
|
+
def matches?(string, what)
|
163
|
+
Regexp === what ? string.strip =~ what : string == what.to_s
|
164
|
+
end
|
165
|
+
|
166
|
+
end # ElementLocator
|
167
|
+
end # Celerity
|