0

I'm having an issue where Selenium is saying a button is clickable even when it's disabled.

I'm using Selenium with a website where you have to select a date first and then time slot from a dropdown list before the "Book" button is clickable and will actually do anything. Before the date and time slot are chosen, the button element is

<div id="pt1:b2" class="x28o xfn p_AFDisabled p_AFTextOnly" style="width:300px;" _afrgrp="0" role="presentation"><a data-afr-fcs="false" class="xfp" aria-disabled="true" role="button"><span class="xfx">Book</span></a></div>

After the date and time slot are chosen, the button becomes

<div id="pt1:b2" class="x28o xfn p_AFTextOnly" style="width:300px;" _afrgrp="0" role="presentation"><a href="#" onclick="this.focus();return false" data-afr-fcs="true" class="xfp" role="button"><span class="xfx">Book</span></a></div>

I'm trying to use this code to wait for the button to be clickable

wait = WebDriverWait(driver, 10)
wait.until(EC.element_to_be_clickable((By.ID, 'pt1:b2')))

But Selenium is saying the button is clickable almost immediately upon the website loading even without a date or time slot chosen and the button being completely greyed out and unclickable. I've tested this by checking the timestamps from after navigating to the url and after waiting for the button to be clickable, and there's almost no delay. I've manually resorted to a try except loop and sleeping in between to be able to click the button successfully, but would rather figure out what's causing this issue. Any ideas?

undetected Selenium
  • 183,867
  • 41
  • 278
  • 352
jtanman
  • 654
  • 1
  • 4
  • 18
  • Selenium don't really detects if the element is covered by some other one, collapsed to 1 point or handles the click in the way you don't want to. Element is clickable when it able to accept click event if it impossible to click on one. – Andrej Zacharevicz Jul 01 '20 at 18:56
  • Is there any way to wait for the button to actually be clickable using Selenium? The element properties change a lot between when it actually is and isn't clickable. I'm not super familiar with Selenium's abilities for example, but it could possibly wait for class to not contain the text disabled or wait for the button to have a link destination or something. – jtanman Jul 01 '20 at 19:00
  • This depends on the element and specific circumstances. I had to put additional JS statement as a condition to wait when elemet has specific ptoperties set to ensure that element is really in clickable state from user point of view. – Andrej Zacharevicz Jul 01 '20 at 19:03
  • Which functions from Selenium did you use for this? I've listed the element state both when it isn't and is clickable so wondering if you have any ideas on which properties are most useful for differentiating. – jtanman Jul 01 '20 at 19:05
  • I use `execute_script()` (see [this example](https://stackoverflow.com/questions/5585343/getting-the-return-value-of-javascript-code-in-selenium/5585345#5585345)). But most of the work to provide me with ansver if the conditions for click are met do our front end engineer. Sorry, this is all I know by now. – Andrej Zacharevicz Jul 01 '20 at 19:21

3 Answers3

0

Solved this by just searching for the class attribute to change instead of element_to_be_clickable.

wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, "div[class='x28o xfn p_AFTextOnly']")))
jtanman
  • 654
  • 1
  • 4
  • 18
0

Selenium isclickable checks for isdisplayed and is enabled to check click ability

And isdisplay checks for style attribute but isenabled checks for disabled attribute

Now disable is handle not through html disable attribute in most cases but through javascript and css classes

So clickable condition doesn't work in these cases

https://www.selenium.dev/selenium/docs/api/py/webdriver_support/selenium.webdriver.support.expected_conditions.html

So in this case the click won't throw an error

https://github.com/SeleniumHQ/selenium/blob/trunk/py/selenium/common/exceptions.py

If you check the exception class you can see exceptions are there only for not visible , click intercepted , and not enabled

PDHide
  • 18,113
  • 2
  • 31
  • 46
0

element_to_be_clickable()

element_to_be_clickable is the expectation for checking an element is visible and enabled such that you can click it. It is defined as:

def element_to_be_clickable(locator):
    """ An Expectation for checking an element is visible and enabled such that
    you can click it."""
    def _predicate(driver):
    element = visibility_of_element_located(locator)(driver)
    if element and element.is_enabled():
        return element
    else:
        return False

    return _predicate
    

Now when the website loads even without the date or time slot chosen, the presence of the value p_AFDisabled for the class attribute determines whether the element is enabled or disabled. Next when you fill in the date or time slot the value p_AFDisabled for the class attribute is removed and the element becomes clickable.

So ideally, to wait for the button to be clickable you need to induce WebDriverWait for the element_to_be_clickable() and you can use either of the following Locator Strategies:

  • Using CSS_SELECTOR:

    WebDriverWait(driver, 20).until(EC.element_to_be_clickable((By.CSS_SELECTOR, "div.p_AFTextOnly > a[onclick] > span.xfx"))).click()
    
  • Using XPATH:

    WebDriverWait(driver, 20).until(EC.element_to_be_clickable((By.XPATH, "//div[contains(@class, 'p_AFTextOnly')]/a[@onclick]/span[text()='Book']"))).click()
    
  • Note: You have to add the following imports :

    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support import expected_conditions as EC
    
undetected Selenium
  • 183,867
  • 41
  • 278
  • 352