capybara 2.13.0 → 2.18.0

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 (122) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +218 -18
  3. data/README.md +54 -23
  4. data/lib/capybara/config.rb +132 -0
  5. data/lib/capybara/cucumber.rb +1 -0
  6. data/lib/capybara/driver/base.rb +14 -0
  7. data/lib/capybara/dsl.rb +1 -3
  8. data/lib/capybara/helpers.rb +3 -3
  9. data/lib/capybara/minitest/spec.rb +14 -37
  10. data/lib/capybara/minitest.rb +95 -114
  11. data/lib/capybara/node/actions.rb +10 -10
  12. data/lib/capybara/node/base.rb +7 -2
  13. data/lib/capybara/node/element.rb +9 -3
  14. data/lib/capybara/node/finders.rb +92 -18
  15. data/lib/capybara/node/matchers.rb +21 -9
  16. data/lib/capybara/node/simple.rb +5 -0
  17. data/lib/capybara/queries/ancestor_query.rb +25 -0
  18. data/lib/capybara/queries/base_query.rb +12 -3
  19. data/lib/capybara/queries/current_path_query.rb +13 -9
  20. data/lib/capybara/queries/selector_query.rb +62 -23
  21. data/lib/capybara/queries/sibling_query.rb +25 -0
  22. data/lib/capybara/queries/text_query.rb +10 -5
  23. data/lib/capybara/queries/title_query.rb +1 -0
  24. data/lib/capybara/rack_test/browser.rb +13 -5
  25. data/lib/capybara/rack_test/driver.rb +6 -1
  26. data/lib/capybara/rack_test/form.rb +4 -3
  27. data/lib/capybara/rack_test/node.rb +1 -1
  28. data/lib/capybara/rspec/compound.rb +95 -0
  29. data/lib/capybara/rspec/matcher_proxies.rb +45 -0
  30. data/lib/capybara/rspec/matchers.rb +108 -7
  31. data/lib/capybara/rspec.rb +3 -1
  32. data/lib/capybara/selector/filter.rb +13 -41
  33. data/lib/capybara/selector/filter_set.rb +30 -4
  34. data/lib/capybara/selector/filters/base.rb +33 -0
  35. data/lib/capybara/selector/filters/expression_filter.rb +40 -0
  36. data/lib/capybara/selector/filters/node_filter.rb +27 -0
  37. data/lib/capybara/selector/selector.rb +36 -15
  38. data/lib/capybara/selector.rb +63 -42
  39. data/lib/capybara/selenium/driver.rb +177 -33
  40. data/lib/capybara/selenium/node.rb +106 -55
  41. data/lib/capybara/server.rb +6 -5
  42. data/lib/capybara/session/config.rb +114 -0
  43. data/lib/capybara/session/matchers.rb +15 -4
  44. data/lib/capybara/session.rb +178 -65
  45. data/lib/capybara/spec/fixtures/no_extension +1 -0
  46. data/lib/capybara/spec/public/test.js +18 -3
  47. data/lib/capybara/spec/session/accept_alert_spec.rb +9 -1
  48. data/lib/capybara/spec/session/accept_prompt_spec.rb +29 -1
  49. data/lib/capybara/spec/session/all_spec.rb +13 -1
  50. data/lib/capybara/spec/session/ancestor_spec.rb +85 -0
  51. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +24 -8
  52. data/lib/capybara/spec/session/assert_selector.rb +1 -1
  53. data/lib/capybara/spec/session/assert_text.rb +8 -0
  54. data/lib/capybara/spec/session/assert_title.rb +22 -9
  55. data/lib/capybara/spec/session/attach_file_spec.rb +8 -1
  56. data/lib/capybara/spec/session/check_spec.rb +4 -4
  57. data/lib/capybara/spec/session/choose_spec.rb +2 -2
  58. data/lib/capybara/spec/session/click_button_spec.rb +1 -1
  59. data/lib/capybara/spec/session/click_link_or_button_spec.rb +3 -3
  60. data/lib/capybara/spec/session/click_link_spec.rb +1 -1
  61. data/lib/capybara/spec/session/current_url_spec.rb +3 -3
  62. data/lib/capybara/spec/session/dismiss_confirm_spec.rb +3 -3
  63. data/lib/capybara/spec/session/dismiss_prompt_spec.rb +1 -1
  64. data/lib/capybara/spec/session/evaluate_async_script_spec.rb +22 -0
  65. data/lib/capybara/spec/session/evaluate_script_spec.rb +1 -1
  66. data/lib/capybara/spec/session/fill_in_spec.rb +8 -2
  67. data/lib/capybara/spec/session/find_field_spec.rb +1 -0
  68. data/lib/capybara/spec/session/find_spec.rb +8 -6
  69. data/lib/capybara/spec/session/first_spec.rb +10 -5
  70. data/lib/capybara/spec/session/has_all_selectors_spec.rb +69 -0
  71. data/lib/capybara/spec/session/has_css_spec.rb +11 -0
  72. data/lib/capybara/spec/session/has_current_path_spec.rb +52 -7
  73. data/lib/capybara/spec/session/has_link_spec.rb +4 -4
  74. data/lib/capybara/spec/session/has_none_selectors_spec.rb +76 -0
  75. data/lib/capybara/spec/session/has_select_spec.rb +64 -6
  76. data/lib/capybara/spec/session/has_selector_spec.rb +1 -3
  77. data/lib/capybara/spec/session/has_text_spec.rb +5 -3
  78. data/lib/capybara/spec/session/has_title_spec.rb +4 -2
  79. data/lib/capybara/spec/session/has_xpath_spec.rb +5 -3
  80. data/lib/capybara/spec/session/node_spec.rb +50 -26
  81. data/lib/capybara/spec/session/refresh_spec.rb +28 -0
  82. data/lib/capybara/spec/session/reset_session_spec.rb +3 -3
  83. data/lib/capybara/spec/session/select_spec.rb +3 -2
  84. data/lib/capybara/spec/session/sibling_spec.rb +52 -0
  85. data/lib/capybara/spec/session/uncheck_spec.rb +2 -2
  86. data/lib/capybara/spec/session/unselect_spec.rb +2 -2
  87. data/lib/capybara/spec/session/visit_spec.rb +56 -1
  88. data/lib/capybara/spec/session/window/become_closed_spec.rb +11 -11
  89. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +11 -9
  90. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +4 -4
  91. data/lib/capybara/spec/session/window/within_window_spec.rb +27 -2
  92. data/lib/capybara/spec/spec_helper.rb +28 -4
  93. data/lib/capybara/spec/test_app.rb +3 -1
  94. data/lib/capybara/spec/views/form.erb +27 -1
  95. data/lib/capybara/spec/views/initial_alert.erb +10 -0
  96. data/lib/capybara/spec/views/with_fixed_header_footer.erb +17 -0
  97. data/lib/capybara/spec/views/with_hover.erb +5 -0
  98. data/lib/capybara/spec/views/with_html.erb +33 -2
  99. data/lib/capybara/spec/views/with_js.erb +12 -0
  100. data/lib/capybara/spec/views/with_windows.erb +4 -0
  101. data/lib/capybara/version.rb +1 -1
  102. data/lib/capybara/window.rb +1 -1
  103. data/lib/capybara.rb +102 -124
  104. data/spec/capybara_spec.rb +43 -21
  105. data/spec/dsl_spec.rb +1 -0
  106. data/spec/filter_set_spec.rb +28 -0
  107. data/spec/minitest_spec.rb +9 -1
  108. data/spec/minitest_spec_spec.rb +19 -5
  109. data/spec/per_session_config_spec.rb +67 -0
  110. data/spec/result_spec.rb +20 -0
  111. data/spec/rspec/shared_spec_matchers.rb +148 -44
  112. data/spec/rspec/views_spec.rb +4 -0
  113. data/spec/rspec_matchers_spec.rb +46 -0
  114. data/spec/rspec_spec.rb +77 -0
  115. data/spec/selector_spec.rb +2 -1
  116. data/spec/selenium_spec_chrome.rb +25 -17
  117. data/spec/selenium_spec_firefox.rb +2 -1
  118. data/spec/selenium_spec_marionette.rb +18 -5
  119. data/spec/session_spec.rb +44 -0
  120. data/spec/shared_selenium_session.rb +72 -8
  121. data/spec/spec_helper.rb +4 -0
  122. metadata +55 -8
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+ require 'delegate'
3
+
4
+ module Capybara
5
+ class SessionConfig
6
+ OPTIONS = [:always_include_port, :run_server, :default_selector, :default_max_wait_time, :ignore_hidden_elements,
7
+ :automatic_reload, :match, :exact, :exact_text, :raise_server_errors, :visible_text_only, :wait_on_first_by_default,
8
+ :automatic_label_click, :enable_aria_label, :save_path, :exact_options, :asset_host, :default_host, :app_host,
9
+ :save_and_open_page_path, :server_host, :server_port, :server_errors]
10
+
11
+ attr_accessor(*OPTIONS)
12
+
13
+ ##
14
+ #@!method always_include_port
15
+ # See {Capybara.configure}
16
+ #@!method run_server
17
+ # See {Capybara.configure}
18
+ #@!method default_selector
19
+ # See {Capybara.configure}
20
+ #@!method default_max_wait_time
21
+ # See {Capybara.configure}
22
+ #@!method ignore_hidden_elements
23
+ # See {Capybara.configure}
24
+ #@!method automatic_reload
25
+ # See {Capybara.configure}
26
+ #@!method match
27
+ # See {Capybara.configure}
28
+ #@!method exact
29
+ # See {Capybara.configure}
30
+ #@!method raise_server_errors
31
+ # See {Capybara.configure}
32
+ #@!method visible_text_only
33
+ # See {Capybara.configure}
34
+ #@!method wait_on_first_by_default
35
+ # See {Capybara.configure}
36
+ #@!method automatic_label_click
37
+ # See {Capybara.configure}
38
+ #@!method enable_aria_label
39
+ # See {Capybara.configure}
40
+ #@!method save_path
41
+ # See {Capybara.configure}
42
+ #@deprecated
43
+ #@!method exact_options
44
+ # See {Capybara.configure}
45
+ #@!method asset_host
46
+ # See {Capybara.configure}
47
+ #@!method default_host
48
+ # See {Capybara.configure}
49
+ #@!method app_host
50
+ # See {Capybara.configure}
51
+ #@!method save_and_open_page_path
52
+ # See {Capybara.configure}
53
+ #@!method server_host
54
+ # See {Capybara.configure}
55
+ #@!method server_port
56
+ # See {Capybara.configure}
57
+ #@!method server_errors
58
+ # See {Capybara.configure}
59
+
60
+ remove_method :server_host
61
+
62
+ ##
63
+ #
64
+ # @return [String] The IP address bound by default server
65
+ #
66
+ def server_host
67
+ @server_host || '127.0.0.1'
68
+ end
69
+
70
+ remove_method :server_errors=
71
+ def server_errors=(errors)
72
+ (@server_errors ||= []).replace(errors.dup)
73
+ end
74
+
75
+ remove_method :app_host=
76
+ def app_host=(url)
77
+ 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)
78
+ @app_host = url
79
+ end
80
+
81
+ remove_method :default_host=
82
+ def default_host=(url)
83
+ 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)
84
+ @default_host = url
85
+ end
86
+
87
+ remove_method :save_and_open_page_path=
88
+ def save_and_open_page_path=(path)
89
+ warn "DEPRECATED: #save_and_open_page_path is deprecated, please use #save_path instead. \n"\
90
+ "Note: Behavior is slightly different with relative paths - see documentation" unless path.nil?
91
+ @save_and_open_page_path = path
92
+ end
93
+
94
+ remove_method :exact_options=
95
+ def exact_options=(opt)
96
+ @exact_options = opt
97
+ warn "DEPRECATED: #exact_options is deprecated, please scope your findes/actions and use the `:exact` "\
98
+ "option if similar functionality is needed."
99
+ end
100
+
101
+ def initialize_copy(other)
102
+ super
103
+ @server_errors = @server_errors.dup
104
+ end
105
+ end
106
+
107
+ class ReadOnlySessionConfig < SimpleDelegator
108
+ SessionConfig::OPTIONS.each do |m|
109
+ define_method "#{m}=" do |val|
110
+ raise "Per session settings are only supported when Capybara.threadsafe == true"
111
+ end
112
+ end
113
+ end
114
+ end
@@ -3,16 +3,18 @@ module Capybara
3
3
  module SessionMatchers
