rwebspec 1.4.0.2 → 1.6

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