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!
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.