4
4
  ##
5
5
  # Asserts that the page has the given path.
6
- # By default this will compare against the path+query portion of the full url
6
+ # By default, if passed a full url this will compare against the full url,
7
+ # if passed a path only the path+query portion will be compared, if passed a regexp
8
+ # the comparison will depend on the :url option
7
9
  #
8
10
  # @!macro current_path_query_params
9
11
  # @overload $0(string, options = {})
10
12
  # @param string [String] The string that the current 'path' should equal
11
13
  # @overload $0(regexp, options = {})
12
14
  # @param regexp [Regexp] The regexp that the current 'path' should match to
13
- # @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time that Capybara will wait for the current path to eq/match given string/regexp argument
14
- # @option options [Boolean] :url (false) Whether the compare should be done against the full url
15
- # @option options [Boolean] :only_path (false) Whether the compare should be done against just the path protion of the url
15
+ # @option options [Boolean] :url (true if `string` ia a full url, otherwise false) Whether the compare should be done against the full current url or just the path
16
+ # @option options [Boolean] :ignore_query (false) Whether the query portion of the current url/path should be ignored
17
+ # @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time that Capybara will wait for the current url/path to eq/match given string/regexp argument
16
18
  # @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
17
19
  # @return [true]
18
20
  #
@@ -22,6 +24,9 @@ module Capybara
22
24
 
