Python Selenium: как применить логический оператор And в ожидаемом состоянии

По сути, я хочу выполнить поиск https://www.ssrn.com/index.cfm/en/ для имени, а затем нажмите на частично совпадающее имя. Например, я хочу найти Джона Доу. SSRN вернет список статей, в списке авторов которых есть Джон Доу. Ниже мой код, чтобы попытаться сделать это.

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")

Это работает, если John Doe является полным совпадением, т. е. список авторов имеет вид (John Doe, x, y, z). Это работает, если John Doe является частичным совпадением и других частичных совпадений нет, т.е. список авторов имеет вид (John M. Doe, x, y, z). Однако он ломается, если есть несколько частичных совпадений. Например, если список выглядит так (Джейн Доу, Джон Доу, г, г). Затем мой код выберет Джейн Доу. Я думаю, что хочу что-то похожее на EC.and() в Java, но я не уверен, как это реализовать или есть ли лучший способ сделать это. Спасибо!


person stressed    schedule 27.10.2020    source источник
comment
какой элемент должен вернуть этот .until? так как вы по существу пытаетесь найти 2 элемента   -  person Chase    schedule 27.10.2020
comment
Я хотел, чтобы этот элемент соответствовал и Джону, и Доу. Так что это может быть Джон М. Доу, или Джон Джек Доу, или Джон Доу, если это имеет смысл.   -  person stressed    schedule 27.10.2020


Ответы (1)


Фрагмент

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]))

просто оценивает

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

Таким образом, он всегда проверяет только это условие и только это условие, то есть всегда пытается найти только Doe, полностью игнорируя John. Вот почему вы находите Jane Doe, так как он появляется раньше.

Это не то, как вы проверяете несколько условий, вам нужно передать объект, похожий на функцию, в .until, который может проверять несколько условий и возвращать истинное/ложное значение.

Для ваших конкретных потребностей эта функция может выглядеть так:

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    

Это попытается найти элемент с частичным текстом ссылки Джон, а затем попытается найти элемент с частичным текстом ссылки Доу - если оба элемента найдены и если оба указывают на один и тот же элемент - вы золотой.

Вы можете использовать его в своем, пока так-

WebDriverWait(driver, 10).until(presence_of_element_with_both)

Однако вы можете счесть удобным обобщить это:

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

Это найдет сингулярный элемент, который удовлетворяет всем заданным локаторам.

Вы можете использовать это так:

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)]))

Обратите внимание, что для этого я использую шаблон закрытия. Внутри selenium использует class с функцией __init__ и __call__. сделать то же самое - это называется функцией, подобной объекту, и вы также можете использовать это, если хотите -

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

Вы бы использовали это точно так же, как шаблон закрытия.

person Chase    schedule 27.10.2020
comment
Большое спасибо за столь обстоятельный ответ! Это сработало точно. Я пытался окунуться в некоторые из этих более сложных вещей, и ваш ответ тоже помог с этим. - person stressed; 27.10.2020