capybara 2.7.1 → 2.8.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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +22 -0
  3. data/README.md +27 -3
  4. data/lib/capybara.rb +19 -4
  5. data/lib/capybara/driver/base.rb +6 -2
  6. data/lib/capybara/driver/node.rb +13 -5
  7. data/lib/capybara/helpers.rb +2 -2
  8. data/lib/capybara/node/actions.rb +116 -17
  9. data/lib/capybara/node/base.rb +7 -1
  10. data/lib/capybara/node/element.rb +23 -3
  11. data/lib/capybara/node/finders.rb +45 -29
  12. data/lib/capybara/node/matchers.rb +13 -15
  13. data/lib/capybara/queries/selector_query.rb +22 -5
  14. data/lib/capybara/queries/text_query.rb +42 -6
  15. data/lib/capybara/rack_test/node.rb +13 -1
  16. data/lib/capybara/result.rb +80 -8
  17. data/lib/capybara/rspec/features.rb +13 -6
  18. data/lib/capybara/selector.rb +98 -71
  19. data/lib/capybara/selector/filter_set.rb +46 -0
  20. data/lib/capybara/selenium/driver.rb +22 -23
  21. data/lib/capybara/selenium/node.rb +14 -6
  22. data/lib/capybara/server.rb +20 -10
  23. data/lib/capybara/session.rb +44 -8
  24. data/lib/capybara/spec/session/all_spec.rb +4 -4
  25. data/lib/capybara/spec/session/assert_text.rb +23 -0
  26. data/lib/capybara/spec/session/check_spec.rb +66 -8
  27. data/lib/capybara/spec/session/choose_spec.rb +20 -0
  28. data/lib/capybara/spec/session/click_button_spec.rb +0 -3
  29. data/lib/capybara/spec/session/click_link_spec.rb +7 -0
  30. data/lib/capybara/spec/session/execute_script_spec.rb +6 -1
  31. data/lib/capybara/spec/session/find_button_spec.rb +19 -1
  32. data/lib/capybara/spec/session/find_field_spec.rb +21 -1
  33. data/lib/capybara/spec/session/find_link_spec.rb +19 -1
  34. data/lib/capybara/spec/session/find_spec.rb +32 -4
  35. data/lib/capybara/spec/session/first_spec.rb +4 -4
  36. data/lib/capybara/spec/session/has_field_spec.rb +4 -0
  37. data/lib/capybara/spec/session/has_text_spec.rb +2 -2
  38. data/lib/capybara/spec/session/node_spec.rb +24 -3
  39. data/lib/capybara/spec/session/reset_session_spec.rb +7 -0
  40. data/lib/capybara/spec/session/selectors_spec.rb +14 -0
  41. data/lib/capybara/spec/session/uncheck_spec.rb +39 -0
  42. data/lib/capybara/spec/session/window/switch_to_window_spec.rb +4 -2
  43. data/lib/capybara/spec/session/window/window_opened_by_spec.rb +2 -1
  44. data/lib/capybara/spec/session/window/window_spec.rb +36 -22
  45. data/lib/capybara/spec/session/within_frame_spec.rb +19 -0
  46. data/lib/capybara/spec/spec_helper.rb +3 -0
  47. data/lib/capybara/spec/views/form.erb +34 -6
  48. data/lib/capybara/spec/views/with_html.erb +5 -1
  49. data/lib/capybara/spec/views/with_unload_alert.erb +3 -1
  50. data/lib/capybara/spec/views/with_windows.erb +2 -0
  51. data/lib/capybara/version.rb +1 -1
  52. data/spec/capybara_spec.rb +34 -0
  53. data/spec/rack_test_spec.rb +24 -0
  54. data/spec/result_spec.rb +25 -0
  55. data/spec/rspec/features_spec.rb +3 -3
  56. data/spec/selenium_spec.rb +6 -3
  57. data/spec/server_spec.rb +2 -2
  58. metadata +18 -4
@@ -17,7 +17,7 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
17
17
  end
18
18
 
19
19
  def value
