capybara 0.4.0 → 0.4.1.rc

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