I was working on a codebase with a custom Capybara matcher that looked for a toggle field using two selectors:
page.has_css?("label[for='#{field_id}']", text: name) ||
page.has_css?("label:has(##{field_id})", text: name)
The idea is simple: try to find the label by for attribute, and if that
doesn’t match, try finding a label that wraps the input.
The “problem” with that is how Capybara works. has_css? is a waiting method,
so if the selector doesn’t match immediately, Capybara will keep retrying until
Capybara.default_max_wait_time is reached. On dev that was 5 seconds, on CI,
30.
So every time the first selector didn’t match, Ruby waited the full timeout before even trying the second one. With 4 tests hitting this matcher, that’s up to 2 minutes of CI time spent just… waiting.
The fix is combining both selectors into a single CSS query using a comma:
page.has_css?("label[for='#{field_id}'], label:has(##{field_id})", text: name)
CSS commas work as an OR and Capybara now checks both selectors in a single pass. If either matches, it returns immediately. No more burning a full timeout cycle on the first selector before trying the second.
But even better, we can skip the manual CSS altogether and let Capybara do what it already knows how to do:
page.has_selector?(:label, name, for: field_id)
Capybara’s built-in :label selector already handles both cases, labels with a
for attribute and labels wrapping the input, in a single query. No custom code
needed!