capybara 2.14.0 → 2.14.1

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +12 -0
  3. data/README.md +1 -1
  4. data/lib/capybara.rb +16 -15
  5. data/lib/capybara/minitest.rb +92 -113
  6. data/lib/capybara/minitest/spec.rb +12 -37
  7. data/lib/capybara/node/matchers.rb +6 -5
  8. data/lib/capybara/queries/base_query.rb +4 -0
  9. data/lib/capybara/queries/current_path_query.rb +1 -0
  10. data/lib/capybara/queries/selector_query.rb +39 -12
  11. data/lib/capybara/queries/text_query.rb +1 -1
  12. data/lib/capybara/queries/title_query.rb +1 -0
  13. data/lib/capybara/rack_test/driver.rb +1 -0
  14. data/lib/capybara/rspec/matcher_proxies.rb +10 -6
  15. data/lib/capybara/rspec/matchers.rb +29 -0
  16. data/lib/capybara/selector.rb +61 -50
  17. data/lib/capybara/selector/expression_filter.rb +40 -0
  18. data/lib/capybara/selector/filter_set.rb +22 -3
  19. data/lib/capybara/selector/selector.rb +33 -12
  20. data/lib/capybara/selenium/driver.rb +130 -25
  21. data/lib/capybara/selenium/node.rb +3 -3
  22. data/lib/capybara/session/config.rb +29 -23
  23. data/lib/capybara/session/matchers.rb +3 -0
  24. data/lib/capybara/spec/session/accept_alert_spec.rb +1 -1
  25. data/lib/capybara/spec/session/all_spec.rb +2 -1
  26. data/lib/capybara/spec/session/assert_selector.rb +1 -1
  27. data/lib/capybara/spec/session/assert_title.rb +22 -9
  28. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +3 -3
  29. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +1 -1
  30. data/lib/capybara/spec/session/find_spec.rb +3 -2
  31. data/lib/capybara/spec/session/first_spec.rb +10 -5
  32. data/lib/capybara/spec/session/has_css_spec.rb +11 -0
  33. data/lib/capybara/spec/session/has_current_path_spec.rb +5 -3
  34. data/lib/capybara/spec/session/has_select_spec.rb +62 -4
  35. data/lib/capybara/spec/session/has_text_spec.rb +5 -3
  36. data/lib/capybara/spec/session/has_title_spec.rb +4 -2
  37. data/lib/capybara/spec/session/has_xpath_spec.rb +5 -3
  38. data/lib/capybara/spec/session/node_spec.rb +8 -4
  39. data/lib/capybara/spec/session/window/become_closed_spec.rb +4 -4
  40. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +4 -4
  41. data/lib/capybara/spec/spec_helper.rb +1 -1
  42. data/lib/capybara/spec/views/form.erb +22 -1
  43. data/lib/capybara/spec/views/with_html.erb +1 -1
  44. data/lib/capybara/version.rb +1 -1
  45. data/spec/capybara_spec.rb +16 -0
  46. data/spec/filter_set_spec.rb +28 -0
  47. data/spec/minitest_spec_spec.rb +4 -4
  48. data/spec/per_session_config_spec.rb +4 -4
  49. data/spec/result_spec.rb +20 -0
  50. data/spec/selector_spec.rb +2 -1
  51. data/spec/selenium_spec_chrome.rb +12 -1
  52. data/spec/selenium_spec_firefox.rb +2 -1
  53. data/spec/selenium_spec_marionette.rb +4 -3
  54. data/spec/shared_selenium_session.rb +14 -7
  55. metadata +18 -2
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+ require 'capybara/selector/filter'
3
+
4
+ module Capybara
5
+ class Selector
6
+ class ExpressionFilter < Filter
7
+ undef_method :matches?
8
+
9
+ def apply_filter(expr, value)
10
+ return expr if skip?(value)
11
+
12
+ if !valid_value?(value)
13
+ msg = "Invalid value #{value.inspect} passed to expression filter #{@name} - "
14
+ if default?
15
+ warn msg + "defaulting to #{default}"
16
+ value = default
17
+ else
18
+ warn msg + "skipping"
19
+ return expr
20
+ end
21
+ end
22
+
23
+ @block.call(expr, value)
24
+ end
25
+ end
26
+
27
+ class IdentityExpressionFilter < ExpressionFilter
28
+ def initialize
29
+ end
30
+
31
+ def default?
32
+ false
33
+ end
34
+
35
+ def apply_filter(expr, _value)
36
+ return expr
37
+ end
38
+ end
39
+ end
40
+ end
@@ -13,9 +13,11 @@ module Capybara
13
13
  end
