oki-celerity 0.8.1.dev
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 +111 -0
- data/LICENSE +621 -0
- data/README.rdoc +80 -0
- data/Rakefile +11 -0
- data/VERSION.yml +5 -0
- data/celerity.gemspec +126 -0
- data/lib/celerity/browser.rb +908 -0
- data/lib/celerity/clickable_element.rb +73 -0
- data/lib/celerity/collections.rb +164 -0
- data/lib/celerity/container.rb +800 -0
- data/lib/celerity/default_viewer.rb +14 -0
- data/lib/celerity/disabled_element.rb +40 -0
- data/lib/celerity/element.rb +311 -0
- data/lib/celerity/element_collection.rb +107 -0
- data/lib/celerity/element_locator.rb +164 -0
- data/lib/celerity/elements/button.rb +54 -0
- data/lib/celerity/elements/file_field.rb +29 -0
- data/lib/celerity/elements/form.rb +22 -0
- data/lib/celerity/elements/frame.rb +86 -0
- data/lib/celerity/elements/image.rb +89 -0
- data/lib/celerity/elements/label.rb +16 -0
- data/lib/celerity/elements/link.rb +43 -0
- data/lib/celerity/elements/meta.rb +14 -0
- data/lib/celerity/elements/non_control_elements.rb +124 -0
- data/lib/celerity/elements/option.rb +38 -0
- data/lib/celerity/elements/radio_check.rb +114 -0
- data/lib/celerity/elements/select_list.rb +146 -0
- data/lib/celerity/elements/table.rb +153 -0
- data/lib/celerity/elements/table_cell.rb +36 -0
- data/lib/celerity/elements/table_elements.rb +42 -0
- data/lib/celerity/elements/table_row.rb +49 -0
- data/lib/celerity/elements/text_field.rb +168 -0
- data/lib/celerity/exception.rb +83 -0
- data/lib/celerity/htmlunit/apache-mime4j-0.6.jar +0 -0
- data/lib/celerity/htmlunit/commons-codec-1.4.jar +0 -0
- data/lib/celerity/htmlunit/commons-collections-3.2.1.jar +0 -0
- data/lib/celerity/htmlunit/commons-io-1.4.jar +0 -0
- data/lib/celerity/htmlunit/commons-lang-2.5.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.9-SNAPSHOT.jar +0 -0
- data/lib/celerity/htmlunit/htmlunit-core-js-2.8.jar +0 -0
- data/lib/celerity/htmlunit/httpclient-4.0.1.jar +0 -0
- data/lib/celerity/htmlunit/httpcore-4.0.1.jar +0 -0
- data/lib/celerity/htmlunit/httpmime-4.0.1.jar +0 -0
- data/lib/celerity/htmlunit/nekohtml-1.9.14.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.9.1.jar +0 -0
- data/lib/celerity/htmlunit/xml-apis-1.3.04.jar +0 -0
- data/lib/celerity/htmlunit.rb +64 -0
- data/lib/celerity/identifier.rb +28 -0
- data/lib/celerity/ignoring_web_connection.rb +15 -0
- data/lib/celerity/input_element.rb +25 -0
- data/lib/celerity/javascript_debugger.rb +32 -0
- data/lib/celerity/listener.rb +143 -0
- data/lib/celerity/resources/no_viewer.png +0 -0
- data/lib/celerity/short_inspect.rb +20 -0
- data/lib/celerity/util.rb +129 -0
- data/lib/celerity/version.rb +3 -0
- data/lib/celerity/viewer_connection.rb +89 -0
- data/lib/celerity/watir_compatibility.rb +70 -0
- data/lib/celerity/xpath_support.rb +52 -0
- data/lib/celerity.rb +77 -0
- data/tasks/benchmark.rake +4 -0
- data/tasks/check.rake +24 -0
- data/tasks/clean.rake +3 -0
- data/tasks/fix.rake +25 -0
- data/tasks/jar.rake +55 -0
- data/tasks/jeweler.rake +28 -0
- data/tasks/rdoc.rake +4 -0
- data/tasks/snapshot.rake +25 -0
- data/tasks/spec.rake +26 -0
- data/tasks/website.rake +10 -0
- data/tasks/yard.rake +16 -0
- metadata +204 -0
@@ -0,0 +1,40 @@
|
|
1
|
+
module Celerity
|
2
|
+
|
3
|
+
#
|
4
|
+
# Mixed in to all elements that can have the 'disabled' attribute.
|
5
|
+
#
|
6
|
+
|
7
|
+
module DisabledElement
|
8
|
+
include Celerity::Exception
|
9
|
+
|
10
|
+
#
|
11
|
+
# Returns false if the element is disabled.
|
12
|
+
#
|
13
|
+
|
14
|
+
def enabled?
|
15
|
+
!disabled?
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# Returns true if the element is disabled.
|
20
|
+
#
|
21
|
+
|
22
|
+
def disabled?
|
23
|
+
assert_exists unless defined?(@object) && @object
|
24
|
+
@object.isDisabled
|
25
|
+
end
|
26
|
+
alias_method :disabled, :disabled?
|
27
|
+
|
28
|
+
#
|
29
|
+
# Used internally.
|
30
|
+
# @api private
|
31
|
+
#
|
32
|
+
|
33
|
+
def assert_enabled
|
34
|
+
if disabled?
|
35
|
+
raise ObjectDisabledException, "Object #{identifier_string} is disabled"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,311 @@
|
|
1
|
+
module Celerity
|
2
|
+
|
3
|
+
#
|
4
|
+
# Superclass for all HTML elements.
|
5
|
+
#
|
6
|
+
|
7
|
+
class Element
|
8
|
+
include Exception
|
9
|
+
include Container
|
10
|
+
|
11
|
+
attr_reader :container
|
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
|
+
when 0
|
48
|
+
@conditions = { :index => Celerity.index_offset }
|
49
|
+
else
|
50
|
+
raise ArgumentError, "wrong number of arguments (#{args.size} for 2)"
|
51
|
+
end
|
52
|
+
|
53
|
+
@conditions.freeze
|
54
|
+
@object = nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def ==(other)
|
58
|
+
return false unless other.kind_of? Element
|
59
|
+
xpath == other.xpath
|
60
|
+
end
|
61
|
+
|
62
|
+
#
|
63
|
+
# Get the parent element
|
64
|
+
# @return [Celerity::Element, nil] subclass of Celerity::Element, or nil if no parent was found
|
65
|
+
#
|
66
|
+
|
67
|
+
def parent
|
68
|
+
assert_exists
|
69
|
+
|
70
|
+
obj = @object.parentNode
|
71
|
+
until element_class = Celerity::Util.htmlunit2celerity(obj.class)
|
72
|
+
return nil if obj.nil?
|
73
|
+
obj = obj.parentNode
|
74
|
+
end
|
75
|
+
|
76
|
+
element_class.new(@container, :object, obj)
|
77
|
+
end
|
78
|
+
|
79
|
+
#
|
80
|
+
# Sets the focus to this element.
|
81
|
+
#
|
82
|
+
|
83
|
+
def focus
|
84
|
+
assert_exists
|
85
|
+
@object.focus
|
86
|
+
end
|
87
|
+
|
88
|
+
#
|
89
|
+
# Returns true if this element is the currently focused element
|
90
|
+
#
|
91
|
+
# Celerity-specific.
|
92
|
+
#
|
93
|
+
|
94
|
+
def focused?
|
95
|
+
assert_exists
|
96
|
+
@object == browser.page.getFocusedElement
|
97
|
+
end
|
98
|
+
|
99
|
+
#
|
100
|
+
# Fires the given event for this element
|
101
|
+
#
|
102
|
+
|
103
|
+
def fire_event(event_name)
|
104
|
+
assert_exists
|
105
|
+
@object.fireEvent(event_name.sub(/^on/, ''))
|
106
|
+
end
|
107
|
+
|
108
|
+
#
|
109
|
+
# Used internally. Find the element on the page.
|
110
|
+
# @api private
|
111
|
+
#
|
112
|
+
|
113
|
+
def locate
|
114
|
+
@object = ElementLocator.new(@container, self.class).find_by_conditions(@conditions)
|
115
|
+
end
|
116
|
+
|
117
|
+
#
|
118
|
+
# Returns the HtmlUnit object backing this element
|
119
|
+
#
|
120
|
+
|
121
|
+
def object
|
122
|
+
@object || locate
|
123
|
+
end
|
124
|
+
|
125
|
+
#
|
126
|
+
# Returns a JavaScript object representing the receiver
|
127
|
+
#
|
128
|
+
# @api internal - subject to change
|
129
|
+
#
|
130
|
+
|
131
|
+
def javascript_object
|
132
|
+
assert_exists
|
133
|
+
@object.getScriptObject
|
134
|
+
end
|
135
|
+
|
136
|
+
#
|
137
|
+
# @return [String] A string representation of the element.
|
138
|
+
#
|
139
|
+
|
140
|
+
def to_s
|
141
|
+
assert_exists
|
142
|
+
Celerity::Util.create_string @object
|
143
|
+
end
|
144
|
+
|
145
|
+
#
|
146
|
+
# @param [String, #to_s] The attribute.
|
147
|
+
# @return [String] The value of the given attribute.
|
148
|
+
#
|
149
|
+
|
150
|
+
def attribute_value(attribute)
|
151
|
+
assert_exists
|
152
|
+
@object.getAttribute attribute.to_s
|
153
|
+
end
|
154
|
+
|
155
|
+
#
|
156
|
+
# Check if the element is visible to the user or not.
|
157
|
+
# Note that this only takes the _style attribute_ of the element (and
|
158
|
+
# its parents) into account - styles from applied CSS is not considered.
|
159
|
+
#
|
160
|
+
# @return [boolean]
|
161
|
+
#
|
162
|
+
|
163
|
+
def visible?
|
164
|
+
assert_exists
|
165
|
+
@object.isDisplayed
|
166
|
+
end
|
167
|
+
|
168
|
+
#
|
169
|
+
# Used internally to ensure the element actually exists.
|
170
|
+
#
|
171
|
+
# @raise [Celerity::Exception::UnknownObjectException] if the element can't be found.
|
172
|
+
# @api private
|
173
|
+
#
|
174
|
+
|
175
|
+
def assert_exists
|
176
|
+
locate unless @object
|
177
|
+
unless @object
|
178
|
+
raise UnknownObjectException, "Unable to locate #{self.class.name[/::(.*)$/, 1]}, using #{identifier_string}"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
#
|
183
|
+
# Checks if the element exists.
|
184
|
+
# @return [true, false]
|
185
|
+
#
|
186
|
+
|
187
|
+
def exists?
|
188
|
+
assert_exists
|
189
|
+
true
|
190
|
+
rescue UnknownObjectException, UnknownFrameException
|
191
|
+
false
|
192
|
+
end
|
193
|
+
alias_method :exist?, :exists?
|
194
|
+
|
195
|
+
#
|
196
|
+
# Return a text representation of the element as it would appear in a browser.
|
197
|
+
#
|
198
|
+
# @see inner_text
|
199
|
+
# @return [String]
|
200
|
+
#
|
201
|
+
|
202
|
+
def text
|
203
|
+
assert_exists
|
204
|
+
@object.asText.strip # this must behave like ElementLocator
|
205
|
+
end
|
206
|
+
|
207
|
+
#
|
208
|
+
# Return the text content of this DOM node, disregarding its visibility.
|
209
|
+
#
|
210
|
+
# (Celerity-specific?)
|
211
|
+
#
|
212
|
+
# @see text
|
213
|
+
# @return [String]
|
214
|
+
#
|
215
|
+
|
216
|
+
def inner_text
|
217
|
+
assert_exists
|
218
|
+
Celerity::Util.normalize_text @object.getTextContent
|
219
|
+
end
|
220
|
+
|
221
|
+
#
|
222
|
+
# @return [String] The normative XML representation of the element (including children).
|
223
|
+
#
|
224
|
+
|
225
|
+
def to_xml
|
226
|
+
assert_exists
|
227
|
+
@object.asXml
|
228
|
+
end
|
229
|
+
alias_method :asXml, :to_xml
|
230
|
+
alias_method :as_xml, :to_xml
|
231
|
+
alias_method :html, :to_xml
|
232
|
+
|
233
|
+
#
|
234
|
+
# @return [String] A string representation of the element's attributes.
|
235
|
+
#
|
236
|
+
|
237
|
+
def attribute_string
|
238
|
+
assert_exists
|
239
|
+
|
240
|
+
result = ''
|
241
|
+
@object.getAttributes.each do |attribute|
|
242
|
+
result << %Q{#{attribute.getName}="#{attribute.getValue}"}
|
243
|
+
end
|
244
|
+
|
245
|
+
result
|
246
|
+
end
|
247
|
+
|
248
|
+
#
|
249
|
+
# return the canonical xpath for this element (Celerity-specific)
|
250
|
+
#
|
251
|
+
|
252
|
+
def xpath
|
253
|
+
assert_exists
|
254
|
+
@object.getCanonicalXPath
|
255
|
+
end
|
256
|
+
|
257
|
+
#
|
258
|
+
# Dynamically get element attributes.
|
259
|
+
#
|
260
|
+
# @see ATTRIBUTES constant for a list of valid methods for a given element.
|
261
|
+
#
|
262
|
+
# @return [String] The resulting attribute.
|
263
|
+
# @raise [NoMethodError] if the element doesn't support this attribute.
|
264
|
+
#
|
265
|
+
|
266
|
+
def method_missing(meth, *args, &blk)
|
267
|
+
assert_exists
|
268
|
+
|
269
|
+
meth = selector_to_attribute(meth)
|
270
|
+
|
271
|
+
if self.class::ATTRIBUTES.include?(meth) || (self.class == Element && @object.hasAttribute(meth.to_s))
|
272
|
+
return @object.getAttribute(meth.to_s)
|
273
|
+
end
|
274
|
+
Log.warn "Element\#method_missing calling super with #{meth.inspect}"
|
275
|
+
super
|
276
|
+
end
|
277
|
+
|
278
|
+
def methods(*args)
|
279
|
+
ms = super
|
280
|
+
ms += self.class::ATTRIBUTES.map { |e| e.to_s }
|
281
|
+
ms.sort
|
282
|
+
end
|
283
|
+
|
284
|
+
def respond_to?(meth, include_private = false)
|
285
|
+
meth = selector_to_attribute(meth)
|
286
|
+
return true if self.class::ATTRIBUTES.include?(meth)
|
287
|
+
super
|
288
|
+
end
|
289
|
+
|
290
|
+
private
|
291
|
+
|
292
|
+
def identifier_string
|
293
|
+
if @conditions.size == 1
|
294
|
+
how, what = @conditions.to_a.first
|
295
|
+
"#{how.inspect} and #{what.inspect}"
|
296
|
+
else
|
297
|
+
@conditions.inspect
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
def selector_to_attribute(meth)
|
302
|
+
case meth
|
303
|
+
when :class_name then :class
|
304
|
+
when :caption then :value
|
305
|
+
when :url then :href
|
306
|
+
else meth
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
end # Element
|
311
|
+
end # Celerity
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module Celerity
|
2
|
+
|
3
|
+
#
|
4
|
+
# This class is the superclass for the iterator classes (Buttons, Links, Spans etc.)
|
5
|
+
# It would normally only be accessed by the iterator methods (Browser#spans, Browser#links, ...).
|
6
|
+
#
|
7
|
+
|
8
|
+
class ElementCollection
|
9
|
+
include Enumerable
|
10
|
+
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
#
|
14
|
+
|
15
|
+
def initialize(container, how = nil, what = nil)
|
16
|
+
@container = container
|
17
|
+
@object = (how == :object ? what : nil)
|
18
|
+
@length = length
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
# @return [Fixnum] The number of elements in this collection.
|
23
|
+
#
|
24
|
+
|
25
|
+
def length
|
26
|
+
if @object
|
27
|
+
@object.length
|
28
|
+
else
|
29
|
+
@elements ||= ElementLocator.new(@container, element_class).elements_by_idents
|
30
|
+
@elements.size
|
31
|
+
end
|
32
|
+
end
|
33
|
+
alias_method :size, :length
|
34
|
+
|
35
|
+
#
|
36
|
+
# @yieldparam [Celerity::Element] element Iterate through the elements in this collection.
|
37
|
+
#
|
38
|
+
|
39
|
+
def each
|
40
|
+
if @elements
|
41
|
+
@elements.each { |e| yield(element_class.new(@container, :object, e)) }
|
42
|
+
else
|
43
|
+
0.upto(@length - 1) { |i| yield iterator_object(i) }
|
44
|
+
end
|
45
|
+
|
46
|
+
@length
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# Get the element at the given index.
|
51
|
+
# By default, this is 1-indexed to keep compatibility with Watir.
|
52
|
+
#
|
53
|
+
# Also note that because of Watir's lazy loading, this will return an Element
|
54
|
+
# instance even if the index is out of bounds.
|
55
|
+
#
|
56
|
+
# @param [Fixnum] n Index of wanted element, 1-indexed unless Celerity.index_offset is changed.
|
57
|
+
# @return [Celerity::Element] Returns a subclass of Celerity::Element
|
58
|
+
#
|
59
|
+
|
60
|
+
def [](n)
|
61
|
+
if @elements && @elements[n - Celerity.index_offset]
|
62
|
+
element_class.new(@container, :object, @elements[n - Celerity.index_offset])
|
63
|
+
else
|
64
|
+
iterator_object(n - Celerity.index_offset)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
#
|
69
|
+
# Get the first element in this collection. (Celerity-specific)
|
70
|
+
#
|
71
|
+
# @return [Celerity::Element] Returns a subclass of Celerity::Element
|
72
|
+
#
|
73
|
+
|
74
|
+
def first
|
75
|
+
self[Celerity.index_offset]
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
# Get the last element in this collection. (Celerity-specific)
|
80
|
+
#
|
81
|
+
# @return [Celerity::Element] Returns a subclass of Celerity::Element
|
82
|
+
#
|
83
|
+
|
84
|
+
def last
|
85
|
+
self[Celerity.index_offset - 1]
|
86
|
+
end
|
87
|
+
|
88
|
+
#
|
89
|
+
# Note: This can be quite useful in irb:
|
90
|
+
#
|
91
|
+
# puts browser.text_fields
|
92
|
+
#
|
93
|
+
# @return [String] A string representation of all elements in this collection.
|
94
|
+
#
|
95
|
+
|
96
|
+
def to_s
|
97
|
+
map { |e| e.to_s }.join("\n")
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def iterator_object(i)
|
103
|
+
element_class.new(@container, :index, i + Celerity.index_offset)
|
104
|
+
end
|
105
|
+
|
106
|
+
end # ElementCollection
|
107
|
+
end # Celerity
|
@@ -0,0 +1,164 @@
|
|
1
|
+
module Celerity
|
2
|
+
|
3
|
+
#
|
4
|
+
# Used internally to locate elements on the page.
|
5
|
+
#
|
6
|
+
|
7
|
+
class ElementLocator
|
8
|
+
include Celerity::Exception
|
9
|
+
attr_accessor :idents
|
10
|
+
|
11
|
+
|
12
|
+
def initialize(container, element_class)
|
13
|
+
container.assert_exists
|
14
|
+
|
15
|
+
@container = container
|
16
|
+
@object = container.object
|
17
|
+
@element_class = element_class
|
18
|
+
@attributes = @element_class::ATTRIBUTES # could check for 'strict' here?
|
19
|
+
@idents = @element_class::TAGS
|
20
|
+
@tags = @idents.map { |e| e.tag.downcase }
|
21
|
+
end
|
22
|
+
|
23
|
+
def find_by_conditions(conditions) # TODO: refactor without performance hit
|
24
|
+
return nil unless @object # probably means we're on a TextPage (content-type is "text/plain")
|
25
|
+
|
26
|
+
@condition_idents = []
|
27
|
+
attributes = Hash.new { |h, k| h[k] = [] }
|
28
|
+
index = 0 # by default, return the first matching element
|
29
|
+
text = nil
|
30
|
+
|
31
|
+
conditions.each do |how, what|
|
32
|
+
case how
|
33
|
+
when :object
|
34
|
+
unless what.is_a?(HtmlUnit::Html::HtmlElement) || what.nil?
|
35
|
+
raise ArgumentError, "expected an HtmlUnit::Html::HtmlElement subclass, got #{what.inspect}:#{what.class}"
|
36
|
+
end
|
37
|
+
return what
|
38
|
+
when :xpath
|
39
|
+
return find_by_xpath(what)
|
40
|
+
when :label
|
41
|
+
return find_by_label(what) unless @attributes.include?(:label)
|
42
|
+
when :class_name
|
43
|
+
how = :class
|
44
|
+
when :url
|
45
|
+
how = :href
|
46
|
+
when :caption
|
47
|
+
how = :text
|
48
|
+
end
|
49
|
+
|
50
|
+
if how == :id && conditions.size == 1
|
51
|
+
return find_by_id(what)
|
52
|
+
elsif @attributes.include?(how = how.to_sym)
|
53
|
+
attributes[how] << what
|
54
|
+
elsif how == :index
|
55
|
+
index = what.to_i - Celerity.index_offset
|
56
|
+
elsif how == :text
|
57
|
+
text = what
|
58
|
+
else
|
59
|
+
raise MissingWayOfFindingObjectException, "No how #{how.inspect}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
@idents.each do |ident|
|
64
|
+
merged = attributes.merge(ident.attributes) { |key, v1, v2| v1 | v2 }
|
65
|
+
id = Identifier.new(ident.tag, merged)
|
66
|
+
id.text = ident.text || text # «original» identifier takes precedence for :text
|
67
|
+
@condition_idents << id
|
68
|
+
end
|
69
|
+
|
70
|
+
if index == 0
|
71
|
+
element_by_idents(@condition_idents)
|
72
|
+
else
|
73
|
+
elements_by_idents(@condition_idents)[index]
|
74
|
+
end
|
75
|
+
|
76
|
+
rescue HtmlUnit::ElementNotFoundException
|
77
|
+
nil # for rcov
|
78
|
+
end
|
79
|
+
|
80
|
+
def find_by_id(what)
|
81
|
+
case what
|
82
|
+
when Regexp
|
83
|
+
elements_by_tag_names.find { |elem| elem.getId =~ what }
|
84
|
+
when String
|
85
|
+
obj = @object.getElementById(what)
|
86
|
+
return obj if @tags.include?(obj.getTagName)
|
87
|
+
|
88
|
+
$stderr.puts "warning: multiple elements with identical id? (#{what.inspect})" if $VERBOSE
|
89
|
+
elements_by_tag_names.find { |elem| elem.getId == what }
|
90
|
+
else
|
91
|
+
raise TypeError, "expected String or Regexp, got #{what.inspect}:#{what.class}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def find_by_xpath(what)
|
96
|
+
what = ".#{what}" if what[0].chr == "/"
|
97
|
+
object = @object.getByXPath(what).to_a.first || return
|
98
|
+
|
99
|
+
return unless @idents.any? { |id| id.match?(object) }
|
100
|
+
|
101
|
+
|
102
|
+
object
|
103
|
+
end
|
104
|
+
|
105
|
+
def find_by_label(what)
|
106
|
+
obj = elements_by_tag_names(%w[label]).find { |e| Util.matches?(e.asText, what) }
|
107
|
+
|
108
|
+
return nil unless obj && (ref = obj.getReferencedElement)
|
109
|
+
return ref if @tags.include?(ref.getTagName)
|
110
|
+
|
111
|
+
find_by_id obj.getForAttribute
|
112
|
+
end
|
113
|
+
|
114
|
+
def elements_by_idents(idents = @idents)
|
115
|
+
get_by_idents(:select, idents)
|
116
|
+
end
|
117
|
+
|
118
|
+
def element_by_idents(idents = @idents)
|
119
|
+
get_by_idents(:find, idents)
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def get_by_idents(meth, idents)
|
125
|
+
with_nullpointer_retry do
|
126
|
+
all_elements.send(meth) do |element|
|
127
|
+
next unless @tags.include?(element.getTagName)
|
128
|
+
idents.any? { |id| id.match?(element) }
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def elements_by_tag_names(tags = @tags)
|
134
|
+
with_nullpointer_retry do
|
135
|
+
# HtmlUnit's getHtmlElementsByTagNames won't get elements in the correct
|
136
|
+
# order (making :index fail), so we're looping through all elements instead.
|
137
|
+
all_elements.select { |elem| tags.include?(elem.getTagName) }
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def all_elements
|
142
|
+
unless @object
|
143
|
+
raise %{internal error in #{self.class}: @container=#{@container.inspect} @element_class=#{@element_class.inspect}
|
144
|
+
Please report this failure and the code/HTML that caused it at http://github.com/jarib/celerity/issues}
|
145
|
+
end
|
146
|
+
|
147
|
+
@object.getHtmlElementDescendants
|
148
|
+
end
|
149
|
+
|
150
|
+
# HtmlUnit throws NPEs sometimes when we're locating elements
|
151
|
+
# Retry seems to work fine.
|
152
|
+
def with_nullpointer_retry(max_retries = 3)
|
153
|
+
tries = 0
|
154
|
+
yield
|
155
|
+
rescue java.lang.NullPointerException => e
|
156
|
+
raise e if tries >= max_retries
|
157
|
+
|
158
|
+
tries += 1
|
159
|
+
warn "warning: celerity caught #{e} - retry ##{tries}"
|
160
|
+
retry
|
161
|
+
end
|
162
|
+
|
163
|
+
end # ElementLocator
|
164
|
+
end # Celerity
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Celerity
|
2
|
+
|
3
|
+
#
|
4
|
+
# Input: Button
|
5
|
+
#
|
6
|
+
# Class representing button elements.
|
7
|
+
#
|
8
|
+
# This class covers both <button> and <input type="submit|reset|image|button" /> elements.
|
9
|
+
#
|
10
|
+
|
11
|
+
class Button < InputElement
|
12
|
+
TAGS = [ Identifier.new('button'),
|
13
|
+
Identifier.new('input', :type => %w[submit reset image button]) ]
|
14
|
+
|
15
|
+
# Attribute list is a little weird due to this class covering both <button>
|
16
|
+
# and <input type="submit|reset|image|button" />
|
17
|
+
ATTRIBUTES = ATTRIBUTES | [
|
18
|
+
:accesskey,
|
19
|
+
:disabled,
|
20
|
+
:ismap,
|
21
|
+
:onblur,
|
22
|
+
:onfocus,
|
23
|
+
:src,
|
24
|
+
:tabindex,
|
25
|
+
:type,
|
26
|
+
:usemap,
|
27
|
+
]
|
28
|
+
DEFAULT_HOW = :value
|
29
|
+
|
30
|
+
#
|
31
|
+
# @api private
|
32
|
+
#
|
33
|
+
|
34
|
+
def locate
|
35
|
+
# We want the :value attribute to point to the inner text for <button> elements,
|
36
|
+
# and to the value attribute for <input type="button"> elements.
|
37
|
+
if (val = @conditions[:value])
|
38
|
+
button_ident = Identifier.new('button')
|
39
|
+
button_ident.text = val
|
40
|
+
input_ident = Identifier.new('input', :type => %w[submit reset image button], :value => [val])
|
41
|
+
|
42
|
+
locator = ElementLocator.new(@container, self.class)
|
43
|
+
locator.idents = [button_ident, input_ident]
|
44
|
+
|
45
|
+
conditions = @conditions.dup
|
46
|
+
conditions.delete(:value)
|
47
|
+
@object = locator.find_by_conditions(conditions)
|
48
|
+
else
|
49
|
+
super
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end # Button
|
54
|
+
end # Celerity
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Celerity
|
2
|
+
|
3
|
+
#
|
4
|
+
# For fields that accept file uploads
|
5
|
+
#
|
6
|
+
|
7
|
+
class FileField < InputElement
|
8
|
+
TAGS = [ Identifier.new('input', :type => %w[file]) ]
|
9
|
+
DEFAULT_HOW = :name
|
10
|
+
|
11
|
+
#
|
12
|
+
# Set the file field to the given path
|
13
|
+
#
|
14
|
+
|
15
|
+
def set(path)
|
16
|
+
assert_exists
|
17
|
+
path = path.to_s
|
18
|
+
|
19
|
+
@object.setValueAttribute path
|
20
|
+
|
21
|
+
unless @object.getContentType
|
22
|
+
@object.setContentType(Celerity::Util.content_type_for(path))
|
23
|
+
end
|
24
|
+
|
25
|
+
path
|
26
|
+
end
|
27
|
+
|
28
|
+
end # FileField
|
29
|
+
end # Celerity
|