capybara 2.12.1 → 2.13.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -57,7 +57,7 @@ module Capybara
57
57
  options[:text]
58
58
  else
59
59
  if exact_text == true
60
- "\\A#{Regexp.escape(options[:text].to_s)}\\z"
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 = "\\A#{Regexp.escape(options[:exact_text])}\\z"
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
- if self[:readonly]
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 self[:readonly]
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
- browser.execute_script("return #{script}", *args.map { |arg| arg.is_a?(Capybara::Selenium::Node) ? arg.native : arg} )
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.browser.execute_script("return arguments[0].textContent", native)
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.browser.execute_script "arguments[0].value = ''", native
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.browser.execute_script script, native
86
- native.send_keys(value.to_s)
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
 
@@ -123,17 +123,19 @@ module Capybara
123
123
  # Raise errors encountered in the server
124
124
  #
125
125
  def raise_server_error!
126
- if Capybara.raise_server_errors and @server and @server.error
126
+ if @server and @server.error
127
127
  # Force an explanation for the error being raised as the exception cause
128
128
  begin
129
- raise CapybaraError, "Your application server raised an error - It has been raised in your test code because Capybara.raise_server_errors == true"
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 = within(document) do # Previous 2.x versions ignored current scope when finding frames - consider changing in 3.0
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 driver.method(:execute_script).arity == 1
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 driver.method(:evaluate_script).arity == 1
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(['Title of the first popup', 'Title of popup two']).to include(@session.title)
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(['Title of the first popup', 'Title of popup two']).to include(@session.title)
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>
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Capybara
3
- VERSION = '2.12.1'
3
+ VERSION = '2.13.0'
4
4
  end
@@ -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