20
- if tag_name == "select" and self[:multiple] and not self[:multiple] == "false"
20
+ if tag_name == "select" and multiple?
21
21
  native.find_elements(:xpath, ".//option").select { |n| n.selected? }.map { |n| n[:value] || n.text }
22
22
  else
23
23
  native[:value]
@@ -38,7 +38,7 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
38
38
  def set(value, options={})
39
39
  tag_name = self.tag_name
40
40
  type = self[:type]
41
- if (Array === value) && !self[:multiple]
41
+ if (Array === value) && !multiple?
42
42
  raise ArgumentError.new "Value cannot be an Array when 'multiple' attribute is not present. Not a #{value.class}"
43
43
  end
44
44
  if tag_name == 'input' and type == 'radio'
@@ -49,7 +49,7 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
49
49
  path_names = value.to_s.empty? ? [] : value
50
50
  native.send_keys(*path_names)
51
51
  elsif tag_name == 'textarea' or tag_name == 'input'
52
- if self[:readonly]
52
+ if readonly?
53
53
  warn "Attempt to set readonly element with value: #{value} \n *This will raise an exception in a future version of Capybara"
54
54
  elsif value.to_s.empty?
55
55
  native.clear
@@ -84,7 +84,7 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
84
84
  end
85
85
 
86
86
  def select_option
87
- native.click unless selected?
87
+ native.click unless selected? || disabled?
88
88
  end
89
89
 
90
90
  def unselect_option
@@ -131,12 +131,21 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
131
131
  selected = native.selected?
132
132
  selected and selected != "false"
133
133
  end
134
+ alias :checked? :selected?
134
135
 
135
136
  def disabled?
136
137
  !native.enabled?
137
138
  end
138
139
 
139
- alias :checked? :selected?
140
+ def readonly?
141
+ readonly = self[:readonly]
142
+ readonly and readonly != "false"
143
+ end
144
+
145
+ def multiple?
146
+ multiple = self[:multiple]
147
+ multiple and multiple != "false"
148
+ end
140
149
 
141
150
  def find_xpath(locator)
142
151
  native.find_elements(:xpath, locator).map { |n| self.class.new(driver, n) }
@@ -175,7 +184,6 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
175
184
  end
176
185
 
177
186
  private
178
-
179
187
  # a reference to the select node if this is an option node
180
188
  def select_node
181
189
  find_xpath('./ancestor::select').first
@@ -61,45 +61,44 @@ module Capybara
61
61
 
62
62
  def initialize(app, port=Capybara.server_port, host=Capybara.server_host)
63
63
  @app = app
64
- @middleware = Middleware.new(@app)
65
64
  @server_thread = nil # suppress warnings
66
65
  @host, @port = host, port
67
- @port ||= Capybara::Server.ports[Capybara.reuse_server ? @app.object_id : @middleware.object_id]
66
+ @port ||= Capybara::Server.ports[port_key]
68
67
  @port ||= find_available_port(host)
69
68
  end
70
69
 
71
70
  def reset_error!
72
- @middleware.error = nil
71
+ middleware.error = nil
73
72
  end
74
73
 
75
74
  def error
76
- @middleware.error
75
+ middleware.error
77
76
  end
78
77
 
79
78
  def responsive?
80
79
  return false if @server_thread && @server_thread.join(0)
81
80
 
82
- res = Net::HTTP.start(host, @port) { |http| http.get('/__identify__') }
81
+ res = Net::HTTP.start(host, port) { |http| http.get('/__identify__') }
83
82
 
84
83
  if res.is_a?(Net::HTTPSuccess) or res.is_a?(Net::HTTPRedirection)
85
- return res.body == @app.object_id.to_s
84
+ return res.body == app.object_id.to_s
86
85
  end
87
86
  rescue SystemCallError
88
87
  return false
89
88
  end
90
89
 
91
90
  def wait_for_pending_requests
92
- Timeout.timeout(60) { sleep(0.01) while @middleware.pending_requests? }
91
+ Timeout.timeout(60) { sleep(0.01) while pending_requests? }
93
92
  rescue Timeout::Error
94
93
  raise "Requests did not finish in 60 seconds"
95
94
  end
96
95
 
