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/History.txt +437 -0
- data/LICENSE.txt +41 -0
- data/README.txt +0 -0
- data/lib/vapir-ie.rb +44 -0
- data/lib/vapir-ie/AutoItX.chm +0 -0
- data/lib/vapir-ie/AutoItX3.dll +0 -0
- data/lib/vapir-ie/IEDialog/Release/IEDialog.dll +0 -0
- data/lib/vapir-ie/autoit.rb +13 -0
- data/lib/vapir-ie/close_all.rb +32 -0
- data/lib/vapir-ie/container.rb +51 -0
- data/lib/vapir-ie/element.rb +376 -0
- data/lib/vapir-ie/elements.rb +9 -0
- data/lib/vapir-ie/form.rb +8 -0
- data/lib/vapir-ie/frame.rb +24 -0
- data/lib/vapir-ie/ie-class.rb +880 -0
- data/lib/vapir-ie/ie-process.rb +60 -0
- data/lib/vapir-ie/image.rb +59 -0
- data/lib/vapir-ie/input_elements.rb +158 -0
- data/lib/vapir-ie/link.rb +23 -0
- data/lib/vapir-ie/logger.rb +21 -0
- data/lib/vapir-ie/modal_dialog.rb +224 -0
- data/lib/vapir-ie/non_control_elements.rb +77 -0
- data/lib/vapir-ie/page_container.rb +203 -0
- data/lib/vapir-ie/process.rb +22 -0
- data/lib/vapir-ie/screen_capture.rb +7 -0
- data/lib/vapir-ie/scripts/select_file.rb +79 -0
- data/lib/vapir-ie/table.rb +33 -0
- data/lib/vapir-ie/version.rb +5 -0
- data/lib/vapir-ie/win32ole.rb +45 -0
- data/lib/vapir-ie/win32ole/win32ole.so +0 -0
- data/lib/vapir/ie.rb +1 -0
- metadata +130 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'vapir-ie/element'
|
2
|
+
require 'vapir-common/elements/elements'
|
3
|
+
require 'vapir-ie/page_container'
|
4
|
+
|
5
|
+
module Vapir
|
6
|
+
class IE::Frame < IE::Element
|
7
|
+
include Frame
|
8
|
+
include IE::PageContainer
|
9
|
+
|
10
|
+
def content_window_object
|
11
|
+
element_object.contentWindow
|
12
|
+
end
|
13
|
+
|
14
|
+
def document_object
|
15
|
+
content_window_object.document
|
16
|
+
end
|
17
|
+
alias document document_object
|
18
|
+
|
19
|
+
def attach_command
|
20
|
+
@container.page_container.attach_command + ".frame(#{@how.inspect}, #{@what.inspect})"
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,880 @@
|
|
1
|
+
require 'vapir-ie/container'
|
2
|
+
require 'vapir-ie/page_container'
|
3
|
+
require 'vapir-ie/close_all'
|
4
|
+
require 'vapir-ie/modal_dialog'
|
5
|
+
require 'vapir-ie/win32ole'
|
6
|
+
require 'vapir-ie/ie-process'
|
7
|
+
require 'vapir-ie/logger'
|
8
|
+
|
9
|
+
module Vapir
|
10
|
+
class IE < Browser
|
11
|
+
include Vapir::Exception
|
12
|
+
include IE::PageContainer
|
13
|
+
|
14
|
+
# Maximum number of seconds to wait when attaching to a window
|
15
|
+
@@attach_timeout = 2.0 # default value
|
16
|
+
def self.attach_timeout
|
17
|
+
@@attach_timeout
|
18
|
+
end
|
19
|
+
def self.attach_timeout=(timeout)
|
20
|
+
@@attach_timeout = timeout
|
21
|
+
end
|
22
|
+
|
23
|
+
# Return the options used when creating new instances of IE.
|
24
|
+
# BUG: this interface invites misunderstanding/misuse such as IE.options[:speed] = :zippy]
|
25
|
+
def self.options
|
26
|
+
{:speed => self.speed, :visible => self.visible, :attach_timeout => self.attach_timeout}
|
27
|
+
end
|
28
|
+
# set values for options used when creating new instances of IE.
|
29
|
+
def self.set_options options
|
30
|
+
options.each do |name, value|
|
31
|
+
send "#{name}=", value
|
32
|
+
end
|
33
|
+
end
|
34
|
+
# The globals $FAST_SPEED and $HIDE_IE are checked both at initialization
|
35
|
+
# and later, because they
|
36
|
+
# might be set after initialization. Setting them beforehand (e.g. from
|
37
|
+
# the command line) will affect the class, otherwise it is only a temporary
|
38
|
+
# effect
|
39
|
+
@@speed = $FAST_SPEED ? :fast : :slow
|
40
|
+
def self.speed
|
41
|
+
return :fast if $FAST_SPEED
|
42
|
+
@@speed
|
43
|
+
end
|
44
|
+
def self.speed= x
|
45
|
+
$FAST_SPEED = nil
|
46
|
+
@@speed = x
|
47
|
+
end
|
48
|
+
@@visible = $HIDE_IE ? false : true
|
49
|
+
def self.visible
|
50
|
+
return false if $HIDE_IE
|
51
|
+
@@visible
|
52
|
+
end
|
53
|
+
def self.visible= x
|
54
|
+
$HIDE_IE = nil
|
55
|
+
@@visible = x
|
56
|
+
end
|
57
|
+
|
58
|
+
# IE inserts some element whose tagName is empty and just acts as block level element
|
59
|
+
# Probably some IE method of cleaning things
|
60
|
+
# To pass the same to the xml parser we need to give some name to empty tagName
|
61
|
+
EMPTY_TAG_NAME = "DUMMY"
|
62
|
+
|
63
|
+
# The time, in seconds, it took for the new page to load after executing the
|
64
|
+
# the last command
|
65
|
+
attr_reader :down_load_time
|
66
|
+
|
67
|
+
# the OLE Internet Explorer object
|
68
|
+
attr_accessor :ie
|
69
|
+
|
70
|
+
# access to the logger object
|
71
|
+
attr_accessor :logger
|
72
|
+
|
73
|
+
# this contains the list of unique urls that have been visited
|
74
|
+
attr_reader :url_list
|
75
|
+
|
76
|
+
# Create a new IE window. Works just like IE.new in Watir 1.4.
|
77
|
+
def self.new_window
|
78
|
+
ie = new true
|
79
|
+
ie._new_window_init
|
80
|
+
ie
|
81
|
+
end
|
82
|
+
|
83
|
+
# Create an IE browser.
|
84
|
+
def initialize suppress_new_window=nil
|
85
|
+
_new_window_init unless suppress_new_window
|
86
|
+
end
|
87
|
+
|
88
|
+
def _new_window_init
|
89
|
+
create_browser_window
|
90
|
+
initialize_options
|
91
|
+
goto 'about:blank' # this avoids numerous problems caused by lack of a document
|
92
|
+
end
|
93
|
+
|
94
|
+
# Create a new IE Window, starting at the specified url.
|
95
|
+
# If no url is given, start empty.
|
96
|
+
def self.start url=nil
|
97
|
+
start_window url
|
98
|
+
end
|
99
|
+
|
100
|
+
# Create a new IE window, starting at the specified url.
|
101
|
+
# If no url is given, start empty. Works like IE.start in Watir 1.4.
|
102
|
+
def self.start_window url=nil
|
103
|
+
ie = new_window
|
104
|
+
ie.goto url if url
|
105
|
+
ie
|
106
|
+
end
|
107
|
+
|
108
|
+
# Create a new IE window in a new process.
|
109
|
+
# This method will not work when
|
110
|
+
# Vapir/Ruby is run under a service (instead of a user).
|
111
|
+
def self.new_process
|
112
|
+
ie = new true
|
113
|
+
ie._new_process_init
|
114
|
+
ie
|
115
|
+
end
|
116
|
+
|
117
|
+
def _new_process_init
|
118
|
+
iep = Process.start
|
119
|
+
@ie = iep.window
|
120
|
+
@process_id = iep.process_id
|
121
|
+
initialize_options
|
122
|
+
goto 'about:blank'
|
123
|
+
end
|
124
|
+
|
125
|
+
# Create a new IE window in a new process, starting at the specified URL.
|
126
|
+
# Same as IE.start.
|
127
|
+
def self.start_process url=nil
|
128
|
+
ie = new_process
|
129
|
+
ie.goto url if url
|
130
|
+
ie
|
131
|
+
end
|
132
|
+
|
133
|
+
# Return a Vapir::IE object for an existing IE window. Window can be
|
134
|
+
# referenced by url, title, or window handle.
|
135
|
+
# Second argument can be either a string or a regular expression in the
|
136
|
+
# case of of :url or :title.
|
137
|
+
# IE.attach(:url, 'http://www.google.com')
|
138
|
+
# IE.attach(:title, 'Google')
|
139
|
+
# IE.attach(:hwnd, 528140)
|
140
|
+
# This method will not work when
|
141
|
+
# Vapir/Ruby is run under a service (instead of a user).
|
142
|
+
def self.attach how, what
|
143
|
+
ie = new true # don't create window
|
144
|
+
ie._attach_init(how, what)
|
145
|
+
ie
|
146
|
+
end
|
147
|
+
|
148
|
+
# this method is used internally to attach to an existing window
|
149
|
+
def _attach_init how, what
|
150
|
+
attach_browser_window how, what
|
151
|
+
initialize_options
|
152
|
+
wait
|
153
|
+
end
|
154
|
+
|
155
|
+
# Return an IE object that wraps the given window, typically obtained from
|
156
|
+
# Shell.Application.windows.
|
157
|
+
def self.bind window
|
158
|
+
ie = new true
|
159
|
+
ie.ie = window
|
160
|
+
ie.initialize_options
|
161
|
+
ie
|
162
|
+
end
|
163
|
+
|
164
|
+
def create_browser_window
|
165
|
+
@ie = WIN32OLE.new('InternetExplorer.Application')
|
166
|
+
end
|
167
|
+
private :create_browser_window
|
168
|
+
|
169
|
+
def initialize_options
|
170
|
+
self.visible = IE.visible
|
171
|
+
self.speed = IE.speed
|
172
|
+
|
173
|
+
@element_object = nil
|
174
|
+
@page_container = self
|
175
|
+
@error_checkers = []
|
176
|
+
|
177
|
+
@logger = DefaultLogger.new
|
178
|
+
@url_list = []
|
179
|
+
end
|
180
|
+
|
181
|
+
# Specifies the speed that commands will be executed at. Choices are:
|
182
|
+
# * :slow (default)
|
183
|
+
# * :fast
|
184
|
+
# * :zippy
|
185
|
+
# With IE#speed= :zippy, text fields will be entered at once, instead of
|
186
|
+
# character by character (default).
|
187
|
+
def speed= how_fast
|
188
|
+
case how_fast
|
189
|
+
when :zippy then
|
190
|
+
@typingspeed = 0
|
191
|
+
@pause_after_wait = 0.01
|
192
|
+
@type_keys = false
|
193
|
+
@speed = :fast
|
194
|
+
when :fast then
|
195
|
+
@typingspeed = 0
|
196
|
+
@pause_after_wait = 0.01
|
197
|
+
@type_keys = true
|
198
|
+
@speed = :fast
|
199
|
+
when :slow then
|
200
|
+
@typingspeed = 0.08
|
201
|
+
@pause_after_wait = 0.1
|
202
|
+
@type_keys = true
|
203
|
+
@speed = :slow
|
204
|
+
else
|
205
|
+
raise ArgumentError, "Invalid speed: #{how_fast}"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def speed
|
210
|
+
return @speed if @speed == :slow
|
211
|
+
return @type_keys ? :fast : :zippy
|
212
|
+
end
|
213
|
+
|
214
|
+
# deprecated: use speed = :fast instead
|
215
|
+
def set_fast_speed
|
216
|
+
self.speed = :fast
|
217
|
+
end
|
218
|
+
|
219
|
+
# deprecated: use speed = :slow instead
|
220
|
+
def set_slow_speed
|
221
|
+
self.speed = :slow
|
222
|
+
end
|
223
|
+
|
224
|
+
def visible
|
225
|
+
assert_exists
|
226
|
+
@ie.visible
|
227
|
+
end
|
228
|
+
def visible=(boolean)
|
229
|
+
assert_exists
|
230
|
+
@ie.visible = boolean if boolean != @ie.visible
|
231
|
+
end
|
232
|
+
|
233
|
+
# Yields successively to each IE window on the current desktop. Takes a block.
|
234
|
+
# This method will not work when
|
235
|
+
# Vapir/Ruby is run under a service (instead of a user).
|
236
|
+
# Yields to the window and its hwnd.
|
237
|
+
def self.each
|
238
|
+
shell = WIN32OLE.new('Shell.Application')
|
239
|
+
shell.Windows.each do |window|
|
240
|
+
next unless (window.path =~ /Internet Explorer/ rescue false)
|
241
|
+
next unless (hwnd = window.hwnd rescue false)
|
242
|
+
ie = IE.bind(window)
|
243
|
+
ie.hwnd = hwnd
|
244
|
+
yield ie
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
# return internet explorer instance as specified. if none is found,
|
249
|
+
# return nil.
|
250
|
+
# arguments:
|
251
|
+
# :url, url -- the URL of the IE browser window
|
252
|
+
# :title, title -- the title of the browser page
|
253
|
+
# :hwnd, hwnd -- the window handle of the browser window.
|
254
|
+
# This method will not work when
|
255
|
+
# Vapir/Ruby is run under a service (instead of a user).
|
256
|
+
def self.find(how, what)
|
257
|
+
ie_ole = IE._find(how, what)
|
258
|
+
IE.bind ie_ole if ie_ole
|
259
|
+
end
|
260
|
+
|
261
|
+
def self._find(how, what)
|
262
|
+
ieTemp = nil
|
263
|
+
IE.each do |ie|
|
264
|
+
window = ie.ie
|
265
|
+
|
266
|
+
case how
|
267
|
+
when :url
|
268
|
+
ieTemp = window if Vapir::fuzzy_match(window.locationURL, what)
|
269
|
+
when :title
|
270
|
+
# normal windows explorer shells do not have document
|
271
|
+
# note window.document will fail for "new" browsers
|
272
|
+
begin
|
273
|
+
title = window.locationname
|
274
|
+
title = window.document.title
|
275
|
+
rescue WIN32OLERuntimeError
|
276
|
+
end
|
277
|
+
ieTemp = window if Vapir::fuzzy_match(title, what)
|
278
|
+
when :hwnd
|
279
|
+
begin
|
280
|
+
ieTemp = window if what == window.HWND
|
281
|
+
rescue WIN32OLERuntimeError
|
282
|
+
end
|
283
|
+
else
|
284
|
+
raise ArgumentError
|
285
|
+
end
|
286
|
+
end
|
287
|
+
return ieTemp
|
288
|
+
end
|
289
|
+
|
290
|
+
def attach_browser_window how, what
|
291
|
+
log "Seeking Window with #{how}: #{what}"
|
292
|
+
ieTemp = nil
|
293
|
+
begin
|
294
|
+
Vapir::until_with_timeout do
|
295
|
+
ieTemp = IE._find how, what
|
296
|
+
end
|
297
|
+
rescue TimeOutException
|
298
|
+
raise NoMatchingWindowFoundException,
|
299
|
+
"Unable to locate a window with #{how} of #{what}"
|
300
|
+
end
|
301
|
+
@ie = ieTemp
|
302
|
+
end
|
303
|
+
private :attach_browser_window
|
304
|
+
|
305
|
+
def browser_object
|
306
|
+
assert_exists
|
307
|
+
@ie
|
308
|
+
end
|
309
|
+
|
310
|
+
# Return the current window handle
|
311
|
+
def hwnd
|
312
|
+
assert_exists
|
313
|
+
@hwnd ||= @ie.hwnd
|
314
|
+
end
|
315
|
+
attr_writer :hwnd
|
316
|
+
|
317
|
+
def win_window
|
318
|
+
@win_window||= WinWindow.new(hwnd)
|
319
|
+
end
|
320
|
+
|
321
|
+
def modal_dialog(options={})
|
322
|
+
assert_exists do
|
323
|
+
raise ArgumentError, "options argument must be a hash; received #{options.inspect} (#{options.class})" unless options.is_a?(Hash)
|
324
|
+
modal=IE::ModalDialog.new(self, options.merge(:error => false))
|
325
|
+
modal.exists? ? modal : nil
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
def modal_dialog!(options={})
|
330
|
+
assert_exists do
|
331
|
+
IE::ModalDialog.new(self, options.merge(:error => true))
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
# we expect one of these error codes when quitting or checking existence.
|
336
|
+
ExistenceFailureCodesRE = Regexp.new(
|
337
|
+
{ '0x800706ba' => 'The RPC server is unavailable',
|
338
|
+
'0x80010108' => 'The object invoked has disconnected from its clients.',
|
339
|
+
'0x800706be' => 'The remote procedure call failed.',
|
340
|
+
}.keys.join('|'), Regexp::IGNORECASE)
|
341
|
+
|
342
|
+
# Are we attached to an open browser?
|
343
|
+
def exists?
|
344
|
+
!!(@ie && begin
|
345
|
+
@ie.name
|
346
|
+
rescue WIN32OLERuntimeError
|
347
|
+
raise unless $!.message =~ ExistenceFailureCodesRE
|
348
|
+
false
|
349
|
+
end)
|
350
|
+
end
|
351
|
+
alias :exist? :exists?
|
352
|
+
|
353
|
+
# deprecated: use logger= instead
|
354
|
+
def set_logger(logger)
|
355
|
+
@logger = logger
|
356
|
+
end
|
357
|
+
|
358
|
+
def log(what)
|
359
|
+
@logger.debug(what) if @logger
|
360
|
+
end
|
361
|
+
|
362
|
+
#
|
363
|
+
# Accessing data outside the document
|
364
|
+
#
|
365
|
+
|
366
|
+
# Return the title of the document
|
367
|
+
def title
|
368
|
+
@ie.document.title
|
369
|
+
end
|
370
|
+
|
371
|
+
# Return the status of the window, typically from the status bar at the bottom.
|
372
|
+
def status
|
373
|
+
return @ie.statusText
|
374
|
+
end
|
375
|
+
|
376
|
+
#
|
377
|
+
# Navigation
|
378
|
+
#
|
379
|
+
|
380
|
+
# Navigate to the specified URL.
|
381
|
+
# * url - string - the URL to navigate to
|
382
|
+
def goto(url)
|
383
|
+
assert_exists do
|
384
|
+
@ie.navigate(url)
|
385
|
+
wait
|
386
|
+
return @down_load_time
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
# Go to the previous page - the same as clicking the browsers back button
|
391
|
+
# an WIN32OLERuntimeError exception is raised if the browser cant go back
|
392
|
+
def back
|
393
|
+
assert_exists do
|
394
|
+
@ie.GoBack
|
395
|
+
wait
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
# Go to the next page - the same as clicking the browsers forward button
|
400
|
+
# an WIN32OLERuntimeError exception is raised if the browser cant go forward
|
401
|
+
def forward
|
402
|
+
assert_exists do
|
403
|
+
@ie.GoForward
|
404
|
+
wait
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
module RefreshConstants
|
409
|
+
# http://msdn.microsoft.com/en-us/library/bb268230%28v=VS.85%29.aspx
|
410
|
+
REFRESH_NORMAL = 0
|
411
|
+
REFRESH_IFEXPIRED = 1
|
412
|
+
REFRESH_COMPLETELY = 3
|
413
|
+
end
|
414
|
+
# Refresh the current page - the same as clicking the browsers refresh button
|
415
|
+
# an WIN32OLERuntimeError exception is raised if the browser cant refresh
|
416
|
+
def refresh
|
417
|
+
assert_exists do
|
418
|
+
@ie.refresh2(RefreshConstants::REFRESH_COMPLETELY)
|
419
|
+
wait
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
# clear the list of urls that we have visited
|
424
|
+
def clear_url_list
|
425
|
+
@url_list.clear
|
426
|
+
end
|
427
|
+
|
428
|
+
# Closes the Browser
|
429
|
+
def close
|
430
|
+
assert_exists
|
431
|
+
@ie.stop
|
432
|
+
@ie.quit
|
433
|
+
# TODO/fix timeout; this shouldn't be a hard-coded magic number.
|
434
|
+
::Waiter.try_for(32, :exception => WindowFailedToCloseException.new("The browser window did not close"), :interval => 1) do
|
435
|
+
begin
|
436
|
+
if exists?
|
437
|
+
@ie.quit
|
438
|
+
false
|
439
|
+
else
|
440
|
+
true
|
441
|
+
end
|
442
|
+
rescue WIN32OLERuntimeError
|
443
|
+
raise unless $!.message =~ ExistenceFailureCodesRE
|
444
|
+
true
|
445
|
+
end
|
446
|
+
end
|
447
|
+
@ie=nil
|
448
|
+
end
|
449
|
+
|
450
|
+
# Maximize the window (expands to fill the screen)
|
451
|
+
def maximize
|
452
|
+
win_window.maximize!
|
453
|
+
end
|
454
|
+
|
455
|
+
# Minimize the window (appears as icon on taskbar)
|
456
|
+
def minimize
|
457
|
+
win_window.minimize!
|
458
|
+
end
|
459
|
+
|
460
|
+
# Restore the window (after minimizing or maximizing)
|
461
|
+
def restore
|
462
|
+
win_window.restore!
|
463
|
+
end
|
464
|
+
|
465
|
+
# Make the window come to the front
|
466
|
+
def bring_to_front
|
467
|
+
win_window.really_set_foreground!
|
468
|
+
end
|
469
|
+
|
470
|
+
def front?
|
471
|
+
win_window.foreground?
|
472
|
+
end
|
473
|
+
|
474
|
+
# Send key events to IE window.
|
475
|
+
# See http://www.autoitscript.com/autoit3/docs/appendix/SendKeys.htm
|
476
|
+
# for complete documentation on keys supported and syntax.
|
477
|
+
def send_keys(key_string)
|
478
|
+
assert_exists do
|
479
|
+
require 'vapir-ie/autoit'
|
480
|
+
bring_to_front
|
481
|
+
Vapir.autoit.Send key_string
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
# saves a screenshot of this browser window to the given filename.
|
486
|
+
#
|
487
|
+
# second argument, optional, specifies what area to take a screenshot of.
|
488
|
+
# - :client takes a screenshot of the client area, which excludes the menu bar and other window trimmings.
|
489
|
+
# - :window (default) takes a screenshot of the full browser window
|
490
|
+
# - :desktop takes a screenshot of the full desktop
|
491
|
+
def screen_capture(filename, dc=:window)
|
492
|
+
if dc==:desktop
|
493
|
+
screenshot_win=WinWindow.desktop_window
|
494
|
+
dc=:window
|
495
|
+
else
|
496
|
+
screenshot_win=win_window
|
497
|
+
end
|
498
|
+
screenshot_win.capture_to_bmp_file(filename, :dc => dc)
|
499
|
+
end
|
500
|
+
|
501
|
+
def dir
|
502
|
+
return File.expand_path(File.dirname(__FILE__))
|
503
|
+
end
|
504
|
+
|
505
|
+
#
|
506
|
+
# Document and Document Data
|
507
|
+
#
|
508
|
+
|
509
|
+
# Return the current document
|
510
|
+
def document
|
511
|
+
assert_exists
|
512
|
+
return @ie.document
|
513
|
+
end
|
514
|
+
alias document_object document
|
515
|
+
|
516
|
+
def browser
|
517
|
+
self
|
518
|
+
end
|
519
|
+
|
520
|
+
# returns the current url, as displayed in the address bar of the browser
|
521
|
+
def url
|
522
|
+
assert_exists
|
523
|
+
return @ie.LocationURL
|
524
|
+
end
|
525
|
+
|
526
|
+
# Error checkers
|
527
|
+
|
528
|
+
# this method runs the predefined error checks
|
529
|
+
def run_error_checks
|
530
|
+
assert_exists do
|
531
|
+
@error_checkers.each { |e| e.call(self) }
|
532
|
+
end
|
533
|
+
end
|
534
|
+
|
535
|
+
# this method is used to add an error checker that gets executed on every page load
|
536
|
+
# * checker Proc Object, that contains the code to be run
|
537
|
+
def add_checker(checker)
|
538
|
+
@error_checkers << checker
|
539
|
+
end
|
540
|
+
|
541
|
+
# this allows a checker to be disabled
|
542
|
+
# * checker Proc Object, the checker that is to be disabled
|
543
|
+
def disable_checker(checker)
|
544
|
+
@error_checkers.delete(checker)
|
545
|
+
end
|
546
|
+
|
547
|
+
# this method shows the name, id etc of the object that is currently active - ie the element that has focus
|
548
|
+
# its mostly used in irb when creating a script
|
549
|
+
def show_active # TODO/fix: move to common; test
|
550
|
+
|
551
|
+
current_object = document.activeElement
|
552
|
+
current_element = base_class.factory(current_object)
|
553
|
+
current_element.to_s
|
554
|
+
end
|
555
|
+
|
556
|
+
# Gives focus to the frame
|
557
|
+
def focus
|
558
|
+
document.activeElement.blur
|
559
|
+
document.focus
|
560
|
+
end
|
561
|
+
|
562
|
+
|
563
|
+
# Functions written for using xpath for getting the elements.
|
564
|
+
def xmlparser_document_object
|
565
|
+
if @xml_parser_doc == nil
|
566
|
+
create_xml_parser_doc
|
567
|
+
end
|
568
|
+
return @xml_parser_doc
|
569
|
+
end
|
570
|
+
|
571
|
+
# Create the Nokogiri object if it is nil. This method is private so can be called only
|
572
|
+
# from xmlparser_document_object method.
|
573
|
+
def create_xml_parser_doc
|
574
|
+
require 'nokogiri'
|
575
|
+
if @xml_parser_doc == nil
|
576
|
+
htmlSource ="<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<HTML>\n"
|
577
|
+
htmlSource = html_source(document.body,htmlSource," ")
|
578
|
+
htmlSource += "\n</HTML>\n"
|
579
|
+
# Angrez: Resolving Jira issue WTR-114
|
580
|
+
htmlSource = htmlSource.gsub(/ /, ' ')
|
581
|
+
begin
|
582
|
+
#@xml_parser_doc = Nokogiri::HTML::Document.new(htmlSource)
|
583
|
+
@xml_parser_doc = Nokogiri.parse(htmlSource)
|
584
|
+
rescue => e
|
585
|
+
output_xml_parser_doc("error.xml", htmlSource)
|
586
|
+
raise e
|
587
|
+
end
|
588
|
+
end
|
589
|
+
end
|
590
|
+
private :create_xml_parser_doc
|
591
|
+
|
592
|
+
def output_xml_parser_doc(name, text)
|
593
|
+
file = File.open(name,"w")
|
594
|
+
file.print(text)
|
595
|
+
file.close
|
596
|
+
end
|
597
|
+
private :output_xml_parser_doc
|
598
|
+
|
599
|
+
#Function Tokenizes the tag line and returns array of tokens.
|
600
|
+
#Token could be either tagName or "=" or attribute name or attribute value
|
601
|
+
#Attribute value could be either quoted string or single word
|
602
|
+
def tokenize_tagline(outerHtml)
|
603
|
+
outerHtml = outerHtml.gsub(/\n|\r/," ")
|
604
|
+
#removing "< symbol", opening of current tag
|
605
|
+
outerHtml =~ /^\s*<(.*)$/
|
606
|
+
outerHtml = $1
|
607
|
+
tokens = Array.new
|
608
|
+
i = startOffset = 0
|
609
|
+
length = outerHtml.length
|
610
|
+
#puts outerHtml
|
611
|
+
parsingValue = false
|
612
|
+
while i < length do
|
613
|
+
i +=1 while (i < length && outerHtml[i,1] =~ /\s/)
|
614
|
+
next if i == length
|
615
|
+
currentToken = outerHtml[i,1]
|
616
|
+
|
617
|
+
#Either current tag has been closed or user has not closed the tag >
|
618
|
+
# and we have received the opening of next element
|
619
|
+
break if currentToken =~ /<|>/
|
620
|
+
|
621
|
+
#parse quoted value
|
622
|
+
if(currentToken == "\"" || currentToken == "'")
|
623
|
+
parsingValue = false
|
624
|
+
quote = currentToken
|
625
|
+
startOffset = i
|
626
|
+
i += 1
|
627
|
+
i += 1 while (i < length && (outerHtml[i,1] != quote || outerHtml[i-1,1] == "\\"))
|
628
|
+
if i == length
|
629
|
+
tokens.push quote + outerHtml[startOffset..i-1]
|
630
|
+
else
|
631
|
+
tokens.push outerHtml[startOffset..i]
|
632
|
+
end
|
633
|
+
elsif currentToken == "="
|
634
|
+
tokens.push "="
|
635
|
+
parsingValue = true
|
636
|
+
else
|
637
|
+
startOffset = i
|
638
|
+
i += 1 while (i < length && !(outerHtml[i,1] =~ /\s|=|<|>/)) if !parsingValue
|
639
|
+
i += 1 while (i < length && !(outerHtml[i,1] =~ /\s|<|>/)) if parsingValue
|
640
|
+
parsingValue = false
|
641
|
+
i -= 1
|
642
|
+
tokens.push outerHtml[startOffset..i]
|
643
|
+
end
|
644
|
+
i += 1
|
645
|
+
end
|
646
|
+
return tokens
|
647
|
+
end
|
648
|
+
private :tokenize_tagline
|
649
|
+
|
650
|
+
# This function get and clean all the attributes of the tag.
|
651
|
+
def all_tag_attributes(outerHtml)
|
652
|
+
tokens = tokenize_tagline(outerHtml)
|
653
|
+
#puts tokens
|
654
|
+
tagLine = ""
|
655
|
+
count = 1
|
656
|
+
tokensLength = tokens.length
|
657
|
+
expectedEqualityOP= false
|
658
|
+
while count < tokensLength do
|
659
|
+
if expectedEqualityOP == false
|
660
|
+
#print Attribute Name
|
661
|
+
# If attribute name is valid. Refer: http://www.w3.org/TR/REC-xml/#NT-Name
|
662
|
+
if tokens[count] =~ /^(\w|_|:)(.*)$/
|
663
|
+
tagLine += " #{tokens[count]}"
|
664
|
+
expectedEqualityOP = true
|
665
|
+
end
|
666
|
+
elsif tokens[count] == "="
|
667
|
+
count += 1
|
668
|
+
if count == tokensLength
|
669
|
+
tagLine += "=\"\""
|
670
|
+
elsif(tokens[count][0,1] == "\"" || tokens[count][0,1] == "'")
|
671
|
+
tagLine += "=#{tokens[count]}"
|
672
|
+
else
|
673
|
+
tagLine += "=\"#{tokens[count]}\""
|
674
|
+
end
|
675
|
+
expectedEqualityOP = false
|
676
|
+
else
|
677
|
+
#Opps! equality was expected but its not there.
|
678
|
+
#Set value same as the attribute name e.g. selected="selected"
|
679
|
+
tagLine += "=\"#{tokens[count-1]}\""
|
680
|
+
expectedEqualityOP = false
|
681
|
+
next
|
682
|
+
end
|
683
|
+
count += 1
|
684
|
+
end
|
685
|
+
tagLine += "=\"#{tokens[count-1]}\" " if expectedEqualityOP == true
|
686
|
+
#puts tagLine
|
687
|
+
return tagLine
|
688
|
+
end
|
689
|
+
private :all_tag_attributes
|
690
|
+
|
691
|
+
# This function is used to escape the characters that are not valid XML data.
|
692
|
+
def xml_escape(str)
|
693
|
+
str = str.gsub(/&/,'&')
|
694
|
+
str = str.gsub(/</,'<')
|
695
|
+
str = str.gsub(/>/,'>')
|
696
|
+
str = str.gsub(/"/, '"')
|
697
|
+
str
|
698
|
+
end
|
699
|
+
private :xml_escape
|
700
|
+
|
701
|
+
# Returns HTML Source
|
702
|
+
# Traverse the DOM tree rooted at body element
|
703
|
+
# and generate the HTML source.
|
704
|
+
# element: Represent Current element
|
705
|
+
# htmlString:HTML Source
|
706
|
+
# spaces:(Used for debugging). Helps in indentation
|
707
|
+
def html_source(element, htmlString, spaceString)
|
708
|
+
begin
|
709
|
+
tagLine = ""
|
710
|
+
outerHtml = ""
|
711
|
+
tagName = ""
|
712
|
+
begin
|
713
|
+
tagName = element.tagName.downcase
|
714
|
+
tagName = EMPTY_TAG_NAME if tagName == ""
|
715
|
+
# If tag is a mismatched tag.
|
716
|
+
if !(tagName =~ /^(\w|_|:)(.*)$/)
|
717
|
+
return htmlString
|
718
|
+
end
|
719
|
+
rescue
|
720
|
+
#handling text nodes
|
721
|
+
htmlString += xml_escape(element.toString)
|
722
|
+
return htmlString
|
723
|
+
end
|
724
|
+
#puts tagName
|
725
|
+
#Skip comment and script tag
|
726
|
+
if tagName =~ /^!/ || tagName== "script" || tagName =="style"
|
727
|
+
return htmlString
|
728
|
+
end
|
729
|
+
#tagLine += spaceString
|
730
|
+
outerHtml = all_tag_attributes(element.outerHtml) if tagName != EMPTY_TAG_NAME
|
731
|
+
tagLine += "<#{tagName} #{outerHtml}"
|
732
|
+
|
733
|
+
canHaveChildren = element.canHaveChildren
|
734
|
+
if canHaveChildren
|
735
|
+
tagLine += ">"
|
736
|
+
else
|
737
|
+
tagLine += "/>" #self closing tag
|
738
|
+
end
|
739
|
+
#spaceString += spaceString
|
740
|
+
htmlString += tagLine
|
741
|
+
childElements = element.childnodes
|
742
|
+
childElements.each do |child|
|
743
|
+
htmlString = html_source(child,htmlString,spaceString)
|
744
|
+
end
|
745
|
+
if canHaveChildren
|
746
|
+
#tagLine += spaceString
|
747
|
+
tagLine ="</" + tagName + ">"
|
748
|
+
htmlString += tagLine
|
749
|
+
end
|
750
|
+
return htmlString
|
751
|
+
rescue => e
|
752
|
+
puts e.to_s
|
753
|
+
end
|
754
|
+
return htmlString
|
755
|
+
end
|
756
|
+
private :html_source
|
757
|
+
|
758
|
+
public
|
759
|
+
# return the first element object (not Element) that matches the xpath
|
760
|
+
def element_object_by_xpath(xpath)
|
761
|
+
objects= element_objects_by_xpath(xpath)
|
762
|
+
return (objects && objects[0])
|
763
|
+
end
|
764
|
+
|
765
|
+
# execute xpath and return an array of elements
|
766
|
+
def element_objects_by_xpath(xpath)
|
767
|
+
doc = xmlparser_document_object
|
768
|
+
modifiedXpath = ""
|
769
|
+
selectedElements = Array.new
|
770
|
+
|
771
|
+
# strip any trailing slash from the xpath expression (as used in watir unit tests)
|
772
|
+
xpath.chop! unless (/\/$/ =~ xpath).nil?
|
773
|
+
|
774
|
+
doc.xpath(xpath).each do |element|
|
775
|
+
modifiedXpath = element.path
|
776
|
+
temp = element_by_absolute_xpath(modifiedXpath) # temp = a DOM/COM element
|
777
|
+
selectedElements << temp if temp != nil
|
778
|
+
end
|
779
|
+
#puts selectedElements.length
|
780
|
+
if selectedElements.length == 0
|
781
|
+
return nil
|
782
|
+
else
|
783
|
+
return selectedElements
|
784
|
+
end
|
785
|
+
end
|
786
|
+
|
787
|
+
# Method that iterates over IE DOM object and get the elements for the given
|
788
|
+
# xpath.
|
789
|
+
def element_by_absolute_xpath(xpath)
|
790
|
+
curElem = nil
|
791
|
+
|
792
|
+
#puts "Hello; Given xpath is : #{xpath}"
|
793
|
+
doc = document
|
794
|
+
curElem = doc.getElementsByTagName("body").item(0)
|
795
|
+
xpath =~ /^.*\/body\[?\d*\]?\/(.*)/
|
796
|
+
xpath = $1
|
797
|
+
|
798
|
+
if xpath == nil
|
799
|
+
puts "Function Requires absolute XPath."
|
800
|
+
return
|
801
|
+
end
|
802
|
+
|
803
|
+
arr = xpath.split(/\//)
|
804
|
+
return nil if arr.length == 0
|
805
|
+
|
806
|
+
lastTagName = arr[arr.length-1].to_s.upcase
|
807
|
+
|
808
|
+
# lastTagName is like tagName[number] or just tagName. For the first case we need to
|
809
|
+
# separate tagName and number.
|
810
|
+
lastTagName =~ /(\w*)\[?\d*\]?/
|
811
|
+
lastTagName = $1
|
812
|
+
#puts lastTagName
|
813
|
+
|
814
|
+
for element in arr do
|
815
|
+
element =~ /(\w*)\[?(\d*)\]?/
|
816
|
+
tagname = $1
|
817
|
+
tagname = tagname.upcase
|
818
|
+
|
819
|
+
if $2 != nil && $2 != ""
|
820
|
+
index = $2
|
821
|
+
index = "#{index}".to_i - 1
|
822
|
+
else
|
823
|
+
index = 0
|
824
|
+
end
|
825
|
+
|
826
|
+
#puts "#{element} #{tagname} #{index}"
|
827
|
+
allElemns = curElem.childnodes
|
828
|
+
if allElemns == nil || allElemns.length == 0
|
829
|
+
puts "#{element} is null"
|
830
|
+
next # Go to next element
|
831
|
+
end
|
832
|
+
|
833
|
+
#puts "Current element is : #{curElem.tagName}"
|
834
|
+
allElemns.each do |child|
|
835
|
+
gotIt = false
|
836
|
+
begin
|
837
|
+
curTag = child.tagName
|
838
|
+
curTag = EMPTY_TAG_NAME if curTag == ""
|
839
|
+
rescue
|
840
|
+
next
|
841
|
+
end
|
842
|
+
#puts child.tagName
|
843
|
+
if curTag == tagname
|
844
|
+
index-=1
|
845
|
+
if index < 0
|
846
|
+
curElem = child
|
847
|
+
break
|
848
|
+
end
|
849
|
+
end
|
850
|
+
end
|
851
|
+
|
852
|
+
#puts "Node selected at index #{index.to_s} : #{curElem.tagName}"
|
853
|
+
end
|
854
|
+
begin
|
855
|
+
if curElem.tagName == lastTagName
|
856
|
+
#puts curElem.tagName
|
857
|
+
return curElem
|
858
|
+
else
|
859
|
+
return nil
|
860
|
+
end
|
861
|
+
rescue
|
862
|
+
return nil
|
863
|
+
end
|
864
|
+
end
|
865
|
+
private :element_by_absolute_xpath
|
866
|
+
|
867
|
+
def attach_command
|
868
|
+
"Vapir::IE.attach(:hwnd, #{hwnd})"
|
869
|
+
end
|
870
|
+
|
871
|
+
private
|
872
|
+
def base_element_class
|
873
|
+
IE::Element
|
874
|
+
end
|
875
|
+
def browser_class
|
876
|
+
IE
|
877
|
+
end
|
878
|
+
|
879
|
+
end # class IE
|
880
|
+
end
|