Home > OS >  How do I make assertions consistently wait until a turbo frame has been updated?
How do I make assertions consistently wait until a turbo frame has been updated?

Time:12-10

My system tests are experiencing a race condition leading to inconsistent test results. Sometimes the turbo frame is updated before my assertion (test passes) and sometimes afterward (test fails).

View:

<div data-controller="filter">
  <%= form_with url: root_path, method: :get, data: { turbo_frame: "intakes", filter_target: "form", action: "change->filter#submit" } do %>
      <%= select_tag "country", options_for_select(@countries) %>
  <% end %>

  <%= turbo_frame_tag "intakes" do %>
      <table>
        <% @family_intakes.each do |family_intake| %>
          <tr>
            <td><%= family_intake.full_name %></td>
          </tr>
        <% end %>
      </table>
  <% end %>
</div>

This Stimulus controller submits my form on change event (ultimately updating the turbo frame):

export default class extends Controller {
  static targets = ["form"];

  submit(event) {
    this.formTarget.requestSubmit();
  }
}

Test:

class FamilyIntakesTest < ApplicationSystemTestCase
  test "Only intakes from selected country are displayed" do
    login
    select "Afghanistan", from: "country"
    assert_selector "td", text: family_intakes(:manizha).first
    assert_selector "td", text: family_intakes(:sayed).first
    refute_selector "td", text: family_intakes(:mohammad).first
  end
end

Error:

Failure:
FamilyIntakesTest#test_Only_intakes_from_selected_country_are_displayed [/home/eric/<redacted>/test/system/family_intakes_test.rb:31]:
expected not to find visible css "td" with text "Mohammad", found 1 match: "Mohammad Ahmad". Also found "Manizha Ahmadi", "Sayed Shinwari", which matched the selector but not all filters.

CodePudding user response:

My current solution is to add a turbo-response class to the table being updated inside my turbo frame and to scope my test assertions to that css class. That way I can't get a false negative if my assertion runs before before the frame is updated.

View update

<table turbo-response" if turbo_frame_request? %>">

Helper:

module ApplicationHelper
  def turbo_frame_request?
    request.headers["Turbo-Frame"]
  end
end

Test:

class FamilyIntakesTest < ApplicationSystemTestCase
  test "Only intakes from selected country are displayed" do
    login
    select "Afghanistan", from: "country"
    within('.turbo-response') do
      assert_selector "td", text: family_intakes(:manizha).first
      assert_selector "td", text: family_intakes(:sayed).first
      refute_selector "td", text: family_intakes(:mohammad).first
    end
  end
end

Also, during the debugging process, to prevent any "accidental" success of my tests caused by the turbo frame just "happening" to load first, I intentionally slowed down my JavaScript form submission:

submit(event) {
  setTimeout(() => this.formTarget.requestSubmit(), 1500);
}

The slowdown should be less than Capybara.default_max_wait_time which is typically 2 seconds or else Capybara really will give up. I went with 1.5 seconds or 1500 milliseconds. Just make sure to remove the setTimout later! Getting my tests to pass with or without that slowdown gives me way more confidence that they are passing for the right reasons!

  • Related