In general I like how Capybara works. I just find it hard to believe that it's really a good idea to codify user-visible text in the tests; e.g.
click_link('Delete', match: :first)
OK, so I write my app with tests like this, and then my designer comes through, or the brand/marketing person sees "Delete" and has a fit because we do not "delete" things, we "remove" them (or whatever - point is, it's arbitrary). So they go through and make arbitrary changes to the content of the buttons and links in my app, and now all my tests are broken even though the app works fine. They're not really coders, so it's not really their responsibility to know what to do with my tests, and really, I don't think my tests should care what text they choose to change. They shouldn't really care if I need to translate the whole shebang to some other language either.
What's a sane way to address this? I was thinking I could just add a CSS class to each testable element and look for that, but that seems likely to run afoul of the designer. Can I just add otherwise-meaningless metadata for my tests to cue on? Or am I looking at this the wrong way?
1) The easiest way I think to use find method with id of element or with xpath and click() function:
Here is an example:
feature 'Shall check devise authorization' do
scenario "shall authorize User with correct email and password" do
user = User.find(1)
visit new_user_session_path
fill_in "user[email]", with: user.email
fill_in "user[password]", with: user.password
# click_button "Sign in"
find('input#sign_in_button').click()
# find("//input[@id='sign_in_button']").click()
has_selector? 'input#created_at_first'
end
end
Here click_button "Sign in" the same as
find('input#sign_in_button').click()
[id of element]
and also the same as
find("//input[@id='sign_in_button']").click()
[xpath]
Choose whatever you want.
If you would like to choose element by its class and id, here is the solution:
find('input#sign_in_button.btn.btn-primary.btn-success').click()
find("//input[@class='btn btn-primary btn-success'][@id='sign_in_button']").click()
Also it can be helpful to inspect page capybara#finding. There are a lot of useful information.
2) Another option is to use capybara#scripting.
But it is a little harder, because you need to configure your Capybara driver for testing JS.
To configure your Capybara driver you should add gem 'capybara-webkit' in your Gemfile (you need to have libqtwebkit-dev).
sudo apt-get install libqtwebkit-dev
bundle install
Then place in your spec_helper.rb something like this:
Capybara.current_driver = :webkit
Capybara.run_server = true #Whether start server when testing
#Capybara.server_port = 3000
#Capybara.default_selector = :css #:xpath #default selector , you can change to :css
Capybara.default_wait_time = 5 #When we testing AJAX, we can set a default wait time
Capybara.ignore_hidden_elements = false #Ignore hidden elements when testing, make helpful when you hide or show elements using javascript
Capybara.javascript_driver = :webkit
and the following spec will work correctly:
# -*- encoding : utf-8 -*-
require_relative '../spec_helper'
feature 'Shall check devise authorization' do
scenario "shall authorize User with correct email and password", js: true do
user = User.find(1)
visit new_user_session_path
fill_in "user[email]", with: user.email
fill_in "user[password]", with: user.password
page.execute_script("$('#sign_in_button').click()")
has_selector? 'input#created_at_first'
end
end
The capybara-webkit driver is for true headless testing. It uses QtWebKit to start a rendering engine process. It can execute JavaScript as well. It is significantly faster than drivers like Selenium since it does not load an entire browser
For more details please visit test-javascript-with-capybara-webkit
UPDATE:
Also you can browse this spec from capybara source, which can help you to understand how you can find elements or add selectors:
I usually address this problem by using locales instead of adding text directly into my views. For example:
# config/locales/en.yml
en:
buttons:
delete: &delete "Remove"
my_model:
index:
delete: *delete
show:
delete: *delete
Then I can access the locale in my view. This erb code will render a button to delete my record with the text "Remove":
<!-- app/views/my_models/show.html.erb -->
<%= link_to t(".delete"), my_model_path(@my_model), method: :delete %>
I can use the translation helper in my specs to find the button. To do this, first I need to include the translation helper:
# spec/rails_helper.rb
RSpec.configure do |config|
config.include ActionView::Helpers::TranslationHelper
# ...
end
# spec/features/my_model_spec.rb
RSpec.feature "MyModel", type: :feature do
scenario "clicking a delete button" do
click_link(t("buttons.delete"), match: :first)
end
end
The only trick now is teaching my designers how to use i18n which is its own challenge...