This repository has been archived by the owner on Jun 6, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 76
/
ie-class.rb
775 lines (674 loc) · 20.2 KB
/
ie-class.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
module Watir
class IE
include WaitHelper
include Exception
include Container
include PageContainer
# Maximum number of seconds to wait when attaching to a window
@@attach_timeout = 2.0 # default value
def self.attach_timeout
@@attach_timeout
end
def self.attach_timeout=(timeout)
@@attach_timeout = timeout
end
# Return the options used when creating new instances of IE.
# BUG: this interface invites misunderstanding/misuse such as IE.options[:speed] = :zippy]
def self.options
{:speed => self.speed, :visible => self.visible, :attach_timeout => self.attach_timeout, :zero_based_indexing => self.zero_based_indexing}
end
# set values for options used when creating new instances of IE.
def self.set_options options
options.each do |name, value|
send "#{name}=", value
end
end
# The globals $FAST_SPEED and $HIDE_IE are checked both at initialization
# and later, because they
# might be set after initialization. Setting them beforehand (e.g. from
# the command line) will affect the class, otherwise it is only a temporary
# effect
@@speed = $FAST_SPEED ? :fast : :slow
def self.speed
return :fast if $FAST_SPEED
@@speed
end
def self.speed= x
$FAST_SPEED = nil
@@speed = x
end
@@visible = $HIDE_IE ? false : true
def self.visible
return false if $HIDE_IE
@@visible
end
def self.visible= x
$HIDE_IE = nil
@@visible = x
end
@@zero_based_indexing = true
def self.zero_based_indexing= enabled
@@zero_based_indexing = enabled
end
def self.zero_based_indexing
@@zero_based_indexing
end
def self.base_index
self.zero_based_indexing ? 0 : 1
end
# Used internally to determine when IE has finished loading a page
READYSTATES = {:complete => 4}
# The default color for highlighting objects as they are accessed.
HIGHLIGHT_COLOR = 'yellow'
# The time, in seconds, it took for the new page to load after executing the
# the last command
attr_reader :down_load_time
# the OLE Internet Explorer object
attr_accessor :ie
# access to the logger object
attr_accessor :logger
# this contains the list of unique urls that have been visited
attr_reader :url_list
# Create a new IE window. Works just like IE.new in Watir 1.4.
def self.new_window
ie = new true
ie._new_window_init
ie
end
# Create an IE browser.
def initialize suppress_new_window=nil
_new_window_init unless suppress_new_window
end
def _new_window_init
create_browser_window
initialize_options
goto 'about:blank' # this avoids numerous problems caused by lack of a document
end
# Create a new IE Window, starting at the specified url.
# If no url is given, start empty.
def self.start url=nil
start_window url
end
# Create a new IE window, starting at the specified url.
# If no url is given, start empty. Works like IE.start in Watir 1.4.
def self.start_window url=nil
ie = new_window
ie.goto url if url
ie
end
# Create a new IE window in a new process.
# This method will not work when
# Watir/Ruby is run under a service (instead of a user).
def self.new_process
ie = new true
ie._new_process_init
ie
end
def _new_process_init
iep = Process.start
@ie = iep.window
@process_id = iep.process_id
initialize_options
goto 'about:blank'
end
# Create a new IE window in a new process, starting at the specified URL.
# Same as IE.start.
def self.start_process url=nil
ie = new_process
ie.goto url if url
ie
end
# Return a Watir::IE object for an existing IE window. Window can be
# referenced by url, title, or window handle.
# Second argument can be either a string or a regular expression in the
# case of of :url or :title.
# IE.attach(:url, 'http://www.google.com')
# IE.attach(:title, 'Google')
# IE.attach(:hwnd, 528140)
# This method will not work when
# Watir/Ruby is run under a service (instead of a user).
def self.attach how, what
ie = new true # don't create window
ie._attach_init(how, what)
ie
end
# this method is used internally to attach to an existing window
def _attach_init how, what
attach_browser_window how, what
initialize_options
wait
end
# Return an IE object that wraps the given window, typically obtained from
# Shell.Application.windows.
def self.bind window
ie = new true
ie.ie = window
ie.initialize_options
ie
end
def initialize_options
self.visible = IE.visible
self.speed = IE.speed
@ole_object = nil
@page_container = self
@error_checkers = []
@activeObjectHighLightColor = HIGHLIGHT_COLOR
@logger = DefaultLogger.new
@url_list = []
end
# Specifies the speed that commands will be executed at. Choices are:
# * :slow (default)
# * :fast
# * :zippy
# With IE#speed= :zippy, text fields will be entered at once, instead of
# character by character (default).
def speed= how_fast
case how_fast
when :zippy then
@typingspeed = 0
@pause_after_wait = 0.01
@type_keys = false
@speed = :fast
when :fast then
@typingspeed = 0
@pause_after_wait = 0.01
@type_keys = true
@speed = :fast
when :slow then
@typingspeed = 0.08
@pause_after_wait = 0.1
@type_keys = true
@speed = :slow
else
raise ArgumentError, "Invalid speed: #{how_fast}"
end
end
def speed
return @speed if @speed == :slow
return @type_keys ? :fast : :zippy
end
# deprecated: use speed = :fast instead
def set_fast_speed
self.speed = :fast
end
# deprecated: use speed = :slow instead
def set_slow_speed
self.speed = :slow
end
def visible
@ie.visible
end
def visible=(boolean)
@ie.visible = boolean if boolean != @ie.visible
end
# Yields successively to each IE window on the current desktop. Takes a block.
# This method will not work when
# Watir/Ruby is run under a service (instead of a user).
# Yields to the window and its hwnd.
def self.each
shell = WIN32OLE.new('Shell.Application')
ie_browsers = []
shell.Windows.each do |window|
next unless (window.path =~ /Internet Explorer/ rescue false)
next unless (hwnd = window.hwnd rescue false)
ie = IE.bind(window)
ie.hwnd = hwnd
ie_browsers << ie
end
ie_browsers.each do |ie|
yield ie
end
end
def self.version
@ie_version ||= begin
require 'win32/registry'
::Win32::Registry::HKEY_LOCAL_MACHINE.open("SOFTWARE\\Microsoft\\Internet Explorer") do |ie_key|
ie_key.read('Version').last
end
# OR: ::WIN32OLE.new("WScript.Shell").RegRead("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Internet Explorer\\Version")
end
end
def self.version_parts
version.split('.')
end
# return internet explorer instance as specified. if none is found,
# return nil.
# arguments:
# :url, url -- the URL of the IE browser window
# :title, title -- the title of the browser page
# :hwnd, hwnd -- the window handle of the browser window.
# This method will not work when
# Watir/Ruby is run under a service (instead of a user).
def self.find(how, what)
ie_ole = IE._find(how, what)
IE.bind ie_ole if ie_ole
end
def self._find(how, what)
self._find_all(how, what).first
end
def self._find_all(how, what)
ies = []
count = -1
IE.each do |ie|
window = ie.ie
case how
when :url
ies << window if (what.matches(window.locationURL))
when :title
# normal windows explorer shells do not have document
# note window.document will fail for "new" browsers
begin
title = window.locationname
title = window.document.title
rescue WIN32OLERuntimeError
end
ies << window if what.matches(title)
when :hwnd
begin
ies << window if what == window.HWND
rescue WIN32OLERuntimeError
end
when :index
count += 1
if count == what
ies << window
break
end
when nil
ies << window
else
raise ArgumentError
end
end
ies
end
# Return the current window handle
def hwnd
raise "Not attached to a browser" if @ie.nil?
@hwnd ||= @ie.hwnd
end
attr_writer :hwnd
def name
:ie
end
# Are we attached to an open browser?
def exists?
begin
!!(@ie.name =~ /Internet Explorer/)
rescue WIN32OLERuntimeError, NoMethodError
false
end
end
alias :exist? :exists?
# deprecated: use logger= instead
def set_logger(logger)
@logger = logger
end
def log(what)
@logger.debug(what) if @logger
end
#
# Accessing data outside the document
#
# Return the title of the document
def title
@ie.document.title
end
# Return the status of the window, typically from the status bar at the bottom.
def status
@ie.statusText
rescue WIN32OLERuntimeError
""
end
#
# Navigation
#
# Navigate to the specified URL.
# * url - string - the URL to navigate to
def goto(url)
url = "http://" + url unless url =~ %r{://} || url == "about:blank"
@ie.navigate(url)
wait
return @down_load_time
end
# Go to the previous page - the same as clicking the browsers back button
# an WIN32OLERuntimeError exception is raised if the browser cant go back
def back
@ie.GoBack
wait
end
# Go to the next page - the same as clicking the browsers forward button
# an WIN32OLERuntimeError exception is raised if the browser cant go forward
def forward
@ie.GoForward
wait
end
# Refresh the current page - the same as clicking the browsers refresh button
# an WIN32OLERuntimeError exception is raised if the browser cant refresh
def refresh
@ie.refresh2(3)
wait
end
def inspect
'#<%s:0x%x url=%s title=%s>' % [self.class, hash*2, url.inspect, title.inspect]
end
# clear the list of urls that we have visited
def clear_url_list
@url_list.clear
end
# Closes the Browser
def close
return unless exists?
@ie.stop
wait rescue nil
chwnd = @ie.hwnd.to_i
@ie.quit
t = ::Time.now
while exists?
# just in case to avoid possible endless loop if failing to close some
# window or tab
break if ::Time.now - t > 10
sleep 0.3
end
end
# Maximize the window (expands to fill the screen)
def maximize
rautomation.maximize
end
# Minimize the window (appears as icon on taskbar)
def minimize
rautomation.minimize
end
def minimized?
rautomation.minimized?
end
# Restore the window (after minimizing or maximizing)
def restore
rautomation.restore
end
# Make the window come to the front
def activate
rautomation.activate
end
alias :bring_to_front :activate
def active?
rautomation.active?
end
alias :front? :active?
def rautomation
@rautomation ||= ::RAutomation::Window.new(:hwnd => hwnd)
@rautomation
end
def autoit
Kernel.warn "Usage of Watir::IE#autoit method is DEPRECATED! Use Watir::IE#rautomation method instead. Refer to https://github.com/jarmo/RAutomation for updating your scripts."
@autoit ||= ::RAutomation::Window.new(:hwnd => hwnd, :adapter => :autoit)
@autoit
end
# Activates the window and sends keys to it.
#
# Example:
# browser.send_keys("Hello World{enter}")
#
# Refer to RAutomation::Adapter::WinFfi::KeystrokeConverter.convert_special_characters for
# special characters conversion.
# @see RAutomation::Window#send_keys
def send_keys(*keys)
rautomation.send_keys *keys
end
def dir
return File.expand_path(File.dirname(__FILE__))
end
#
# Document and Document Data
#
# Return the current document
def document
return @ie.document
end
# returns the current url, as displayed in the address bar of the browser
def url
return @ie.LocationURL
end
def screenshot
Screenshot.new(hwnd)
end
def window(specifiers={}, &blk)
win = Window.new(self, specifiers, &blk)
win.use &blk if blk
win
end
def windows(specifiers={}, &blk)
self.class._find_all(specifiers.keys.first, specifiers.values.first).map {|ie| Window.new(self, specifiers, IE.bind(ie), &blk)}
end
def cookies
Cookies.new(self)
end
#
# Synchronization
#
# Block execution until the page has loaded.
#
# Will raise Timeout::Error if page hasn't been loaded within 5 minutes.
# =nodoc
# Note: This code needs to be prepared for the ie object to be closed at
# any moment!
def wait(no_sleep=false)
@xml_parser_doc = nil
@down_load_time = 0.0
interval = 0.05
start_load_time = ::Time.now
Timeout::timeout(5*60) do
begin
while @ie.busy
sleep interval
end
until READYSTATES.has_value?(@ie.readyState)
sleep interval
end
until @ie.document
sleep interval
end
documents_to_wait_for = [@ie.document]
rescue WIN32OLERuntimeError # IE window must have been closed
@down_load_time = ::Time.now - start_load_time
return @down_load_time
end
while doc = documents_to_wait_for.shift
begin
until READYSTATES.has_key?(doc.readyState.to_sym)
sleep interval
end
@url_list << doc.location.href unless @url_list.include?(doc.location.href)
doc.frames.length.times do |n|
begin
documents_to_wait_for << doc.frames[n.to_s].document
rescue WIN32OLERuntimeError, NoMethodError
end
end
rescue WIN32OLERuntimeError
end
end
end
@down_load_time = ::Time.now - start_load_time
run_error_checks
sleep @pause_after_wait unless no_sleep
@down_load_time
end
# Error checkers
# this method runs the predefined error checks
def run_error_checks
@error_checkers.each { |e| e.call(self) }
end
# this method is used to add an error checker that gets executed on every page load
# * checker Proc Object, that contains the code to be run
def add_checker(checker)
@error_checkers << checker
end
# this allows a checker to be disabled
# * checker Proc Object, the checker that is to be disabled
def disable_checker(checker)
@error_checkers.delete(checker)
end
#
# Show me state
#
# Show all forms displays all the forms that are on a web page.
def show_forms
if all_forms = self.forms
count = all_forms.length
puts "There are #{count} forms"
all_forms.each do |form|
puts "Form name: #{form.name}"
puts " id: #{form.id}"
puts " method: #{form.method}"
puts " action: #{form.action}"
end
else
puts "No forms"
end
end
# this method shows all the images availble in the document
def show_images
doc = document
index = 1
doc.images.each do |l|
puts "image: name: #{l.name}"
puts " id: #{l.invoke("id")}"
puts " src: #{l.src}"
puts " index: #{index}"
index += 1
end
end
# this method shows all the links availble in the document
def show_links
props = ["name", "id", "href"]
print_sizes = [12, 12, 60]
doc = document
index = 0
text_size = 60
# draw the table header
s = "index".ljust(6)
props.each_with_index do |p, i|
s += p.ljust(print_sizes[i])
end
s += "text/src".ljust(text_size)
s += "\n"
# now get the details of the links
doc.links.each do |n|
index += 1
s = s + index.to_s.ljust(6)
props.each_with_index do |prop, i|
printsize = print_sizes[i]
begin
p = n.invoke(prop)
temp_var = "#{p}".to_s.ljust(printsize)
rescue
# this object probably doesnt have this property
temp_var = "".to_s.ljust(printsize)
end
s += temp_var
end
s += n.innerText
if n.getElementsByTagName("IMG").length > 0
s += " / " + n.getElementsByTagName("IMG").item(0).src
end
s += "\n"
end
puts s
end
# this method shows the name, id etc of the object that is currently active - ie the element that has focus
# its mostly used in irb when creating a script
def show_active
s = ""
current = document.activeElement
begin
s += current.invoke("type").to_s.ljust(16)
rescue
end
props = ["name", "id", "value", "alt", "src", "innerText", "href"]
props.each do |prop|
begin
p = current.invoke(prop)
s += " " + "#{prop}=#{p}".to_s.ljust(18)
rescue
#this object probably doesnt have this property
end
end
s += "\n"
end
# this method shows all the divs availble in the document
def show_divs
divs = document.getElementsByTagName("DIV")
puts "Found #{divs.length} div tags"
index = 1
divs.each do |d|
puts "#{index} id=#{d.invoke('id')} class=#{d.invoke("className")}"
index += 1
end
end
# this method is used to show all the tables that are available
def show_tables
tables = document.getElementsByTagName("TABLE")
puts "Found #{tables.length} tables"
index = 1
tables.each do |d|
puts "#{index} id=#{d.invoke('id')} rows=#{d.rows.length} columns=#{begin d.rows["0"].cells.length; rescue; end}"
index += 1
end
end
def show_pres
pres = document.getElementsByTagName("PRE")
puts "Found #{ pres.length } pre tags"
index = 1
pres.each do |d|
puts "#{index} id=#{d.invoke('id')} class=#{d.invoke("className")}"
index+=1
end
end
# this method shows all the spans availble in the document
def show_spans
spans = document.getElementsByTagName("SPAN")
puts "Found #{spans.length} span tags"
index = 1
spans.each do |d|
puts "#{index} id=#{d.invoke('id')} class=#{d.invoke("className")}"
index += 1
end
end
def show_labels
labels = document.getElementsByTagName("LABEL")
puts "Found #{labels.length} label tags"
index = 1
labels.each do |d|
puts "#{index} text=#{d.invoke('innerText')} class=#{d.invoke("className")} for=#{d.invoke("htmlFor")}"
index += 1
end
end
# Gives focus to the frame
def focus
active_element = document.activeElement
active_element.blur unless active_element.tagName == "BODY"
document.focus
end
def attach_command
"Watir::IE.attach(:hwnd, #{hwnd})"
end
private
def create_browser_window
@ie = WIN32OLE.new('InternetExplorer.Application')
end
def attach_browser_window how, what
log "Seeking Window with #{how}: #{what}"
ieTemp = nil
begin
Watir::until_with_timeout do
ieTemp = IE._find how, what
end
rescue Watir::Wait::TimeoutError
raise NoMatchingWindowFoundException,
"Unable to locate a window with #{how} of #{what}"
end
@ie = ieTemp
end
end # class IE
end