vapir-ie 1.7.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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