capybara 2.3.0 → 2.4.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 +21 -0
- data/README.md +50 -12
- data/lib/capybara.rb +8 -1
- data/lib/capybara/driver/base.rb +28 -0
- data/lib/capybara/driver/node.rb +3 -2
- data/lib/capybara/helpers.rb +2 -3
- data/lib/capybara/node/actions.rb +5 -2
- data/lib/capybara/node/base.rb +10 -0
- data/lib/capybara/node/document.rb +2 -0
- data/lib/capybara/node/document_matchers.rb +68 -0
- data/lib/capybara/node/element.rb +17 -2
- data/lib/capybara/node/finders.rb +5 -20
- data/lib/capybara/node/matchers.rb +101 -71
- data/lib/capybara/node/simple.rb +9 -15
- data/lib/capybara/queries/base_query.rb +29 -0
- data/lib/capybara/queries/text_query.rb +56 -0
- data/lib/capybara/queries/title_query.rb +40 -0
- data/lib/capybara/query.rb +30 -20
- data/lib/capybara/rack_test/node.rb +11 -3
- data/lib/capybara/result.rb +1 -1
- data/lib/capybara/rspec/features.rb +38 -21
- data/lib/capybara/rspec/matchers.rb +53 -38
- data/lib/capybara/selector.rb +68 -14
- data/lib/capybara/selenium/driver.rb +54 -6
- data/lib/capybara/selenium/node.rb +4 -2
- data/lib/capybara/session.rb +109 -35
- data/lib/capybara/spec/public/test.js +34 -1
- data/lib/capybara/spec/session/accept_alert_spec.rb +57 -0
- data/lib/capybara/spec/session/accept_confirm_spec.rb +19 -0
- data/lib/capybara/spec/session/accept_prompt_spec.rb +49 -0
- data/lib/capybara/spec/session/assert_text.rb +195 -0
- data/lib/capybara/spec/session/assert_title.rb +69 -0
- data/lib/capybara/spec/session/dismiss_confirm_spec.rb +35 -0
- data/lib/capybara/spec/session/dismiss_prompt_spec.rb +19 -0
- data/lib/capybara/spec/session/find_field_spec.rb +6 -0
- data/lib/capybara/spec/session/has_text_spec.rb +1 -1
- data/lib/capybara/spec/session/node_spec.rb +16 -1
- data/lib/capybara/spec/session/save_and_open_screenshot_spec.rb +1 -1
- data/lib/capybara/spec/session/visit_spec.rb +5 -0
- data/lib/capybara/spec/views/with_html.erb +4 -0
- data/lib/capybara/spec/views/with_js.erb +17 -0
- data/lib/capybara/version.rb +1 -1
- data/spec/dsl_spec.rb +3 -1
- data/spec/rack_test_spec.rb +12 -1
- data/spec/rspec/features_spec.rb +1 -1
- data/spec/rspec/matchers_spec.rb +113 -20
- data/spec/selenium_spec.rb +10 -1
- metadata +13 -2
@@ -91,13 +91,26 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
91
91
|
def reset!
|
92
92
|
# Use instance variable directly so we avoid starting the browser just to reset the session
|
93
93
|
if @browser
|
94
|
-
begin
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
94
|
+
begin
|
95
|
+
begin @browser.manage.delete_all_cookies
|
96
|
+
rescue Selenium::WebDriver::Error::UnhandledError
|
97
|
+
# delete_all_cookies fails when we've previously gone
|
98
|
+
# to about:blank, so we rescue this error and do nothing
|
99
|
+
# instead.
|
100
|
+
end
|
101
|
+
@browser.navigate.to("about:blank")
|
102
|
+
rescue Selenium::WebDriver::Error::UnhandledAlertError
|
103
|
+
# This error is thrown if an unhandled alert is on the page
|
104
|
+
# Firefox appears to automatically dismiss this alert, chrome does not
|
105
|
+
# We'll try to accept it
|
106
|
+
begin
|
107
|
+
@browser.switch_to.alert.accept
|
108
|
+
rescue Selenium::WebDriver::Error::NoAlertPresentError
|
109
|
+
# The alert is now gone - nothing to do
|
110
|
+
end
|
111
|
+
# try cleaning up the browser again
|
112
|
+
retry
|
99
113
|
end
|
100
|
-
@browser.navigate.to("about:blank")
|
101
114
|
end
|
102
115
|
end
|
103
116
|
|
@@ -191,6 +204,23 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
191
204
|
browser.switch_to.window(handle) { yield }
|
192
205
|
end
|
193
206
|
|
207
|
+
def accept_modal(type, options={}, &blk)
|
208
|
+
yield if block_given?
|
209
|
+
modal = find_modal(options)
|
210
|
+
modal.send_keys options[:with] if options[:with]
|
211
|
+
message = modal.text
|
212
|
+
modal.accept
|
213
|
+
message
|
214
|
+
end
|
215
|
+
|
216
|
+
def dismiss_modal(type, options={}, &blk)
|
217
|
+
yield if block_given?
|
218
|
+
modal = find_modal(options)
|
219
|
+
message = modal.text
|
220
|
+
modal.dismiss
|
221
|
+
message
|
222
|
+
end
|
223
|
+
|
194
224
|
def quit
|
195
225
|
@browser.quit if @browser
|
196
226
|
rescue Errno::ECONNREFUSED
|
@@ -220,4 +250,22 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
220
250
|
result
|
221
251
|
end
|
222
252
|
end
|
253
|
+
|
254
|
+
def find_modal(options={})
|
255
|
+
# Selenium has its own built in wait (2 seconds)for a modal to show up, so this wait is really the minimum time
|
256
|
+
# Actual wait time may be longer than specified
|
257
|
+
wait = Selenium::WebDriver::Wait.new(
|
258
|
+
timeout: (options[:wait] || Capybara.default_wait_time),
|
259
|
+
ignore: Selenium::WebDriver::Error::NoAlertPresentError)
|
260
|
+
begin
|
261
|
+
modal = wait.until do
|
262
|
+
alert = @browser.switch_to.alert
|
263
|
+
regexp = options[:text].is_a?(Regexp) ? options[:text] : Regexp.escape(options[:text].to_s)
|
264
|
+
alert.text.match(regexp) ? alert : nil
|
265
|
+
end
|
266
|
+
rescue Selenium::WebDriver::Error::TimeOutError
|
267
|
+
raise Capybara::ModalNotFound.new("Unable to find modal dialog#{" with #{options[:text]}" if options[:text]}")
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
223
271
|
end
|
@@ -37,11 +37,13 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
37
37
|
path_names = value.to_s.empty? ? [] : value
|
38
38
|
native.send_keys(*path_names)
|
39
39
|
elsif tag_name == 'textarea' or tag_name == 'input'
|
40
|
-
if
|
40
|
+
if self[:readonly]
|
41
|
+
warn "Attempt to set readonly element with value: #{value} \n *This will raise an exception in a future version of Capybara"
|
42
|
+
elsif value.to_s.empty?
|
41
43
|
native.clear
|
42
44
|
else
|
43
45
|
#script can change a readonly element which user input cannot, so dont execute if readonly
|
44
|
-
driver.browser.execute_script "arguments[0].value = ''", native
|
46
|
+
driver.browser.execute_script "arguments[0].value = ''", native
|
45
47
|
native.send_keys(value.to_s)
|
46
48
|
end
|
47
49
|
elsif native.attribute('isContentEditable')
|
data/lib/capybara/session.rb
CHANGED
@@ -35,7 +35,11 @@ module Capybara
|
|
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
37
|
:has_no_unchecked_field?, :query, :assert_selector, :assert_no_selector,
|
38
|
-
:refute_selector
|
38
|
+
:refute_selector, :assert_text, :assert_no_text
|
39
|
+
]
|
40
|
+
# @api private
|
41
|
+
DOCUMENT_METHODS = [
|
42
|
+
:title, :assert_title, :assert_no_title, :has_title?, :has_no_title?
|
39
43
|
]
|
40
44
|
SESSION_METHODS = [
|
41
45
|
:body, :html, :source, :current_url, :current_host, :current_path,
|
@@ -44,9 +48,13 @@ module Capybara
|
|
44
48
|
:windows, :open_new_window, :switch_to_window, :within_window, :window_opened_by,
|
45
49
|
:save_page, :save_and_open_page, :save_screenshot,
|
46
50
|
:save_and_open_screenshot, :reset_session!, :response_headers,
|
47
|
-
:status_code, :
|
51
|
+
:status_code, :current_scope
|
52
|
+
] + DOCUMENT_METHODS
|
53
|
+
MODAL_METHODS = [
|
54
|
+
:accept_alert, :accept_confirm, :dismiss_confirm, :accept_prompt,
|
55
|
+
:dismiss_prompt
|
48
56
|
]
|
49
|
-
DSL_METHODS = NODE_METHODS + SESSION_METHODS
|
57
|
+
DSL_METHODS = NODE_METHODS + SESSION_METHODS + MODAL_METHODS
|
50
58
|
|
51
59
|
attr_reader :mode, :app, :server
|
52
60
|
attr_accessor :synchronized
|
@@ -78,7 +86,7 @@ module Capybara
|
|
78
86
|
#
|
79
87
|
# This method does not:
|
80
88
|
#
|
81
|
-
# * accept modal dialogs if they are present
|
89
|
+
# * accept modal dialogs if they are present (Selenium driver now does, others may not)
|
82
90
|
# * clear browser cache/HTML 5 local storage/IndexedDB/Web SQL database/etc.
|
83
91
|
# * modify state of the driver/underlying browser in any other way
|
84
92
|
#
|
@@ -167,14 +175,6 @@ module Capybara
|
|
167
175
|
driver.current_url
|
168
176
|
end
|
169
177
|
|
170
|
-
##
|
171
|
-
#
|
172
|
-
# @return [String] Title of the current page
|
173
|
-
#
|
174
|
-
def title
|
175
|
-
driver.title
|
176
|
-
end
|
177
|
-
|
178
178
|
##
|
179
179
|
#
|
180
180
|
# Navigate to the given URL. The URL can either be a relative URL or an absolute URL
|
@@ -206,12 +206,15 @@ module Capybara
|
|
206
206
|
|
207
207
|
@touched = true
|
208
208
|
|
209
|
-
|
209
|
+
url_relative = URI.parse(url.to_s).scheme.nil?
|
210
|
+
|
211
|
+
if url_relative && Capybara.app_host
|
210
212
|
url = Capybara.app_host + url.to_s
|
213
|
+
url_relative = false
|
211
214
|
end
|
212
215
|
|
213
216
|
if @server
|
214
|
-
url = "http://#{@server.host}:#{@server.port}" + url.to_s
|
217
|
+
url = "http://#{@server.host}:#{@server.port}" + url.to_s if url_relative
|
215
218
|
|
216
219
|
if Capybara.always_include_port
|
217
220
|
uri = URI.parse(url)
|
@@ -523,6 +526,93 @@ module Capybara
|
|
523
526
|
driver.evaluate_script(script)
|
524
527
|
end
|
525
528
|
|
529
|
+
##
|
530
|
+
#
|
531
|
+
# Execute the block, accepting a alert.
|
532
|
+
#
|
533
|
+
# @!macro modal_params
|
534
|
+
# @overload $0(text, options = {}, &blk)
|
535
|
+
# @param text [String, Regexp] Text or regex to match against the text in the modal. If not provided any modal is matched
|
536
|
+
# @overload $0(options = {}, &blk)
|
537
|
+
# @option options [Numeric] :wait How long to wait for the modal to appear after executing the block.
|
538
|
+
# @return [String] the message shown in the modal
|
539
|
+
# @raise [Capybara::ModalNotFound] if modal dialog hasn't been found
|
540
|
+
#
|
541
|
+
def accept_alert(text_or_options=nil, options={}, &blk)
|
542
|
+
if text_or_options.is_a? Hash
|
543
|
+
options=text_or_options
|
544
|
+
else
|
545
|
+
options[:text]=text_or_options
|
546
|
+
end
|
547
|
+
|
548
|
+
driver.accept_modal(:alert, options, &blk)
|
549
|
+
end
|
550
|
+
|
551
|
+
##
|
552
|
+
#
|
553
|
+
# Execute the block, accepting a confirm.
|
554
|
+
#
|
555
|
+
# @macro modal_params
|
556
|
+
#
|
557
|
+
def accept_confirm(text_or_options=nil, options={}, &blk)
|
558
|
+
if text_or_options.is_a? Hash
|
559
|
+
options=text_or_options
|
560
|
+
else
|
561
|
+
options[:text]=text_or_options
|
562
|
+
end
|
563
|
+
|
564
|
+
driver.accept_modal(:confirm, options, &blk)
|
565
|
+
end
|
566
|
+
|
567
|
+
##
|
568
|
+
#
|
569
|
+
# Execute the block, dismissing a confirm.
|
570
|
+
#
|
571
|
+
# @macro modal_params
|
572
|
+
#
|
573
|
+
def dismiss_confirm(text_or_options=nil, options={}, &blk)
|
574
|
+
if text_or_options.is_a? Hash
|
575
|
+
options=text_or_options
|
576
|
+
else
|
577
|
+
options[:text]=text_or_options
|
578
|
+
end
|
579
|
+
|
580
|
+
driver.dismiss_modal(:confirm, options, &blk)
|
581
|
+
end
|
582
|
+
|
583
|
+
##
|
584
|
+
#
|
585
|
+
# Execute the block, accepting a prompt, optionally responding to the prompt.
|
586
|
+
#
|
587
|
+
# @macro modal_params
|
588
|
+
# @option options [String] :with Response to provide to the prompt
|
589
|
+
#
|
590
|
+
def accept_prompt(text_or_options=nil, options={}, &blk)
|
591
|
+
if text_or_options.is_a? Hash
|
592
|
+
options=text_or_options
|
593
|
+
else
|
594
|
+
options[:text]=text_or_options
|
595
|
+
end
|
596
|
+
|
597
|
+
driver.accept_modal(:prompt, options, &blk)
|
598
|
+
end
|
599
|
+
|
600
|
+
##
|
601
|
+
#
|
602
|
+
# Execute the block, dismissing a prompt.
|
603
|
+
#
|
604
|
+
# @macro modal_params
|
605
|
+
#
|
606
|
+
def dismiss_prompt(text_or_options=nil, options={}, &blk)
|
607
|
+
if text_or_options.is_a? Hash
|
608
|
+
options=text_or_options
|
609
|
+
else
|
610
|
+
options[:text]=text_or_options
|
611
|
+
end
|
612
|
+
|
613
|
+
driver.dismiss_modal(:prompt, options, &blk)
|
614
|
+
end
|
615
|
+
|
526
616
|
##
|
527
617
|
#
|
528
618
|
# Save a snapshot of the page.
|
@@ -586,30 +676,14 @@ module Capybara
|
|
586
676
|
end
|
587
677
|
end
|
588
678
|
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
def has_title?(content)
|
594
|
-
document.synchronize do
|
595
|
-
unless title.match(Capybara::Helpers.to_regexp(content))
|
596
|
-
raise ExpectationNotMet
|
597
|
-
end
|
679
|
+
DOCUMENT_METHODS.each do |method|
|
680
|
+
define_method method do |*args, &block|
|
681
|
+
document.send(method, *args, &block)
|
598
682
|
end
|
599
|
-
return true
|
600
|
-
rescue Capybara::ExpectationNotMet
|
601
|
-
return false
|
602
683
|
end
|
603
684
|
|
604
|
-
def
|
605
|
-
|
606
|
-
if title.match(Capybara::Helpers.to_regexp(content))
|
607
|
-
raise ExpectationNotMet
|
608
|
-
end
|
609
|
-
end
|
610
|
-
return true
|
611
|
-
rescue Capybara::ExpectationNotMet
|
612
|
-
return false
|
685
|
+
def inspect
|
686
|
+
%(#<Capybara::Session>)
|
613
687
|
end
|
614
688
|
|
615
689
|
def current_scope
|
@@ -53,7 +53,7 @@ $(function() {
|
|
53
53
|
$('#reload-list').click(function() {
|
54
54
|
setTimeout(function() {
|
55
55
|
$('#the-list').html('<li>Foo</li><li>Bar</li>');
|
56
|
-
},
|
56
|
+
}, 550)
|
57
57
|
});
|
58
58
|
$('#change-title').click(function() {
|
59
59
|
setTimeout(function() {
|
@@ -67,4 +67,37 @@ $(function() {
|
|
67
67
|
e.preventDefault();
|
68
68
|
$(this).after('<a id="has-been-right-clicked" href="#">Has been right clicked</a>');
|
69
69
|
});
|
70
|
+
$('#open-alert').click(function() {
|
71
|
+
alert('Alert opened');
|
72
|
+
$(this).attr('opened', 'true');
|
73
|
+
});
|
74
|
+
$('#open-delayed-alert').click(function() {
|
75
|
+
var link = this;
|
76
|
+
setTimeout(function() {
|
77
|
+
alert('Delayed alert opened');
|
78
|
+
$(link).attr('opened', 'true');
|
79
|
+
}, 250);
|
80
|
+
});
|
81
|
+
$('#open-slow-alert').click(function() {
|
82
|
+
var link = this;
|
83
|
+
setTimeout(function() {
|
84
|
+
alert('Delayed alert opened');
|
85
|
+
$(link).attr('opened', 'true');
|
86
|
+
}, 3000);
|
87
|
+
});
|
88
|
+
$('#open-confirm').click(function() {
|
89
|
+
if(confirm('Confirm opened')) {
|
90
|
+
$(this).attr('confirmed', 'true');
|
91
|
+
} else {
|
92
|
+
$(this).attr('confirmed', 'false');
|
93
|
+
}
|
94
|
+
});
|
95
|
+
$('#open-prompt').click(function() {
|
96
|
+
var response = prompt('Prompt opened');
|
97
|
+
if(response === null) {
|
98
|
+
$(this).attr('response', 'dismissed');
|
99
|
+
} else {
|
100
|
+
$(this).attr('response', response);
|
101
|
+
}
|
102
|
+
});
|
70
103
|
});
|
@@ -0,0 +1,57 @@
|
|
1
|
+
Capybara::SpecHelper.spec '#accept_alert', :requires => [:modals] do
|
2
|
+
before do
|
3
|
+
@session.visit('/with_js')
|
4
|
+
end
|
5
|
+
|
6
|
+
it "should accept the alert" do
|
7
|
+
@session.accept_alert do
|
8
|
+
@session.click_link('Open alert')
|
9
|
+
end
|
10
|
+
expect(@session).to have_xpath("//a[@id='open-alert' and @opened='true']")
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should accept the alert if the text matches" do
|
14
|
+
@session.accept_alert 'Alert opened' do
|
15
|
+
@session.click_link('Open alert')
|
16
|
+
end
|
17
|
+
expect(@session).to have_xpath("//a[@id='open-alert' and @opened='true']")
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should not accept the alert if the text doesnt match" do
|
21
|
+
expect do
|
22
|
+
@session.accept_alert 'Incorrect Text' do
|
23
|
+
@session.click_link('Open alert')
|
24
|
+
end
|
25
|
+
end.to raise_error(Capybara::ModalNotFound)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should return the message presented" do
|
29
|
+
message = @session.accept_alert do
|
30
|
+
@session.click_link('Open alert')
|
31
|
+
end
|
32
|
+
expect(message).to eq('Alert opened')
|
33
|
+
end
|
34
|
+
|
35
|
+
context "with an asynchronous alert" do
|
36
|
+
it "should accept the alert" do
|
37
|
+
@session.accept_alert do
|
38
|
+
@session.click_link('Open delayed alert')
|
39
|
+
end
|
40
|
+
expect(@session).to have_xpath("//a[@id='open-delayed-alert' and @opened='true']")
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should return the message presented" do
|
44
|
+
message = @session.accept_alert do
|
45
|
+
@session.click_link('Open delayed alert')
|
46
|
+
end
|
47
|
+
expect(message).to eq('Delayed alert opened')
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should allow to adjust the delay" do
|
51
|
+
@session.accept_alert wait: 4 do
|
52
|
+
@session.click_link('Open slow alert')
|
53
|
+
end
|
54
|
+
expect(@session).to have_xpath("//a[@id='open-slow-alert' and @opened='true']")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
Capybara::SpecHelper.spec '#accept_confirm', :requires => [:modals] do
|
2
|
+
before do
|
3
|
+
@session.visit('/with_js')
|
4
|
+
end
|
5
|
+
|
6
|
+
it "should accept the confirm" do
|
7
|
+
@session.accept_confirm do
|
8
|
+
@session.click_link('Open confirm')
|
9
|
+
end
|
10
|
+
expect(@session).to have_xpath("//a[@id='open-confirm' and @confirmed='true']")
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should return the message presented" do
|
14
|
+
message = @session.accept_confirm do
|
15
|
+
@session.click_link('Open confirm')
|
16
|
+
end
|
17
|
+
expect(message).to eq('Confirm opened')
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
Capybara::SpecHelper.spec '#accept_prompt', :requires => [:modals] do
|
2
|
+
before do
|
3
|
+
@session.visit('/with_js')
|
4
|
+
end
|
5
|
+
|
6
|
+
it "should accept the prompt with no message" do
|
7
|
+
@session.accept_prompt do
|
8
|
+
@session.click_link('Open prompt')
|
9
|
+
end
|
10
|
+
expect(@session).to have_xpath("//a[@id='open-prompt' and @response='']")
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should return the message presented" do
|
14
|
+
message = @session.accept_prompt do
|
15
|
+
@session.click_link('Open prompt')
|
16
|
+
end
|
17
|
+
expect(message).to eq('Prompt opened')
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should accept the prompt with a response" do
|
21
|
+
@session.accept_prompt with: 'the response' do
|
22
|
+
@session.click_link('Open prompt')
|
23
|
+
end
|
24
|
+
expect(@session).to have_xpath("//a[@id='open-prompt' and @response='the response']")
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should accept the prompt if the message matches" do
|
28
|
+
@session.accept_prompt 'Prompt opened', with: 'matched' do
|
29
|
+
@session.click_link('Open prompt')
|
30
|
+
end
|
31
|
+
expect(@session).to have_xpath("//a[@id='open-prompt' and @response='matched']")
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should not accept the prompt if the message doesn't match" do
|
35
|
+
expect do
|
36
|
+
@session.accept_prompt 'Incorrect Text', with: 'not matched' do
|
37
|
+
@session.click_link('Open prompt')
|
38
|
+
end
|
39
|
+
end.to raise_error(Capybara::ModalNotFound)
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
it "should return the message presented" do
|
44
|
+
message = @session.accept_prompt with: 'the response' do
|
45
|
+
@session.click_link('Open prompt')
|
46
|
+
end
|
47
|
+
expect(message).to eq('Prompt opened')
|
48
|
+
end
|
49
|
+
end
|