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