28

I am writing tests for a web application. Some commands pull up dialog boxes that have controls that are visible, but not available for a few moments. (They are greyed out, but webdriver still sees them as visible).

How can I tell Selenium to wait for the element to be actually accessible, and not just visible?

    try:
        print "about to look for element"
        element = WebDriverWait(driver, 10).until(lambda driver : driver.find_element_by_id("createFolderCreateBtn"))
        print "still looking?"
    finally: print 'yowp'

Here is the code that I have tried, but it "sees" the button before it is usable and basically charges right past the supposed "wait".

Note that I can stuff a ten second sleep into the code instead of this and the code will work properly, but that is ugly, unreliable, and inefficient. But it does prove that the problem is just that "click" command is racing ahead of the availability of the controls.

Skip Huffman
  • 5,239
  • 11
  • 41
  • 51
  • 1
    No doubt it is working correctly. I am trying to properly define what it should be waiting for. In this case a specific element, when it becomes available, not just visible. – Skip Huffman Feb 06 '12 at 16:04
  • 2
    Yeah, imprecise on my part. I meant my test charged right past when I wanted it to pause. Fault lying with me as the programmer, not selenium as the tool. – Skip Huffman Feb 07 '12 at 12:25
  • 3
    This question saved my job. – ravemir Dec 12 '14 at 16:46
  • Just a clarification: I believe the default behavior of findElement in Selenium is to also check for visibility. This is different that checking for 'presence' on the DOM, which might return true even when something is not visible. – djangofan Apr 27 '16 at 16:47

4 Answers4

17

I assume the events timeline goes like this:

  1. there are no needed elements on page.
  2. needed element appears, but is disabled:
    <input type="button" id="createFolderCreateBtn" disabled="disabled" />
  3. needed element becomes enabled:
    <input type="button" id="createFolderCreateBtn" />

Currently you are searching for element by id, and you find one on step 2, which is earlier than you need. What you need to do, is to search it by xpath:

//input[@id="createFolderCreateBtn" and not(@disabled)]

Here's the difference:

from lxml import etree


html = """
<input type="button" id="createFolderCreateBtn" disabled="disabled" />
<input type="button" id="createFolderCreateBtn" />
"""

tree = etree.fromstring(html, parser=etree.HTMLParser())

tree.xpath('//input[@id="createFolderCreateBtn"]')
# returns both elements:
# [<Element input at 102a73680>, <Element input at 102a73578>]


tree.xpath('//input[@id="createFolderCreateBtn" and not(@disabled)]')
# returns single element:
# [<Element input at 102a73578>]

To wrap it up, here's your fixed code:

try:
    print "about to look for element"
    element_xpath = '//input[@id="createFolderCreateBtn" and not(@disabled)]'
    element = WebDriverWait(driver, 10).until(
            lambda driver : driver.find_element_by_xpath(element_xpath)
    )
    print "still looking?"
finally: 
    print 'yowp'

UPDATE:
Repasting the same with the actual webdriver.
Here's the example.html page code:

<input type="button" id="createFolderCreateBtn" disabled="disabled" />
<input type="button" id="createFolderCreateBtn" />

Here's the ipython session:

In [1]: from selenium.webdriver import Firefox

In [2]: browser = Firefox()

In [3]: browser.get('file:///tmp/example.html')

In [4]: browser.find_elements_by_xpath('//input[@id="createFolderCreateBtn"]')
Out[4]: 
[<selenium.webdriver.remote.webelement.WebElement at 0x103f75110>,
 <selenium.webdriver.remote.webelement.WebElement at 0x103f75150>]

In [5]: browser.find_elements_by_xpath('//input[@id="createFolderCreateBtn" and not(@disabled)]')
Out[5]: 
[<selenium.webdriver.remote.webelement.WebElement at 0x103f75290>]

UPDATE 2:

It works with this as well:

