rsel 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/Rakefile +22 -8
- data/docs/development.md +1 -0
- data/docs/history.md +11 -1
- data/docs/index.md +1 -0
- data/docs/scoping.md +1 -1
- data/docs/studying.md +76 -0
- data/lib/rsel/selenium_test.rb +505 -90
- data/lib/rsel/study_html.rb +198 -0
- data/lib/rsel/support.rb +105 -4
- data/rsel.gemspec +1 -1
- data/spec/spec_helper.rb +4 -22
- data/spec/st_alerts.rb +48 -0
- data/spec/st_browser_spec.rb +58 -0
- data/spec/st_buttons_spec.rb +95 -0
- data/spec/st_checkboxes_spec.rb +235 -0
- data/spec/st_conditionals_spec.rb +180 -0
- data/spec/st_dropdowns_spec.rb +140 -0
- data/spec/st_field_equals_among_spec.rb +48 -0
- data/spec/st_fields_equal_among_spec.rb +74 -0
- data/spec/st_fields_equal_spec.rb +90 -0
- data/spec/st_fields_spec.rb +167 -0
- data/spec/st_initialization_spec.rb +33 -0
- data/spec/st_links_spec.rb +84 -0
- data/spec/st_method_missing_spec.rb +59 -0
- data/spec/st_navigation_spec.rb +56 -0
- data/spec/st_radiobuttons_spec.rb +123 -0
- data/spec/st_respond_to_spec.rb +16 -0
- data/spec/st_scenario_spec.rb +26 -0
- data/spec/st_set_field_among_spec.rb +45 -0
- data/spec/st_set_field_spec.rb +842 -0
- data/spec/st_set_fields_among_spec.rb +74 -0
- data/spec/st_set_fields_spec.rb +97 -0
- data/spec/st_spec_helper.rb +43 -0
- data/spec/st_stop_on_failure_spec.rb +199 -0
- data/spec/st_tables_spec.rb +42 -0
- data/spec/st_temporal_visibility_spec.rb +122 -0
- data/spec/st_visibility_spec.rb +125 -0
- data/spec/st_waiting_spec.rb +37 -0
- data/spec/study_html_spec.rb +310 -0
- data/spec/support_spec.rb +163 -13
- data/test/server/README.txt +3 -0
- data/test/views/alert.erb +15 -0
- data/test/views/form.erb +6 -1
- data/test/views/index.erb +2 -0
- data/test/views/slowtext.erb +1 -1
- metadata +38 -9
- data/spec/selenium_test_spec.rb +0 -2656
data/.gitignore
CHANGED
data/Rakefile
CHANGED
@@ -5,9 +5,10 @@ require 'rspec/core/rake_task'
|
|
5
5
|
|
6
6
|
ROOT_DIR = File.expand_path(File.dirname(__FILE__))
|
7
7
|
TEST_APP = File.join(ROOT_DIR, 'test', 'app.rb')
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
SELENIUM_JAR = 'selenium-server-standalone-2.20.0.jar'
|
9
|
+
SELENIUM_DOWNLOAD_URL = 'http://selenium.googlecode.com/files/' + SELENIUM_JAR
|
10
|
+
SELENIUM_JAR_PATH = File.join(ROOT_DIR, 'test', 'server', SELENIUM_JAR)
|
11
|
+
SELENIUM_LOG_PATH = File.join(ROOT_DIR, 'selenium-rc.log')
|
11
12
|
|
12
13
|
namespace 'testapp' do
|
13
14
|
desc "Start the test webapp in the background"
|
@@ -28,13 +29,25 @@ namespace 'testapp' do
|
|
28
29
|
end
|
29
30
|
end
|
30
31
|
|
32
|
+
namespace 'selenium' do
|
33
|
+
desc "Download the official selenium-server jar file"
|
34
|
+
task :download do
|
35
|
+
if File.exist?(SELENIUM_JAR_PATH)
|
36
|
+
puts "#{SELENIUM_JAR_PATH} already exists. Skipping download."
|
37
|
+
else
|
38
|
+
puts "Downloading #{SELENIUM_JAR_PATH} (this may take a minute)..."
|
39
|
+
system("wget #{SELENIUM_DOWNLOAD_URL} --output-document=#{SELENIUM_JAR_PATH}")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
31
44
|
Selenium::Rake::RemoteControlStartTask.new do |rc|
|
32
|
-
rc.jar_file =
|
45
|
+
rc.jar_file = SELENIUM_JAR_PATH
|
33
46
|
rc.port = 4444
|
34
47
|
rc.background = true
|
35
48
|
rc.timeout_in_seconds = 60
|
36
49
|
rc.wait_until_up_and_running = true
|
37
|
-
rc.log_to =
|
50
|
+
rc.log_to = SELENIUM_LOG_PATH
|
38
51
|
end
|
39
52
|
|
40
53
|
Selenium::Rake::RemoteControlStopTask.new do |rc|
|
@@ -58,18 +71,18 @@ namespace 'rcov' do
|
|
58
71
|
t.rcov = true
|
59
72
|
t.rcov_opts = [
|
60
73
|
'--exclude /.gem/,/gems/,spec',
|
61
|
-
'--include lib/**/*.rb',
|
74
|
+
'--include-file lib/**/*.rb',
|
62
75
|
]
|
63
76
|
end
|
64
77
|
|
65
|
-
desc "Run
|
78
|
+
desc "Run all spec tests with coverage analysis"
|
66
79
|
RSpec::Core::RakeTask.new(:all) do |t|
|
67
80
|
t.pattern = 'spec/**/*.rb'
|
68
81
|
t.rspec_opts = ['--color', '--format doc']
|
69
82
|
t.rcov = true
|
70
83
|
t.rcov_opts = [
|
71
84
|
'--exclude /.gem/,/gems/,spec',
|
72
|
-
'--include lib/**/*.rb',
|
85
|
+
'--include-file lib/**/*.rb',
|
73
86
|
# Ensure the main .rb file gets included
|
74
87
|
'--include-file lib/rsel/selenium_test.rb',
|
75
88
|
]
|
@@ -79,6 +92,7 @@ end
|
|
79
92
|
namespace 'servers' do
|
80
93
|
desc "Start the Selenium and testapp servers"
|
81
94
|
task :start do
|
95
|
+
Rake::Task['selenium:download'].invoke
|
82
96
|
Rake::Task['testapp:start'].invoke
|
83
97
|
Rake::Task['selenium:rc:start'].invoke
|
84
98
|
end
|
data/docs/development.md
CHANGED
@@ -39,6 +39,7 @@ gets broken. You can do this with a single `rake` command:
|
|
39
39
|
|
40
40
|
This will do the following:
|
41
41
|
|
42
|
+
- Download an official selenium-server `.jar` into `test/server` (if needed)
|
42
43
|
- Start the Sinatra test application (code in `test/app.rb` and `test/views/*.erb`)
|
43
44
|
- Start the Selenium server (the `.jar` file in `test/server`)
|
44
45
|
- Run RSpec
|
data/docs/history.md
CHANGED
@@ -1,11 +1,21 @@
|
|
1
1
|
Rsel History
|
2
2
|
============
|
3
3
|
|
4
|
+
0.1.2
|
5
|
+
-----
|
6
|
+
|
7
|
+
- Studying: customizable optimization to improve performance
|
8
|
+
- More scoping (see, do_not_see, see_within_seconds, do_not_see_within_seconds)
|
9
|
+
- Alert verification (see_alert_within_seconds)
|
10
|
+
- Glob and regex can now be used in more places (selenium_compare)
|
11
|
+
|
12
|
+
|
13
|
+
|
4
14
|
0.1.1
|
5
15
|
-----
|
6
16
|
|
7
17
|
- Conditional expressions (if_is, if_i_see, if_parameter, otherwise, end_if)
|
8
|
-
- Temporal visibility (
|
18
|
+
- Temporal visibility (see_within_seconds, do_not_see_within_seconds)
|
9
19
|
- Generic field equality (generic_field_equals)
|
10
20
|
- Generic field fill-in (set_field, set_field_among)
|
11
21
|
- Hash-based field fill-in (set_fields, sef_fields_among)
|
data/docs/index.md
CHANGED
data/docs/scoping.md
CHANGED
@@ -141,5 +141,5 @@ have a use case that isn't covered by the existing scopes, please [submit an
|
|
141
141
|
issue](http://github.com/a-e/rsel/issues), or better yet, implement it yourself
|
142
142
|
and submit a pull request. See [Development](development.md) for more info.
|
143
143
|
|
144
|
-
Next: [
|
144
|
+
Next: [Studying](studying.md)
|
145
145
|
|
data/docs/studying.md
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
Studying
|
2
|
+
========
|
3
|
+
|
4
|
+
What is studying?
|
5
|
+
-----------------
|
6
|
+
Studying is looking at some material to recall it more easily later. No,
|
7
|
+
really! In this case, studying refers to a process that sends the entire web
|
8
|
+
page from the browser to the Rsel server for further analysis. The name is
|
9
|
+
based on Perl's `study` command.
|
10
|
+
|
11
|
+
Why would I want to study? (I'm not in school anymore!)
|
12
|
+
--------------------------------------------------------
|
13
|
+
The main reason for studying is that Internet Explorer works with pages, and
|
14
|
+
xpaths in particular, very slowly. It so happens that the xpaths sent by
|
15
|
+
normal Rsel commands are particularly slow to process. Studying can make your
|
16
|
+
test run five times as fast or more!
|
17
|
+
|
18
|
+
Wow! What's the catch?
|
19
|
+
-----------------------
|
20
|
+
The catch is that when you study, you may not be aware of what's going on
|
21
|
+
around you. While studying, if the page changes on the web browser, you often
|
22
|
+
won't see those changes. This can lead to failed identification of controls,
|
23
|
+
or worse, misidentifications.
|
24
|
+
|
25
|
+
The other, minor catch is that you could wind up spending more time sending
|
26
|
+
data (entire web pages) than you save by studying.
|
27
|
+
|
28
|
+
Alright, I'll be careful. How do I use studying?
|
29
|
+
-------------------------------------------------
|
30
|
+
There are two ways, the easy way and the efficient way. The efficient way is
|
31
|
+
to identify blocks where you are doing a lot of work on one page, but the HTML
|
32
|
+
content of a page won't change. Then place a `begin_study` call before the
|
33
|
+
block, and an `end_study` call after it.
|
34
|
+
|
35
|
+
What happens if I forget about end_study?
|
36
|
+
-----------------------------------------
|
37
|
+
Don't worry: studying is smart enough to notice certain commands that indicate
|
38
|
+
major page changes, such as `page_loads_in_seconds_or_less` and
|
39
|
+
`see_within_seconds`. Of course, this could also lead to your study blocks
|
40
|
+
ending before you expect them to, but such commands almost always mean a study
|
41
|
+
block should end.
|
42
|
+
|
43
|
+
That's nice, but adding all those blocks sounds like a pain. What's the easy way?
|
44
|
+
----------------------------------------------------------------------------------
|
45
|
+
The easy way is to set a minimum number of fields which, when worked with at
|
46
|
+
once, will be studied before the work starts. You do this with either the
|
47
|
+
`study` option when initializing Rsel or with the `set_fields_study_min`
|
48
|
+
command within Rsel. Zero, the default, means to never study. Setting the
|
49
|
+
value to 10, for instance, would mean that when one command works with 10 or
|
50
|
+
more different fields at once, a study would happen before the work begins.
|
51
|
+
Right now, only the `set_fields` and `fields_equal` classes of commands can
|
52
|
+
work with so many different fields at once.
|
53
|
+
|
54
|
+
What if I set set_fields_study_min to 1? Does it...study for every command?
|
55
|
+
----------------------------------------------------------------------------
|
56
|
+
Almost. Along with the set_fields and fields_equal commands mentioned above,
|
57
|
+
every command that uses an xpath of 100 characters or more gets studied. (This
|
58
|
+
bit is currently hard-coded; I may modify it in future versions.) Some
|
59
|
+
commands are also known not to affect the page text, like `see` or `get_text`.
|
60
|
+
These do not trigger re-studying for the next command. Even if
|
61
|
+
`set_fields_study_min` is greater than 1, commands after a `fields_equal` may
|
62
|
+
also benefit from the studying done in that command.
|
63
|
+
|
64
|
+
Too long; didn't read. Can't I just say "go fast" or something?
|
65
|
+
----------------------------------------------------------------
|
66
|
+
Pretty much. Add the `study:auto` value to the Rsel initialization hash.
|
67
|
+
|
68
|
+
Sweet, most of my tests are fast on IE! But some now fail.
|
69
|
+
-----------------------------------------------------------
|
70
|
+
Auto-studying works very hard to be transparent and invisible, but rarely it
|
71
|
+
fails. For sections like this, you can turn off studying with
|
72
|
+
`set_fields_study_min(0)` or `set_fields_study_min('never')`. You can later go
|
73
|
+
back to what you set in the initialization with
|
74
|
+
`set_fields_study_min('default')`.
|
75
|
+
|
76
|
+
Next: [Examples](examples.md)
|
data/lib/rsel/selenium_test.rb
CHANGED
@@ -5,6 +5,7 @@ require 'xpath'
|
|
5
5
|
require 'selenium/client'
|
6
6
|
require 'rsel/support'
|
7
7
|
require 'rsel/exceptions'
|
8
|
+
require 'rsel/study_html'
|
8
9
|
|
9
10
|
module Rsel
|
10
11
|
|
@@ -42,9 +43,16 @@ module Rsel
|
|
42
43
|
# @option options [String, Boolean] :stop_on_failure
|
43
44
|
# `true` or `'true'` to abort the test when a failure occurs;
|
44
45
|
# `false` or `'false'` to continue execution when failure occurs.
|
46
|
+
# @option options [String, Integer] :study
|
47
|
+
# How many steps have to be done at once to force studying. Default
|
48
|
+
# is `Never` (0). Other accepted strings are `Always' (1), `Auto(matic)`
|
49
|
+
# (10 for most browsers and 1 for Internet Explorer), or an integer.
|
50
|
+
# Unrecognized strings result in the default.
|
45
51
|
# @option options [String, Integer] :timeout
|
46
52
|
# Default timeout in seconds. This determines how long the `open` method
|
47
|
-
# will wait for the page to load
|
53
|
+
# will wait for the page to load, as well as the default timeout for
|
54
|
+
# methods like `see_within_seconds`, `do_not_see_within_seconds`, and
|
55
|
+
# `see_alert_within_seconds`.
|
48
56
|
#
|
49
57
|
# @example
|
50
58
|
# | script | selenium test | http://site.to.test/ |
|
@@ -69,6 +77,14 @@ module Rsel
|
|
69
77
|
end
|
70
78
|
@found_failure = false
|
71
79
|
@conditional_stack = [ true ]
|
80
|
+
# Study data
|
81
|
+
@study = StudyHtml.new()
|
82
|
+
# @fields_study_min: The minimum number of fields to set_fields or fields_equal at once before studying is invoked.
|
83
|
+
# To be on the safe side, studying is turned off by default, unless you set another value.
|
84
|
+
@fields_study_min = parse_fields_study_min(options[:study], 0)
|
85
|
+
@default_fields_study_min = @fields_study_min
|
86
|
+
# @xpath_study_length_min: The minimum number of characters in an xpath before studying is invoked when @fields_study_min == 1.
|
87
|
+
@xpath_study_length_min = 100
|
72
88
|
# A list of error messages:
|
73
89
|
@errors = []
|
74
90
|
end
|
@@ -78,7 +94,7 @@ module Rsel
|
|
78
94
|
|
79
95
|
|
80
96
|
# Start the session and open a browser to the URL defined at the start of
|
81
|
-
# the test.
|
97
|
+
# the test. If a browser session is already open, just return true.
|
82
98
|
#
|
83
99
|
# @example
|
84
100
|
# | Open browser |
|
@@ -86,6 +102,7 @@ module Rsel
|
|
86
102
|
# @raise [StopTestCannotConnect] if Selenium connection cannot be made
|
87
103
|
#
|
88
104
|
def open_browser
|
105
|
+
return true if @browser.session_started?
|
89
106
|
begin
|
90
107
|
@browser.start_new_browser_session
|
91
108
|
rescue
|
@@ -115,6 +132,13 @@ module Rsel
|
|
115
132
|
#
|
116
133
|
def close_browser(show_errors='')
|
117
134
|
@browser.close_current_browser_session
|
135
|
+
end_study
|
136
|
+
if in_conditional?
|
137
|
+
# Note the lack of return. This just adds an error to the stack if we're in a conditional.
|
138
|
+
failure "If without matching End if"
|
139
|
+
reset_conditionals
|
140
|
+
end
|
141
|
+
|
118
142
|
# Show errors in an exception if requested.
|
119
143
|
if (!(/not|without/i === show_errors) && @errors.length > 0)
|
120
144
|
raise StopTestStepFailed, @errors.join("\n").gsub('<','<')
|
@@ -136,6 +160,69 @@ module Rsel
|
|
136
160
|
return current_errors.join("\n")
|
137
161
|
end
|
138
162
|
|
163
|
+
|
164
|
+
# Study the current web page, for more efficient parsing and data retrieval
|
165
|
+
# on the Rsel side rather than the Selenium browser side. Named for the
|
166
|
+
# Perl "study" command. Like its namesake, begin_study takes some time.
|
167
|
+
#
|
168
|
+
# Warning: When you study, you can learn information more quickly, but you
|
169
|
+
# may not be aware of what's going on around you! If any action you perform
|
170
|
+
# has side-effects on the web page, you may not notice them while studying
|
171
|
+
# is on. This is why so many methods call {#end_study} automatically. But
|
172
|
+
# many cases remain where you could get into trouble if you forget this.
|
173
|
+
#
|
174
|
+
# Note: Calling this twice in a row without an intervening end_study will
|
175
|
+
# result in the first end_study not actually ending studying. Again, many
|
176
|
+
# methods call end_study, so this is a possible, but unlikely, problem.
|
177
|
+
def begin_study
|
178
|
+
return skip_status if skip_step?
|
179
|
+
fail_on_exception do
|
180
|
+
@study.begin_section { page_to_study }
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Turn off studying. Several other methods call this, including:
|
185
|
+
# * {#click_back}
|
186
|
+
# * {#close_browser}
|
187
|
+
# * {#end_scenario}
|
188
|
+
# * {#page_loads_in_seconds_or_less}
|
189
|
+
# * {#refresh_page}
|
190
|
+
# * {#see_within_seconds}
|
191
|
+
# * {#do_not_see_within_seconds}
|
192
|
+
# * {#visit}
|
193
|
+
# Don't be afraid to call this method as many times as you need to!
|
194
|
+
def end_study
|
195
|
+
@study.end_section
|
196
|
+
return skip_status if skip_step?
|
197
|
+
return true
|
198
|
+
end
|
199
|
+
|
200
|
+
# If studying is turned off, a flag is set, but the page is not deleted.
|
201
|
+
# This method turns the flag back on, without reloading the web page.
|
202
|
+
# Useful if you called one of the methods that calls {#end_study}, but
|
203
|
+
# you expect that the page (or the relevant part of a page) didn't
|
204
|
+
# actually change. (Or you just want to verify data on the old page.)
|
205
|
+
def continue_study
|
206
|
+
return skip_status if skip_step?
|
207
|
+
pass_if @study.keep_clean(true), "Unable to continue studying: no page has been studied!"
|
208
|
+
end
|
209
|
+
|
210
|
+
# Set the minimum number of fields that must appear in a set_fields
|
211
|
+
# before studying is invoked. "Never", or 0, turns off studying.
|
212
|
+
# "Always", or 1, turns studying on whenever a long xpath is found.
|
213
|
+
# Any other non-integer string resumes the studying pattern set in
|
214
|
+
# the initial option.
|
215
|
+
#
|
216
|
+
# @param [String or Integer] level
|
217
|
+
# A (parsable) integer
|
218
|
+
def set_fields_study_min(level=nil)
|
219
|
+
return skip_status if skip_step?
|
220
|
+
fail_on_exception do
|
221
|
+
@fields_study_min = parse_fields_study_min(level, @default_fields_study_min)
|
222
|
+
end_study if @fields_study_min == 0
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
139
226
|
# Begin a new scenario, and forget about any previous failures.
|
140
227
|
# This allows you to modularize your tests into standalone sections
|
141
228
|
# that can run independently of previous scenarios, regardless of
|
@@ -162,6 +249,7 @@ module Rsel
|
|
162
249
|
# @since 0.0.9
|
163
250
|
#
|
164
251
|
def end_scenario
|
252
|
+
end_study
|
165
253
|
return true
|
166
254
|
end
|
167
255
|
|
@@ -178,6 +266,7 @@ module Rsel
|
|
178
266
|
#
|
179
267
|
def visit(path_or_url)
|
180
268
|
return skip_status if skip_step?
|
269
|
+
end_study
|
181
270
|
fail_on_exception do
|
182
271
|
@browser.open(strip_tags(path_or_url))
|
183
272
|
end
|
@@ -191,6 +280,7 @@ module Rsel
|
|
191
280
|
#
|
192
281
|
def click_back
|
193
282
|
return skip_status if skip_step?
|
283
|
+
end_study
|
194
284
|
fail_on_exception do
|
195
285
|
@browser.go_back
|
196
286
|
end
|
@@ -204,6 +294,7 @@ module Rsel
|
|
204
294
|
#
|
205
295
|
def refresh_page
|
206
296
|
return skip_status if skip_step?
|
297
|
+
end_study
|
207
298
|
fail_on_exception do
|
208
299
|
@browser.refresh
|
209
300
|
end
|
@@ -226,12 +317,27 @@ module Rsel
|
|
226
317
|
# @param [String] text
|
227
318
|
# Plain text that should be visible on the current page
|
228
319
|
#
|
320
|
+
# @param [Hash] scope
|
321
|
+
# Scoping keywords as understood by {#xpath}
|
322
|
+
#
|
229
323
|
# @example
|
230
324
|
# | See | Welcome, Marcus |
|
231
325
|
#
|
232
|
-
def see(text)
|
326
|
+
def see(text, scope=nil)
|
233
327
|
return skip_status if skip_step?
|
234
|
-
|
328
|
+
if scope.nil?
|
329
|
+
# Can't do a Study workaround - it doesn't know what's visible.
|
330
|
+
return pass_if @browser.text?(text)
|
331
|
+
else
|
332
|
+
selector = loc("css=", '', scope).strip
|
333
|
+
@study.undo_last_dirty # This method does not modify the browser page contents.
|
334
|
+
# Can't do a Study workaround - it doesn't know what's visible.
|
335
|
+
fail_on_exception do
|
336
|
+
match = selenium_compare(@browser.get_text(selector), globify(text))
|
337
|
+
return pass_if match,
|
338
|
+
"'#{text}' not found in '#{@browser.get_text(selector)}'"
|
339
|
+
end
|
340
|
+
end
|
235
341
|
end
|
236
342
|
|
237
343
|
|
@@ -240,12 +346,30 @@ module Rsel
|
|
240
346
|
# @param [String] text
|
241
347
|
# Plain text that should not be visible on the current page
|
242
348
|
#
|
349
|
+
# @param [Hash] scope
|
350
|
+
# Scoping keywords as understood by {#xpath}
|
351
|
+
#
|
243
352
|
# @example
|
244
353
|
# | Do not see | Take a hike |
|
245
354
|
#
|
246
|
-
def do_not_see(text)
|
355
|
+
def do_not_see(text, scope=nil)
|
247
356
|
return skip_status if skip_step?
|
248
|
-
|
357
|
+
if scope.nil?
|
358
|
+
# Can't do a Study workaround - it doesn't know what's visible.
|
359
|
+
return pass_if !@browser.text?(text)
|
360
|
+
else
|
361
|
+
selector = loc("css=", '', scope).strip
|
362
|
+
@study.undo_last_dirty # This method does not modify the browser page contents.
|
363
|
+
begin
|
364
|
+
# Can't do a Study workaround - it doesn't know what's visible.
|
365
|
+
match = selenium_compare(@browser.get_text(selector), globify(text))
|
366
|
+
return pass_if !match,
|
367
|
+
"'#{text}' not expected, but found in '#{@browser.get_text(selector)}'"
|
368
|
+
rescue
|
369
|
+
# Do not see the selector, so do not see the text within it.
|
370
|
+
return true
|
371
|
+
end
|
372
|
+
end
|
249
373
|
end
|
250
374
|
|
251
375
|
|
@@ -255,31 +379,50 @@ module Rsel
|
|
255
379
|
#
|
256
380
|
# @param [String] text
|
257
381
|
# Plain text that should be appear on or visible on the current page
|
258
|
-
# @param [String] seconds
|
259
|
-
# Integer number of seconds to wait.
|
382
|
+
# @param [Integer, String] seconds
|
383
|
+
# Integer number of seconds to wait, or `-1` to use the default timeout.
|
384
|
+
# @param [Hash] scope
|
385
|
+
# Scoping keywords as understood by {#xpath}
|
386
|
+
#
|
260
387
|
#
|
261
388
|
# @example
|
262
389
|
# | Click | ajax_login | button |
|
263
390
|
# | See | Welcome back, Marcus | within | 10 | seconds |
|
264
391
|
# | Note | The following uses the Selenium default timeout: |
|
265
|
-
# | See | How do you feel? | within seconds |
|
392
|
+
# | See | How do you feel? | within seconds | !{within:terminal} |
|
266
393
|
#
|
267
394
|
# @since 0.1.1
|
268
395
|
#
|
269
|
-
def see_within_seconds(text, seconds=-1)
|
396
|
+
def see_within_seconds(text, seconds=-1, scope=nil)
|
270
397
|
return skip_status if skip_step?
|
398
|
+
end_study
|
399
|
+
if scope.nil? && (seconds.is_a? Hash)
|
400
|
+
scope = seconds
|
401
|
+
seconds = -1
|
402
|
+
end
|
271
403
|
seconds = @browser.default_timeout_in_seconds if seconds == -1
|
272
|
-
|
273
|
-
|
274
|
-
|
404
|
+
if scope.nil?
|
405
|
+
return pass_if result_within(seconds) {
|
406
|
+
@browser.text?(text)
|
407
|
+
}
|
408
|
+
# This would be better if it worked:
|
409
|
+
# pass_if @browser.wait_for(:text => text, :timeout_in_seconds => seconds);
|
410
|
+
else
|
411
|
+
selector = loc("css=", '', scope).strip
|
412
|
+
return pass_if result_within(seconds) {
|
413
|
+
selenium_compare(@browser.get_text(selector), globify(text))
|
414
|
+
}
|
415
|
+
end
|
275
416
|
end
|
276
417
|
|
277
418
|
# Ensure that the given text does not appear on the current page, eventually.
|
278
419
|
#
|
279
420
|
# @param [String] text
|
280
421
|
# Plain text that should disappear from or not be present on the current page
|
281
|
-
# @param [String] seconds
|
282
|
-
# Integer number of seconds to wait.
|
422
|
+
# @param [Integer, String] seconds
|
423
|
+
# Integer number of seconds to wait, or `-1` to use the default timeout.
|
424
|
+
# @param [Hash] scope
|
425
|
+
# Scoping keywords as understood by {#xpath}
|
283
426
|
#
|
284
427
|
# @example
|
285
428
|
# | Click | close | button | !{within:popup_ad} |
|
@@ -287,12 +430,26 @@ module Rsel
|
|
287
430
|
#
|
288
431
|
# @since 0.1.1
|
289
432
|
#
|
290
|
-
def do_not_see_within_seconds(text, seconds=-1)
|
433
|
+
def do_not_see_within_seconds(text, seconds=-1, scope=nil)
|
291
434
|
return skip_status if skip_step?
|
435
|
+
end_study
|
436
|
+
if scope.nil? && (seconds.is_a? Hash)
|
437
|
+
scope = seconds
|
438
|
+
seconds = -1
|
439
|
+
end
|
292
440
|
seconds = @browser.default_timeout_in_seconds if seconds == -1
|
293
|
-
|
294
|
-
|
295
|
-
|
441
|
+
if scope.nil?
|
442
|
+
return pass_if result_within(seconds) {
|
443
|
+
!@browser.text?(text)
|
444
|
+
}
|
445
|
+
# This would be better if it worked:
|
446
|
+
# pass_if @browser.wait_for(:no_text => text, :timeout_in_seconds => seconds);
|
447
|
+
else
|
448
|
+
selector = loc("css=", '', scope).strip
|
449
|
+
return pass_if failed_within(seconds) {
|
450
|
+
selenium_compare(@browser.get_text(selector), globify(text))
|
451
|
+
}
|
452
|
+
end
|
296
453
|
end
|
297
454
|
|
298
455
|
|
@@ -307,7 +464,11 @@ module Rsel
|
|
307
464
|
#
|
308
465
|
def see_title(title)
|
309
466
|
return skip_status if skip_step?
|
310
|
-
|
467
|
+
# Study workaround when possible. (Probably won't happen a lot, but possible.)
|
468
|
+
bodynode = @study.get_node('xpath=/html/head/title')
|
469
|
+
return true if bodynode && bodynode.inner_text.strip == title
|
470
|
+
pass_if @browser.get_title == title,
|
471
|
+
"Page title is '#{@browser.get_title}', not '#{title}'"
|
311
472
|
end
|
312
473
|
|
313
474
|
|
@@ -324,6 +485,49 @@ module Rsel
|
|
324
485
|
pass_if !(@browser.get_title == title)
|
325
486
|
end
|
326
487
|
|
488
|
+
# Ensure that an alert appears, optionally having the given text, within the
|
489
|
+
# given time or the default timeout.
|
490
|
+
#
|
491
|
+
# @param [String] text
|
492
|
+
# Text of the alert that you expect to see
|
493
|
+
#
|
494
|
+
# @param [Integer, String] seconds
|
495
|
+
# Integer number of seconds to wait, or `-1` to use the default timeout.
|
496
|
+
#
|
497
|
+
# @example
|
498
|
+
# | see alert within seconds |
|
499
|
+
# Validates any alert within the default timeout.
|
500
|
+
#
|
501
|
+
# @example
|
502
|
+
# | see alert | Illegal operation! Authorities have been notified. | within | 15 | seconds |
|
503
|
+
# Validates that the next alert that appears within the given timeout is as specified.
|
504
|
+
#
|
505
|
+
# @since 0.1.2
|
506
|
+
#
|
507
|
+
def see_alert_within_seconds(text=nil, seconds=-1)
|
508
|
+
return skip_status if skip_step?
|
509
|
+
# Handle the case of being given seconds, but not text.
|
510
|
+
if seconds == -1
|
511
|
+
begin
|
512
|
+
if Integer(text.to_s).to_s == text.to_s
|
513
|
+
seconds = text.to_s
|
514
|
+
text = nil
|
515
|
+
end
|
516
|
+
rescue
|
517
|
+
end
|
518
|
+
end
|
519
|
+
seconds = @browser.default_timeout_in_seconds if seconds == -1
|
520
|
+
alert_text = result_within(seconds) {
|
521
|
+
@browser.get_alert
|
522
|
+
}
|
523
|
+
if alert_text
|
524
|
+
return true if text.nil?
|
525
|
+
return pass_if selenium_compare(alert_text, text),
|
526
|
+
"Expected alert '#{text}', but got '#{alert_text}'!"
|
527
|
+
else
|
528
|
+
return failure
|
529
|
+
end
|
530
|
+
end
|
327
531
|
|
328
532
|
# Ensure that a link exists on the page.
|
329
533
|
#
|
@@ -340,7 +544,13 @@ module Rsel
|
|
340
544
|
#
|
341
545
|
def link_exists(locator, scope={})
|
342
546
|
return skip_status if skip_step?
|
343
|
-
|
547
|
+
locator = loc(locator, 'link', scope)
|
548
|
+
@study.undo_last_dirty # This method does not modify the browser page contents.
|
549
|
+
|
550
|
+
# Study workaround when possible.
|
551
|
+
bodynode = @study.get_node(locator)
|
552
|
+
return true if bodynode
|
553
|
+
pass_if @browser.element?(locator)
|
344
554
|
end
|
345
555
|
|
346
556
|
|
@@ -359,7 +569,13 @@ module Rsel
|
|
359
569
|
#
|
360
570
|
def button_exists(locator, scope={})
|
361
571
|
return skip_status if skip_step?
|
362
|
-
|
572
|
+
locator = loc(locator, 'button', scope)
|
573
|
+
@study.undo_last_dirty # This method does not modify the browser page contents.
|
574
|
+
|
575
|
+
# Study workaround when possible.
|
576
|
+
bodynode = @study.get_node(locator)
|
577
|
+
return true if bodynode
|
578
|
+
pass_if @browser.element?(locator)
|
363
579
|
end
|
364
580
|
|
365
581
|
|
@@ -378,7 +594,13 @@ module Rsel
|
|
378
594
|
#
|
379
595
|
def row_exists(cells)
|
380
596
|
return skip_status if skip_step?
|
381
|
-
|
597
|
+
cells = cells.split(/, */).map { |s| escape_for_hash(s) }
|
598
|
+
locator = "xpath=#{xpath_row_containing(cells)}"
|
599
|
+
|
600
|
+
# Study workaround when possible.
|
601
|
+
bodynode = @study.get_node(locator)
|
602
|
+
return true if bodynode
|
603
|
+
pass_if @browser.element?(locator)
|
382
604
|
end
|
383
605
|
|
384
606
|
#
|
@@ -424,7 +646,6 @@ module Rsel
|
|
424
646
|
type_into_field(text, locator, scope)
|
425
647
|
end
|
426
648
|
|
427
|
-
|
428
649
|
# Verify that a text field contains the given text. The field may include
|
429
650
|
# additional text, as long as the expected value is in there somewhere.
|
430
651
|
#
|
@@ -440,6 +661,7 @@ module Rsel
|
|
440
661
|
return skip_status if skip_step?
|
441
662
|
begin
|
442
663
|
field = @browser.field(loc(locator, 'field', scope))
|
664
|
+
@study.undo_last_dirty # This method does not modify the browser page contents.
|
443
665
|
rescue
|
444
666
|
failure "Can't identify field #{locator}"
|
445
667
|
else
|
@@ -464,6 +686,7 @@ module Rsel
|
|
464
686
|
return skip_status if skip_step?
|
465
687
|
begin
|
466
688
|
field = @browser.field(loc(locator, 'field', scope))
|
689
|
+
@study.undo_last_dirty # This method does not modify the browser page contents.
|
467
690
|
rescue
|
468
691
|
failure "Can't identify field #{locator}"
|
469
692
|
else
|
@@ -592,6 +815,7 @@ module Rsel
|
|
592
815
|
def checkbox_is_enabled(locator, scope={})
|
593
816
|
return skip_status if skip_step?
|
594
817
|
xp = loc(locator, 'checkbox', scope)
|
818
|
+
@study.undo_last_dirty # This method does not modify the browser page contents.
|
595
819
|
begin
|
596
820
|
enabled = @browser.checked?(xp)
|
597
821
|
rescue
|
@@ -618,6 +842,7 @@ module Rsel
|
|
618
842
|
def radio_is_enabled(locator, scope={})
|
619
843
|
return skip_status if skip_step?
|
620
844
|
xp = loc(locator, 'radio_button', scope)
|
845
|
+
@study.undo_last_dirty # This method does not modify the browser page contents.
|
621
846
|
begin
|
622
847
|
enabled = @browser.checked?(xp)
|
623
848
|
rescue
|
@@ -642,6 +867,7 @@ module Rsel
|
|
642
867
|
def checkbox_is_disabled(locator, scope={})
|
643
868
|
return skip_status if skip_step?
|
644
869
|
xp = loc(locator, 'checkbox', scope)
|
870
|
+
@study.undo_last_dirty # This method does not modify the browser page contents.
|
645
871
|
begin
|
646
872
|
enabled = @browser.checked?(xp)
|
647
873
|
rescue
|
@@ -668,6 +894,7 @@ module Rsel
|
|
668
894
|
def radio_is_disabled(locator, scope={})
|
669
895
|
return skip_status if skip_step?
|
670
896
|
xp = loc(locator, 'radio_button', scope)
|
897
|
+
@study.undo_last_dirty # This method does not modify the browser page contents.
|
671
898
|
begin
|
672
899
|
enabled = @browser.checked?(xp)
|
673
900
|
rescue
|
@@ -738,8 +965,11 @@ module Rsel
|
|
738
965
|
# TODO: Apply scope
|
739
966
|
dropdown = XPath::HTML.select(locator)
|
740
967
|
opt = dropdown[XPath::HTML.option(option)]
|
741
|
-
opt_str = opt.to_s
|
742
|
-
|
968
|
+
opt_str = "xpath=#{opt.to_s}"
|
969
|
+
# Study workaround when possible.
|
970
|
+
bodynode = @study.get_node(opt_str)
|
971
|
+
return true if bodynode
|
972
|
+
pass_if @browser.element?(opt_str)
|
743
973
|
end
|
744
974
|
|
745
975
|
|
@@ -759,6 +989,7 @@ module Rsel
|
|
759
989
|
return skip_status if skip_step?
|
760
990
|
begin
|
761
991
|
selected = @browser.get_selected_label(loc(locator, 'select', scope))
|
992
|
+
@study.undo_last_dirty # This method does not modify the browser page contents.
|
762
993
|
rescue
|
763
994
|
failure "Can't identify dropdown #{locator}"
|
764
995
|
else
|
@@ -794,6 +1025,7 @@ module Rsel
|
|
794
1025
|
#
|
795
1026
|
def page_loads_in_seconds_or_less(seconds)
|
796
1027
|
return skip_status if skip_step?
|
1028
|
+
end_study
|
797
1029
|
fail_on_exception do
|
798
1030
|
@browser.wait_for_page_to_load(seconds)
|
799
1031
|
end
|
@@ -832,14 +1064,17 @@ module Rsel
|
|
832
1064
|
def set_field(locator, value='', scope={})
|
833
1065
|
return skip_status if skip_step?
|
834
1066
|
fail_on_exception do
|
835
|
-
|
1067
|
+
loceval = loc(locator, 'field', scope)
|
1068
|
+
mytagname = ''
|
836
1069
|
begin
|
837
|
-
|
1070
|
+
mytagname = tagname(loceval)
|
838
1071
|
rescue
|
839
1072
|
loceval = loc(locator, 'link_or_button', scope)
|
1073
|
+
mytagname = tagname(loceval)
|
840
1074
|
end
|
841
|
-
|
842
|
-
|
1075
|
+
#puts "My tag name is #{mytagname}\n"
|
1076
|
+
#puts "My loceval is #{loceval}\n" #if /^id=/ === loceval
|
1077
|
+
case mytagname
|
843
1078
|
when 'input.text', /^textarea\./
|
844
1079
|
return type_into_field(value, loceval)
|
845
1080
|
when 'input.radio'
|
@@ -855,7 +1090,6 @@ module Rsel
|
|
855
1090
|
when /^(a|button)\./,'input.button','input.submit','input.image','input.reset'
|
856
1091
|
return click(loceval)
|
857
1092
|
else
|
858
|
-
#raise ArgumentError, "Unidentified field #{locator}."
|
859
1093
|
return failure("Unidentified field #{locator}.")
|
860
1094
|
end
|
861
1095
|
end
|
@@ -906,13 +1140,23 @@ module Rsel
|
|
906
1140
|
return skip_status if skip_step?
|
907
1141
|
# FitNesse passes in "" for an empty field. Fix it.
|
908
1142
|
fields = {} if fields == ""
|
1143
|
+
|
1144
|
+
method_study = false
|
1145
|
+
if @fields_study_min != 0 && @fields_study_min <= fields.length
|
1146
|
+
method_study = true
|
1147
|
+
# Then we want the page to be studied throughout this method.
|
1148
|
+
begin_study
|
1149
|
+
end
|
1150
|
+
|
909
1151
|
fields.each do |key, value|
|
910
1152
|
key_esc = escape_for_hash(key.to_s)
|
911
1153
|
value_esc = escape_for_hash(value.to_s)
|
912
1154
|
unless set_field(key_esc, value_esc, scope)
|
1155
|
+
end_study if method_study
|
913
1156
|
return failure "Failed to set field #{key_esc} to #{value_esc}"
|
914
1157
|
end
|
915
1158
|
end
|
1159
|
+
end_study if method_study
|
916
1160
|
return true
|
917
1161
|
end
|
918
1162
|
|
@@ -958,13 +1202,22 @@ module Rsel
|
|
958
1202
|
ids = {} if ids == ""
|
959
1203
|
fields = {} if fields == ""
|
960
1204
|
|
1205
|
+
method_study = false
|
1206
|
+
if @fields_study_min != 0 && @fields_study_min <= fields.length
|
1207
|
+
method_study = true
|
1208
|
+
# Then we want the page to be studied throughout this method.
|
1209
|
+
begin_study
|
1210
|
+
end
|
1211
|
+
|
961
1212
|
fields.each do |key, value|
|
962
1213
|
key_esc = escape_for_hash(key.to_s)
|
963
1214
|
value_esc = escape_for_hash(value.to_s)
|
964
1215
|
unless set_field_among(key_esc, value_esc, ids, scope)
|
1216
|
+
end_study if method_study
|
965
1217
|
return failure("Failed to set #{key_esc} (#{ids[key_esc]}) to #{value_esc}")
|
966
1218
|
end
|
967
1219
|
end
|
1220
|
+
end_study if method_study
|
968
1221
|
return true
|
969
1222
|
end
|
970
1223
|
|
@@ -1000,13 +1253,8 @@ module Rsel
|
|
1000
1253
|
def generic_field_equals(locator, value='', scope={})
|
1001
1254
|
return skip_status if skip_step?
|
1002
1255
|
fail_on_exception do
|
1003
|
-
|
1004
|
-
|
1005
|
-
loceval = loc(locator, 'field', scope)
|
1006
|
-
rescue
|
1007
|
-
loceval = loc(locator, 'link_or_button', scope)
|
1008
|
-
end
|
1009
|
-
|
1256
|
+
loceval = loc(locator, 'field', scope)
|
1257
|
+
@study.undo_last_dirty # This method does not modify the browser page contents.
|
1010
1258
|
case tagname(loceval)
|
1011
1259
|
when 'input.text', /^textarea\./
|
1012
1260
|
return field_equals(loceval, value)
|
@@ -1025,19 +1273,11 @@ module Rsel
|
|
1025
1273
|
when /^select\./
|
1026
1274
|
return dropdown_equals(loceval, value)
|
1027
1275
|
else
|
1028
|
-
#raise ArgumentError, "Unidentified field #{locator}."
|
1029
1276
|
return failure("Unidentified field for comparison: #{locator}.")
|
1030
1277
|
end
|
1031
1278
|
end
|
1032
1279
|
end
|
1033
1280
|
|
1034
|
-
|
1035
|
-
def tagname(loceval)
|
1036
|
-
return @browser.get_eval(
|
1037
|
-
'var loceval=this.browserbot.findElement("' +
|
1038
|
-
loceval + '");loceval.tagName+"."+loceval.type').downcase
|
1039
|
-
end
|
1040
|
-
|
1041
1281
|
# Check a value (with {#set_field}) in the named field, based on the given
|
1042
1282
|
# name/value pairs. Uses {#escape_for_hash} to allow certain characters in
|
1043
1283
|
# FitNesse.
|
@@ -1082,8 +1322,23 @@ module Rsel
|
|
1082
1322
|
return skip_status if skip_step?
|
1083
1323
|
# FitNesse passes in "" for an empty field. Fix it.
|
1084
1324
|
fields = {} if fields == ""
|
1325
|
+
|
1326
|
+
method_study = false
|
1327
|
+
if @fields_study_min != 0 && @fields_study_min <= fields.length
|
1328
|
+
method_study = true
|
1329
|
+
# Then we want the page to be studied throughout this method.
|
1330
|
+
begin_study
|
1331
|
+
end
|
1332
|
+
|
1085
1333
|
fields.keys.each do |field|
|
1086
|
-
|
1334
|
+
unless generic_field_equals(escape_for_hash(field.to_s), escape_for_hash(fields[field]), scope)
|
1335
|
+
end_study if method_study
|
1336
|
+
return failure
|
1337
|
+
end
|
1338
|
+
end
|
1339
|
+
if method_study
|
1340
|
+
end_study
|
1341
|
+
@study.undo_last_dirty
|
1087
1342
|
end
|
1088
1343
|
return true
|
1089
1344
|
end
|
@@ -1131,8 +1386,22 @@ module Rsel
|
|
1131
1386
|
ids = {} if ids == ""
|
1132
1387
|
fields = {} if fields == ""
|
1133
1388
|
|
1389
|
+
method_study = false
|
1390
|
+
if @fields_study_min != 0 && @fields_study_min <= fields.length
|
1391
|
+
method_study = true
|
1392
|
+
# Then we want the page to be studied throughout this method.
|
1393
|
+
begin_study
|
1394
|
+
end
|
1395
|
+
|
1134
1396
|
fields.keys.each do |field|
|
1135
|
-
|
1397
|
+
unless field_equals_among(escape_for_hash(field.to_s), escape_for_hash(fields[field]), ids, scope)
|
1398
|
+
end_study if method_study
|
1399
|
+
return failure
|
1400
|
+
end
|
1401
|
+
end
|
1402
|
+
if method_study
|
1403
|
+
end_study
|
1404
|
+
@study.undo_last_dirty
|
1136
1405
|
end
|
1137
1406
|
return true
|
1138
1407
|
end
|
@@ -1143,17 +1412,40 @@ module Rsel
|
|
1143
1412
|
# instance can respond to that method. If so, that method is called
|
1144
1413
|
# instead.
|
1145
1414
|
#
|
1415
|
+
# Prefixing a method with "check_" will verify the return string
|
1416
|
+
# against the last argument. This allows checking string values
|
1417
|
+
# within an if block.
|
1418
|
+
#
|
1146
1419
|
# @since 0.0.6
|
1147
1420
|
#
|
1148
1421
|
def method_missing(method, *args, &block)
|
1149
1422
|
return skip_status if skip_step?
|
1423
|
+
do_check = false
|
1424
|
+
if(/^check_/ === method.to_s)
|
1425
|
+
# This emulates the FitNesse | Check | prefix, without the messy string return.
|
1426
|
+
do_check = true
|
1427
|
+
method = method.to_s.sub(/^check_/,'').to_sym
|
1428
|
+
check_against = args.pop.to_s
|
1429
|
+
end
|
1430
|
+
|
1431
|
+
# Allow methods like "Type" that have Ruby homonyms to be called with a "selenium" prefix.
|
1432
|
+
method = method.to_s.sub(/^selenium_/,'').to_sym if /^selenium_/ === method.to_s
|
1433
|
+
|
1150
1434
|
if @browser.respond_to?(method)
|
1435
|
+
# Most methods should dirty the study object. These include get_eval and get_expression.
|
1436
|
+
@study.dirty unless /^(get_(el|[^e])|is_)/ === method.to_s
|
1151
1437
|
begin
|
1152
1438
|
result = @browser.send(method, *args, &block)
|
1153
1439
|
rescue
|
1154
1440
|
failure "Method #{method} error"
|
1155
1441
|
else
|
1156
|
-
# The method call succeeded
|
1442
|
+
# The method call succeeded
|
1443
|
+
# Should we check this against another string?
|
1444
|
+
if do_check
|
1445
|
+
return pass_if selenium_compare(result.to_s, check_against),
|
1446
|
+
"Expected '#{check_against}', but got '#{result.to_s}'"
|
1447
|
+
end
|
1448
|
+
# Did it return true or false?
|
1157
1449
|
return failure if result == false
|
1158
1450
|
# If a string, return that. We might Check or Show it.
|
1159
1451
|
return result if result == true || (result.is_a? String)
|
@@ -1173,7 +1465,9 @@ module Rsel
|
|
1173
1465
|
#
|
1174
1466
|
# @since 0.0.6
|
1175
1467
|
#
|
1176
|
-
def respond_to?(
|
1468
|
+
def respond_to?(orgmethod, include_private=false)
|
1469
|
+
# Allow methods like "Type" that have Ruby homonyms to be called with a "selenium" prefix.
|
1470
|
+
method = orgmethod.to_s.sub(/^(check_)?(selenium_)?/,'')
|
1177
1471
|
if @browser.respond_to?(method)
|
1178
1472
|
true
|
1179
1473
|
else
|
@@ -1198,18 +1492,14 @@ module Rsel
|
|
1198
1492
|
#
|
1199
1493
|
def if_i_see(text)
|
1200
1494
|
return false if aborted?
|
1201
|
-
|
1202
|
-
|
1203
|
-
@
|
1204
|
-
|
1495
|
+
cond = false
|
1496
|
+
begin
|
1497
|
+
cond = @browser.text?(text)
|
1498
|
+
rescue
|
1499
|
+
# Something went wrong. Therefore, I did not see the text.
|
1500
|
+
cond = false
|
1205
1501
|
end
|
1206
|
-
|
1207
|
-
# Test the condition.
|
1208
|
-
@conditional_stack.push @browser.text?(text)
|
1209
|
-
|
1210
|
-
return true if @conditional_stack.last == true
|
1211
|
-
return nil if @conditional_stack.last == false
|
1212
|
-
return failure
|
1502
|
+
return push_conditional(cond)
|
1213
1503
|
end
|
1214
1504
|
|
1215
1505
|
# If the given parameter is "yes" or "true", do the steps until I see an
|
@@ -1230,17 +1520,7 @@ module Rsel
|
|
1230
1520
|
#
|
1231
1521
|
def if_parameter(text)
|
1232
1522
|
return false if aborted?
|
1233
|
-
|
1234
|
-
@conditional_stack.push nil
|
1235
|
-
return nil
|
1236
|
-
end
|
1237
|
-
|
1238
|
-
# Test the condition.
|
1239
|
-
@conditional_stack.push string_is_true?(text)
|
1240
|
-
|
1241
|
-
return true if @conditional_stack.last == true
|
1242
|
-
return nil if @conditional_stack.last == false
|
1243
|
-
return failure
|
1523
|
+
return push_conditional(string_is_true?(text))
|
1244
1524
|
end
|
1245
1525
|
|
1246
1526
|
# If the first parameter is the same as the second, do the steps until I see an
|
@@ -1265,17 +1545,7 @@ module Rsel
|
|
1265
1545
|
#
|
1266
1546
|
def if_is(text, expected)
|
1267
1547
|
return false if aborted?
|
1268
|
-
|
1269
|
-
@conditional_stack.push nil
|
1270
|
-
return nil
|
1271
|
-
end
|
1272
|
-
|
1273
|
-
# Test the condition.
|
1274
|
-
@conditional_stack.push selenium_compare(text, expected)
|
1275
|
-
|
1276
|
-
return true if @conditional_stack.last == true
|
1277
|
-
return nil if @conditional_stack.last == false
|
1278
|
-
return failure
|
1548
|
+
return push_conditional(selenium_compare(text, expected))
|
1279
1549
|
end
|
1280
1550
|
|
1281
1551
|
# End an if block.
|
@@ -1285,11 +1555,11 @@ module Rsel
|
|
1285
1555
|
def end_if
|
1286
1556
|
return false if aborted?
|
1287
1557
|
# If there was no prior matching if, fail.
|
1288
|
-
return failure if
|
1558
|
+
return failure "End if without matching if" if !in_conditional?
|
1289
1559
|
|
1290
1560
|
last_status = @conditional_stack.pop
|
1291
1561
|
# If this end_if is within an un-executed if block, don't execute it.
|
1292
|
-
return nil if last_status
|
1562
|
+
return nil if last_status.nil?
|
1293
1563
|
return true
|
1294
1564
|
end
|
1295
1565
|
|
@@ -1307,10 +1577,10 @@ module Rsel
|
|
1307
1577
|
def otherwise
|
1308
1578
|
return false if aborted?
|
1309
1579
|
# If there was no prior matching if, fail.
|
1310
|
-
return failure if
|
1580
|
+
return failure "Otherwise without matching if" if !in_conditional?
|
1311
1581
|
|
1312
1582
|
# If this otherwise is within an un-executed if block, don't execute it.
|
1313
|
-
return nil if
|
1583
|
+
return nil if in_nil_conditional?
|
1314
1584
|
|
1315
1585
|
last_stack = @conditional_stack.pop
|
1316
1586
|
@conditional_stack.push !last_stack
|
@@ -1319,9 +1589,154 @@ module Rsel
|
|
1319
1589
|
return failure
|
1320
1590
|
end
|
1321
1591
|
|
1592
|
+
# Reset the conditional stack to its initial state. This method is included
|
1593
|
+
# mainly for unit testing purposes, and is not intended to be called by
|
1594
|
+
# normal test scripts.
|
1595
|
+
#
|
1596
|
+
# @since 0.1.2
|
1597
|
+
#
|
1598
|
+
def reset_conditionals
|
1599
|
+
@conditional_stack = [true]
|
1600
|
+
end
|
1322
1601
|
|
1323
1602
|
private
|
1324
1603
|
|
1604
|
+
# Return true if we're inside a conditional block, false otherwise.
|
1605
|
+
#
|
1606
|
+
# @since 0.1.2
|
1607
|
+
#
|
1608
|
+
def in_conditional?
|
1609
|
+
return @conditional_stack.length > 1
|
1610
|
+
end
|
1611
|
+
|
1612
|
+
# Return true if we're inside a nil conditional block (one whose evaluation
|
1613
|
+
# doesn't matter because it was precluded by an outer conditional block
|
1614
|
+
# that was false).
|
1615
|
+
#
|
1616
|
+
# @since 0.1.2
|
1617
|
+
#
|
1618
|
+
def in_nil_conditional?
|
1619
|
+
return in_conditional? && @conditional_stack.last.nil?
|
1620
|
+
end
|
1621
|
+
|
1622
|
+
# Return true if we're inside a conditional block that was skipped,
|
1623
|
+
# either because it evaluated false, or because it was inside another
|
1624
|
+
# conditional that evaluated false.
|
1625
|
+
#
|
1626
|
+
# @since 0.1.2
|
1627
|
+
#
|
1628
|
+
def in_skipped_conditional?
|
1629
|
+
return in_conditional? && !@conditional_stack.last
|
1630
|
+
end
|
1631
|
+
|
1632
|
+
# Push a conditional result onto the stack.
|
1633
|
+
#
|
1634
|
+
# @param [Boolean] result
|
1635
|
+
# The result of the conditional expression you're pushing.
|
1636
|
+
#
|
1637
|
+
# @return [Boolean, nil]
|
1638
|
+
# true if the result is true, false otherwise.
|
1639
|
+
#
|
1640
|
+
# @since 0.1.2
|
1641
|
+
#
|
1642
|
+
def push_conditional(result)
|
1643
|
+
# If this if is inside a block that's not running, record that.
|
1644
|
+
if in_skipped_conditional?
|
1645
|
+
@conditional_stack.push nil
|
1646
|
+
return nil
|
1647
|
+
end
|
1648
|
+
|
1649
|
+
# Test the condition.
|
1650
|
+
@conditional_stack.push result
|
1651
|
+
|
1652
|
+
return true if @conditional_stack.last == true
|
1653
|
+
return nil if @conditional_stack.last == false
|
1654
|
+
return failure
|
1655
|
+
end
|
1656
|
+
|
1657
|
+
# Overrides the support.rb loc() method, allowing searching a studied page
|
1658
|
+
# to try simplify a CSS or XPath expression to an id= or name= expression.
|
1659
|
+
#
|
1660
|
+
# @param [Boolean] try_study
|
1661
|
+
# Try to use the studied page to simplify the path? Defaults to true.
|
1662
|
+
def loc(locator, kind='', scope={}, try_study=true)
|
1663
|
+
locator = super(locator, kind, scope)
|
1664
|
+
return locator unless try_study
|
1665
|
+
@study.study(page_to_study) if(@fields_study_min == 1 && !@study.clean? && locator[0,6] == 'xpath=' && locator.length >= @xpath_study_length_min)
|
1666
|
+
begin
|
1667
|
+
retval = @study.simplify_locator(locator)
|
1668
|
+
rescue
|
1669
|
+
retval = locator
|
1670
|
+
end
|
1671
|
+
@study.dirty
|
1672
|
+
return retval
|
1673
|
+
end
|
1674
|
+
|
1675
|
+
# Returns the HTML of the current browser page, for studying.
|
1676
|
+
# Just to keep this consistent.
|
1677
|
+
def page_to_study
|
1678
|
+
#puts "Page to study: <html>#{@browser.get_eval('window.document.getElementsByTagName(\'html\')[0].innerHTML')}</html>"
|
1679
|
+
return "<html>#{@browser.get_eval('window.document.getElementsByTagName(\'html\')[0].innerHTML')}</html>"
|
1680
|
+
end
|
1681
|
+
|
1682
|
+
# Parse the argument given into a value for @fields_study_min
|
1683
|
+
# Returns the value, rather than setting @fields_study_min.
|
1684
|
+
def parse_fields_study_min(s, default)
|
1685
|
+
begin
|
1686
|
+
s = s.downcase.strip
|
1687
|
+
rescue
|
1688
|
+
end
|
1689
|
+
|
1690
|
+
case s
|
1691
|
+
when 'never'
|
1692
|
+
return 0
|
1693
|
+
when 'always'
|
1694
|
+
return 1
|
1695
|
+
when /^auto/
|
1696
|
+
if @browser.browser_string == '*iexplore'
|
1697
|
+
@default_fields_study_min = 1
|
1698
|
+
else
|
1699
|
+
@default_fields_study_min = 10
|
1700
|
+
end
|
1701
|
+
else
|
1702
|
+
begin
|
1703
|
+
return Integer(s.gsub(/[^0-9]/,''))
|
1704
|
+
rescue
|
1705
|
+
# Default case:
|
1706
|
+
return default
|
1707
|
+
end
|
1708
|
+
end
|
1709
|
+
end
|
1710
|
+
|
1711
|
+
# Use Javascript to determine the type of field referenced by loceval.
|
1712
|
+
# Also turns loceval into an id= locator if possible.
|
1713
|
+
#
|
1714
|
+
# @param [String] loceval
|
1715
|
+
# Selenium-style locator
|
1716
|
+
# Warning: This parameter is effectively passed by reference!
|
1717
|
+
# We attempt to replace an xpath locator with an id=.
|
1718
|
+
#
|
1719
|
+
# @since 0.1.1
|
1720
|
+
#
|
1721
|
+
def tagname(loceval)
|
1722
|
+
tname = nil
|
1723
|
+
if @study.clean?
|
1724
|
+
tname = @study.get_node(loceval)
|
1725
|
+
# If we've studied, loceval should have already been converted to an id, or name, etc. So trying to change it again would be pointless.
|
1726
|
+
return "#{tname.node_name}.#{tname['type']}" unless tname == nil
|
1727
|
+
# If get_studied_node failed, try the old-fashioned way.
|
1728
|
+
#puts "Studying tagname #{loceval} failed."
|
1729
|
+
end
|
1730
|
+
tname = @browser.get_eval(
|
1731
|
+
'var ev=this.browserbot.findElement("' +
|
1732
|
+
loceval + '");ev.tagName+"."+ev.type+";"+((ev.id != "" && window.document.getElementById(ev.id)==ev)?ev.id:"")').downcase
|
1733
|
+
|
1734
|
+
tname = tname.split(';',2)
|
1735
|
+
# This modifies loceval in-place.
|
1736
|
+
loceval[0,loceval.length] = "id=#{tname[1]}" if tname[1] != ''
|
1737
|
+
return tname[0]
|
1738
|
+
end
|
1739
|
+
|
1325
1740
|
# Execute the given block, and return false if it raises an exception.
|
1326
1741
|
# Otherwise, return true.
|
1327
1742
|
#
|
@@ -1336,7 +1751,7 @@ module Rsel
|
|
1336
1751
|
rescue => e
|
1337
1752
|
#puts e.message
|
1338
1753
|
#puts e.backtrace
|
1339
|
-
failure("#{e.message}")
|
1754
|
+
failure("#{e.message}:<br>#{e.backtrace.join("\n")}")
|
1340
1755
|
else
|
1341
1756
|
return true
|
1342
1757
|
end
|
@@ -1394,7 +1809,7 @@ module Rsel
|
|
1394
1809
|
# @since 0.1.1
|
1395
1810
|
#
|
1396
1811
|
def skip_step?
|
1397
|
-
return aborted? ||
|
1812
|
+
return aborted? || in_skipped_conditional?
|
1398
1813
|
end
|
1399
1814
|
|
1400
1815
|
# Presuming the current step should be skipped, what status should I return?
|
@@ -1403,7 +1818,7 @@ module Rsel
|
|
1403
1818
|
#
|
1404
1819
|
def skip_status
|
1405
1820
|
return false if aborted?
|
1406
|
-
return nil if
|
1821
|
+
return nil if in_skipped_conditional?
|
1407
1822
|
end
|
1408
1823
|
|
1409
1824
|
|