capybara 2.3.0 → 2.4.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 +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
|