97
96
  def boot
98
97
  unless responsive?
99
- Capybara::Server.ports[Capybara.reuse_server ? @app.object_id : @middleware.object_id] = @port
98
+ Capybara::Server.ports[port_key] = port
100
99
 
101
100
  @server_thread = Thread.new do
102
- Capybara.server.call(@middleware, @port, @host)
101
+ Capybara.server.call(middleware, port, host)
103
102
  end
104
103
 
105
104
  Timeout.timeout(60) { @server_thread.join(0.1) until responsive? }
@@ -112,12 +111,23 @@ module Capybara
112
111
 
113
112
  private
114
113
 
114
+ def middleware
115
+ @middleware ||= Middleware.new(app)
116
+ end
117
+
118
+ def port_key
119
+ Capybara.reuse_server ? app.object_id : middleware.object_id
120
+ end
121
+
122
+ def pending_requests?
123
+ middleware.pending_requests?
124
+ end
125
+
115
126
  def find_available_port(host)
116
127
  server = TCPServer.new(host, 0)
117
128
  server.addr[1]
118
129
  ensure
119
130
  server.close if server
120
131
  end
121
-
122
132
  end
123
133
  end
@@ -120,7 +120,14 @@ module Capybara
120
120
  # Raise errors encountered in the server
121
121
  #
122
122
  def raise_server_error!
123
- raise @server.error if Capybara.raise_server_errors and @server and @server.error
123
+ if Capybara.raise_server_errors and @server and @server.error
124
+ # Force an explanation for the error being raised as the exception cause
125
+ begin
126
+ raise CapybaraError, "Your application server raised an error - It has been raised in your test code because Capybara.raise_server_errors == true"
127
+ rescue CapybaraError
128
+ raise @server.error
129
+ end
130
+ end
124
131
  ensure
125
132
  @server.reset_error! if @server
126
133
  end
@@ -320,18 +327,47 @@ module Capybara
320
327
 
321
328
  ##
322
329
  #
323
- # Execute the given block within the given iframe using given frame name or index.
324
- # May be supported by not all drivers. Drivers that support it, may provide additional options.
330
+ # Execute the given block within the given iframe using given frame, frame name/id or index.
331
+ # May not be supported by all drivers.
325
332
  #
333
+ # @overload within_frame(element)
334
+ # @param [Capybara::Node::Element] frame element
335
+ # @overload within_frame(name)
336
+ # @param [String] name name/id of a frame
326
337
  # @overload within_frame(index)
327
338
  # @param [Integer] index index of a frame
328
- # @overload within_frame(name)
329
- # @param [String] name name of a frame
330
339
  #
331
- def within_frame(frame_handle)
340
+ def within_frame(locator)
332
341
  scopes.push(nil)
333
- driver.within_frame(frame_handle) do
334
- yield
342
+
343
+ #support older driver frame api for now
344
+ frame = case locator
345
+ when Capybara::Node::Element
346
+ locator
347
+ when String
348
+ find(:frame, locator)
349
+ when Integer
350
+ all(:frame, minimum: locator+1)[locator]
351
+ else
352
+ raise ArgumentError
353
+ end
354
+
355
+ begin
356
+ driver.switch_to_frame(frame)
357
+ begin
358
+ yield
359
+ ensure
360
+ driver.switch_to_frame(:parent)
361
+ end
362
+ rescue Capybara::NotSupportedByDriverError
363
+ # Support older driver frame API for now
364
+ if driver.respond_to?(:within_frame)
365
+ driver.within_frame(frame) do
366
+ yield
367
+ end
368
+ else
369
+ raise
370
+ end
335
371
  end
336
372
  ensure
337
373
  scopes.pop
@@ -7,7 +7,7 @@ Capybara::SpecHelper.spec "#all" do
7
7
  it "should find all elements using the given locator" do
8
8
  expect(@session.all('//p').size).to eq(3)
9
9
  expect(@session.all('//h1').first.text).to eq('This is a test')
10
- expect(@session.all("//input[@id='test_field']").first[:value]).to eq('monkey')
10
+ expect(@session.all("//input[@id='test_field']").first.value).to eq('monkey')
11
11
  end
