Selenium Explicit Wait fails to wait for clickability

41 Views Asked by At

I am having an issue with selenium explicit wait for a button to be clickable. When the button webelement is returned by

self.driver_wait.until(
            expected_conditions.element_to_be_clickable((By.ID, 'login-btn'))
            )

I am getting an obstruction error:

selenium.common.exceptions.ElementClickInterceptedException: Message: Element <a id="login-btn" class="fm-btn fm-btn-ghost-white account"> is not clickable at point (886,96) because another element <div id="ot-pc-content" class="ot-pc-scrollbar"> obscures it

Ironically it explicitely states, that the element is not clickable, although the explicit wait instruction should wait exactly for this to my undestanding ...

I tried to wait until the obstructing element is not visible anymore, but that didn't help:

    self.driver_wait.until(
        expected_conditions.invisibility_of_element((By.ID, 'ot-pc-content'))
        )
    button_login_1 = self.driver_wait.until(
        expected_conditions.element_to_be_clickable((By.ID, 'login-btn'))
        )

I guess this behaviour will be reproducible using this minimum example, that includes everything needed from a class definition:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions
import time
import datetime as dt
from typing import Union

class Session_FLM:
    def __init__(self,
                 url: str = 'https://www.freelancermap.de/',
                 browser: str = "FireFox",
                 session_time: Union[None, dt.datetime] = None,
                 use_fixed_wait_default: bool = False,
                 default_wait_time_fixed: float = 1,
                 default_explicit_wait_max: float = 10,
                 implicit_wait_max: float = 10
                 ):
        self.browser: str = browser
        if session_time:
            self.session_time = session_time
        else:
            self.session_time = dt.datetime.now()
        self.url: str = url
        self.cookies: bool = False
        self.default_explicit_wait_max: float = default_explicit_wait_max
        self.use_fixed_wait_default: bool = use_fixed_wait_default
        self.default_wait_time_fixed: float = default_wait_time_fixed
        self.implicit_wait_max: float = implicit_wait_max
        self.driver: Union[
            webdriver.firefox.webdriver.WebDriver,
            webdriver.chrome.webdriver.WebDriver,
            None] = None    
        self.driver_wait: Union[WebDriverWait, None] = None     
        self.headers: dict = {
            'User-Agent': 'Mozilla/5.0'
            }
     
    def init_webdriver(
            self,
            url: Union[str, None] = None,
            use_fixed_wait: Union[bool, None] = None,
            wait_time_fixed: Union[float, None] = None,
            activate_implicit_wait: bool = False,
            implicit_wait_max: Union[float, None] = None,
            explicit_wait_max: Union[float, None] = None
            ) -> None:

        if not url:
            url = self.url
        if not use_fixed_wait:
            use_fixed_wait = self.use_fixed_wait_default
        if not wait_time_fixed:
            wait_time_fixed = self.default_wait_time_fixed
        if not implicit_wait_max:
            implicit_wait_max = self.implicit_wait_max
        if not explicit_wait_max:
            explicit_wait_max = self.default_explicit_wait_max

        if self.browser == 'Chrome':
            driver_options = webdriver.ChromeOptions()
            driver_options.page_load_strategy = 'normal'        # wait for entire page content to be loaded
            self.driver = webdriver.Chrome(options=driver_options)
        elif self.browser == 'FireFox':
            driver_options = webdriver.FirefoxOptions()
            driver_options.page_load_strategy = 'normal'        # wait for entire page content to be loaded
            self.driver = webdriver.Firefox(options=driver_options)

        self.driver_wait = WebDriverWait(self.driver, explicit_wait_max)

        if activate_implicit_wait:
            self.driver.implicitly_wait(implicit_wait_max)
        self.driver.get(url=url)
        if use_fixed_wait:
            time.sleep(wait_time_fixed)

    def cookies_handling(
            self,
            accept_cookies: bool = False,
            use_fixed_wait: Union[bool, None]  = None,
            wait_time_fixed: Union[float, None] = None
            ) -> None:
        """
        Handle freelancermap cookies.

        Args:
            accept_cookies (bool, optional): True: Accept all cookies; False: Enter cookie details and only accept obigatory cookies.
            use_fixed_wait: (Union[bool, None], optional): Only use fixed wait if True
            wait_time_fixed (Union[float, None], optional): Fixed waiting time, which is used whenever "Selenium Implicit Wait" is set to be inactive; overrides "Session_FLM" objects default fixed waiting time.
        """     

        if not use_fixed_wait:
            use_fixed_wait = self.use_fixed_wait_default
        if not wait_time_fixed:
            wait_time_fixed = self.default_wait_time_fixed

        if accept_cookies:
            button_accept_cookies = self.driver_wait.until(
                expected_conditions.element_to_be_clickable((By.ID, 'onetrust-accept-btn-handler'))
            )
            button_accept_cookies.click()
        else:
            button_settings_cookies = self.driver_wait.until(
                expected_conditions.element_to_be_clickable((By.ID, 'onetrust-pc-btn-handler'))
                )
            button_settings_cookies.click()
            if use_fixed_wait:
                time.sleep(wait_time_fixed)
            button_accept_selected_cookies = self.driver_wait.until(
                expected_conditions.element_to_be_clickable((By.CLASS_NAME, 'save-preference-btn-handler'))
                )
            button_accept_selected_cookies.click()

        if use_fixed_wait:
            time.sleep(wait_time_fixed)

    def login(
            self,
            email: str,
            password: str,
            use_fixed_wait: Union[bool, None]  = None,
            wait_time_fixed: Union[float, None] = None
            ) -> None:
        """
        Login with an existing freelancermap account.

        Args:
            email (str): Account email
            password (str): Account password
            use_fixed_wait: (Union[bool, None], optional): Only use fixed wait if True
            wait_time_fixed(Union[float, None], optional): Fixed waiting time, which is used whenever "Selenium Implicit Wait" is set to be inactive; overrides "Session_FLM" objects default fixed waiting time.
        """          

        if not wait_time_fixed:
            wait_time_fixed = self.default_wait_time_fixed
            
        if use_fixed_wait:
            time.sleep(wait_time_fixed)
        self.driver_wait.until(
            expected_conditions.invisibility_of_element((By.ID, 'ot-pc-content'))
            )
        button_login_1 = self.driver_wait.until(
            expected_conditions.element_to_be_clickable((By.ID, 'login-btn'))
            )
        button_login_1.click()
        if use_fixed_wait:
            time.sleep(wait_time_fixed)
        input_field_email = self.driver.find_element(
            By.ID,
            "login"
        )
        input_field_email.send_keys(email)
        input_field_pw = self.driver.find_element(
            By.ID,
            "password"
        )
        input_field_pw.send_keys(password)
        button_login_2 = self.driver_wait.until(
            expected_conditions.element_to_be_clickable((By.CSS_SELECTOR, 'button.fm-btn-primary:nth-child(1)'))
            )
        button_login_2.submit()
        self.driver_wait.until(
            expected_conditions.url_to_be('https://www.freelancermap.de/mein_account.html')
            )
        if use_fixed_wait:
            time.sleep(wait_time_fixed)
        self.driver.back()
        if use_fixed_wait:
            time.sleep(wait_time_fixed)

