capybara 2.12.1 → 2.13.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 +12 -0
- data/README.md +31 -9
- data/lib/capybara/minitest.rb +297 -0
- data/lib/capybara/minitest/spec.rb +200 -0
- data/lib/capybara/queries/selector_query.rb +2 -2
- data/lib/capybara/rack_test/node.rb +10 -6
- data/lib/capybara/selenium/driver.rb +15 -1
- data/lib/capybara/selenium/node.rb +13 -4
- data/lib/capybara/session.rb +43 -23
- data/lib/capybara/spec/session/accept_prompt_spec.rb +0 -3
- data/lib/capybara/spec/session/evaluate_script_spec.rb +7 -0
- data/lib/capybara/spec/session/node_spec.rb +25 -1
- data/lib/capybara/spec/session/window/within_window_spec.rb +2 -2
- data/lib/capybara/spec/views/with_js.erb +2 -1
- data/lib/capybara/version.rb +1 -1
- data/spec/minitest_spec.rb +122 -0
- data/spec/minitest_spec_spec.rb +121 -0
- data/spec/shared_selenium_session.rb +28 -0
- metadata +26 -8
@@ -57,7 +57,7 @@ module Capybara
|
|
57
57
|
options[:text]
|
58
58
|
else
|
59
59
|
if exact_text == true
|
60
|
-
|
60
|
+
/\A#{Regexp.escape(options[:text].to_s)}\z/
|
61
61
|
else
|
62
62
|
Regexp.escape(options[:text].to_s)
|
63
63
|
end
|
@@ -68,7 +68,7 @@ module Capybara
|
|
68
68
|
end
|
69
69
|
|
70
70
|
if exact_text.is_a?(String)
|
71
|
-
regexp =
|
71
|
+
regexp = /\A#{Regexp.escape(options[:exact_text])}\z/
|
72
72
|
text_visible = visible
|
73
73
|
text_visible = :all if text_visible == :hidden
|
74
74
|
return false if not node.text(text_visible).match(regexp)
|
@@ -17,6 +17,12 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def set(value)
|
20
|
+
return if disabled?
|
21
|
+
if readonly?
|
22
|
+
warn "Attempt to set readonly element with value: #{value} \n * This will raise an exception in a future version of Capybara"
|
23
|
+
return
|
24
|
+
end
|
25
|
+
|
20
26
|
if (Array === value) && !multiple?
|
21
27
|
raise TypeError.new "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}"
|
22
28
|
end
|
@@ -28,11 +34,7 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
|
|
28
34
|
elsif input_field?
|
29
35
|
set_input(value)
|
30
36
|
elsif textarea?
|
31
|
-
|
32
|
-
warn "Attempt to set readonly element with value: #{value} \n * This will raise an exception in a future version of Capybara"
|
33
|
-
else
|
34
|
-
native['_capybara_raw_value'] = value.to_s
|
35
|
-
end
|
37
|
+
native['_capybara_raw_value'] = value.to_s
|
36
38
|
end
|
37
39
|
end
|
38
40
|
|
@@ -60,6 +62,8 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
|
|
60
62
|
((tag_name == 'button') and type.nil? or type == "submit")
|
61
63
|
associated_form = form
|
62
64
|
Capybara::RackTest::Form.new(driver, associated_form).submit(self) if associated_form
|
65
|
+
elsif (tag_name == 'input' and %w(checkbox radio).include?(type))
|
66
|
+
set(!checked?)
|
63
67
|
elsif (tag_name == 'label')
|
64
68
|
labelled_control = if native[:for]
|
65
69
|
find_xpath("//input[@id='#{native[:for]}']").first
|
@@ -181,7 +185,7 @@ private
|
|
181
185
|
end
|
182
186
|
native.remove
|
183
187
|
else
|
184
|
-
if
|
188
|
+
if readonly?
|
185
189
|
warn "Attempt to set readonly element with value: #{value} \n *This will raise an exception in a future version of Capybara"
|
186
190
|
else
|
187
191
|
native['value'] = value.to_s
|
@@ -91,7 +91,8 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
91
91
|
end
|
92
92
|
|
93
93
|
def evaluate_script(script, *args)
|
94
|
-
|
94
|
+
result = execute_script("return #{script}", *args)
|
95
|
+
unwrap_script_result(result)
|
95
96
|
end
|
96
97
|
|
97
98
|
def save_screenshot(path, _options={})
|
@@ -325,4 +326,17 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
|
|
325
326
|
def silenced_unknown_error_messages
|
326
327
|
[ /Error communicating with the remote browser/ ]
|
327
328
|
end
|
329
|
+
|
330
|
+
def unwrap_script_result(arg)
|
331
|
+
case arg
|
332
|
+
when Array
|
333
|
+
arg.map { |e| unwrap_script_result(e) }
|
334
|
+
when Hash
|
335
|
+
arg.each { |k, v| arg[k] = unwrap_script_result(v) }
|
336
|
+
when Selenium::WebDriver::Element
|
337
|
+
Capybara::Selenium::Node.new(self, arg)
|
338
|
+
else
|
339
|
+
arg
|
340
|
+
end
|
341
|
+
end
|
328
342
|
end
|
@@ -6,7 +6,7 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def all_text
|
9
|
-
text = driver.
|
9
|
+
text = driver.execute_script("return arguments[0].textContent", self)
|
10
10
|
Capybara::Helpers.normalize_whitespace(text)
|
11
11
|
end
|
12
12
|
|
@@ -70,20 +70,29 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
|
70
70
|
# Clear field by JavaScript assignment of the value property.
|
71
71
|
# Script can change a readonly element which user input cannot, so
|
72
72
|
# don't execute if readonly.
|
73
|
-
driver.
|
73
|
+
driver.execute_script "arguments[0].value = ''", self
|
74
74
|
native.send_keys(value.to_s)
|
75
75
|
end
|
76
76
|
end
|
77
77
|
elsif native.attribute('isContentEditable')
|
78
78
|
#ensure we are focused on the element
|
79
|
+
native.click
|
79
80
|
script = <<-JS
|
80
81
|
var range = document.createRange();
|
81
82
|
arguments[0].focus();
|
82
83
|
range.selectNodeContents(arguments[0]);
|
83
84
|
window.getSelection().addRange(range);
|
84
85
|
JS
|
85
|
-
driver.
|
86
|
-
|
86
|
+
driver.execute_script script, self
|
87
|
+
if (driver.options[:browser].to_s == "chrome") ||
|
88
|
+
(driver.options[:browser].to_s == "firefox" && !driver.marionette?)
|
89
|
+
# chromedriver raises a can't focus element if we use native.send_keys
|
90
|
+
# we've already focused it so just use action api
|
91
|
+
driver.browser.action.send_keys(value.to_s).perform
|
92
|
+
else
|
93
|
+
# action api is really slow here just use native.send_keys
|
94
|
+
native.send_keys(value.to_s)
|
95
|
+
end
|
87
96
|
end
|
88
97
|
end
|
89
98
|
|
data/lib/capybara/session.rb
CHANGED
@@ -123,17 +123,19 @@ module Capybara
|
|
123
123
|
# Raise errors encountered in the server
|
124
124
|
#
|
125
125
|
def raise_server_error!
|
126
|
-
if
|
126
|
+
if @server and @server.error
|
127
127
|
# Force an explanation for the error being raised as the exception cause
|
128
128
|
begin
|
129
|
-
|
129
|
+
if Capybara.raise_server_errors
|
130
|
+
raise CapybaraError, "Your application server raised an error - It has been raised in your test code because Capybara.raise_server_errors == true"
|
131
|
+
end
|
130
132
|
rescue CapybaraError
|
131
133
|
#needed to get the cause set correctly in JRuby -- otherwise we could just do raise @server.error
|
132
134
|
raise @server.error, @server.error.message, @server.error.backtrace
|
135
|
+
ensure
|
136
|
+
@server.reset_error!
|
133
137
|
end
|
134
138
|
end
|
135
|
-
ensure
|
136
|
-
@server.reset_error! if @server
|
137
139
|
end
|
138
140
|
|
139
141
|
##
|
@@ -387,21 +389,7 @@ module Capybara
|
|
387
389
|
# @overload within_frame(index)
|
388
390
|
# @param [Integer] index index of a frame (0 based)
|
389
391
|
def within_frame(*args)
|
390
|
-
frame =
|
391
|
-
case args[0]
|
392
|
-
when Capybara::Node::Element
|
393
|
-
args[0]
|
394
|
-
when String, Hash
|
395
|
-
find(:frame, *args)
|
396
|
-
when Symbol
|
397
|
-
find(*args)
|
398
|
-
when Integer
|
399
|
-
idx = args[0]
|
400
|
-
all(:frame, minimum: idx+1)[idx]
|
401
|
-
else
|
402
|
-
raise TypeError
|
403
|
-
end
|
404
|
-
end
|
392
|
+
frame = _find_frame(*args)
|
405
393
|
|
406
394
|
begin
|
407
395
|
switch_to_frame(frame)
|
@@ -612,10 +600,10 @@ module Capybara
|
|
612
600
|
#
|
613
601
|
def execute_script(script, *args)
|
614
602
|
@touched = true
|
615
|
-
if
|
616
|
-
raise Capybara::NotSupportedByDriverError, "The current driver does not support arguments being passed with execute_script" unless args.empty?
|
603
|
+
if args.empty?
|
617
604
|
driver.execute_script(script)
|
618
605
|
else
|
606
|
+
raise Capybara::NotSupportedByDriverError, "The current driver does not support execute_script arguments" if driver.method(:execute_script).arity == 1
|
619
607
|
driver.execute_script(script, *args.map { |arg| arg.is_a?(Capybara::Node::Element) ? arg.base : arg} )
|
620
608
|
end
|
621
609
|
end
|
@@ -631,12 +619,13 @@ module Capybara
|
|
631
619
|
#
|
632
620
|
def evaluate_script(script, *args)
|
633
621
|
@touched = true
|
634
|
-
if
|
635
|
-
raise Capybara::NotSupportedByDriverError, "The current driver does not support arguments being passed with execute_script" unless args.empty?
|
622
|
+
result = if args.empty?
|
636
623
|
driver.evaluate_script(script)
|
637
624
|
else
|
625
|
+
raise Capybara::NotSupportedByDriverError, "The current driver does not support evaluate_script arguments" if driver.method(:evaluate_script).arity == 1
|
638
626
|
driver.evaluate_script(script, *args.map { |arg| arg.is_a?(Capybara::Node::Element) ? arg.base : arg} )
|
639
627
|
end
|
628
|
+
element_script_result(result)
|
640
629
|
end
|
641
630
|
|
642
631
|
##
|
@@ -842,5 +831,36 @@ module Capybara
|
|
842
831
|
def scopes
|
843
832
|
@scopes ||= [nil]
|
844
833
|
end
|
834
|
+
|
835
|
+
def element_script_result(arg)
|
836
|
+
case arg
|
837
|
+
when Array
|
838
|
+
arg.map { |e| element_script_result(e) }
|
839
|
+
when Hash
|
840
|
+
arg.each { |k, v| arg[k] = element_script_result(v) }
|
841
|
+
when Capybara::Driver::Node
|
842
|
+
Capybara::Node::Element.new(self, arg, nil, nil)
|
843
|
+
else
|
844
|
+
arg
|
845
|
+
end
|
846
|
+
end
|
847
|
+
|
848
|
+
def _find_frame(*args)
|
849
|
+
within(document) do # Previous 2.x versions ignored current scope when finding frames - consider changing in 3.0
|
850
|
+
case args[0]
|
851
|
+
when Capybara::Node::Element
|
852
|
+
args[0]
|
853
|
+
when String, Hash
|
854
|
+
find(:frame, *args)
|
855
|
+
when Symbol
|
856
|
+
find(*args)
|
857
|
+
when Integer
|
858
|
+
idx = args[0]
|
859
|
+
all(:frame, minimum: idx+1)[idx]
|
860
|
+
else
|
861
|
+
raise TypeError
|
862
|
+
end
|
863
|
+
end
|
864
|
+
end
|
845
865
|
end
|
846
866
|
end
|
@@ -19,7 +19,6 @@ Capybara::SpecHelper.spec '#accept_prompt', requires: [:modals] do
|
|
19
19
|
end
|
20
20
|
|
21
21
|
it "should accept the prompt with a response" do
|
22
|
-
pending "selenium-webdriver/geckodriver doesn't support sending a response to prompts" if marionette?(@session)
|
23
22
|
@session.accept_prompt with: 'the response' do
|
24
23
|
@session.click_link('Open prompt')
|
25
24
|
end
|
@@ -27,7 +26,6 @@ Capybara::SpecHelper.spec '#accept_prompt', requires: [:modals] do
|
|
27
26
|
end
|
28
27
|
|
29
28
|
it "should accept the prompt if the message matches" do
|
30
|
-
pending "selenium-webdriver/geckodriver doesn't support sending a response to prompts" if marionette?(@session)
|
31
29
|
@session.accept_prompt 'Prompt opened', with: 'matched' do
|
32
30
|
@session.click_link('Open prompt')
|
33
31
|
end
|
@@ -44,7 +42,6 @@ Capybara::SpecHelper.spec '#accept_prompt', requires: [:modals] do
|
|
44
42
|
|
45
43
|
|
46
44
|
it "should return the message presented" do
|
47
|
-
pending "selenium-webdriver/geckodriver doesn't support sending a response to prompts" if marionette?(@session)
|
48
45
|
message = @session.accept_prompt with: 'the response' do
|
49
46
|
@session.click_link('Open prompt')
|
50
47
|
end
|
@@ -18,4 +18,11 @@ Capybara::SpecHelper.spec "#evaluate_script", requires: [:js] do
|
|
18
18
|
expect(@session).to have_css('#change', text: 'Doodle Funk')
|
19
19
|
end
|
20
20
|
|
21
|
+
it "should support returning elements", requires: [:js, :es_args] do
|
22
|
+
@session.visit('/with_js')
|
23
|
+
el = @session.find(:css, '#change')
|
24
|
+
el = @session.evaluate_script("document.getElementById('change')")
|
25
|
+
expect(el).to be_instance_of(Capybara::Node::Element)
|
26
|
+
expect(el).to eq(@session.find(:css, '#change'))
|
27
|
+
end
|
21
28
|
end
|
@@ -135,10 +135,10 @@ Capybara::SpecHelper.spec "node" do
|
|
135
135
|
end
|
136
136
|
|
137
137
|
it 'should allow me to change the contents of a contenteditable elements child', requires: [:js] do
|
138
|
-
pending "Selenium doesn't like editing nested contents" if @session.respond_to?(:mode) && @session.mode.to_s =~ /^selenium_/
|
139
138
|
@session.visit('/with_js')
|
140
139
|
@session.find(:css,'#existing_content_editable_child').set('WYSIWYG')
|
141
140
|
expect(@session.find(:css,'#existing_content_editable_child').text).to eq('WYSIWYG')
|
141
|
+
expect(@session.find(:css,'#existing_content_editable_child_parent').text).to eq('Some content WYSIWYG')
|
142
142
|
end
|
143
143
|
end
|
144
144
|
|
@@ -313,6 +313,30 @@ Capybara::SpecHelper.spec "node" do
|
|
313
313
|
@session.find(:css, '#link_placeholder').click
|
314
314
|
expect(@session.current_url).to match(%r{/with_html$})
|
315
315
|
end
|
316
|
+
|
317
|
+
it "should be able to check a checkbox" do
|
318
|
+
@session.visit('form')
|
319
|
+
cbox = @session.find(:checkbox, 'form_terms_of_use')
|
320
|
+
expect(cbox).not_to be_checked
|
321
|
+
cbox.click
|
322
|
+
expect(cbox).to be_checked
|
323
|
+
end
|
324
|
+
|
325
|
+
it "should be able to uncheck a checkbox" do
|
326
|
+
@session.visit('/form')
|
327
|
+
cbox = @session.find(:checkbox, 'form_pets_dog')
|
328
|
+
expect(cbox).to be_checked
|
329
|
+
cbox.click
|
330
|
+
expect(cbox).not_to be_checked
|
331
|
+
end
|
332
|
+
|
333
|
+
it "should be able to select a radio button" do
|
334
|
+
@session.visit('/form')
|
335
|
+
radio = @session.find(:radio_button, 'gender_male')
|
336
|
+
expect(radio).not_to be_checked
|
337
|
+
radio.click
|
338
|
+
expect(radio).to be_checked
|
339
|
+
end
|
316
340
|
end
|
317
341
|
|
318
342
|
describe '#double_click', requires: [:js] do
|
@@ -30,7 +30,7 @@ Capybara::SpecHelper.spec '#within_window', requires: [:windows] do
|
|
30
30
|
window = (@session.windows - [@window]).first
|
31
31
|
expect(@session.driver).to receive(:switch_to_window).exactly(5).times.and_call_original
|
32
32
|
@session.within_window window do
|
33
|
-
expect(
|
33
|
+
expect(@session).to have_title(/Title of the first popup|Title of popup two/)
|
34
34
|
end
|
35
35
|
expect(@session.title).to eq('With Windows')
|
36
36
|
end
|
@@ -140,7 +140,7 @@ Capybara::SpecHelper.spec '#within_window', requires: [:windows] do
|
|
140
140
|
it "should find window by handle" do
|
141
141
|
window = (@session.windows - [@window]).first
|
142
142
|
@session.within_window window.handle do
|
143
|
-
expect(
|
143
|
+
expect(@session).to have_title(/Title of the first popup|Title of popup two/)
|
144
144
|
end
|
145
145
|
end
|
146
146
|
|
@@ -40,7 +40,8 @@
|
|
40
40
|
<p>
|
41
41
|
<div contenteditable='true' id='existing_content_editable'>Editable content</div>
|
42
42
|
<div contenteditable='true' id='blank_content_editable' style='height: 1em;'></div>
|
43
|
-
<div contenteditable='true' style='height: 1em;'>
|
43
|
+
<div contenteditable='true' id='existing_content_editable_child_parent' style='height: 1em;'>
|
44
|
+
Some content
|
44
45
|
<div id='existing_content_editable_child' style='height: 1em;'>Content</div>
|
45
46
|
</div>
|
46
47
|
</p>
|
data/lib/capybara/version.rb
CHANGED
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'capybara/minitest'
|
4
|
+
|
5
|
+
class MinitestTest < Minitest::Test
|
6
|
+
include Capybara::DSL
|
7
|
+
include Capybara::Minitest::Assertions
|
8
|
+
|
9
|
+
def setup
|
10
|
+
visit('/form')
|
11
|
+
end
|
12
|
+
|
13
|
+
def teardown
|
14
|
+
Capybara.reset_sessions!
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_assert_text
|
18
|
+
assert_text('Form')
|
19
|
+
assert_no_text('Not on the page')
|
20
|
+
refute_text('Also Not on the page')
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_assert_title
|
24
|
+
visit('/with_title')
|
25
|
+
assert_title('Test Title')
|
26
|
+
assert_no_title('Not the title')
|
27
|
+
refute_title('Not the title')
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_assert_current_path
|
31
|
+
assert_current_path('/form')
|
32
|
+
assert_no_current_path('/not_form')
|
33
|
+
refute_current_path('/not_form')
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_assert_xpath
|
37
|
+
assert_xpath('.//select[@id="form_title"]')
|
38
|
+
assert_xpath('.//select', count: 1) { |el| el[:id] == "form_title" }
|
39
|
+
assert_no_xpath('.//select[@id="not_form_title"]')
|
40
|
+
assert_no_xpath('.//select') { |el| el[:id] == "not_form_title"}
|
41
|
+
refute_xpath('.//select[@id="not_form_title"]')
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_assert_css
|
45
|
+
assert_css('select#form_title')
|
46
|
+
assert_no_css('select#not_form_title')
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_assert_link
|
50
|
+
visit('/with_html')
|
51
|
+
assert_link('A link')
|
52
|
+
assert_link(count: 1){ |el| el.text == 'A link'}
|
53
|
+
assert_no_link('Not on page')
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_assert_button
|
57
|
+
assert_button('fresh_btn')
|
58
|
+
assert_button(count: 1){ |el| el[:id] == 'fresh_btn' }
|
59
|
+
assert_no_button('not_btn')
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_assert_field
|
63
|
+
assert_field('customer_email')
|
64
|
+
assert_no_field('not_on_the_form')
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_assert_select
|
68
|
+
assert_select('form_title')
|
69
|
+
assert_no_select('not_form_title')
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_assert_checked_field
|
73
|
+
assert_checked_field('form_pets_dog')
|
74
|
+
assert_no_checked_field('form_pets_cat')
|
75
|
+
refute_checked_field('form_pets_snake')
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_assert_unchecked_field
|
79
|
+
assert_unchecked_field('form_pets_cat')
|
80
|
+
assert_no_unchecked_field('form_pets_dog')
|
81
|
+
refute_unchecked_field('form_pets_snake')
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_assert_table
|
85
|
+
visit('/tables')
|
86
|
+
assert_table('agent_table')
|
87
|
+
assert_no_table('not_on_form')
|
88
|
+
refute_table('not_on_form')
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_assert_matches_selector
|
92
|
+
assert_matches_selector(find(:field, 'customer_email'), :field, 'customer_email')
|
93
|
+
assert_not_matches_selector(find(:select, 'form_title'), :field, 'customer_email')
|
94
|
+
refute_matches_selector(find(:select, 'form_title'), :field, 'customer_email')
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_assert_matches_css
|
98
|
+
assert_matches_css(find(:select, 'form_title'), 'select#form_title')
|
99
|
+
refute_matches_css(find(:select, 'form_title'), 'select#form_other_title')
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_assert_matches_xpath
|
103
|
+
assert_matches_xpath(find(:select, 'form_title'), './/select[@id="form_title"]')
|
104
|
+
refute_matches_xpath(find(:select, 'form_title'), './/select[@id="form_other_title"]')
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
RSpec.describe 'capybara/minitest' do
|
109
|
+
before do
|
110
|
+
Capybara.current_driver = :rack_test
|
111
|
+
Capybara.app = TestApp
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should support minitest" do
|
115
|
+
output = StringIO.new
|
116
|
+
reporter = Minitest::SummaryReporter.new(output)
|
117
|
+
reporter.start
|
118
|
+
MinitestTest.run reporter, {}
|
119
|
+
reporter.report
|
120
|
+
expect(output.string).to include("15 runs, 42 assertions, 0 failures, 0 errors, 0 skips")
|
121
|
+
end
|
122
|
+
end
|