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.
Files changed (48) hide show
  1. data/.gitignore +1 -0
  2. data/Rakefile +22 -8
  3. data/docs/development.md +1 -0
  4. data/docs/history.md +11 -1
  5. data/docs/index.md +1 -0
  6. data/docs/scoping.md +1 -1
  7. data/docs/studying.md +76 -0
  8. data/lib/rsel/selenium_test.rb +505 -90
  9. data/lib/rsel/study_html.rb +198 -0
  10. data/lib/rsel/support.rb +105 -4
  11. data/rsel.gemspec +1 -1
  12. data/spec/spec_helper.rb +4 -22
  13. data/spec/st_alerts.rb +48 -0
  14. data/spec/st_browser_spec.rb +58 -0
  15. data/spec/st_buttons_spec.rb +95 -0
  16. data/spec/st_checkboxes_spec.rb +235 -0
  17. data/spec/st_conditionals_spec.rb +180 -0
  18. data/spec/st_dropdowns_spec.rb +140 -0
  19. data/spec/st_field_equals_among_spec.rb +48 -0
  20. data/spec/st_fields_equal_among_spec.rb +74 -0
  21. data/spec/st_fields_equal_spec.rb +90 -0
  22. data/spec/st_fields_spec.rb +167 -0
  23. data/spec/st_initialization_spec.rb +33 -0
  24. data/spec/st_links_spec.rb +84 -0
  25. data/spec/st_method_missing_spec.rb +59 -0
  26. data/spec/st_navigation_spec.rb +56 -0
  27. data/spec/st_radiobuttons_spec.rb +123 -0
  28. data/spec/st_respond_to_spec.rb +16 -0
  29. data/spec/st_scenario_spec.rb +26 -0
  30. data/spec/st_set_field_among_spec.rb +45 -0
  31. data/spec/st_set_field_spec.rb +842 -0
  32. data/spec/st_set_fields_among_spec.rb +74 -0
  33. data/spec/st_set_fields_spec.rb +97 -0
  34. data/spec/st_spec_helper.rb +43 -0
  35. data/spec/st_stop_on_failure_spec.rb +199 -0
  36. data/spec/st_tables_spec.rb +42 -0
  37. data/spec/st_temporal_visibility_spec.rb +122 -0
  38. data/spec/st_visibility_spec.rb +125 -0
  39. data/spec/st_waiting_spec.rb +37 -0
  40. data/spec/study_html_spec.rb +310 -0
  41. data/spec/support_spec.rb +163 -13
  42. data/test/server/README.txt +3 -0
  43. data/test/views/alert.erb +15 -0
  44. data/test/views/form.erb +6 -1
  45. data/test/views/index.erb +2 -0
  46. data/test/views/slowtext.erb +1 -1
  47. metadata +38 -9
  48. data/spec/selenium_test_spec.rb +0 -2656
data/.gitignore CHANGED
@@ -6,3 +6,4 @@ pkg/*
6
6
  *~
7
7
  coverage/*
8
8
  doc/*
9
+ test/server/*.jar
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
- #SELENIUM_RC_JAR = File.join(ROOT_DIR, 'test', 'server', 'selenium-server-1.0.3-SNAPSHOT-standalone.jar')
9
- SELENIUM_RC_JAR = File.join(ROOT_DIR, 'test', 'server', 'selenium-server-standalone-2.4.0.jar')
10
- SELENIUM_RC_LOG = File.join(ROOT_DIR, 'selenium-rc.log')
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 = SELENIUM_RC_JAR
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 = SELENIUM_RC_LOG
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 support spec tests with coverage analysis"
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
@@ -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
@@ -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 (see|do_not_see)_within_seconds
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)
@@ -10,6 +10,7 @@ Rsel is a [Selenium](http://seleniumhq.org) wrapper for
10
10
  - [FitNesse](fitnesse.md)
11
11
  - [Locators](locators.md)
12
12
  - [Scoping](scoping.md)
13
+ - [Studying](studying.md)
13
14
  - [Examples](examples.md)
14
15
  - [Customization](custom.md)
15
16
  - [Scenarios](scenarios.md)
@@ -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: [Examples](examples.md)
144
+ Next: [Studying](studying.md)
145
145
 
@@ -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)
@@ -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('<','&lt;')
@@ -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
- pass_if @browser.text?(text)
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
- pass_if !@browser.text?(text)
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
- pass_if !(Integer(seconds)+1).times{ break if (@browser.text?(text) rescue false); sleep 1 }
273
- # This would be better if it worked:
274
- # pass_if @browser.wait_for(:text => text, :timeout_in_seconds => seconds);
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
- pass_if !(Integer(seconds)+1).times{ break if (!@browser.text?(text) rescue false); sleep 1 }
294
- # This would be better if it worked:
295
- # pass_if @browser.wait_for(:no_text => text, :timeout_in_seconds => seconds);
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
- pass_if @browser.get_title == title, "Page title is '#{@browser.get_title}', not '#{title}'"
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
- pass_if @browser.element?(loc(locator, 'link', scope))
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
- pass_if @browser.element?(loc(locator, 'button', scope))
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
- pass_if @browser.element?("xpath=#{xpath_row_containing(cells.split(/, */).map{|s| escape_for_hash(s)})}")
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
- pass_if @browser.element?("xpath=#{opt_str}")
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
- # First, use Javascript to find out what the field is.
1067
+ loceval = loc(locator, 'field', scope)
1068
+ mytagname = ''
836
1069
  begin
837
- loceval = loc(locator, 'field', scope)
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
- case tagname(loceval)
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
- # First, use Javascript to find out what the field is.
1004
- begin
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
- return failure unless generic_field_equals(escape_for_hash(field.to_s), escape_for_hash(fields[field]), scope)
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
- return failure unless field_equals_among(escape_for_hash(field.to_s), escape_for_hash(fields[field]), ids, scope)
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; did it return true or false?
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?(method, include_private=false)
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
- # If this if is inside a block that's not running, record that.
1202
- if !@conditional_stack.last
1203
- @conditional_stack.push nil
1204
- return nil
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
- if !@conditional_stack.last
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
- if !@conditional_stack.last
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 @conditional_stack.length <= 1
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 == nil
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 @conditional_stack.length <= 1
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 @conditional_stack.last == nil
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? || !@conditional_stack.last
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 !@conditional_stack.last
1821
+ return nil if in_skipped_conditional?
1407
1822
  end
1408
1823
 
1409
1824