Napybara
So you're writing an integration test for the following page:
<html>
<body>
<ul class='message-list'>
<li class="message" id="message-1">Hello world!</li>
<li class="message" id="message-2">Kamusta mundo!</li>
</ul>
<form class='new-message'>
<div class="message-row" />
<label for='message'>Message</label>
<input id='message' type='text' name='message'>
</div>
<input type='submit' value='Send'/>
</form>
</html>
Wouldn't it be nice if you can write test helpers so that they followed the page structure?
.visit!
.form..text_field.get.set 'Hello World!'
.form.submit!
expect(.(Message.find(1))).to have_content('Hello world!')
expect(.(Message.find(2))).to have_content('Kamusta mundo!')
expect(.[0]).to have_content('Hello world!')
expect(.[1]).to have_content('Kamusta mundo!')
With Napybara, now you can!
Napybara::Element.new and #get
First off, let's wrap the Capybara session in a Napybara element:
let(:messages_page) do
::Element.new(self)
end
In Rails integration tests which use Capybara, self usually is the Capybara session.
You can get the capybara element wrapped by the Napybara element with
Napybara::Element#get:
expect(.get).to eq(self)
Finding by selector
You can add finders to the Napybara page with Napybara::Element#finder:
let(:messages_page) do
::Element.new(self) do |page|
page.finder :form, 'form.new-message'
end
end
# ...
expect(.form.get['class']).to eq('new-message')
Finding by object
In order to find an element representing a particular ruby object, you need to add a separate selector which incorporates the ruby object's id:
let(:messages_page) do
::Element.new(self) do |page|
page.finder :message, '.message', '#message-{id}'
end
end
let(:some_message) do
Message.find(1)
end
# ...
expect(.().get['id'])
.to eq("messages-#{some_message.id}")
In the above example, the message finder looks for an element matching the
given selector (#message-{id}) with some_message's id (1). So it ends up
looking for "#message-1".
If the ruby object is identified by a method other than the object's id, you can replace {id} with the method e.g. {name}, {to_s}.
Checking if an element exists
Napybara::Element#finder also adds has_ and has_no_ methods to the element.
With the Napybara elements above, you can call:
expect(.has_form?).to be_true
expect().to have_form
expect(.().to be_true
expect().to ()
= Message.find(3)
expect(.()).to be_true
expect().to ()
Due to the magic that Capybara does when finding elements in a Ajaxified page,
it's recommended to call expect(element).to have_no_... instead of
expect(element).to_not have..., since the former relies on Capybara's Ajax-
friendly has_no_css? method.
Finding all elements matching a selector
Finally, Napybara::Element#finder adds a pluralized version of the finder. For example,
let(:messages_page) do
::Element.new(self) do |page|
page.finder :message, '.message'
end
end
# ...
expect(.[0].get.text).to eq("Hello world!")
expect(.[1].get.text).to eq("Kamusta mundo!")
Napybara uses ActiveSupport to get the plural version of the finder name.
Adding custom methods to a Napybara element
We can add new methods to a Napybara element with plain Ruby:
let(:messages_page) do
::Element.new(self) do |page|
def page.visit!
get.visit get.
end
end
end
# ...
.visit!
Extending a Napybara element with a module
Adding the same methods to multiple Napybara elements? You can share the methods in a module:
module PageExtensions
def visit!
get.visit get.
@visited = true
end
def visited?
!! @visited
end
end
let(:messages_page) do
::Element.new() do |page|
page.extend PageExtensions
end
end
# ...
.visit!
expect().to be_visited
Putting it all together
Oh yeah, the "N" in Napybara stands for nesting. Here's how you can define the helpers at the start of this README:
let(:messages_page) do
::Element.new() do |page|
def page.visit!
get.visit get.
end
page.finder :form, 'form.new-message' do |form|
def form.submit!
.get.click
end
form.finder :message_row, '.message-row' do |row|
row.finder :text_field, 'input[type=text]'
end
form.finder :submit_button, 'input[type=submit]'
end
page.finder :message, '.message-list .message', '#message-{id}'
end
end
Installation
$ gem install
Contributing
I'm still looking for ways to improve Napybara's DSL. If you have an idea, a pull request would be awesome :)