I'm trying to write a custom RSpec matcher to be used in Rails system tests, running under Capybara — the idea is to match text while ignoring certain <span>
tags with it.
This is the matcher:
RSpec::Matchers.define :have_cleaned_text do |text|
match do |content|
content.body.include?(text) || content.body.gsub(%r{<span class=["']separator["'].*?>.*?</span>}, ' ').include?(text)
end
end
and the HTML body of the page being tested:
<h1>Test Page</h1>
<div id='constraint'>
<p>Clean text</p>
<p>Broken<span class='separator'>|<span>text</p>
</div>
The first two tests pass:
within('#constraint') do
expect(page).to have_cleaned_text('Clean text')
expect(page).to have_cleaned_text('Broken text')
expect(page).not_to have_cleaned_text('Test Page') # fails!
end
…but the third fails, as have_cleaned_text
is ignoring the within
block and testing against the whole page.
How can I make my matcher respect the within
block? I would have expected it to have been passed as content
, not the whole page…
In your example page
is a Capybara session (which contains its current scope). When you call body
(source
and html
are aliases) on a session it returns the HTML source of the document. Since you're looking for the HTML source of an element you need something like
RSpec::Matchers.define :have_cleaned_text do |text|
match do |session|
session.current_scope[:innerHTML].include?(text) || session.current_scope[:innerHTML].gsub(%r{<span class=["']separator["'].*?>.*?</span>}, ' ').include?(text)
end
end
Note that a matcher written like that won't have any waiting/retrying behavior, like the Capybara provided matchers, so you need to ensure your page is loaded/stable before using it.