capybara 2.11.0 → 2.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +30 -4
  3. data/README.md +4 -0
  4. data/lib/capybara.rb +4 -2
  5. data/lib/capybara/driver/base.rb +2 -2
  6. data/lib/capybara/helpers.rb +8 -2
  7. data/lib/capybara/node/actions.rb +52 -1
  8. data/lib/capybara/node/document_matchers.rb +1 -0
  9. data/lib/capybara/node/finders.rb +2 -1
  10. data/lib/capybara/node/matchers.rb +54 -0
  11. data/lib/capybara/node/simple.rb +1 -1
  12. data/lib/capybara/queries/current_path_query.rb +4 -2
  13. data/lib/capybara/queries/selector_query.rb +23 -3
  14. data/lib/capybara/queries/text_query.rb +15 -7
  15. data/lib/capybara/queries/title_query.rb +2 -2
  16. data/lib/capybara/rack_test/form.rb +1 -1
  17. data/lib/capybara/rack_test/node.rb +4 -4
  18. data/lib/capybara/result.rb +2 -2
  19. data/lib/capybara/selector.rb +16 -4
  20. data/lib/capybara/selenium/driver.rb +27 -22
  21. data/lib/capybara/selenium/node.rb +10 -1
  22. data/lib/capybara/session.rb +91 -30
  23. data/lib/capybara/spec/session/accept_prompt_spec.rb +3 -0
  24. data/lib/capybara/spec/session/assert_all_of_selectors_spec.rb +94 -0
  25. data/lib/capybara/spec/session/assert_current_path.rb +12 -0
  26. data/lib/capybara/spec/session/attach_file_spec.rb +30 -0
  27. data/lib/capybara/spec/session/click_link_spec.rb +12 -1
  28. data/lib/capybara/spec/session/current_url_spec.rb +8 -0
  29. data/lib/capybara/spec/session/evaluate_script_spec.rb +14 -0
  30. data/lib/capybara/spec/session/execute_script_spec.rb +13 -0
  31. data/lib/capybara/spec/session/fill_in_spec.rb +6 -0
  32. data/lib/capybara/spec/session/find_field_spec.rb +2 -0
  33. data/lib/capybara/spec/session/find_spec.rb +3 -3
  34. data/lib/capybara/spec/session/frame/switch_to_frame_spec.rb +103 -0
  35. data/lib/capybara/spec/session/{within_frame_spec.rb → frame/within_frame_spec.rb} +12 -0
  36. data/lib/capybara/spec/session/has_current_path_spec.rb +28 -0
  37. data/lib/capybara/spec/session/has_selector_spec.rb +21 -0
  38. data/lib/capybara/spec/session/has_text_spec.rb +13 -1
  39. data/lib/capybara/spec/session/has_title_spec.rb +15 -0
  40. data/lib/capybara/spec/session/node_spec.rb +34 -1
  41. data/lib/capybara/spec/session/within_spec.rb +7 -0
  42. data/lib/capybara/spec/spec_helper.rb +4 -0
  43. data/lib/capybara/spec/views/form.erb +48 -0
  44. data/lib/capybara/spec/views/with_js.erb +5 -0
  45. data/lib/capybara/spec/views/within_frames.erb +1 -1
  46. data/lib/capybara/version.rb +1 -1
  47. data/lib/capybara/window.rb +1 -1
  48. data/spec/capybara_spec.rb +2 -2
  49. data/spec/rack_test_spec.rb +10 -0
  50. data/spec/result_spec.rb +3 -3
  51. data/spec/rspec/shared_spec_matchers.rb +1 -1
  52. data/spec/session_spec.rb +10 -0
  53. data/spec/shared_selenium_session.rb +2 -1
  54. data/spec/spec_helper.rb +2 -0
  55. metadata +7 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b4d75941bdc33b8e4b3d26aa8beb1a6d063fabca
