Wait until enabled button is present #771

Closed
badeball opened this Issue Aug 1, 2012 · 15 comments

Projects

None yet

8 participants

@badeball
badeball commented Aug 1, 2012

Hi,

First of all--let me thank you for a wonderful piece of software!

I am currently creating automated integration tests for an ExtJS application, which rely heavily on Javascript and Ajax. (Maybe that's obvious.) I'm 100% dependant on the wait time defined by Capybara.default_wait_time, as elements may not be present at once, due to Ajax calls not being finished.

However, now I've encountered a similar problem, for which there does not seem to be an elegant solution to. The problem is that the button is present, but it is not enabled at once. So, I need something that will help me select a button from a locator string that is also enabled. I ended up monkey patching Capybara like this.

module Capybara
  class Session
    def click_enabled_button(locator)
      current_node.click_enabled_button(locator)
    end
  end

  module Node
    module Actions
      def click_enabled_button(locator)
        msg = "no enabled button with value or id or text '#{locator}' found"
        find(:xpath, XPath::HTML.enabled_button(locator), :message => msg).click
      end
    end
  end
end

module XPath
  module HTML
    def enabled_button(locator)
      button(locator)[~attr(:disabled)]
    end
  end
end

Do you think this is the right way to do that? Is that something that may be implemented in Capybara? (If so, I guess I could make a pull request.)

@jnicklas
Collaborator
jnicklas commented Aug 2, 2012

It does seem to make sense that we only ever want to click enabled buttons, to be honest. I don't think we should have a separate click_enabled_button action though. I'm torn about this. @joliss, what do you think?

@joliss
Collaborator
joliss commented Aug 2, 2012

Seems reasonable to me to change the default behavior of click_button/click_on to exclude disabled buttons.

@jnicklas
Collaborator
jnicklas commented Aug 2, 2012

How about something like fill_in then? If we disallow it in one place, we should disallow it everywhere, right?

@badeball
badeball commented Aug 2, 2012

Good point. The problem applies to anything that can be disabled, really.

@joliss
Collaborator
joliss commented Aug 2, 2012

Yes, looks like the XPaths for all form interaction methods would have to be updated.

@badeball
badeball commented Aug 7, 2012

@synth, I'm not sure if your problem is related to this. I also have stumbled upon issues where Capybara has simulated user actions before dynamic javascripts has been loaded. I solved it by extending my step definition which navigates the browser.

Given /^(?:|I )am on (.+)$/ do |page_name|
  page_url = $config[:host] + path_to page_name

  unless browser.current_url.include? page_url
    browser.visit page_url
    browser.wait_until do
      browser.evaluate_script "Ext.isReady"
    end
  end
end

You get the gist, right? I'm sure there's something equivalent in RequireJS.

@synth
synth commented Aug 8, 2012

@badeball you're a genius! this has solved the remaining funky issues I was having! I created a helper to handle the various ui cases that I needed to wait for:

https://gist.github.com/3292441

@joliss
Collaborator
joliss commented Aug 8, 2012

@synth Re your gist, you can implement wait_until_overlay_has_popped_up without resorting to evaluate_script by doing something like this (assuming RSpec; untested):

page.should have_css('.div.overlay', visible: true)

It will wait for the element to appear. For the other method, analogously with have_no_css.

@jnicklas jnicklas closed this in dd805d6 Sep 9, 2012
@eostrom
eostrom commented Dec 6, 2012

I agree that you never want to press a disabled button, but it can be useful to find one — you may actually want to verify that a button is present but not enabled.

I don't personally have a use case for this right now, I just ran across it while updating Kelp. I'm just bringing it up for (belated) discussion.

@wapcaplet

From Kelp's perspective, the problem of disabled-button invisibility appears to be due to this xpath commit made at around the same time as the above fix; I'll open an issue over there with the details. (Edit: see xpath issue #47)

Regarding a use case for this, I would put forward the example where a "Save" button is dynamically enabled or disabled depending on user actions within a form. For instance, a Cucumber scenario might say:

  When I leave a required field blank
  Then the "Save" button should be disabled
  When I fill in all required fields
  Then the "Save" button should be enabled

I agree with the above, that it doesn't make much sense to click a disabled button, but even if a button (or other form element) is disabled, it still exists, and is visible.

@leevyzz
leevyzz commented Jan 31, 2013

I am a newer. I want to verify if a button is disabled, how to do it?

@adamwaite

How can we do this now that wait_until has been removed? My JS is loaded asynchronously with require and Capybara gets there before the assets are loaded... (I can use sleep but that's lame)

@badeball

@adamwaite, given that Capybara is simply a fast user, one can assume that a regular user with a slow connection might experience similar troubles when actuating on your site before assets has finished loading asynchronously. How would you solve that? You could for instance output buttons in a disabled state and enable them with Javascript, thus forcing a user to wait until it has loaded. Or you can query some other element that you know will change once that the Javascript has loaded.

@adamwaite

ah yes, good thinking. That's given me some new ideas to try out tomorrow. Thanks.

@adamwaite

My solution, in case any stumbles upon a similar problem...

Append class ready to an element in the DOM using an asynchronous module that should come last. Then query that element. I hade a helper in support/some_helper_file.rb:

def js_ready_page
  page.find ".ready"
end

and then we can:

let(:user_menu_button_click) { -> { js_ready_page.find('#present-user-menu-button').trigger 'click' }  }
    context 'selecting the user menu' do
      before { user_menu_button_click.call }
      context 'unauthenticated user' do
        specify { js_ready_page.should have_css "#user-menu-overlay.presented[data-authenticated~='false']" }
      end
      context 'unauthenticated user' do
        specify { js_ready_page.should have_css "#user-menu-overlay.presented[data-authenticated~='true']" }
      end
    end

Inspired by @badeball, Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment