Filling in dropdown is very slow
varyform opened this issue · comments
I had following in my spec:
factory :business_details do
name Forgery(:name).company_name
phone Forgery(:address).phone
country Forgery(:address).country
state Forgery(:address).state
city Forgery(:address).city
zip Forgery(:address).zip
address_1 Forgery(:address).street_address
end
fill_form :business_details, attributes_for(:business_details)
It was taking ~10 seconds.
All fields are string inputs except 1 country dropdown.
After refactoring to:
fill_form :business_details, attributes_for(:business_details).except(:country)
select attributes_for(:business_details)[:country], from: 'Country'
It took less than 1 second.
Interesting. Is this in a js spec? If possible, I'd love to get either the markup being tested against or a sanitized version of that if you prefer. I'm going to use that to put together a benchmark spec to ensure that once we speed this up, it stays that way.
Yep, it only happens in JS specs. I can post examples, but it won't help.
The issue is in StringInput#fill.
It tries to find text input first, then radio and finally select.
page.has_selector?
does wait_conditionally_until
twice in first 2 if/else
branches before it can finally find a select.
My Capybara.default_wait_time
is 5 seconds, that's why it takes 10 seconds for me.
Yeah, that's what I figured as well. I'll see if I can reproduce with the existing fixture in a benchmark. My only thought is that we can add a configuration to Formulaic or a flag to change the wait time, then change Capybara.default_wait_time
in a block and reset when done with the spec.
Actually it occurs to me that that might solve your problem now, and if it does we can incorporate it more nicely. Try this, if you don't mind (and if your form isn't loading dynamically - there's nothing to be done in that case):
def with_wait_time(time)
old_wait_time = Capybara.default_wait_time
Capybara.default_wait_time = time
yield
ensure
Capybara.default_wait_time = old_wait_time
end
# then in your spec
with_wait_time(1) do
fill_form :business_details, attributes_for(:business_details)
end
You can play with that wait time to see how low you can get it - offhand I'm not sure if Capybara can deal with a float for that value. 0 might work as well.
Yeah, I did similar thing and it worked well :)
def fill_form_without_waiting(*args)
using_wait_time 0 do
fill_form *args
end
end
It might not be pretty, but wouldn't you be able to craft a "big and dirty" xpath statement that would match all these possibilities and then just inspect the result (rather than issuing eg. three has_selector?
queries?
I might be missing something with that, but I'll paste below some similar (hacky) code I'm currently using to inspect the values of existing forms
def input_value(name)
page.find(:xpath, "//input[@name='#{name}']").value
end
def textarea_value(name)
page.find(:xpath, "//textarea[@name='#{name}']").value
end
def radio_value(name)
page.find(:xpath, "//input[@name='#{name}'][@type='radio' and @checked]").value
end
def checked_value(name)
page.find(:xpath, "//input[@name='#{name}'][@type='checkbox']")['checked']
end
def selected_value(name)
page.find(:xpath, "//select[@name='#{name}']/option[@selected]").value
end
def selected_value_grouped(name)
page.find(:xpath, "//select[@name='#{name}']/optgroup/option[@selected]").value
end
In the example I pasted they are all separate functions, but it seems possible to merge the xpaths if you know what you are doing with it (and I most certainly don't!)
Hope this is helpful - I'm not sure if just changing the Capybara timeouts is a better (and certainly simpler) solution...
@varyform @tobypinder What I'm actually leaning toward is making the timeout off-by-default even in javascript tests. To do this, I'd do basically what Oleh and I discussed inside of fill_form
somewhere.
In my experience, the case where a form is being added by html is exceptional, and I'd rather optimize for that case and provide a configuration option to not override the setting.
I see your point, Toby, but I'm not entirely convinced that the sanctity of Capybara timeouts is more important than the time it takes to run a suite.
Fair enough: I have little knowledge of the internals of Capybara, so if that's a viable option then it's by far the neater option.
You can fix this by combining the selector into one call. If you remove the wait period, this will cause intermittent failures in all JavaScript capybara suites, and not just ones that use Ajax.