Using Capybara matchers on arbitrary HTML content

Apart from using it for integration tests, it’s really easy to use Capybara outside of browser tests. And that means, on any HTML content (string) you have.

Let’s say we have an API endpoint that returns some HTML in its body. With a default RSpec setup it is tempting to write (and we indeed have written):


describe "GET /form" do
  it "includes a surnames field" do
    get "/form"
    expect(response.body).to include(
     '<input type="text" name="surnames" id="surnames" value="Doe" />'
    )
  end
end

This works fine, but if we need to test for absence of an element, it gets trickier:


describe "GET /form" do
  context "when we know stuff" do
    if "does not include a surnames field" do
      get "/form"
      expect(response.body).to_not include(
        '<input type="text" name="surnames" id="surnames" value="Doe" />'
      )
    end
  end
end

If the code responsible of generating the HTML slightly changes, this test may pass for the wrong reasons.

A more robust solution is to use an HTML-aware comparison tool. We had Capybara set up for feature specs, we knew how to use Capybara matchers, and we wanted to use it again for this–if possible. Looking around a bit on the documentation we found out about Capybara::Node::Simple: it offers the interface needed for RSpec matchers without any browser interactions.


let(:page) { Capybara::Node::Simple.new(response.body) }

describe "GET /form" do
  context "when we don't know stuff" do
    if "includes a surnames field" do
      expect(page).to have_field("surnames", type: "text", with: "Doe")
    end
  end

  context "when we know stuff" do
    if "does not include a surnames field" do
      get "/form"
      expect(page).to_not have_field("surnames")
    end
  end
end

The test now is more expressive and robust.

Share this:

Leave a Reply

Your email address will not be published. Required fields are marked *