capybara 2.11.0 → 2.12.0

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 (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?