vapir-ie 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.
data/README.txt ADDED
File without changes
data/lib/vapir-ie.rb ADDED
@@ -0,0 +1,44 @@
1
+ require 'vapir-common'
2
+
3
+ # create stub class since everything is defined in Vapir::IE namespace - this needs to be defined before the real class.
4
+ require 'vapir-common/browser'
5
+ module Vapir
6
+ IE= Class.new(Vapir::Browser)
7
+ end
8
+
9
+ # these switches need to be deleted from ARGV to enable the Test::Unit
10
+ # functionality that grabs
11
+ # the remaining ARGV as a filter on what tests to run.
12
+ # Note: this means that watir must be require'd BEFORE test/unit.
13
+ # (Alternatively, you could require test/unit first and then put the Vapir::IE
14
+ # arguments after the '--'.)
15
+
16
+ # Make Internet Explorer invisible. -b stands for background
17
+ $HIDE_IE ||= ARGV.delete('-b')
18
+
19
+ # Run fast
20
+ $FAST_SPEED = ARGV.delete('-f')
21
+
22
+ # Eat the -s command line switch (deprecated)
23
+ ARGV.delete('-s')
24
+
25
+ require 'vapir-ie/ie-class'
26
+ require 'vapir-ie/elements'
27
+ require 'vapir-ie/version'
28
+
29
+ require 'vapir-common/waiter'
30
+
31
+ module Vapir
32
+ include Vapir::Exception
33
+
34
+ # Directory containing the watir.rb file
35
+ @@dir = File.expand_path(File.dirname(__FILE__))
36
+
37
+ ATTACHER = Waiter.new
38
+ # Like regular Ruby "until", except that a TimeOutException is raised
39
+ # if the timeout is exceeded. Timeout is IE.attach_timeout.
40
+ def self.until_with_timeout # block
41
+ ATTACHER.timeout = IE.attach_timeout
42
+ ATTACHER.wait_until { yield }
43
+ end
44
+ end
Binary file
Binary file
@@ -0,0 +1,13 @@
1
+ module Vapir
2
+ AutoItDLL=File.join(File.expand_path(File.dirname(__FILE__)),'AutoItX3.dll')
3
+ def self.autoit
4
+ @@autoit||= begin
5
+ begin
6
+ WIN32OLE.new('AutoItX3.Control')
7
+ rescue WIN32OLERuntimeError
8
+ system("regsvr32.exe /s \"#{AutoItDLL.gsub('/', '\\')}\"")
9
+ WIN32OLE.new('AutoItX3.Control')
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,32 @@
1
+ require 'vapir-ie/ie-class'
2
+
3
+ module Vapir
4
+ class IE
5
+ # close all ie browser windows
6
+ def self.close_all
7
+ close_all_but nil
8
+ end
9
+ # find other ie browser windows and close them
10
+ def close_others
11
+ IE.close_all_but self
12
+ end
13
+ private
14
+ def self.close_all_but(except=nil)
15
+ Vapir::IE.each do |ie|
16
+ ie.close_modal
17
+ ie.close unless except and except.hwnd == ie.hwnd
18
+ end
19
+ sleep 1.0 # replace with polling for window count to be zero?
20
+ end
21
+ public
22
+ # close modal dialog. unlike IE#modal_dialog.close, does not wait for dialog
23
+ # to appear and does not raise exception if no window is found.
24
+ # returns true if modal was found and close, otherwise false
25
+ def close_modal
26
+ modal_dialog=modal_dialog(:timeout => 0, :error => false)
27
+ if modal_dialog && modal_dialog.exists?
28
+ modal_dialog.close
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,51 @@
1
+ require 'vapir-common/container'
2
+
3
+ module Vapir
4
+ # This module contains the factory methods that are used to access most html objects
5
+ #
6
+ # For example, to access a button on a web page that has the following html
7
+ # <input type = button name= 'b1' value='Click Me' onClick='javascript:doSomething()'>
8
+ #
9
+ # the following watir code could be used
10
+ #
11
+ # ie.button(:name, 'b1').click
12
+ #
13
+ # or
14
+ #
15
+ # ie.button(:value, 'Click Me').to_s
16
+ #
17
+ # there are many methods available to the Button object
18
+ #
19
+ # Is includable for classes that have @container, document and ole_inner_elements
20
+ module IE::Container
21
+ include Vapir::Container
22
+ include Vapir::Exception
23
+
24
+ # Note: @container is the container of this object, i.e. the container
25
+ # of this container.
26
+ # In other words, for ie.table().this_thing().text_field().set,
27
+ # container of this_thing is the table.
28
+
29
+ # This is used to change the typing speed when entering text on a page.
30
+ attr_accessor :typingspeed
31
+ attr_accessor :type_keys
32
+
33
+ def copy_test_config(container) # only used by form and frame
34
+ @typingspeed = container.typingspeed
35
+ @type_keys = container.type_keys
36
+ end
37
+ private :copy_test_config
38
+
39
+ # Write the specified string to the log.
40
+ def log(what)
41
+ @container.logger.debug(what) if @logger
42
+ end
43
+
44
+ def set_container container
45
+ @container = container
46
+ @page_container = container.page_container
47
+ end
48
+
49
+ private
50
+ end # module
51
+ end
@@ -0,0 +1,376 @@
1
+ require 'vapir-common/element'
2
+ require 'vapir-ie/container'
3
+
4
+ module Vapir
5
+ # Base class for html elements.
6
+ # This is not a class that users would normally access.
7
+ class IE::Element # Wrapper
8
+ include IE::Container # presumes @container is defined
9
+ include Vapir::Element
10
+ include Vapir::Exception
11
+
12
+ alias containing_object element_object
13
+ alias ole_object element_object # TODO: deprecate this?
14
+
15
+ dom_attr :currentStyle => [:current_style_object, :computed_style_object]
16
+ alias_deprecated :currentStyle, :current_style_object
17
+ dom_attr :disabled => [:disabled, :disabled?] # this applies to all elements for IE, apparently.
18
+ def enabled?
19
+ !disabled
20
+ end
21
+
22
+ private
23
+ def base_element_class
24
+ IE::Element
25
+ end
26
+ def browser_class
27
+ IE
28
+ end
29
+
30
+ public
31
+
32
+ # return the unique COM number for the element
33
+ dom_attr :uniqueNumber => :unique_number
34
+ # Return the outer html of the object - see http://msdn.microsoft.com/workshop/author/dhtml/reference/properties/outerhtml.asp?frame=true
35
+ dom_attr :outerHTML => :outer_html
36
+
37
+ # return the text before the element
38
+ def before_text
39
+ element_object.getAdjacentText("afterEnd")
40
+ end
41
+
42
+ # return the text after the element
43
+ def after_text
44
+ element_object.getAdjacentText("beforeBegin")
45
+ end
46
+
47
+ # Returns the text content of the element.
48
+ dom_attr :innerText => :text
49
+
50
+ # include Comparable
51
+ # def <=> other
52
+ # assert_exists
53
+ # other.assert_exists
54
+ # ole_object.sourceindex <=> other.ole_object.sourceindex
55
+ # end
56
+ dom_attr :sourceIndex => :source_index
57
+
58
+ # Return true if self is contained earlier in the html than other.
59
+ def before?(other)
60
+ source_index < other.source_index
61
+ end
62
+ # Return true if self is contained later in the html than other.
63
+ def after?(other)
64
+ source_index > other.source_index
65
+ end
66
+
67
+ def typingspeed
68
+ @container.typingspeed
69
+ end
70
+ def type_keys
71
+ @type_keys || @container.type_keys
72
+ end
73
+
74
+ private
75
+ # for use with events' button property
76
+ MouseButtonCodes=
77
+ { :left => 1,
78
+ :middle => 4,
79
+ :right => 2,
80
+ }
81
+
82
+ # returns an object representing an event (a WIN32OLE object)
83
+ # see:
84
+ # http://msdn.microsoft.com/en-us/library/ms535863%28VS.85%29.aspx
85
+ def create_event_object(event_type, options)
86
+ event_object=document_object.createEventObject
87
+
88
+ event_object_hash=create_event_object_hash(event_type, options)
89
+ event_object_hash.each_pair do |key,val|
90
+ event_object.send(key.to_s+'=', val)
91
+ end
92
+ return event_object
93
+ end
94
+
95
+ def create_event_object_hash(event_type, options)
96
+ event_stuff=
97
+ { :type => event_type,
98
+ :keyCode => 0, # TODO/fix, implement this
99
+ :ctrlKey => false,
100
+ :ctrlLeft => false,
101
+ :altKey => false,
102
+ :altLeft => false,
103
+ :shiftKey => false,
104
+ :shiftLeft => false,
105
+ }
106
+
107
+ if %w(onclick onmousedown onmouseup ondblclick onmouseover onmouseout).include?(event_type)
108
+ client_center=self.client_center
109
+
110
+ button_code=options[:button_code] ||
111
+ MouseButtonCodes[options[:button]] ||
112
+ (%w(onclick onmousedown onmouseup ondblclick).include?(event_type) ? MouseButtonCodes[:left] : 0)
113
+
114
+ event_stuff.merge!(
115
+ { :screenX => 0, # TODO/fix: use screen_center when implemented
116
+ :screenY => 0,
117
+ :clientX => client_center[0],
118
+ :clientY => client_center[1],
119
+ #:offsetX => , # if set this will clobber clientX/clientY. is itself set from clientX/Y when not set.
120
+ #:offsetY => ,
121
+ #:x => , # these also seem to get set from clientX/Y
122
+ #:y => ,
123
+ :button => button_code,
124
+ })
125
+ end
126
+ relevant_options=options.reject do |optkey,optv|
127
+ !%w(type keyCode ctrlKey ctrlLeft altKey altLeft shiftKey shiftLeft screenX screenY clientX clientY offsetX offsetY x y).detect{|keystr| keystr.to_sym==optkey}
128
+ end
129
+ return event_stuff.merge(relevant_options)
130
+ end
131
+
132
+ # makes json for an event object from the given options.
133
+ # does so in a sort of naive way, but a way that doesn't require
134
+ # something as heavyweight as pulling in ActiveSupport or the JSON gem.
135
+ def create_event_object_json(options)
136
+ event_object_hash=create_event_object_hash(nil, options).reject{|(k,v)| v.nil? }
137
+ event_object_json="{"+event_object_hash.map do |(attr, val)|
138
+ raise RuntimeError, "unexpected attribute #{attr}" unless attr.is_a?(Symbol) && attr.to_s=~ /\A[\w_]+\z/
139
+ unless [Numeric, String, TrueClass, FalseClass].any?{|klass| val.is_a?(klass) }
140
+ raise ArgumentError, "Cannot pass given key/value pair: #{attr.inspect} => #{val.inspect} (#{val.class})"
141
+ end
142
+ attr.to_s.inspect+": "+val.inspect
143
+ end.join(", ")+"}"
144
+ end
145
+
146
+ public
147
+
148
+ # Fires the click event on this element.
149
+ #
150
+ # Options:
151
+ # - :wait => true or false. If true, waits for the javascript call to return, and calls the #wait method.
152
+ # If false, does not wait for the javascript to return and does not call #wait.
153
+ # Default is true.
154
+ # - :highlight => true or false. Highlights the element while clicking if true. Default is true.
155
+ def click(options={})
156
+ options={:wait => true, :highlight => true}.merge(options)
157
+ result=nil
158
+ with_highlight(options) do
159
+ assert_enabled if respond_to?(:assert_enabled)
160
+ if options[:wait]
161
+ # we're putting all of the clicking actions in an array so that we can more easily separate out the
162
+ # overhead of checking existence and returning if existence fails.
163
+ actions=
164
+ [ proc { fire_event('mousedown', options) },
165
+ proc { fire_event('mouseup', options) },
166
+ #proc { fire_event('click', options) },
167
+ proc { element_object.respond_to?(:click) ? element_object.click : fire_event('click', options)
168
+ # TODO/fix: this calls the 'click' function if there is one, but that doesn't pass information
169
+ # like button/clientX/etc. figure out how to pass that to the event that click fires.
170
+ # we can't just use the fire_event, because the click function does more than that. for example,
171
+ # a link won't be followed just from firing the onclick event; the click function has to be called.
172
+ },
173
+ ]
174
+ actions.each do |action|
175
+ # javascript stuff responding to previous events can cause self to stop existing, so check at every subsequent step
176
+ if exists?
177
+ result=action.call
178
+ else
179
+ return result
180
+ end
181
+ end
182
+ wait
183
+ result
184
+ else
185
+ document_object.parentWindow.setTimeout("
186
+ (function(tagName, uniqueNumber, event_options)
187
+ { var event_object=document.createEventObject();
188
+ for(key in event_options)
189
+ { event_object[key]=event_options[key];
190
+ }
191
+ var candidate_elements=document.getElementsByTagName(tagName);
192
+ for(var i=0;i<candidate_elements.length;++i)
193
+ { var element=candidate_elements[i];
194
+ if(element.uniqueNumber==uniqueNumber)
195
+ { element.fireEvent('onmousedown', event_object);
196
+ element.fireEvent('onmouseup', event_object);
197
+ //element.fireEvent('onclick', event_object); // #TODO/fix - same as above with click() vs fireEvent('onclick', ...)
198
+ element.click ? element.click() : element.fireEvent('onclick', event_object);
199
+ }
200
+ }
201
+ })(#{self.tagName.inspect}, #{element_object.uniqueNumber.inspect}, #{create_event_object_json(options)})
202
+ ", 0)
203
+ nil
204
+ end
205
+ end
206
+ result
207
+ end
208
+
209
+ # calls #click with :wait option false.
210
+ # Takes options:
211
+ # - :highlight => true or false. Highlights the element while clicking if true. Default is true.
212
+ def click_no_wait(options={})
213
+ click(options.merge(:wait => false))
214
+ end
215
+
216
+ # Executes a user defined "fireEvent" for objects with JavaScript events tied to them such as DHTML menus.
217
+ # usage: allows a generic way to fire javascript events on page objects such as "onMouseOver", "onClick", etc.
218
+ # raises: UnknownObjectException if the object is not found
219
+ # ObjectDisabledException if the object is currently disabled
220
+ def fire_event(event_type, options={})
221
+ event_type = event_type.to_s.downcase # in case event_type was given as a symbol
222
+ unless event_type =~ /\Aon(.*)\z/i
223
+ event_type = "on"+event_type
224
+ end
225
+
226
+ options={:highlight => true, :wait => true}.merge(options)
227
+ with_highlight(options) do
228
+ assert_enabled if respond_to?(:assert_enabled)
229
+ if options[:wait]
230
+ # we need to pass fireEvent two arguments - the event type, and the event object.
231
+ # we can't do this directly. there is a bug or something, the result of which is
232
+ # that if we pass the WIN32OLE that is the return from document.createEventObject,
233
+ # none of the information about it actually gets passed. its button attribute is
234
+ # 0 regardless of what it was set to; same with clientx, clientY, and everything else.
235
+ # this seems to be an issue only with passing arguments which are WIN32OLEs to
236
+ # functions that are native functions (as opposed to functions defined by a user
237
+ # in javascript). so, a workaround is to make a function that is written in javascript
238
+ # that wraps around the native function and just passes the arguments to it. this
239
+ # causes the objects to be passed correctly. to illustrate, compare:
240
+ # window.alert(document.createEventObject)
241
+ # this causes an alert to appear with the text "[object]"
242
+ # window.eval("alert_wrapped=function(message){alert(message);}")
243
+ # window.alert_wrapped(document.createEventObject)
244
+ # this causes an alert to appear with the text "[object Event]"
245
+ # so, information is lost in the first one, where it's passed straight
246
+ # to the native function but not in the second one where the native function
247
+ # is wrapped in a javascript function.
248
+ #
249
+ # a generic solution follows, but it doesn't work. I'm leaving it in here in case
250
+ # I can figure out something to do with it later:
251
+ #window.eval("watir_wrap_native_for_win32ole=function(object, functionname)
252
+ # { var args=[];
253
+ # for(var i=2; i<arguments.length; ++i)
254
+ # { args.push(arguments[i]);
255
+ # }
256
+ # return object[functionname].apply(object, args);
257
+ # }")
258
+ #
259
+ # the problem with the above, using apply, is that it sometimse raises:
260
+ # WIN32OLERuntimeError: watir_wrap_native_for_win32ole
261
+ # OLE error code:0 in <Unknown>
262
+ # <No Description>
263
+ # HRESULT error code:0x80020101
264
+ #
265
+ # so, instead, implementing to a version that doesn't use apply but
266
+ # therefore has to have fixed number of arguments.
267
+ # TODO: move this to its own function? will when I run into a need for it outside of here, I guess.
268
+ window=document_object.parentWindow
269
+ window.eval("watir_wrap_native_for_win32ole_two_args=function(object, functionname, arg1, arg2)
270
+ { return object[functionname](arg1, arg2);
271
+ }")
272
+ # then use it, passing the event object.
273
+ # thus the buttons and mouse position and all that are successfully passed.
274
+ event_object= create_event_object(event_type, options)
275
+ result=window.watir_wrap_native_for_win32ole_two_args(element_object, 'fireEvent', event_type, event_object)
276
+ wait
277
+ result
278
+ else
279
+ document_object.parentWindow.setTimeout("
280
+ (function(tagName, uniqueNumber, event_type, event_options)
281
+ { var event_object=document.createEventObject();
282
+ for(key in event_options)
283
+ { event_object[key]=event_options[key];
284
+ }
285
+ var candidate_elements=document.getElementsByTagName(tagName);
286
+ for(var i=0;i<candidate_elements.length;++i)
287
+ { if(candidate_elements[i].uniqueNumber==uniqueNumber)
288
+ { candidate_elements[i].fireEvent(event_type, event_object);
289
+ }
290
+ }
291
+ })(#{self.tagName.inspect}, #{element_object.uniqueNumber.inspect}, #{event_type.to_s.inspect}, #{create_event_object_json(options)})
292
+ ", 0)
293
+ nil
294
+ end
295
+ end
296
+ end
297
+ # Executes a user defined "fireEvent" for objects with JavaScript events tied to them such as DHTML menus.
298
+ # usage: allows a generic way to fire javascript events on page objects such as "onMouseOver", "onClick", etc.
299
+ # raises: UnknownObjectException if the object is not found
300
+ # ObjectDisabledException if the object is currently disabled
301
+ def fire_event_no_wait(event, options={})
302
+ fire_event(event, options.merge(:wait => false))
303
+ end
304
+
305
+ def wait(options={})
306
+ @container.wait(options)
307
+ end
308
+
309
+ def self.element_object_style(element_object, document_object)
310
+ if element_object.nodeType==1
311
+ element_object.currentStyle
312
+ else
313
+ nil
314
+ end
315
+ end
316
+
317
+
318
+ # returns a two-element Vector containing the current scroll offset of this element relative
319
+ # to any scrolling parents.
320
+ # this is basically stolen from prototype - see http://www.prototypejs.org/api/element/cumulativescrolloffset
321
+ def scroll_offset
322
+ # override the #scroll_offset defined in vapir-common because it calls #respond_to? which is very slow on WIN32OLE
323
+ xy=Vector[0,0]
324
+ el=element_object
325
+ begin
326
+ begin
327
+ if (scroll_left=el.scrollLeft).is_a?(Numeric) && (scroll_top=el.scrollTop).is_a?(Numeric)
328
+ xy+=Vector[scroll_left, scroll_top]
329
+ end
330
+ rescue WIN32OLERuntimeError
331
+ # doesn't respond to those; do nothing.
332
+ end
333
+ el=el.parentNode
334
+ end while el
335
+ xy
336
+ end
337
+
338
+ private
339
+ def element_object_exists?
340
+ return nil if !@element_object
341
+
342
+ begin
343
+ win=container.document_object.parentWindow
344
+ rescue WIN32OLERuntimeError
345
+ # if the document_object no longer has a parentWindow, we don't exist. if that's not the error, it's unexpected; raise.
346
+ if $!.message =~ /unknown property or method `parentWindow'/
347
+ return false
348
+ else
349
+ raise
350
+ end
351
+ end
352
+ document_object=win.document # I don't know why container.document_object != container.document_object.parentWindow.document
353
+
354
+ # we need a javascript function to test equality because comparing two WIN32OLEs always returns false (unless they have the same object_id, which these don't)
355
+ win.execScript("__watir_javascript_equals__=function(a, b){return a==b;}")
356
+
357
+ current_node=@element_object
358
+ while current_node
359
+ begin
360
+ if win.__watir_javascript_equals__(current_node, document_object)
361
+ # if we encounter the correct current document going up the parentNodes, @element_object does exist.
362
+ return true
363
+ end
364
+ current_node=current_node.parentNode
365
+ rescue WIN32OLERuntimeError
366
+ # two possibilities for why we're here:
367
+ # if the method __watir_javascript_equals__ stops existing, then that probably means the window changed, meaning @element object doesn't exist anymore.
368
+ # if we encounter an error trying to access parentNode before reaching the current document, @element_object doesn't exist.
369
+ return false
370
+ end
371
+ end
372
+ # if we escaped that loop, parentNode returned nil without encountering the current document; @element_object doesn't exist.
373
+ return false
374
+ end
375
+ end
376
+ end