mirror of
https://github.com/teamcapybara/capybara.git
synced 2022-11-09 12:08:07 -05:00
Merge pull request #1982 from teamcapybara/whitespace
Change #text to be closer to what a browser displays
This commit is contained in:
commit
90874d88cc
15 changed files with 115 additions and 47 deletions
|
@ -6,7 +6,7 @@ module Capybara
|
||||||
extend self
|
extend self
|
||||||
|
|
||||||
##
|
##
|
||||||
#
|
# @deprecated
|
||||||
# Normalizes whitespace space by stripping leading and trailing
|
# Normalizes whitespace space by stripping leading and trailing
|
||||||
# whitespace and replacing sequences of whitespace characters
|
# whitespace and replacing sequences of whitespace characters
|
||||||
# with a single space.
|
# with a single space.
|
||||||
|
@ -15,6 +15,7 @@ module Capybara
|
||||||
# @return [String] Normalized text
|
# @return [String] Normalized text
|
||||||
#
|
#
|
||||||
def normalize_whitespace(text)
|
def normalize_whitespace(text)
|
||||||
|
warn "DEPRECATED: Capybara::Helpers::normalize_whitespace is deprecated, please update your driver"
|
||||||
text.to_s.gsub(/[[:space:]]+/, ' ').strip
|
text.to_s.gsub(/[[:space:]]+/, ' ').strip
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -28,10 +29,11 @@ module Capybara
|
||||||
# @param [Fixnum, Boolean, nil] options Options passed to Regexp.new when creating the Regexp
|
# @param [Fixnum, Boolean, nil] options Options passed to Regexp.new when creating the Regexp
|
||||||
# @return [Regexp] Regexp to match the passed in text and options
|
# @return [Regexp] Regexp to match the passed in text and options
|
||||||
#
|
#
|
||||||
def to_regexp(text, exact: false, options: nil)
|
def to_regexp(text, exact: false, all_whitespace: false, options: nil)
|
||||||
return text if text.is_a?(Regexp)
|
return text if text.is_a?(Regexp)
|
||||||
|
|
||||||
escaped = Regexp.escape(normalize_whitespace(text))
|
escaped = Regexp.escape(text)
|
||||||
|
escaped = escaped.gsub("\\ ", "[[:blank:]]") if all_whitespace
|
||||||
escaped = "\\A#{escaped}\\z" if exact
|
escaped = "\\A#{escaped}\\z" if exact
|
||||||
Regexp.new(escaped, options)
|
Regexp.new(escaped, options)
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
module Capybara
|
module Capybara
|
||||||
module Queries
|
module Queries
|
||||||
class AncestorQuery < MatchQuery
|
class AncestorQuery < Capybara::Queries::SelectorQuery
|
||||||
# @api private
|
# @api private
|
||||||
def resolve_for(node, exact = nil)
|
def resolve_for(node, exact = nil)
|
||||||
@child_node = node
|
@child_node = node
|
||||||
|
@ -18,6 +18,12 @@ module Capybara
|
||||||
desc += " that is an ancestor of #{child_query.description}" if child_query
|
desc += " that is an ancestor of #{child_query.description}" if child_query
|
||||||
desc
|
desc
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def valid_keys
|
||||||
|
super - COUNT_KEYS
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,11 +10,8 @@ module Capybara
|
||||||
else
|
else
|
||||||
type
|
type
|
||||||
end
|
end
|
||||||
@expected_text = if expected_text.is_a?(Regexp)
|
|
||||||
expected_text
|
@expected_text = expected_text.is_a?(Regexp) ? expected_text : expected_text.to_s
|
||||||
else
|
|
||||||
Capybara::Helpers.normalize_whitespace(expected_text)
|
|
||||||
end
|
|
||||||
@options = options
|
@options = options
|
||||||
super(@options)
|
super(@options)
|
||||||
self.session_options = session_options
|
self.session_options = session_options
|
||||||
|
@ -94,7 +91,7 @@ module Capybara
|
||||||
end
|
end
|
||||||
|
|
||||||
def text(node, query_type)
|
def text(node, query_type)
|
||||||
Capybara::Helpers.normalize_whitespace(node.text(query_type))
|
node.text(query_type)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,10 +5,10 @@ module Capybara
|
||||||
module Queries
|
module Queries
|
||||||
class TitleQuery < BaseQuery
|
class TitleQuery < BaseQuery
|
||||||
def initialize(expected_title, **options)
|
def initialize(expected_title, **options)
|
||||||
@expected_title = expected_title.is_a?(Regexp) ? expected_title : Capybara::Helpers.normalize_whitespace(expected_title)
|
@expected_title = expected_title.is_a?(Regexp) ? expected_title : expected_title.to_s
|
||||||
@options = options
|
@options = options
|
||||||
super(@options)
|
super(@options)
|
||||||
@search_regexp = Capybara::Helpers.to_regexp(@expected_title, exact: options.fetch(:exact, false))
|
@search_regexp = Capybara::Helpers.to_regexp(@expected_title, all_whitespace: true, exact: options.fetch(:exact, false))
|
||||||
assert_valid_keys
|
assert_valid_keys
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,23 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Capybara::RackTest::Node < Capybara::Driver::Node
|
class Capybara::RackTest::Node < Capybara::Driver::Node
|
||||||
|
BLOCK_ELEMENTS = %w[p h1 h2 h3 h4 h5 h6 ol ul pre address blockquote dl div fieldset form hr noscript table].freeze
|
||||||
|
|
||||||
def all_text
|
def all_text
|
||||||
Capybara::Helpers.normalize_whitespace(native.text)
|
native.text
|
||||||
|
.gsub(/[\u200b\u200e\u200f]/, '')
|
||||||
|
.gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
|
||||||
|
.gsub(/\A[[:space:]&&[^\u00a0]]+/, "")
|
||||||
|
.gsub(/[[:space:]&&[^\u00a0]]+\z/, "")
|
||||||
|
.tr("\u00a0", ' ')
|
||||||
end
|
end
|
||||||
|
|
||||||
def visible_text
|
def visible_text
|
||||||
Capybara::Helpers.normalize_whitespace(unnormalized_text)
|
displayed_text.gsub(/\ +/, ' ')
|
||||||
|
.gsub(/[\ \n]*\n[\ \n]*/, "\n")
|
||||||
|
.gsub(/\A[[:space:]&&[^\u00a0]]+/, "")
|
||||||
|
.gsub(/[[:space:]&&[^\u00a0]]+\z/, "")
|
||||||
|
.tr("\u00a0", ' ')
|
||||||
end
|
end
|
||||||
|
|
||||||
def [](name)
|
def [](name)
|
||||||
|
@ -103,15 +114,20 @@ class Capybara::RackTest::Node < Capybara::Driver::Node
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def unnormalized_text(check_ancestor_visibility = true)
|
# @api private
|
||||||
if !string_node.visible?(check_ancestor_visibility)
|
def displayed_text(check_ancestor: true)
|
||||||
|
if !string_node.visible?(check_ancestor)
|
||||||
''
|
''
|
||||||
elsif native.text?
|
elsif native.text?
|
||||||
native.text
|
native.text
|
||||||
|
.gsub(/[\u200b\u200e\u200f]/, '')
|
||||||
|
.gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
|
||||||
elsif native.element?
|
elsif native.element?
|
||||||
native.children.map do |child|
|
text = native.children.map do |child|
|
||||||
Capybara::RackTest::Node.new(driver, child).unnormalized_text(false)
|
Capybara::RackTest::Node.new(driver, child).displayed_text(check_ancestor: false)
|
||||||
end.join
|
end.join || ''
|
||||||
|
text = "\n#{text}\n" if BLOCK_ELEMENTS.include?(tag_name)
|
||||||
|
text
|
||||||
else
|
else
|
||||||
''
|
''
|
||||||
end
|
end
|
||||||
|
|
|
@ -154,7 +154,6 @@ module Capybara
|
||||||
end
|
end
|
||||||
|
|
||||||
def format(content)
|
def format(content)
|
||||||
content = Capybara::Helpers.normalize_whitespace(content) unless content.is_a? Regexp
|
|
||||||
content.inspect
|
content.inspect
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,13 +15,16 @@ class Capybara::Selenium::Node < Capybara::Driver::Node
|
||||||
)
|
)
|
||||||
|
|
||||||
def visible_text
|
def visible_text
|
||||||
# Selenium doesn't normalize Unicode whitespace.
|
native.text
|
||||||
Capybara::Helpers.normalize_whitespace(native.text)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def all_text
|
def all_text
|
||||||
text = driver.execute_script("return arguments[0].textContent", self)
|
text = driver.execute_script("return arguments[0].textContent", self)
|
||||||
Capybara::Helpers.normalize_whitespace(text)
|
text.gsub(/[\u200b\u200e\u200f]/, '')
|
||||||
|
.gsub(/[\ \n\f\t\v\u2028\u2029]+/, ' ')
|
||||||
|
.gsub(/\A[[:space:]&&[^\u00a0]]+/, "")
|
||||||
|
.gsub(/[[:space:]&&[^\u00a0]]+\z/, "")
|
||||||
|
.tr("\u00a0", ' ')
|
||||||
end
|
end
|
||||||
|
|
||||||
def [](name)
|
def [](name)
|
||||||
|
|
|
@ -17,7 +17,7 @@ Capybara::SpecHelper.spec '#ancestor' do
|
||||||
|
|
||||||
it "should find the ancestor element using the given locator and options" do
|
it "should find the ancestor element using the given locator and options" do
|
||||||
el = @session.find(:css, '#child')
|
el = @session.find(:css, '#child')
|
||||||
expect(el.ancestor('//div', text: 'Ancestor Ancestor Ancestor')[:id]).to eq('ancestor3')
|
expect(el.ancestor('//div', text: "Ancestor\nAncestor\nAncestor")[:id]).to eq('ancestor3')
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should raise an error if there are multiple matches" do
|
it "should raise an error if there are multiple matches" do
|
||||||
|
@ -53,8 +53,8 @@ Capybara::SpecHelper.spec '#ancestor' do
|
||||||
xpath { |num| ".//*[@id='ancestor#{num}']" }
|
xpath { |num| ".//*[@id='ancestor#{num}']" }
|
||||||
end
|
end
|
||||||
el = @session.find(:css, '#child')
|
el = @session.find(:css, '#child')
|
||||||
expect(el.ancestor(:level, 1).text).to eq('Ancestor Child')
|
expect(el.ancestor(:level, 1)[:id]).to eq "ancestor1"
|
||||||
expect(el.ancestor(:level, 3).text).to eq('Ancestor Ancestor Ancestor Child')
|
expect(el.ancestor(:level, 3)[:id]).to eq "ancestor3"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ Capybara::SpecHelper.spec '#ancestor' do
|
||||||
el = @session.find(:css, '#child')
|
el = @session.find(:css, '#child')
|
||||||
expect do
|
expect do
|
||||||
el.ancestor(:xpath, '//div[@id="nosuchthing"]')
|
el.ancestor(:xpath, '//div[@id="nosuchthing"]')
|
||||||
end.to raise_error(Capybara::ElementNotFound, "Unable to find xpath \"//div[@id=\\\"nosuchthing\\\"]\" that is an ancestor of visible css \"#child\"")
|
end.to raise_error(Capybara::ElementNotFound, "Unable to find visible xpath \"//div[@id=\\\"nosuchthing\\\"]\" that is an ancestor of visible css \"#child\"")
|
||||||
end
|
end
|
||||||
|
|
||||||
context "within a scope" do
|
context "within a scope" do
|
||||||
|
|
|
@ -7,8 +7,7 @@ Capybara::SpecHelper.spec '#assert_text' do
|
||||||
expect(@session.assert_text('Lorem')).to eq(true)
|
expect(@session.assert_text('Lorem')).to eq(true)
|
||||||
expect(@session.assert_text('Redirect')).to eq(true)
|
expect(@session.assert_text('Redirect')).to eq(true)
|
||||||
expect(@session.assert_text(:Redirect)).to eq(true)
|
expect(@session.assert_text(:Redirect)).to eq(true)
|
||||||
expect(@session.assert_text('text with whitespace')).to eq(true)
|
expect(@session.assert_text('text with whitespace')).to eq(true)
|
||||||
expect(@session.assert_text("text with \n\n whitespace")).to eq(true)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should take scopes into account" do
|
it "should take scopes into account" do
|
||||||
|
@ -49,7 +48,7 @@ Capybara::SpecHelper.spec '#assert_text' do
|
||||||
it "should raise error with a helpful message if the requested text is present but with incorrect case" do
|
it "should raise error with a helpful message if the requested text is present but with incorrect case" do
|
||||||
@session.visit('/with_html')
|
@session.visit('/with_html')
|
||||||
expect do
|
expect do
|
||||||
@session.assert_text('Text With Whitespace')
|
@session.assert_text('Text With Whitespace')
|
||||||
end.to raise_error(Capybara::ExpectationNotMet, /it was found 1 time using a case insensitive search/)
|
end.to raise_error(Capybara::ExpectationNotMet, /it was found 1 time using a case insensitive search/)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -77,7 +76,7 @@ Capybara::SpecHelper.spec '#assert_text' do
|
||||||
@session.visit('/with_html')
|
@session.visit('/with_html')
|
||||||
expect do
|
expect do
|
||||||
@session.assert_text(/xxxxyzzz/)
|
@session.assert_text(/xxxxyzzz/)
|
||||||
end.to raise_error(Capybara::ExpectationNotMet, /\Aexpected to find text matching \/xxxxyzzz\/ in "This is a test Header Class(.+)"\Z/)
|
end.to raise_error(Capybara::ExpectationNotMet, /\Aexpected to find text matching \/xxxxyzzz\/ in "This is a test\\nHeader Class(.+)"\Z/)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should escape any characters that would have special meaning in a regexp" do
|
it "should escape any characters that would have special meaning in a regexp" do
|
||||||
|
@ -112,7 +111,7 @@ Capybara::SpecHelper.spec '#assert_text' do
|
||||||
Capybara.using_wait_time(0) do
|
Capybara.using_wait_time(0) do
|
||||||
@session.visit('/with_js')
|
@session.visit('/with_js')
|
||||||
@session.find(:css, '#reload-list').click
|
@session.find(:css, '#reload-list').click
|
||||||
@session.find(:css, '#the-list').assert_text('Foo Bar', wait: 0.9)
|
@session.find(:css, '#the-list').assert_text("Foo\nBar", wait: 0.9)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -174,7 +173,7 @@ Capybara::SpecHelper.spec '#assert_no_text' do
|
||||||
@session.visit('/with_html')
|
@session.visit('/with_html')
|
||||||
expect do
|
expect do
|
||||||
@session.assert_no_text('Lorem')
|
@session.assert_no_text('Lorem')
|
||||||
end.to raise_error(Capybara::ExpectationNotMet, /\Aexpected not to find text "Lorem" in "This is a test Header Class.+"\Z/)
|
end.to raise_error(Capybara::ExpectationNotMet, /\Aexpected not to find text "Lorem" in "This is a test.*"\z/)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should be true if scoped to an element which does not have the text" do
|
it "should be true if scoped to an element which does not have the text" do
|
||||||
|
|
|
@ -40,11 +40,20 @@ Capybara::SpecHelper.spec '#assert_title' do
|
||||||
end.to raise_error(Capybara::ExpectationNotMet, 'expected "with_js" to include "monkey"')
|
end.to raise_error(Capybara::ExpectationNotMet, 'expected "with_js" to include "monkey"')
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should normalize given title" do
|
it "should not normalize given title" do
|
||||||
@session.assert_title(' with_js ')
|
@session.visit('/with_js')
|
||||||
|
expect { @session.assert_title(' with_js ') }.to raise_error(Capybara::ExpectationNotMet)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should normalize given title in error message" do
|
it "should match correctly normalized title" do
|
||||||
|
uri = Addressable::URI.parse('/with_title')
|
||||||
|
uri.query_values = { title: ' with space title ' }
|
||||||
|
@session.visit(uri.to_s)
|
||||||
|
@session.assert_title(' with space title')
|
||||||
|
expect { @session.assert_title('with space title') }.to raise_error(Capybara::ExpectationNotMet)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not normalize given title in error message" do
|
||||||
expect do
|
expect do
|
||||||
@session.assert_title(2)
|
@session.assert_title(2)
|
||||||
end.to raise_error(Capybara::ExpectationNotMet, 'expected "with_js" to include "2"')
|
end.to raise_error(Capybara::ExpectationNotMet, 'expected "with_js" to include "2"')
|
||||||
|
|
|
@ -29,14 +29,9 @@ Capybara::SpecHelper.spec '#has_text?' do
|
||||||
expect(@session).to have_text('exercitation ullamco laboris')
|
expect(@session).to have_text('exercitation ullamco laboris')
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should ignore extra whitespace and newlines" do
|
it "should search correctly normalized text" do
|
||||||
@session.visit('/with_html')
|
@session.visit('/with_html')
|
||||||
expect(@session).to have_text('text with whitespace')
|
expect(@session).to have_text('text with whitespace')
|
||||||
end
|
|
||||||
|
|
||||||
it "should ignore whitespace and newlines in the search string" do
|
|
||||||
@session.visit('/with_html')
|
|
||||||
expect(@session).to have_text("text with \n\n whitespace")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should be false if the given text is not on the page" do
|
it "should be false if the given text is not on the page" do
|
||||||
|
|
|
@ -119,7 +119,7 @@ Capybara::SpecHelper.spec "node" do
|
||||||
@session.visit('/with_js')
|
@session.visit('/with_js')
|
||||||
@session.find(:css, '#existing_content_editable_child').set('WYSIWYG')
|
@session.find(:css, '#existing_content_editable_child').set('WYSIWYG')
|
||||||
expect(@session.find(:css, '#existing_content_editable_child').text).to eq('WYSIWYG')
|
expect(@session.find(:css, '#existing_content_editable_child').text).to eq('WYSIWYG')
|
||||||
expect(@session.find(:css, '#existing_content_editable_child_parent').text).to eq('Some content WYSIWYG')
|
expect(@session.find(:css, '#existing_content_editable_child_parent').text).to eq("Some content\nWYSIWYG")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -51,9 +51,22 @@ Capybara::SpecHelper.spec '#text' do
|
||||||
after { Capybara.default_selector = :xpath }
|
after { Capybara.default_selector = :xpath }
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should strip whitespace" do
|
it "should be correctly normalized when visible" do
|
||||||
@session.visit('/with_html')
|
@session.visit('/with_html')
|
||||||
@session.find(:css, '#second')
|
el = @session.find(:css, '#normalized')
|
||||||
expect(@session.find(:css, '#second').text).to match(/\ADuis aute .* text with whitespace .* est laborum\.\z/)
|
expect(el.text).to eq "Some text\nMore text\nAnd more text\nEven more text on multiple lines"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be a textContent with irrelevant whitespace collapsed when non-visible" do
|
||||||
|
@session.visit('/with_html')
|
||||||
|
el = @session.find(:css, '#non_visible_normalized', visible: false)
|
||||||
|
expect(el.text(:all)).to eq "Some textMore text And more text Even more text on multiple lines"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should strip correctly" do
|
||||||
|
@session.visit('/with_html')
|
||||||
|
el = @session.find(:css, '#ws')
|
||||||
|
expect(el.text).to eq " "
|
||||||
|
expect(el.text(:all)).to eq " "
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -144,6 +144,15 @@ class TestApp < Sinatra::Base
|
||||||
erb :with_html, locals: { referrer: request.referrer }
|
erb :with_html, locals: { referrer: request.referrer }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
get '/with_title' do
|
||||||
|
<<-HTML
|
||||||
|
<title>#{params[:title] || 'Test Title'}</title>
|
||||||
|
<body>
|
||||||
|
<svg><title>abcdefg</title></svg>
|
||||||
|
</body>
|
||||||
|
HTML
|
||||||
|
end
|
||||||
|
|
||||||
get '/:view' do |view|
|
get '/:view' do |view|
|
||||||
erb view.to_sym, locals: { referrer: request.referrer }
|
erb view.to_sym, locals: { referrer: request.referrer }
|
||||||
end
|
end
|
||||||
|
|
|
@ -153,3 +153,23 @@ banana</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id='1escape.me' class="2escape">needs escaping</div>
|
<div id='1escape.me' class="2escape">needs escaping</div>
|
||||||
|
|
||||||
|
<div id="normalized">
|
||||||
|
Some text<div>More text</div>
|
||||||
|
<div> And more text</div>
|
||||||
|
Even more text
|
||||||
|
|
||||||
|
on multiple lines
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="non_visible_normalized" style="display: none">
|
||||||
|
Some text<div>More text</div>
|
||||||
|
<div> And more text</div>
|
||||||
|
Even more text
|
||||||
|
|
||||||
|
on multiple lines
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="ws">
|
||||||
|
                   
|
||||||
|
</div>
|
||||||
|
|
Loading…
Add table
Reference in a new issue