如何在Python中实现Selenium Multiple WebDriverWait的方法链接

时间:2018-11-25 13:05:42

标签: python selenium lambda method-chaining webdriverwait

我正在寻求实现Selenium WebDriverWaits的方法链接

首先,实现单个 WebDriverWait 的这段代码很完美:

from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait

options = webdriver.ChromeOptions() 
options.add_argument("start-maximized")
options.add_argument('disable-infobars')
driver = webdriver.Chrome(chrome_options=options, executable_path=r'C:\Utility\BrowserDrivers\chromedriver.exe')
driver.get('https://www.facebook.com')
element = WebDriverWait(driver, 5).until(lambda x: x.find_element_by_xpath("//input[@id='email']"))
element.send_keys("method_chaining")

根据我当前的要求,我必须实现两个WebDriverWait实例的链接,因为其思想是获取从第一个 WebDriverWait 返回的元素作为(链接的)输入第二个 WebDriverWait

要实现这一点,我遵循了讨论method chaining in python,并尝试通过方法链接使用{@来使用use Python lambda函数3}}。

这是我的代码试用版:

from pipe import *
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC

options = webdriver.ChromeOptions() 
options.add_argument("start-maximized")
options.add_argument('disable-infobars')
driver = webdriver.Chrome(chrome_options=options, executable_path=r'C:\WebDrivers\chromedriver.exe')
driver.get('https://www.facebook.com')
element = WebDriverWait(driver,15).until((lambda driver: driver.find_element_by_xpath("//input[@id='email']"))
        | where(lambda driver: driver.find_element_by_css_selector("table[role='presentation']")))  
element.send_keys("method_chaining")

我看到以下错误:

DevTools listening on ws://127.0.0.1:52456/devtools/browser/e09c1d5e-35e3-4c00-80eb-cb642fa273ad
Traceback (most recent call last):
  File "C:\Users\Soma Bhattacharjee\Desktop\Debanjan\PyPrograms\python_pipe_example.py", line 24, in <module>
    | where(lambda driver: driver.find_elements(By.CSS_SELECTOR,"table[role='presentation']")))
  File "C:\Python\lib\site-packages\pipe.py", line 58, in __ror__
    return self.function(other)
  File "C:\Python\lib\site-packages\pipe.py", line 61, in <lambda>
    return Pipe(lambda x: self.function(x, *args, **kwargs))
  File "C:\Python\lib\site-packages\pipe.py", line 271, in where
    return (x for x in iterable if (predicate(x)))
TypeError: 'function' object is not iterable

遵循了以下讨论:

仍然不知道我在想什么。

有人可以引导我哪里出问题了吗?

3 个答案:

答案 0 :(得分:3)

编辑:

我不知道它是否满足您的要求,但是我们需要创建自定义结构。

@Pipe
def where(i, p):
    if isinstance(i, list):
        return [x for x in i if p(x)]
    else:
        return i if p(i.find_element_by_xpath('//ancestor::*')) else None

element = WebDriverWait(driver, 5).until(lambda x: x.find_element_by_xpath("//input[@id='email']") \
        | where(lambda y: y.find_element_by_css_selector("table[role='presentation']")))  

element.send_keys("method_chaining")

旧:


我不是专家,但我想也许您对管道的工作方式有误解,例如,默认情况下它会处理先前的值

original | filter = result
[a,b,c] | select(b) = b

您想要的是and运算符

WebDriverWait(driver,15).until(
    lambda driver: driver.find_element(By.CSS_SELECTOR,"table[role='presentation']")
    and driver.find_element(By.XPATH,"//input[@id='email']"))

或硒ActionChains,但没有等待方法,我们需要扩展它

from selenium.webdriver import ActionChains

class Actions(ActionChains):
    def wait(self, second, condition):
        element = WebDriverWait(self._driver, second).until(condition)
        self.move_to_element(element)
        return self

Actions(driver) \
    .wait(15, EC.presence_of_element_located((By.CSS_SELECTOR, "table[role='presentation']"))) \
    .wait(15, EC.element_to_be_clickable((By.XPATH, "//input[@id='email']"))) \
    .click() \
    .send_keys('method_chaining') \
    .perform()

答案 1 :(得分:3)

在我收集您的要求时,实际上是要拥有一个具有两个预期条件的WebDriverWait,而不是不惜一切代价使用方法链接和pipe库。

您可以通过稍微推动lambda表达式语法来做到这一点。最干净的解决方案是使其在函数调用中可枚举,并获取所需的返回值:

element = WebDriverWait(driver, 5).until(lambda x: (x.find_element_by_xpath("//input[@id='email']"), x.find_element_by_css_selector("table[role='presentation']"))[1])

有了索引,您将收到第二个元组成员,例如table元素。

表达式可以重写为布尔值:

element = WebDriverWait(driver, 5).until(lambda x: x.find_element_by_xpath("//input[@id='email']") and x.find_element_by_css_selector("table[role='presentation']"))

然后您将再次获得第二个元素,即布尔值的最后一部分。但是,当(ab)与过于复杂的布尔值一起使用时,该实现会有一些陷阱,因此YMMV;我会坚持第一个,枚举。

使用这种方法,您将获得WebDriverWait的好处-如果元组中的任何调用引发了一个已处理的异常之一,则将重试,等等。
但是,这种方法会降低性能-对第一个方法的调用将在每个周期执行,即使它已经成功通过并且现在等待第二个条件也是如此。


这是一个完全不同的替代方案,也是最干净的解决方案-我看到您并不害羞地使用xpath,所以是一个纯粹的xpath。 因为您的目标是获取table元素,所以当且仅当存在input时,这样做:

//table[@role='presentation' and ancestor::*//input[@id='email']]

这将选择具有角色的table。它的另一个条件是提升其祖先-ancestor轴将上升到DOM的顶部节点-并从那里向下查找具有该id属性的input元素。

因此,如果input仍然不存在,则xpath将不匹配任何内容。可用时,还有一个带有该角色值的table-它会被返回。
自然,只需一个条件即可直接在WebDriverWait中使用此选择器:

element = WebDriverWait(driver, 5).until(lambda x: x.find_element_by_xpath("//table[@role='presentation' and ancestor::*//input[@id='email']]"))

答案 2 :(得分:1)

如您的错误所示:

  

TypeError:“函数”对象不可迭代

这是因为:

  

where()方法       仅产生给定可迭代项的匹配项       例如[1, 2, 3] | where(lambda x: x % 2 == 0) | concat => '2'

希望这个答案有人可以指导我我要去哪里了吗?

编辑:

如果您想使用pipe.where: 因此,您可以使用与所做的更改相同的代码来完成此操作: 您的element var应该是这样的:

element = WebDriverWait(driver,15).until((lambda driver: driver.find_elements(By.XPATH,"//input[@id='email']"))) \
       | where(WebDriverWait(driver,15).until(lambda driver: driver.find_elements(By.CSS_SELECTOR,"table[role='presentation']")))

但是element不能做任何事情,因为它是 generator对象

“为两个预期条件实现WebDriverWait” 的最佳做法是仅包含两行WebDriverWait

赞:

presentation_element = WebDriverWait(driver, 20).until(EC.presence_of_element_located((By.CSS_SELECTOR,"table[role='presentation']")))
email_element = WebDriverWait(driver, 20).until(EC.element_to_be_clickable((By.XPATH, "//input[@id='email']")))
email_element.send_keys("method_chaining")

希望这对您有帮助...

Edit2

我认为您正在寻找的是构建一个here所示的管道。