Blaine Lafreniere
Blaine Lafreniere

Reputation: 3600

Capybara not finding content on page when it should?

I'm using Capybara, and I'm selecting an item from a dropdown using this command:

select "Some Option", from: "client_id", visible: :all

This is the view code I'm using to generate the select:

<%= form_with(url: '#', method: 'GET') do |f| %>
    <%= f.select :client_id,
        options_for_select(
            Client.accessible_by(current_ability)
            .map{|c| [c.name, c.id]}, selected_client_id
        ),
        {include_blank: defined?(include_blank) ? include_blank : false},
        {
            class: "form-control selectpicker border",
            id: "client_id",
            onchange: "this.form.submit()",
            data: {'live-search': true, style: "btn-white"}
        } 
    %>
<% end %>

Which renders fine and works as expected when I myself am interacting with it on the page.

I believe the problem stems from this: onchange: "this.form.submit()"

When Capybara chooses an option from the select, it's supposed to submit the form, and then the page is supposed to reload with a query string /?client_id=X, but that is not what's happening and I don't know why.

When I look at the screenshot provided from the error, it appears as though Capybara hasn't even selected anything from the dropdown, it just says "Nothing selected."


Here's the Capybara test:

    def check_grand_total(expected_value)
        visit current_path

        click_link "Balance"

        # it seems as though Capybara is not waiting for this to finish... but I don't know, just my theory
        # this is supposed to trigger: this.form.submit() on the dropdown
        select "Some Client", from: "client_id", visible: :all 

        actual_value = page.first('.grand-total').text
        assert_equal(expected_value, actual_value)
    end

The error:

E

Error:
OrdersTest#test_deposit_orders:
Capybara::ExpectationNotMet: expected to find css ".grand-total" at least 1 time but there were no matches
    test/system/helpers/balance_test_helper.rb:10:in `check_grand_total'
    test/system/orders_test.rb:113:in `block in <class:OrdersTest>'

I'm thinking maybe Capybara isn't actually triggering the change event for some reason.

Upvotes: 0

Views: 1132

Answers (2)

Thomas Walpole
Thomas Walpole

Reputation: 49870

The class names you're specifying, and the fact that you're specifying (visible: :all) imply you may be using some type of JS widget to replace the standard html select element. Is that true? Overriding visibility checking in Capybara actions doesn't make any sense since you shouldn't be interacting with non-visible elements, and depending on exactly which action and driver will either error, or just not do anything. If you are using a replacement widget then you need to interact with the elements created by the widget rather than using select

If you're not using a JS widget then it could just be a case of a badly written test. There are a couple of things right away - first is that you are visiting current_path, second is that you're asserting against text rather than using one of the Capybara provided assertions. The thing to remember is that browser actions occur asynchronously, so when you tell Capybara to choose an option from a select element there is no guarantee the actions triggered by doing that have completed when the select call returns. Because of that you should be using the capybara provided assertions/expectations which include waiting/retrying behavior in order to sync the browser with the tests.

def check_grand_total(expected_value)
    # This visit looks dangerous because it's not guaranteed to have changed 
    # the page when the `visit` returns, which means it may try and click
    # the link on the previous page. You should really be checking for something
    # that wouldn't be on the previous page -- It's also strange that there is a current path since tests should be independent
    visit current_path 

    click_link "Balance" # see above comment

    select "Some Client", from: "client_id"

    # This will wait up to Capybara.default_max_wait_time seconds for 
    # the grand total element to contain the expected text
    assert_selector '.grand-total', exact_text: expected_value
    # If using RSpec it would be
    # expect(page).to have_selector '.grand-total', exact_text: expected_value
end

Upvotes: 2

engineersmnky
engineersmnky

Reputation: 29308

So I think the issue is that the onchange even does not actually fire This "issue" suggests causing focus loss via

fill_in("foo", with: "hello world").send_keys(:tab)

so it may be possible to adapt that to

select("Some Client", from: "client_id", visible: :all).send_keys(:tab)

That being said it also appears that a method exists to force an event on an Element Capybara::Node::Element#trigger so I would start with trying:

select("Some Client", from: "client_id", visible: :all).trigger(:onchange)

Disclaimer: I am not very familiar with capybara so this answer is based on source review and googling. According to Thomas Walpole, who clearly has extensive experience, " trigger isn't supported by most drivers because it doesn't make a lot of sense to use during testing due to it not replicating what a user would actually do."

Upvotes: 1

Related Questions