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.
@@ -34,14 +34,14 @@ module Vapir
34
34
  end
35
35
  end
36
36
  extra=extra_for_contained.merge(:index => index)
37
- case other[:locate]
38
- when :assert, true, false
39
- element=klass.new(how, what, extra.merge(:locate => other[:locate]))
40
- when :nil_unless_exists
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
- raise ArgumentError, "Unrecognized value given for :locate: #{other[:locate].inspect} (#{other[:locate].class})"
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
- raise Vapir::Exception::MissingWayOfFindingObjectException, "no first argument (how) was given!"
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 # this is different because the index number doesn't go in the 'what'
78
- how=first
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
- was_asserting_exists=@asserting_exists
102
- if (!@asserting_exists || options[:force])
103
- locate!
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 option, :handle, which indicates how the method should handle an
119
- # encountered exception.
120
- # :handle may be one of:
121
- # * :ignore (default) - the exception is ignored and nil is returned.
122
- # * :raise - the exception is raised (same as if this method weren't used at all).
123
- # * :return - returns the exception which was raised.
124
- # * Proc, Method - the proc or method is called with the exception as an argument.
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, :handle => :ignore)
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 default_extra_for_contained
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 default_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.to_factory
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
@@ -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
- inspect_this :how
84
+ inspect_this_if :how
460
85
  inspect_this_if(:what) do |element|
461
- ![:element_object, :index].include?(element.how) # if how==:element_object, don't show the element object in inspect. if how==:index, what is nil.
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
- # takes options hash. currently the only option is
567
- # - :relocate => nil, :recursive, true, false
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
- element_object_existed=!!@element_object
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=@what # this is needed for checking its existence
593
- if options[:relocate] && !element_object_exists?
594
- raise Vapir::Exception::UnableToRelocateException, "This #{self.class.name} was specified using #{how.inspect} and cannot be relocated."
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
- assert_container
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
- unless what.is_a?(Label)
613
- raise "how=:label specified on this #{self.class}, but 'what' is not a Label! what=#{what.inspect} (#{what.class})"
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
- assert_container
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 :index
238
+ when nil
239
+ assert_container_exists
626
240
  unless @what.nil?
627
- raise ArgumentError, "'what' was specified, but when 'how'=:index, no 'what' is used (just extra[:index])"
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
- # the alternative to this would be for the calling code to loop over the element collection
642
- # for this class on the container - that is, instead of:
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, @extra))
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
- if !element_object_existed && @element_object
658
- @updated_at=Time.now
659
- end
660
- @element_object
265
+ @element_object=new_element_object
661
266
  end
662
- def locate!(options={})
663
- locate(options) || begin
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
- # a_table=browser.tables.first
706
- # => #<Vapir::IE::Table:0x071bc70c how=:index index=:first tagName="TABLE">
707
- # a_element=a_table.elements.first
708
- # => #<Vapir::IE::Element:0x071b856c how=:index index=:first tagName="TBODY" id="">
709
- # a_element.to_factory
710
- # => #<Vapir::IE::TableBody:0x071af78c how=:index index=:first tagName="TBODY" id="">
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 to_factory
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
- # doesn't actually perform the highlighting if argument do_highlight is false.
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 for methods that highlight, the
725
- # assert_exists can generally be omitted from there.
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
- was_highlighting=@highlighting
733
- if (!@highlighting && options[:highlight])
734
- set_highlight(highlight_options)
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
- #options=handle_options(options, :color => DEFAULT_HIGHLIGHT_COLOR)
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
- Kernel.warn "DEPRECATION WARNING: #{self.class.name}\#flash takes an options hash - passing a number is deprecated. Please use #{self.class.name}\#flash(:count => #{options[:count]})\n(called from #{caller.map{|c|"\n"+c}})"
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)