capybara 1.1.4 → 2.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. data/History.txt +100 -0
  2. data/License.txt +22 -0
  3. data/README.md +829 -0
  4. data/lib/capybara.rb +124 -6
  5. data/lib/capybara/cucumber.rb +2 -5
  6. data/lib/capybara/driver/base.rb +5 -5
  7. data/lib/capybara/driver/node.rb +2 -2
  8. data/lib/capybara/dsl.rb +3 -121
  9. data/lib/capybara/node/actions.rb +12 -28
  10. data/lib/capybara/node/base.rb +5 -13
  11. data/lib/capybara/node/element.rb +21 -21
  12. data/lib/capybara/node/finders.rb +27 -89
  13. data/lib/capybara/node/matchers.rb +107 -69
  14. data/lib/capybara/node/simple.rb +11 -13
  15. data/lib/capybara/query.rb +78 -0
  16. data/lib/capybara/rack_test/browser.rb +16 -27
  17. data/lib/capybara/rack_test/driver.rb +11 -1
  18. data/lib/capybara/rack_test/node.rb +17 -1
  19. data/lib/capybara/result.rb +84 -0
  20. data/lib/capybara/rspec/matchers.rb +28 -63
  21. data/lib/capybara/selector.rb +97 -33
  22. data/lib/capybara/selenium/driver.rb +14 -61
  23. data/lib/capybara/selenium/node.rb +6 -15
  24. data/lib/capybara/server.rb +32 -27
  25. data/lib/capybara/session.rb +54 -30
  26. data/lib/capybara/spec/public/jquery-ui.js +791 -0
  27. data/lib/capybara/spec/public/jquery.js +9046 -0
  28. data/lib/capybara/spec/public/test.js +4 -1
  29. data/lib/capybara/spec/session.rb +56 -27
  30. data/lib/capybara/spec/session/all_spec.rb +8 -4
  31. data/lib/capybara/spec/session/attach_file_spec.rb +12 -9
  32. data/lib/capybara/spec/session/check_spec.rb +6 -3
  33. data/lib/capybara/spec/session/choose_spec.rb +4 -1
  34. data/lib/capybara/spec/session/click_button_spec.rb +5 -14
  35. data/lib/capybara/spec/session/click_link_or_button_spec.rb +2 -1
  36. data/lib/capybara/spec/session/click_link_spec.rb +3 -17
  37. data/lib/capybara/spec/session/current_url_spec.rb +77 -9
  38. data/lib/capybara/spec/session/fill_in_spec.rb +8 -18
  39. data/lib/capybara/spec/session/find_spec.rb +19 -46
  40. data/lib/capybara/spec/session/first_spec.rb +2 -34
  41. data/lib/capybara/spec/session/has_css_spec.rb +1 -1
  42. data/lib/capybara/spec/session/has_field_spec.rb +28 -0
  43. data/lib/capybara/spec/session/has_select_spec.rb +84 -31
  44. data/lib/capybara/spec/session/has_table_spec.rb +7 -69
  45. data/lib/capybara/spec/session/has_text_spec.rb +168 -0
  46. data/lib/capybara/spec/session/javascript.rb +65 -81
  47. data/lib/capybara/spec/session/node_spec.rb +115 -0
  48. data/lib/capybara/spec/session/screenshot.rb +29 -0
  49. data/lib/capybara/spec/session/select_spec.rb +12 -12
  50. data/lib/capybara/spec/session/text_spec.rb +9 -4
  51. data/lib/capybara/spec/session/unselect_spec.rb +12 -6
  52. data/lib/capybara/spec/session/visit_spec.rb +76 -0
  53. data/lib/capybara/spec/session/within_frame_spec.rb +33 -0
  54. data/lib/capybara/spec/session/within_spec.rb +47 -58
  55. data/lib/capybara/spec/session/within_window_spec.rb +40 -0
  56. data/lib/capybara/spec/test_app.rb +27 -3
  57. data/lib/capybara/spec/views/form.erb +11 -10
  58. data/lib/capybara/spec/views/host_links.erb +2 -2
  59. data/lib/capybara/spec/views/tables.erb +6 -66
  60. data/lib/capybara/spec/views/with_html.erb +3 -3
  61. data/lib/capybara/spec/views/with_js.erb +11 -8
  62. data/lib/capybara/util/save_and_open_page.rb +4 -3
  63. data/lib/capybara/version.rb +1 -1
  64. data/spec/basic_node_spec.rb +15 -3
  65. data/spec/dsl_spec.rb +12 -10
  66. data/spec/rack_test_spec.rb +152 -0
  67. data/spec/rspec/features_spec.rb +0 -2
  68. data/spec/rspec/matchers_spec.rb +164 -89
  69. data/spec/rspec_spec.rb +0 -2
  70. data/spec/selenium_spec.rb +67 -0
  71. data/spec/server_spec.rb +35 -23
  72. data/spec/spec_helper.rb +18 -2
  73. metadata +30 -30
  74. data/README.rdoc +0 -722
  75. data/lib/capybara/spec/driver.rb +0 -301
  76. data/lib/capybara/spec/session/current_host_spec.rb +0 -68
  77. data/lib/capybara/spec/session/has_content_spec.rb +0 -106
  78. data/lib/capybara/util/timeout.rb +0 -27
  79. data/spec/driver/rack_test_driver_spec.rb +0 -89
  80. data/spec/driver/selenium_driver_spec.rb +0 -37
  81. data/spec/session/rack_test_session_spec.rb +0 -55
  82. data/spec/session/selenium_session_spec.rb +0 -26
  83. data/spec/string_spec.rb +0 -77
  84. data/spec/timeout_spec.rb +0 -28
