Unable to select an element from a dropdown after programmatically refreshing the page

95 views
Skip to first unread message

Jonathan Jalal

unread,
Sep 1, 2021, 3:38:29 AM9/1/21
to Capybara
Hello everyone,

Capybara Version: 3.32.2
Driver Information: Selenium 3.142.7 with headless_chrome and Chrome browser.

Context
I have a test suite running on a page with a form where one can upload multiple documents. To upload a document, selecting an associated nature from a dropdown list (e.g. "ID card" or "Passport" for a proof of Identity document) is mandatory. 
If a document nature is selected and the user chooses a document to upload, the form is automatically submitted using Rails.fire(formId, "submit"), which triggers a page refresh with SSR (via a .js.erb file).

Now, this page has 5 documents, which all have to be uploaded before being able to go to the next page. So this is what my test looks like :

select nature1, from: dropdown_name1 
attach_file(input_id1, test_file_path, visible: false)
...
select nature5, from: dropdown_name5
attach_file(input_id5, test_file_path, visible: false)

Unfortunately, it seems that after uploading a document, Capybara tries "too quickly" to select the next document's nature even though the page refresh isn't fully done yet. Which ends up failing the test suite as it tries performing actions on the next page despite not having visited it yet. Note that this behavior seems to be a bit flaky.

To mitigate this problem, I tried using many things such as :
  • find("#{dropdown_id}").find(:option, nature).select_option instead of using the select matcher
  • using expect statements to make sure the elements are present before interacting with them
  • increasing the Capybara default max wait time
  • using a wait_for_ajax helper as implemented here
  • moving each select + attach_file within a using_wait_time block
  • checking that the dropdown was actually populated with options before selecting one; which it is.
None of these worked, so I had to go with a sleep 0.5 in between each upload, and that does the trick. But I am not happy with this solution; which is why I am writing to you hoping there can be a cleaner way of achieving this.

Please do not hesitate to ask if you need further information.

Kind regards,
Jonathan

Thomas Walpole

unread,
Sep 1, 2021, 10:55:54 AM9/1/21
to Capybara
Hi Jonathan - TLDR; the answer to all synchronization type issues is set an expectation for what on the page indicates it's ready for the next step 

In more detail I'll explain why each of the things you tried didn't make any difference

- find("#{dropdown_id}").find(:option, nature).select_option instead of using the select matcher

This approach always confuses me, and if you can explain why you thought it would make a difference maybe I can use that to improve the documentation.  If a higher level Capybara method is succeeding (the method call isn't erroring) then breaking the exact same behavior down into more basic things isn't going to change anything.  That's how the higher level methods are implemented so why would you expect doing this to change anything?

- using expect statements to make sure the elements are present before interacting with them

Good thought but the elements are already there, otherwise the initial methods would have failed when trying to select

- increasing the Capybara default max wait time

This is the maximum wait time -- unless something was timing out then it's not going to make any difference allowing to wait longer because it's already finding everything it was asked to find

- using a wait_for_ajax helper as implemented here

Just no no no no no no - This is *never the right answer (* 99.99999% of the time)

- moving each select + attach_file within a using_wait_time block

This is just the same as increasing Capybara default max wait time - just locally - it increase before the block is run and resets it after

- checking that the dropdown was actually populated with options before selecting one; which it is.

Already known since the `select` call isn't failing

-----------------------------------

What you need to do between each of these file uploads is look for whatever on the page indicates the previous submission has completed.  That could be a flash message, it could be a counter of how many files are already uploaded shown on the page, it could be a specific number of elements with a class that indicates the files are uploaded, etc

select nature1, from: dropdown_name1 
attach_file(input_id1, test_file_path, visible: false)

expect(page).to have_text('File uploaded') # look for flash message that indicates the upload succeeded
expect(page).not_to have_text('File uploaded') # wait for the message to go away (so it's not still there after the next step

select nature2, from: dropdown_name2
attach_file(input_id2, test_file_path, visible: false)

...

Or  if the page happened to render already uploaded files in divs with the class 'uploaded_file'

expect(page).not_to have_css('.uploaded_file')

select nature1, from: dropdown_name1 
attach_file(input_id1, test_file_path, visible: false)

expect(page).to have_css('.uploaded_file', count: 1)

select nature2, from: dropdown_name2
attach_file(input_id2, test_file_path, visible: false)

expect(page).to have_css('.uploaded_file', count: 2)

...

It's all about figuring out what on/in the page indicates it's ready for the users next step

Jonathan Jalal

unread,
Sep 8, 2021, 4:23:06 AM9/8/21
to Capybara
Hi Thomas,

Thank you so much for the time and pedagogy you put in your answer. What you said in the TL;DR helped me understand this aspect about system tests pertaining to synchronization which I had not thought about before.
The approach I ended up taking was one where I checked that the nature dropdown associated to a document was disabled (i.e., the document was uploaded successfully) :

select nature1, from: dropdown_name1 
attach_file(input_id1, test_file_path, visible: false)
expect(page).to have_field(dropdown_name1, disabled: true)

Regarding your comment about why I tried using find("#{dropdown_id}").find(:option, nature).select_option, that was because I thought that maybe the Capybara select matcher was not going to wait for the Capybara default wait time before failing, while I knew the find matcher was going to wait. Note that I had not checked the implementation of the select matcher. So I guess it's not an issue with the documentation, but just with my own understanding of Capybara matchers :)

Take care,
Jonathan

Reply all
Reply to author
Forward
0 new messages