zhimin-rwebspec 1.4.0.2 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGELOG CHANGED
@@ -1,8 +1,14 @@
1
1
  CHANGELOG
2
2
  =========
3
+ 1.4.1
4
+ Added LoadTestHelper
5
+ add expect_page to web_browser
6
+
7
+
3
8
  1.4.0.2
4
9
  Fixed: reuse browser does not work
5
10
  Add gem dependency (Watir/Firewatir) depends on platforms
11
+ Patch Unicode by default
6
12
  More rdoc update
7
13
 
8
14
  1.4.0
data/Rakefile CHANGED
@@ -69,7 +69,7 @@ end
69
69
  spec = Gem::Specification.new do |s|
70
70
  s.platform= Gem::Platform::RUBY
71
71
  s.name = "rwebspec"
72
- s.version = "1.4.0.2"
72
+ s.version = "1.4.1"
73
73
  s.summary = "Executable functional specification for web applications in RSpec syntax and Watir"
74
74
  # s.description = ""
75
75
 
@@ -13,7 +13,7 @@ end
13
13
  require 'active_support/core_ext'
14
14
  require 'spec'
15
15
 
16
- RWEBSPEC_VERSION = RWEBUNIT_VERSION = "1.4.0.2"
16
+ RWEBSPEC_VERSION = RWEBUNIT_VERSION = "1.4.1.0"
17
17
 
18
18
  # Extra full path to load libraries
19
19
  require File.dirname(__FILE__) + "/rwebspec/using_pages"
@@ -26,6 +26,7 @@ require File.dirname(__FILE__) + "/rwebspec/driver"
26
26
  require File.dirname(__FILE__) + "/rwebspec/test_script"
27
27
  require File.dirname(__FILE__) + "/rwebspec/context"
28
28
  require File.dirname(__FILE__) + "/rwebspec/rspec_helper"
29
+ require File.dirname(__FILE__) + "/rwebspec/load_test_helper"
29
30
  require File.dirname(__FILE__) + "/rspec_extensions"
30
31
  require File.dirname(__FILE__) + "/watir_extensions"
31
32
  require File.dirname(__FILE__) + "/rwebspec/matchers/contains_text"
@@ -1,734 +1,741 @@
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
+
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
+ @web_browser.expect_page(page_clazz, argument)
115
+ else
116
+ @web_browser.expect_page(page_clazz)
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