vapir-common 1.7.0.rc1

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.
@@ -0,0 +1,2 @@
1
+ require 'vapir-common/element'
2
+ require 'vapir-common/elements/elements'
@@ -0,0 +1,56 @@
1
+ module Vapir
2
+ module Exception
3
+
4
+ # Root class for all Vapir Exceptions
5
+ class VapirException < RuntimeError
6
+ def initialize(message="")
7
+ super(message)
8
+ end
9
+ end
10
+
11
+ class NoBrowserException < VapirException; end
12
+
13
+ # This exception is thrown if an attempt is made to access an object that doesn't exist
14
+ class UnknownObjectException < VapirException; end
15
+
16
+ # This exception is raised if attempting to relocate an Element that was located in a way that does not support relocating
17
+ class UnableToRelocateException < UnknownObjectException; end
18
+
19
+ # This exception is thrown if an attempt is made to access a frame that cannot be found
20
+ class UnknownFrameException< UnknownObjectException; end
21
+
22
+ # This exception is thrown if an attempt is made to access an object that is in a disabled state
23
+ class ObjectDisabledException < VapirException; end
24
+
25
+ # This exception is thrown if an attempt is made to access an object that is in a read only state
26
+ class ObjectReadOnlyException < VapirException; end
27
+
28
+ # This exception is thrown if an attempt is made to access an object when the specified value cannot be found
29
+ class NoValueFoundException < VapirException; end
30
+
31
+ # This exception gets raised if part of finding an object is missing
32
+ class MissingWayOfFindingObjectException < VapirException; end
33
+
34
+ class WindowException < VapirException; end
35
+ # This exception is thrown if the window cannot be found
36
+ class NoMatchingWindowFoundException < WindowException; end
37
+ class WindowFailedToCloseException < WindowException; end
38
+
39
+ # This exception is thrown if an attemp is made to acces the status bar of the browser when it doesnt exist
40
+ class NoStatusBarException < VapirException; end
41
+
42
+ # This exception is thrown if an http error, such as a 404, 500 etc is encountered while navigating
43
+ class NavigationException < VapirException; end
44
+
45
+ # This exception is raised if an element does not have a container defined, and needs one.
46
+ class MissingContainerException < VapirException; end
47
+
48
+ # This exception is raised if a timeout is exceeded
49
+ class TimeOutException < VapirException
50
+ def initialize(duration, timeout)
51
+ @duration, @timeout = duration, timeout
52
+ end
53
+ attr_reader :duration, :timeout
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,15 @@
1
+ # takes given options and default options, and optionally a list of additional allowed keys not specified in default options
2
+ # (this is useful when you want to pass options along to another function but don't want to specify a default that will
3
+ # clobber that function's default)
4
+ # raises ArgumentError if the given options have an invalid key (defined as one not
5
+ # specified in default options or other_allowed_keys), and sets default values in given options where nothing is set.
6
+ def handle_options(given_options, default_options, other_allowed_keys=[])
7
+ given_options=given_options.dup
8
+ unless (unknown_keys=(given_options.keys-default_options.keys-other_allowed_keys)).empty?
9
+ raise ArgumentError, "Unknown options: #{(given_options.keys-default_options.keys).map(&:inspect).join(', ')}. Known options are #{(default_options.keys+other_allowed_keys).map(&:inspect).join(', ')}"
10
+ end
11
+ (default_options.keys-given_options.keys).each do |key|
12
+ given_options[key]=default_options[key]
13
+ end
14
+ given_options
15
+ end
@@ -0,0 +1,27 @@
1
+ module Vapir
2
+ module ModalDialog
3
+ DEFAULT_TIMEOUT=4
4
+ def default_initialize(browser, options={})
5
+ options=handle_options(options, :timeout => ModalDialog::DEFAULT_TIMEOUT, :error => true)
6
+ @browser=browser
7
+ ::Waiter.try_for(options[:timeout], :exception => (options[:error] && Vapir::Exception::NoMatchingWindowFoundException.new("No popup was found on the browser"))) do
8
+ locate
9
+ end
10
+ end
11
+ alias initialize default_initialize
12
+
13
+ def locate!(options={})
14
+ exists? || raise(Vapir::Exception::NoMatchingWindowFoundException, "The modal dialog seems to have stopped existing.")
15
+ end
16
+ alias assert_exists locate!
17
+
18
+ attr_reader :browser
19
+ attr_reader :modal_window
20
+
21
+ [:locate, :exists?, :text, :set_text_field, :click_button, :close, :document].each do |virtual_method|
22
+ define_method(virtual_method) do
23
+ raise NotImplementedError, "The method \##{virtual method} should be defined on the class #{self.class}"
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,49 @@
1
+ require 'user-choices'
2
+
3
+ module Vapir
4
+ @@options_file = nil
5
+ @@options = nil
6
+ class << self
7
+ # Specify the location of a yaml file containing Vapir options. Must be
8
+ # specified before the options are parsed.
9
+ def options_file= file
10
+ @@options_file = file
11
+ end
12
+ def options_file
13
+ @@options_file
14
+ end
15
+ def options= x
16
+ @@options = x
17
+ end
18
+ # Return the Vapir options, as a hash. If they haven't been parsed yet,
19
+ # they will be now.
20
+ def options
21
+ @@options ||= Vapir::Options.new.execute
22
+ end
23
+ end
24
+
25
+ class Options < UserChoices::Command
26
+ include UserChoices
27
+ def add_sources builder
28
+ builder.add_source EnvironmentSource, :with_prefix, 'watir_'
29
+ if Vapir.options_file
30
+ builder.add_source YamlConfigFileSource, :from_complete_path,
31
+ Vapir.options_file
32
+ end
33
+ end
34
+ def add_choices builder
35
+ builder.add_choice :browser,
36
+ :type => Vapir::Browser.browser_names,
37
+ :default => Vapir::Browser.default
38
+ builder.add_choice :speed,
39
+ :type => ['slow', 'fast', 'zippy'],
40
+ :default => 'fast'
41
+ builder.add_choice :visible,
42
+ :type => :boolean
43
+ end
44
+ def execute
45
+ @user_choices[:speed] = @user_choices[:speed].to_sym
46
+ @user_choices
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,322 @@
1
+ module Vapir
2
+ # This module is included in ElementCollection and Element. it
3
+ # it expects the includer to have defined:
4
+ # - @container
5
+ # - @extra
6
+ module ElementObjectCandidates
7
+ private
8
+
9
+ # raises an error unless @container is set
10
+ def assert_container
11
+ unless @container
12
+ raise Vapir::Exception::MissingContainerException, "No container is defined for this #{self.class.inspect}"
13
+ end
14
+ end
15
+
16
+ # raises an error unless @container is set and exists
17
+ def assert_container_exists
18
+ assert_container
19
+ @container.locate!
20
+ end
21
+
22
+ # this returns an Enumerable of element objects that _may_ (not necessarily do) match the
23
+ # the given specifier. sometimes specifier is completely ignored. behavor depends on
24
+ # @extra[:candidates]. when the value of @extra[:candidates] is:
25
+ # - nil (default), this uses #get_elements_by_specifiers which uses one of getElementById,
26
+ # getElementsByTagName, getElementsByName, getElementsByClassName.
27
+ # - a symbol - this is assumed to be a method of the containing_object (@container.containing_object).
28
+ # this is called, made to be an enumerable, and returned.
29
+ # - a proc - this is yielded @container and the proc is trusted to return an enumerable
30
+ # of whatever candidate element objects are desired.
31
+ # this is used by #locate in Element, and by ElementCollection.
32
+ def element_object_candidates(specifiers, aliases)
33
+ @container.assert_exists(:force => true) do
34
+ case @extra[:candidates]
35
+ when nil
36
+ get_elements_by_specifiers(@container, specifiers, aliases, respond_to?(:index_is_first) ? index_is_first : false)
37
+ when Symbol
38
+ Vapir::Element.object_collection_to_enumerable(@container.containing_object.send(@extra[:candidates]))
39
+ when Proc
40
+ @extra[:candidates].call(@container)
41
+ else
42
+ raise Vapir::Exception::MissingWayOfFindingObjectException, "Unknown method of specifying candidates: #{@extra[:candidates].inspect} (#{@extra[:candidates].class})"
43
+ end
44
+ end
45
+ end
46
+ # returns an enumerable of
47
+ def matched_candidates(specifiers, aliases, &block)
48
+ match_candidates(element_object_candidates(specifiers, aliases), specifiers, aliases, &block)
49
+ end
50
+
51
+ # this is a list of what users can specify (there are additional possible hows that may be given
52
+ # to the Element constructor, but not generally for use by users, such as :element_object or :label
53
+ HowList=[:attributes, :xpath, :custom, :element_object, :label]
54
+
55
+ # returns an Enumerable of element objects that _may_ match (note, not do match, necessarily)
56
+ # the given specifiers on the given container. these are obtained from the container's containing_object
57
+ # using one of getElementById, getElementsByName, getElementsByClassName, or getElementsByTagName.
58
+ def get_elements_by_specifiers(container, specifiers, aliases, want_first=false)
59
+ if container.nil?
60
+ raise ArgumentError, "no container specified!"
61
+ end
62
+ unless specifiers.is_a?(Enumerable) && specifiers.all?{|spec| spec.is_a?(Hash)}
63
+ raise ArgumentError, "specifiers should be a list of Hashes!"
64
+ end
65
+ attributes_in_specifiers=proc do |attr|
66
+ specifiers.inject([]) do |arr, spec|
67
+ spec.each_pair do |spec_attr, spec_val|
68
+ if (aliases[attr] || []).include?(spec_attr) && !arr.include?(spec_val)
69
+ arr << spec_val
70
+ end
71
+ end
72
+ arr
73
+ end
74
+ end
75
+ ids=attributes_in_specifiers.call(:id)
76
+ tags=attributes_in_specifiers.call(:tagName)
77
+ names=attributes_in_specifiers.call(:name)
78
+ classNames=attributes_in_specifiers.call(:className)
79
+
80
+ # we can only use getElementById if:
81
+ # - id is a string, as getElementById doesn't do regexp
82
+ # - index is 1 or nil; otherwise even though it's not really valid, other identical ids won't get searched
83
+ # - id is the _only_ specifier, otherwise if the same id is used multiple times but the first one doesn't match
84
+ # the given specifiers, the element won't be found
85
+ # - container has getElementById defined (that is, it's a Browser or a Frame), otherwise if we called
86
+ # container.containing_object.getElementById we wouldn't know if what's returned is below container in the DOM heirarchy or not
87
+ # since this is almost always called with specifiers including tag name, input type, etc, getElementById is basically never used.
88
+ # TODO: have a user-settable flag somewhere that specifies that IDs are unique in pages they use. then getElementById
89
+ # could be used a lot more than it is limited to here, and stuff would be faster.
90
+ can_use_getElementById= ids.size==1 &&
91
+ ids.first.is_a?(String) &&
92
+ want_first &&
93
+ !specifiers.any?{|s| s.keys.any?{|k|k!=:id}} &&
94
+ container.containing_object.object_respond_to?(:getElementById)
95
+
96
+ # we can only use getElementsByName if:
97
+ # - name is a string; getElementsByName doesn't do regexp
98
+ # - we're only looking for elements that have a valid name attribute. those are BUTTON TEXTAREA APPLET SELECT FORM FRAME IFRAME IMG A INPUT OBJECT MAP PARAM META
99
+ # getElementsByTagName doesn't return elements that have a name attribute if name isn't supported on that type of element;
100
+ # it's treated as expando. see http://jszen.blogspot.com/2004/07/whats-in-name.html
101
+ # and http://www.w3.org/TR/html401/index/attributes.html
102
+ # this only applies to IE, and firefox could use getElementsByName more liberally, but not going to bother detecting that here.
103
+ #
104
+ # TODO/FIX: account for other bugginess in IE's getElementById / getElementsByName ?
105
+ # - http://www.romantika.name/v2/javascripts-getelementsbyname-ie-vs-firefox/
106
+ # - http://webbugtrack.blogspot.com/2007/08/bug-411-getelementsbyname-doesnt-work.html
107
+ # - http://webbugtrack.blogspot.com/2007/08/bug-152-getelementbyid-returns.html
108
+ can_use_getElementsByName=names.size==1 &&
109
+ names.first.is_a?(String) &&
110
+ container.containing_object.object_respond_to?(:getElementsByName) &&
111
+ specifiers.all?{|specifier| specifier[:tagName].is_a?(String) && %w(BUTTON TEXTAREA APPLET SELECT FORM FRAME IFRAME IMG A INPUT OBJECT MAP PARAM META).include?(specifier[:tagName].upcase) }
112
+ if can_use_getElementById
113
+ candidates= if by_id=container.containing_object.getElementById(ids.first)
114
+ [by_id]
115
+ else
116
+ []
117
+ end
118
+ elsif can_use_getElementsByName
119
+ candidates=container.containing_object.getElementsByName(names.first)#.to_array
120
+ elsif classNames.size==1 && classNames.first.is_a?(String) && container.containing_object.object_respond_to?(:getElementsByClassName)
121
+ candidates=container.containing_object.getElementsByClassName(classNames.first)
122
+ elsif tags.size==1 && tags.first.is_a?(String)
123
+ candidates=container.containing_object.getElementsByTagName(tags.first)
124
+ else # would be nice to use getElementsByTagName for each tag name, but we can't because then we don't know the ordering for index
125
+ candidates=container.containing_object.getElementsByTagName('*')
126
+ end
127
+ # return:
128
+ if candidates.is_a?(Array)
129
+ candidates
130
+ elsif Object.const_defined?('JsshObject') && candidates.is_a?(JsshObject)
131
+ candidates.to_array
132
+ elsif Object.const_defined?('WIN32OLE') && candidates.is_a?(WIN32OLE)
133
+ candidates.send :extend, Enumerable
134
+ else
135
+ raise RuntimeError, "candidates ended up unexpectedly being #{candidates.inspect} (#{candidates.class}) - don't know what to do with this" # this shouldn't happen
136
+ end
137
+ end
138
+
139
+ module_function
140
+ def match_candidates(candidates, specifiers_list, aliases)
141
+ unless specifiers_list.is_a?(Enumerable) && specifiers_list.all?{|spec| spec.is_a?(Hash)}
142
+ raise ArgumentError, "specifiers_list should be a list of Hashes!"
143
+ end
144
+ if candidates.length != 0 && Object.const_defined?('JsshObject') && (candidates.is_a?(JsshObject) || candidates.all?{|c| c.is_a?(JsshObject)})
145
+ # optimize for JSSH by moving code to the other side of the socket, rather than talking across it a whole lot
146
+ # this javascript should be exactly the same as the ruby in the else (minus WIN32OLE optimization) -
147
+ # just written in javascript instead of ruby.
148
+ #
149
+ # Note that the else block works perfectly fine, but is much much slower due to the amount of
150
+ # socket activity.
151
+ jssh_socket= candidates.is_a?(JsshObject) ? candidates.jssh_socket : candidates.first.jssh_socket
152
+ match_candidates_js=JsshObject.new("
153
+ (function(candidates, specifiers_list, aliases)
154
+ { candidates=$A(candidates);
155
+ specifiers_list=$A(specifiers_list);
156
+ var matched_candidates=[];
157
+ var fuzzy_match=function(attr, what)
158
+ { if(typeof what=='string')
159
+ { if(typeof attr=='string')
160
+ { return attr.toLowerCase().strip()==what.toLowerCase().strip();
161
+ }
162
+ else
163
+ { return attr==what;
164
+ }
165
+ }
166
+ else if(typeof what=='number')
167
+ { return attr==what || attr==what.toString();
168
+ }
169
+ else
170
+ { if(typeof attr=='string')
171
+ { return attr.match(what);
172
+ }
173
+ else
174
+ { return attr==what;
175
+ }
176
+ }
177
+ };
178
+ candidates.each(function(candidate)
179
+ { var candidate_attributes=function(attr)
180
+ { var attrs=[];
181
+ if(candidate.hasAttribute && candidate.hasAttribute(attr))
182
+ { attrs.push(candidate.getAttributeNode(attr).value);
183
+ }
184
+ if(candidate[attr])
185
+ { attrs.push(candidate[attr]);
186
+ }
187
+ return $A(attrs);
188
+ };
189
+ var match=true;
190
+ match= match && candidate.nodeType==1;
191
+ match= match && specifiers_list.any(function(specifier)
192
+ { return $H(specifier).all(function(howwhat)
193
+ { how=howwhat.key;
194
+ what=howwhat.value;
195
+ if(how=='types')
196
+ { return what.any(function(type)
197
+ { return candidate_attributes('type').any(function(attr){ return fuzzy_match(attr, type); });
198
+ });
199
+ }
200
+ else
201
+ { var matched_aliases=$H(aliases).reject(function(dom_attr_alias_list)
202
+ { var alias_list=$A(dom_attr_alias_list.value);
203
+ return !alias_list.include(how);
204
+ }).pluck('key');
205
+ return $A([how].concat(matched_aliases)).any(function(how_alias)
206
+ { return candidate_attributes(how_alias).any(function(attr){ return fuzzy_match(attr, what); });
207
+ });
208
+ }
209
+ })
210
+ });
211
+ if(match)
212
+ { matched_candidates.push(candidate);
213
+ }
214
+ });
215
+ return matched_candidates;
216
+ })
217
+ ", jssh_socket, :debug_name => 'match_candidates_function')
218
+ matched_candidates=match_candidates_js.call(candidates, specifiers_list, aliases)
219
+ if block_given?
220
+ matched_candidates.to_array.each do |matched_candidate|
221
+ yield matched_candidate
222
+ end
223
+ end
224
+ return matched_candidates.to_array
225
+ else
226
+ # IF YOU CHANGE THIS CODE CHANGE THE CORRESPONDING JAVASCRIPT ABOVE TOO
227
+ matched_candidates=[]
228
+ candidates.each do |candidate|
229
+ candidate_attributes=proc do |attr|
230
+ attrs=[]
231
+ if Object.const_defined?('WIN32OLE') && candidate.is_a?(WIN32OLE)
232
+ # ie & WIN32OLE optimization: hasAttribute does not exist on IE, and also avoid respond_to? on WIN32OLE; it is slow.
233
+ begin
234
+ attr_node=candidate.getAttributeNode(attr.to_s)
235
+ attrs << attr_node.value if attr_node
236
+ rescue WIN32OLERuntimeError
237
+ end
238
+ begin
239
+ attrs << candidate.invoke(attr.to_s)
240
+ rescue WIN32OLERuntimeError
241
+ end
242
+ else
243
+ # this doesn't actually get called anymore, since there are optimizations for both IE and firefox.
244
+ # leaving it here anyway - maybe someday a different browser will have an object this code can use,
245
+ # or maybe someday IE or firefox or both will not need the optimizations above.
246
+ if candidate.object_respond_to?(:hasAttribute) && candidate.hasAttribute(attr)
247
+ attrs << candidate.getAttributeNode(attr.to_s).value
248
+ end
249
+ if candidate.object_respond_to?(attr)
250
+ attrs << candidate.invoke(attr.to_s)
251
+ end
252
+ end
253
+ attrs
254
+ end
255
+ match=true
256
+ if Object.const_defined?('WIN32OLE') && candidate.is_a?(WIN32OLE)
257
+ begin
258
+ match &&= candidate.nodeType==1
259
+ rescue WIN32OLERuntimeError
260
+ match=false
261
+ end
262
+ else
263
+ if candidate.object_respond_to?(:nodeType)
264
+ match &&= candidate.candidate.nodeType==1
265
+ else
266
+ match=false
267
+ end
268
+ end
269
+ match &&= specifiers_list.any? do |specifier|
270
+ specifier.all? do |(how, what)|
271
+ if how==:types
272
+ what.any? do |type|
273
+ candidate_attributes.call(:type).any?{|attr| Vapir::fuzzy_match(attr, type)}
274
+ end
275
+ else
276
+ matched_aliases = aliases.reject do |dom_attr, alias_list|
277
+ !alias_list.include?(how)
278
+ end.keys
279
+ (matched_aliases+[how]).any? do |how_alias|
280
+ candidate_attributes.call(how_alias).any?{|attr| Vapir::fuzzy_match(attr, what)}
281
+ end
282
+ end
283
+ end
284
+ end
285
+ if match
286
+ if block_given?
287
+ yield candidate
288
+ end
289
+ matched_candidates << candidate
290
+ end
291
+ end
292
+ return matched_candidates
293
+ end
294
+ end
295
+ end
296
+
297
+ # This is on the Vapir module itself because it's used in a number of other places, should be in a broad namespace.
298
+ module_function
299
+ def fuzzy_match(attr, what)
300
+ # IF YOU CHANGE THIS, CHANGE THE JAVASCRIPT REIMPLEMENTATION IN match_candidates
301
+ case what
302
+ when String, Symbol
303
+ case attr
304
+ when String, Symbol
305
+ attr.to_s.downcase.strip==what.to_s.downcase.strip
306
+ else
307
+ attr==what
308
+ end
309
+ when Regexp
310
+ case attr
311
+ when Regexp
312
+ attr==what
313
+ else
314
+ attr =~ what
315
+ end
316
+ when Numeric
317
+ attr==what || attr==what.to_s
318
+ else
319
+ attr==what
320
+ end
321
+ end
322
+ end
@@ -0,0 +1,89 @@
1
+ require 'test/unit'
2
+ require 'test/unit/assertions'
3
+
4
+ module Vapir
5
+ # Verification methods
6
+ module Assertions
7
+ include Test::Unit::Assertions
8
+
9
+ # Log a failure if the boolean is true. The message is the failure
10
+ # message logged.
11
+ # Whether true or false, the assertion count is incremented.
12
+ def verify boolean, message = 'verify failed.'
13
+ add_assertion
14
+ add_failure message.to_s, caller unless boolean
15
+ end
16
+
17
+ def verify_equal expected, actual, message=nil
18
+ full_message = build_message(message, <<EOT, expected, actual)
19
+ <?> expected but was
20
+ <?>.
21
+ EOT
22
+ verify(expected == actual, full_message)
23
+ end
24
+ def verify_match pattern, string, message=nil
25
+ pattern = case(pattern)
26
+ when String
27
+ Regexp.new(Regexp.escape(pattern))
28
+ else
29
+ pattern
30
+ end
31
+ full_message = build_message(message, "<?> expected to be =~\n<?>.", string, pattern)
32
+ verify(string =~ pattern, full_message)
33
+ end
34
+
35
+ end
36
+
37
+ class TestCase < Test::Unit::TestCase
38
+ include Vapir::Assertions
39
+ @@order = :sequentially
40
+ def initialize name
41
+ throw :invalid_test if name == :default_test && self.class == Vapir::TestCase
42
+ super
43
+ end
44
+ class << self
45
+ attr_accessor :test_methods, :order
46
+ def test_methods
47
+ @test_methods ||= []
48
+ end
49
+ def order
50
+ @order || @@order
51
+ end
52
+ def default_order= order
53
+ @@order = order
54
+ end
55
+ def sorted_test_methods
56
+ case order
57
+ when :alphabetically then test_methods.sort
58
+ when :sequentially then test_methods
59
+ when :reversed_sequentially then test_methods.reverse
60
+ when :reversed_alphabetically then test_methods.sort.reverse
61
+ else raise ArgumentError, "Execute option not supported: #{@order}"
62
+ end
63
+ end
64
+ def suite
65
+ suite = Test::Unit::TestSuite.new(name)
66
+ sorted_test_methods.each do |test|
67
+ catch :invalid_test do
68
+ suite << new(test)
69
+ end
70
+ end
71
+ if (suite.empty?)
72
+ catch :invalid_test do
73
+ suite << new(:default_test)
74
+ end
75
+ end
76
+ return suite
77
+ end
78
+ def method_added id
79
+ name = id.id2name
80
+ test_methods << name if name =~ /^test./
81
+ end
82
+ def execute order
83
+ @order = order
84
+ end
85
+ end
86
+ public :add_assertion
87
+ end
88
+
89
+ end