rsel 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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