@@ -95,7 +95,7 @@ module Capybara
95
95
  # @return [Boolean] Whether the element is visible
96
96
  #
97
97
  def visible?
98
- native.xpath("./ancestor-or-self::*[contains(@style, 'display:none') or contains(@style, 'display: none')]").size == 0
98
+ native.xpath("./ancestor-or-self::*[contains(@style, 'display:none') or contains(@style, 'display: none') or name()='script' or name()='head']").size == 0
99
99
  end
100
100
 
101
101
  ##
@@ -118,22 +118,20 @@ module Capybara
118
118
  native[:selected]
119
119
  end
120
120
 
121
- def allow_reload!
122
- # no op
123
- end
124
-
125
- def without_wait
126
- yield
121
+ def synchronize
122
+ yield # simple nodes don't need to wait
127
123
  end
128
124
 
129
- protected
130
-
131
- def find_in_base(selector, xpath)
132
- native.xpath(xpath).map { |node| self.class.new(node) }
125
+ def allow_reload!
126
+ # no op
133
127
  end
134
128
 
135
- def wait_until
136
- yield # simple nodes don't need to wait
129
+ def all(*args)
130
+ query = Capybara::Query.new(*args)
131
+ elements = native.xpath(query.xpath).map do |node|
132
+ self.class.new(node)
133
+ end
134
+ Capybara::Result.new(elements, query)
137
135
  end
138
136
  end
139
137
  end
