vapir-common 1.7.2 → 1.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|