capybara 0.4.0 → 0.4.1.rc

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 (46) hide show
  1. data/History.txt +35 -0
  2. data/README.rdoc +60 -19
  3. data/lib/capybara.rb +81 -5
  4. data/lib/capybara/driver/base.rb +1 -6
  5. data/lib/capybara/driver/celerity_driver.rb +19 -9
  6. data/lib/capybara/driver/node.rb +8 -0
  7. data/lib/capybara/driver/rack_test_driver.rb +42 -29
  8. data/lib/capybara/driver/selenium_driver.rb +11 -3
  9. data/lib/capybara/dsl.rb +11 -0
  10. data/lib/capybara/node/actions.rb +4 -14
  11. data/lib/capybara/node/base.rb +47 -0
  12. data/lib/capybara/node/document.rb +17 -0
  13. data/lib/capybara/node/element.rb +178 -0
  14. data/lib/capybara/node/finders.rb +78 -27
  15. data/lib/capybara/node/matchers.rb +40 -11
  16. data/lib/capybara/node/simple.rb +116 -0
  17. data/lib/capybara/rspec.rb +18 -0
  18. data/lib/capybara/server.rb +5 -10
  19. data/lib/capybara/session.rb +7 -16
  20. data/lib/capybara/spec/driver.rb +16 -1
  21. data/lib/capybara/spec/session.rb +1 -0
  22. data/lib/capybara/spec/session/all_spec.rb +5 -0
  23. data/lib/capybara/spec/session/attach_file_spec.rb +6 -0
  24. data/lib/capybara/spec/session/click_link_or_button_spec.rb +2 -3
  25. data/lib/capybara/spec/session/find_spec.rb +0 -6
  26. data/lib/capybara/spec/session/first_spec.rb +72 -0
  27. data/lib/capybara/spec/session/has_css_spec.rb +107 -1
  28. data/lib/capybara/spec/session/has_field_spec.rb +60 -0
  29. data/lib/capybara/spec/session/has_select_spec.rb +40 -0
  30. data/lib/capybara/spec/session/javascript.rb +4 -13
  31. data/lib/capybara/spec/session/within_spec.rb +10 -3
  32. data/lib/capybara/spec/test_app.rb +20 -1
  33. data/lib/capybara/spec/views/form.erb +27 -0
  34. data/lib/capybara/spec/views/with_html.erb +21 -5
  35. data/lib/capybara/util/save_and_open_page.rb +8 -5
  36. data/lib/capybara/version.rb +1 -1
  37. data/spec/basic_node_spec.rb +77 -0
  38. data/spec/capybara_spec.rb +18 -0
  39. data/spec/dsl_spec.rb +26 -0
  40. data/spec/rspec_spec.rb +47 -0
  41. data/spec/save_and_open_page_spec.rb +83 -7
  42. data/spec/server_spec.rb +20 -0
  43. data/spec/session/rack_test_session_spec.rb +10 -0
  44. data/spec/string_spec.rb +77 -0
  45. metadata +306 -295
  46. data/lib/capybara/node.rb +0 -221
@@ -1,5 +1,5 @@
1
1
  module Capybara
2
- class Node
2
+ module Node
3
3
  module Matchers
4
4
 
5
5
  ##
@@ -37,8 +37,17 @@ module Capybara
37
37
  wait_conditionally_until do
38
38
  results = all(*args)
39
39
 
40
- if options[:count]
41
- results.size == options[:count]
40
+ case
41
+ when results.empty?
42
+ false
43
+ when options[:between]
44
+ options[:between] === results.size
45
+ when options[:count]
46
+ options[:count] == results.size
47
+ when options[:maximum]
48
+ options[:maximum] >= results.size
49
+ when options[:minimum]
50
+ options[:minimum] <= results.size
42
51
  else
43
52
  results.size > 0
44
53
  end
@@ -60,8 +69,17 @@ module Capybara
60
69
  wait_conditionally_until do
61
70
  results = all(*args)
62
71
 
63
- if options[:count]
64
- results.size != options[:count]
72
+ case
73
+ when results.empty?
74
+ true
75
+ when options[:between]
76
+ not(options[:between] === results.size)
77
+ when options[:count]
78
+ not(options[:count] == results.size)
79
+ when options[:maximum]
80
+ not(options[:maximum] >= results.size)
81
+ when options[:minimum]
82
+ not(options[:minimum] <= results.size)
65
83
  else
66
84
  results.empty?
67
85
  end
@@ -242,7 +260,8 @@ module Capybara
242
260
  # @return [Boolean] Whether it exists