14
14
 
15
15
  def filter(name, *types_and_options, &block)
16
- options = types_and_options.last.is_a?(Hash) ? types_and_options.pop.dup : {}
17
- types_and_options.each { |k| options[k] = true}
18
- filters[name] = Filter.new(name, block, options)
16
+ add_filter(name, Filter, *types_and_options, &block)
17
+ end
18
+
19
+ def expression_filter(name, *types_and_options, &block)
20
+ add_filter(name, ExpressionFilter, *types_and_options, &block)
19
21
  end
20
22
 
21
23
  def describe(&block)
@@ -30,7 +32,16 @@ module Capybara
30
32
  @filters ||= {}
31
33
  end
32
34
 
35
+ def node_filters
36
+ filters.reject { |_n, f| f.nil? || f.is_a?(ExpressionFilter) }.freeze
37
+ end
38
+
39
+ def expression_filters
40
+ filters.select { |_n, f| f.nil? || f.is_a?(ExpressionFilter) }.freeze
41
+ end
42
+
33
43
  class << self
44
+
34
45
  def all
35
46
  @filter_sets ||= {}
36
47
  end
@@ -43,6 +54,14 @@ module Capybara
43
54
  all.delete(name.to_sym)
44
55
  end
45
56
  end
57
+
58
+ private
59
+
60
+ def add_filter(name, filter_class, *types_and_options, &block)
61
+ options = types_and_options.last.is_a?(Hash) ? types_and_options.pop.dup : {}
62
+ types_and_options.each { |k| options[k] = true}
63
+ filters[name] = filter_class.new(name, block, options)
64
+ end
46
65
  end
47
66
  end
48
67
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require 'capybara/selector/expression_filter'
2
3
  require 'capybara/selector/filter_set'
3
4
  require 'capybara/selector/css'
4
5
  require 'xpath'
@@ -21,7 +22,7 @@ end
21
22
  module Capybara
22
23
  class Selector
23
24
 
24
- attr_reader :name, :format, :expression_filters
25
+ attr_reader :name, :format
25
26
 
26
27
  class << self
27
28
  def all
@@ -50,7 +51,7 @@ module Capybara
50
51
  @description = nil
51
52
  @format = nil
52
53
  @expression = nil
53
- @expression_filters = []
54
+ @expression_filters = {}
54
55
  @default_visibility = nil
55
56
  instance_eval(&block)
56
57
  end
@@ -59,6 +60,14 @@ module Capybara
59
60
  @filter_set.filters
60
61
  end
61
62
 
63
+ def node_filters
64
+ @filter_set.node_filters
65
+ end
66
+
67
+ def expression_filters
68
+ @filter_set.expression_filters
69
+ end
70
+
62
71
  ##
63
72
  #
64
73
  # Define a selector by an xpath expression
@@ -74,7 +83,10 @@ module Capybara
74
83
  # @return [#call] The block that will be called to generate the XPath expression
75
84
  #
76
85
  def xpath(*expression_filters, &block)
77
- @format, @expression_filters, @expression = :xpath, expression_filters.flatten, block if block
86
+ if block
87
+ @format, @expression = :xpath, block
88
+ expression_filters.flatten.each { |ef| custom_filters[ef] = IdentityExpressionFilter.new }
89
+ end
78
90
  format == :xpath ? @expression : nil
79
91
  end
80
92
 
@@ -93,7 +105,10 @@ module Capybara
93
105
  # @return [#call] The block that will be called to generate the CSS selector
94
106
  #
95
107
  def css(*expression_filters, &block)