@@ -0,0 +1,78 @@
1
+ module Capybara
2
+ class Query
3
+ attr_accessor :selector, :locator, :options, :xpath, :find, :negative
4
+
5
+ VALID_KEYS = [:text, :visible, :between, :count, :maximum, :minimum]
6
+
7
+ def initialize(*args)
8
+ @options = if args.last.is_a?(Hash) then args.pop.dup else {} end
9
+
10
+ unless options.has_key?(:visible)
11
+ @options[:visible] = Capybara.ignore_hidden_elements
12
+ end
13
+
14
+ if args[1]
15
+ @selector = Selector.all[args[0]]
16
+ @locator = args[1]
17
+ else
18
+ @selector = Selector.all.values.find { |s| s.match?(args[0]) }
19
+ @locator = args[0]
20
+ end
21
+ @selector ||= Selector.all[Capybara.default_selector]
22
+
23
+ @xpath = @selector.call(@locator).to_s
24
+
25
+ assert_valid_keys!
26
+ end
27
+
28
+ def name; selector.name; end
29
+ def label; selector.label or selector.name; end
30
+
31
+ def description
32
+ @description = "#{label} #{locator.inspect}"
33
+ @description << " with text #{options[:text].inspect}" if options[:text]
34
+ @description
35
+ end
36
+
37
+ def matches_filters?(node)
38
+ if options[:text]
39
+ regexp = options[:text].is_a?(Regexp) ? options[:text] : Regexp.escape(options[:text])
40
+ return false if not node.text.match(regexp)
41
+ end
42
+ return false if options[:visible] and not node.visible?
43
+ selector.custom_filters.each do |name, block|
44
+ return false if options.has_key?(name) and not block.call(node, options[name])
45
+ end
46
+ true
47
+ end
48
+
49
+ def matches_count?(count)
50
+ case
51
+ when count.zero?
52
+ false
53
+ when options[:between]
54
+ options[:between] === count
55
+ when options[:count]
56
+ options[:count].to_i == count
57
+ when options[:maximum]
58
+ options[:maximum].to_i >= count
59
+ when options[:minimum]
60
+ options[:minimum].to_i <= count
61
+ else
62
+ count > 0
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def assert_valid_keys!
69
+ valid_keys = VALID_KEYS + @selector.custom_filters.keys
70
+ invalid_keys = @options.keys - valid_keys
71
+ unless invalid_keys.empty?
72
+ invalid_names = invalid_keys.map(&:inspect).join(", ")
73
+ valid_names = valid_keys.map(&:inspect).join(", ")
74
+ raise ArgumentError, "invalid keys #{invalid_names}, should be one of #{valid_names}"
75
+ end
76
+ end
77
+ end
78
+ end
@@ -18,35 +18,36 @@ class Capybara::RackTest::Browser
18
18
 
19
19
  def visit(path, attributes = {})
20
20
  reset_host!
21
- process(:get, path, attributes)
22
- follow_redirects!
21
+ process_and_follow_redirects(:get, path, attributes)
23
22
  end
24
23
 
25
24
  def submit(method, path, attributes)
26
25
  path = request_path if not path or path.empty?
27
- process(method, path, attributes)
28
- follow_redirects!
26
+ process_and_follow_redirects(method, path, attributes, {'HTTP_REFERER' => current_url})
29
27
  end
30
28
 
31
29
  def follow(method, path, attributes = {})
