0

Basically, what I want to do is search https://www.ssrn.com/index.cfm/en/ for a name and then click on some partially matching name. For example, I want to search "John Doe". SSRN will return a list of papers that contain John Doe in their author list. Below is my code to try and do that.

name = "John Doe"
try:
    element = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.PARTIAL_LINK_TEXT, name.split(' ', 1)[0])) and
        EC.presence_of_element_located((By.PARTIAL_LINK_TEXT, name.split(' ', 1)[1]))
    )
    # I want the element to be clicked only if both the first name and last name appear.
    element.click()
except:
    print("No result")

It works if John Doe is a full match, i.e. the author list looks like (John Doe, x, y, z). It works if John Doe is a partial match and there are no other partial matches, i.e. the author list looks like (John M. Doe, x, y, z). However, it breaks if there are multiple partial matches. For example, if the list looks like (Jane Doe, John Doe, y, z). Then, my code will select Jane Doe. I think I want something similar to Java's EC.and(), but I'm not sure how to implement it or if there's a better way to do this. Thank you!

stressed
  • 328
  • 2
  • 7
  • which element should that `.until` return? since you're essentially trying to find 2 elements – Chase Oct 27 '20 at 13:46
  • I wanted the element to be whatever matches both "John" and "Doe". So it could be John M. Doe or John Jack Doe or John Doe, if that makes sense. – stressed Oct 27 '20 at 13:50

1 Answers1

1

The snippet

EC.presence_of_element_located((By.PARTIAL_LINK_TEXT, name.split(' ', 1)[0])) and
EC.presence_of_element_located((By.PARTIAL_LINK_TEXT, name.split(' ', 1)[1]))

just evaluates to

EC.presence_of_element_located((By.PARTIAL_LINK_TEXT, name.split(' ', 1)[1]))

So it always only checks that condition and that condition only, i.e it always only tries to find Doe, completely ignoring John. Which is why you find Jane Doe since it appears before.

This is not how you check for multiple conditions, you need to pass a function-like object to .until, that can check for multiple conditions and return a truthy/falsy value.

For your specific needs that function could look like-

def presence_of_element_with_both(driver):
    name = "John Doe"
    first = EC.presence_of_element_located((By.PARTIAL_LINK_TEXT, name.split(' ', 1)[0]))(driver)
    second = EC.presence_of_element_located((By.PARTIAL_LINK_TEXT, name.split(' ', 1)[1]))(driver)
    if first == second:
        # Both elements exist and they are the same
        return first
    else:
        return False    

This will try finding an element with partial link text "John" and then it will try finding an element with partial link text "Doe" - if both of the elements are found and if both point to the same element - you're gold.

You can use it in your until like so-

WebDriverWait(driver, 10).until(presence_of_element_with_both)

You might, however, find it convenient to generalize this-

def presence_of_element_with_all(locators):
    def inner(driver):
        # Get all the elements that match each given locator
        results = [EC.presence_of_element_located(locator)(driver) for locator in locators]
        # Check if all the elements are the same
        # If they are, all the conditions have been met
        # If they are not, all the conditions did not meet
        return results[0] if len(set(results)) == 1 else False
    return inner

This will find the singular element that satisfies all locators given.

You can use this like so-

first_name, last_name = "John Doe".split(' ')
WebDriverWait(driver, 10).until(presence_of_element_with_all([(By.PARTIAL_LINK_TEXT, first_name), (By.PARTIAL_LINK_TEXT, last_name)]))

Do note that, I'm using the closure pattern to do this. Internally, selenium uses a class with an __init__ and __call__ function to do the same - this is called a function like object and you can use this too if you want-

class presence_of_element_with_all:
    def __init__(self, locators):
        self.locators = locators
    def __call__(self, driver):
        results = [EC.presence_of_element_located(locator)(driver) for locator in self.locators]
        # Check if all the elements are the same
        # If they are, all the conditions have been met
        # If they are not, all the conditions did not meet
        return results[0] if len(set(results)) == 1 else False

You'd use this the exact same way as the closure pattern.

Chase
  • 5,315
  • 2
  • 15
  • 41
  • Thank you so much for such a thorough answer! It worked exactly. I've been trying to dip my toes into some of this more complex stuff too and your answer helped with that as well. – stressed Oct 27 '20 at 15:26