nimmolo
nimmolo

Reputation: 583

Capybara `attach_file` - Turbo response fires but does not append to page

I've got a Capybara test (using the Cuprite driver) for a file field with attach_file that has worked for a year. In the UI, a Stimulus controller catches the click, loads the file, sends a request to a Rails controller action with some of the file's attributes, retrieves a template as an HTML response, and "manually" appends the template to the page, populating the img src with the attached file's data URI, and other fields with some file attributes like the name and the size.

Currently I'm refactoring that controller to send a turbo_stream response. Turbo prepends the blank HTML to a div on the page; then the Stimulus controller listens for the targetConnected and populates the template as before. In the browser, everything works fine.

But in the system test, the assertion for the presence of the appended element is failing. In headless mode, nothing seems to happen. There are no errors in Rails or in the console.

First I thought attach_file, or the click on the input, wasn't working, but I now know it does attach the file, and it does send the request to the controller with the correct file attributes as params. If I break in the controller action everything is OK, including the params, and if I break in the turbo_stream template everything is still OK.

I now realize that although the file is getting attached and Turbo is firing the response, the stream HTML is never prepended to the div, even when the assertion adds wait: 6 (or 9, or whatever). Oddly, the assertion passes maybe one in 20 times; I've tried everything I can think of.

What could be going on?

# system test
label = first(".file-field")
scroll_to(label, align: :center)
attach_file("select_images_button",
            Rails.root.join("test/images/#{filename}"),
            make_visible: true)

assert_selector(".added_image_wrapper", text: /image_caption/)
# controller#new
<%=
turbo_stream.prepend(
  "added_images",
  partial: "controller/form/images/carousel_item",
  locals: { upload: true, index: params[:index],
            img_id: params[:img_id],
            img_file_name: params[:img_file_name],
            img_file_size: params[:img_file_size] }
)
%>

The template for the carousel item does indeed have the class .added_image_wrapper.

Upvotes: 0

Views: 79

Answers (1)

nimmolo
nimmolo

Reputation: 583

As usual, the problem wasn't exactly Capybara. The problem lies in the difference between my test expectations and the sequence of browser calculations, which i would call humanly unpredictable.

In this case I had CSS applied on the .carousel-inner div to hide the carousel if it had no children.

.carousel-inner:not(:has(.item)) {
  display: none;
}

Very unexpectedly, the chromium browser did not update the display according to this CSS rule quickly enough, even when Turbo appends the child element to the div. At the point of failure, the child was present in the DOM, but the parent was still display: none, for the next 20 assertions. sleep(x) didn't help either.

I had to change many subsequent expectations to look for hidden selectors, to get the test to pass.

You have to pay very close attention to every possibility when you're writing system tests.

Upvotes: 1

Related Questions