96
- @format, @expression_filters, @expression = :css, expression_filters.flatten, block if block
108
+ if block
109
+ @format, @expression = :css, block
110
+ expression_filters.flatten.each { |ef| custom_filters[ef] = nil }
111
+ end
97
112
  format == :css ? @expression : nil
98
113
  end
99
114
 
@@ -172,10 +187,16 @@ module Capybara
172
187
  #
173
188
  def filter(name, *types_and_options, &block)
174
189
  options = types_and_options.last.is_a?(Hash) ? types_and_options.pop.dup : {}
175
- types_and_options.each { |k| options[k] = true}
190
+ types_and_options.each { |k| options[k] = true }
176
191
  custom_filters[name] = Filter.new(name, block, options)
177
192
  end
178
193
 
194
+ def expression_filter(name, *types_and_options, &block)
195
+ options = types_and_options.last.is_a?(Hash) ? types_and_options.pop.dup : {}
196
+ types_and_options.each { |k| options[k] = true }
197
+ custom_filters[name] = ExpressionFilter.new(name, block, options)
198
+ end
199
+
179
200
  def filter_set(name, filters_to_use = nil)
180
201
  f_set = FilterSet.all[name]
181
202
  f_set.filters.each do |n, filter|
@@ -215,17 +236,17 @@ module Capybara
215
236
  locate_xpath = xpath #need to save original xpath for the label wrap
216
237
  if locator
217
238
  locator = locator.to_s
218
- attr_matchers = XPath.attr(:id).equals(locator) |
219
- XPath.attr(:name).equals(locator) |
220
- XPath.attr(:placeholder).equals(locator) |
221
- XPath.attr(:id).equals(XPath.anywhere(:label)[XPath.string.n.is(locator)].attr(:for))
222
- attr_matchers |= XPath.attr(:'aria-label').is(locator) if options[:enable_aria_label]
239
+ attr_matchers = XPath.attr(:id).equals(locator).or(
240
+ XPath.attr(:name).equals(locator)).or(
241
+ XPath.attr(:placeholder).equals(locator)).or(
242
+ XPath.attr(:id).equals(XPath.anywhere(:label)[XPath.string.n.is(locator)].attr(:for)))
243
+ attr_matchers = attr_matchers.or XPath.attr(:'aria-label').is(locator) if options[:enable_aria_label]
223
244
 
224
245
  locate_xpath = locate_xpath[attr_matchers]
225
- locate_xpath += XPath.descendant(:label)[XPath.string.n.is(locator)].descendant(xpath)
246
+ locate_xpath = locate_xpath.union(XPath.descendant(:label)[XPath.string.n.is(locator)].descendant(xpath))
226
247
  end
227
248
 