243
261
  #
244
262
  def has_field?(locator, options={})
245
- has_xpath?(XPath::HTML.field(locator, options))
263
+ options, with = split_options(options, :with)
264
+ has_xpath?(XPath::HTML.field(locator, options), with)
246
265
  end
247
266
 
248
267
  ##
@@ -255,7 +274,8 @@ module Capybara
255
274
  # @return [Boolean] Whether it doesn't exist
256
275
  #
257
276
  def has_no_field?(locator, options={})
258
- has_no_xpath?(XPath::HTML.field(locator, options))
277
+ options, with = split_options(options, :with)
278
+ has_no_xpath?(XPath::HTML.field(locator, options), with)
259
279
  end
260
280
 
261
281
  ##
@@ -268,7 +288,7 @@ module Capybara
268
288
  # @return [Boolean] Whether it exists
269
289
  #
270
290
  def has_checked_field?(locator)
271
- has_xpath?(XPath::HTML.field(locator, :checked => true))
291
+ has_xpath?(XPath::HTML.field(locator), :checked => true)
272
292
  end
273
293
 
274
294
  ##
@@ -281,7 +301,7 @@ module Capybara
281
301
  # @return [Boolean] Whether it exists
282
302
  #
283
303
  def has_unchecked_field?(locator)
284
- has_xpath?(XPath::HTML.field(locator, :unchecked => true))
304
+ has_xpath?(XPath::HTML.field(locator), :unchecked => true)
285
305
  end
286
306
 
287
307
  ##
@@ -308,7 +328,8 @@ module Capybara
308
328
  # @return [Boolean] Whether it exists
309
329
  #
310
330
  def has_select?(locator, options={})
311
- has_xpath?(XPath::HTML.select(locator, options))
331
+ options, selected = split_options(options, :selected)
332
+ has_xpath?(XPath::HTML.select(locator, options), selected)
312
333
  end
313
334
 
314
335
  ##
@@ -320,7 +341,8 @@ module Capybara
320
341
  # @return [Boolean] Whether it doesn't exist
321
342
  #
322
343
  def has_no_select?(locator, options={})
323
- has_no_xpath?(XPath::HTML.select(locator, options))
344
+ options, selected = split_options(options, :selected)
345
+ has_no_xpath?(XPath::HTML.select(locator, options), selected)
324
346
  end
325
347
 
326
348
  ##
@@ -355,6 +377,13 @@ module Capybara
355
377
  def has_no_table?(locator, options={})
356
378
  has_no_xpath?(XPath::HTML.table(locator, options))
357
379
  end
380
+
381
+ protected
382
+
383
+ def split_options(options, key)
384
+ options = options.dup
385
+ [options, if options.has_key?(key) then {key => options.delete(key)} else {} end]
386
+ end
358
387
  end
359
388
  end
360
389
  end