12
12
 
13
13
  it "should return an empty array when nothing was found" do
@@ -28,7 +28,7 @@ Capybara::SpecHelper.spec "#all" do
28
28
  context "with css selectors" do
29
29
  it "should find all elements using the given selector" do
30
30
  expect(@session.all(:css, 'h1').first.text).to eq('This is a test')
31
- expect(@session.all(:css, "input[id='test_field']").first[:value]).to eq('monkey')
31
+ expect(@session.all(:css, "input[id='test_field']").first.value).to eq('monkey')
32
32
  end
33
33
 
34
34
  it "should find all elements when given a list of selectors" do
@@ -39,7 +39,7 @@ Capybara::SpecHelper.spec "#all" do
39
39
  context "with xpath selectors" do
40
40
  it "should find the first element using the given locator" do
41
41
  expect(@session.all(:xpath, '//h1').first.text).to eq('This is a test')
42
- expect(@session.all(:xpath, "//input[@id='test_field']").first[:value]).to eq('monkey')
42
+ expect(@session.all(:xpath, "//input[@id='test_field']").first.value).to eq('monkey')
43
43
  end
44
44
  end
45
45
 
@@ -47,7 +47,7 @@ Capybara::SpecHelper.spec "#all" do
47
47
  before { Capybara.default_selector = :css }
48
48
  it "should find the first element using the given locator" do
49
49
  expect(@session.all('h1').first.text).to eq('This is a test')
50
- expect(@session.all("input[id='test_field']").first[:value]).to eq('monkey')
50
+ expect(@session.all("input[id='test_field']").first.value).to eq('monkey')
51
51
  end
52
52
  end
53
53
 
@@ -37,6 +37,21 @@ Capybara::SpecHelper.spec '#assert_text' do
37
37
  expect(@session.assert_text('Some of this text is hidden!')).to eq(true)
38
38
  end
39
39
 
40
+ it "should raise error with a helpful message if the requested text is present but invisible" do
41
+ @session.visit('/with_html')
42
+ el = @session.find(:css, '#hidden-text')
43
+ expect do
44
+ el.assert_text(:visible, 'Some of this text is hidden!')
45
+ end.to raise_error(Capybara::ExpectationNotMet, /it was found 1 time including non-visible text/)
46
+ end
47
+
48
+ it "should raise error with a helpful message if the requested text is present but with incorrect case" do
49
+ @session.visit('/with_html')
50
+ expect do
51
+ @session.assert_text('Text With Whitespace')
52
+ end.to raise_error(Capybara::ExpectationNotMet, /it was found 1 time using a case insensitive search/)
53
+ end
54
+
40
55
  it "should be true if the text in the page matches given regexp" do
41
56
  @session.visit('/with_html')
42
57
  expect(@session.assert_text(/Lorem/)).to eq(true)
@@ -166,6 +181,14 @@ Capybara::SpecHelper.spec '#assert_no_text' do
166
181
  end.to raise_error(Capybara::ExpectationNotMet, 'expected not to find text "Some of this text is hidden!" in "Some of this text is hidden!"')
167
182
  end
168
183
 
184
+ it "should raise error if :all given and text is invisible." do
185
+ @session.visit('/with_html')
186
+ el = @session.find(:css, '#some-hidden-text', visible: false)
187
+ expect do
188
+ el.assert_no_text(:visible, 'hidden')
189
+ end.to raise_error(Capybara::ExpectationNotMet, 'expected not to find text "hidden" in "Some of this text is not hidden"')
190
+ end
191
+
169
192
  it "should be true if the text in the page doesn't match given regexp" do
170
193
  @session.visit('/with_html')
171
194
  @session.assert_no_text(/xxxxyzzz/)
@@ -23,29 +23,29 @@ Capybara::SpecHelper.spec "#check" do
23
23
 
24
24
  describe "checking" do
25
25
  it "should not change an already checked checkbox" do
26
- expect(@session.find(:xpath, "//input[@id='form_pets_dog']")['checked']).to be_truthy
26
+ expect(@session.find(:xpath, "//input[@id='form_pets_dog']")).to be_checked
27
27
  @session.check('form_pets_dog')