<input type="button" id="createFolderCreateBtn" disabled />
Misha Akovantsev
  • 1,817
  • 13
  • 14
  • 1
    I think that is pointing me in the correct direction, but it seems that "disabled" is not the correct attribute. I need to catch the darn thing with firebug while it is unavailable, I guess. I hope my video game skills are up to the speed challenge. – Skip Huffman Feb 06 '12 at 15:59
  • 1
    @SkipHuffman, it easily can be a css class too. To catch the desired page state: 1. store `browser.page_source` value in some variable right after the element is found. 2. store that variable in `.html` file. 3. Open it with chrome/firefox and inspect with firebug/developer console. – Misha Akovantsev Feb 06 '12 at 16:08
  • css looks like it will do it. 'type="button" disabled="disabled" id="createFolderCreateBtn"' seems to be unique to this control when it is disabled. It loses the "disabled" attribute entirely when the button is active. (Makes sense.) Now I just need to rework the css selector to do a "this, but not that" sort. – Skip Huffman Feb 06 '12 at 17:07
  • (Working this through. Perhaps my thought process will help others). I can find the button with element_css = 'button[type="button"][id="createFolderCreateBtn"]' So it can search for a list of parameters (a button thing that has a type of "button" and an id of "createFolderCreateBtn") Good. But now I need to add a negative search, it should fail if the attributes includes 'disabled="disabled"'. Now I could do it programmatically I suppose, but I suspect that if I am just a bit more clever with the search string I will get just what I need. – Skip Huffman Feb 06 '12 at 17:38
  • 1
    @SkipHuffman Read my initial answer carefully. I gave you valid xpath selector, which matches button, which is NOT disabled: `//input[@id="createFolderCreateBtn" and not(@disabled)]`, note the `not(@disabled)` part. – Misha Akovantsev Feb 06 '12 at 19:46
  • Misha, I am sure your code would work fine in other situations, such as using etree. I was trying to work within the confines of selenium webdriver, and it does not accept the negated item. I simultaneously asked this question in the selenium chat room, and eventually reached the conclusion that webdriver simply wouldn't do what I needed in a single step. I will add the code that we ended up with below. Thank you for your assistance. – Skip Huffman Feb 07 '12 at 12:30
  • @SkipHuffman Unless in your case element's html differs, my solution works (at least for me). See the answer update. – Misha Akovantsev Feb 07 '12 at 12:49
  • Thanks Misha. The HTML that I am accessing is quite different. The object is defined in about a kb of css rather than a simple html input object. There are parts I have to treat as a semi-black box. – Skip Huffman Feb 07 '12 at 16:43
  • @SkipHuffman, given that your solution works, it is still strange to me, that mine - doesn't. Anyway I am glad you solved the problem. – Misha Akovantsev Feb 07 '12 at 17:39
  • @MishaAkovantsev - 1- How do you debug this issue in chrome developer tools to check if "disabled" attribute is causing the problem ? 2- Are there any other ways to do make an element disabled or attribute = disabled is the only way ? – MasterJoe Mar 25 '17 at 01:54
13
    print time.time()
    try:
        print "about to look for element"
        def find(driver):
            e = driver.find_element_by_id("createFolderCreateBtn")
            if (e.get_attribute("disabled")=='true'):
                return False
            return e
        element = WebDriverWait(driver, 10).until(find)
        print "still looking?"
    finally: print 'yowp'
    print "ok, left the loop"
    print time.time()

Here is what we ended up with. (Thanks to lukeis and RossPatterson.) Note that we had to find all the items by id and then filter by "disabled". I would have preferred a single search pattern, but what can you do?

Skip Huffman
  • 5,239
  • 11
  • 41
  • 51
1

I think something along these lines should work as well:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions

browser = webdriver.Firefox()
wait = WebDriverWait(browser, 30)
wait.until(expected_conditions.presence_of_element_located((By.XPATH, "//*[@id='createFolderCreateBrn' and not(@disabled)]")))
Milan Cermak
  • 7,476
  • 3
  • 44
  • 59
0

There are already some great answers posted up here, but I thought I would add my solution. Explicit wait etc. are great functions for use in testing with selenium. However explicit wait merely performs the function of a Thread.Sleep() that you can only set one time. The function below is what I used to "Shave off" a few minutes. It waits until the element is "accessible."

    //ALTERNATIVE FOR THREAD.SLEEP
public static class Wait
{  
    //public static void wait(this IWebDriver driver, List<IWebElement> IWebElementLIst)
    public static void wait(this IWebDriver driver, By bylocator)
    {
        bool elementPresent = IsPresent.isPresent(driver, bylocator);
        while (elementPresent != true)
        {
            Thread.Sleep(1000);

            elementPresent = IsPresent.isPresent(driver, bylocator); 

        }

    }

}

It is in C#, but to adapt it would not be that difficult. Hope this helps.

Marko
  • 20,385
  • 13
  • 48
  • 64
newITguy
  • 155
  • 1
  • 11
  • You need to be careful about the use of 'isPresent' since it will return true when item is on the DOM but not visible. In the context of the question asked in this thread, you should use 'isVisible' instead. Also, its a good idea to use an ExpectedCondition, if your Selenium bindings allow it. In fact, default behavior of findElement is (I think) .isVisible rather than .isPresent . – djangofan Apr 27 '16 at 16:44
  • What if the element is enabled after 1010 milliseconds or more ? Your test will fail. What if the element is loaded between 300millisec to 500millisec, then your test is wasting time. Not much time, but when you use this wait for several elements, it can add up. – MasterJoe Mar 25 '17 at 01:56