'Python selenium : Explicitly wait for one of two elements to be loaded

Is there a way using which I can wait for one of two elements to get loaded in selenium. I am using explicit waits and so far, haven't been able to figure out the solution.

Simply doing

WebDriverWait(driver,5).until(lambda driver : driver.find_element(By.ID,"a") or driver.find_element(By.ID,"b"))

doesn't seem to work. It just looks for element with id ="a".

Thanks!



Solution 1:[1]

find_element raises NoSuchElementException exception if no element is found.

If element with the id a does not exist, driver.find_element(By.ID,"a") will raises the exception and the driver.find_element(By.ID,"b") will not be executed.

A simple way to solve the problem is using find_elements which return empty list instead of raising the exception:

WebDriverWait(driver,5).until(
    lambda driver: driver.find_elements(By.ID,"a") or driver.find_elements(By.ID,"b"))

Solution 2:[2]

As falsetru explained if your first find_element call fails to find an element, it will raise NoSuchElementException and the 2nd part of your test won't execute.

I would suggest using a CSS selector that matches either IDs you are looking for:

WebDriverWait(driver, 5).until(
    lambda driver : driver.find_element_by_css_selector("#a, #b"))

This has an advantage over performing two find_elements calls becayse this will make only one roundtrip between your Selenium client (your script) and the Selenium server. This should always be faster than performing two find_elements calls. When performing tests locally, the difference won't be much but if you perform tests remotely, for instance using Sauce Labs or Browser Stack, the difference will be significant.

Solution 3:[3]

Note, find_elements returns a list, so if you wanted to test for the element you will have to iterate through a list So in the below code I needed to test for 2 elements (depending on the scenario) and then test what element was returned so I can do the apropriate action

# If the number is NOT a WhatsApp number then there will be an OK Button, not the Message Textbox
            # Test for both situations -> find_elements returns a List
            ctrl_element = self.wait.until(
                lambda ctrl_self:
                    ctrl_self.find_elements(By.XPATH, nr_not_found_xpath) or
                    ctrl_self.find_elements(By.XPATH, inp_xpath)
            )
            # Iterate through the list of elements to test each if they are a textBox or a Button
            for i in ctrl_element:
                if i.aria_role == 'textbox':
                    # This is a WhatsApp Number -> Send Message
                    i.send_keys(message + Keys.ENTER)
                    msg = f"Message sent successfully to {self.mobile}"

                elif i.aria_role == 'button':
                    # This is NOT a WhatsApp Number -> Press enter and continue
                    i.send_keys(Keys.ENTER)
                    msg = f"Not a WhatsApp Number {self.mobile}"

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Community
Solution 3 Cornelius Mostert