28
- expect(@session.find(:xpath, "//input[@id='form_pets_dog']")['checked']).to be_truthy
28
+ expect(@session.find(:xpath, "//input[@id='form_pets_dog']")).to be_checked
29
29
  end
30
30
 
31
31
  it "should check an unchecked checkbox" do
32
- expect(@session.find(:xpath, "//input[@id='form_pets_cat']")['checked']).to be_falsey
32
+ expect(@session.find(:xpath, "//input[@id='form_pets_cat']")).not_to be_checked
33
33
  @session.check('form_pets_cat')
34
- expect(@session.find(:xpath, "//input[@id='form_pets_cat']")['checked']).to be_truthy
34
+ expect(@session.find(:xpath, "//input[@id='form_pets_cat']")).to be_checked
35
35
  end
36
36
  end
37
37
 
38
38
  describe "unchecking" do
39
39
  it "should not change an already unchecked checkbox" do
40
- expect(@session.find(:xpath, "//input[@id='form_pets_cat']")['checked']).to be_falsey
40
+ expect(@session.find(:xpath, "//input[@id='form_pets_cat']")).not_to be_checked
41
41
  @session.uncheck('form_pets_cat')
42
- expect(@session.find(:xpath, "//input[@id='form_pets_cat']")['checked']).to be_falsey
42
+ expect(@session.find(:xpath, "//input[@id='form_pets_cat']")).not_to be_checked
43
43
  end
44
44
 
45
45
  it "should uncheck a checked checkbox" do
46
- expect(@session.find(:xpath, "//input[@id='form_pets_dog']")['checked']).to be_truthy
46
+ expect(@session.find(:xpath, "//input[@id='form_pets_dog']")).to be_checked
47
47
  @session.uncheck('form_pets_dog')
48
- expect(@session.find(:xpath, "//input[@id='form_pets_dog']")['checked']).to be_falsey
48
+ expect(@session.find(:xpath, "//input[@id='form_pets_dog']")).not_to be_checked
49
49
  end
50
50
  end
51
51
 
@@ -111,4 +111,62 @@ Capybara::SpecHelper.spec "#check" do
111
111
  end.to raise_error(Capybara::ElementNotFound)
112
112
  end
113
113
  end
114
+
115
+ context "when checkbox hidden" do
116
+ context "with Capybara.automatic_label_click == true" do
117
+ around do |spec|
118
+ old_click_label, Capybara.automatic_label_click = Capybara.automatic_label_click, true
119
+ spec.run
120
+ Capybara.automatic_label_click = old_click_label
121
+ end
122
+
123
+ it "should check via clicking the label with :for attribute if possible" do
124
+ expect(@session.find(:checkbox, 'form_cars_tesla', unchecked: true, visible: :hidden)).to be
125
+ @session.check('form_cars_tesla')
126
+ @session.click_button('awesome')
127
+ expect(extract_results(@session)['cars']).to include('tesla')
128
+ end
129
+
130
+ it "should check via clicking the wrapping label if possible" do
131
+ expect(@session.find(:checkbox, 'form_cars_mclaren', unchecked: true, visible: :hidden)).to be
132
+ @session.check('form_cars_mclaren')
133
+ @session.click_button('awesome')
134
+ expect(extract_results(@session)['cars']).to include('mclaren')
135
+ end
136
+
137
+ it "should not click the label if unneeded" do
138
+ expect(@session.find(:checkbox, 'form_cars_jaguar', checked: true, visible: :hidden)).to be
139
+ @session.check('form_cars_jaguar')
140
+ @session.click_button('awesome')
141
+ expect(extract_results(@session)['cars']).to include('jaguar')
142
+ end
143
+
144
+ it "should raise original error when no label available" do
145
+ expect { @session.check('form_cars_ariel') }.to raise_error(Capybara::ElementNotFound, 'Unable to find checkbox "form_cars_ariel"')
146
+ end
147
+
148
+ it "should raise error if not allowed to click label" do
149
+ expect{@session.check('form_cars_mclaren', allow_label_click: false)}.to raise_error(Capybara::ElementNotFound, 'Unable to find checkbox "form_cars_mclaren"')
150
+ end
151
+ end
152
+
153
+ context "with Capybara.automatic_label_click == false" do
154
+ around do |spec|
155
+ old_label_click, Capybara.automatic_label_click = Capybara.automatic_label_click, false
156
+ spec.run
157
+ Capybara.automatic_label_click = old_label_click
158
+ end
159
+
160
+ it "should raise error if checkbox not visible" do
161
+ expect{@session.check('form_cars_mclaren')}.to raise_error(Capybara::ElementNotFound, 'Unable to find checkbox "form_cars_mclaren"')
162
+ end
163
+
164
+ it "should check via the label if allow_label_click == true" do
165
+ expect(@session.find(:checkbox, 'form_cars_tesla', unchecked: true, visible: :hidden)).to be
166
+ @session.check('form_cars_tesla', allow_label_click: true)
167
+ @session.click_button('awesome')
168
+ expect(extract_results(@session)['cars']).to include('tesla')
169
+ end
170
+ end
171
+ end
114
172
  end