@@ -0,0 +1,116 @@
1
+ module Capybara
2
+ module Node
3
+
4
+ ##
5
+ #
6
+ # A {Capybara::Node::Simple} is a simpler version of {Capybara::Node::Base} which
7
+ # includes only {Capybara::Node::Finders} and {Capybara::Node::Matchers} and does
8
+ # not include {Capybara::Node::Actions}. This type of node is returned when
9
+ # using {Capybara.string}.
10
+ #
11
+ # It is useful in that it does not require a session, an application or a driver,
12
+ # but can still use Capybara's finders and matchers on any string that contains HTML.
13
+ #
14
+ class Simple
15
+ include Capybara::Node::Finders
16
+ include Capybara::Node::Matchers
17
+
18
+ attr_reader :native
19
+
20
+ def initialize(native)
21
+ native = Nokogiri::HTML(native) if native.is_a?(String)
22
+ @native = native
23
+ end
24
+
25
+ ##
26
+ #
27
+ # @return [String] The text of the element
28
+ #
29
+ def text
30
+ native.text
31
+ end
32
+
33
+ ##
34
+ #
35
+ # Retrieve the given attribute
36
+ #
37
+ # element[:title] # => HTML title attribute
38
+ #
39
+ # @param [Symbol] attribute The attribute to retrieve
40
+ # @return [String] The value of the attribute
41
+ #
42
+ def [](name)
43
+ attr_name = name.to_s
44
+ if attr_name == 'value'
45
+ value
46
+ elsif 'input' == tag_name and 'checkbox' == native[:type] and 'checked' == attr_name
47
+ native['checked'] == 'checked'
48
+ else
49
+ native[attr_name]
50
+ end
51
+ end
52
+
53
+ ##
54
+ #
55
+ # @return [String] The tag name of the element
56
+ #
57
+ def tag_name
58
+ native.node_name
59
+ end
60
+
61
+ ##
62
+ #
63
+ # An XPath expression describing where on the page the element can be found
64
+ #
65
+ # @return [String] An XPath expression
66
+ #
67
+ def path
68
+ native.path
69
+ end
70
+
71
+ ##
72
+ #
73
+ # @return [String] The value of the form element
74
+ #
75
+ def value
76
+ if tag_name == 'textarea'
77
+ native.content
78
+ elsif tag_name == 'select'
79
+ if native['multiple'] == 'multiple'
80
+ native.xpath(".//option[@selected='selected']").map { |option| option[:value] || option.content }
81
+ else
82
+ option = native.xpath(".//option[@selected='selected']").first || native.xpath(".//option").first
83
+ option[:value] || option.content if option
84
+ end
85
+ else
86
+ native[:value]
87
+ end
88
+ end
89
+
90
+ ##
91
+ #
92
+ # Whether or not the element is visible. Does not support CSS, so
93
+ # the result may be inaccurate.
94
+ #
95
+ # @return [Boolean] Whether the element is visible
96
+ #
97
+ def visible?
98
+ native.xpath("./ancestor-or-self::*[contains(@style, 'display:none') or contains(@style, 'display: none')]").size == 0
99
+ end
100
+
101
+ protected
102
+
103
+ def find_in_base(xpath)
104
+ native.xpath(xpath).map { |node| self.class.new(node) }
105
+ end
106
+
107
+ def convert_element(element)
108
+ element
109
+ end
110
+
111
+ def wait?
112
+ false
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,18 @@
1
+ require 'capybara'
2
+ require 'capybara/dsl'
3
+
4
+ RSpec.configure do |config|
5
+ config.include Capybara, :type => :acceptance
6
+ config.after do
7
+ if example.metadata[:type] == :acceptance
8
+ Capybara.reset_sessions!
9
+ Capybara.use_default_driver
10
+ end
11
+ end
12
+ config.before do
13
+ if example.metadata[:type] == :acceptance
14
+ Capybara.current_driver = Capybara.javascript_driver if example.metadata[:js]
15
+ Capybara.current_driver = example.metadata[:driver] if example.metadata[:driver]
16
+ end
17
+ end
18
+ end
@@ -62,20 +62,15 @@ module Capybara
62
62
  Capybara::Server.ports[@app.object_id] = @port
63
63
 
64
64
  Thread.new do
65
- begin
66
- require 'rack/handler/thin'
67
- Thin::Logging.silent = true
68
- Rack::Handler::Thin.run(Identify.new(@app), :Port => @port)
69
- rescue LoadError
70
- require 'rack/handler/webrick'
71
- Rack::Handler::WEBrick.run(Identify.new(@app), :Port => @port, :AccessLog => [], :Logger => WEBrick::Log::new(nil, 0))
72
- end
65
+ Capybara.server.call(Identify.new(@app), @port)
73
66
  end
74
67
 
75
- Capybara.timeout(10) { if responsive? then true else sleep(0.5) and false end }
68
+ Capybara.timeout(Capybara.server_boot_timeout) do
69
+ if responsive? then true else sleep(0.5) and false end
70
+ end
76
71
  end
77
72
  end
78
- rescue Timeout::Error
73
+ rescue TimeoutError
79
74
  puts "Rack application timed out during boot"
80
75
  exit
81
76
  else
@@ -27,12 +27,12 @@ module Capybara
27
27
  #
28
28
  class Session
29
29
  DSL_METHODS = [
30
- :all, :attach_file, :body, :check, :choose, :click_link_or_button, :click_button, :click_link, :current_url, :drag, :evaluate_script,
30
+ :all, :first, :attach_file, :body, :check, :choose, :click_link_or_button, :click_button, :click_link, :current_url, :drag, :evaluate_script,
31
31
  :field_labeled, :fill_in, :find, :find_button, :find_by_id, :find_field, :find_link, :has_content?, :has_css?,
32
32
  :has_no_content?, :has_no_css?, :has_no_xpath?, :has_xpath?, :locate, :save_and_open_page, :select, :source, :uncheck,
33
33
  :visit, :wait_until, :within, :within_fieldset, :within_table, :within_frame, :within_window, :has_link?, :has_no_link?, :has_button?,
34
34
  :has_no_button?, :has_field?, :has_no_field?, :has_checked_field?, :has_unchecked_field?, :has_no_table?, :has_table?,
35
- :unselect, :has_select?, :has_no_select?, :current_path, :click, :has_selector?, :has_no_selector?
35
+ :unselect, :has_select?, :has_no_select?, :current_path, :click, :has_selector?, :has_no_selector?, :click_on
36
36
  ]