228
- locate_xpath = [:name, :placeholder].inject(locate_xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
249
+ # locate_xpath = [:name, :placeholder].inject(locate_xpath) { |memo, ef| memo[find_by_attr(ef, options[ef])] }
229
250
  locate_xpath
230
251
  end
231
252
 
@@ -14,12 +14,16 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
14
14
 
15
15
  def browser
16
16
  unless @browser
17
- if options[:browser].to_s == "firefox"
18
- options[:desired_capabilities] ||= Selenium::WebDriver::Remote::Capabilities.firefox
17
+ if firefox?
18
+ options[:desired_capabilities] ||= {}
19
19
  options[:desired_capabilities].merge!({ unexpectedAlertBehaviour: "ignore" })
20
20
  end
21
21
 
22
- @browser = Selenium::WebDriver.for(options[:browser], options.reject { |key,_val| SPECIAL_OPTIONS.include?(key) })
22
+ @processed_options = options.reject { |key,_val| SPECIAL_OPTIONS.include?(key) }
23
+ @browser = Selenium::WebDriver.for(options[:browser], @processed_options)
24
+
25
+ @w3c = ((defined?(Selenium::WebDriver::Remote::W3CCapabilities) && @browser.capabilities.is_a?(Selenium::WebDriver::Remote::W3CCapabilities)) ||
26
+ (defined?(Selenium::WebDriver::Remote::W3C::Capabilities) && @browser.capabilities.is_a?(Selenium::WebDriver::Remote::W3C::Capabilities)))
23
27
 
24
28
  main = Process.pid
25
29
  at_exit do
@@ -33,6 +37,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
33
37
  end
34
38
 
35
39
  def initialize(app, options={})
40
+ @session = nil
36
41
  begin
37
42
  require 'selenium-webdriver'
38
43
  # Fix for selenium-webdriver 3.4.0 which misnamed these
@@ -145,7 +150,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
145
150
  raise Capybara::ExpectationNotMet.new('Timed out waiting for Selenium session reset') if (Capybara::Helpers.monotonic_time - start_time) >= 10
146
151
  sleep 0.05
147
152
  end
148
- rescue Selenium::WebDriver::Error::UnhandledAlertError
153
+ rescue Selenium::WebDriver::Error::UnhandledAlertError, Selenium::WebDriver::Error::UnexpectedAlertOpenError
149
154
  # This error is thrown if an unhandled alert is on the page
150
155
  # Firefox appears to automatically dismiss this alert, chrome does not
151
156
  # We'll try to accept it
@@ -232,20 +237,32 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
232
237
  end
233
238
 
234
239
  def accept_modal(_type, options={})
235
- yield if block_given?
236
- modal = find_modal(options)
237
- modal.send_keys options[:with] if options[:with]
238
- message = modal.text
239
- modal.accept
240
- message
240
+ if headless_chrome?
241
+ insert_modal_handlers(true, options[:with], options[:text])
242
+ yield if block_given?
243
+ find_headless_modal(options)
244
+ else
245
+ yield if block_given?
246
+ modal = find_modal(options)
247
+ modal.send_keys options[:with] if options[:with]
248
+ message = modal.text
249
+ modal.accept
250
+ message
251
+ end
241
252
  end
242
253
 
243
254
  def dismiss_modal(_type, options={})
244
- yield if block_given?
245
- modal = find_modal(options)
246
- message = modal.text
247
- modal.dismiss
248
- message
255
+ if headless_chrome?
256
+ insert_modal_handlers(false, options[:with], options[:text])
257
+ yield if block_given?
258
+ find_headless_modal(options)
259
+ else
260
+ yield if block_given?
261
+ modal = find_modal(options)
262
+ message = modal.text
263
+ modal.dismiss
264
+ message
265
+ end
249
266
  end
250
267
 
251
268
  def quit
@@ -275,6 +292,37 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
275
292
  end
276
293
 
277
294
  # @api private
295
+ def marionette?
296
+ firefox? && browser && @w3c
297
+ end
298
+
299
+ # @api private
300
+ def firefox?
301
+ browser_name == "firefox"
302
+ end
303
+
304
+ # @api private
305
+ def chrome?
306
+ browser_name == "chrome"
307
+ end
308
+
309
+ # @api private
310
+ def headless_chrome?
311
+ chrome? && ((@processed_options[:desired_capabilities][:chrome_options] || {})['args'] || []).include?("headless")
312
+ end
313
+
314
+ # @deprecated This method is being removed
315
+ def browser_initialized?
316
+ super && !@browser.nil?
317
+ end
318
+
319
+ private
320
+
321
+ # @api private
322
+ def browser_name
323
+ options[:browser].to_s
324
+ end
325
+
278
326
  def find_window(locator)
279
327
  handles = browser.window_handles
280
328
  return locator if handles.include? locator
@@ -292,18 +340,49 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
292
340
  raise Capybara::ElementNotFound, "Could not find a window identified by #{locator}"
293
341
  end
294
342
 
295
- #@api private
296
- def marionette?
297
- (options[:browser].to_s == "firefox") && browser.capabilities.is_a?(Selenium::WebDriver::Remote::W3CCapabilities)
343
+ def insert_modal_handlers(accept, response_text, expected_text=nil)
344
+ script = <<-JS
345
+ if (typeof window.capybara === 'undefined') {
346
+ window.capybara = {
347
+ modal_handlers: [],
348
+ current_modal_status: function() {
349
+ return [this.modal_handlers[0].called, this.modal_handlers[0].modal_text];
350
+ },
351
+ add_handler: function(handler) {
352
+ this.modal_handlers.unshift(handler);
353
+ },
354
+ remove_handler: function(handler) {
355
+ window.alert = handler.alert;
356
+ window.confirm = handler.confirm;
357
+ window.prompt = handler.prompt;
358
+ },
359
+ handler_called: function(handler, str) {
360
+ handler.called = true;
361
+ handler.modal_text = str;
362
+ this.remove_handler(handler);
363
+ }
364
+ };
365
+ };
366
+
367
+ var modal_handler = {
368
+ prompt: window.prompt,
369
+ confirm: window.confirm,
370
+ alert: window.alert,
371
+ }
372
+ window.capybara.add_handler(modal_handler);
373
+
374
+ window.alert = window.confirm = function(str) {
375
+ window.capybara.handler_called(modal_handler, str);
376
+ return #{accept ? 'true' : 'false'};
377
+ };
378
+ window.prompt = function(str) {
379
+ window.capybara.handler_called(modal_handler, str);
380
+ return #{accept ? "'#{response_text}'" : 'null'};
381
+ }
382
+ JS
383
+ execute_script script
298
384
  end
299
385
 
300
- # @deprecated This method is being removed
301
- def browser_initialized?
302
- super && !@browser.nil?
303
- end
304
-
305
- private
306
-
307
386
  def within_given_window(handle)
308
387
  original_handle = self.current_window_handle
309
388
  if handle == original_handle
@@ -333,6 +412,32 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
333
412
  end
334
413
  end
335
414
 
415
+ def find_headless_modal(options={})
416
+ # Selenium has its own built in wait (2 seconds)for a modal to show up, so this wait is really the minimum time
417
+ # Actual wait time may be longer than specified
418
+ wait = Selenium::WebDriver::Wait.new(
419
+ timeout: options.fetch(:wait, session_options.default_max_wait_time) || 0 ,
420
+ ignore: Selenium::WebDriver::Error::NoAlertPresentError)
421
+ begin
422
+ wait.until do
423
+ called, alert_text = evaluate_script('window.capybara.current_modal_status()')
424
+ if called
425
+ execute_script('window.capybara.modal_handlers.shift()')
426
+ regexp = options[:text].is_a?(Regexp) ? options[:text] : Regexp.escape(options[:text].to_s)
427
+ if alert_text.match(regexp)
428
+ alert_text
429
+ else
430
+ raise Capybara::ModalNotFound.new("Unable to find modal dialog#{" with #{options[:text]}" if options[:text]}")
431
+ end
432
+ else
433
+ nil
434
+ end
435
+ end
436
+ rescue Selenium::WebDriver::Error::TimeOutError
437
+ raise Capybara::ModalNotFound.new("Unable to find modal dialog#{" with #{options[:text]}" if options[:text]}")
438
+ end
439
+ end
440
+
336
441
  def silenced_unknown_error_message?(msg)
337
442
  silenced_unknown_error_messages.any? { |r| msg =~ r }
338
443
  end
@@ -47,7 +47,7 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
47
47
  click if value ^ native.attribute('checked').to_s.eql?("true")
48
48
  elsif tag_name == 'input' and type == 'file'
49
49
  path_names = value.to_s.empty? ? [] : value
50
- if driver.options[:browser].to_s == "chrome"
50
+ if driver.chrome?
51
51
  native.send_keys(Array(path_names).join("\n"))
52
52
  else
53
53
  native.send_keys(*path_names)
@@ -88,8 +88,8 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
88
88
  JS
89
89
  driver.execute_script script, self
90
90
 
91
- if (driver.options[:browser].to_s == "chrome") ||
92
- (driver.options[:browser].to_s == "firefox" && !driver.marionette?)
91
+ if (driver.chrome?) ||
92
+ (driver.firefox? && !driver.marionette?)
93
93
  # chromedriver raises a can't focus element for child elements if we use native.send_keys
94
94
  # we've already focused it so just use action api
95
95
  driver.browser.action.send_keys(value.to_s).perform
@@ -8,53 +8,55 @@ module Capybara
8
8
  :automatic_label_click, :enable_aria_label, :save_path, :exact_options, :asset_host, :default_host, :app_host,
9
9
  :save_and_open_page_path, :server_host, :server_port, :server_errors]
10
10
 
11
- attr_accessor *OPTIONS
11
+ attr_accessor(*OPTIONS)
12
12
 
13
13
  ##
14
14
  #@!method always_include_port
15
- # See {Capybara#configure}
15
+ # See {Capybara.configure}
16
16
  #@!method run_server
17
- # See {Capybara#configure}
17
+ # See {Capybara.configure}
18
18
  #@!method default_selector
19
- # See {Capybara#configure}
19
+ # See {Capybara.configure}
20
20
  #@!method default_max_wait_time
21
- # See {Capybara#configure}
21
+ # See {Capybara.configure}
22
22
  #@!method ignore_hidden_elements
23
- # See {Capybara#configure}
23
+ # See {Capybara.configure}
24
24
  #@!method automatic_reload
25
- # See {Capybara#configure}
25
+ # See {Capybara.configure}
26
26
  #@!method match
27
- # See {Capybara#configure}
27
+ # See {Capybara.configure}
28
28
  #@!method exact
29
- # See {Capybara#configure}
29
+ # See {Capybara.configure}
30
30
  #@!method raise_server_errors
31
- # See {Capybara#configure}
31
+ # See {Capybara.configure}
32
32
  #@!method visible_text_only
33
- # See {Capybara#configure}
33
+ # See {Capybara.configure}
34
34
  #@!method wait_on_first_by_default
35
- # See {Capybara#configure}
35
+ # See {Capybara.configure}
36
36
  #@!method automatic_label_click
37
- # See {Capybara#configure}
37
+ # See {Capybara.configure}
38
38
  #@!method enable_aria_label
39
- # See {Capybara#configure}
39
+ # See {Capybara.configure}
40
40
  #@!method save_path
41
- # See {Capybara#configure}
41
+ # See {Capybara.configure}
42
42
  #@!method exact_options
43
- # See {Capybara#configure}
43
+ # See {Capybara.configure}
44
44
  #@!method asset_host
45
- # See {Capybara#configure}
45
+ # See {Capybara.configure}
46
46
  #@!method default_host
47
- # See {Capybara#configure}
47
+ # See {Capybara.configure}
48
48
  #@!method app_host
49
- # See {Capybara#configure}
49
+ # See {Capybara.configure}
50
50
  #@!method save_and_open_page_path
51
- # See {Capybara#configure}
51
+ # See {Capybara.configure}
52
52
  #@!method server_host
53
- # See {Capybara#configure}
53
+ # See {Capybara.configure}
54
54
  #@!method server_port
55
- # See {Capybara#configure}
55
+ # See {Capybara.configure}
56
56
  #@!method server_errors
57
- # See {Capybara#configure}
57
+ # See {Capybara.configure}
58
+
59
+ remove_method :server_host
58
60
 
59
61
  ##
60
62
  #
@@ -64,20 +66,24 @@ module Capybara
64
66
  @server_host || '127.0.0.1'
65
67
  end
66
68
 
69
+ remove_method :server_errors=
67
70
  def server_errors=(errors)
68
71
  (@server_errors ||= []).replace(errors.dup)
69
72
  end
70
73
 
74
+ remove_method :app_host=
71
75
  def app_host=(url)
72
76
  raise ArgumentError.new("Capybara.app_host should be set to a url (http://www.example.com)") unless url.nil? || (url =~ URI::Parser.new.make_regexp)
73
77
  @app_host = url
74
78
  end
75
79
 
80
+ remove_method :default_host=
76
81
  def default_host=(url)
77
82
  raise ArgumentError.new("Capybara.default_host should be set to a url (http://www.example.com)") unless url.nil? || (url =~ URI::Parser.new.make_regexp)
78
83
  @default_host = url
79
84
  end
80
85
 
86
+ remove_method :save_and_open_page_path=
81
87
  def save_and_open_page_path=(path)
82
88
  warn "DEPRECATED: #save_and_open_page_path is deprecated, please use #save_path instead. \n"\
83
89
  "Note: Behavior is slightly different with relative paths - see documentation" unless path.nil?