【问题标题】:How to implement method chaining of Selenium multiple WebDriverWait in Python如何在 Python 中实现 Selenium 多个 WebDriverWait 的方法链接
【发布时间】:2019-04-27 07:24:37
【问题描述】:

我希望实现 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 的讨论尝试通过方法链 使用Pipe - Python library to use infix notation in Python 来使用Pythonlambda 函数。

这是我的代码试用版:

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

关注了以下讨论:

仍然不知道我错过了什么。

谁能指导我哪里出错了?

【问题讨论】:

  • 为两个预期条件实现 WebDriverWait... 哪些预期条件?
  • @Andersson 好问题...(在我的回答中,我假设presence_of_element_locatedelement_to_be_clickable)。
  • @DebanjanB 在我的回答中,你可以看到你的一些问题,我相信你会有更优雅的方式来实现它......
  • @Andersson 感谢您的关注。为简洁起见,我已经更新了问题。如果您需要更多详细信息,请告诉我。
  • 问题不断变化,目标不断变化:)。最新的编辑是“因为想法是获取从第一个 WebDriverWait 返回的元素作为(链接的)第二个 WebDriverWait 的输入。” - 但在您的尝试示例中,您没有使用第一个元素,等待基于 driver - 所以它来自 DOM 的顶部。

标签: python selenium lambda method-chaining webdriverwait


【解决方案1】:

如您的错误所示:

TypeError: 'function' 对象不可迭代

这是因为:

where() 方法 只产生给定迭代的匹配项 例如[1, 2, 3] | where(lambda x: x % 2 == 0) | concat =&gt; '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 做任何事情,因为它是一个生成器对象

“在两个预期条件下实现 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")

希望对你有帮助...

编辑2

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

【讨论】:

  • 谢谢@MosheSlavin 为了简洁起见,我已经更新了这个问题。可以再看看吗?
  • @DebanjanB 我不知道是否有可能,但你可以尝试构建你的一个管道......
【解决方案2】:

编辑:

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

@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']"))

或者 selenium 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()

【讨论】:

  • 谢谢@ewwink 为了简洁起见,我已经更新了这个问题。可以再看看吗?
【解决方案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']]"))

【讨论】:

  • 答案的第一部分非常有帮助。为了更简洁,我已经逐字更新了要求。可以再看一遍吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-13
  • 2013-03-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多