Here are a few common patterns that will probably lead to flaky specs. If you notice them in your specs, please make sure that you have not introduced a flaky spec.
Using RSpec matchers
One rule of thumb I try to follow in capybara tests is using capybara matchers and not plain rspec matchers.
One example:
visit(some_page)
text_field = find('.textfield')
expect(text_field['value']).to match /pattern/
This can work, but is too brittle and flaky. match
will not retry or synchronize the value of text_field
.
The equivalent code with a capybara matcher:
visit(some_page)
expect(page).to have_field('.textfield', with: /pattern/)
have_field
will retry for and synchronize the text_field.
Using .limit(N)
If your asserting a list of records that is somehow limited via a .limit
clause, make sure your order is deterministic, otherwise you'll receive different records in different runs. For example:
# User(id bigint, ends_on: Date)
User.order(:ends_on).limit(5) # Bad, because the order is not deterministic if a lot of users end on the same date
User.order(:ends_on, :id).limit(5) # Better, because it takes the id into account in such a case
Using an external ui component library
If you're using an external UI component library, you've probably introduced a lot of flakyness to your spec. UI Components often introduce autoplay features, animations, take longer to initialize, are lazy loaded etc. Make sure you have a safe way to assert against the most glaring issues in your component. Often times, there's a good aria-*
selector you can assert against. E.g.
expect(page).to have_selector('.modal[aria-hidden="false"]') # wait for the modal
expect(page).to have_css("[data-datepicker-loaded='true']") # make sure the datepicker is loaded before interacting with it