calebhearth / formulaic

Simplify form filling with Capybara

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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.