capybara 2.2.1 → 2.3.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.
- checksums.yaml +4 -4
- data/History.md +26 -0
- data/README.md +36 -14
- data/lib/capybara.rb +6 -3
- data/lib/capybara/driver/base.rb +37 -1
- data/lib/capybara/driver/node.rb +10 -2
- data/lib/capybara/helpers.rb +21 -13
- data/lib/capybara/node/base.rb +12 -7
- data/lib/capybara/node/element.rb +17 -1
- data/lib/capybara/node/finders.rb +22 -1
- data/lib/capybara/node/matchers.rb +26 -5
- data/lib/capybara/node/simple.rb +9 -2
- data/lib/capybara/rack_test/css_handlers.rb +3 -1
- data/lib/capybara/rack_test/form.rb +3 -2
- data/lib/capybara/rack_test/node.rb +3 -3
- data/lib/capybara/rspec.rb +1 -0
- data/lib/capybara/rspec/features.rb +2 -1
- data/lib/capybara/rspec/matchers.rb +50 -5
- data/lib/capybara/selenium/driver.rb +76 -12
- data/lib/capybara/selenium/node.rb +8 -0
- data/lib/capybara/server.rb +1 -1
- data/lib/capybara/session.rb +234 -29
- data/lib/capybara/spec/public/jquery.js +1 -1
- data/lib/capybara/spec/public/test.js +7 -0
- data/lib/capybara/spec/session/all_spec.rb +88 -17
- data/lib/capybara/spec/session/assert_selector.rb +6 -0
- data/lib/capybara/spec/session/attach_file_spec.rb +15 -15
- data/lib/capybara/spec/session/body_spec.rb +4 -4
- data/lib/capybara/spec/session/check_spec.rb +16 -16
- data/lib/capybara/spec/session/choose_spec.rb +5 -5
- data/lib/capybara/spec/session/click_button_spec.rb +93 -84
- data/lib/capybara/spec/session/click_link_or_button_spec.rb +8 -8
- data/lib/capybara/spec/session/click_link_spec.rb +26 -19
- data/lib/capybara/spec/session/current_scope_spec.rb +3 -3
- data/lib/capybara/spec/session/current_url_spec.rb +8 -8
- data/lib/capybara/spec/session/evaluate_script_spec.rb +1 -1
- data/lib/capybara/spec/session/execute_script_spec.rb +2 -2
- data/lib/capybara/spec/session/fill_in_spec.rb +22 -22
- data/lib/capybara/spec/session/find_button_spec.rb +4 -4
- data/lib/capybara/spec/session/find_by_id_spec.rb +3 -3
- data/lib/capybara/spec/session/find_field_spec.rb +7 -7
- data/lib/capybara/spec/session/find_link_spec.rb +4 -4
- data/lib/capybara/spec/session/find_spec.rb +46 -46
- data/lib/capybara/spec/session/first_spec.rb +23 -23
- data/lib/capybara/spec/session/go_back_spec.rb +3 -3
- data/lib/capybara/spec/session/go_forward_spec.rb +4 -4
- data/lib/capybara/spec/session/has_button_spec.rb +13 -13
- data/lib/capybara/spec/session/has_css_spec.rb +87 -87
- data/lib/capybara/spec/session/has_field_spec.rb +87 -87
- data/lib/capybara/spec/session/has_link_spec.rb +11 -11
- data/lib/capybara/spec/session/has_select_spec.rb +58 -58
- data/lib/capybara/spec/session/has_selector_spec.rb +48 -48
- data/lib/capybara/spec/session/has_table_spec.rb +7 -7
- data/lib/capybara/spec/session/has_text_spec.rb +73 -73
- data/lib/capybara/spec/session/has_title_spec.rb +10 -10
- data/lib/capybara/spec/session/has_xpath_spec.rb +44 -44
- data/lib/capybara/spec/session/headers.rb +1 -1
- data/lib/capybara/spec/session/html_spec.rb +9 -9
- data/lib/capybara/spec/session/node_spec.rb +81 -65
- data/lib/capybara/spec/session/reset_session_spec.rb +15 -15
- data/lib/capybara/spec/session/response_code.rb +1 -1
- data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +46 -0
- data/lib/capybara/spec/session/save_page_spec.rb +9 -9
- data/lib/capybara/spec/session/{screenshot.rb → screenshot_spec.rb} +4 -2
- data/lib/capybara/spec/session/select_spec.rb +22 -22
- data/lib/capybara/spec/session/text_spec.rb +15 -10
- data/lib/capybara/spec/session/title_spec.rb +2 -2
- data/lib/capybara/spec/session/uncheck_spec.rb +7 -7
- data/lib/capybara/spec/session/unselect_spec.rb +14 -14
- data/lib/capybara/spec/session/visit_spec.rb +24 -17
- data/lib/capybara/spec/session/window/become_closed_spec.rb +84 -0
- data/lib/capybara/spec/session/window/current_window_spec.rb +25 -0
- data/lib/capybara/spec/session/window/open_new_window_spec.rb +28 -0
- data/lib/capybara/spec/session/window/switch_to_window_spec.rb +114 -0
- data/lib/capybara/spec/session/window/window_opened_by_spec.rb +83 -0
- data/lib/capybara/spec/session/window/window_spec.rb +141 -0
- data/lib/capybara/spec/session/window/windows_spec.rb +31 -0
- data/lib/capybara/spec/session/window/within_window_spec.rb +188 -0
- data/lib/capybara/spec/session/within_frame_spec.rb +9 -9
- data/lib/capybara/spec/session/within_spec.rb +16 -16
- data/lib/capybara/spec/spec_helper.rb +14 -4
- data/lib/capybara/spec/views/form.erb +7 -0
- data/lib/capybara/spec/views/popup_one.erb +1 -1
- data/lib/capybara/spec/views/popup_two.erb +1 -1
- data/lib/capybara/spec/views/with_js.erb +2 -0
- data/lib/capybara/spec/views/with_windows.erb +38 -0
- data/lib/capybara/version.rb +1 -1
- data/lib/capybara/window.rb +123 -0
- data/spec/basic_node_spec.rb +32 -32
- data/spec/capybara_spec.rb +6 -7
- data/spec/dsl_spec.rb +48 -48
- data/spec/fixtures/selenium_driver_rspec_failure.rb +2 -2
- data/spec/fixtures/selenium_driver_rspec_success.rb +2 -2
- data/spec/rack_test_spec.rb +33 -19
- data/spec/result_spec.rb +13 -13
- data/spec/rspec/features_spec.rb +20 -15
- data/spec/rspec/matchers_spec.rb +109 -109
- data/spec/rspec_spec.rb +10 -10
- data/spec/selenium_spec.rb +31 -6
- data/spec/selenium_spec_chrome.rb +2 -2
- data/spec/server_spec.rb +13 -13
- metadata +51 -62
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/lib/capybara/spec/session/within_window_spec.rb +0 -45
- data/lib/capybara/spec/views/within_popups.erb +0 -25
- metadata.gz.sig +0 -0
@@ -68,7 +68,11 @@ module Capybara
|
|
68
68
|
#
|
69
69
|
# page.assert_selector('p#foo', :count => 4)
|
70
70
|
#
|
71
|
-
# This will check if the expression occurs exactly 4 times.
|
71
|
+
# This will check if the expression occurs exactly 4 times. See
|
72
|
+
# {Capybara::Node::Finders#all} for other available result size options.
|
73
|
+
#
|
74
|
+
# If a :count of 0 is specified, it will behave like {#assert_no_selector};
|
75
|
+
# however, use of that method is preferred over this one.
|
72
76
|
#
|
73
77
|
# It also accepts all options that {Capybara::Node::Finders#all} accepts,
|
74
78
|
# such as :text and :visible.
|
@@ -88,7 +92,7 @@ module Capybara
|
|
88
92
|
query = Capybara::Query.new(*args)
|
89
93
|
synchronize(query.wait) do
|
90
94
|
result = all(*args)
|
91
|
-
|
95
|
+
raise Capybara::ExpectationNotMet, result.failure_message if result.size == 0 && !Capybara::Helpers.expects_none?(query.options)
|
92
96
|
end
|
93
97
|
return true
|
94
98
|
end
|
@@ -98,18 +102,35 @@ module Capybara
|
|
98
102
|
# Asserts that a given selector is not on the page or current node.
|
99
103
|
# Usage is identical to Capybara::Node::Matchers#assert_selector
|
100
104
|
#
|
105
|
+
# Query options such as :count, :minimum, :maximum, and :between are
|
106
|
+
# considered to be an integral part of the selector. This will return
|
107
|
+
# true, for example, if a page contains 4 anchors but the query expects 5:
|
108
|
+
#
|
109
|
+
# page.assert_no_selector('a', :minimum => 1) # Found, raises Capybara::ExpectationNotMet
|
110
|
+
# page.assert_no_selector('a', :count => 4) # Found, raises Capybara::ExpectationNotMet
|
111
|
+
# page.assert_no_selector('a', :count => 5) # Not Found, returns true
|
112
|
+
#
|
101
113
|
# @param (see Capybara::Node::Finders#assert_selector)
|
102
114
|
# @raise [Capybara::ExpectationNotMet] If the selector exists
|
103
115
|
#
|
104
116
|
def assert_no_selector(*args)
|
105
117
|
query = Capybara::Query.new(*args)
|
106
118
|
synchronize(query.wait) do
|
107
|
-
|
108
|
-
|
119
|
+
begin
|
120
|
+
result = all(*args)
|
121
|
+
rescue Capybara::ExpectationNotMet => e
|
122
|
+
return true
|
123
|
+
else
|
124
|
+
if result.size > 0 || (result.size == 0 && Capybara::Helpers.expects_none?(query.options))
|
125
|
+
raise(Capybara::ExpectationNotMet, result.negative_failure_message)
|
126
|
+
end
|
127
|
+
end
|
109
128
|
end
|
110
129
|
return true
|
111
130
|
end
|
112
131
|
|
132
|
+
alias_method :refute_selector, :assert_no_selector
|
133
|
+
|
113
134
|
##
|
114
135
|
#
|
115
136
|
# Checks if a given XPath expression is on the page or current node.
|
@@ -469,7 +490,7 @@ module Capybara
|
|
469
490
|
content, options = args
|
470
491
|
count = Capybara::Helpers.normalize_whitespace(text(type)).scan(Capybara::Helpers.to_regexp(content)).count
|
471
492
|
|
472
|
-
Capybara::Helpers.matches_count?(count, options || {})
|
493
|
+
Capybara::Helpers.matches_count?(count, {:minimum=>1}.merge(options || {}))
|
473
494
|
end
|
474
495
|
end
|
475
496
|
end
|
data/lib/capybara/node/simple.rb
CHANGED
@@ -94,10 +94,17 @@ module Capybara
|
|
94
94
|
# Whether or not the element is visible. Does not support CSS, so
|
95
95
|
# the result may be inaccurate.
|
96
96
|
#
|
97
|
+
# @param [Boolean] check_ancestors Whether to inherit visibility from ancestors
|
97
98
|
# @return [Boolean] Whether the element is visible
|
98
99
|
#
|
99
|
-
def visible?
|
100
|
-
|
100
|
+
def visible?(check_ancestors = true)
|
101
|
+
if check_ancestors
|
102
|
+
#check size because oldest supported nokogiri doesnt support xpath boolean() function
|
103
|
+
native.xpath("./ancestor-or-self::*[contains(@style, 'display:none') or contains(@style, 'display: none') or @hidden or name()='script' or name()='head']").size() == 0
|
104
|
+
else
|
105
|
+
#no need for an xpath if only checking the current element
|
106
|
+
!(native.has_attribute?('hidden') || (native[:style] =~ /display:\s?none/) || %w(script head).include?(tag_name))
|
107
|
+
end
|
101
108
|
end
|
102
109
|
|
103
110
|
##
|
@@ -1,6 +1,6 @@
|
|
1
1
|
class Capybara::RackTest::Form < Capybara::RackTest::Node
|
2
2
|
# This only needs to inherit from Rack::Test::UploadedFile because Rack::Test checks for
|
3
|
-
# the class specifically when
|
3
|
+
# the class specifically when determining whether to construct the request as multipart.
|
4
4
|
# That check should be based solely on the form element's 'enctype' attribute value,
|
5
5
|
# which should probably be provided to Rack::Test in its non-GET request methods.
|
6
6
|
class NilUploadedFile < Rack::Test::UploadedFile
|
@@ -71,7 +71,8 @@ class Capybara::RackTest::Form < Capybara::RackTest::Node
|
|
71
71
|
end
|
72
72
|
|
73
73
|
def submit(button)
|
74
|
-
|
74
|
+
action = (button && button['formaction']) || native['action']
|
75
|
+
driver.submit(method, action.to_s, params(button))
|
75
76
|
end
|
76
77
|
|
77
78
|
def multipart?
|
@@ -99,14 +99,14 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
|
|
99
99
|
|
100
100
|
protected
|
101
101
|
|
102
|
-
def unnormalized_text
|
103
|
-
if !visible?
|
102
|
+
def unnormalized_text(check_ancestor_visibility = true)
|
103
|
+
if !string_node.visible?(check_ancestor_visibility)
|
104
104
|
''
|
105
105
|
elsif native.text?
|
106
106
|
native.text
|
107
107
|
elsif native.element?
|
108
108
|
native.children.map do |child|
|
109
|
-
Capybara::RackTest::Node.new(driver, child).unnormalized_text
|
109
|
+
Capybara::RackTest::Node.new(driver, child).unnormalized_text(false)
|
110
110
|
end.join
|
111
111
|
else
|
112
112
|
''
|
data/lib/capybara/rspec.rb
CHANGED
@@ -21,7 +21,8 @@ def self.feature(*args, &block)
|
|
21
21
|
options[:caller] ||= caller
|
22
22
|
args.push(options)
|
23
23
|
|
24
|
-
describe
|
24
|
+
#call describe on RSpec in case user has expose_dsl_globally set to false
|
25
|
+
RSpec.describe(*args, &block)
|
25
26
|
end
|
26
27
|
|
27
28
|
RSpec.configuration.include Capybara::Features, :capybara_feature => true
|
@@ -51,16 +51,20 @@ module Capybara
|
|
51
51
|
@actual.has_no_text?(type, content, options)
|
52
52
|
end
|
53
53
|
|
54
|
-
def
|
54
|
+
def failure_message
|
55
55
|
message = Capybara::Helpers.failure_message(description, options)
|
56
56
|
message << " in #{format(@actual.text(type))}"
|
57
57
|
message
|
58
58
|
end
|
59
59
|
|
60
|
-
def
|
61
|
-
|
60
|
+
def failure_message_when_negated
|
61
|
+
failure_message.sub(/(to find)/, 'not \1')
|
62
62
|
end
|
63
63
|
|
64
|
+
# RSpec 2 compatibility:
|
65
|
+
alias_method :failure_message_for_should, :failure_message
|
66
|
+
alias_method :failure_message_for_should_not, :failure_message_when_negated
|
67
|
+
|
64
68
|
def description
|
65
69
|
"text #{format(content)}"
|
66
70
|
end
|
@@ -88,19 +92,50 @@ module Capybara
|
|
88
92
|
@actual.has_no_title?(title)
|
89
93
|
end
|
90
94
|
|
91
|
-
def
|
95
|
+
def failure_message
|
92
96
|
"expected there to be title #{title.inspect} in #{@actual.title.inspect}"
|
93
97
|
end
|
94
98
|
|
95
|
-
def
|
99
|
+
def failure_message_when_negated
|
96
100
|
"expected there not to be title #{title.inspect} in #{@actual.title.inspect}"
|
97
101
|
end
|
98
102
|
|
103
|
+
# RSpec 2 compatibility:
|
104
|
+
alias_method :failure_message_for_should, :failure_message
|
105
|
+
alias_method :failure_message_for_should_not, :failure_message_when_negated
|
106
|
+
|
99
107
|
def description
|
100
108
|
"have title #{title.inspect}"
|
101
109
|
end
|
102
110
|
end
|
103
111
|
|
112
|
+
class BecomeClosed
|
113
|
+
def initialize(options)
|
114
|
+
@wait_time = Capybara::Query.new(options).wait
|
115
|
+
end
|
116
|
+
|
117
|
+
def matches?(window)
|
118
|
+
@window = window
|
119
|
+
start_time = Time.now
|
120
|
+
while window.exists? && (Time.now - start_time) < @wait_time
|
121
|
+
sleep 0.05
|
122
|
+
end
|
123
|
+
window.closed?
|
124
|
+
end
|
125
|
+
|
126
|
+
def failure_message
|
127
|
+
"expected #{@window.inspect} to become closed after #{@wait_time} seconds"
|
128
|
+
end
|
129
|
+
|
130
|
+
def failure_message_when_negated
|
131
|
+
"expected #{@window.inspect} not to become closed after #{@wait_time} seconds"
|
132
|
+
end
|
133
|
+
|
134
|
+
# RSpec 2 compatibility:
|
135
|
+
alias_method :failure_message_for_should, :failure_message
|
136
|
+
alias_method :failure_message_for_should_not, :failure_message_when_negated
|
137
|
+
end
|
138
|
+
|
104
139
|
def have_selector(*args)
|
105
140
|
HaveSelector.new(*args)
|
106
141
|
end
|
@@ -149,5 +184,15 @@ module Capybara
|
|
149
184
|
def have_table(locator, options={})
|
150
185
|
HaveSelector.new(:table, locator, options)
|
151
186
|
end
|
187
|
+
|
188
|
+
##
|
189
|
+
# Wait for window to become closed.
|
190
|
+
# @example
|
191
|
+
# expect(window).to become_closed(wait: 0.8)
|
192
|
+
# @param options [Hash] optional param
|
193
|
+
# @option options [Numeric] :wait (Capybara.default_wait_time) wait time
|
194
|
+
def become_closed(options = {})
|
195
|
+
BecomeClosed.new(options)
|
196
|
+
end
|
152
197
|
end
|
153
198
|
end
|
@@ -126,34 +126,98 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
126
126
|
@frame_handles[browser.window_handle].each { |fh| browser.switch_to.frame(fh) }
|
127
127
|
end
|
128
128
|
|
129
|
-
def
|
129
|
+
def current_window_handle
|
130
|
+
browser.window_handle
|
131
|
+
end
|
132
|
+
|
133
|
+
def window_size(handle)
|
134
|
+
within_given_window(handle) do
|
135
|
+
size = browser.manage.window.size
|
136
|
+
[size.width, size.height]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def resize_window_to(handle, width, height)
|
141
|
+
within_given_window(handle) do
|
142
|
+
browser.manage.window.resize_to(width, height)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def maximize_window(handle)
|
147
|
+
within_given_window(handle) do
|
148
|
+
browser.manage.window.maximize
|
149
|
+
end
|
150
|
+
sleep 0.1 # work around for https://code.google.com/p/selenium/issues/detail?id=7405
|
151
|
+
end
|
152
|
+
|
153
|
+
def close_window(handle)
|
154
|
+
within_given_window(handle) do
|
155
|
+
browser.close
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def window_handles
|
160
|
+
browser.window_handles
|
161
|
+
end
|
162
|
+
|
163
|
+
def open_new_window
|
164
|
+
browser.execute_script('window.open();')
|
165
|
+
end
|
166
|
+
|
167
|
+
def switch_to_window(handle)
|
168
|
+
browser.switch_to.window handle
|
169
|
+
end
|
170
|
+
|
171
|
+
# @api private
|
172
|
+
def find_window(locator)
|
173
|
+
handles = browser.window_handles
|
174
|
+
return locator if handles.include? locator
|
175
|
+
|
130
176
|
original_handle = browser.window_handle
|
131
|
-
|
132
|
-
|
133
|
-
if(
|
134
|
-
browser.title.include?(
|
135
|
-
browser.current_url.include?(
|
136
|
-
|
137
|
-
browser.switch_to.window original_handle
|
177
|
+
handles.each do |handle|
|
178
|
+
switch_to_window(handle)
|
179
|
+
if (locator == browser.execute_script("return window.name") ||
|
180
|
+
browser.title.include?(locator) ||
|
181
|
+
browser.current_url.include?(locator))
|
182
|
+
switch_to_window(original_handle)
|
138
183
|
return handle
|
139
184
|
end
|
140
185
|
end
|
141
|
-
raise Capybara::ElementNotFound, "Could not find a window identified by #{
|
186
|
+
raise Capybara::ElementNotFound, "Could not find a window identified by #{locator}"
|
142
187
|
end
|
143
188
|
|
144
|
-
def within_window(
|
145
|
-
handle = find_window(
|
146
|
-
browser.switch_to.window(handle
|
189
|
+
def within_window(locator)
|
190
|
+
handle = find_window(locator)
|
191
|
+
browser.switch_to.window(handle) { yield }
|
147
192
|
end
|
148
193
|
|
149
194
|
def quit
|
150
195
|
@browser.quit if @browser
|
151
196
|
rescue Errno::ECONNREFUSED
|
152
197
|
# Browser must have already gone
|
198
|
+
ensure
|
199
|
+
@browser = nil
|
153
200
|
end
|
154
201
|
|
155
202
|
def invalid_element_errors
|
156
203
|
[Selenium::WebDriver::Error::StaleElementReferenceError, Selenium::WebDriver::Error::UnhandledError, Selenium::WebDriver::Error::ElementNotVisibleError]
|
157
204
|
end
|
158
205
|
|
206
|
+
def no_such_window_error
|
207
|
+
Selenium::WebDriver::Error::NoSuchWindowError
|
208
|
+
end
|
209
|
+
|
210
|
+
private
|
211
|
+
|
212
|
+
def within_given_window(handle)
|
213
|
+
original_handle = self.current_window_handle
|
214
|
+
if handle == original_handle
|
215
|
+
yield
|
216
|
+
else
|
217
|
+
switch_to_window(handle)
|
218
|
+
result = yield
|
219
|
+
switch_to_window(original_handle)
|
220
|
+
result
|
221
|
+
end
|
222
|
+
end
|
159
223
|
end
|
@@ -70,6 +70,14 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
70
70
|
def click
|
71
71
|
native.click
|
72
72
|
end
|
73
|
+
|
74
|
+
def right_click
|
75
|
+
driver.browser.action.context_click(native).perform
|
76
|
+
end
|
77
|
+
|
78
|
+
def double_click
|
79
|
+
driver.browser.action.double_click(native).perform
|
80
|
+
end
|
73
81
|
|
74
82
|
def hover
|
75
83
|
driver.browser.action.move_to(native).perform
|
data/lib/capybara/server.rb
CHANGED
@@ -36,7 +36,7 @@ module Capybara
|
|
36
36
|
def initialize(app, port=Capybara.server_port, host=Capybara.server_host)
|
37
37
|
@app = app
|
38
38
|
@middleware = Middleware.new(@app)
|
39
|
-
@server_thread = nil #
|
39
|
+
@server_thread = nil # suppress warnings
|
40
40
|
@host, @port = host, port
|
41
41
|
@port ||= Capybara::Server.ports[@app.object_id]
|
42
42
|
@port ||= find_available_port
|
data/lib/capybara/session.rb
CHANGED
@@ -19,7 +19,7 @@ module Capybara
|
|
19
19
|
#
|
20
20
|
# session.fill_in('q', :with => 'Capybara')
|
21
21
|
# session.click_button('Search')
|
22
|
-
# session.
|
22
|
+
# expect(session).to have_content('Capybara')
|
23
23
|
#
|
24
24
|
# When using capybara/dsl, the Session is initialized automatically for you.
|
25
25
|
#
|
@@ -34,15 +34,17 @@ module Capybara
|
|
34
34
|
:has_no_field?, :has_checked_field?, :has_unchecked_field?,
|
35
35
|
:has_no_table?, :has_table?, :unselect, :has_select?, :has_no_select?,
|
36
36
|
:has_selector?, :has_no_selector?, :click_on, :has_no_checked_field?,
|
37
|
-
:has_no_unchecked_field?, :query, :assert_selector, :assert_no_selector
|
37
|
+
:has_no_unchecked_field?, :query, :assert_selector, :assert_no_selector,
|
38
|
+
:refute_selector
|
38
39
|
]
|
39
40
|
SESSION_METHODS = [
|
40
41
|
:body, :html, :source, :current_url, :current_host, :current_path,
|
41
42
|
:execute_script, :evaluate_script, :visit, :go_back, :go_forward,
|
42
|
-
:within, :within_fieldset, :within_table, :within_frame, :
|
43
|
+
:within, :within_fieldset, :within_table, :within_frame, :current_window,
|
44
|
+
:windows, :open_new_window, :switch_to_window, :within_window, :window_opened_by,
|
43
45
|
:save_page, :save_and_open_page, :save_screenshot,
|
44
|
-
:reset_session!, :response_headers,
|
45
|
-
:title, :has_title?, :has_no_title?, :current_scope
|
46
|
+
:save_and_open_screenshot, :reset_session!, :response_headers,
|
47
|
+
:status_code, :title, :has_title?, :has_no_title?, :current_scope
|
46
48
|
]
|
47
49
|
DSL_METHODS = NODE_METHODS + SESSION_METHODS
|
48
50
|
|
@@ -72,20 +74,42 @@ module Capybara
|
|
72
74
|
|
73
75
|
##
|
74
76
|
#
|
75
|
-
# Reset the session
|
77
|
+
# Reset the session (i.e. remove cookies and navigate to blank page)
|
78
|
+
#
|
79
|
+
# This method does not:
|
80
|
+
#
|
81
|
+
# * accept modal dialogs if they are present
|
82
|
+
# * clear browser cache/HTML 5 local storage/IndexedDB/Web SQL database/etc.
|
83
|
+
# * modify state of the driver/underlying browser in any other way
|
84
|
+
#
|
85
|
+
# as doing so will result in performance downsides and it's not needed to do everything from the list above for most apps.
|
86
|
+
#
|
87
|
+
# If you want to do anything from the list above on a general basis you can:
|
88
|
+
#
|
89
|
+
# * write RSpec/Cucumber/etc. after hook
|
90
|
+
# * monkeypatch this method
|
91
|
+
# * use Ruby's `prepend` method
|
76
92
|
#
|
77
93
|
def reset!
|
78
94
|
if @touched
|
79
95
|
driver.reset!
|
80
|
-
@touched = false
|
81
96
|
assert_no_selector :xpath, "/html/body/*"
|
97
|
+
@touched = false
|
82
98
|
end
|
99
|
+
raise_server_error!
|
100
|
+
end
|
101
|
+
alias_method :cleanup!, :reset!
|
102
|
+
alias_method :reset_session!, :reset!
|
103
|
+
|
104
|
+
##
|
105
|
+
#
|
106
|
+
# Raise errors encountered in the server
|
107
|
+
#
|
108
|
+
def raise_server_error!
|
83
109
|
raise @server.error if Capybara.raise_server_errors and @server and @server.error
|
84
110
|
ensure
|
85
111
|
@server.reset_error! if @server
|
86
112
|
end
|
87
|
-
alias_method :cleanup!, :reset!
|
88
|
-
alias_method :reset_session!, :reset!
|
89
113
|
|
90
114
|
##
|
91
115
|
#
|
@@ -178,6 +202,8 @@ module Capybara
|
|
178
202
|
# @param [String] url The URL to navigate to
|
179
203
|
#
|
180
204
|
def visit(url)
|
205
|
+
raise_server_error!
|
206
|
+
|
181
207
|
@touched = true
|
182
208
|
|
183
209
|
if url !~ /^http/ and Capybara.app_host
|
@@ -302,17 +328,172 @@ module Capybara
|
|
302
328
|
end
|
303
329
|
|
304
330
|
##
|
331
|
+
# @return [Capybara::Window] current window
|
305
332
|
#
|
306
|
-
|
307
|
-
|
333
|
+
def current_window
|
334
|
+
Window.new(self, driver.current_window_handle)
|
335
|
+
end
|
336
|
+
|
337
|
+
##
|
338
|
+
# Get all opened windows.
|
339
|
+
# The order of windows in returned array is not defined.
|
340
|
+
# The driver may sort windows by their creation time but it's not required.
|
308
341
|
#
|
309
|
-
# @
|
342
|
+
# @return [Array<Capybara::Window>] an array of all windows
|
310
343
|
#
|
311
|
-
def
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
344
|
+
def windows
|
345
|
+
driver.window_handles.map do |handle|
|
346
|
+
Window.new(self, handle)
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
##
|
351
|
+
# Open new window.
|
352
|
+
# Current window doesn't change as the result of this call.
|
353
|
+
# It should be switched to explicitly.
|
354
|
+
#
|
355
|
+
# @return [Capybara::Window] window that has been opened
|
356
|
+
#
|
357
|
+
def open_new_window
|
358
|
+
window_opened_by do
|
359
|
+
driver.open_new_window
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
##
|
364
|
+
# @overload switch_to_window(&block)
|
365
|
+
# Switches to the first window for which given block returns a value other than false or nil.
|
366
|
+
# If window that matches block can't be found, the window will be switched back and `WindowError` will be raised.
|
367
|
+
# @example
|
368
|
+
# window = switch_to_window { title == 'Page title' }
|
369
|
+
# @raise [Capybara::WindowError] if no window matches given block
|
370
|
+
# @overload switch_to_window(window)
|
371
|
+
# @param window [Capybara::Window] window that should be switched to
|
372
|
+
# @raise [Capybara::Driver::Base#no_such_window_error] if unexistent (e.g. closed) window was passed
|
373
|
+
#
|
374
|
+
# @return [Capybara::Window] window that has been switched to
|
375
|
+
# @raise [Capybara::ScopeError] if this method is invoked inside `within`,
|
376
|
+
# `within_frame` or `within_window` methods
|
377
|
+
# @raise [ArgumentError] if both or neither arguments were provided
|
378
|
+
#
|
379
|
+
def switch_to_window(window = nil)
|
380
|
+
block_given = block_given?
|
381
|
+
if window && block_given
|
382
|
+
raise ArgumentError, "`switch_to_window` can take either a block or a window, not both"
|
383
|
+
elsif !window && !block_given
|
384
|
+
raise ArgumentError, "`switch_to_window`: either window or block should be provided"
|
385
|
+
elsif scopes.size > 1
|
386
|
+
raise Capybara::ScopeError, "`switch_to_window` is not supposed to be invoked from "\
|
387
|
+
"`within`'s, `within_frame`'s' or `within_window`'s' block."
|
388
|
+
end
|
389
|
+
|
390
|
+
if window
|
391
|
+
driver.switch_to_window(window.handle)
|
392
|
+
window
|
393
|
+
else
|
394
|
+
original_window_handle = driver.current_window_handle
|
395
|
+
begin
|
396
|
+
driver.window_handles.each do |handle|
|
397
|
+
driver.switch_to_window handle
|
398
|
+
if yield
|
399
|
+
return Window.new(self, handle)
|
400
|
+
end
|
401
|
+
end
|
402
|
+
rescue => e
|
403
|
+
driver.switch_to_window(original_window_handle)
|
404
|
+
raise e
|
405
|
+
else
|
406
|
+
driver.switch_to_window(original_window_handle)
|
407
|
+
raise Capybara::WindowError, "Could not find a window matching block/lambda"
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
##
|
413
|
+
# This method does the following:
|
414
|
+
#
|
415
|
+
# 1. Switches to the given window (it can be located by window instance/lambda/string).
|
416
|
+
# 2. Executes the given block (within window located at previous step).
|
417
|
+
# 3. Switches back (this step will be invoked even if exception will happen at second step)
|
418
|
+
#
|
419
|
+
# @overload within_window(window) { do_something }
|
420
|
+
# @param window [Capybara::Window] instance of `Capybara::Window` class
|
421
|
+
# that will be switched to
|
422
|
+
# @raise [driver#no_such_window_error] if unexistent (e.g. closed) window was passed
|
423
|
+
# @overload within_window(proc_or_lambda) { do_something }
|
424
|
+
# @param lambda [Proc] lambda. First window for which lambda
|
425
|
+
# returns a value other than false or nil will be switched to.
|
426
|
+
# @example
|
427
|
+
# within_window(->{ page.title == 'Page title' }) { click_button 'Submit' }
|
428
|
+
# @raise [Capybara::WindowError] if no window matching lambda was found
|
429
|
+
# @overload within_window(string) { do_something }
|
430
|
+
# @deprecated Pass window or lambda instead
|
431
|
+
# @param [String] handle, name, url or title of the window
|
432
|
+
#
|
433
|
+
# @raise [Capybara::ScopeError] if this method is invoked inside `within`,
|
434
|
+
# `within_frame` or `within_window` methods
|
435
|
+
# @return value returned by the block
|
436
|
+
#
|
437
|
+
def within_window(window_or_handle)
|
438
|
+
if window_or_handle.instance_of?(Capybara::Window)
|
439
|
+
original = current_window
|
440
|
+
switch_to_window(window_or_handle) unless original == window_or_handle
|
441
|
+
scopes << nil
|
442
|
+
begin
|
443
|
+
yield
|
444
|
+
ensure
|
445
|
+
@scopes.pop
|
446
|
+
switch_to_window(original) unless original == window_or_handle
|
447
|
+
end
|
448
|
+
elsif window_or_handle.is_a?(Proc)
|
449
|
+
original = current_window
|
450
|
+
switch_to_window { window_or_handle.call }
|
451
|
+
scopes << nil
|
452
|
+
begin
|
453
|
+
yield
|
454
|
+
ensure
|
455
|
+
@scopes.pop
|
456
|
+
switch_to_window(original)
|
457
|
+
end
|
458
|
+
else
|
459
|
+
offending_line = caller.first
|
460
|
+
file_line = offending_line.match(/^(.+?):(\d+)/)[0]
|
461
|
+
warn "DEPRECATION WARNING: Passing string argument to #within_window is deprecated. "\
|
462
|
+
"Pass window object or lambda. (called from #{file_line})"
|
463
|
+
begin
|
464
|
+
scopes << nil
|
465
|
+
driver.within_window(window_or_handle) { yield }
|
466
|
+
ensure
|
467
|
+
@scopes.pop
|
468
|
+
end
|
469
|
+
end
|
470
|
+
end
|
471
|
+
|
472
|
+
##
|
473
|
+
# Get the window that has been opened by the passed block.
|
474
|
+
# It will wait for it to be opened (in the same way as other Capybara methods wait).
|
475
|
+
# It's better to use this method than `windows.last`
|
476
|
+
# {https://dvcs.w3.org/hg/webdriver/raw-file/default/webdriver-spec.html#h_note_10 as order of windows isn't defined in some drivers}
|
477
|
+
#
|
478
|
+
# @param options [Hash]
|
479
|
+
# @option options [Numeric] :wait (Capybara.default_wait_time) wait time
|
480
|
+
# @return [Capybara::Window] the window that has been opened within a block
|
481
|
+
# @raise [Capybara::WindowError] if block passed to window hasn't opened window
|
482
|
+
# or opened more than one window
|
483
|
+
#
|
484
|
+
def window_opened_by(options = {}, &block)
|
485
|
+
old_handles = driver.window_handles
|
486
|
+
block.call
|
487
|
+
|
488
|
+
wait_time = Capybara::Query.new(options).wait
|
489
|
+
document.synchronize(wait_time, errors: [Capybara::WindowError]) do
|
490
|
+
opened_handles = (driver.window_handles - old_handles)
|
491
|
+
if opened_handles.size != 1
|
492
|
+
raise Capybara::WindowError, "block passed to #window_opened_by "\
|
493
|
+
"opened #{opened_handles.size} windows instead of 1"
|
494
|
+
end
|
495
|
+
Window.new(self, opened_handles.first)
|
496
|
+
end
|
316
497
|
end
|
317
498
|
|
318
499
|
##
|
@@ -349,12 +530,11 @@ module Capybara
|
|
349
530
|
# @param [String] path The path to where it should be saved [optional]
|
350
531
|
#
|
351
532
|
def save_page(path=nil)
|
352
|
-
path ||=
|
353
|
-
path = File.expand_path(path, Capybara.save_and_open_page_path)
|
533
|
+
path ||= default_path('html')
|
354
534
|
|
355
535
|
FileUtils.mkdir_p(File.dirname(path))
|
356
536
|
|
357
|
-
File.open(path,'
|
537
|
+
File.open(path,'wb') { |f| f.write(Capybara::Helpers.inject_asset_host(body)) }
|
358
538
|
path
|
359
539
|
end
|
360
540
|
|
@@ -366,14 +546,7 @@ module Capybara
|
|
366
546
|
#
|
367
547
|
def save_and_open_page(file_name=nil)
|
368
548
|
file_name = save_page(file_name)
|
369
|
-
|
370
|
-
begin
|
371
|
-
require "launchy"
|
372
|
-
Launchy.open(file_name)
|
373
|
-
rescue LoadError
|
374
|
-
warn "Page saved to #{file_name} with save_and_open_page."
|
375
|
-
warn "Please install the launchy gem to open page automatically."
|
376
|
-
end
|
549
|
+
open_file(file_name)
|
377
550
|
end
|
378
551
|
|
379
552
|
##
|
@@ -383,7 +556,23 @@ module Capybara
|
|
383
556
|
# @param [String] path A string of image path
|
384
557
|
# @option [Hash] options Options for saving screenshot
|
385
558
|
def save_screenshot(path, options={})
|
559
|
+
path ||= default_path('png')
|
560
|
+
|
561
|
+
FileUtils.mkdir_p(File.dirname(path))
|
562
|
+
|
386
563
|
driver.save_screenshot(path, options)
|
564
|
+
path
|
565
|
+
end
|
566
|
+
|
567
|
+
##
|
568
|
+
#
|
569
|
+
# Save a screenshot of the page and open it for inspection
|
570
|
+
#
|
571
|
+
# @param [String] file_name The path to where it should be saved [optional]
|
572
|
+
#
|
573
|
+
def save_and_open_screenshot(file_name=nil)
|
574
|
+
file_name = save_screenshot(file_name)
|
575
|
+
open_file(file_name)
|
387
576
|
end
|
388
577
|
|
389
578
|
def document
|
@@ -429,8 +618,24 @@ module Capybara
|
|
429
618
|
|
430
619
|
private
|
431
620
|
|
621
|
+
def open_file(file_name)
|
622
|
+
begin
|
623
|
+
require "launchy"
|
624
|
+
Launchy.open(file_name)
|
625
|
+
rescue LoadError
|
626
|
+
warn "File saved to #{file_name}."
|
627
|
+
warn "Please install the launchy gem to open the file automatically."
|
628
|
+
end
|
629
|
+
end
|
630
|
+
|
631
|
+
def default_path(extension)
|
632
|
+
timestamp = Time.new.strftime("%Y%m%d%H%M%S")
|
633
|
+
path = "capybara-#{timestamp}#{rand(10**10)}.#{extension}"
|
634
|
+
File.expand_path(path, Capybara.save_and_open_page_path)
|
635
|
+
end
|
636
|
+
|
432
637
|
def scopes
|
433
|
-
@scopes ||= [
|
638
|
+
@scopes ||= [nil]
|
434
639
|
end
|
435
640
|
end
|
436
641
|
end
|