capybara 2.7.1 → 2.8.0

Sign up to get free protection for your applications and to get access to all the features.
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