4
- data.tar.gz: 692a680d4d550e566e6c558bfe53608a024b4e49
3
+ metadata.gz: bc177e8165f5b72a1dc1c1303b726701bc24254c
4
+ data.tar.gz: dab6b087b3ef03904d496bef6d3f66e2736b5bbd
5
5
  SHA512:
6
- metadata.gz: e9eee2dae8d51f3bff73da658a08d2aeefd6383c16cec7b269abbb2c99203239c996c83b0145946d473cc0d3d1076abd623f672dfa07dd4ad0ed9a74ea2ef001
7
- data.tar.gz: 2fef0e0fb22d31ccbd4879df37100feb6c717508c57266539245d07a3ba74c3849ae02cab2dfc735054b6344effb2c4f6783658e95a3513737aa81cabb39f83e
6
+ metadata.gz: f21cbebab1e87a9b9a411caf05acab9d912257604d6004db90af7439a692b862966e11291134424105698477b772cca46774aa95aad6ab78bdecd827ab862e83
7
+ data.tar.gz: c643a679ac6ac6cf17f03d63f74a97a20919980eead96c30b8b8c571120b19e0f94d2b7be978878580c6365483cc41476a83710b96200559e6be124dce620af0
data/History.md CHANGED
@@ -1,4 +1,30 @@
1
- #2.11.0
1
+ #Version 2.12.0
2
+ Release date: 2017-01-22
3
+
4
+ ### Added
5
+
6
+ * Session#switch_to_frame for manually handling frame switching - Issue #1365 [Thomas Walpole]
7
+ * Session#within_frame now accepts a selector type (defaults to :frame) and locator [Thomas Walpole]
8
+ * Session#execute_script and Session#evaluate_script now accept optional arguments that will be passed to the JS function. This may not be supported
9
+ by all drivers, and the types of arguments that may be passed is limited. If drivers opt to support this feature they should support passing page elements. [Thomas Walpole]
10
+ * :exact option for text and title matchers - Issue #1256 [Thomas Walpole]
11
+ * :exact_text option for selector finders/minders - Issue #1256 [Thomas Walpole]
12
+ * Capybara.exact_text setting that affects the text matchers and :text options passed to selector finders/matchers. Issue #1256 [Thomas Walpole]
13
+ * :make_visible option for #attach_file that allows for convenient changing of the CSS style of a file input element before attaching the file to it. Requires driver
14
+ support for passing page elements to Session#execute_script [Thomas Walpole]
15
+ * assert_all_selectors/assert_none_of_selectors assertions added
16
+ * :link selector (used by find_link/click_link) now supports finding hyperlink placeholders (no href attribute) when href: nil option is specified [Thomas Walpole]
17
+ * `within_element` as an alias of `within` due to RSpec collision
18
+
19
+ ### Fixed
20
+ * Fields inside a disabled fieldset are now correctly considered disabled - Issue #1816 [Thomas Walpole]
21
+ * Lazy Capybara::Results evaluation enabled for JRuby 9.1.6.0+
22
+ * A driver returning nil for #current_url won't raise an exception when calling #current_path [Dylan Reichstadt]
23
+ * Support Ruby 2.4.0 unified Integer [Koichi ITO]
24
+ * RackTest driver no longer modifies the text content of textarea elements in order to behave more like a real browser [Thomas Walpole]
25
+ * TextQuery (assert_text/have_text/etc) now ignores errors when trying to generate more helpful errors messages so the original error isn't hidden [Thomas Walpole]
26
+
27
+ #Version 2.11.0
2
28
  Release date: 2016-12-05
3
29
 
4
30
  ### Added
@@ -13,14 +39,14 @@ Release date: 2016-12-05
13
39
  * Selenium driver with Chrome should support multiple file upload [Thomas Walpole]
14
40
  * Fix visible: :hidden with :text option behavior [Thomas Walpole]
15
41
 
16
- #2.10.2
42
+ #Version 2.10.2
17
43
  Release date: 2016-11-30
18
44
 
19
45
  ### Fixed
20
46
  * App exceptions with multiple parameter initializers now re-raised correctly - Issue #1785 [Michael Lutsiuk]
