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