watir 1.5.2 → 1.5.3
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.rb +20 -12
- data/unittests/html/table1.html +41 -1
- data/unittests/links_test.rb +2 -2
- data/unittests/navigate_test.rb +2 -2
- data/unittests/parent_child_test.rb +0 -15
- data/unittests/table_test.rb +5 -2
- data/unittests/textarea_test.rb +55 -39
- data/unittests/windows/iedialog_test.rb +2 -2
- data/watir.rb +20 -4386
- data/watir/{elements.rb → bonus-elements.rb} +0 -0
- data/watir/collections.rb +317 -0
- data/watir/container.rb +883 -0
- data/watir/element.rb +306 -0
- data/watir/element_collections.rb +82 -0
- data/watir/form.rb +151 -0
- data/watir/frame.rb +60 -0
- data/watir/ie.rb +973 -0
- data/watir/image.rb +131 -0
- data/watir/input_elements.rb +518 -0
- data/watir/link.rb +65 -0
- data/watir/locator.rb +79 -0
- data/watir/logger.rb +19 -0
- data/watir/modal_dialog.rb +123 -0
- data/watir/non_control_elements.rb +91 -0
- data/watir/page-container.rb +106 -0
- data/watir/popup.rb +30 -0
- data/watir/table.rb +356 -0
- data/watir/win32.rb +29 -0
- metadata +21 -3
data/watir/frame.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
module Watir
|
2
|
+
class Frame
|
3
|
+
include Container
|
4
|
+
include PageContainer
|
5
|
+
|
6
|
+
# Find the frame denoted by how and what in the container and return its ole_object
|
7
|
+
def locate
|
8
|
+
how = @how
|
9
|
+
what = @what
|
10
|
+
frames = @container.document.frames
|
11
|
+
target = nil
|
12
|
+
|
13
|
+
for i in 0..(frames.length - 1)
|
14
|
+
this_frame = frames.item(i)
|
15
|
+
case how
|
16
|
+
when :index
|
17
|
+
index = i + 1
|
18
|
+
return this_frame if index == what
|
19
|
+
when :name
|
20
|
+
begin
|
21
|
+
return this_frame if what.matches(this_frame.name)
|
22
|
+
rescue # access denied?
|
23
|
+
end
|
24
|
+
when :id
|
25
|
+
# We assume that pages contain frames or iframes, but not both.
|
26
|
+
this_frame_tag = @container.document.getElementsByTagName("FRAME").item(i)
|
27
|
+
return this_frame if this_frame_tag and what.matches(this_frame_tag.invoke("id"))
|
28
|
+
this_iframe_tag = @container.document.getElementsByTagName("IFRAME").item(i)
|
29
|
+
return this_frame if this_iframe_tag and what.matches(this_iframe_tag.invoke("id"))
|
30
|
+
when :src
|
31
|
+
this_frame_tag = @container.document.getElementsByTagName("FRAME").item(i)
|
32
|
+
return this_frame if this_frame_tag and what.matches(this_frame_tag.src)
|
33
|
+
this_iframe_tag = @container.document.getElementsByTagName("IFRAME").item(i)
|
34
|
+
return this_frame if this_iframe_tag and what.matches(this_iframe_tag.src)
|
35
|
+
else
|
36
|
+
raise ArgumentError, "Argument #{how} not supported"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
raise UnknownFrameException, "Unable to locate a frame with #{how.to_s} #{what}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize(container, how, what)
|
44
|
+
set_container container
|
45
|
+
@how = how
|
46
|
+
@what = what
|
47
|
+
@o = locate
|
48
|
+
copy_test_config container
|
49
|
+
end
|
50
|
+
|
51
|
+
def document
|
52
|
+
@o.document
|
53
|
+
end
|
54
|
+
|
55
|
+
def attach_command
|
56
|
+
@container.page_container.attach_command + ".frame(#{@how.inspect}, #{@what.inspect})"
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
data/watir/ie.rb
ADDED
@@ -0,0 +1,973 @@
|
|
1
|
+
module Watir
|
2
|
+
class IE
|
3
|
+
include Watir::Exception
|
4
|
+
include Container
|
5
|
+
include PageContainer
|
6
|
+
|
7
|
+
def self.quit
|
8
|
+
end
|
9
|
+
|
10
|
+
# Maximum number of seconds to wait when attaching to a window
|
11
|
+
def self.reset_attach_timeout
|
12
|
+
@@attach_timeout = 2.0
|
13
|
+
end
|
14
|
+
reset_attach_timeout
|
15
|
+
def self.attach_timeout
|
16
|
+
@@attach_timeout
|
17
|
+
end
|
18
|
+
def self.attach_timeout=(timeout)
|
19
|
+
@@attach_timeout = timeout
|
20
|
+
end
|
21
|
+
|
22
|
+
# The revision number (according to Subversion)
|
23
|
+
REVISION_STRING = '$Revision: 1263 $'
|
24
|
+
REVISION_STRING.scan(/Revision: (\d*)/)
|
25
|
+
REVISION = $1 or 'unknown'
|
26
|
+
|
27
|
+
# The Release number
|
28
|
+
VERSION_SHORT = '1.5.3'
|
29
|
+
VERSION = VERSION_SHORT + '.' + REVISION
|
30
|
+
|
31
|
+
# Used internally to determine when IE has finished loading a page
|
32
|
+
READYSTATE_COMPLETE = 4
|
33
|
+
|
34
|
+
# TODO: the following constants should be able to be specified by object (not class)
|
35
|
+
|
36
|
+
# The delay when entering text on a web page when speed = :slow.
|
37
|
+
DEFAULT_TYPING_SPEED = 0.08
|
38
|
+
|
39
|
+
# The default time we wait after a page has loaded when speed = :slow.
|
40
|
+
DEFAULT_SLEEP_TIME = 0.1
|
41
|
+
|
42
|
+
# The default color for highlighting objects as they are accessed.
|
43
|
+
HIGHLIGHT_COLOR = 'yellow'
|
44
|
+
|
45
|
+
# IE inserts some element whose tagName is empty and just acts as block level element
|
46
|
+
# Probably some IE method of cleaning things
|
47
|
+
# To pass the same to REXML we need to give some name to empty tagName
|
48
|
+
EMPTY_TAG_NAME = "DUMMY"
|
49
|
+
|
50
|
+
# The time, in seconds, it took for the new page to load after executing the
|
51
|
+
# the last command
|
52
|
+
attr_reader :down_load_time
|
53
|
+
|
54
|
+
# Whether the speed is :fast or :slow
|
55
|
+
attr_reader :speed
|
56
|
+
|
57
|
+
# the OLE Internet Explorer object
|
58
|
+
attr_accessor :ie
|
59
|
+
|
60
|
+
# access to the logger object
|
61
|
+
attr_accessor :logger
|
62
|
+
|
63
|
+
# this contains the list of unique urls that have been visited
|
64
|
+
attr_reader :url_list
|
65
|
+
|
66
|
+
# Create a new IE window. Works just like IE.new in Watir 1.4.
|
67
|
+
def self.new_window
|
68
|
+
ie = new true
|
69
|
+
ie._new_window_init
|
70
|
+
ie
|
71
|
+
end
|
72
|
+
|
73
|
+
# Create an IE browser.
|
74
|
+
def initialize suppress_new_window=nil
|
75
|
+
_new_window_init unless suppress_new_window
|
76
|
+
end
|
77
|
+
|
78
|
+
def _new_window_init
|
79
|
+
create_browser_window
|
80
|
+
set_defaults
|
81
|
+
goto 'about:blank' # this avoids numerous problems caused by lack of a document
|
82
|
+
end
|
83
|
+
|
84
|
+
# Create a new IE Window, starting at the specified url.
|
85
|
+
# If no url is given, start empty.
|
86
|
+
def self.start url=nil
|
87
|
+
start_window url
|
88
|
+
end
|
89
|
+
|
90
|
+
# Create a new IE window, starting at the specified url.
|
91
|
+
# If no url is given, start empty. Works like IE.start in Watir 1.4.
|
92
|
+
def self.start_window url=nil
|
93
|
+
ie = new_window
|
94
|
+
ie.goto url if url
|
95
|
+
ie
|
96
|
+
end
|
97
|
+
|
98
|
+
# Create a new IE window in a new process.
|
99
|
+
# This method will not work when
|
100
|
+
# Watir/Ruby is run under a service (instead of a user).
|
101
|
+
def self.new_process
|
102
|
+
ie = new true
|
103
|
+
ie._new_process_init
|
104
|
+
ie
|
105
|
+
end
|
106
|
+
|
107
|
+
def _new_process_init
|
108
|
+
iep = Process.start
|
109
|
+
@ie = iep.window
|
110
|
+
@process_id = iep.process_id
|
111
|
+
set_defaults
|
112
|
+
goto 'about:blank'
|
113
|
+
end
|
114
|
+
|
115
|
+
# Create a new IE window in a new process, starting at the specified URL.
|
116
|
+
# Same as IE.start.
|
117
|
+
def self.start_process url=nil
|
118
|
+
ie = new_process
|
119
|
+
ie.goto url if url
|
120
|
+
ie
|
121
|
+
end
|
122
|
+
|
123
|
+
# Return a Watir::IE object for an existing IE window. Window can be
|
124
|
+
# referenced by url, title, or window handle.
|
125
|
+
# Second argument can be either a string or a regular expression in the
|
126
|
+
# case of of :url or :title.
|
127
|
+
# IE.attach(:url, 'http://www.google.com')
|
128
|
+
# IE.attach(:title, 'Google')
|
129
|
+
# IE.attach(:hwnd, 528140)
|
130
|
+
# This method will not work when
|
131
|
+
# Watir/Ruby is run under a service (instead of a user).
|
132
|
+
def self.attach how, what
|
133
|
+
ie = new true # don't create window
|
134
|
+
ie._attach_init(how, what)
|
135
|
+
ie
|
136
|
+
end
|
137
|
+
|
138
|
+
# this method is used internally to attach to an existing window
|
139
|
+
def _attach_init how, what
|
140
|
+
attach_browser_window how, what
|
141
|
+
set_defaults
|
142
|
+
wait
|
143
|
+
end
|
144
|
+
|
145
|
+
# Return an IE object that wraps the given window, typically obtained from
|
146
|
+
# Shell.Application.windows.
|
147
|
+
def self.bind window
|
148
|
+
ie = new true
|
149
|
+
ie.ie = window
|
150
|
+
ie.set_defaults
|
151
|
+
ie
|
152
|
+
end
|
153
|
+
|
154
|
+
def create_browser_window
|
155
|
+
@ie = WIN32OLE.new('InternetExplorer.Application')
|
156
|
+
end
|
157
|
+
private :create_browser_window
|
158
|
+
|
159
|
+
def set_defaults
|
160
|
+
self.visible = ! $HIDE_IE
|
161
|
+
@ole_object = nil
|
162
|
+
@page_container = self
|
163
|
+
@error_checkers = []
|
164
|
+
@activeObjectHighLightColor = HIGHLIGHT_COLOR
|
165
|
+
|
166
|
+
if $FAST_SPEED
|
167
|
+
set_fast_speed
|
168
|
+
else
|
169
|
+
set_slow_speed
|
170
|
+
end
|
171
|
+
|
172
|
+
@logger = DefaultLogger.new
|
173
|
+
@url_list = []
|
174
|
+
end
|
175
|
+
|
176
|
+
def speed= how_fast
|
177
|
+
case how_fast
|
178
|
+
when :fast : set_fast_speed
|
179
|
+
when :slow : set_slow_speed
|
180
|
+
else
|
181
|
+
raise ArgumentError, "Invalid speed: #{how_fast}"
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# deprecated: use speed = :fast instead
|
186
|
+
def set_fast_speed
|
187
|
+
@typingspeed = 0
|
188
|
+
@defaultSleepTime = 0.01
|
189
|
+
@speed = :fast
|
190
|
+
end
|
191
|
+
|
192
|
+
# deprecated: use speed = :slow instead
|
193
|
+
def set_slow_speed
|
194
|
+
@typingspeed = DEFAULT_TYPING_SPEED
|
195
|
+
@defaultSleepTime = DEFAULT_SLEEP_TIME
|
196
|
+
@speed = :slow
|
197
|
+
end
|
198
|
+
|
199
|
+
def visible
|
200
|
+
@ie.visible
|
201
|
+
end
|
202
|
+
def visible=(boolean)
|
203
|
+
@ie.visible = boolean if boolean != @ie.visible
|
204
|
+
end
|
205
|
+
|
206
|
+
# Yields successively to each IE window on the current desktop. Takes a block.
|
207
|
+
# This method will not work when
|
208
|
+
# Watir/Ruby is run under a service (instead of a user).
|
209
|
+
# Yields to the window and its hwnd.
|
210
|
+
def self.each
|
211
|
+
shell = WIN32OLE.new('Shell.Application')
|
212
|
+
shell.Windows.each do |window|
|
213
|
+
next unless (window.path =~ /Internet Explorer/ rescue false)
|
214
|
+
next unless (hwnd = window.hwnd rescue false)
|
215
|
+
ie = IE.bind(window)
|
216
|
+
ie.hwnd = hwnd
|
217
|
+
yield ie
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# return internet explorer instance as specified. if none is found,
|
222
|
+
# return nil.
|
223
|
+
# arguments:
|
224
|
+
# :url, url -- the URL of the IE browser window
|
225
|
+
# :title, title -- the title of the browser page
|
226
|
+
# :hwnd, hwnd -- the window handle of the browser window.
|
227
|
+
# This method will not work when
|
228
|
+
# Watir/Ruby is run under a service (instead of a user).
|
229
|
+
def self.find(how, what)
|
230
|
+
ie_ole = IE._find(how, what)
|
231
|
+
IE.bind ie_ole if ie_ole
|
232
|
+
end
|
233
|
+
|
234
|
+
def self._find(how, what)
|
235
|
+
ieTemp = nil
|
236
|
+
IE.each do |ie|
|
237
|
+
window = ie.ie
|
238
|
+
|
239
|
+
case how
|
240
|
+
when :url
|
241
|
+
ieTemp = window if (what.matches(window.locationURL))
|
242
|
+
when :title
|
243
|
+
# normal windows explorer shells do not have document
|
244
|
+
# note window.document will fail for "new" browsers
|
245
|
+
begin
|
246
|
+
title = window.locationname
|
247
|
+
title = window.document.title
|
248
|
+
rescue WIN32OLERuntimeError
|
249
|
+
end
|
250
|
+
ieTemp = window if what.matches(title)
|
251
|
+
when :hwnd
|
252
|
+
begin
|
253
|
+
ieTemp = window if what == window.HWND
|
254
|
+
rescue WIN32OLERuntimeError
|
255
|
+
end
|
256
|
+
else
|
257
|
+
raise ArgumentError
|
258
|
+
end
|
259
|
+
end
|
260
|
+
return ieTemp
|
261
|
+
end
|
262
|
+
|
263
|
+
def attach_browser_window how, what
|
264
|
+
log "Seeking Window with #{how}: #{what}"
|
265
|
+
ieTemp = nil
|
266
|
+
begin
|
267
|
+
Watir::until_with_timeout do
|
268
|
+
ieTemp = IE._find how, what
|
269
|
+
end
|
270
|
+
rescue TimeOutException
|
271
|
+
raise NoMatchingWindowFoundException,
|
272
|
+
"Unable to locate a window with #{how} of #{what}"
|
273
|
+
end
|
274
|
+
@ie = ieTemp
|
275
|
+
end
|
276
|
+
private :attach_browser_window
|
277
|
+
|
278
|
+
# Return the current window handle
|
279
|
+
def hwnd
|
280
|
+
raise "Not attached to a browser" if @ie.nil?
|
281
|
+
@hwnd ||= @ie.hwnd
|
282
|
+
end
|
283
|
+
attr_writer :hwnd
|
284
|
+
|
285
|
+
include Watir::Win32
|
286
|
+
|
287
|
+
# Are we attached to an open browser?
|
288
|
+
def exists?
|
289
|
+
return false if @closing
|
290
|
+
begin
|
291
|
+
@ie.name =~ /Internet Explorer/
|
292
|
+
rescue WIN32OLERuntimeError
|
293
|
+
false
|
294
|
+
end
|
295
|
+
end
|
296
|
+
alias :exist? :exists?
|
297
|
+
|
298
|
+
# deprecated: use logger= instead
|
299
|
+
def set_logger(logger)
|
300
|
+
@logger = logger
|
301
|
+
end
|
302
|
+
|
303
|
+
def log(what)
|
304
|
+
@logger.debug(what) if @logger
|
305
|
+
end
|
306
|
+
|
307
|
+
#
|
308
|
+
# Accessing data outside the document
|
309
|
+
#
|
310
|
+
|
311
|
+
# Return the title of the document
|
312
|
+
def title
|
313
|
+
@ie.document.title
|
314
|
+
end
|
315
|
+
|
316
|
+
# Return the status of the window, typically from the status bar at the bottom.
|
317
|
+
def status
|
318
|
+
raise NoStatusBarException if !@ie.statusBar
|
319
|
+
return @ie.statusText
|
320
|
+
end
|
321
|
+
|
322
|
+
#
|
323
|
+
# Navigation
|
324
|
+
#
|
325
|
+
|
326
|
+
# Navigate to the specified URL.
|
327
|
+
# * url - string - the URL to navigate to
|
328
|
+
def goto(url)
|
329
|
+
@ie.navigate(url)
|
330
|
+
wait
|
331
|
+
return @down_load_time
|
332
|
+
end
|
333
|
+
|
334
|
+
# Go to the previous page - the same as clicking the browsers back button
|
335
|
+
# an WIN32OLERuntimeError exception is raised if the browser cant go back
|
336
|
+
def back
|
337
|
+
@ie.GoBack
|
338
|
+
wait
|
339
|
+
end
|
340
|
+
|
341
|
+
# Go to the next page - the same as clicking the browsers forward button
|
342
|
+
# an WIN32OLERuntimeError exception is raised if the browser cant go forward
|
343
|
+
def forward
|
344
|
+
@ie.GoForward
|
345
|
+
wait
|
346
|
+
end
|
347
|
+
|
348
|
+
# Refresh the current page - the same as clicking the browsers refresh button
|
349
|
+
# an WIN32OLERuntimeError exception is raised if the browser cant refresh
|
350
|
+
def refresh
|
351
|
+
@ie.refresh2(3)
|
352
|
+
wait
|
353
|
+
end
|
354
|
+
|
355
|
+
# clear the list of urls that we have visited
|
356
|
+
def clear_url_list
|
357
|
+
@url_list.clear
|
358
|
+
end
|
359
|
+
|
360
|
+
# Closes the Browser
|
361
|
+
def close
|
362
|
+
@closing = true
|
363
|
+
@ie.quit
|
364
|
+
end
|
365
|
+
|
366
|
+
# Maximize the window (expands to fill the screen)
|
367
|
+
def maximize
|
368
|
+
set_window_state :SW_MAXIMIZE
|
369
|
+
end
|
370
|
+
|
371
|
+
# Minimize the window (appears as icon on taskbar)
|
372
|
+
def minimize
|
373
|
+
set_window_state :SW_MINIMIZE
|
374
|
+
end
|
375
|
+
|
376
|
+
# Restore the window (after minimizing or maximizing)
|
377
|
+
def restore
|
378
|
+
set_window_state :SW_RESTORE
|
379
|
+
end
|
380
|
+
|
381
|
+
# Make the window come to the front
|
382
|
+
def bring_to_front
|
383
|
+
autoit.WinActivate title, ''
|
384
|
+
end
|
385
|
+
|
386
|
+
def front?
|
387
|
+
1 == autoit.WinActive(title, '')
|
388
|
+
end
|
389
|
+
|
390
|
+
private
|
391
|
+
def set_window_state(state)
|
392
|
+
autoit.WinSetState title, '', autoit.send(state)
|
393
|
+
end
|
394
|
+
def autoit
|
395
|
+
Watir::autoit
|
396
|
+
end
|
397
|
+
public
|
398
|
+
|
399
|
+
# Send key events to IE window.
|
400
|
+
# See http://www.autoitscript.com/autoit3/docs/appendix/SendKeys.htm
|
401
|
+
# for complete documentation on keys supported and syntax.
|
402
|
+
def send_keys(key_string)
|
403
|
+
autoit.WinActivate title
|
404
|
+
autoit.Send key_string
|
405
|
+
end
|
406
|
+
|
407
|
+
def dir
|
408
|
+
return File.expand_path(File.dirname(__FILE__))
|
409
|
+
end
|
410
|
+
|
411
|
+
#
|
412
|
+
# Document and Document Data
|
413
|
+
#
|
414
|
+
|
415
|
+
# Return the current document
|
416
|
+
def document
|
417
|
+
return @ie.document
|
418
|
+
end
|
419
|
+
|
420
|
+
# returns the current url, as displayed in the address bar of the browser
|
421
|
+
def url
|
422
|
+
return @ie.LocationURL
|
423
|
+
end
|
424
|
+
|
425
|
+
#
|
426
|
+
# Synchronization
|
427
|
+
#
|
428
|
+
include Watir::Utils
|
429
|
+
|
430
|
+
# Block execution until the page has loaded.
|
431
|
+
# =nodoc
|
432
|
+
# Note: This code needs to be prepared for the ie object to be closed at
|
433
|
+
# any moment!
|
434
|
+
def wait(no_sleep=false)
|
435
|
+
@rexmlDomobject = nil
|
436
|
+
@down_load_time = 0.0
|
437
|
+
a_moment = 0.2 # seconds
|
438
|
+
start_load_time = Time.now
|
439
|
+
|
440
|
+
begin
|
441
|
+
while @ie.busy # XXX need to add time out
|
442
|
+
sleep a_moment
|
443
|
+
end
|
444
|
+
until @ie.readyState == READYSTATE_COMPLETE do
|
445
|
+
sleep a_moment
|
446
|
+
end
|
447
|
+
sleep a_moment
|
448
|
+
until @ie.document do
|
449
|
+
sleep a_moment
|
450
|
+
end
|
451
|
+
|
452
|
+
documents_to_wait_for = [@ie.document]
|
453
|
+
|
454
|
+
rescue WIN32OLERuntimeError # IE window must have been closed
|
455
|
+
@down_load_time = Time.now - start_load_time
|
456
|
+
sleep @defaultSleepTime unless no_sleep
|
457
|
+
return @down_load_time
|
458
|
+
end
|
459
|
+
|
460
|
+
while doc = documents_to_wait_for.shift
|
461
|
+
begin
|
462
|
+
until doc.readyState == "complete" do
|
463
|
+
sleep a_moment
|
464
|
+
end
|
465
|
+
@url_list << doc.url unless @url_list.include?(doc.url)
|
466
|
+
doc.frames.length.times do |n|
|
467
|
+
begin
|
468
|
+
documents_to_wait_for << doc.frames[n.to_s].document
|
469
|
+
rescue WIN32OLERuntimeError
|
470
|
+
end
|
471
|
+
end
|
472
|
+
rescue WIN32OLERuntimeError
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
@down_load_time = Time.now - start_load_time
|
477
|
+
run_error_checks
|
478
|
+
sleep @defaultSleepTime unless no_sleep
|
479
|
+
@down_load_time
|
480
|
+
end
|
481
|
+
|
482
|
+
# Error checkers
|
483
|
+
|
484
|
+
# this method runs the predefined error checks
|
485
|
+
def run_error_checks
|
486
|
+
@error_checkers.each { |e| e.call(self) }
|
487
|
+
end
|
488
|
+
|
489
|
+
# this method is used to add an error checker that gets executed on every page load
|
490
|
+
# * checker Proc Object, that contains the code to be run
|
491
|
+
def add_checker(checker)
|
492
|
+
@error_checkers << checker
|
493
|
+
end
|
494
|
+
|
495
|
+
# this allows a checker to be disabled
|
496
|
+
# * checker Proc Object, the checker that is to be disabled
|
497
|
+
def disable_checker(checker)
|
498
|
+
@error_checkers.delete(checker)
|
499
|
+
end
|
500
|
+
|
501
|
+
#
|
502
|
+
# Show me state
|
503
|
+
#
|
504
|
+
|
505
|
+
# Show all forms displays all the forms that are on a web page.
|
506
|
+
def show_forms
|
507
|
+
if allForms = document.forms
|
508
|
+
count = allForms.length
|
509
|
+
puts "There are #{count} forms"
|
510
|
+
for i in 0..count-1 do
|
511
|
+
wrapped = FormWrapper.new(allForms.item(i))
|
512
|
+
puts "Form name: #{wrapped.name}"
|
513
|
+
puts " id: #{wrapped.id}"
|
514
|
+
puts " method: #{wrapped.method}"
|
515
|
+
puts " action: #{wrapped.action}"
|
516
|
+
end
|
517
|
+
else
|
518
|
+
puts "No forms"
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
# this method shows all the images availble in the document
|
523
|
+
def show_images
|
524
|
+
doc = document
|
525
|
+
index = 1
|
526
|
+
doc.images.each do |l|
|
527
|
+
puts "image: name: #{l.name}"
|
528
|
+
puts " id: #{l.invoke("id")}"
|
529
|
+
puts " src: #{l.src}"
|
530
|
+
puts " index: #{index}"
|
531
|
+
index += 1
|
532
|
+
end
|
533
|
+
end
|
534
|
+
|
535
|
+
# this method shows all the links availble in the document
|
536
|
+
def show_links
|
537
|
+
props = ["name", "id", "href"]
|
538
|
+
print_sizes = [12, 12, 60]
|
539
|
+
doc = document
|
540
|
+
index = 0
|
541
|
+
text_size = 60
|
542
|
+
# draw the table header
|
543
|
+
s = "index".ljust(6)
|
544
|
+
props.each_with_index do |p, i|
|
545
|
+
s += p.ljust(print_sizes[i])
|
546
|
+
end
|
547
|
+
s += "text/src".ljust(text_size)
|
548
|
+
s += "\n"
|
549
|
+
|
550
|
+
# now get the details of the links
|
551
|
+
doc.links.each do |n|
|
552
|
+
index += 1
|
553
|
+
s = s + index.to_s.ljust(6)
|
554
|
+
props.each_with_index do |prop, i|
|
555
|
+
printsize = print_sizes[i]
|
556
|
+
begin
|
557
|
+
p = n.invoke(prop)
|
558
|
+
temp_var = "#{p}".to_s.ljust(printsize)
|
559
|
+
rescue
|
560
|
+
# this object probably doesnt have this property
|
561
|
+
temp_var = "".to_s.ljust(printsize)
|
562
|
+
end
|
563
|
+
s += temp_var
|
564
|
+
end
|
565
|
+
s += n.innerText
|
566
|
+
if n.getElementsByTagName("IMG").length > 0
|
567
|
+
s += " / " + n.getElementsByTagName("IMG")[0.to_s].src
|
568
|
+
end
|
569
|
+
s += "\n"
|
570
|
+
end
|
571
|
+
puts s
|
572
|
+
end
|
573
|
+
|
574
|
+
# this method shows the name, id etc of the object that is currently active - ie the element that has focus
|
575
|
+
# its mostly used in irb when creating a script
|
576
|
+
def show_active
|
577
|
+
s = ""
|
578
|
+
|
579
|
+
current = document.activeElement
|
580
|
+
begin
|
581
|
+
s += current.invoke("type").to_s.ljust(16)
|
582
|
+
rescue
|
583
|
+
end
|
584
|
+
props = ["name", "id", "value", "alt", "src", "innerText", "href"]
|
585
|
+
props.each do |prop|
|
586
|
+
begin
|
587
|
+
p = current.invoke(prop)
|
588
|
+
s += " " + "#{prop}=#{p}".to_s.ljust(18)
|
589
|
+
rescue
|
590
|
+
#this object probably doesnt have this property
|
591
|
+
end
|
592
|
+
end
|
593
|
+
s += "\n"
|
594
|
+
end
|
595
|
+
|
596
|
+
# this method shows all the divs availble in the document
|
597
|
+
def show_divs
|
598
|
+
divs = document.getElementsByTagName("DIV")
|
599
|
+
puts "Found #{divs.length} div tags"
|
600
|
+
index = 1
|
601
|
+
divs.each do |d|
|
602
|
+
puts "#{index} id=#{d.invoke('id')} class=#{d.invoke("className")}"
|
603
|
+
index += 1
|
604
|
+
end
|
605
|
+
end
|
606
|
+
|
607
|
+
# this method is used to show all the tables that are available
|
608
|
+
def show_tables
|
609
|
+
tables = document.getElementsByTagName("TABLE")
|
610
|
+
puts "Found #{tables.length} tables"
|
611
|
+
index = 1
|
612
|
+
tables.each do |d|
|
613
|
+
puts "#{index} id=#{d.invoke('id')} rows=#{d.rows.length} columns=#{begin d.rows["0"].cells.length; rescue; end}"
|
614
|
+
index += 1
|
615
|
+
end
|
616
|
+
end
|
617
|
+
|
618
|
+
def show_pres
|
619
|
+
pres = document.getElementsByTagName("PRE")
|
620
|
+
puts "Found #{ pres.length } pre tags"
|
621
|
+
index = 1
|
622
|
+
pres.each do |d|
|
623
|
+
puts "#{index} id=#{d.invoke('id')} class=#{d.invoke("className")}"
|
624
|
+
index+=1
|
625
|
+
end
|
626
|
+
end
|
627
|
+
|
628
|
+
# this method shows all the spans availble in the document
|
629
|
+
def show_spans
|
630
|
+
spans = document.getElementsByTagName("SPAN")
|
631
|
+
puts "Found #{spans.length} span tags"
|
632
|
+
index = 1
|
633
|
+
spans.each do |d|
|
634
|
+
puts "#{index} id=#{d.invoke('id')} class=#{d.invoke("className")}"
|
635
|
+
index += 1
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
639
|
+
def show_labels
|
640
|
+
labels = document.getElementsByTagName("LABEL")
|
641
|
+
puts "Found #{labels.length} label tags"
|
642
|
+
index = 1
|
643
|
+
labels.each do |d|
|
644
|
+
puts "#{index} text=#{d.invoke('innerText')} class=#{d.invoke("className")} for=#{d.invoke("htmlFor")}"
|
645
|
+
index += 1
|
646
|
+
end
|
647
|
+
end
|
648
|
+
|
649
|
+
# Gives focus to the frame
|
650
|
+
def focus
|
651
|
+
document.activeElement.blur
|
652
|
+
document.focus
|
653
|
+
end
|
654
|
+
|
655
|
+
#
|
656
|
+
# Functions written for using xpath for getting the elements.
|
657
|
+
#
|
658
|
+
|
659
|
+
# Get the Rexml object.
|
660
|
+
def rexml_document_object
|
661
|
+
#puts "Value of rexmlDomobject is : #{@rexmlDomobject}"
|
662
|
+
if @rexmlDomobject == nil
|
663
|
+
create_rexml_document_object
|
664
|
+
end
|
665
|
+
return @rexmlDomobject
|
666
|
+
end
|
667
|
+
|
668
|
+
# Create the Rexml object if it is nil. This method is private so can be called only
|
669
|
+
# from rexml_document_object method.
|
670
|
+
def create_rexml_document_object
|
671
|
+
# Use our modified rexml libraries
|
672
|
+
require 'rexml/document'
|
673
|
+
unless REXML::Version >= '3.1.4'
|
674
|
+
raise "Requires REXML version of at least 3.1.4. Actual: #{REXML::Version}"
|
675
|
+
end
|
676
|
+
if @rexmlDomobject == nil
|
677
|
+
htmlSource ="<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<HTML>\n"
|
678
|
+
htmlSource = html_source(document.body,htmlSource," ")
|
679
|
+
htmlSource += "\n</HTML>\n"
|
680
|
+
# Angrez: Resolving Jira issue WTR-114
|
681
|
+
htmlSource = htmlSource.gsub(/ /, ' ')
|
682
|
+
begin
|
683
|
+
@rexmlDomobject = REXML::Document.new(htmlSource)
|
684
|
+
rescue => e
|
685
|
+
output_rexml_document("error.xml", htmlSource)
|
686
|
+
raise e
|
687
|
+
end
|
688
|
+
end
|
689
|
+
end
|
690
|
+
private :create_rexml_document_object
|
691
|
+
|
692
|
+
def output_rexml_document(name, text)
|
693
|
+
file = File.open(name,"w")
|
694
|
+
file.print(text)
|
695
|
+
file.close
|
696
|
+
end
|
697
|
+
private :output_rexml_document
|
698
|
+
|
699
|
+
#Function Tokenizes the tag line and returns array of tokens.
|
700
|
+
#Token could be either tagName or "=" or attribute name or attribute value
|
701
|
+
#Attribute value could be either quoted string or single word
|
702
|
+
def tokenize_tagline(outerHtml)
|
703
|
+
outerHtml = outerHtml.gsub(/\n|\r/," ")
|
704
|
+
#removing "< symbol", opening of current tag
|
705
|
+
outerHtml =~ /^\s*<(.*)$/
|
706
|
+
outerHtml = $1
|
707
|
+
tokens = Array.new
|
708
|
+
i = startOffset = 0
|
709
|
+
length = outerHtml.length
|
710
|
+
#puts outerHtml
|
711
|
+
parsingValue = false
|
712
|
+
while i < length do
|
713
|
+
i +=1 while (i < length && outerHtml[i,1] =~ /\s/)
|
714
|
+
next if i == length
|
715
|
+
currentToken = outerHtml[i,1]
|
716
|
+
|
717
|
+
#Either current tag has been closed or user has not closed the tag >
|
718
|
+
# and we have received the opening of next element
|
719
|
+
break if currentToken =~ /<|>/
|
720
|
+
|
721
|
+
#parse quoted value
|
722
|
+
if(currentToken == "\"" || currentToken == "'")
|
723
|
+
parsingValue = false
|
724
|
+
quote = currentToken
|
725
|
+
startOffset = i
|
726
|
+
i += 1
|
727
|
+
i += 1 while (i < length && (outerHtml[i,1] != quote || outerHtml[i-1,1] == "\\"))
|
728
|
+
if i == length
|
729
|
+
tokens.push quote + outerHtml[startOffset..i-1]
|
730
|
+
else
|
731
|
+
tokens.push outerHtml[startOffset..i]
|
732
|
+
end
|
733
|
+
elsif currentToken == "="
|
734
|
+
tokens.push "="
|
735
|
+
parsingValue = true
|
736
|
+
else
|
737
|
+
startOffset = i
|
738
|
+
i += 1 while (i < length && !(outerHtml[i,1] =~ /\s|=|<|>/)) if !parsingValue
|
739
|
+
i += 1 while (i < length && !(outerHtml[i,1] =~ /\s|<|>/)) if parsingValue
|
740
|
+
parsingValue = false
|
741
|
+
i -= 1
|
742
|
+
tokens.push outerHtml[startOffset..i]
|
743
|
+
end
|
744
|
+
i += 1
|
745
|
+
end
|
746
|
+
return tokens
|
747
|
+
end
|
748
|
+
private :tokenize_tagline
|
749
|
+
|
750
|
+
# This function get and clean all the attributes of the tag.
|
751
|
+
def all_tag_attributes(outerHtml)
|
752
|
+
tokens = tokenize_tagline(outerHtml)
|
753
|
+
#puts tokens
|
754
|
+
tagLine = ""
|
755
|
+
count = 1
|
756
|
+
tokensLength = tokens.length
|
757
|
+
expectedEqualityOP= false
|
758
|
+
while count < tokensLength do
|
759
|
+
if expectedEqualityOP == false
|
760
|
+
#print Attribute Name
|
761
|
+
# If attribute name is valid. Refer: http://www.w3.org/TR/REC-xml/#NT-Name
|
762
|
+
if tokens[count] =~ /^(\w|_|:)(.*)$/
|
763
|
+
tagLine += " #{tokens[count]}"
|
764
|
+
expectedEqualityOP = true
|
765
|
+
end
|
766
|
+
elsif tokens[count] == "="
|
767
|
+
count += 1
|
768
|
+
if count == tokensLength
|
769
|
+
tagLine += "=\"\""
|
770
|
+
elsif(tokens[count][0,1] == "\"" || tokens[count][0,1] == "'")
|
771
|
+
tagLine += "=#{tokens[count]}"
|
772
|
+
else
|
773
|
+
tagLine += "=\"#{tokens[count]}\""
|
774
|
+
end
|
775
|
+
expectedEqualityOP = false
|
776
|
+
else
|
777
|
+
#Opps! equality was expected but its not there.
|
778
|
+
#Set value same as the attribute name e.g. selected="selected"
|
779
|
+
tagLine += "=\"#{tokens[count-1]}\""
|
780
|
+
expectedEqualityOP = false
|
781
|
+
next
|
782
|
+
end
|
783
|
+
count += 1
|
784
|
+
end
|
785
|
+
tagLine += "=\"#{tokens[count-1]}\" " if expectedEqualityOP == true
|
786
|
+
#puts tagLine
|
787
|
+
return tagLine
|
788
|
+
end
|
789
|
+
private :all_tag_attributes
|
790
|
+
|
791
|
+
# This function is used to escape the characters that are not valid XML data.
|
792
|
+
def xml_escape(str)
|
793
|
+
str = str.gsub(/&/,'&')
|
794
|
+
str = str.gsub(/</,'<')
|
795
|
+
str = str.gsub(/>/,'>')
|
796
|
+
str = str.gsub(/"/, '"')
|
797
|
+
str
|
798
|
+
end
|
799
|
+
private :xml_escape
|
800
|
+
|
801
|
+
# Returns HTML Source
|
802
|
+
# Traverse the DOM tree rooted at body element
|
803
|
+
# and generate the HTML source.
|
804
|
+
# element: Represent Current element
|
805
|
+
# htmlString:HTML Source
|
806
|
+
# spaces:(Used for debugging). Helps in indentation
|
807
|
+
def html_source(element, htmlString, spaceString)
|
808
|
+
begin
|
809
|
+
tagLine = ""
|
810
|
+
outerHtml = ""
|
811
|
+
tagName = ""
|
812
|
+
begin
|
813
|
+
tagName = element.tagName.downcase
|
814
|
+
tagName = EMPTY_TAG_NAME if tagName == ""
|
815
|
+
# If tag is a mismatched tag.
|
816
|
+
if !(tagName =~ /^(\w|_|:)(.*)$/)
|
817
|
+
return htmlString
|
818
|
+
end
|
819
|
+
rescue
|
820
|
+
#handling text nodes
|
821
|
+
htmlString += xml_escape(element.toString)
|
822
|
+
return htmlString
|
823
|
+
end
|
824
|
+
#puts tagName
|
825
|
+
#Skip comment and script tag
|
826
|
+
if tagName =~ /^!/ || tagName== "script" || tagName =="style"
|
827
|
+
return htmlString
|
828
|
+
end
|
829
|
+
#tagLine += spaceString
|
830
|
+
outerHtml = all_tag_attributes(element.outerHtml) if tagName != EMPTY_TAG_NAME
|
831
|
+
tagLine += "<#{tagName} #{outerHtml}"
|
832
|
+
|
833
|
+
canHaveChildren = element.canHaveChildren
|
834
|
+
if canHaveChildren
|
835
|
+
tagLine += ">"
|
836
|
+
else
|
837
|
+
tagLine += "/>" #self closing tag
|
838
|
+
end
|
839
|
+
#spaceString += spaceString
|
840
|
+
htmlString += tagLine
|
841
|
+
childElements = element.childnodes
|
842
|
+
childElements.each do |child|
|
843
|
+
htmlString = html_source(child,htmlString,spaceString)
|
844
|
+
end
|
845
|
+
if canHaveChildren
|
846
|
+
#tagLine += spaceString
|
847
|
+
tagLine ="</" + tagName + ">"
|
848
|
+
htmlString += tagLine
|
849
|
+
end
|
850
|
+
return htmlString
|
851
|
+
rescue => e
|
852
|
+
puts e.to_s
|
853
|
+
end
|
854
|
+
return htmlString
|
855
|
+
end
|
856
|
+
private :html_source
|
857
|
+
|
858
|
+
# return the first element that matches the xpath
|
859
|
+
def element_by_xpath(xpath)
|
860
|
+
temp = elements_by_xpath(xpath)
|
861
|
+
temp = temp[0] if temp
|
862
|
+
return temp
|
863
|
+
end
|
864
|
+
|
865
|
+
# execute xpath and return an array of elements
|
866
|
+
def elements_by_xpath(xpath)
|
867
|
+
doc = rexml_document_object
|
868
|
+
modifiedXpath = ""
|
869
|
+
selectedElements = Array.new
|
870
|
+
doc.elements.each(xpath) do |element|
|
871
|
+
modifiedXpath = element.xpath # element = a REXML element
|
872
|
+
# puts "modified xpath: #{modifiedXpath}"
|
873
|
+
# puts "text: #{element.text}"
|
874
|
+
# puts "class: #{element.attributes['class']}"
|
875
|
+
# require 'breakpoint'; breakpoint
|
876
|
+
temp = element_by_absolute_xpath(modifiedXpath) # temp = a DOM/COM element
|
877
|
+
selectedElements << temp if temp != nil
|
878
|
+
end
|
879
|
+
#puts selectedElements.length
|
880
|
+
if selectedElements.length == 0
|
881
|
+
return nil
|
882
|
+
else
|
883
|
+
return selectedElements
|
884
|
+
end
|
885
|
+
end
|
886
|
+
|
887
|
+
# Method that iterates over IE DOM object and get the elements for the given
|
888
|
+
# xpath.
|
889
|
+
def element_by_absolute_xpath(xpath)
|
890
|
+
curElem = nil
|
891
|
+
|
892
|
+
#puts "Hello; Given xpath is : #{xpath}"
|
893
|
+
doc = document
|
894
|
+
curElem = doc.getElementsByTagName("body")["0"]
|
895
|
+
xpath =~ /^.*\/body\[?\d*\]?\/(.*)/
|
896
|
+
xpath = $1
|
897
|
+
|
898
|
+
if xpath == nil
|
899
|
+
puts "Function Requires absolute XPath."
|
900
|
+
return
|
901
|
+
end
|
902
|
+
|
903
|
+
arr = xpath.split(/\//)
|
904
|
+
return nil if arr.length == 0
|
905
|
+
|
906
|
+
lastTagName = arr[arr.length-1].to_s.upcase
|
907
|
+
|
908
|
+
# lastTagName is like tagName[number] or just tagName. For the first case we need to
|
909
|
+
# separate tagName and number.
|
910
|
+
lastTagName =~ /(\w*)\[?\d*\]?/
|
911
|
+
lastTagName = $1
|
912
|
+
#puts lastTagName
|
913
|
+
|
914
|
+
for element in arr do
|
915
|
+
element =~ /(\w*)\[?(\d*)\]?/
|
916
|
+
tagname = $1
|
917
|
+
tagname = tagname.upcase
|
918
|
+
|
919
|
+
if $2 != nil && $2 != ""
|
920
|
+
index = $2
|
921
|
+
index = "#{index}".to_i - 1
|
922
|
+
else
|
923
|
+
index = 0
|
924
|
+
end
|
925
|
+
|
926
|
+
#puts "#{element} #{tagname} #{index}"
|
927
|
+
allElemns = curElem.childnodes
|
928
|
+
if allElemns == nil || allElemns.length == 0
|
929
|
+
puts "#{element} is null"
|
930
|
+
next # Go to next element
|
931
|
+
end
|
932
|
+
|
933
|
+
#puts "Current element is : #{curElem.tagName}"
|
934
|
+
allElemns.each do |child|
|
935
|
+
gotIt = false
|
936
|
+
begin
|
937
|
+
curTag = child.tagName
|
938
|
+
curTag = EMPTY_TAG_NAME if curTag == ""
|
939
|
+
rescue
|
940
|
+
next
|
941
|
+
end
|
942
|
+
#puts child.tagName
|
943
|
+
if curTag == tagname
|
944
|
+
index-=1
|
945
|
+
if index < 0
|
946
|
+
curElem = child
|
947
|
+
break
|
948
|
+
end
|
949
|
+
end
|
950
|
+
end
|
951
|
+
|
952
|
+
#puts "Node selected at index #{index.to_s} : #{curElem.tagName}"
|
953
|
+
end
|
954
|
+
begin
|
955
|
+
if curElem.tagName == lastTagName
|
956
|
+
#puts curElem.tagName
|
957
|
+
return curElem
|
958
|
+
else
|
959
|
+
return nil
|
960
|
+
end
|
961
|
+
rescue
|
962
|
+
return nil
|
963
|
+
end
|
964
|
+
end
|
965
|
+
private :element_by_absolute_xpath
|
966
|
+
|
967
|
+
def attach_command
|
968
|
+
"Watir::IE.attach(:hwnd, #{hwnd})"
|
969
|
+
end
|
970
|
+
|
971
|
+
|
972
|
+
end # class IE
|
973
|
+
end
|