37
37
 
38
38
  attr_reader :mode, :app
@@ -151,11 +151,11 @@ module Capybara
151
151
  # fill_in('Street', :with => '12 Main Street')
152
152
  # end
153
153
  #
154
- # @param [:css, :xpath, String] kind The type of selector or the selector if the second argument is blank
155
- # @param [String] selector The selector within which to execute the given block
154
+ # @param (see Capybara::Node::Finders#all)
155
+ # @raise [Capybara::ElementNotFound] If the scope can't be found before time expires
156
156
  #
157
- def within(kind, selector=nil)
158
- new_scope = find(kind, selector, :message => "scope '#{selector || kind}' not found on page")
157
+ def within(*args)
158
+ new_scope = find(*args)
159
159
  begin
160
160
  scopes.push(new_scope)
161
161
  yield
@@ -247,15 +247,6 @@ module Capybara
247
247
  driver.evaluate_script(script)
248
248
  end
249
249
 
250
- ##
251
- #
252
- # @deprecated click is deprecated, please use {Capybara::Node::Actions#click_link_or_button} instead
253
- #
254
- def click(locator)
255
- Capybara.deprecate("click", "click_link_or_button")
256
- current_node.click_link_or_button(locator)
257
- end
258
-
259
250
  ##
260
251
  #
261
252
  # Save a snapshot of the page and open it in a browser for inspection
@@ -266,7 +257,7 @@ module Capybara
266
257
  end
267
258
 
268
259
  def document
269
- Capybara::Document.new(self, driver)
260
+ Capybara::Node::Document.new(self, driver)
270
261
  end
271
262
 
272
263
  def method_missing(*args)
@@ -42,6 +42,7 @@ shared_examples_for 'driver' do
42
42
  it "should extract node attributes" do
43
43
  @driver.find('//a')[0][:class].should == 'simple'
44
44
  @driver.find('//a')[1][:id].should == 'foo'
45
+ @driver.find('//input')[0][:type].should == 'text'
45
46
  end
46
47
 
47
48
  it "should extract boolean node attributes" do
@@ -70,6 +71,20 @@ shared_examples_for 'driver' do
70
71
  @driver.find('//div[@id="hidden"]')[0].should_not be_visible
71
72
  @driver.find('//div[@id="hidden_via_ancestor"]')[0].should_not be_visible
72
73
  end
74
+
75
+ it "should extract node checked state" do
76
+ @driver.visit('/form')
77
+ @driver.find('//input[@id="gender_female"]')[0].should be_checked
78
+ @driver.find('//input[@id="gender_male"]')[0].should_not be_checked
79
+ @driver.find('//h1')[0].should_not be_checked
80
+ end
81
+
82
+ it "should extract node selected state" do
83
+ @driver.visit('/form')
84
+ @driver.find('//option[@value="en"]')[0].should be_selected
85
+ @driver.find('//option[@value="sv"]')[0].should_not be_selected
86
+ @driver.find('//h1')[0].should_not be_selected
87
+ end
73
88
  end
74
89
  end
75
90
  end
@@ -103,7 +118,7 @@ end
103
118
  shared_examples_for "driver with header support" do
104
119
  it "should make headers available through response_headers" do
105
120
  @driver.visit('/with_simple_html')
106
- @driver.response_headers['Content-Type'].should == 'text/html'
121
+ @driver.response_headers['Content-Type'].should =~ /text\/html/
107
122
  end
108
123
  end
109
124
 
@@ -42,6 +42,7 @@ shared_examples_for "session" do
42
42
  end
43
43
 
44
44
  it_should_behave_like "all"
45
+ it_should_behave_like "first"
45
46
  it_should_behave_like "attach_file"
46
47
  it_should_behave_like "check"
47
48
  it_should_behave_like "choose"
@@ -56,6 +56,11 @@ shared_examples_for "all" do
56
56
  Capybara.ignore_hidden_elements = true
57
57
  @session.all("//a[@title='awesome title']").should have(1).elements
58
58
  end
59
+
60
+ it "should only find invisible nodes" do
61
+ Capybara.ignore_hidden_elements = true
62
+ @session.all("//a[@title='awesome title']", :visible => false).should have(2).elements
63
+ end
59
64
  end
60
65
 
61
66
  context "within a scope" do
