rwebspec 1.4.0 → 1.4.0.1

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