if __name__ == '__main__':
    flm = Session_FLM(
        url='https://www.freelancermap.de/projektboerse.html',
        default_wait_time_fixed=2
        )
    flm.init_webdriver(activate_implicit_wait=False)
    flm.cookies_handling(
        accept_cookies=False,
        use_fixed_wait=False)
    flm.login(
        email='abc',
        password='123', 
        use_fixed_wait=False
        )
        
  • Clicking the button works, when cookies_handling() is called with the argument use_fixed_wait=True.
  • Clicking the button works, when login() is called with the argument use_fixed_wait=True.
  • Clicking the button works, when cookies_handling() is called with the argument accept_cookies=False, as in this case the obstructing element won't appear at all.
  • Clicking the button fails, when all those parameters are set to False.

I would really love to understand what is the problem with my approach and how to cope with it without adding time.sleep().

1

There are 1 best solutions below

0
JeffC On

This is what you should have posted as an MCVE,

url = 'https://www.freelancermap.de/projektboerse.html'
driver = webdriver.Chrome()
driver.maximize_window()
driver.get(url)

wait = WebDriverWait(driver, 10)
wait.until(EC.element_to_be_clickable((By.ID, "login-btn"))).click()

The rest of your code is irrelevant to the error and just creates confusion and obfuscates the issue.

This code works just fine on my machine. From looking at the HTML provided by the click intercepted exception, it looks like the cookie banner is what your code is clicking. My suggestion is to accept or otherwise dismiss the cookie banner first, then proceed with the rest of your code and you shouldn't have any issues.