@@ -53,6 +53,12 @@ shared_examples_for "attach_file" do
53
53
  @session.click_button 'Upload'
54
54
  @session.body.should include('image/jpeg')
55
55
  end
56
+
57
+ it "should not break when using HTML5 multiple file input" do
58
+ @session.attach_file "Multiple Documents", @test_file_path
59
+ @session.click_button('Upload Multiple')
60
+ @session.body.should include(File.read(@test_file_path))
61
+ end
56
62
  end
57
63
 
58
64
  context "with a locator that doesn't exist" do
@@ -18,10 +18,9 @@ shared_examples_for "click_link_or_button" do
18
18
  extract_results(@session)['first_name'].should == 'John'
19
19
  end
20
20
 
21
- it "should be aliased as click for backward compatibility" do
22
- Capybara.should_receive(:deprecate).with("click", "click_link_or_button")
21
+ it "should be aliased as click_on" do
23
22
  @session.visit('/form')
24
- @session.click('awe123')
23
+ @session.click_on('awe123')
25
24
  extract_results(@session)['first_name'].should == 'John'
26
25
  end
27
26
 
@@ -13,12 +13,6 @@ shared_examples_for "find" do
13
13
  @session.find("//input[@id='test_field']")[:value].should == 'monkey'
14
14
  end
15
15
 
16
- it "should be aliased as locate for backward compatibility" do
17
- Capybara.should_receive(:deprecate).with("locate", "find").twice
18
- @session.locate('//h1').text.should == 'This is a test'
19
- @session.locate("//input[@id='test_field']")[:value].should == 'monkey'
20
- end
21
-
22
16
  it "should find the first element using the given locator and options" do
23
17
  @session.find('//a', :text => 'Redirect')[:id].should == 'red'
24
18
  @session.find(:css, 'a', :text => 'A link')[:title].should == 'twas a fine link'
@@ -0,0 +1,72 @@
1
+ shared_examples_for "first" do
2
+ describe '#first' do
3
+ before do
4
+ @session.visit('/with_html')
5
+ end
6
+
7
+ it "should find the first element using the given locator" do
8
+ @session.first('//h1').text.should == 'This is a test'
9
+ @session.first("//input[@id='test_field']")[:value].should == 'monkey'
10
+ end
11
+
12
+ it "should return nil when nothing was found" do
13
+ @session.first('//div[@id="nosuchthing"]').should be_nil
14
+ end
15
+
16
+ it "should accept an XPath instance" do
17
+ @session.visit('/form')
18
+ @xpath = XPath::HTML.fillable_field('Name')
19
+ @session.first(@xpath).value.should == 'John Smith'
20
+ end
21
+
22
+ context "with css selectors" do
23
+ it "should find the first element using the given selector" do
24
+ @session.first(:css, 'h1').text.should == 'This is a test'
25
+ @session.first(:css, "input[id='test_field']")[:value].should == 'monkey'
26
+ end
27
+ end
28
+
29
+ context "with xpath selectors" do
30
+ it "should find the first element using the given locator" do
31
+ @session.first(:xpath, '//h1').text.should == 'This is a test'
32
+ @session.first(:xpath, "//input[@id='test_field']")[:value].should == 'monkey'
33
+ end
34
+ end
35
+
36
+ context "with css as default selector" do
37
+ before { Capybara.default_selector = :css }
38
+ it "should find the first element using the given locator" do
39
+ @session.first('h1').text.should == 'This is a test'
40
+ @session.first("input[id='test_field']")[:value].should == 'monkey'
41
+ end
42
+ after { Capybara.default_selector = :xpath }
43
+ end
44
+
45
+ context "with visible filter" do
46
+ after { Capybara.ignore_hidden_elements = false }
47
+ it "should only find visible nodes" do
48
+ @session.first(:css, "a.visibility").should_not be_visible
49
+ @session.first(:css, "a.visibility", :visible => true).should be_visible
50
+ Capybara.ignore_hidden_elements = true
51
+ @session.first(:css, "a.visibility").should be_visible
52
+ end
53
+
54
+ it "should only find invisible nodes" do
55
+ Capybara.ignore_hidden_elements = true
56
+ @session.first(:css, "a.visibility", :visible => false).should_not be_visible
57
+ end
58
+ end
59
+
60
+ context "within a scope" do
61
+ before do
62
+ @session.visit('/with_scope')
63
+ end
64
+
65
+ it "should find the first element using the given locator" do
66
+ @session.within(:xpath, "//div[@id='for_bar']") do
67
+ @session.first('.//form').should_not be_nil
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end