1
0
Fork 0
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:
Thomas Walpole 2018-03-12 12:34:21 -07:00 committed by GitHub
commit 90874d88cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 115 additions and 47 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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: ' &nbsp; with space &nbsp;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"')

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 &nbsp;&nbsp; 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 &nbsp;&nbsp; text
on multiple lines
</div>
<div id="ws">
&#x20;&#x1680;&#x2000;&#x2001;&#x2002; &#x2003;&#x2004;&nbsp;&#x2005; &#x2006;&#x2007;&#x2008;&#x2009;&#x200A;&#x202F;&#x205F;&#x3000;
</div>