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.
@@ -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)