21
47
  * Use Addressable::URI when parsing current_path since it's more lenient of technically invalid URLs - Issue #1801 [Marcos Duque, Thomas Walpole]
22
48
 
23
- #2.10.1
49
+ #Version 2.10.1
24
50
  Release date: 2016-10-08
25
51
 
26
52
  ### Fixed
@@ -28,7 +54,7 @@ Release date: 2016-10-08
28
54
  * Capybara::Result optimization disabled in JRuby due to issue with lazy enumerator evaluation [Thomas Walpole]
29
55
  See: https://github.com/jruby/jruby/issues/4212
30
56
 
31
- #2.10.0
57
+ #Version 2.10.0
32
58
  Release date: 2016-10-05
33
59
 
34
60
  ### Added
data/README.md CHANGED
@@ -672,6 +672,10 @@ Or have it save and automatically open:
672
672
  save_and_open_screenshot
673
673
  ```
674
674
 
675
+ Screenshots are saved to `Capybara.save_path`, relative to the app directory.
676
+ If you have required `capybara/rails`, `Capybara.save_path` will default to
677
+ `tmp/capybara`.
678
+
675
679
  ## <a name="matching"></a>Matching
676
680
 
677
681
  It is possible to customize how Capybara finds elements. At your disposal
@@ -29,6 +29,7 @@ module Capybara
29
29
  attr_accessor :raise_server_errors, :server_errors
30
30
  attr_writer :default_driver, :current_driver, :javascript_driver, :session_name, :server_host
31
31
  attr_reader :save_and_open_page_path
32
+ attr_accessor :exact_text
32
33
  attr_accessor :app
33
34
 
34
35
  ##
@@ -244,7 +245,7 @@ module Capybara
244
245
  # manually.
245
246
  #
246
247
  # @param [Rack Application] app The rack application to run
247
- # @param [Fixnum] port The port to run the application on
248
+ # @param [Integer] port The port to run the application on
248
249
  #
249
250
  def run_default_server(app, port)
250
251
  servers[:webrick].call(app, port, server_host)
@@ -368,7 +369,7 @@ module Capybara
368
369
  def HTML(html)
369
370
  Nokogiri::HTML(html).tap do |document|
370
371
  document.xpath('//textarea').each do |textarea|
371
- textarea.content=textarea.content.sub(/\A\n/,'')
372
+ textarea['_capybara_raw_value'] = textarea.content.sub(/\A\n/,'')
372
373
  end
373
374
  end
374
375
  end
@@ -495,6 +496,7 @@ Capybara.configure do |config|
495
496
  config.automatic_reload = true
496
497
  config.match = :smart
497
498
  config.exact = false
499
+ config.exact_text = false
498
500
  config.raise_server_errors = true
499
501
  config.server_errors = [StandardError]
500
502
  config.visible_text_only = false
@@ -28,11 +28,11 @@ class Capybara::Driver::Base
28
28
  raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#go_forward'
29
29
  end
30
30
 
31
- def execute_script(script)
31
+ def execute_script(script, *args)
32
32
  raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#execute_script'
33
33
  end
34
34
 
35
- def evaluate_script(script)
35
+ def evaluate_script(script, *args)
36
36
  raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#evaluate_script'
37
37
  end
38
38
 
@@ -28,8 +28,14 @@ module Capybara
28
28
  # @param [String] text Text to escape
29
29
  # @return [String] Escaped text
30
30
  #
31
- def to_regexp(text, options=nil)
32
- text.is_a?(Regexp) ? text : Regexp.new(Regexp.escape(normalize_whitespace(text)), options)
31
+ def to_regexp(text, regexp_options=nil, exact=false)
32
+ if text.is_a?(Regexp)
33
+ text
34
+ else
35
+ escaped = Regexp.escape(normalize_whitespace(text))
36
+ escaped = "\\A#{escaped}\\z" if exact
37
+ Regexp.new(escaped, regexp_options)
38
+ end
33
39
  end
34
40
 
35
41
  ##
@@ -75,6 +75,7 @@ module Capybara
75
75
  # @macro waiting_behavior
76
76
  # @option options [String] :with The value to fill in - required
77
77
  # @option options [Hash] :fill_options Driver specific options regarding how to fill fields
78
+ # @option options [String] :currently_with The current value property of the field to fill in
78
79
  # @option options [Boolean] :multiple Match fields that can have multiple values?
79
80
  # @option options [String] :id Match fields that match the id attribute
80
81
  # @option options [String] :name Match fields that match the name attribute
@@ -87,6 +88,7 @@ module Capybara
87
88
  raise "Must pass a hash containing 'with'" if not options.is_a?(Hash) or not options.has_key?(:with)
88
89
  with = options.delete(:with)
89
90
  fill_options = options.delete(:fill_options)
91
+ options[:with] = options.delete(:currently_with) if options.has_key?(:currently_with)
90
92
  find(:fillable_field, locator, options).set(with, fill_options)
91
93
  end
92
94
 
@@ -228,6 +230,7 @@ module Capybara
228
230
  # @option options [String] id Match fields that match the id attribute
229
231
  # @option options [String] name Match fields that match the name attribute
230
232
  # @option options [String, Array<String>] :class Match links that match the class(es) provided
233
+ # @option options [true, Hash] make_visible A Hash of CSS styles to change before attempting to attach the file, if `true` { opacity: 1, display: 'block', visibility: 'visible' } is used (may not be supported by all drivers)
231
234
  #
232
235
  # @return [Capybara::Node::Element] The file field element
233
236
  def attach_file(locator, path, options={})
@@ -235,10 +238,58 @@ module Capybara
235
238
  Array(path).each do |p|
236
239
  raise Capybara::FileNotFound, "cannot attach file, #{p} does not exist" unless File.exist?(p.to_s)
237
240
  end
238
- find(:file_field, locator, options).set(path)
241
+ # Allow user to update the CSS style of the file input since they are so often hidden on a page
242
+ if style = options.delete(:make_visible)
243
+ style = { opacity: 1, display: 'block', visibility: 'visible' } if style == true
244
+ ff = find(:file_field, locator, options.merge({visible: :all}))
245
+ _update_style(ff, style)
246
+ if ff.visible?
247
+ begin
248
+ ff.set(path)
249
+ ensure
250
+ _reset_style(ff)
251
+ end
252
+ else
253
+ raise ExpectationNotMet, "The style changes in :make_visible did not make the file input visible"
254
+ end
255
+ else
256
+ find(:file_field, locator, options).set(path)
257
+ end
239
258
  end
240
259
 
241
260
  private
261
+ def _update_style(element, style)
262
+ script = <<-JS
263
+ var el = arguments[0];
264
+ el.capybara_style_cache = el.style.cssText;
265
+ var css = arguments[1];
266
+ for (var prop in css){
267
+ if (css.hasOwnProperty(prop)) {
268
+ el.style[prop] = css[prop]
269
+ }
270
+ }
271
+ JS
272
+ begin
273
+ session.execute_script(script, element, style)
274
+ rescue Capybara::NotSupportedByDriverError
275
+ warn "The :make_visible option is not supported by the current driver - ignoring"
276
+ end
277
+ end
278
+
279
+ def _reset_style(element)
280
+ script = <<-JS
281
+ var el = arguments[0];
282
+ if (el.hasOwnProperty('capybara_style_cache')) {
283
+ el.style.cssText = el.capybara_style_cache;
284
+ delete el.capybara_style_cache;
285
+ }
286
+ JS
287
+ begin
288
+ session.execute_script(script, element)
289
+ rescue
290
+ end
291
+ end
292
+
242
293
 
243
294
  def _check_with_label(selector, checked, locator, options)
244
295
  locator, options = nil, locator if locator.is_a? Hash
@@ -11,6 +11,7 @@ module Capybara
11
11
  # @overload $0(regexp, options = {})
12
12
  # @param regexp [Regexp] The regexp that title should match to
13
13
  # @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time that Capybara will wait for title to eq/match given string/regexp argument
14
+ # @option options [Boolean] :exact (false) When passed a string should the match be exact or just substring
14
15
  # @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
15
16
  # @return [true]
16
17
  #
@@ -89,7 +89,7 @@ module Capybara
89
89
  #
90
90
  # @macro waiting_behavior
91
91
  #
92
- # @option options [String,Regexp] href Value to match against the links href
92
+ # @option options [String,Regexp,nil] href Value to match against the links href, if nil finds link placeholders (<a> elements with no href attribute)
93
93
  # @option options [String] id Match links with the id provided
94
94
  # @option options [String] title Match links with the title provided
95
95
  # @option options [String] alt Match links with a contained img element whose alt matches
@@ -188,6 +188,7 @@ module Capybara
188
188
  # @param [Symbol] kind Optional selector type (:css, :xpath, :field, etc.) - Defaults to Capybara.default_selector
189
189
  # @param [String] locator The selector
190
190
  # @option options [String, Regexp] text Only find elements which contain this text or match this regexp
191
+ # @option options [String, Boolean] exact_text (Capybara.exact_text) When String the string the elements contained text must match exactly, when Boolean controls whether the :text option must match exactly
191
192
  # @option options [Boolean, Symbol] visible Only find elements with the specified visibility:
192
193
  # * true - only finds visible elements.
193
194
  # * false - finds invisible _and_ visible elements.
@@ -97,6 +97,58 @@ module Capybara
97
97
  end
98
98
  end
99
99
 
100
+ # Asserts that all of the provided selectors are present on the given page
101
+ # or descendants of the current node. If options are provided, the assertion
102
+ # will check that each locator is present with those options as well (other than :wait).
103
+ #
104
+ # page.assert_all_of_selectors(:custom, 'Tom', 'Joe', visible: all)
105
+ # page.assert_all_of_selectors(:css, '#my_div', 'a.not_clicked')
106
+ #
107
+ # It accepts all options that {Capybara::Node::Finders#all} accepts,
108
+ # such as :text and :visible.
109
+ #
110
+ # The :wait option applies to all of the selectors as a group, so all of the locators must be present
111
+ # within :wait (Defaults to Capybara.default_max_wait_time) seconds.
112
+ #
113
+ # @overload assert_all_of_selectors([kind = Capybara.default_selector], *locators, options = {})
114
+ #
115
+ def assert_all_of_selectors(*args, &optional_filter_block)
116
+ options = if args.last.is_a?(Hash) then args.pop.dup else {} end
117
+ selector = if args.first.is_a?(Symbol) then args.shift else Capybara.default_selector end
118
+ wait = options.fetch(:wait, Capybara.default_max_wait_time)
119
+ synchronize(wait) do
120
+ args.each do |locator|
121
+ assert_selector(selector, locator, options, &optional_filter_block)
122
+ end
123
+ end
124
+ end
125
+
126
+ # Asserts that none of the provided selectors are present on the given page
127
+ # or descendants of the current node. If options are provided, the assertion
128
+ # will check that each locator is present with those options as well (other than :wait).
129
+ #
130
+ # page.assert_none_of_selectors(:custom, 'Tom', 'Joe', visible: all)
131
+ # page.assert_none_of_selectors(:css, '#my_div', 'a.not_clicked')
132
+ #
133
+ # It accepts all options that {Capybara::Node::Finders#all} accepts,
134
+ # such as :text and :visible.
135
+ #
136
+ # The :wait option applies to all of the selectors as a group, so none of the locators must be present
137
+ # within :wait (Defaults to Capybara.default_max_wait_time) seconds.
138
+ #
139
+ # @overload assert_none_of_selectors([kind = Capybara.default_selector], *locators, options = {})
140
+ #
141
+ def assert_none_of_selectors(*args, &optional_filter_block)
142
+ options = if args.last.is_a?(Hash) then args.pop.dup else {} end
143
+ selector = if args.first.is_a?(Symbol) then args.shift else Capybara.default_selector end
144
+ wait = options.fetch(:wait, Capybara.default_max_wait_time)
145
+ synchronize(wait) do
146
+ args.each do |locator|
147
+ assert_no_selector(selector, locator, options, &optional_filter_block)
148
+ end
149
+ end
150
+ end
151
+
100
152
  ##
101
153
  #
102
154
  # Asserts that a given selector is not on the page or a descendant of the current node.
@@ -546,6 +598,7 @@ module Capybara
546
598
  # @option options [Integer] :maximum (nil) Maximum number of times the text is expected to occur
547
599
  # @option options [Range] :between (nil) Range of times that is expected to contain number of times text occurs
548
600
  # @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time that Capybara will wait for text to eq/match given string/regexp argument
601
+ # @option options [Boolean] :exact (Capybara.exact_text) Whether text must be an exact match or just substring
549
602
  # @overload $0(text, options = {})
550
603
  # @param [String, Regexp] text The string/regexp to check for. If it's a string, text is expected to include it. If it's a regexp, text is expected to match it.
551
604
  # @option options [Integer] :count (nil) Number of times the text is expected to occur
@@ -553,6 +606,7 @@ module Capybara
553
606
  # @option options [Integer] :maximum (nil) Maximum number of times the text is expected to occur
554
607
  # @option options [Range] :between (nil) Range of times that is expected to contain number of times text occurs
555
608
  # @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time that Capybara will wait for text to eq/match given string/regexp argument
609
+ # @option options [Boolean] :exact (Capybara.exact_text) Whether text must be an exact match or just substring
556
610
  # @raise [Capybara::ExpectationNotMet] if the assertion hasn't succeeded during wait time
557
611
  # @return [true]
558
612
  #
@@ -76,7 +76,7 @@ module Capybara
76
76
  #
77
77
  def value
78
78
  if tag_name == 'textarea'
79
- native.content
79
+ native['_capybara_raw_value']
80
80
  elsif tag_name == 'select'
81
81
  if native['multiple'] == 'multiple'
82
82
  native.xpath(".//option[@selected='selected']").map { |option| option[:value] || option.content }
@@ -17,10 +17,12 @@ module Capybara
17
17
  @actual_path = if options[:url]
18
18
  session.current_url
19
19
  else
20
+ uri = ::Addressable::URI.parse(session.current_url)
21
+
20
22
  if options[:only_path]
21
- ::Addressable::URI.parse(session.current_url).path
23
+ uri.path unless uri.nil? # Ensure the parsed url isn't nil.
22
24
  else
23
- ::Addressable::URI.parse(session.current_url).request_uri
25
+ uri.request_uri unless uri.nil? # Ensure the parsed url isn't nil.
24
26
  end
25
27
  end
26
28
 
@@ -4,7 +4,7 @@ module Capybara
4
4
  class SelectorQuery < Queries::BaseQuery
5
5
  attr_accessor :selector, :locator, :options, :expression, :find, :negative
6
6
 
7
- VALID_KEYS = COUNT_KEYS + [:text, :id, :class, :visible, :exact, :match, :wait, :filter_set]
7
+ VALID_KEYS = COUNT_KEYS + [:text, :id, :class, :visible, :exact, :exact_text, :match, :wait, :filter_set]
8
8
  VALID_MATCH = [:first, :smart, :prefer_exact, :one]
9
9
 
10
10
  def initialize(*args, &filter_block)
@@ -42,7 +42,8 @@ module Capybara
42
42
 
43
43
  def description
44
44
  @description = String.new("#{label} #{locator.inspect}")
45
- @description << " with text #{options[:text].inspect}" if options[:text]
45
+ @description << " with#{" exact" if exact_text === true} text #{options[:text].inspect}" if options[:text]
46
+ @description << " with exact text #{options[:exact_text]}" if options[:exact_text].is_a?(String)
46
47
  @description << " with id #{options[:id]}" if options[:id]
47
48
  @description << " with classes #{Array(options[:class]).join(',')}]" if options[:class]
48
49
  @description << selector.description(options)
@@ -52,7 +53,22 @@ module Capybara
52
53
 
53
54
  def matches_filters?(node)
54
55
  if options[:text]
55
- regexp = options[:text].is_a?(Regexp) ? options[:text] : Regexp.escape(options[:text].to_s)
56
+ regexp = if options[:text].is_a?(Regexp)
57
+ options[:text]
58
+ else
59
+ if exact_text === true
60
+ "\\A#{Regexp.escape(options[:text].to_s)}\\z"
61
+ else
62
+ Regexp.escape(options[:text].to_s)
63
+ end
64
+ end
65
+ text_visible = visible
66
+ text_visible = :all if text_visible == :hidden
67
+ return false if not node.text(text_visible).match(regexp)
68
+ end
69
+
70
+ if exact_text.is_a?(String)
71
+ regexp = "\\A#{Regexp.escape(options[:exact_text])}\\z"
56
72
  text_visible = visible
57
73
  text_visible = :all if text_visible == :hidden
58
74
  return false if not node.text(text_visible).match(regexp)
@@ -187,6 +203,10 @@ module Capybara
187
203
  warn "The :exact option only has an effect on queries using the XPath#is method. Using it with the query \"#{expression.to_s}\" has no effect."
188
204
  end
189
205
  end
206
+
207
+ def exact_text
208
+ exact_text = options.fetch(:exact_text, Capybara.exact_text)
209
+ end
190
210
  end
191
211
  end
192
212
  end
@@ -10,8 +10,8 @@ module Capybara
10
10
  unless @expected_text.is_a?(Regexp)
11
11
  @expected_text = Capybara::Helpers.normalize_whitespace(@expected_text)
12
12
  end
13
- @search_regexp = Capybara::Helpers.to_regexp(@expected_text)
14
13
  @options ||= {}
14
+ @search_regexp = Capybara::Helpers.to_regexp(@expected_text, nil, exact?)
15
15
  assert_valid_keys
16
16
  end
17
17
 
@@ -33,12 +33,16 @@ module Capybara
33
33
  if @expected_text.is_a?(Regexp)
34
34
  "text matching #{@expected_text.inspect}"
35
35
  else
36
- "text #{@expected_text.inspect}"
36
+ "#{"exact " if exact?}text #{@expected_text.inspect}"
37
37
  end
38
38
  end
39
39
 
40
40
  private
41
41
 
42
+ def exact?
43
+ options.fetch(:exact, Capybara.exact_text)
44
+ end
45
+
42
46
  def build_message(report_on_invisible)
43
47
  message = String.new()
44
48
  unless (COUNT_KEYS & @options.keys).empty?
@@ -57,10 +61,14 @@ module Capybara
57
61
  end
58
62
 
59
63
  if @node and check_visible_text? and report_on_invisible
60
- invisible_text = text(@node, :all)
61
- invisible_count = invisible_text.scan(@search_regexp).size
62
- if invisible_count != @count
63
- details_message << ". it was found #{invisible_count} #{Capybara::Helpers.declension("time", "times", invisible_count)} including non-visible text"
64
+ begin
65
+ invisible_text = text(@node, :all)
66
+ invisible_count = invisible_text.scan(@search_regexp).size
67
+ if invisible_count != @count
68
+ details_message << ". it was found #{invisible_count} #{Capybara::Helpers.declension("time", "times", invisible_count)} including non-visible text"
69
+ end
70
+ rescue
71
+ # An error getting the non-visible text (if element goes out of scope) should not affect the response
64
72
  end
65
73
  end
66
74
 
@@ -70,7 +78,7 @@ module Capybara
70
78
  end
71
79
 
72
80
  def valid_keys
73
- COUNT_KEYS + [:wait]
81
+ COUNT_KEYS + [:wait, :exact]
74
82
  end
75
83
 
76
84
  def check_visible_text?