23
25
  ##
24
26
  # Asserts that the page doesn't have the given path.
27
+ # By default, if passed a full url this will compare against the full url,
28
+ # if passed a path only the path+query portion will be compared, if passed a regexp
29
+ # the comparison will depend on the :url option
25
30
  #
26
31
  # @macro current_path_query_params
27
32
  # @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
@@ -33,6 +38,9 @@ module Capybara
33
38
 
34
39
  ##
35
40
  # Checks if the page has the given path.
41
+ # By default, if passed a full url this will compare against the full url,
42
+ # if passed a path only the path+query portion will be compared, if passed a regexp
43
+ # the comparison will depend on the :url option
36
44
  #
37
45
  # @macro current_path_query_params
38
46
  # @return [Boolean]
@@ -45,6 +53,9 @@ module Capybara
45
53
 
46
54
  ##
47
55
  # Checks if the page doesn't have the given path.
56
+ # By default, if passed a full url this will compare against the full url,
57
+ # if passed a path only the path+query portion will be compared, if passed a regexp
58
+ # the comparison will depend on the :url option
48
59
  #
49
60
  # @macro current_path_query_params
50
61
  # @return [Boolean]
@@ -17,6 +17,14 @@ module Capybara
17
17
  # session = Capybara::Session.new(:culerity)