@@ -66,4 +66,24 @@ Capybara::SpecHelper.spec "#choose" do
66
66
  end.to raise_error(Capybara::ElementNotFound)
67
67
  end
68
68
  end
69
+
70
+ context "with hidden radio buttons" do
71
+ context "with Capybara.automatic_label_click == true" do
72
+ around do |spec|
73
+ old_click_label, Capybara.automatic_label_click = Capybara.automatic_label_click, true
74
+ spec.run
75
+ Capybara.automatic_label_click = old_click_label
76
+ end
77
+
78
+ it "should select by clicking the link if available" do
79
+ @session.choose("party_democrat")
80
+ @session.click_button('awesome')
81
+ expect(extract_results(@session)['party']).to eq('democrat')
82
+ end
83
+
84
+ it "should raise error if not allowed to click label" do
85
+ expect{@session.choose("party_democrat", allow_label_click: false)}.to raise_error(Capybara::ElementNotFound, 'Unable to find radio button "party_democrat"')
86
+ end
87
+ end
88
+ end
69
89
  end
@@ -413,12 +413,9 @@ Capybara::SpecHelper.spec '#click_button' do
413
413
  @session.visit('/form')
414
414
  @session.fill_in('address1_city', :with =>'Paris')
415
415
  @session.fill_in('address1_street', :with =>'CDG')
416
- @session.fill_in('address1_street', :with =>'CDG')
417
- @session.select("France", :from => 'address1_country')
418
416
 
419
417
  @session.fill_in('address2_city', :with => 'Mikolaiv')
420
418
  @session.fill_in('address2_street', :with => 'PGS')
421
- @session.select("Ukraine", :from => 'address2_country')
422
419
 
423
420
  @session.click_button "awesome"
424
421
 
@@ -179,4 +179,11 @@ Capybara::SpecHelper.spec '#click_link' do
179
179
  end.to raise_error(Capybara::ElementNotFound)
180
180
  end
181
181
  end
182
+
183
+ context "without locator" do
184
+ it "uses options" do
185
+ @session.click_link(href: '/foo')
186
+ expect(@session).to have_content('Another World')
187
+ end
188
+ end
182
189
  end
@@ -2,7 +2,12 @@
2
2
  Capybara::SpecHelper.spec "#execute_script", :requires => [:js] do
3
3
  it "should execute the given script and return nothing" do
4
4
  @session.visit('/with_js')
5
- expect(@session.execute_script("$('#change').text('Funky Doodle')")).to be_nil
5
+ expect(@session.execute_script("document.getElementById('change').textContent = 'Funky Doodle'")).to be_nil
6
6
  expect(@session).to have_css('#change', :text => 'Funky Doodle')
7
7
  end
8
+
9
+ it "should be able to call functions defined in the page" do
10
+ @session.visit('/with_js')
11
+ expect{ @session.execute_script("$('#change').text('Funky Doodle')") }.not_to raise_error
12
+ end
8
13
  end