rsel 0.1.1 → 0.1.2
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/.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
|
|