18
18
  # session.visit('http://www.google.com')
19
19
  #
20
+ # When Capybara.threadsafe == true the sessions options will be initially set to the
21
+ # current values of the global options and a configuration block can be passed to the session initializer.
22
+ # For available options see {Capybara::SessionConfig::OPTIONS}
23
+ #
24
+ # session = Capybara::Session.new(:driver, MyRackApp) do |config|
25
+ # config.app_host = "http://my_host.dev"
26
+ # end
27
+ #
20
28
  # Session provides a number of methods for controlling the navigation of the page, such as +visit+,
21
29
  # +current_path, and so on. It also delegate a number of methods to a Capybara::Document, representing
22
30
  # the current HTML document. This allows interaction:
@@ -50,7 +58,7 @@ module Capybara
50
58
  ]
51
59
  SESSION_METHODS = [
52
60
  :body, :html, :source, :current_url, :current_host, :current_path,
53
- :execute_script, :evaluate_script, :visit, :go_back, :go_forward,
61
+ :execute_script, :evaluate_script, :visit, :refresh, :go_back, :go_forward,
54
62
  :within, :within_element, :within_fieldset, :within_table, :within_frame, :switch_to_frame,
55
63
  :current_window, :windows, :open_new_window, :switch_to_window, :within_window, :window_opened_by,
56
64
  :save_page, :save_and_open_page, :save_screenshot,
@@ -69,10 +77,15 @@ module Capybara
69
77
 
70
78
  def initialize(mode, app=nil)
71
79
  raise TypeError, "The second parameter to Session::new should be a rack app if passed." if app && !app.respond_to?(:call)
80
+ @@instance_created = true
72
81
  @mode = mode
73
82
  @app = app
74
- if Capybara.run_server and @app and driver.needs_server?
75
- @server = Capybara::Server.new(@app).boot
83
+ if block_given?
84
+ raise "A configuration block is only accepted when Capybara.threadsafe == true" unless Capybara.threadsafe
85
+ yield config if block_given?
86
+ end
87
+ if config.run_server and @app and driver.needs_server?
88
+ @server = Capybara::Server.new(@app, config.server_port, config.server_host, config.server_errors).boot
76
89
  else
77
90
  @server = nil
78
91
  end
@@ -85,7 +98,9 @@ module Capybara
85
98
  other_drivers = Capybara.drivers.keys.map { |key| key.inspect }
86
99
  raise Capybara::DriverNotFoundError, "no driver called #{mode.inspect} was found, available drivers: #{other_drivers.join(', ')}"
87
100
  end
88
- Capybara.drivers[mode].call(app)
101
+ driver = Capybara.drivers[mode].call(app)
102
+ driver.session = self if driver.respond_to?(:session=)
103
+ driver
89
104
  end
90
105
  end
91
106
 
@@ -126,7 +141,7 @@ module Capybara
126
141
  if @server and @server.error
127
142
  # Force an explanation for the error being raised as the exception cause
128
143
  begin
129
- if Capybara.raise_server_errors
144
+ if config.raise_server_errors
130
145
  raise CapybaraError, "Your application server raised an error - It has been raised in your test code because Capybara.raise_server_errors == true"
131
146
  end
132
147
  rescue CapybaraError
@@ -232,28 +247,42 @@ module Capybara
232
247
  raise_server_error!
233
248
  @touched = true
234
249
 
235
- visit_uri = URI.parse(visit_uri.to_s)
250
+ visit_uri = ::Addressable::URI.parse(visit_uri.to_s)
236
251
 
237
252
  uri_base = if @server
238
- visit_uri.port = @server.port if Capybara.always_include_port && (visit_uri.port == visit_uri.default_port)
239
- URI.parse(Capybara.app_host || "http://#{@server.host}:#{@server.port}")
253
+ ::Addressable::URI.parse(config.app_host || "http://#{@server.host}:#{@server.port}")
240
254
  else
241
- Capybara.app_host && URI.parse(Capybara.app_host)
255
+ config.app_host && ::Addressable::URI.parse(config.app_host)
242
256
  end
243
257
 
244
- # TODO - this is only for compatability with previous 2.x behavior that concatenated
245
- # Capybara.app_host and a "relative" path - Consider removing in 3.0
246
- # @abotalov brought up a good point about this behavior potentially being useful to people
247
- # deploying to a subdirectory and/or single page apps where only the url fragment changes
248
- if visit_uri.scheme.nil? && uri_base
249
- visit_uri.path = uri_base.path + visit_uri.path
250
- end
258
+ if uri_base && [nil, 'http', 'https'].include?(visit_uri.scheme)
259
+ if visit_uri.relative?
260
+ uri_base.port ||= @server.port if @server && config.always_include_port
251
261
 
252
- visit_uri = uri_base.merge(visit_uri) unless uri_base.nil?
262
+ visit_uri_parts = visit_uri.to_hash.delete_if { |k,v| v.nil? }
263
+
264
+ # Useful to people deploying to a subdirectory
265
+ # and/or single page apps where only the url fragment changes
266
+ visit_uri_parts[:path] = uri_base.path + visit_uri.path
267
+
268
+ visit_uri = uri_base.merge(visit_uri_parts)
269
+ else
270
+ visit_uri.port ||= @server.port if @server && config.always_include_port
271
+ end
272
+ end
253
273
 
254
274
  driver.visit(visit_uri.to_s)
255
275
  end
256
276
 
277
+ ##
278
+ #
279
+ # Refresh the page
280
+ #
281
+ def refresh
282
+ raise_server_error!
283
+ driver.refresh
284
+ end
285
+
257
286
  ##
258
287
  #
259
288
  # Move back a single entry in the browser's history.
@@ -457,14 +486,14 @@ module Capybara
457
486
  # @raise [Capybara::WindowError] if no window matches given block
458
487
  # @overload switch_to_window(window)
459
488
  # @param window [Capybara::Window] window that should be switched to
460
- # @raise [Capybara::Driver::Base#no_such_window_error] if unexistent (e.g. closed) window was passed
489
+ # @raise [Capybara::Driver::Base#no_such_window_error] if nonexistent (e.g. closed) window was passed
461
490
  #
462
491
  # @return [Capybara::Window] window that has been switched to
463
- # @raise [Capybara::ScopeError] if this method is invoked inside `within`,
464
- # `within_frame` or `within_window` methods
492
+ # @raise [Capybara::ScopeError] if this method is invoked inside `within` or
493
+ # `within_frame` methods
465
494
  # @raise [ArgumentError] if both or neither arguments were provided
466
495
  #
467
- def switch_to_window(window = nil, options= {})
496
+ def switch_to_window(window = nil, options= {}, &window_locator)
468
497
  options, window = window, nil if window.is_a? Hash
469
498
 
470
499
  block_given = block_given?
@@ -472,34 +501,12 @@ module Capybara
472
501
  raise ArgumentError, "`switch_to_window` can take either a block or a window, not both"
473
502
  elsif !window && !block_given
474
503
  raise ArgumentError, "`switch_to_window`: either window or block should be provided"
475
- elsif scopes.size > 1
504
+ elsif !scopes.last.nil?
476
505
  raise Capybara::ScopeError, "`switch_to_window` is not supposed to be invoked from "\
477
- "`within`'s, `within_frame`'s' or `within_window`'s' block."
506
+ "`within` or `within_frame` blocks."
478
507
  end
479
508
 
480
- if window
481
- driver.switch_to_window(window.handle)
482
- window
483
- else
484
- wait_time = Capybara::Queries::BaseQuery.wait(options)
485
- document.synchronize(wait_time, errors: [Capybara::WindowError]) do
486
- original_window_handle = driver.current_window_handle
487
- begin
488
- driver.window_handles.each do |handle|
489
- driver.switch_to_window handle
490
- if yield
491
- return Window.new(self, handle)
492
- end
493
- end
494
- rescue => e
495
- driver.switch_to_window(original_window_handle)
496
- raise e
497
- else
498
- driver.switch_to_window(original_window_handle)
499
- raise Capybara::WindowError, "Could not find a window matching block/lambda"
500
- end
501
- end
502
- end
509
+ _switch_to_window(window, options, &window_locator)
503
510
  end
504
511
 
505
512
  ##
@@ -512,7 +519,7 @@ module Capybara
512
519
  # @overload within_window(window) { do_something }
513
520
  # @param window [Capybara::Window] instance of `Capybara::Window` class
514
521
  # that will be switched to
515
- # @raise [driver#no_such_window_error] if unexistent (e.g. closed) window was passed
522
+ # @raise [driver#no_such_window_error] if nonexistent (e.g. closed) window was passed
516
523
  # @overload within_window(proc_or_lambda) { do_something }
517
524
  # @param lambda [Proc] lambda. First window for which lambda
518
525
  # returns a value other than false or nil will be switched to.
@@ -523,30 +530,35 @@ module Capybara
523
530
  # @deprecated Pass window or lambda instead
524
531
  # @param [String] handle, name, url or title of the window
525
532
  #
526
- # @raise [Capybara::ScopeError] if this method is invoked inside `within`,
527
- # `within_frame` or `within_window` methods
533
+ # @raise [Capybara::ScopeError] if this method is invoked inside `within_frame` method
528
534
  # @return value returned by the block
529
535
  #
530
536
  def within_window(window_or_handle)
531
537
  if window_or_handle.instance_of?(Capybara::Window)
532
538
  original = current_window
533
- switch_to_window(window_or_handle) unless original == window_or_handle
534
539
  scopes << nil
535
540
  begin
536
- yield
541
+ _switch_to_window(window_or_handle) unless original == window_or_handle
542
+ begin
543
+ yield
544
+ ensure
545
+ _switch_to_window(original) unless original == window_or_handle
546
+ end
537
547
  ensure
538
- @scopes.pop
539
- switch_to_window(original) unless original == window_or_handle
548
+ scopes.pop
540
549
  end
541
550
  elsif window_or_handle.is_a?(Proc)
542
551
  original = current_window
543
- switch_to_window { window_or_handle.call }
544
552
  scopes << nil
545
553
  begin
546
- yield
554
+ _switch_to_window { window_or_handle.call }
555
+ begin
556
+ yield
557
+ ensure
558
+ _switch_to_window(original)
559
+ end
547
560
  ensure
548
- @scopes.pop
549
- switch_to_window(original)
561
+ scopes.pop
550
562
  end
551
563
  else
552
564
  offending_line = caller.first
@@ -557,7 +569,7 @@ module Capybara
557
569
  scopes << nil
558
570
  driver.within_window(window_or_handle) { yield }
559
571
  ensure
560
- @scopes.pop
572
+ scopes.pop
561
573
  end
562
574
  end
563
575
  end
@@ -578,7 +590,7 @@ module Capybara
578
590
  old_handles = driver.window_handles
579
591
  block.call
580
592
 
581
- wait_time = Capybara::Queries::BaseQuery.wait(options)
593
+ wait_time = Capybara::Queries::BaseQuery.wait(options, config.default_max_wait_time)
582
594
  document.synchronize(wait_time, errors: [Capybara::WindowError]) do
583
595
  opened_handles = (driver.window_handles - old_handles)
584
596
  if opened_handles.size != 1
@@ -628,18 +640,45 @@ module Capybara
628
640
  element_script_result(result)
629
641
  end
630
642
 
643
+ ##
644
+ #
645
+ # Evaluate the given JavaScript and obtain the result from a callback function which will be passed as the last argument to the script.
646
+ #
647
+ # @param [String] script A string of JavaScript to evaluate
648
+ # @return [Object] The result of the evaluated JavaScript (may be driver specific)
649
+ #
650
+ def evaluate_async_script(script, *args)
651
+ @touched = true
652
+ result = if args.empty?
653
+ driver.evaluate_async_script(script)
654
+ else
655
+ raise Capybara::NotSupportedByDriverError, "The current driver does not support evaluate_async_script arguments" if driver.method(:evaluate_async_script).arity == 1
656
+ driver.evaluate_async_script(script, *args.map { |arg| arg.is_a?(Capybara::Node::Element) ? arg.base : arg} )
657
+ end
658
+ element_script_result(result)
659
+ end
660
+
631
661
  ##
632
662
  #
633
663
  # Execute the block, accepting a alert.
634
664
  #
635
665
  # @!macro modal_params
666
+ # Expects a block whose actions will trigger the display modal to appear
667
+ # @example
668
+ # $0 do
669
+ # click_link('link that triggers appearance of system modal')
670
+ # end
636
671
  # @overload $0(text, options = {}, &blk)
637
672
  # @param text [String, Regexp] Text or regex to match against the text in the modal. If not provided any modal is matched
673
+ # @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time to wait for the modal to appear after executing the block.
674
+ # @yield Block whose actions will trigger the system modal
638
675
  # @overload $0(options = {}, &blk)
639
- # @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time to wait for the modal to appear after executing the block.
676
+ # @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time to wait for the modal to appear after executing the block.
677
+ # @yield Block whose actions will trigger the system modal
640
678
  # @return [String] the message shown in the modal
641
679
  # @raise [Capybara::ModalNotFound] if modal dialog hasn't been found
642
680
  #
681
+ #
643
682
  def accept_alert(text_or_options=nil, options={}, &blk)
644
683
  accept_modal(:alert, text_or_options, options, &blk)
645
684
  end
@@ -701,7 +740,7 @@ module Capybara
701
740
  #
702
741
  def save_page(path = nil)
703
742
  path = prepare_path(path, 'html')
704
- File.write(path, Capybara::Helpers.inject_asset_host(body), mode: 'wb')
743
+ File.write(path, Capybara::Helpers.inject_asset_host(body, config.asset_host), mode: 'wb')
705
744
  path
706
745
  end
707
746
 
@@ -786,7 +825,49 @@ module Capybara
786
825
  scope
787
826
  end
788
827
 
828
+ ##
829
+ #
830
+ # Yield a block using a specific wait time
831
+ #
832
+ def using_wait_time(seconds)
833
+ if Capybara.threadsafe
834
+ begin
835
+ previous_wait_time = config.default_max_wait_time
836
+ config.default_max_wait_time = seconds
837
+ yield
838
+ ensure
839
+ config.default_max_wait_time = previous_wait_time
840
+ end
841
+ else
842
+ Capybara.using_wait_time(seconds) { yield }
843
+ end
844
+ end
845
+
846
+ ##
847
+ #
848
+ # Accepts a block to set the configuration options if Capybara.threadsafe == true. Note that some options only have an effect
849
+ # if set at initialization time, so look at the configuration block that can be passed to the initializer too
850
+ #
851
+ def configure
852
+ raise "Session configuration is only supported when Capybara.threadsafe == true" unless Capybara.threadsafe
853
+ yield config
854
+ end
855
+
856
+ def self.instance_created?
857
+ @@instance_created
858
+ end
859
+
860
+ def config
861
+ @config ||= if Capybara.threadsafe
862
+ Capybara.session_options.dup
863
+ else
864
+ Capybara::ReadOnlySessionConfig.new(Capybara.session_options)
865
+ end
866
+ end
789
867
  private
868
+
869
+ @@instance_created = false
870
+
790
871
  def accept_modal(type, text_or_options, options, &blk)
791
872
  driver.accept_modal(type, modal_options(text_or_options, options), &blk)
792
873
  end
@@ -798,7 +879,7 @@ module Capybara
798
879
  def modal_options(text_or_options, options)
799
880
  text_or_options, options = nil, text_or_options if text_or_options.is_a?(Hash)
800
881
  options[:text] ||= text_or_options unless text_or_options.nil?
801
- options[:wait] ||= Capybara.default_max_wait_time
882
+ options[:wait] ||= config.default_max_wait_time
802
883
  options
803
884
  end
804
885
 
@@ -814,10 +895,10 @@ module Capybara
814
895
  end
815
896
 
816
897
  def prepare_path(path, extension)
817
- if Capybara.save_path || Capybara.save_and_open_page_path.nil?
818
- path = File.expand_path(path || default_fn(extension), Capybara.save_path)
898
+ if config.save_path || config.save_and_open_page_path.nil?
899
+ path = File.expand_path(path || default_fn(extension), config.save_path)
819
900
  else
820
- path = File.expand_path(default_fn(extension), Capybara.save_and_open_page_path) if path.nil?
901
+ path = File.expand_path(default_fn(extension), config.save_and_open_page_path) if path.nil?
821
902
  end
822
903
  FileUtils.mkdir_p(File.dirname(path))
823
904
  path
@@ -862,5 +943,37 @@ module Capybara
862
943
  end
863
944
  end
864
945
  end
946
+
947
+ def _switch_to_window(window = nil, options= {})
948
+ options, window = window, nil if window.is_a? Hash
949
+
950
+ raise Capybara::ScopeError, "Window cannot be switched inside a `within_frame` block" if scopes.include?(:frame)
951
+ raise Capybara::ScopeError, "Window cannot be switch inside a `within` block" unless scopes.last.nil?
952
+
953
+ if window
954
+ driver.switch_to_window(window.handle)
955
+ window
956
+ else
957
+ wait_time = Capybara::Queries::BaseQuery.wait(options, config.default_max_wait_time)
958
+ document.synchronize(wait_time, errors: [Capybara::WindowError]) do
959
+ original_window_handle = driver.current_window_handle
960
+ begin
961
+ driver.window_handles.each do |handle|
962
+ driver.switch_to_window handle
963
+ if yield
964
+ return Window.new(self, handle)
965
+ end
966
+ end
967
+ rescue => e
968
+ driver.switch_to_window(original_window_handle)
969
+ raise e
970
+ else
971
+ driver.switch_to_window(original_window_handle)
972
+ raise Capybara::WindowError, "Could not find a window matching block/lambda"
973
+ end
974
+ end
975
+ end
976
+ end
977
+
865
978
  end
866
979
  end
@@ -0,0 +1 @@
1
+ ThisFileHAsNoExtension
@@ -1,8 +1,8 @@
1
1
  var activeRequests = 0;
2
2
  $(function() {
3
3
  $('#change').text('I changed it');
4
- $('#drag').draggable();
5
- $('#drop').droppable({
4
+ $('#drag, #drag_scroll').draggable();
5
+ $('#drop, #drop_scroll').droppable({
6
6
  drop: function(event, ui) {
7
7
  ui.draggable.remove();
8
8
  $(this).html('Dropped!');
@@ -37,6 +37,9 @@ $(function() {
37
37
  $('#with_change_event').change(function() {
38
38
  $('body').append($('<p class="change_event_triggered"></p>').text(this.value));
39
39
  });
40
+ $('#with_change_event').on('input', function() {
41
+ $('body').append($('<p class="input_event_triggered"></p>').text(this.value));
42
+ });
40
43
  $('#checkbox_with_event').click(function() {
41
44
  $('body').append('<p id="checkbox_event_triggered">Checkbox event triggered</p>');
42
45
  });
@@ -58,7 +61,7 @@ $(function() {
58
61
  $('#change-title').click(function() {
59
62
  setTimeout(function() {
60
63
  $('title').text('changed title')
61
- }, 250)
64
+ }, 400)
62
65
  });
63
66
  $('#click-test').on({
64
67
  dblclick: function() {
@@ -87,6 +90,10 @@ $(function() {
87
90
  $(link).attr('opened', 'true');
88
91
  }, 3000);
89
92
  });
93
+ $('#alert-page-change').click(function() {
94
+ alert('Page is changing');
95
+ return true;
96
+ });
90
97
  $('#open-confirm').click(function() {
91
98
  if(confirm('Confirm opened')) {
92
99
  $(this).attr('confirmed', 'true');
@@ -102,6 +109,14 @@ $(function() {
102
109
  $(this).attr('response', response);
103
110
  }
104
111
  });
112
+ $('#open-prompt-with-default').click(function() {
113
+ var response = prompt('Prompt opened', 'Default value!');
114
+ if(response === null) {
115
+ $(this).attr('response', 'dismissed');
116
+ } else {
117
+ $(this).attr('response', response);
118
+ }
119
+ });
105
120
  $('#open-twice').click(function() {
106
121
  if (confirm('Are you sure?')) {
107
122
  if (!confirm('Are you really sure?')) {
@@ -47,6 +47,14 @@ Capybara::SpecHelper.spec '#accept_alert', requires: [:modals] do
47
47
  expect(message).to eq('Alert opened [*Yay?*]')
48
48
  end
49
49
 
50
+ it "should handle the alert if the page changes" do
51
+ @session.accept_alert do
52
+ @session.click_link('Alert page change')
53
+ sleep 1 # ensure page change occurs before the accept_alert block exits
54
+ end
55
+ expect(@session).to have_current_path('/with_html')
56
+ end
57
+
50
58
  context "with an asynchronous alert" do
51
59
  it "should accept the alert" do
52
60
  @session.accept_alert do
@@ -63,7 +71,7 @@ Capybara::SpecHelper.spec '#accept_alert', requires: [:modals] do
63
71
  end
64
72
 
65
73
  it "should allow to adjust the delay" do
66
- @session.accept_alert wait: 4 do
74
+ @session.accept_alert wait: 10 do
67
75
  @session.click_link('Open slow alert')
68
76
  end
69
77
  expect(@session).to have_xpath("//a[@id='open-slow-alert' and @opened='true']")