rwebspec 1.4.0.2 → 1.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,741 +1,878 @@
1
- # convenient methods to drive the browser.
2
- #
3
- # Instead of
4
- # browser.click_button("submit")
5
- # You can just use
6
- # click_button("submit")
7
- #
8
- require File.join(File.dirname(__FILE__), 'itest_plugin')
9
- require File.join(File.dirname(__FILE__), 'popup')
10
- require 'timeout'
11
- require 'uri'
12
-
13
- module RWebSpec
14
- module Driver
15
- include RWebSpec::ITestPlugin
16
- include RWebSpec::Popup
17
-
18
- @@default_polling_interval = 1 # second
19
- @@default_timeout = 30 # seconds
20
-
21
- # open a browser, and set base_url via hash, but does not acually
22
- #
23
- # example:
24
- # open_browser :base_url => http://localhost:8080
25
- #
26
- # There are 3 ways to set base url
27
- # 1. pass as first argument
28
- # 2. If running using iTest2, used as confiured
29
- # 3. Use default value set
30
- def open_browser(base_url = nil, options = {})
31
-
32
- begin
33
- support_unicode
34
- rescue => e
35
- puts "Unicode may not work in IE, #{e}"
36
- end
37
-
38
- base_url ||= $ITEST2_PROJECT_BASE_URL
39
- base_url ||= $BASE_URL
40
- raise "base_url must be set" if base_url.nil?
41
-
42
- default_options = {:speed => "fast",
43
- :visible => true,
44
- :highlight_colour => 'yellow',
45
- :close_others => true,
46
- :start_new => false, # start a new browser always
47
- :go => true}
48
-
49
- options = default_options.merge options
50
- options[:firefox] = true if "Firefox" == $ITEST2_BROWSER || "Firefox" == $BROWSER
51
- ($ITEST2_HIDE_BROWSER) ? $HIDE_IE = true : $HIDE_IE = false
52
-
53
- if base_url =~ /^file:/
54
- uri_base = base_url
55
- else
56
- uri = URI.parse(base_url)
57
- uri_base = "#{uri.scheme}://#{uri.host}:#{uri.port}"
58
- end
59
-
60
- if options[:start_new] || $celerity_loaded
61
- @web_browser = WebBrowser.new(uri_base, nil, options)
62
- else
63
- @web_browser = WebBrowser.reuse(uri_base, options) # Reuse existing browser
64
- end
65
-
66
- if base_url =~ /^file:/
67
- goto_url(base_url) # for files, no base url
68
- else
69
- (uri.path.length == 0) ? begin_at("/") : begin_at(uri.path) if options[:go]
70
- end
71
-
72
- return @web_browser
73
- end
74
- alias open_browser_with open_browser
75
-
76
- # return the underlying RWebSpec::Browser object
77
- def browser
78
- @web_browser
79
- end
80
-
81
-
82
- # Close the current browser window (started by the script). If no browser started, then close
83
- # all browser windows.
84
- #
85
- def close_browser
86
- if @web_browser
87
- # Old iTest2 version
88
- # @web_browser.close_browser unless $ITEST2_LEAVE_BROWSER_OPEN_AFTER_RUN
89
- @web_browser.close_browser
90
- else
91
- WebBrowser.close_all_browsers
92
- end
93
- end
94
- alias close_ie close_browser
95
-
96
-
97
- # Close all opening browser windows
98
- #
99
- def close_all_browsers
100
- if is_firefox?
101
- FireWatir::Firefox.close_all
102
- else
103
- Watir::IE.close_all
104
- end
105
- end
106
-
107
- # Verify the next page following an operation.
108
- #
109
- # Typical usage:
110
- # login_page.click_login
111
- # expect_page HomePage
112
- def expect_page(page_clazz, argument = nil)
113
- if argument
114
- page_clazz.new(@web_browser, argument)
115
- else
116
- page_clazz.new(@web_browser)
117
- end
118
- end
119
-
120
- def context
121
- @web_browser.context
122
- end
123
-
124
- # Starting browser with a URL
125
- #
126
- # Example:
127
- # begin_at("http://www.itest2.com")
128
- def begin_at(url)
129
- dump_caller_stack
130
- @web_browser.begin_at(url)
131
- end
132
-
133
- # Return the Watir::IE instance
134
- #
135
- def ie
136
- @web_browser.ie
137
- end
138
-
139
- # Return the FireWatir::Firefox instance
140
- #
141
- def firefox
142
- @web_browser.firefox
143
- end
144
-
145
- def is_firefox?
146
- @web_browser.is_firefox? if @web_browser
147
- end
148
-
149
-
150
- # Go to another page on the testing site.
151
- #
152
- # open_browser("http://www.itest2.com")
153
- # goto_page("/demo") # visit page http://www.itest2.com/demo
154
- #
155
- def goto_page(page)
156
- operation_delay
157
- dump_caller_stack
158
- @web_browser.goto_page(page) if @web_browser
159
- end
160
- alias visit goto_page
161
-
162
- # Go to another web site, normally different site being tested on
163
- #
164
- # open_browser("http://www.itest2.com")
165
- # goto_url("http://myorganized.info")
166
- def goto_url(url)
167
- @web_browser.goto_url url
168
- end
169
-
170
- # Attach to existinb browser window
171
- #
172
- # attach_browser(:title, )
173
- def attach_browser(how, what, options = {})
174
- options.merge!(:browser => is_firefox? ? "Firefox" : "IE")
175
- begin
176
- options.merge!(:base_url => browser.context.base_url)
177
- rescue => e
178
- puts "error to attach to browser: #{e}"
179
- end
180
- WebBrowser.attach_browser(how, what, options)
181
- end
182
-
183
- # Reuse current an opened browser window instead of opening a new one
184
- # example:
185
- # use_current_browser(:title, /.*/) # use what ever browser window
186
- # use_current_browser(:title, "iTest2") # use browser window with title "iTest2"
187
- def use_current_browser(how = :title, what = /.*/)
188
- @web_browser = WebBrowser.attach_browser(how, what)
189
- end
190
-
191
- ##
192
- # Delegate to WebTester
193
- #
194
- # Note:
195
- # label(:id, "abc") # OK
196
- # label(:id, :abc) # Error
197
- #
198
- # Depends on which object type, you can use following attribute
199
- # More details: http://wiki.openqa.org/display/WTR/Methods+supported+by+Element
200
- #
201
- # :id Used to find an element that has an "id=" attribute. Since each id should be unique, according to the XHTML specification, this is recommended as the most reliable method to find an object. *
202
- # :name Used to find an element that has a "name=" attribute. This is useful for older versions of HTML, but "name" is deprecated in XHTML. *
203
- # :value Used to find a text field with a given default value, or a button with a given caption, or a text field
204
- # :text Used for links, spans, divs and other element that contain text.
205
- # :index Used to find the nth element of the specified type on a page. For example, button(:index, 2) finds the second button. Current versions of WATIR use 1-based indexing, but future versions will use 0-based indexing.
206
- # :class Used for an element that has a "class=" attribute.
207
- # :title Used for an element that has a "title=" attribute.
208
- # :xpath Finds the item using xpath query.
209
- # :method Used only for forms, the method attribute of a form is either GET or POST.
210
- # :action Used only for form elements, specifies the URL where the form is to be submitted.
211
- # :href Used to identify a link by its "href=" attribute.
212
- # :src Used to identify an image by its URL.
213
- #
214
-
215
- # area <area> tags
216
- # button <input> tags with type=button, submit, image or reset
217
- # check_box <input> tags with type=checkbox
218
- # div <div> tags
219
- # form <form> tags
220
- # frame frames, including both the <frame> elements and the corresponding pages
221
- # h1 - h6 <h1>, <h2>, <h3>, <h4>, <h5>, <h6> tags
222
- # hidden <input> tags with type=hidden
223
- # image <img> tags
224
- # label <label> tags (including "for" attribute)
225
- # li <li> tags
226
- # link <a> (anchor) tags
227
- # map <map> tags
228
- # radio <input> tags with the type=radio; known as radio buttons
229
- # select_list <select> tags, known as drop-downs or drop-down lists
230
- # span <span> tags
231
- # table <table> tags, including row and cell methods for accessing nested elements
232
- # text_field <input> tags with the type=text (single-line), type=textarea (multi-line), and type=password
233
- # p <p> (paragraph) tags, because
234
- [:area, :button, :cell, :checkbox, :div, :form, :frame, :h1, :h2, :h3, :h4, :h5, :h6, :hidden, :image, :li, :link, :map, :pre, :row, :radio, :select_list, :span, :table, :text_field, :paragraph, :file_field, :label].each do |method|
235
- define_method method do |*args|
236
- dump_caller_stack
237
- # add check for @web_browser, in case the moudule included without init browser
238
- @web_browser.send(method, *args) if @web_browser
239
- end
240
- end
241
- alias td cell
242
- alias check_box checkbox # seems watir doc is wrong, checkbox not check_box
243
- alias tr row
244
-
245
- [:back, :forward, :refresh].each do |method|
246
- define_method(method) do
247
- dump_caller_stack
248
- operation_delay
249
- @web_browser.send(method) if @web_browser
250
- end
251
- end
252
- alias refresh_page refresh
253
- alias go_back back
254
- alias go_forward forward
255
-
256
- [:images, :links, :buttons, :select_lists, :checkboxes, :radios, :text_fields].each do |method|
257
- define_method method do
258
- dump_caller_stack
259
- @web_browser.send(method) if @web_browser
260
- end
261
- end
262
-
263
- # Check one or more checkboxes with same name, can accept a string or an array of string as values checkbox, pass array as values will try to set mulitple checkboxes.
264
- #
265
- # page.check_checkbox('bad_ones', 'Chicken Little')
266
- # page.check_checkbox('good_ones', ['Cars', 'Toy Story'])
267
- #
268
- [:set_form_element, :click_link_with_text, :click_link_with_id, :submit, :click_button_with_id, :click_button_with_name, :click_button_with_caption, :click_button_with_value, :click_radio_option, :clear_radio_option, :select_file_for_upload, :check_checkbox, :uncheck_checkbox, :select_option].each do |method|
269
- define_method method do |*args|
270
- dump_caller_stack
271
- operation_delay
272
- @web_browser.send(method, *args) if @web_browser
273
- end
274
- end
275
-
276
- alias enter_text set_form_element
277
- alias set_hidden_field set_form_element
278
- alias click_link click_link_with_text
279
- alias click_button_with_text click_button_with_caption
280
- alias click_button click_button_with_caption
281
- alias click_radio_button click_radio_option
282
- alias clear_radio_button clear_radio_option
283
-
284
- # for text field can be easier to be identified by attribute "id" instead of "name", not recommended though
285
- def enter_text_with_id(textfield_id, value)
286
- dump_caller_stack
287
- operation_delay
288
- text_field(:id, textfield_id).set(value)
289
- end
290
-
291
- def contains_text(text)
292
- @web_browser.contains_text(text)
293
- end
294
-
295
- # Click image buttion with image source name
296
- #
297
- # For an image submit button <input name="submit" type="image" src="/images/search_button.gif">
298
- # click_button_with_image("search_button.gif")
299
- def click_button_with_image_src_contains(image_filename)
300
- dump_caller_stack
301
- operation_delay
302
- found = nil
303
- raise "no buttons in this page" if buttons.length <= 0
304
- buttons.each { |btn|
305
- if btn && btn.src && btn.src.include?(image_filename) then
306
- found = btn
307
- break
308
- end
309
- }
310
- raise "not image button with src: #{image_filename} found" if found.nil?
311
- found.click
312
- end
313
- alias click_button_with_image click_button_with_image_src_contains
314
-
315
- def new_popup_window(options)
316
- @web_browser.new_popup_window(options)
317
- end
318
-
319
-
320
- # Warning: this does not work well with Firefox yet.
321
- def element_text(elem_id)
322
- @web_browser.element_value(elem_id)
323
- end
324
-
325
- # Identify DOM element by ID
326
- # Warning: it is only supported on IE
327
- def element_by_id(elem_id)
328
- @web_browser.element_by_id(elem_id)
329
- end
330
-
331
- # ---
332
- # For debugging
333
- # ---
334
- def dump_response(stream = nil)
335
- @web_browser.dump_response(stream)
336
- end
337
-
338
- # For current page souce to a file in specified folder for inspection
339
- #
340
- # save_current_page(:dir => "C:\\mysite", filename => "abc", :replacement => true)
341
- def save_current_page(options = {})
342
- default_options = {:replacement => true}
343
- options = default_options.merge(options)
344
- if options[:dir]
345
- # already defined the dir
346
- to_dir = options[:dir]
347
- elsif $ITEST2_RUNNING_SPEC_ID && $ITEST2_WORKING_DIR
348
-
349
- $ITEST2_DUMP_DIR = File.join($ITEST2_WORKING_DIR, "dump")
350
- FileUtils.mkdir($ITEST2_DUMP_DIR) unless File.exists?($ITEST2_DUMP_DIR)
351
-
352
- spec_run_id = $ITEST2_RUNNING_SPEC_ID
353
- spec_run_dir_name = spec_run_id.to_s.rjust(4, "0") unless spec_run_id == "unknown"
354
- to_dir = File.join($ITEST2_DUMP_DIR, spec_run_dir_name)
355
- else
356
- to_dir = ENV['TEMP_DIR'] || (is_windows? ? "C:\\temp" : "/tmp")
357
- end
358
-
359
- if options[:filename]
360
- file_name = options[:filename]
361
- else
362
- file_name = Time.now.strftime("%m%d%H%M%S") + ".html"
363
- end
364
-
365
- Dir.mkdir(to_dir) unless File.exists?(to_dir)
366
- file = File.join(to_dir, file_name)
367
-
368
- content = page_source
369
- base_url = @web_browser.context.base_url
370
- current_url = @web_browser.url
371
- current_url =~ /(.*\/).*$/
372
- current_url_parent = $1
373
- if options[:replacement] && base_url =~ /^http:/
374
- File.new(file, "w").puts absolutize_page_hpricot(content, base_url, current_url_parent)
375
- else
376
- File.new(file, "w").puts content
377
- end
378
-
379
- end
380
-
381
-
382
- # <link rel="stylesheet" type="text/css" href="/stylesheets/default.css" />
383
- # '<script type="text/javascript" src="http://www.jeroenwijering.com/embed/swfobject.js"></script>'
384
- # <script type="text/javascript" src="/javascripts/prototype.js"></script>
385
- # <script type="text/javascript" src="/javascripts/scriptaculous.js?load=effects,builder"></script>
386
- # <script type="text/javascript" src="/javascripts/extensions/gallery/lightbox.js"></script>
387
- # <link href="/stylesheets/extensions/gallery/lightbox.css" rel="stylesheet" type="text/css" />
388
- # <img src="images/mission_48.png" />
389
- def absolutize_page(content, base_url, current_url_parent)
390
- modified_content = ""
391
- content.each_line do |line|
392
- if line =~ /<script\s+.*src=["'']?(.*)["'].*/i then
393
- script_src = $1
394
- substitute_relative_path_in_src_line(line, script_src, base_url, current_url_parent)
395
- elsif line =~ /<link\s+.*href=["'']?(.*)["'].*/i then
396
- link_href = $1
397
- substitute_relative_path_in_src_line(line, link_href, base_url, current_url_parent)
398
- elsif line =~ /<img\s+.*src=["'']?(.*)["'].*/i then
399
- img_src = $1
400
- substitute_relative_path_in_src_line(line, img_src, base_url, current_url_parent)
401
- end
402
-
403
- modified_content += line
404
- end
405
- return modified_content
406
- end
407
-
408
- # absolutize_page referencs using hpricot
409
- #
410
- def absolutize_page_hpricot(content, base_url, parent_url)
411
- return absolutize_page(content, base_url, parent_url) if RUBY_PLATFORM == 'java'
412
- begin
413
- require 'hpricot'
414
- doc = Hpricot(content)
415
- base_url.slice!(-1) if ends_with?(base_url, "/")
416
- (doc/'link').each { |e| e['href'] = absolutify_url(e['href'], base_url, parent_url) || ""}
417
- (doc/'img').each { |e| e['src'] = absolutify_url(e['src'], base_url, parent_url) || ""}
418
- (doc/'script').each { |e| e['src'] = absolutify_url(e['src'], base_url, parent_url) || ""}
419
- return doc.to_html
420
- rescue => e
421
- absolutize_page(content, base_url, parent_url)
422
- end
423
- end
424
-
425
- ##
426
- # change
427
- # <script type="text/javascript" src="/javascripts/prototype.js"></script>
428
- # to
429
- # <script type="text/javascript" src="http://itest2.com/javascripts/prototype.js"></script>
430
- def absolutify_url(src, base_url, parent_url)
431
- if src.nil? || src.empty? || src == "//:" || src =~ /\s*http:\/\//i
432
- return src
433
- end
434
-
435
- return "#{base_url}#{src}" if src =~ /^\s*\//
436
- return "#{parent_url}#{src}" if parent_url
437
- return src
438
- end
439
-
440
- # substut
441
- def substitute_relative_path_in_src_line(line, script_src, host_url, page_parent_url)
442
- unless script_src =~ /^["']?http:/
443
- host_url.slice!(-1) if ends_with?(host_url, "/")
444
- if script_src =~ /^\s*\// # absolute_path
445
- line.gsub!(script_src, "#{host_url}#{script_src}")
446
- else #relative_path
447
- line.gsub!(script_src, "#{page_parent_url}#{script_src}")
448
- end
449
- end
450
- end
451
-
452
- def ends_with?(str, suffix)
453
- suffix = suffix.to_s
454
- str[-suffix.length, suffix.length] == suffix
455
- end
456
-
457
- # current web page title
458
- def page_title
459
- @web_browser.page_title
460
- end
461
-
462
- # current page source (in HTML)
463
- def page_source
464
- @web_browser.page_source
465
- end
466
-
467
- # return plain text view of page
468
- def page_text
469
- @web_browser.text
470
- end
471
-
472
- # return the text of specific (identified by attribute "id") label tag
473
- # For page containing
474
- # <label id="preferred_ide">iTest2</label>
475
- # label_with_id("preferred_ids") # => iTest2
476
- def label_with_id(label_id)
477
- label(:id, label_id.to_s).text
478
- end
479
-
480
- # return the text of specific (identified by attribute "id") span tag
481
- # For page containing
482
- # <span id="preferred_recorder">iTest2/Watir Recorder</span>
483
- # span_with_id("preferred_recorder") # => iTest2/Watir Recorder
484
- def span_with_id(span_id)
485
- span(:id, span_id).text
486
- end
487
-
488
- # return the text of specific (identified by attribute "id") ta tag
489
- # For page containing
490
- # <td id="preferred_recorder">iTest2/Watir Recorder</span>
491
- # td_with_id("preferred_recorder") # => iTest2/Watir Recorder
492
- def cell_with_id(cell_id)
493
- cell(:id, cell_id).text
494
- end
495
- alias table_data_with_id cell_with_id
496
-
497
-
498
- def is_mac?
499
- RUBY_PLATFORM.downcase.include?("darwin")
500
- end
501
-
502
- def is_windows?
503
- RUBY_PLATFORM.downcase.include?("mswin") or RUBY_PLATFORM.downcase.include?("mingw32")
504
- end
505
-
506
- def is_linux?
507
- RUBY_PLATFORM.downcase.include?("linux")
508
- end
509
-
510
- # Support browser (IE) operations using unicode
511
- # Example:
512
- # click_button("Google 搜索")
513
- # Reference: http://jira.openqa.org/browse/WTR-219
514
- def support_utf8
515
- if is_windows?
516
- require 'win32ole'
517
- WIN32OLE.codepage = WIN32OLE::CP_UTF8
518
- end
519
- end
520
- alias support_unicode support_utf8
521
-
522
- #= Convenient functions
523
- #
524
-
525
- # Using Ruby block syntax to create interesting domain specific language,
526
- # may be appeal to someone.
527
-
528
- # Example:
529
- # on @page do |i|
530
- # i.enter_text('btn1')
531
- # i.click_button('btn1')
532
- # end
533
- def on(page, &block)
534
- yield page
535
- end
536
-
537
- # fail the test if user can perform the operation
538
- #
539
- # Example:
540
- # shall_not_allow { 1/0 }
541
- def shall_not_allow(&block)
542
- operation_performed_ok = false
543
- begin
544
- yield
545
- operation_performed_ok = true
546
- rescue
547
- end
548
- raise "Operation shall not be allowed" if operation_performed_ok
549
- end
550
- alias do_not_allow shall_not_allow
551
-
552
- # Does not provide real function, other than make enhancing test syntax
553
- #
554
- # Example:
555
- # allow { click_button('Register') }
556
- def allow(&block)
557
- yield
558
- end
559
- alias shall_allow allow
560
- alias allowing allow
561
-
562
- # try operation, ignore if errors occur
563
- #
564
- # Example:
565
- # failsafe { click_link("Logout") } # try logout, but it still OK if not being able to (already logout))
566
- def failsafe(&block)
567
- begin
568
- yield
569
- rescue =>e
570
- end
571
- end
572
- alias fail_safe failsafe
573
-
574
-
575
- # Execute the provided block until either (1) it returns true, or
576
- # (2) the timeout (in seconds) has been reached. If the timeout is reached,
577
- # a TimeOutException will be raised. The block will always
578
- # execute at least once.
579
- #
580
- # This does not handle error, if the given block raise error, the statement finish with error
581
- # Examples:
582
- # wait_until {puts 'hello'}
583
- # wait_until { div(:id, :receipt_date).exists? }
584
- #
585
- def wait_until(timeout = @@default_timeout || 30, polling_interval = @@default_polling_interval || 1, &block)
586
- waiter = Watir::Waiter.new(timeout, polling_interval)
587
- waiter.wait_until { yield }
588
- end
589
-
590
- # Wait for specific seconds for an Ajax update finish.
591
- # Trick: In your Rails application,
592
- # :loading => "Element.show('search_indicator');
593
- # :complete => "Element.hide('search_indicator');
594
- #
595
- # <%= image_tag("indicator.gif", :id => 'search_indicator', :style => 'display:none') %>
596
- #
597
- # Typical usage:
598
- # ajax_wait_for_element("search_indicator", 30)
599
- # ajax_wait_for_element("search_indicator", 30, "show")
600
- # ajax_wait_for_element("search_indicator", 30, "hide")
601
- # ajax_wait_for_element("search_indicator", 30, "show", 5) # check every 5 seconds
602
- #
603
- # Warning: this method has not been fully tested, if you are not using Rails, change your parameter accordingly.
604
- #
605
- def ajax_wait_for_element(element_id, seconds, status='show', check_interval = @@default_polling_interval)
606
- count = 0
607
- check_interval = 1 if check_interval < 1 or check_interval > seconds
608
- while count < (seconds / check_interval) do
609
- search_indicator = @web_browser.element_by_id(element_id)
610
- search_indicator_outer_html = search_indicator.outerHtml if search_indicator
611
- if status == 'hide'
612
- return true if search_indicator_outer_html and !search_indicator_outer_html.include?('style="DISPLAY: none"')
613
- else
614
- return true if search_indicator_outer_html and search_indicator_outer_html.include?('style="DISPLAY: none"')
615
- end
616
- sleep check_interval if check_interval > 0 and check_interval < 5 * 60 # wait max 5 minutes
617
- count += 1
618
- end
619
- return false
620
- end
621
-
622
- #Wait the element with given id to be present in web page
623
- #
624
- # Warning: this not working in Firefox, try use wait_util or try instead
625
- def wait_for_element(element_id, timeout = @@default_timeout, interval = @@default_polling_interval)
626
- start_time = Time.now
627
- #TODO might not work with Firefox
628
- until @web_browser.element_by_id(element_id) do
629
- sleep(interval)
630
- if (Time.now - start_time) > timeout
631
- raise RuntimeError, "failed to find element: #{element_id} for max #{timeout}"
632
- end
633
- end
634
- end
635
- =begin
636
-
637
- # TODO: Firewatir does not suport retrieving style or outerHtml
638
- # http://jira.openqa.org/browse/WTR-260
639
- # http://code.google.com/p/firewatir/issues/detail?id=76
640
- #
641
- # Max timeout value is 10 minutes
642
- #
643
- def ajax_call_complete_after_element_hidden(elem_id, check_start = 0.5, timeout = 5, interval = 0.5, &block)
644
- yield
645
- sleep check_start # the time allowed to perform the coomplete
646
- timeout = 10 * 60 if timeout > 10 * 600 or timeout <= 0
647
- begin
648
- Timeout::timeout(timeout) {
649
- begin
650
- elem = element_by_id(elem_id)
651
- while elem do
652
- puts "outer=>#{elem.outerHtml}|"
653
- puts "style =>#{elem.attribute_value('style')}|"
654
- sleep interval
655
- elem = element_by_id(elem_id)
656
- end
657
- rescue => e
658
- puts e
659
- end
660
- }
661
- rescue Timeout::Error
662
- # Too slow!!
663
- raise "Too slow, wait max #{timeout} seconds, the element #{elem_id} still there"
664
- end
665
- end
666
-
667
- =end
668
-
669
- # Try the operation up to specified times, and sleep given interval (in seconds)
670
- # Error will be ignored until timeout
671
- # Example
672
- # repeat_try(3, 2) { click_button('Search' } # 3 times, 6 seconds in total
673
- # repeat_try { click_button('Search' } # using default 5 tries, 2 second interval
674
- def repeat_try(num_tries = @@default_timeout || 30, interval = @@default_polling_interval || 1, &block)
675
- num_tries ||= 1
676
- (num_tries - 1).times do |num|
677
- begin
678
- yield
679
- return
680
- rescue => e
681
- # puts "debug: #{num} failed: #{e}"
682
- sleep interval
683
- end
684
- end
685
-
686
- # last try, throw error if still fails
687
- begin
688
- yield
689
- rescue => e
690
- raise e.to_s + " after trying #{num_tries} times every #{interval} seconds"
691
- end
692
- yield
693
- end
694
-
695
- # TODO: syntax
696
-
697
- # Try the operation up to specified timeout (in seconds), and sleep given interval (in seconds).
698
- # Error will be ignored until timeout
699
- # Example
700
- # try { click_link('waiting')}
701
- # try(10, 2) { click_button('Search' } # try to click the 'Search' button upto 10 seconds, try every 2 seconds
702
- # try { click_button('Search' }
703
- def try(timeout = @@default_timeout, polling_interval = @@default_polling_interval || 1, &block)
704
- start_time = Time.now
705
-
706
- last_error = nil
707
- until (duration = Time.now - start_time) > timeout
708
- begin
709
- return if yield
710
- last_error = nil
711
- rescue => e
712
- last_error = e
713
- end
714
- sleep polling_interval
715
- end
716
-
717
- raise "Timeout after #{duration.to_i} seconds with error: #{last_error}." if last_error
718
- raise "Timeout after #{duration.to_i} seconds."
719
- end
720
- alias try_upto try
721
-
722
- ##
723
- # Convert :first to 1, :second to 2, and so on...
724
- def symbol_to_sequence(symb)
725
- value = { :zero => 0,
726
- :first => 1,
727
- :second => 2,
728
- :third => 3,
729
- :fourth => 4,
730
- :fifth => 5,
731
- :sixth => 6,
732
- :seventh => 7,
733
- :eighth => 8,
734
- :ninth => 9,
735
- :tenth => 10 }[symb]
736
- return value || symb.to_i
737
- end
738
-
739
- end
740
-
741
- end
1
+ # convenient methods to drive the browser.
2
+ # convenient methods to drive the browser.
3
+ #
4
+ # Instead of
5
+ # browser.click_button("submit")
6
+ # You can just use
7
+ # click_button("submit")
8
+ #
9
+ require File.join(File.dirname(__FILE__), 'testwise_plugin')
10
+ require File.join(File.dirname(__FILE__), 'popup')
11
+ require File.join(File.dirname(__FILE__), 'matchers', "contains_text.rb")
12
+
13
+ require 'timeout'
14
+ require 'uri'
15
+
16
+ require 'watir/screen_capture' if RUBY_PLATFORM.downcase.include?("mswin") or RUBY_PLATFORM.downcase.include?("mingw")
17
+
18
+ module RWebSpec
19
+ module Driver
20
+ include RWebSpec::TestWisePlugin
21
+ include RWebSpec::Popup
22
+ include Watir::ScreenCapture if RUBY_PLATFORM.downcase.include?("mswin") or RUBY_PLATFORM.downcase.include?("mingw")
23
+
24
+ @@default_polling_interval = 1 # second
25
+ @@default_timeout = 30 # seconds
26
+
27
+ # open a browser, and set base_url via hash, but does not acually
28
+ #
29
+ # example:
30
+ # open_browser :base_url => http://localhost:8080
31
+ #
32
+ # There are 3 ways to set base url
33
+ # 1. pass as first argument
34
+ # 2. If running using TestWise, used as confiured
35
+ # 3. Use default value set
36
+ def open_browser(base_url = nil, options = {})
37
+
38
+ begin
39
+ support_unicode
40
+ rescue => e
41
+ puts "Unicode may not work in IE, #{e}"
42
+ end
43
+
44
+ base_url ||= $ITEST2_PROJECT_BASE_URL
45
+ base_url ||= $BASE_URL
46
+ raise "base_url must be set" if base_url.nil?
47
+
48
+ default_options = {:speed => "fast",
49
+ :visible => true,
50
+ :highlight_colour => 'yellow',
51
+ :close_others => true,
52
+ :start_new => false, # start a new browser always
53
+ :go => true}
54
+
55
+ options = default_options.merge options
56
+ options[:firefox] = true if "Firefox" == $ITEST2_BROWSER || "Firefox" == $BROWSER
57
+ ($ITEST2_HIDE_BROWSER) ? $HIDE_IE = true : $HIDE_IE = false
58
+
59
+ if base_url =~ /^file:/
60
+ uri_base = base_url
61
+ else
62
+ uri = URI.parse(base_url)
63
+ uri_base = "#{uri.scheme}://#{uri.host}:#{uri.port}"
64
+ end
65
+
66
+ if options[:start_new] || $celerity_loaded
67
+ @web_browser = WebBrowser.new(uri_base, nil, options)
68
+ else
69
+ @web_browser = WebBrowser.reuse(uri_base, options) # Reuse existing browser
70
+ end
71
+
72
+ if base_url =~ /^file:/
73
+ goto_url(base_url) # for files, no base url
74
+ else
75
+ (uri.path.length == 0) ? begin_at("/") : begin_at(uri.path) if options[:go]
76
+ end
77
+
78
+ return @web_browser
79
+ end
80
+
81
+ alias open_browser_with open_browser
82
+
83
+ # return the underlying RWebSpec::Browser object
84
+ def browser
85
+ @web_browser
86
+ end
87
+
88
+
89
+ # Close the current browser window (started by the script). If no browser started, then close
90
+ # all browser windows.
91
+ #
92
+ def close_browser
93
+ if @web_browser
94
+ # Old TestWise version
95
+ # @web_browser.close_browser unless $ITEST2_LEAVE_BROWSER_OPEN_AFTER_RUN
96
+ @web_browser.close_browser
97
+ else
98
+ WebBrowser.close_all_browsers
99
+ end
100
+ end
101
+
102
+ alias close_ie close_browser
103
+
104
+
105
+ # Close all opening browser windows
106
+ #
107
+ def close_all_browsers
108
+ if is_firefox?
109
+ FireWatir::Firefox.close_all
110
+ else
111
+ Watir::IE.close_all
112
+ end
113
+ end
114
+
115
+ # Verify the next page following an operation.
116
+ #
117
+ # Typical usage:
118
+ # login_page.click_login
119
+ # expect_page HomePage
120
+ def expect_page(page_clazz, argument = nil)
121
+ if argument
122
+ @web_browser.expect_page(page_clazz, argument)
123
+ else
124
+ @web_browser.expect_page(page_clazz)
125
+ end
126
+ end
127
+
128
+ def context
129
+ @web_browser.context
130
+ end
131
+
132
+ # Starting browser with a URL
133
+ #
134
+ # Example:
135
+ # begin_at("http://www.itest2.com")
136
+ def begin_at(url)
137
+ dump_caller_stack
138
+ @web_browser.begin_at(url)
139
+ end
140
+
141
+ # Return the Watir::IE instance
142
+ #
143
+ def ie
144
+ @web_browser.ie
145
+ end
146
+
147
+ # Return the FireWatir::Firefox instance
148
+ #
149
+ def firefox
150
+ @web_browser.firefox
151
+ end
152
+
153
+ def is_firefox?
154
+ @web_browser.is_firefox? if @web_browser
155
+ end
156
+
157
+
158
+ # Go to another page on the testing site.
159
+ #
160
+ # open_browser("http://www.itest2.com")
161
+ # goto_page("/demo") # visit page http://www.itest2.com/demo
162
+ #
163
+ def goto_page(page)
164
+ perform_operation {
165
+ @web_browser.goto_page(page) if @web_browser
166
+ }
167
+ end
168
+
169
+ alias visit goto_page
170
+
171
+ # Go to another web site, normally different site being tested on
172
+ #
173
+ # open_browser("http://www.itest2.com")
174
+ # goto_url("http://myorganized.info")
175
+ def goto_url(url)
176
+ @web_browser.goto_url url
177
+ end
178
+
179
+ # Attach to existinb browser window
180
+ #
181
+ # attach_browser(:title, )
182
+ def attach_browser(how, what, options = {})
183
+ options.merge!(:browser => is_firefox? ? "Firefox" : "IE")
184
+ begin
185
+ options.merge!(:base_url => browser.context.base_url)
186
+ rescue => e
187
+ puts "error to attach to browser: #{e}"
188
+ end
189
+ WebBrowser.attach_browser(how, what, options)
190
+ end
191
+
192
+ # Reuse current an opened browser window instead of opening a new one
193
+ # example:
194
+ # use_current_browser(:title, /.*/) # use what ever browser window
195
+ # use_current_browser(:title, "TestWise") # use browser window with title "TestWise"
196
+ def use_current_browser(how = :title, what = /.*/)
197
+ @web_browser = WebBrowser.attach_browser(how, what)
198
+ end
199
+
200
+ ##
201
+ # Delegate to WebTester
202
+ #
203
+ # Note:
204
+ # label(:id, "abc") # OK
205
+ # label(:id, :abc) # Error
206
+ #
207
+ # Depends on which object type, you can use following attribute
208
+ # More details: http://wiki.openqa.org/display/WTR/Methods+supported+by+Element
209
+ #
210
+ # :id Used to find an element that has an "id=" attribute. Since each id should be unique, according to the XHTML specification, this is recommended as the most reliable method to find an object. *
211
+ # :name Used to find an element that has a "name=" attribute. This is useful for older versions of HTML, but "name" is deprecated in XHTML. *
212
+ # :value Used to find a text field with a given default value, or a button with a given caption, or a text field
213
+ # :text Used for links, spans, divs and other element that contain text.
214
+ # :index Used to find the nth element of the specified type on a page. For example, button(:index, 2) finds the second button. Current versions of WATIR use 1-based indexing, but future versions will use 0-based indexing.
215
+ # :class Used for an element that has a "class=" attribute.
216
+ # :title Used for an element that has a "title=" attribute.
217
+ # :xpath Finds the item using xpath query.
218
+ # :method Used only for forms, the method attribute of a form is either GET or POST.
219
+ # :action Used only for form elements, specifies the URL where the form is to be submitted.
220
+ # :href Used to identify a link by its "href=" attribute.
221
+ # :src Used to identify an image by its URL.
222
+ #
223
+
224
+ # area <area> tags
225
+ # button <input> tags with type=button, submit, image or reset
226
+ # check_box <input> tags with type=checkbox
227
+ # div <div> tags
228
+ # form <form> tags
229
+ # frame frames, including both the <frame> elements and the corresponding pages
230
+ # h1 - h6 <h1>, <h2>, <h3>, <h4>, <h5>, <h6> tags
231
+ # hidden <input> tags with type=hidden
232
+ # image <img> tags
233
+ # label <label> tags (including "for" attribute)
234
+ # li <li> tags
235
+ # link <a> (anchor) tags
236
+ # map <map> tags
237
+ # radio <input> tags with the type=radio; known as radio buttons
238
+ # select_list <select> tags, known as drop-downs or drop-down lists
239
+ # span <span> tags
240
+ # table <table> tags, including row and cell methods for accessing nested elements
241
+ # text_field <input> tags with the type=text (single-line), type=textarea (multi-line), and type=password
242
+ # p <p> (paragraph) tags, because
243
+ [:area, :button, :cell, :checkbox, :div, :form, :frame, :h1, :h2, :h3, :h4, :h5, :h6, :hidden, :image, :li, :link, :map, :pre, :row, :radio, :select_list, :span, :table, :text_field, :paragraph, :file_field, :label].each do |method|
244
+ define_method method do |*args|
245
+ perform_operation { @web_browser.send(method, *args) if @web_browser }
246
+ end
247
+ end
248
+ alias td cell
249
+ alias check_box checkbox # seems watir doc is wrong, checkbox not check_box
250
+ alias tr row
251
+
252
+ [:back, :forward, :refresh].each do |method|
253
+ define_method(method) do
254
+ perform_operation { @web_browser.send(method) if @web_browser }
255
+ end
256
+ end
257
+ alias refresh_page refresh
258
+ alias go_back back
259
+ alias go_forward forward
260
+
261
+ [:images, :links, :buttons, :select_lists, :checkboxes, :radios, :text_fields].each do |method|
262
+ define_method method do
263
+ perform_operation { @web_browser.send(method) if @web_browser }
264
+ end
265
+ end
266
+
267
+ # Check one or more checkboxes with same name, can accept a string or an array of string as values checkbox, pass array as values will try to set mulitple checkboxes.
268
+ #
269
+ # page.check_checkbox('bad_ones', 'Chicken Little')
270
+ # page.check_checkbox('good_ones', ['Cars', 'Toy Story'])
271
+ #
272
+ [:set_form_element, :click_link_with_text, :click_link_with_id, :submit, :click_button_with_id, :click_button_with_name, :click_button_with_caption, :click_button_with_value, :click_radio_option, :clear_radio_option, :check_checkbox, :uncheck_checkbox, :select_option].each do |method|
273
+ define_method method do |*args|
274
+ perform_operation { @web_browser.send(method, *args) if @web_browser }
275
+ end
276
+ end
277
+
278
+ alias enter_text set_form_element
279
+ alias set_hidden_field set_form_element
280
+ alias click_link click_link_with_text
281
+ alias click_button_with_text click_button_with_caption
282
+ alias click_button click_button_with_caption
283
+ alias click_radio_button click_radio_option
284
+ alias clear_radio_button clear_radio_option
285
+
286
+ # for text field can be easier to be identified by attribute "id" instead of "name", not recommended though
287
+ def enter_text_with_id(textfield_id, value)
288
+ perform_operation { text_field(:id, textfield_id).set(value) }
289
+ end
290
+
291
+ def perform_operation(&block)
292
+ begin
293
+ dump_caller_stack
294
+ operation_delay
295
+ yield
296
+ rescue RuntimeError => re
297
+ puts "[DEBUG] operation error: #{re}"
298
+ raise re
299
+ # ensure
300
+ # puts "[DEBUG] ensure #{perform_ok}" unless perform_ok
301
+ end
302
+ end
303
+
304
+ def contains_text(text)
305
+ @web_browser.contains_text(text)
306
+ end
307
+
308
+ # In pages, can't use include, text.should include("abc") won't work
309
+ # Instead,
310
+ # text.should contains("abc"
311
+ def contains(str)
312
+ ContainsText.new(str)
313
+ end
314
+
315
+ alias contain contains
316
+
317
+ # Click image buttion with image source name
318
+ #
319
+ # For an image submit button <input name="submit" type="image" src="/images/search_button.gif">
320
+ # click_button_with_image("search_button.gif")
321
+ def click_button_with_image_src_contains(image_filename)
322
+ perform_operation {
323
+ found = nil
324
+ raise "no buttons in this page" if buttons.length <= 0
325
+ buttons.each { |btn|
326
+ if btn && btn.src && btn.src.include?(image_filename) then
327
+ found = btn
328
+ break
329
+ end
330
+ }
331
+ raise "not image button with src: #{image_filename} found" if found.nil?
332
+ found.click
333
+ }
334
+ end
335
+
336
+ alias click_button_with_image click_button_with_image_src_contains
337
+
338
+ def new_popup_window(options)
339
+ @web_browser.new_popup_window(options)
340
+ end
341
+
342
+
343
+ # Warning: this does not work well with Firefox yet.
344
+ def element_text(elem_id)
345
+ @web_browser.element_value(elem_id)
346
+ end
347
+
348
+ # Identify DOM element by ID
349
+ # Warning: it is only supported on IE
350
+ def element_by_id(elem_id)
351
+ @web_browser.element_by_id(elem_id)
352
+ end
353
+
354
+ # ---
355
+ # For debugging
356
+ # ---
357
+ def dump_response(stream = nil)
358
+ @web_browser.dump_response(stream)
359
+ end
360
+
361
+ def default_dump_dir
362
+ if $ITEST2_RUNNING_SPEC_ID && $ITEST2_WORKING_DIR
363
+
364
+ $ITEST2_DUMP_DIR = File.join($ITEST2_WORKING_DIR, "dump")
365
+ FileUtils.mkdir($ITEST2_DUMP_DIR) unless File.exists?($ITEST2_DUMP_DIR)
366
+
367
+ spec_run_id = $ITEST2_RUNNING_SPEC_ID
368
+ spec_run_dir_name = spec_run_id.to_s.rjust(4, "0") unless spec_run_id == "unknown"
369
+ to_dir = File.join($ITEST2_DUMP_DIR, spec_run_dir_name)
370
+ else
371
+ to_dir = ENV['TEMP_DIR'] || (is_windows? ? "C:\\temp" : "/tmp")
372
+ end
373
+ end
374
+
375
+ # For current page souce to a file in specified folder for inspection
376
+ #
377
+ # save_current_page(:dir => "C:\\mysite", filename => "abc", :replacement => true)
378
+ def save_current_page(options = {})
379
+ default_options = {:replacement => true}
380
+ options = default_options.merge(options)
381
+ to_dir = options[:dir] || default_dump_dir
382
+
383
+ if options[:filename]
384
+ file_name = options[:filename]
385
+ else
386
+ file_name = Time.now.strftime("%m%d%H%M%S") + ".html"
387
+ end
388
+
389
+ Dir.mkdir(to_dir) unless File.exists?(to_dir)
390
+ file = File.join(to_dir, file_name)
391
+
392
+ content = page_source
393
+ base_url = @web_browser.context.base_url
394
+ current_url = @web_browser.url
395
+ current_url =~ /(.*\/).*$/
396
+ current_url_parent = $1
397
+ if options[:replacement] && base_url =~ /^http:/
398
+ File.new(file, "w").puts absolutize_page_hpricot(content, base_url, current_url_parent)
399
+ else
400
+ File.new(file, "w").puts content
401
+ end
402
+
403
+ end
404
+
405
+
406
+ # <link rel="stylesheet" type="text/css" href="/stylesheets/default.css" />
407
+ # '<script type="text/javascript" src="http://www.jeroenwijering.com/embed/swfobject.js"></script>'
408
+ # <script type="text/javascript" src="/javascripts/prototype.js"></script>
409
+ # <script type="text/javascript" src="/javascripts/scriptaculous.js?load=effects,builder"></script>
410
+ # <script type="text/javascript" src="/javascripts/extensions/gallery/lightbox.js"></script>
411
+ # <link href="/stylesheets/extensions/gallery/lightbox.css" rel="stylesheet" type="text/css" />
412
+ # <img src="images/mission_48.png" />
413
+ def absolutize_page(content, base_url, current_url_parent)
414
+ modified_content = ""
415
+ content.each_line do |line|
416
+ if line =~ /<script\s+.*src=["'']?(.*)["'].*/i then
417
+ script_src = $1
418
+ substitute_relative_path_in_src_line(line, script_src, base_url, current_url_parent)
419
+ elsif line =~ /<link\s+.*href=["'']?(.*)["'].*/i then
420
+ link_href = $1
421
+ substitute_relative_path_in_src_line(line, link_href, base_url, current_url_parent)
422
+ elsif line =~ /<img\s+.*src=["'']?(.*)["'].*/i then
423
+ img_src = $1
424
+ substitute_relative_path_in_src_line(line, img_src, base_url, current_url_parent)
425
+ end
426
+
427
+ modified_content += line
428
+ end
429
+ return modified_content
430
+ end
431
+
432
+ # absolutize_page referencs using hpricot
433
+ #
434
+ def absolutize_page_hpricot(content, base_url, parent_url)
435
+ return absolutize_page(content, base_url, parent_url) if RUBY_PLATFORM == 'java'
436
+ begin
437
+ require 'hpricot'
438
+ doc = Hpricot(content)
439
+ base_url.slice!(-1) if ends_with?(base_url, "/")
440
+ (doc/'link').each { |e| e['href'] = absolutify_url(e['href'], base_url, parent_url) || ""}
441
+ (doc/'img').each { |e| e['src'] = absolutify_url(e['src'], base_url, parent_url) || ""}
442
+ (doc/'script').each { |e| e['src'] = absolutify_url(e['src'], base_url, parent_url) || ""}
443
+ return doc.to_html
444
+ rescue => e
445
+ absolutize_page(content, base_url, parent_url)
446
+ end
447
+ end
448
+
449
+ ##
450
+ # change
451
+ # <script type="text/javascript" src="/javascripts/prototype.js"></script>
452
+ # to
453
+ # <script type="text/javascript" src="http://itest2.com/javascripts/prototype.js"></script>
454
+ def absolutify_url(src, base_url, parent_url)
455
+ if src.nil? || src.empty? || src == "//:" || src =~ /\s*http:\/\//i
456
+ return src
457
+ end
458
+
459
+ return "#{base_url}#{src}" if src =~ /^\s*\//
460
+ return "#{parent_url}#{src}" if parent_url
461
+ return src
462
+ end
463
+
464
+ # substut
465
+ def substitute_relative_path_in_src_line(line, script_src, host_url, page_parent_url)
466
+ unless script_src =~ /^["']?http:/
467
+ host_url.slice!(-1) if ends_with?(host_url, "/")
468
+ if script_src =~ /^\s*\// # absolute_path
469
+ line.gsub!(script_src, "#{host_url}#{script_src}")
470
+ else #relative_path
471
+ line.gsub!(script_src, "#{page_parent_url}#{script_src}")
472
+ end
473
+ end
474
+ end
475
+
476
+ def ends_with?(str, suffix)
477
+ suffix = suffix.to_s
478
+ str[-suffix.length, suffix.length] == suffix
479
+ end
480
+
481
+ # current web page title
482
+ def page_title
483
+ @web_browser.page_title
484
+ end
485
+
486
+ # current page source (in HTML)
487
+ def page_source
488
+ @web_browser.page_source
489
+ end
490
+
491
+ # return plain text view of page
492
+ def page_text
493
+ @web_browser.text
494
+ end
495
+
496
+ # return the text of specific (identified by attribute "id") label tag
497
+ # For page containing
498
+ # <label id="preferred_ide">TestWise</label>
499
+ # label_with_id("preferred_ids") # => TestWise
500
+ # label_with_id("preferred_ids", :index => 2) # => TestWise
501
+ def label_with_id(label_id, options = {})
502
+ if options && options[:index] then
503
+ label(:id => label_id.to_s, :index => options[:index]).text
504
+ else
505
+ label(:id, label_id.to_s).text
506
+ end
507
+ end
508
+
509
+ # return the text of specific (identified by attribute "id") span tag
510
+ # For page containing
511
+ # <span id="preferred_recorder">iTest2/Watir Recorder</span>
512
+ # span_with_id("preferred_recorder") # => iTest2/Watir Recorder
513
+ def span_with_id(span_id, options = {})
514
+ if options && options[:index] then
515
+ span(:id => span_id.to_s, :index => options[:index]).text
516
+ else
517
+ span(:id, span_id).text
518
+ end
519
+ end
520
+
521
+ # return the text of specific (identified by attribute "id") ta tag
522
+ # For page containing
523
+ # <td id="preferred_recorder">iTest2/Watir Recorder</span>
524
+ # td_with_id("preferred_recorder") # => iTest2/Watir Recorder
525
+ def cell_with_id(cell_id, options = {})
526
+ if options && options[:index] then
527
+ cell(:id => cell_id.to_s, :index => options[:index]).text
528
+ else
529
+ cell(:id, cell_id).text
530
+ end
531
+ end
532
+
533
+ alias table_data_with_id cell_with_id
534
+
535
+
536
+ def is_mac?
537
+ RUBY_PLATFORM.downcase.include?("darwin")
538
+ end
539
+
540
+ def is_windows?
541
+ RUBY_PLATFORM.downcase.include?("mswin") or RUBY_PLATFORM.downcase.include?("mingw")
542
+ end
543
+
544
+ def is_linux?
545
+ RUBY_PLATFORM.downcase.include?("linux")
546
+ end
547
+
548
+ # Support browser (IE) operations using unicode
549
+ # Example:
550
+ # click_button("Google 搜索")
551
+ # Reference: http://jira.openqa.org/browse/WTR-219
552
+ def support_utf8
553
+ if is_windows?
554
+ require 'win32ole'
555
+ WIN32OLE.codepage = WIN32OLE::CP_UTF8
556
+ end
557
+ end
558
+
559
+ alias support_unicode support_utf8
560
+
561
+ #= Convenient functions
562
+ #
563
+
564
+ # Using Ruby block syntax to create interesting domain specific language,
565
+ # may be appeal to someone.
566
+
567
+ # Example:
568
+ # on @page do |i|
569
+ # i.enter_text('btn1')
570
+ # i.click_button('btn1')
571
+ # end
572
+ def on(page, &block)
573
+ yield page
574
+ end
575
+
576
+ # fail the test if user can perform the operation
577
+ #
578
+ # Example:
579
+ # shall_not_allow { 1/0 }
580
+ def shall_not_allow(&block)
581
+ operation_performed_ok = false
582
+ begin
583
+ yield
584
+ operation_performed_ok = true
585
+ rescue
586
+ end
587
+ raise "Operation shall not be allowed" if operation_performed_ok
588
+ end
589
+
590
+ alias do_not_allow shall_not_allow
591
+
592
+ # Does not provide real function, other than make enhancing test syntax
593
+ #
594
+ # Example:
595
+ # allow { click_button('Register') }
596
+ def allow(&block)
597
+ yield
598
+ end
599
+
600
+ alias shall_allow allow
601
+ alias allowing allow
602
+
603
+ # try operation, ignore if errors occur
604
+ #
605
+ # Example:
606
+ # failsafe { click_link("Logout") } # try logout, but it still OK if not being able to (already logout))
607
+ def failsafe(&block)
608
+ begin
609
+ yield
610
+ rescue =>e
611
+ end
612
+ end
613
+
614
+ alias fail_safe failsafe
615
+
616
+
617
+ # Execute the provided block until either (1) it returns true, or
618
+ # (2) the timeout (in seconds) has been reached. If the timeout is reached,
619
+ # a TimeOutException will be raised. The block will always
620
+ # execute at least once.
621
+ #
622
+ # This does not handle error, if the given block raise error, the statement finish with error
623
+ # Examples:
624
+ # wait_until {puts 'hello'}
625
+ # wait_until { div(:id, :receipt_date).exists? }
626
+ #
627
+ def wait_until(timeout = @@default_timeout || 30, polling_interval = @@default_polling_interval || 1, &block)
628
+ waiter = Watir::Waiter.new(timeout, polling_interval)
629
+ waiter.wait_until { yield }
630
+ end
631
+
632
+ # Wait for specific seconds for an Ajax update finish.
633
+ # Trick: In your Rails application,
634
+ # :loading => "Element.show('search_indicator');
635
+ # :complete => "Element.hide('search_indicator');
636
+ #
637
+ # <%= image_tag("indicator.gif", :id => 'search_indicator', :style => 'display:none') %>
638
+ #
639
+ # Typical usage:
640
+ # ajax_wait_for_element("search_indicator", 30)
641
+ # ajax_wait_for_element("search_indicator", 30, "show")
642
+ # ajax_wait_for_element("search_indicator", 30, "hide")
643
+ # ajax_wait_for_element("search_indicator", 30, "show", 5) # check every 5 seconds
644
+ #
645
+ # Warning: this method has not been fully tested, if you are not using Rails, change your parameter accordingly.
646
+ #
647
+ def ajax_wait_for_element(element_id, seconds, status='show', check_interval = @@default_polling_interval)
648
+ count = 0
649
+ check_interval = 1 if check_interval < 1 or check_interval > seconds
650
+ while count < (seconds / check_interval) do
651
+ search_indicator = @web_browser.element_by_id(element_id)
652
+ search_indicator_outer_html = search_indicator.outerHtml if search_indicator
653
+ if status == 'hide'
654
+ return true if search_indicator_outer_html and !search_indicator_outer_html.include?('style="DISPLAY: none"')
655
+ else
656
+ return true if search_indicator_outer_html and search_indicator_outer_html.include?('style="DISPLAY: none"')
657
+ end
658
+ sleep check_interval if check_interval > 0 and check_interval < 5 * 60 # wait max 5 minutes
659
+ count += 1
660
+ end
661
+ return false
662
+ end
663
+
664
+ #Wait the element with given id to be present in web page
665
+ #
666
+ # Warning: this not working in Firefox, try use wait_util or try instead
667
+ def wait_for_element(element_id, timeout = @@default_timeout, interval = @@default_polling_interval)
668
+ start_time = Time.now
669
+ #TODO might not work with Firefox
670
+ until @web_browser.element_by_id(element_id) do
671
+ sleep(interval)
672
+ if (Time.now - start_time) > timeout
673
+ raise RuntimeError, "failed to find element: #{element_id} for max #{timeout}"
674
+ end
675
+ end
676
+ end
677
+
678
+ =begin
679
+
680
+ # TODO: Firewatir does not suport retrieving style or outerHtml
681
+ # http://jira.openqa.org/browse/WTR-260
682
+ # http://code.google.com/p/firewatir/issues/detail?id=76
683
+ #
684
+ # Max timeout value is 10 minutes
685
+ #
686
+ def ajax_call_complete_after_element_hidden(elem_id, check_start = 0.5, timeout = 5, interval = 0.5, &block)
687
+ yield
688
+ sleep check_start # the time allowed to perform the coomplete
689
+ timeout = 10 * 60 if timeout > 10 * 600 or timeout <= 0
690
+ begin
691
+ Timeout::timeout(timeout) {
692
+ begin
693
+ elem = element_by_id(elem_id)
694
+ while elem do
695
+ puts "outer=>#{elem.outerHtml}|"
696
+ puts "style =>#{elem.attribute_value('style')}|"
697
+ sleep interval
698
+ elem = element_by_id(elem_id)
699
+ end
700
+ rescue => e
701
+ puts e
702
+ end
703
+ }
704
+ rescue Timeout::Error
705
+ # Too slow!!
706
+ raise "Too slow, wait max #{timeout} seconds, the element #{elem_id} still there"
707
+ end
708
+ end
709
+
710
+ =end
711
+
712
+ # Try the operation up to specified times, and sleep given interval (in seconds)
713
+ # Error will be ignored until timeout
714
+ # Example
715
+ # repeat_try(3, 2) { click_button('Search' } # 3 times, 6 seconds in total
716
+ # repeat_try { click_button('Search' } # using default 5 tries, 2 second interval
717
+ def repeat_try(num_tries = @@default_timeout || 30, interval = @@default_polling_interval || 1, &block)
718
+ num_tries ||= 1
719
+ (num_tries - 1).times do |num|
720
+ begin
721
+ yield
722
+ return
723
+ rescue => e
724
+ # puts "debug: #{num} failed: #{e}"
725
+ sleep interval
726
+ end
727
+ end
728
+
729
+ # last try, throw error if still fails
730
+ begin
731
+ yield
732
+ rescue => e
733
+ raise e.to_s + " after trying #{num_tries} times every #{interval} seconds"
734
+ end
735
+ yield
736
+ end
737
+
738
+ # TODO: syntax
739
+
740
+ # Try the operation up to specified timeout (in seconds), and sleep given interval (in seconds).
741
+ # Error will be ignored until timeout
742
+ # Example
743
+ # try { click_link('waiting')}
744
+ # try(10, 2) { click_button('Search' } # try to click the 'Search' button upto 10 seconds, try every 2 seconds
745
+ # try { click_button('Search' }
746
+ def try(timeout = @@default_timeout, polling_interval = @@default_polling_interval || 1, &block)
747
+ start_time = Time.now
748
+
749
+ last_error = nil
750
+ until (duration = Time.now - start_time) > timeout
751
+ begin
752
+ return if yield
753
+ last_error = nil
754
+ rescue => e
755
+ last_error = e
756
+ end
757
+ sleep polling_interval
758
+ end
759
+
760
+ raise "Timeout after #{duration.to_i} seconds with error: #{last_error}." if last_error
761
+ raise "Timeout after #{duration.to_i} seconds."
762
+ end
763
+
764
+ alias try_upto try
765
+
766
+ ##
767
+ # Convert :first to 1, :second to 2, and so on...
768
+ def symbol_to_sequence(symb)
769
+ value = { :zero => 0,
770
+ :first => 1,
771
+ :second => 2,
772
+ :third => 3,
773
+ :fourth => 4,
774
+ :fifth => 5,
775
+ :sixth => 6,
776
+ :seventh => 7,
777
+ :eighth => 8,
778
+ :ninth => 9,
779
+ :tenth => 10 }[symb]
780
+ return value || symb.to_i
781
+ end
782
+
783
+ # Clear popup windows such as 'Security Alert' or 'Security Information' popup window,
784
+ #
785
+ # Screenshot see http://kb2.adobe.com/cps/165/tn_16588.html
786
+ #
787
+ # You can also by pass security alerts by change IE setting, http://kb.iu.edu/data/amuj.html
788
+ #
789
+ # Example
790
+ # clear_popup("Security Information", 5, true) # check for Security Information for 5 seconds, click Yes
791
+ def clear_popup(popup_win_title, seconds = 10, yes = true)
792
+ # commonly "Security Alert", "Security Information"
793
+ if is_windows?
794
+ sleep 1
795
+ autoit = WIN32OLE.new('AutoItX3.Control')
796
+ # Look for window with given title. Give up after 1 second.
797
+ ret = autoit.WinWait(popup_win_title, '', seconds)
798
+ #
799
+ # If window found, send appropriate keystroke (e.g. {enter}, {Y}, {N}).
800
+ if ret == 1 then
801
+ puts "about to send click Yes" if debugging?
802
+ button_id = yes ? "Button1" : "Button2" # Yes or No
803
+ autoit.ControlClick(popup_win_title, '', button_id)
804
+ end
805
+ sleep(0.5)
806
+ else
807
+ raise "Currently supported only on Windows"
808
+ end
809
+ end
810
+
811
+ def select_file_for_upload(file_field_name, file_path)
812
+ if is_windows?
813
+ normalized_file_path = file_path.gsub("/", "\\")
814
+ if $support_ie8 && check_ie_version && @ie_version >= 8
815
+ # puts "IE8"
816
+ file_field(:name, file_field).set(normalized_file_path)
817
+ # choose_file_dialog(normalized_file_path)
818
+ else
819
+ file_field(:name, file_field).set(normalized_file_path)
820
+ end
821
+ else
822
+ # for firefox, just call file_field, it may fail
823
+ file_field(:name, file_field).set(normalized_file_path)
824
+ end
825
+ end
826
+
827
+ def choose_file_dialog(file_path)
828
+ Watir.autoit.WinWaitActive("Choose File to Upload", '', 10)
829
+ Watir.autoit.ControlSetText("Choose File to Upload", "", 1148, file_path)
830
+ Watir.autoit.ControlClick("Choose File to Upload", "", "&Open")
831
+ end
832
+
833
+ def check_ie_version
834
+ if is_windows? && @ie_version.nil?
835
+ begin
836
+ cmd = 'reg query "HKLM\SOFTWARE\Microsoft\Internet Explorer" /v Version';
837
+ result = `#{cmd}`
838
+ version_line = nil
839
+ result.each do |line|
840
+ if (line =~ /Version\s+REG_SZ\s+([\d\.]+)/)
841
+ version_line = $1
842
+ end
843
+ end
844
+
845
+ if version_line =~ /^\s*(\d+)\./
846
+ @ie_version = $1.to_i
847
+ end
848
+ rescue => e
849
+ end
850
+ end
851
+ end
852
+
853
+
854
+ # take_screenshot to save the current active window
855
+ # TODO can't move mouse
856
+ def take_screenshot
857
+ if is_windows? && $ITEST2_DUMP_PAGE
858
+ begin
859
+ puts "[DEBUG] Capturing screenshots..."
860
+ screenshot_image_filename = "rwebspec_" + Time.now.strftime("%m%d%H%M%S") + ".jpg"
861
+ the_dump_dir = default_dump_dir
862
+ FileUtils.mkdir_p(the_dump_dir) unless File.exists?(the_dump_dir)
863
+ screenshot_image_filepath = File.join(the_dump_dir, screenshot_image_filename)
864
+
865
+ screenshot_image_filepath.gsub!("/", "\\") if is_windows?
866
+ screen_capture(screenshot_image_filepath, true)
867
+
868
+ notify_screenshot_location(screenshot_image_filepath)
869
+ rescue
870
+ puts "error: #{Failed to capture screen}"
871
+ end
872
+ end
873
+ end
874
+
875
+
876
+ end
877
+
878
+ end