Skip to content

Capybara UI Best Practices

Adam edited this page Jan 21, 2016 · 1 revision

Suggestions for success and happiness in the Capybara-UI world.

Keep Widgets Skinny

Widgets can grow fat with custom methods and sub widgets, which leads to more complex code.

# an overly complex widget
list :todo_items, '.todo-items', do
  widget :todo_item, '.todo-item'

  def find_item(text)
    root.items.detect { |item| item.text == text }
  end
end

def select_item(text)
  widget(:todo_items).find_item(text).click
end

This example has an additional drawback, in that if you are adding items to the list via javascript, your find_item method will not wait for the new item to appear.

As a general rule, if you're implementing an element finder in Capybara-UI, reach for a Proc instead.

We can improve this example by focusing instead on the list items, resulting in less code and a widget that waits to find any asynchronously added list items.

# a better widget
widget :todo_item, -> (text) ['.todo-item', text: text]

def select_item(text)
  click :todo_item, text
end

Our new widget also works nicely with see in RSpec.

expect(roles.user).to see :todo_item, 'Buy Milk'

Role Methods Are for Business Logic

Role methods allow you to define clear steps your role may take, and act as a form of documentation for how the code works. They can also help you DRY up repetitive code.

Consider this example in RSpec.

feature 'when a user creates a new todo item' do
  let(:user) { roles.user }

  scenario 'with valid information, the item appears in the list' do
    visit new_items_path
    user.widget(:new_todo_item_form).submit_with(item_name: 'Buy Milk')
    
    expect(user.widget?(:todo_item, 'Buy Milk')).to eq(true)
  end
end

Now let's look at that example when we use role methods named after our business logic.

# much better
feature 'when a user creates a new todo item' do
  let(:user) { roles.user }

  scenario 'with valid information, the item appears in the list' do
    user.view_new_item_page
    user.create_item('Buy Milk')
    
    expect(user).to see :todo_item, 'Buy Milk'
  end
end