vapir-common 1.7.2 → 1.8.0
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 +0 -5
- data/lib/vapir-common.rb +16 -4
- data/lib/vapir-common/browser.rb +189 -144
- data/lib/vapir-common/browsers.rb +21 -9
- data/lib/vapir-common/config.rb +341 -0
- data/lib/vapir-common/container.rb +160 -30
- data/lib/vapir-common/element.rb +65 -555
- data/lib/vapir-common/element_class_and_module.rb +378 -0
- data/lib/vapir-common/element_collection.rb +108 -20
- data/lib/vapir-common/elements/elements.rb +243 -67
- data/lib/vapir-common/external/core_extensions.rb +62 -0
- data/lib/vapir-common/handle_options.rb +1 -1
- data/lib/vapir-common/keycodes.rb +135 -0
- data/lib/vapir-common/options.rb +5 -38
- data/lib/vapir-common/page_container.rb +26 -21
- data/lib/vapir-common/specifier.rb +2 -2
- data/lib/vapir-common/version.rb +5 -0
- data/lib/vapir-common/waiter.rb +44 -90
- data/lib/vapir.rb +7 -0
- metadata +12 -27
- data/lib/vapir-common/testcase.rb +0 -89
- data/lib/vapir-common/win_window.rb +0 -1227
@@ -34,14 +34,14 @@ module Vapir
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
extra=extra_for_contained.merge(:index => index)
|
37
|
-
|
38
|
-
|
39
|
-
element=klass.new(how, what, extra
|
40
|
-
|
37
|
+
extra.merge!(other[:extra]) if other[:extra]
|
38
|
+
if !other.key?(:locate)
|
39
|
+
element=klass.new(how, what, extra)
|
40
|
+
elsif other[:locate]==:nil_unless_exists
|
41
41
|
element=klass.new(how, what, extra.merge(:locate => true))
|
42
42
|
element.exists? ? element : nil
|
43
43
|
else
|
44
|
-
|
44
|
+
element=klass.new(how, what, extra.merge(:locate => other[:locate]))
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
@@ -52,7 +52,11 @@ module Vapir
|
|
52
52
|
def normalize_how_what_index(first, second, klass)
|
53
53
|
case first
|
54
54
|
when nil
|
55
|
-
|
55
|
+
if second==nil
|
56
|
+
how, what, index = nil, nil, nil
|
57
|
+
else
|
58
|
+
raise Vapir::Exception::MissingWayOfFindingObjectException, "first argument ('how') was nil but second argument ('what') was given as #{second.inspect}"
|
59
|
+
end
|
56
60
|
when Hash
|
57
61
|
how=:attributes
|
58
62
|
what=first.dup
|
@@ -74,8 +78,8 @@ module Vapir
|
|
74
78
|
else
|
75
79
|
raise Vapir::Exception::MissingWayOfFindingObjectException, "Cannot search using arguments #{first.inspect} (#{first.class}) and #{second.inspect} (#{second.class})"
|
76
80
|
end
|
77
|
-
elsif first==:index #
|
78
|
-
how=
|
81
|
+
elsif first==:index # index isn't a real 'how'
|
82
|
+
how=nil
|
79
83
|
what=nil
|
80
84
|
index=second
|
81
85
|
else
|
@@ -98,15 +102,9 @@ module Vapir
|
|
98
102
|
# asserts that this element exists - optionally, takes a block, and other calls to assert_exists
|
99
103
|
# over the course of the block will not cause redundant assertions.
|
100
104
|
def assert_exists(options={})
|
101
|
-
|
102
|
-
if
|
103
|
-
|
104
|
-
end
|
105
|
-
@asserting_exists=true
|
106
|
-
begin
|
107
|
-
if block_given?
|
108
|
-
result=yield
|
109
|
-
end
|
105
|
+
# yeah, this line is an unreadable mess, but I have to skip over it so many times debugging that it's worth just sticking it on one line
|
106
|
+
(was_asserting_exists=@asserting_exists); (locate! if !@asserting_exists || options[:force]); (@asserting_exists=true)
|
107
|
+
begin; result=yield if block_given?
|
110
108
|
ensure
|
111
109
|
@asserting_exists=was_asserting_exists
|
112
110
|
end
|
@@ -115,26 +113,42 @@ module Vapir
|
|
115
113
|
public
|
116
114
|
# catch exceptions that indicate some failure of something existing.
|
117
115
|
#
|
118
|
-
# takes an
|
119
|
-
# encountered exception.
|
120
|
-
# :
|
121
|
-
#
|
122
|
-
#
|
123
|
-
#
|
124
|
-
#
|
116
|
+
# takes an options hash:
|
117
|
+
# - :handle indicates how the method should handle an encountered exception. value may be:
|
118
|
+
# - :ignore (default) - the exception is ignored and nil is returned.
|
119
|
+
# - :raise - the exception is raised (same as if this method weren't used at all).
|
120
|
+
# - :return - returns the exception which was raised.
|
121
|
+
# - Proc, Method - the proc or method is called with the exception as an argument.
|
122
|
+
# - :assert_exists causes the method to check existence before yielding to the block.
|
123
|
+
# value may be:
|
124
|
+
# - :force (default) - assert_exists(:force => true) is called so that existence is checked
|
125
|
+
# even if we're inside an assert_exists block already. this is the most common case, since
|
126
|
+
# this method is generally used when the element may have recently stopped existing.
|
127
|
+
# - true - assert_exists is called (without the :force option)
|
128
|
+
# - false - assert_exists is not called.
|
125
129
|
#
|
126
130
|
# If no exception was raised, then the result of the give block is returned.
|
127
131
|
#--
|
128
132
|
# this may be overridden elsewhere to deal with any other stuff that indicates failure to exist, as it is
|
129
|
-
# to catch WIN32OLERuntimeErrors.
|
133
|
+
# to catch WIN32OLERuntimeErrors for Vapir::IE.
|
130
134
|
def handling_existence_failure(options={})
|
131
|
-
options=handle_options(options, :
|
135
|
+
options=handle_options(options, {:assert_exists => :force}, [:handle])
|
132
136
|
begin
|
137
|
+
case options[:assert_exists]
|
138
|
+
when true
|
139
|
+
assert_exists
|
140
|
+
when :force
|
141
|
+
assert_exists(:force => true)
|
142
|
+
when false, nil
|
143
|
+
else
|
144
|
+
raise ArgumentError, "option :assert_exists should be true, false, or :force; got #{options[:assert_exists].inspect}"
|
145
|
+
end
|
133
146
|
yield
|
134
147
|
rescue Vapir::Exception::ExistenceFailureException
|
135
|
-
handle_existence_failure($!, options)
|
148
|
+
handle_existence_failure($!, options.reject{|k,v| ![:handle].include?(k) })
|
136
149
|
end
|
137
150
|
end
|
151
|
+
alias base_handling_existence_failure handling_existence_failure # :nodoc:
|
138
152
|
private
|
139
153
|
# handles any errors encountered by #handling_existence_failure (either the
|
140
154
|
# common one or a browser-specific one)
|
@@ -155,13 +169,81 @@ module Vapir
|
|
155
169
|
end
|
156
170
|
public
|
157
171
|
|
158
|
-
def
|
172
|
+
def base_extra_for_contained
|
159
173
|
extra={:container => self}
|
160
174
|
extra[:browser]= browser if respond_to?(:browser)
|
161
175
|
extra[:page_container]= page_container if respond_to?(:page_container)
|
162
176
|
extra
|
163
177
|
end
|
164
|
-
alias extra_for_contained
|
178
|
+
alias extra_for_contained base_extra_for_contained
|
179
|
+
|
180
|
+
# returns an array of text nodes below this element in the DOM heirarchy which are visible -
|
181
|
+
# that is, their parent element is visible.
|
182
|
+
def visible_text_nodes
|
183
|
+
# TODO: needs tests
|
184
|
+
assert_exists do
|
185
|
+
recurse_text_nodes=ycomb do |recurse|
|
186
|
+
proc do |node, parent_visibility|
|
187
|
+
case node.nodeType
|
188
|
+
when 1, 9 # TODO: name a constant ELEMENT_NODE, rather than magic number
|
189
|
+
style= node.nodeType==1 ? base_element_class.element_object_style(node, document_object) : nil
|
190
|
+
our_visibility = style && (visibility=style.invoke('visibility'))
|
191
|
+
unless our_visibility && ['hidden', 'collapse', 'visible'].include?(our_visibility=our_visibility.strip.downcase)
|
192
|
+
our_visibility = parent_visibility
|
193
|
+
end
|
194
|
+
display = style && style.invoke('display')
|
195
|
+
if display && display.strip.downcase=='none'
|
196
|
+
[]
|
197
|
+
else
|
198
|
+
Vapir::Element.object_collection_to_enumerable(node.childNodes).inject([]) do |result, child_node|
|
199
|
+
result + recurse.call(child_node, our_visibility)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
when 3 # TODO: name a constant TEXT_NODE, rather than magic number
|
203
|
+
if parent_visibility && ['hidden','collapse'].include?(parent_visibility.downcase)
|
204
|
+
[]
|
205
|
+
else
|
206
|
+
[node.data]
|
207
|
+
end
|
208
|
+
else
|
209
|
+
#Kernel.warn("ignoring node of type #{node.nodeType}")
|
210
|
+
[]
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# determine the current visibility and display. TODO: this is copied/adapted from #visible?; should DRY
|
216
|
+
element_to_check=containing_object
|
217
|
+
real_visibility=nil
|
218
|
+
while element_to_check #&& !element_to_check.instanceof(nsIDOMDocument)
|
219
|
+
if (style=base_element_class.element_object_style(element_to_check, document_object))
|
220
|
+
# only pay attention to the innermost definition that really defines visibility - one of 'hidden', 'collapse' (only for table elements),
|
221
|
+
# or 'visible'. ignore 'inherit'; keep looking upward.
|
222
|
+
# this makes it so that if we encounter an explicit 'visible', we don't pay attention to any 'hidden' further up.
|
223
|
+
# this style is inherited - may be pointless for firefox, but IE uses the 'inherited' value. not sure if/when ff does.
|
224
|
+
if real_visibility==nil && (visibility=style.invoke('visibility'))
|
225
|
+
visibility=visibility.strip.downcase
|
226
|
+
if ['hidden', 'collapse', 'visible'].include?(visibility)
|
227
|
+
real_visibility=visibility
|
228
|
+
end
|
229
|
+
end
|
230
|
+
# check for display property. this is not inherited, and a parent with display of 'none' overrides an immediate visibility='visible'
|
231
|
+
display=style.invoke('display')
|
232
|
+
if display && (display=display.strip.downcase)=='none'
|
233
|
+
# if display is none, then this element is not visible, and thus has no visible text nodes underneath.
|
234
|
+
return []
|
235
|
+
end
|
236
|
+
end
|
237
|
+
element_to_check=element_to_check.parentNode
|
238
|
+
end
|
239
|
+
recurse_text_nodes.call(containing_object, real_visibility)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
# returns an visible text inside this element by concatenating text nodes below this element in the DOM heirarchy which are visible.
|
243
|
+
def visible_text
|
244
|
+
# TODO: needs tests
|
245
|
+
visible_text_nodes.join('')
|
246
|
+
end
|
165
247
|
|
166
248
|
# Checks if this container's text includes the given regexp or string.
|
167
249
|
# Returns true if the container's #text matches the given String or Regexp; otherwise false.
|
@@ -181,7 +263,29 @@ module Vapir
|
|
181
263
|
end
|
182
264
|
end
|
183
265
|
alias contains_text contains_text?
|
266
|
+
|
267
|
+
# this is defined on each class to reflect the browser's particular implementation.
|
268
|
+
def element_object_style(element_object, document_object)
|
269
|
+
base_element_class.element_object_style(element_object, document_object)
|
270
|
+
end
|
271
|
+
private :element_object_style
|
184
272
|
|
273
|
+
# for a common module, such as a TextField, returns an elements-specific class (such as
|
274
|
+
# Firefox::TextField) that inherits from the base_element_class of self. That is, this returns
|
275
|
+
# a sibling class, as it were, of whatever class inheriting from Element is instantiated.
|
276
|
+
def element_class_for(common_module)
|
277
|
+
element_class=nil
|
278
|
+
ObjectSpace.each_object(Class) do |klass|
|
279
|
+
if klass < common_module && klass <= base_element_class && (!element_class || element_class < klass)
|
280
|
+
element_class= klass
|
281
|
+
end
|
282
|
+
end
|
283
|
+
unless element_class
|
284
|
+
raise RuntimeError, "No class found that inherits from both #{common_module} and #{base_element_class}"
|
285
|
+
end
|
286
|
+
element_class
|
287
|
+
end
|
288
|
+
|
185
289
|
# shows the available objects on the current container.
|
186
290
|
# This is usually only used for debugging or writing new test scripts.
|
187
291
|
# This is a nice feature to help find out what HTML objects are on a page
|
@@ -195,11 +299,37 @@ module Vapir
|
|
195
299
|
def show_all_objects(write_to=$stdout)
|
196
300
|
# this used to reject tagNames 'br', 'hr', 'doctype', 'meta', and elements with no tagName
|
197
301
|
elements.map do |element|
|
198
|
-
element=element.
|
302
|
+
element=element.to_subtype
|
199
303
|
write_to.write element.to_s+"\n"
|
200
304
|
write_to.write '-'*42+"\n"
|
201
305
|
element
|
202
306
|
end
|
203
307
|
end
|
308
|
+
module WatirContainerConfigCompatibility
|
309
|
+
def type_keys
|
310
|
+
if config.warn_deprecated
|
311
|
+
Kernel.warn_with_caller "WARNING: #type_keys is deprecated; please use the new config framework with config.type_keys"
|
312
|
+
end
|
313
|
+
config.type_keys
|
314
|
+
end
|
315
|
+
def type_keys=(arg) # deprecate
|
316
|
+
if config.warn_deprecated
|
317
|
+
Kernel.warn_with_caller "WARNING: #type_keys= is deprecated; please use the new config framework with config.type_keys="
|
318
|
+
end
|
319
|
+
config.type_keys= arg
|
320
|
+
end
|
321
|
+
def typingspeed
|
322
|
+
if config.warn_deprecated
|
323
|
+
Kernel.warn_with_caller "WARNING: #typingspeed is deprecated; please use the new config framework with config.typing_interval"
|
324
|
+
end
|
325
|
+
config.typing_interval
|
326
|
+
end
|
327
|
+
def typingspeed=(arg)
|
328
|
+
if config.warn_deprecated
|
329
|
+
Kernel.warn_with_caller "WARNING: #typingspeed= is deprecated; please use the new config framework with config.typing_interval="
|
330
|
+
end
|
331
|
+
config.typing_interval=arg
|
332
|
+
end
|
333
|
+
end
|
204
334
|
end
|
205
335
|
end
|
data/lib/vapir-common/element.rb
CHANGED
@@ -1,394 +1,19 @@
|
|
1
1
|
require 'vapir-common/element_collection'
|
2
2
|
require 'set'
|
3
3
|
require 'matrix'
|
4
|
+
require 'vapir-common/element_class_and_module'
|
4
5
|
|
5
|
-
class Module
|
6
|
-
def alias_deprecated(to, from)
|
7
|
-
define_method to do |*args|
|
8
|
-
Kernel.warn "DEPRECATION WARNING: #{self.class.name}\##{to} is deprecated. Please use #{self.class.name}\##{from}\n(called from #{caller.map{|c|"\n"+c}})"
|
9
|
-
send(from, *args)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
6
|
module Vapir
|
14
|
-
# this module is for methods that should go on both common element modules (ie, TextField) as well
|
15
|
-
# as browser-specific element classes (ie, Firefox::TextField).
|
16
|
-
module ElementClassAndModuleMethods
|
17
|
-
# takes an element_object (JsshObject or WIN32OLE), and finds the most specific class
|
18
|
-
# that is < self whose specifiers match it. Returns an instance of that class using the given
|
19
|
-
# element_object.
|
20
|
-
#
|
21
|
-
# second argument, extra, is passed as the 'extra' argument to the Element constructor (see its documentation).
|
22
|
-
#
|
23
|
-
# if you give a different how/what (third and fourth arguments, optional), then those are passed
|
24
|
-
# to the Element constructor.
|
25
|
-
def factory(element_object, extra={}, how=nil, what=nil)
|
26
|
-
curr_klass=self
|
27
|
-
# since this gets included in the Element modules, too, check where we are
|
28
|
-
unless self.is_a?(Class) && self < Vapir::Element
|
29
|
-
raise TypeError, "factory was called on #{self} (#{self.class}), which is not a Class that is < Element"
|
30
|
-
end
|
31
|
-
if how
|
32
|
-
# use how and what as given
|
33
|
-
elsif what
|
34
|
-
raise ArgumentError, "'what' was given as #{what.inspect} (#{what.class}) but how was not given"
|
35
|
-
else
|
36
|
-
how=:element_object
|
37
|
-
what=element_object
|
38
|
-
end
|
39
|
-
ObjectSpace.each_object(Class) do |klass|
|
40
|
-
if klass < curr_klass
|
41
|
-
Vapir::ElementObjectCandidates.match_candidates([element_object], klass.specifiers, klass.all_dom_attr_aliases) do |match|
|
42
|
-
curr_klass=klass
|
43
|
-
break
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
curr_klass.new(how, what, extra)
|
48
|
-
end
|
49
|
-
|
50
|
-
# takes any number of arguments, where each argument is either:
|
51
|
-
# - a symbol or strings representing a method that is the same in ruby and on the dom
|
52
|
-
# - or a hash of key/value pairs where each key is a dom attribute, and each value
|
53
|
-
# is a is a corresponding ruby method name or list of ruby method names.
|
54
|
-
def dom_attr(*dom_attrs)
|
55
|
-
dom_attrs.each do |arg|
|
56
|
-
hash=arg.is_a?(Hash) ? arg : arg.is_a?(Symbol) || arg.is_a?(String) ? {arg => arg} : raise(ArgumentError, "don't know what to do with arg #{arg.inspect} (#{arg.class})")
|
57
|
-
hash.each_pair do |dom_attr, ruby_method_names|
|
58
|
-
ruby_method_names= ruby_method_names.is_a?(Array) ? ruby_method_names : [ruby_method_names]
|
59
|
-
class_array_append 'dom_attrs', dom_attr
|
60
|
-
ruby_method_names.each do |ruby_method_name|
|
61
|
-
dom_attr_locate_alias(dom_attr, ruby_method_name)
|
62
|
-
define_method ruby_method_name do
|
63
|
-
method_from_element_object(dom_attr)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
# creates aliases for locating by
|
71
|
-
def dom_attr_locate_alias(dom_attr, alias_name)
|
72
|
-
dom_attr_aliases=class_hash_get('dom_attr_aliases')
|
73
|
-
dom_attr_aliases[dom_attr] ||= Set.new
|
74
|
-
dom_attr_aliases[dom_attr] << alias_name
|
75
|
-
end
|
76
|
-
|
77
|
-
# dom_function is about the same as dom_attr, but dom_attr doesn't take arguments.
|
78
|
-
# also, dom_function methods call #wait; dom_attr ones don't.
|
79
|
-
def dom_function(*dom_functions)
|
80
|
-
dom_functions.each do |arg|
|
81
|
-
hash=arg.is_a?(Hash) ? arg : arg.is_a?(Symbol) || arg.is_a?(String) ? {arg => arg} : raise(ArgumentError, "don't know what to do with arg #{arg.inspect} (#{arg.class})")
|
82
|
-
hash.each_pair do |dom_function, ruby_method_names|
|
83
|
-
ruby_method_names= ruby_method_names.is_a?(Array) ? ruby_method_names : [ruby_method_names]
|
84
|
-
class_array_append 'dom_functions', dom_function
|
85
|
-
ruby_method_names.each do |ruby_method_name|
|
86
|
-
define_method ruby_method_name do |*args|
|
87
|
-
result=method_from_element_object(dom_function, *args)
|
88
|
-
wait
|
89
|
-
result
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
# dom_setter takes arguments in the same format as dom_attr, but sends the given setter method (plus = sign)
|
97
|
-
# to the element object. eg,
|
98
|
-
# module TextField
|
99
|
-
# dom_setter :value
|
100
|
-
# dom_setter :maxLength => :maxlength
|
101
|
-
# end
|
102
|
-
# the #value= method in ruby will send to #value= on the element object
|
103
|
-
# the #maxlength= method in ruby will send to #maxLength= on the element object (note case difference).
|
104
|
-
def dom_setter(*dom_setters)
|
105
|
-
dom_setters.each do |arg|
|
106
|
-
hash=arg.is_a?(Hash) ? arg : arg.is_a?(Symbol) || arg.is_a?(String) ? {arg => arg} : raise(ArgumentError, "don't know what to do with arg #{arg.inspect} (#{arg.class})")
|
107
|
-
hash.each_pair do |dom_setter, ruby_method_names|
|
108
|
-
ruby_method_names= ruby_method_names.is_a?(Array) ? ruby_method_names : [ruby_method_names]
|
109
|
-
class_array_append 'dom_setters', dom_setter
|
110
|
-
ruby_method_names.each do |ruby_method_name|
|
111
|
-
define_method(ruby_method_name.to_s+'=') do |value|
|
112
|
-
element_object.send(dom_setter.to_s+'=', value)
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
# defines an element collection method on the given element - such as SelectList#options
|
120
|
-
# or Table#rows. takes the name of the dom method that returns a collection
|
121
|
-
# of element objects, a ruby method name, and an element class - actually this is
|
122
|
-
# generally an Element module; this method goes ahead and finds the browser-specific
|
123
|
-
# class that will actually be instantiated. the defined method returns an
|
124
|
-
# ElementCollection.
|
125
|
-
def element_collection(dom_attr, ruby_method_name, element_class)
|
126
|
-
define_method ruby_method_name do
|
127
|
-
assert_exists do
|
128
|
-
ElementCollection.new(self, element_class_for(element_class), extra_for_contained.merge(:candidates => dom_attr))
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
# notes the given arguments to be inspected by #inspect and #to_s on each inheriting element.
|
134
|
-
# each argument may be a symbol, in which case the corresponding method is called on the element, or
|
135
|
-
# a hash, with the following keys:
|
136
|
-
# - :label - how the the attribute is labeled in the string returned by #inspect or #to_s.
|
137
|
-
# should be a string or symbol (but anything works; #to_s is called on the label).
|
138
|
-
# - :value - can be one of:
|
139
|
-
# - String starting with '@' - assumes this is an instance variable; gets the value of that instance variable
|
140
|
-
# - Symbol - assumes it is a method name, gives this to #send on the element. this is most commonly-used.
|
141
|
-
# - Proc - calls the proc, giving this element as an argument. should return a string. #to_s is called on its return value.
|
142
|
-
# - anything else - just assumes that that is the value that is wanted in the string.
|
143
|
-
# (see Element#attributes_for_stringifying)
|
144
|
-
# - :if - if defined, should be a proc that returns false/nil if this should not be included in the
|
145
|
-
# string, or anything else (that is, any value considered 'true') if it should. this element is passed
|
146
|
-
# as an argument to the proc.
|
147
|
-
def inspect_these(*inspect_these)
|
148
|
-
inspect_these.each do |inspect_this|
|
149
|
-
attribute_to_inspect=case inspect_this
|
150
|
-
when Hash
|
151
|
-
inspect_this
|
152
|
-
when Symbol
|
153
|
-
{:label => inspect_this, :value => inspect_this}
|
154
|
-
else
|
155
|
-
raise ArgumentError, "unrecognized thing to inspect: #{inspect_this} (#{inspect_this.class})"
|
156
|
-
end
|
157
|
-
class_array_append 'attributes_to_inspect', attribute_to_inspect
|
158
|
-
end
|
159
|
-
end
|
160
|
-
alias inspect_this inspect_these
|
161
|
-
# inspect_this_if(inspect_this, &block) is shorthand for
|
162
|
-
# inspect_this({:label => inspect_this, :value => inspect_this, :if => block)
|
163
|
-
# if a block isn't given, the :if proc is the result of sending the inspect_this symbol to the element.
|
164
|
-
# if inspect_this isn't a symbol, and no block is given, raises ArgumentError.
|
165
|
-
def inspect_this_if inspect_this, &block
|
166
|
-
unless inspect_this.is_a?(Symbol) || block
|
167
|
-
raise ArgumentError, "Either give a block, or specify a symbol as the first argument, instead of #{inspect_this.inspect} (#{inspect_this.class})"
|
168
|
-
end
|
169
|
-
to_inspect={:label => inspect_this, :value => inspect_this}
|
170
|
-
to_inspect[:if]= block || proc {|element| element.send(inspect_this) }
|
171
|
-
class_array_append 'attributes_to_inspect', to_inspect
|
172
|
-
end
|
173
|
-
|
174
|
-
def class_array_append(name, *elements)
|
175
|
-
=begin
|
176
|
-
name='@@'+name.to_s
|
177
|
-
unless self.class_variable_defined?(name)
|
178
|
-
class_variable_set(name, [])
|
179
|
-
end
|
180
|
-
class_variable_get(name).push(*elements)
|
181
|
-
=end
|
182
|
-
name=name.to_s.capitalize
|
183
|
-
unless self.const_defined?(name)
|
184
|
-
self.const_set(name, [])
|
185
|
-
end
|
186
|
-
self.const_get(name).push(*elements)
|
187
|
-
end
|
188
|
-
|
189
|
-
def class_array_get(name)
|
190
|
-
# just return the value of appending nothing
|
191
|
-
class_array_append(name)
|
192
|
-
end
|
193
|
-
def class_hash_merge(name, hash)
|
194
|
-
name=name.to_s.capitalize
|
195
|
-
unless self.const_defined?(name)
|
196
|
-
self.const_set(name, {})
|
197
|
-
end
|
198
|
-
self.const_get(name).merge!(hash)
|
199
|
-
end
|
200
|
-
def class_hash_get(name)
|
201
|
-
class_hash_merge(name, {})
|
202
|
-
end
|
203
|
-
def set_or_get_class_var(class_var, *arg)
|
204
|
-
if arg.length==0
|
205
|
-
class_variable_defined?(class_var) ? class_variable_get(class_var) : nil
|
206
|
-
elsif arg.length==1
|
207
|
-
class_variable_set(class_var, arg.first)
|
208
|
-
else
|
209
|
-
raise ArgumentError, "#{arg.length} arguments given; expected one or two. arguments were #{arg.inspect}"
|
210
|
-
end
|
211
|
-
end
|
212
|
-
def default_how(*arg)
|
213
|
-
set_or_get_class_var('@@default_how', *arg)
|
214
|
-
end
|
215
|
-
def add_container_method_extra_args(*args)
|
216
|
-
class_array_append('container_method_extra_args', *args)
|
217
|
-
end
|
218
|
-
def container_method_extra_args
|
219
|
-
class_array_get('container_method_extra_args')
|
220
|
-
end
|
221
|
-
def specifiers
|
222
|
-
class_array_get 'specifiers'
|
223
|
-
end
|
224
|
-
def container_single_methods
|
225
|
-
class_array_get 'container_single_methods'
|
226
|
-
end
|
227
|
-
def container_collection_methods
|
228
|
-
class_array_get 'container_collection_methods'
|
229
|
-
end
|
230
|
-
|
231
|
-
def parent_element_module(*arg)
|
232
|
-
defined_parent=set_or_get_class_var('@@parent_element_module', *arg)
|
233
|
-
defined_parent || (self==Watir::Element ? nil : Watir::Element)
|
234
|
-
end
|
235
|
-
def all_dom_attrs
|
236
|
-
super_attrs= parent_element_module ? parent_element_module.all_dom_attrs : []
|
237
|
-
super_attrs + class_array_get('dom_attrs')
|
238
|
-
end
|
239
|
-
def all_dom_attr_aliases
|
240
|
-
aliases=class_hash_get('dom_attr_aliases').dup
|
241
|
-
super_aliases= parent_element_module ? parent_element_module.all_dom_attr_aliases : {}
|
242
|
-
super_aliases.each_pair do |attr, alias_list|
|
243
|
-
aliases[attr] = (aliases[attr] || Set.new) + alias_list
|
244
|
-
end
|
245
|
-
aliases
|
246
|
-
end
|
247
|
-
end
|
248
|
-
module ElementHelper
|
249
|
-
def add_specifier(specifier)
|
250
|
-
class_array_append 'specifiers', specifier
|
251
|
-
end
|
252
|
-
|
253
|
-
def container_single_method(*method_names)
|
254
|
-
class_array_append 'container_single_methods', *method_names
|
255
|
-
element_module=self
|
256
|
-
method_names.each do |method_name|
|
257
|
-
Vapir::Element.module_eval do
|
258
|
-
# these methods (Element#parent_table, Element#parent_div, etc)
|
259
|
-
# iterate through parent nodes looking for a parent of the specified
|
260
|
-
# type. if no element of that type is found which is a parent of
|
261
|
-
# self, returns nil.
|
262
|
-
define_method("parent_#{method_name}") do
|
263
|
-
element_class=element_class_for(element_module)
|
264
|
-
parentNode=element_object
|
265
|
-
while true
|
266
|
-
parentNode=parentNode.parentNode
|
267
|
-
unless parentNode && parentNode != document_object # don't ascend up to the document. #TODO/Fix - for IE, comparing WIN32OLEs doesn't really work, this comparison is pointless.
|
268
|
-
return nil
|
269
|
-
end
|
270
|
-
matched=Vapir::ElementObjectCandidates.match_candidates([parentNode], element_class.specifiers, element_class.all_dom_attr_aliases)
|
271
|
-
if matched.size > 0
|
272
|
-
return element_class.new(:element_object, parentNode, extra_for_contained) # this is a little weird, passing extra_for_contained so that this is the container of its parent.
|
273
|
-
end
|
274
|
-
end
|
275
|
-
end
|
276
|
-
end
|
277
|
-
end
|
278
|
-
end
|
279
|
-
def container_collection_method(*method_names)
|
280
|
-
class_array_append 'container_collection_methods', *method_names
|
281
|
-
end
|
282
|
-
|
283
|
-
include ElementClassAndModuleMethods
|
284
|
-
|
285
|
-
def included(including_class)
|
286
|
-
including_class.send :extend, ElementClassAndModuleMethods
|
287
|
-
|
288
|
-
# get Container modules that the including_class includes (ie, Vapir::Firefox::TextField includes the Vapir::Firefox::Container Container module)
|
289
|
-
container_modules=including_class.included_modules.select do |mod|
|
290
|
-
mod.included_modules.include?(Vapir::Container)
|
291
|
-
end
|
292
|
-
|
293
|
-
container_modules.each do |container_module|
|
294
|
-
class_array_get('container_single_methods').each do |container_single_method|
|
295
|
-
# define both bang-methods (like #text_field!) and not (#text_field) with corresponding :locate option for element_by_howwhat
|
296
|
-
[ {:method_name => container_single_method, :locate => true},
|
297
|
-
{:method_name => container_single_method.to_s+'!', :locate => :assert},
|
298
|
-
{:method_name => container_single_method.to_s+'?', :locate => :nil_unless_exists},
|
299
|
-
].each do |method_hash|
|
300
|
-
unless container_module.method_defined?(method_hash[:method_name])
|
301
|
-
container_module.module_eval do
|
302
|
-
define_method(method_hash[:method_name]) do |how, *what_args| # can't take how, what as args because blocks don't do default values so it will want 2 args
|
303
|
-
#locate! # make sure self is located before trying contained stuff
|
304
|
-
what=what_args.shift # what is the first what_arg
|
305
|
-
other_attribute_keys=including_class.container_method_extra_args
|
306
|
-
if what_args.size>other_attribute_keys.length
|
307
|
-
raise ArgumentError, "\##{method_hash[:method_name]} takes 1 to #{2+other_attribute_keys.length} arguments! Got #{([how, what]+what_args).map{|a|a.inspect}.join(', ')}}"
|
308
|
-
end
|
309
|
-
if what_args.size == 0
|
310
|
-
other_attributes= nil
|
311
|
-
else
|
312
|
-
other_attributes={}
|
313
|
-
what_args.each_with_index do |arg, i|
|
314
|
-
other_attributes[other_attribute_keys[i]]=arg
|
315
|
-
end
|
316
|
-
end
|
317
|
-
element_by_howwhat(including_class, how, what, :locate => method_hash[:locate], :other_attributes => other_attributes)
|
318
|
-
end
|
319
|
-
end
|
320
|
-
end
|
321
|
-
end
|
322
|
-
end
|
323
|
-
class_array_get('container_collection_methods').each do |container_multiple_method|
|
324
|
-
container_module.module_eval do
|
325
|
-
# returns an ElementCollection of Elements that are instances of the including class
|
326
|
-
define_method(container_multiple_method) do
|
327
|
-
ElementCollection.new(self, including_class, extra_for_contained)
|
328
|
-
end
|
329
|
-
end
|
330
|
-
container_module.module_eval do
|
331
|
-
define_method('child_'+container_multiple_method.to_s) do
|
332
|
-
ElementCollection.new(self, including_class, extra_for_contained.merge(:candidates => :childNodes))
|
333
|
-
end
|
334
|
-
define_method('show_'+container_multiple_method.to_s) do |*io|
|
335
|
-
io=io.first||$stdout # io is a *array so that you don't have to give an arg (since procs don't do default args)
|
336
|
-
element_collection=ElementCollection.new(self, including_class, extra_for_contained)
|
337
|
-
io.write("There are #{element_collection.length} #{container_multiple_method}\n")
|
338
|
-
element_collection.each do |element|
|
339
|
-
io.write(element.to_s)
|
340
|
-
end
|
341
|
-
end
|
342
|
-
alias_deprecated "show#{container_multiple_method.to_s.capitalize}", "show_"+container_multiple_method.to_s
|
343
|
-
end
|
344
|
-
end
|
345
|
-
end
|
346
|
-
|
347
|
-
# copy constants (like Specifiers) onto classes when inherited
|
348
|
-
# this is here to set the constants of the Element modules below onto the actual classes that instantiate
|
349
|
-
# per-browser (Vapir::IE::TextField, Vapir::Firefox::TextField, etc) so that calling #const_defined? on those
|
350
|
-
# returns true, and so that the constants defined here clobber any inherited stuff from superclasses
|
351
|
-
# which is unwanted.
|
352
|
-
self.constants.each do |const| # copy all of its constants onto wherever it was included
|
353
|
-
to_copy=self.const_get(const)
|
354
|
-
to_copy=to_copy.dup if [Hash, Array, Set].any?{|klass| to_copy.is_a?(klass) }
|
355
|
-
including_class.const_set(const, to_copy)
|
356
|
-
end
|
357
|
-
|
358
|
-
# now the constants (above) have switched away from constants to class variables, pretty much, so copy those.
|
359
|
-
self.class_variables.each do |class_var|
|
360
|
-
to_copy=class_variable_get(class_var)
|
361
|
-
to_copy=to_copy.dup if [Hash, Array, Set].any?{|klass| to_copy.is_a?(klass) }
|
362
|
-
including_class.send(:class_variable_set, class_var, to_copy)
|
363
|
-
end
|
364
|
-
|
365
|
-
class << including_class
|
366
|
-
def attributes_to_inspect
|
367
|
-
super_attrs=superclass.respond_to?(:attributes_to_inspect) ? superclass.attributes_to_inspect : []
|
368
|
-
super_attrs + class_array_get('attributes_to_inspect')
|
369
|
-
end
|
370
|
-
def all_dom_attrs
|
371
|
-
super_attrs=superclass.respond_to?(:all_dom_attrs) ? superclass.all_dom_attrs : []
|
372
|
-
super_attrs + class_array_get('dom_attrs')
|
373
|
-
end
|
374
|
-
def all_dom_attr_aliases
|
375
|
-
aliases=class_hash_get('dom_attr_aliases').dup
|
376
|
-
super_aliases=superclass.respond_to?(:all_dom_attr_aliases) ? superclass.all_dom_attr_aliases : {}
|
377
|
-
super_aliases.each_pair do |attr, alias_list|
|
378
|
-
aliases[attr] = (aliases[attr] || Set.new) + alias_list
|
379
|
-
end
|
380
|
-
aliases
|
381
|
-
end
|
382
|
-
end
|
383
|
-
end
|
384
|
-
|
385
|
-
end
|
386
|
-
|
387
7
|
# this is included by every Element. it relies on the including class implementing a
|
388
8
|
# #element_object method
|
389
9
|
# some stuff assumes the element has a defined @container.
|
390
10
|
module Element
|
391
11
|
extend ElementHelper
|
12
|
+
include Configurable
|
13
|
+
def configuration_parent
|
14
|
+
@container ? @container.config : @browser ? @browser.config : browser_class.config
|
15
|
+
end
|
16
|
+
|
392
17
|
add_specifier({}) # one specifier with no criteria - note that having no specifiers
|
393
18
|
# would match no elements; having a specifier with no criteria matches any
|
394
19
|
# element.
|
@@ -456,9 +81,9 @@ module Vapir
|
|
456
81
|
public
|
457
82
|
|
458
83
|
dom_attr :id
|
459
|
-
|
84
|
+
inspect_this_if :how
|
460
85
|
inspect_this_if(:what) do |element|
|
461
|
-
![:element_object,
|
86
|
+
![:element_object, nil].include?(element.how) # if how==:element_object, don't show the element object in inspect. if no how/what, don't show anything for them.
|
462
87
|
end
|
463
88
|
inspect_this_if(:index) # uses the default 'if'; shows index if it's not nil
|
464
89
|
inspect_these :tag_name, :id
|
@@ -484,7 +109,6 @@ module Vapir
|
|
484
109
|
attr_reader :index
|
485
110
|
|
486
111
|
def html
|
487
|
-
Kernel.warn "#html is deprecated, please use #outer_html or #inner_html. #html currently returns #outer_html (note that it previously returned inner_html on firefox)\n(called from #{caller.map{|c|"\n"+c}})"
|
488
112
|
outer_html
|
489
113
|
end
|
490
114
|
|
@@ -496,7 +120,7 @@ module Vapir
|
|
496
120
|
# after they've done their stuff
|
497
121
|
def default_initialize(how, what, extra={})
|
498
122
|
@how, @what=how, what
|
499
|
-
raise ArgumentError, "how (first argument) should be a Symbol, not: #{how.inspect}" unless how.is_a?(Symbol)
|
123
|
+
raise ArgumentError, "how (first argument) should be a Symbol, not: #{how.inspect}" unless how.is_a?(Symbol) || how==nil
|
500
124
|
@extra=extra
|
501
125
|
@index=begin
|
502
126
|
valid_symbols=[:first, :last]
|
@@ -562,41 +186,21 @@ module Vapir
|
|
562
186
|
end
|
563
187
|
public
|
564
188
|
# locates the element object for this element
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
# - nil or not set (default): this Element is only relocated if the browser is updated (in firefox) or the WIN32OLE stops existing (IE).
|
569
|
-
# - :recursive: this element and its containers are relocated, recursively up to the containing browser.
|
570
|
-
# - false: no relocating is done even if the browser is updated or the element_object stops existing.
|
571
|
-
# - true: this Element is relocated. the container is relocated only if the browser is updated or the element_object stops existing.
|
572
|
-
def locate(options={})
|
573
|
-
if options[:relocate]==nil && @element_object # don't override if it is set to false; only if it's nil, and don't set :relocate there's no @element_object (that's an initial locate, not a relocate)
|
574
|
-
if @browser && @updated_at && @browser.respond_to?(:updated_at) && @browser.updated_at > @updated_at # TODO: implement this for IE; only exists for Firefox now.
|
575
|
-
options[:relocate]=:recursive
|
576
|
-
elsif !element_object_exists?
|
577
|
-
options[:relocate]=true
|
578
|
-
end
|
579
|
-
end
|
580
|
-
container_locate_options={}
|
581
|
-
if options[:relocate]==:recursive
|
582
|
-
container_locate_options[:relocate]= options[:relocate]
|
583
|
-
end
|
584
|
-
if options[:relocate]
|
585
|
-
@element_object=nil
|
189
|
+
def locate
|
190
|
+
if element_object_exists?
|
191
|
+
return @element_object
|
586
192
|
end
|
587
|
-
|
588
|
-
@element_object||= begin
|
193
|
+
new_element_object= begin
|
589
194
|
case @how
|
590
195
|
when :element_object
|
591
196
|
assert_no_index
|
592
|
-
@element_object
|
593
|
-
|
594
|
-
|
197
|
+
if @element_object # if @element_object is already set, it must not exist, since we check #element_object_exists? above.
|
198
|
+
raise Vapir::Exception::UnableToRelocateException, "This #{self.class.name} has stopped existing. Tried to relocate it, but it was specified using #{how.inspect} and cannot be relocated."
|
199
|
+
else
|
200
|
+
@what
|
595
201
|
end
|
596
|
-
@what
|
597
202
|
when :xpath
|
598
|
-
|
599
|
-
@container.locate!(container_locate_options)
|
203
|
+
assert_container_exists
|
600
204
|
unless @container.respond_to?(:element_object_by_xpath)
|
601
205
|
raise Vapir::Exception::MissingWayOfFindingObjectException, "Locating by xpath is not supported on the container #{@container.inspect}"
|
602
206
|
end
|
@@ -604,33 +208,41 @@ module Vapir
|
|
604
208
|
assert_no_index
|
605
209
|
by_xpath=@container.element_object_by_xpath(@what)
|
606
210
|
match_candidates(by_xpath ? [by_xpath] : [], self.class.specifiers, self.class.all_dom_attr_aliases).first
|
211
|
+
when :css
|
212
|
+
assert_container_exists
|
213
|
+
candidate_match_at_index(@index, method(:match_candidates), @container.containing_object.querySelectorAll(@what), self.class.specifiers, self.class.all_dom_attr_aliases)
|
607
214
|
when :label
|
608
215
|
assert_no_index
|
609
216
|
unless document_object
|
610
217
|
raise "No document object found for this #{self.inspect} - needed to search by id for label from #{@container.inspect}"
|
611
218
|
end
|
612
|
-
|
613
|
-
|
219
|
+
label_element = case @what
|
220
|
+
when Label
|
221
|
+
@what.locate!
|
222
|
+
@what
|
223
|
+
when String, Regexp
|
224
|
+
page_container.label(:text, @what)
|
225
|
+
else
|
226
|
+
raise Vapir::Exception::MissingWayOfFindingObjectException, "This #{self.class} was specified as 'how'=:label; 'what' was expected to be a Label element or a String or Regexp to match label text. Given 'what'=#{@what.inspect} (#{@what.class})"
|
227
|
+
end
|
228
|
+
if label_element.exists?
|
229
|
+
by_label=document_object.getElementById(label_element.for)
|
614
230
|
end
|
615
|
-
what.locate!(container_locate_options) # 'what' is not the container; our container is the label's container, but the options for locating should be the same.
|
616
|
-
by_label=document_object.getElementById(what.for)
|
617
231
|
match_candidates(by_label ? [by_label] : [], self.class.specifiers, self.class.all_dom_attr_aliases).first
|
618
232
|
when :attributes
|
619
|
-
|
620
|
-
@container.locate!(container_locate_options)
|
233
|
+
assert_container_exists
|
621
234
|
specified_attributes=@what
|
622
235
|
specifiers=self.class.specifiers.map{|spec| spec.merge(specified_attributes)}
|
623
236
|
|
624
237
|
candidate_match_at_index(@index, method(:matched_candidates), specifiers, self.class.all_dom_attr_aliases)
|
625
|
-
when
|
238
|
+
when nil
|
239
|
+
assert_container_exists
|
626
240
|
unless @what.nil?
|
627
|
-
raise ArgumentError, "'what' was specified, but
|
628
|
-
end
|
629
|
-
unless @index
|
630
|
-
raise ArgumentError, "'how' was given as :index but no index was given"
|
241
|
+
raise ArgumentError, "'what' was specified, but 'how' was not given (is nil)"
|
631
242
|
end
|
632
243
|
candidate_match_at_index(@index, method(:matched_candidates), self.class.specifiers, self.class.all_dom_attr_aliases)
|
633
244
|
when :custom
|
245
|
+
assert_container_exists
|
634
246
|
# this allows a proc to be given as 'what', which is called yielding candidates, each being
|
635
247
|
# an instanted Element of this class. this might seem a bit odd - instantiating a bunch
|
636
248
|
# of elements in order to figure out which element_object to use in locating this one.
|
@@ -638,29 +250,22 @@ module Vapir
|
|
638
250
|
# the Elements that are yielded are instantiated by :element object which cannot be
|
639
251
|
# relocated.
|
640
252
|
#
|
641
|
-
#
|
642
|
-
#
|
643
|
-
# found_div=frame.divs.detect{|div| weird_criteria_for(div) }
|
644
|
-
# which can't be relocated - since element collections use :element object - you'd do
|
645
|
-
# found_div=frame.div(:custom, proc{|div| weird_criteria_for(div) })
|
646
|
-
# this way, found_div can be relocated. yay!
|
253
|
+
# this integrates with ElementCollection, where Enumerable methods #detect,
|
254
|
+
# #select, and #reject are overridden to use it.
|
647
255
|
#
|
648
256
|
# the proc should return true (that is, not false or nil) when it likes the given Element -
|
649
257
|
# when it matches what it expects of this Element.
|
650
258
|
candidate_match_at_index(@index, method(:matched_candidates), self.class.specifiers, self.class.all_dom_attr_aliases) do |candidate|
|
651
|
-
what.call(self.class.new(:element_object, candidate, @
|
259
|
+
what.call(self.class.new(:element_object, candidate, @container.extra_for_contained))
|
652
260
|
end
|
653
261
|
else
|
654
262
|
raise Vapir::Exception::MissingWayOfFindingObjectException, "Unknown 'how' given: #{@how.inspect} (#{@how.class}). 'what' was #{@what.inspect} (#{@what.class})"
|
655
263
|
end
|
656
264
|
end
|
657
|
-
|
658
|
-
@updated_at=Time.now
|
659
|
-
end
|
660
|
-
@element_object
|
265
|
+
@element_object=new_element_object
|
661
266
|
end
|
662
|
-
def locate!
|
663
|
-
locate
|
267
|
+
def locate!
|
268
|
+
locate || begin
|
664
269
|
klass=self.is_a?(Frame) ? Vapir::Exception::UnknownFrameException : Vapir::Exception::UnknownObjectException
|
665
270
|
message="Unable to locate #{self.class}, using #{@how}"+(@what ? ": "+@what.inspect : '')+(@index ? ", index #{@index}" : "")
|
666
271
|
message+="\non container: #{@container.inspect}" if @container
|
@@ -671,7 +276,7 @@ module Vapir
|
|
671
276
|
public
|
672
277
|
# Returns whether this element actually exists.
|
673
278
|
def exists?
|
674
|
-
handling_existence_failure(:handle => proc { return false }) do
|
279
|
+
handling_existence_failure(:handle => proc { return false }, :assert_exists => false) do
|
675
280
|
return !!locate
|
676
281
|
end
|
677
282
|
end
|
@@ -702,45 +307,40 @@ module Vapir
|
|
702
307
|
#
|
703
308
|
# For example, if we have a table, get its first element, and call #to_factory on it:
|
704
309
|
#
|
705
|
-
#
|
706
|
-
#
|
707
|
-
#
|
708
|
-
#
|
709
|
-
#
|
710
|
-
#
|
310
|
+
# a_table=browser.tables.first
|
311
|
+
# => #<Vapir::IE::Table:0x071bc70c index=:first tagName="TABLE">
|
312
|
+
# a_element=a_table.elements.first
|
313
|
+
# => #<Vapir::IE::Element:0x071b856c index=:first tagName="TBODY" id="">
|
314
|
+
# a_element.to_factory
|
315
|
+
# => #<Vapir::IE::TableBody:0x071af78c index=:first tagName="TBODY" id="">
|
711
316
|
#
|
712
317
|
# we get back a Vapir::TableBody.
|
713
|
-
def
|
318
|
+
def to_subtype
|
714
319
|
self.class.factory(element_object, @extra, @how, @what)
|
715
320
|
end
|
321
|
+
alias to_factory to_subtype
|
716
322
|
|
717
323
|
# takes a block. sets highlight on this element; calls the block; clears the highlight.
|
718
324
|
# the clear is in an ensure block so that you can call return from the given block.
|
719
|
-
#
|
325
|
+
#
|
326
|
+
# takes an options hash; every argument is ignored except :highlight, which defaults to true;
|
327
|
+
# if set to false then the highlighting won't actually happen, the block will just be called
|
328
|
+
# and its value returned.
|
720
329
|
#
|
721
330
|
# also, you can nest these safely; it checks if you're already highlighting before trying
|
722
331
|
# to set and subsequently clear the highlight.
|
723
332
|
#
|
724
|
-
# the block is called within an assert_exists block, so
|
725
|
-
#
|
333
|
+
# the block is called within an #assert_exists block, so methods that highlight don't need to
|
334
|
+
# also check existence as that'd be redundant.
|
726
335
|
def with_highlight(options={})
|
727
|
-
highlight_option_keys=[:color]
|
728
|
-
#options=handle_options(options, {:highlight => true}, highlight_option_keys)
|
729
|
-
options={:highlight => true}.merge(options)
|
730
|
-
highlight_options=options.reject{|(k,v)| !highlight_option_keys.include?(k) }
|
731
336
|
assert_exists do
|
732
|
-
|
733
|
-
if
|
734
|
-
|
735
|
-
end
|
736
|
-
@highlighting=true
|
737
|
-
begin
|
738
|
-
result=yield
|
337
|
+
# yeah, this line is an unreadable mess, but I have to skip over it so many times debugging that it's worth just sticking it on one line
|
338
|
+
(options={:highlight => true}.merge(options)); (was_highlighting=@highlighting); (set_highlight(options) if !@highlighting && options[:highlight]); (@highlighting=true)
|
339
|
+
begin; result=yield
|
739
340
|
ensure
|
740
341
|
@highlighting=was_highlighting
|
741
342
|
if !@highlighting && options[:highlight]
|
742
343
|
handling_existence_failure do
|
743
|
-
assert_exists :force => true
|
744
344
|
clear_highlight(options)
|
745
345
|
end
|
746
346
|
end
|
@@ -750,9 +350,6 @@ module Vapir
|
|
750
350
|
end
|
751
351
|
|
752
352
|
private
|
753
|
-
# The default color for highlighting objects as they are accessed.
|
754
|
-
DEFAULT_HIGHLIGHT_COLOR = "yellow"
|
755
|
-
|
756
353
|
# Sets or clears the colored highlighting on the currently active element.
|
757
354
|
# set_or_clear - should be
|
758
355
|
# :set - To set highlight
|
@@ -770,8 +367,7 @@ module Vapir
|
|
770
367
|
end
|
771
368
|
|
772
369
|
def set_highlight_color(options={})
|
773
|
-
|
774
|
-
options={:color => DEFAULT_HIGHLIGHT_COLOR}.merge(options)
|
370
|
+
options={:color => config.highlight_color}.merge(options)
|
775
371
|
assert_exists do
|
776
372
|
@original_color=element_object.style.backgroundColor
|
777
373
|
element_object.style.backgroundColor=options[:color]
|
@@ -814,7 +410,9 @@ module Vapir
|
|
814
410
|
def flash(options={})
|
815
411
|
if options.is_a?(Fixnum)
|
816
412
|
options={:count => options}
|
817
|
-
|
413
|
+
if config.warn_deprecated
|
414
|
+
Kernel.warn_with_caller "DEPRECATION WARNING: #{self.class.name}\#flash takes an options hash - passing a number is deprecated. Please use #{self.class.name}\#flash(:count => #{options[:count]})"
|
415
|
+
end
|
818
416
|
end
|
819
417
|
options={:count => 10, :sleep => 0.05}.merge(options)
|
820
418
|
#options=handle_options(options, {:count => 10, :sleep => 0.05}, [:color])
|
@@ -879,12 +477,6 @@ module Vapir
|
|
879
477
|
end
|
880
478
|
return true
|
881
479
|
end
|
882
|
-
private
|
883
|
-
# this is defined on each class to reflect the browser's particular implementation.
|
884
|
-
def element_object_style(element_object, document_object)
|
885
|
-
self.class.element_object_style(element_object, document_object)
|
886
|
-
end
|
887
|
-
public
|
888
480
|
|
889
481
|
# returns an array of all text nodes below this element in the DOM heirarchy
|
890
482
|
def text_nodes
|
@@ -906,72 +498,6 @@ module Vapir
|
|
906
498
|
recurse_text_nodes.call(recurse_text_nodes, element_object)
|
907
499
|
end
|
908
500
|
end
|
909
|
-
# returns an array of text nodes below this element in the DOM heirarchy which are visible -
|
910
|
-
# that is, their parent element is visible.
|
911
|
-
def visible_text_nodes
|
912
|
-
# TODO: needs tests
|
913
|
-
assert_exists do
|
914
|
-
# define a nice recursive function to iterate down through the children
|
915
|
-
recurse_text_nodes=proc do |rproc, e_obj, parent_visibility|
|
916
|
-
case e_obj.nodeType
|
917
|
-
when 1 # TODO: name a constant ELEMENT_NODE, rather than magic number
|
918
|
-
style=element_object_style(e_obj, document_object)
|
919
|
-
our_visibility = style && (visibility=style.invoke('visibility'))
|
920
|
-
unless our_visibility && ['hidden', 'collapse', 'visible'].include?(our_visibility=our_visibility.strip.downcase)
|
921
|
-
our_visibility = parent_visibility
|
922
|
-
end
|
923
|
-
if (display=style.invoke('display')) && display.strip.downcase=='none'
|
924
|
-
[]
|
925
|
-
else
|
926
|
-
object_collection_to_enumerable(e_obj.childNodes).inject([]) do |result, c_obj|
|
927
|
-
result + rproc.call(rproc, c_obj, our_visibility)
|
928
|
-
end
|
929
|
-
end
|
930
|
-
when 3 # TODO: name a constant TEXT_NODE, rather than magic number
|
931
|
-
if ['hidden','collapse'].include?(parent_visibility)
|
932
|
-
[]
|
933
|
-
else
|
934
|
-
[e_obj.data]
|
935
|
-
end
|
936
|
-
else
|
937
|
-
#Kernel.warn("ignoring node of type #{e_obj.nodeType}")
|
938
|
-
[]
|
939
|
-
end
|
940
|
-
end
|
941
|
-
|
942
|
-
# determine the current visibility and display. TODO: this is copied/adapted from #visible?; should DRY
|
943
|
-
element_to_check=element_object
|
944
|
-
real_visibility=nil
|
945
|
-
while element_to_check #&& !element_to_check.instanceof(nsIDOMDocument)
|
946
|
-
if (style=element_object_style(element_to_check, document_object))
|
947
|
-
# only pay attention to the innermost definition that really defines visibility - one of 'hidden', 'collapse' (only for table elements),
|
948
|
-
# or 'visible'. ignore 'inherit'; keep looking upward.
|
949
|
-
# this makes it so that if we encounter an explicit 'visible', we don't pay attention to any 'hidden' further up.
|
950
|
-
# this style is inherited - may be pointless for firefox, but IE uses the 'inherited' value. not sure if/when ff does.
|
951
|
-
if real_visibility==nil && (visibility=style.invoke('visibility'))
|
952
|
-
visibility=visibility.strip.downcase
|
953
|
-
if ['hidden', 'collapse', 'visible'].include?(visibility)
|
954
|
-
real_visibility=visibility
|
955
|
-
end
|
956
|
-
end
|
957
|
-
# check for display property. this is not inherited, and a parent with display of 'none' overrides an immediate visibility='visible'
|
958
|
-
display=style.invoke('display')
|
959
|
-
if display && (display=display.strip.downcase)=='none'
|
960
|
-
# if display is none, then this element is not visible, and thus has no visible text nodes underneath.
|
961
|
-
return []
|
962
|
-
end
|
963
|
-
end
|
964
|
-
element_to_check=element_to_check.parentNode
|
965
|
-
end
|
966
|
-
recurse_text_nodes.call(recurse_text_nodes, element_object, real_visibility)
|
967
|
-
end
|
968
|
-
end
|
969
|
-
# returns an visible text inside this element by concatenating text nodes below this element in the DOM heirarchy which are visible.
|
970
|
-
def visible_text
|
971
|
-
# TODO: needs tests
|
972
|
-
visible_text_nodes.join('')
|
973
|
-
end
|
974
|
-
|
975
501
|
# returns a Vector with two elements, the x,y
|
976
502
|
# coordinates of this element (its top left point)
|
977
503
|
# from the top left edge of the window
|
@@ -1117,22 +643,6 @@ module Vapir
|
|
1117
643
|
end
|
1118
644
|
end
|
1119
645
|
|
1120
|
-
# for a common module, such as a TextField, returns an elements-specific class (such as
|
1121
|
-
# Firefox::TextField) that inherits from the base_element_class of self. That is, this returns
|
1122
|
-
# a sibling class, as it were, of whatever class inheriting from Element is instantiated.
|
1123
|
-
def element_class_for(common_module)
|
1124
|
-
element_class=nil
|
1125
|
-
ObjectSpace.each_object(Class) do |klass|
|
1126
|
-
if klass < common_module && klass < base_element_class
|
1127
|
-
element_class= klass
|
1128
|
-
end
|
1129
|
-
end
|
1130
|
-
unless element_class
|
1131
|
-
raise RuntimeError, "No class found that inherits from both #{common_module} and #{base_element_class}"
|
1132
|
-
end
|
1133
|
-
element_class
|
1134
|
-
end
|
1135
|
-
|
1136
646
|
module_function
|
1137
647
|
def object_collection_to_enumerable(object)
|
1138
648
|
if object.is_a?(Enumerable)
|