RSpec::HTML provides a simple object interface to HTML responses from RSpec Rails request specs.
Add the gem to your Gemfile
:
gem 'rspec-html', '~> 0.2.5'
And rebuild your bundle:
$ bundle install
Require the gem in your spec_helper.rb
:
# spec/spec_helper.rb
require 'rspec/html'
Several matchers are provided to identify text and HTML elements within the DOM. These matchers can only be used with the provided object interface.
The top-level object document
is available in all tests which reflects the current response body (e.g. in request specs).
If you need to parse HTML manually you can use the provided parse_html
helper and then access document
as normal:
before { parse_html('<html><body>hello</body></html>') }
it 'says hello' do
expect(document.body).to contain_text 'hello'
end
To navigate the DOM by a sequence of tag names use chained method calls on the document
object:
expect(document.body.div.span).to contain_text 'some text'
To select an element matching certain attributes pass a hash to any of the chained methods:
expect(document.body.div(id: 'my-div').span(align: 'left')).to contain_text 'some text'
CSS classes are treated as a special case: to select an element matching a set of classes pass the class
parameter:
expect(document.body.div(id: 'my-div').span(class: 'my-class')).to contain_text 'some text'
expect(document.body.div(id: 'my-div').span(class: 'my-class my-other-class')).to contain_text 'some text'
Classes can be provided in any order, i.e. 'my-class my-other-class'
is equivalent to 'my-other-class my-class'
.
To select an element that includes a given text string (i.e. excluding mark-up) use the text
option:
expect(document.body.div(text: 'some text').input[:value]).to eql 'some-value'
To select an attribute from an element use the hash-style interface:
expect(document.body.div.span[:class]).to contain_text 'my-class'
expect(document.body.div.span['data-content']).to contain_text 'my content'
To select an index from a set of matched elements use the array-style interface (the first matching element is 1
, not 0
):
expect(document.body.div[1].span[1][:class]).to contain_text 'my-class'
To test if a matching element was found use the exist
matcher:
expect(document.body.div[1]).to exist
expect(document.body.div[4]).to_not exist
To test the length of matched elements use the #size
or #length
method:
expect(document.body.div.size).to eql 3
expect(document.body.div.length).to eql 3
If you need something more specific you can always use the Nokogiri #xpath
and #css
methods on any element:
expect(document.body.xpath('//span[@class="my-class"]')).to contain_text 'some text'
expect(document.body.css('span.my-class')).to contain_text 'some text'
To simply check that an XPath or CSS selector exists use have_xpath
and have_css
:
expect(document.body).to have_css 'html body div.myclass'
expect(document.body).to have_xpath '//html/body/div[@class="myclass"]'
Use the contain_text
matcher to locate text within a DOM element. All mark-up elements are stripped when using this matcher.
expect(document.body.form).to contain_text 'Please enter your password'
Use the contain_tag
matcher to locate DOM elements within any given element. This matcher accepts two arguments:
- The tag name of the element you want to match (e.g.
:div
); - (Optional) A hash of options. All options supported by the object interface can be used here.
Without options:
expect(document.div(class: 'my-class')).to contain_tag :span
With options:
expect(document.form(class: 'my-form')).to contain_tag :input, name: 'email', class: 'email-input'
Feel free to make a pull request.