32
30
  return if path.gsub(/^#{request_path}/, '').start_with?('#')
33
- process(method, path, attributes)
34
- follow_redirects!
31
+ process_and_follow_redirects(method, path, attributes, {'HTTP_REFERER' => current_url})
35
32
  end
36
33
 
37
- def follow_redirects!
38
- 5.times do
39
- process(:get, last_response["Location"]) if last_response.redirect?
34
+ def process_and_follow_redirects(method, path, attributes = {}, env = {})
35
+ process(method, path, attributes, env)
36
+ if driver.follow_redirects?
37
+ driver.redirect_limit.times do
38
+ process(:get, last_response["Location"], {}, env) if last_response.redirect?
39
+ end
40
+ raise Capybara::InfiniteRedirectError, "redirected more than #{driver.redirect_limit} times, check for infinite redirects." if last_response.redirect?
40
41
  end
41
- raise Capybara::InfiniteRedirectError, "redirected more than 5 times, check for infinite redirects." if last_response.redirect?
42
42
  end
43
43
 
44
- def process(method, path, attributes = {})
44
+ def process(method, path, attributes = {}, env = {})
45
45
  new_uri = URI.parse(path)
46
- current_uri = URI.parse(current_url)
46
+ method.downcase! unless method.is_a? Symbol
47
47
 
48
48
  if new_uri.host
49
- @current_host = new_uri.scheme + '://' + new_uri.host
49
+ @current_host = "#{new_uri.scheme}://#{new_uri.host}"
50
+ @current_host << ":#{new_uri.port}" if new_uri.port != new_uri.default_port
50
51
  end
51
52
 
52
53
  if new_uri.relative?
@@ -59,7 +60,7 @@ class Capybara::RackTest::Browser
59
60
  end
60
61
 
61
62
  reset_cache!
62
- send(method, path, attributes, env)
63
+ send(method, path, attributes, env.merge(options[:headers] || {}))
63
64
  end
64
65
 
65
66
  def current_url
@@ -91,7 +92,7 @@ class Capybara::RackTest::Browser
91
92
  def source
92
93
  last_response.body
93
94
  rescue Rack::Test::Error
94
- nil
95
+ ""
95
96
  end
96
97
 
97
98
  protected
@@ -106,16 +107,4 @@ protected
106
107
  rescue Rack::Test::Error
107
108
  ""
108
109
  end
109
-
110
- def env
111
- env = {}
112
- begin
113
- env["HTTP_REFERER"] = last_request.url
114
- rescue Rack::Test::Error
115
- # no request yet
116
- end
117
- env.merge!(options[:headers]) if options[:headers]
118
- env
119
- end
120
-
121
110
  end
@@ -6,7 +6,9 @@ require 'cgi'
6
6
 
7
7
  class Capybara::RackTest::Driver < Capybara::Driver::Base
8
8
  DEFAULT_OPTIONS = {
9
- :respect_data_method => true
9
+ :respect_data_method => false,
10
+ :follow_redirects => true,
11
+ :redirect_limit => 5
10
12
  }
11
13
  attr_reader :app, :options
12
14
 
@@ -20,6 +22,14 @@ class Capybara::RackTest::Driver < Capybara::Driver::Base
20
22
  @browser ||= Capybara::RackTest::Browser.new(self)
21
23
  end
22
24
 
25
+ def follow_redirects?
26
+ @options[:follow_redirects]
27
+ end
28
+
29
+ def redirect_limit
30
+ @options[:redirect_limit]
31
+ end
32
+
23
33
  def response
24
34
  browser.last_response
25
35
  end
@@ -1,6 +1,6 @@
1
1
  class Capybara::RackTest::Node < Capybara::Driver::Node
2
2
  def text
3
- native.text
3
+ unnormalized_text.strip.gsub(/\s+/, ' ')
4
4
  end
5
5
 
6
6
  def [](name)
@@ -84,6 +84,22 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
84
84
  native.xpath(locator).map { |n| self.class.new(driver, n) }
85
85
  end
86
86
 
87
+ protected
88
+
89
+ def unnormalized_text
90
+ if !visible?
91
+ ''
92
+ elsif native.text?
93
+ native.text
94
+ elsif native.element?
95
+ native.children.map do |child|
96
+ Capybara::RackTest::Node.new(driver, child).unnormalized_text
97
+ end.join
98
+ else
99
+ ''
100
+ end
101
+ end
102
+
87
103
  private
88
104
 
89
105
  def string_node
@@ -0,0 +1,84 @@
1
+ module Capybara
2
+ class Result
3
+ include Enumerable
4
+
5
+ def initialize(elements, query)
6
+ @elements = elements
7
+ @result = elements.select { |node| query.matches_filters?(node) }
8
+ @rest = @elements - @result
9
+ @query = query
10
+ end
11
+
12
+ def each(&block)
13
+ @result.each(&block)
14
+ end
15
+
16
+ def first
17
+ @result.first
18
+ end
19
+
20
+ def matches_count?
21
+ @query.matches_count?(@result.size)
22
+ end
23
+
24
+ def find!
25
+ raise find_error if @result.count != 1
26
+ @result.first
27
+ end
28
+
29
+ def size; @result.size; end
30
+ alias_method :length, :size
31
+ alias_method :count, :size
32
+
33
+ def find_error
34
+ if @result.count == 0
35
+ Capybara::ElementNotFound.new("Unable to find #{@query.description}")
36
+ elsif @result.count > 1
37
+ Capybara::Ambiguous.new("Ambiguous match, found #{size} elements matching #{@query.description}")
38
+ end
39
+ end
40
+
41
+ def failure_message
42
+ message = if @query.options[:count]
43
+ "expected #{@query.description} to be found #{@query.options[:count]} #{declension("time", "times", @query.options[:count])}"
44
+ elsif @query.options[:between]
45
+ "expected #{@query.description} to be found between #{@query.options[:between].first} and #{@query.options[:between].last} times"
46
+ elsif @query.options[:maximum]
47
+ "expected #{@query.description} to be found at most #{@query.options[:maximum]} #{declension("time", "times", @query.options[:maximum])}"
48
+ elsif @query.options[:minimum]
49
+ "expected #{@query.description} to be found at least #{@query.options[:minimum]} #{declension("time", "times", @query.options[:minimum])}"
50
+ else
51
+ "expected to find #{@query.description}"
52
+ end
53
+ if count > 0
54
+ message << ", found #{count} #{declension("match", "matches")}: " << @result.map(&:text).map(&:inspect).join(", ")
55
+ else
56
+ message << " but there were no matches"
57
+ end
58
+ unless @rest.empty?
59
+ elements = @rest.map(&:text).map(&:inspect).join(", ")
60
+ message << ". Also found " << elements << ", which matched the selector but not all filters."
61
+ end
62
+ message
63
+ end
64
+
65
+ def negative_failure_message
66
+ "expected not to find #{@query.description}, but there #{declension("was", "were")} #{count} #{declension("match", "matches")}"
67
+ end
68
+
69
+ def empty?
70
+ @result.empty?
71
+ end
72
+ def [](key); @result[key]; end
73
+
74
+ private
75
+
76
+ def declension(singular, plural, count=count)
77
+ if count == 1
78
+ singular
79
+ else
80
+ plural
81
+ end
82
+ end
83
+ end
84
+ end
@@ -6,35 +6,15 @@ module Capybara
6
6
  end
7
7
 
8
8
  def matches?(actual)
9
- @actual = wrap(actual)
10
- @actual.has_selector?(*@args)
9
+ wrap(actual).assert_selector(*@args)
11
10
  end
12
11
 
13
12
  def does_not_match?(actual)
14
- @actual = wrap(actual)
15
- @actual.has_no_selector?(*@args)
16
- end
17
-
18
- def failure_message_for_should
19
- if normalized.failure_message
20
- normalized.failure_message.call(@actual, normalized)
21
- else
22
- "expected #{selector_name} to return something"
23
- end
24
- end
25
-
26
- def failure_message_for_should_not
27
- "expected #{selector_name} not to return anything"
13
+ wrap(actual).assert_no_selector(*@args)
28
14
  end
29
15
 
30
16
  def description
31
- "has #{selector_name}"
32
- end
33
-
34
- def selector_name
35
- name = "#{normalized.name} #{normalized.locator.inspect}"
36
- name << " with text #{normalized.options[:text].inspect}" if normalized.options[:text]
37
- name
17
+ "have #{query.description}"
38
18
  end
39
19
 
40
20
  def wrap(actual)
@@ -45,55 +25,38 @@ module Capybara
45
25
  end
46
26
  end
47
27
 
48
- def normalized
49
- @normalized ||= Capybara::Selector.normalize(*@args)
28
+ def query
29
+ @query ||= Capybara::Query.new(*@args)
50
30
  end
51
31
  end
52
32
 
53
- class HaveMatcher
54
- attr_reader :name, :locator, :options, :failure_message, :actual
55
-
56
- def initialize(name, locator, options={}, &block)
57
- @name = name
58
- @locator = locator
59
- @options = options
60
- @failure_message = block
61
- end
33
+ class HaveText
34
+ attr_reader :text
62
35
 
63
- def arguments
64
- if options.empty? then [locator] else [locator, options] end
36
+ def initialize(text)
37
+ @text = text
65
38
  end
66
39
 
67
40
  def matches?(actual)
68
41
  @actual = wrap(actual)
69
- @actual.send(:"has_#{name}?", *arguments)
42
+ @actual.has_text?(text)
70
43
  end
71
44
 
72
45
  def does_not_match?(actual)
73
46
  @actual = wrap(actual)
74
- @actual.send(:"has_no_#{name}?", *arguments)
47
+ @actual.has_no_text?(text)
75
48
  end
76
49
 
77
50
  def failure_message_for_should
78
- if failure_message
79
- failure_message.call(actual, self)
80
- else
81
- "expected #{selector_name} to return something"
82
- end
51
+ "expected there to be text #{text.inspect} in #{@actual.text.inspect}"
83
52
  end
84
53
 
85
54
  def failure_message_for_should_not
86
- "expected #{selector_name} not to return anything"
55
+ "expected there not to be text #{text.inspect} in #{@actual.text.inspect}"
87
56
  end
88
57
 
89
58
  def description
90
- "has #{selector_name}"
91
- end
92
-
93
- def selector_name
94
- selector_name = "#{name} #{locator.inspect}"
95
- selector_name << " with text #{options[:text].inspect}" if options[:text]
96
- selector_name
59
+ "have text #{text.inspect}"
97
60
  end
98
61
 
99
62
  def wrap(actual)
@@ -110,45 +73,47 @@ module Capybara
110
73
  end
111
74
 
112
75
  def have_xpath(xpath, options={})
113
- HaveMatcher.new(:xpath, xpath, options)
76
+ HaveSelector.new(:xpath, xpath, options)
114
77
  end
115
78
 
116
79
  def have_css(css, options={})
117
- HaveMatcher.new(:css, css, options)
80
+ HaveSelector.new(:css, css, options)
118
81
  end
119
82
 
120
83
  def have_content(text)
121
- HaveMatcher.new(:content, text.to_s) do |page, matcher|
122
- %(expected there to be content #{matcher.locator.inspect} in #{page.text.inspect})
123
- end
84
+ HaveText.new(text)
85
+ end
86
+
87
+ def have_text(text)
88
+ HaveText.new(text)
124
89
  end
125
90
 
126
91
  def have_link(locator, options={})
127
- HaveMatcher.new(:link, locator, options)
92
+ HaveSelector.new(:link, locator, options)
128
93
  end
129
94
 
130
95
  def have_button(locator)
131
- HaveMatcher.new(:button, locator)
96
+ HaveSelector.new(:button, locator)
132
97
  end
133
98
 
134
99
  def have_field(locator, options={})
135
- HaveMatcher.new(:field, locator, options)
100
+ HaveSelector.new(:field, locator, options)
136
101
  end
137
102
 
138
103
  def have_checked_field(locator)
139
- HaveMatcher.new(:checked_field, locator)
104
+ HaveSelector.new(:field, locator, :checked => true)
140
105
  end
141
106
 
142
107
  def have_unchecked_field(locator)
143
- HaveMatcher.new(:unchecked_field, locator)
108
+ HaveSelector.new(:field, locator, :unchecked => true)
144
109
  end
145
110
 
146
111
  def have_select(locator, options={})
147
- HaveMatcher.new(:select, locator, options)
112
+ HaveSelector.new(:select, locator, options)
148
113
  end
149
114
 
150
115
  def have_table(locator, options={})
151
- HaveMatcher.new(:table, locator, options)
116
+ HaveSelector.new(:table, locator, options)
152
117
  end
153
118
  end
154
119
  end