Selenium - 每行代码后等待
我正在使用Selenium(Python)来测试一个网页应用。不过,有时候一行代码会出错,因为前一行没有足够的时间执行完。例如:
...
driver.find_element_by_id("form_widget_date").click()
driver.find_element_by_link_text(str(self.day)).click()
...
结果是,第二行有时候找不到链接文本,因为Selenium显然没有时间完成第一行。当我在两行之间加一个暂停时,这个错误就不会出现。
我的问题是:有没有办法让每行代码自动多等一会儿,而不是像这样解决问题:
...
time.sleep(2)
driver.find_element_by_id("form_widget_date").click()
time.sleep(2)
driver.find_element_by_link_text(str(self.day)).click()
time.sleep(2)
...
?
3 个回答
实际上,你的代码里常常会有很多等待的情况。不过,你还是要尽量避免使用“睡眠”这种方法。可以看看“显式等待”的用法。这种方法允许你设定一个等待的时间段,如果在这个时间段内没有找到元素,程序就会超时结束。但是一旦找到了元素,不管剩下的等待时间还有多少,程序都会继续执行下去。
来自 selenium HQ 的文档:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait # available since 2.4.0
from selenium.webdriver.support import expected_conditions as EC # available since 2.26.0
ff = webdriver.Firefox()
ff.get("http://somedomain/url_that_delays_loading")
try:
element = WebDriverWait(ff, 10).until(EC.presence_of_element_located((By.ID, "myDynamicElement")))
finally:
ff.quit()
有两种等待方式,分别是 显式等待
和 隐式等待
。
Dan 提供的解决方案是一个显式等待的例子,而我觉得你想要的是隐式等待。
隐式等待就是告诉 WebDriver 在寻找某个元素或多个元素时,如果这些元素没有马上出现,就让它在 DOM(文档对象模型)中等待一段时间。默认情况下,这个等待时间是 0。设置一次后,隐式等待会一直有效,直到 WebDriver 对象被销毁。
你可以在一开始就这样定义隐式等待时间:
driver = webdriver.Firefox()
driver.implicitly_wait(10) # seconds
参考资料:显式和隐式等待
基本上,你有三种选择可以考虑。
使用
time.sleep(...)
。这是现实开发中最糟糕的选择。 这样做很糟糕,因为你每次都必须等待你指定的时间。如果你发现有时候测试需要5秒才能通过,但如果你等待的条件在1秒内就准备好了,你还是得等满5秒。在一个比较大的测试集合中,这样的等待时间会累积起来,导致运行整个测试需要花费更多的时间。
使用Selenium的隐式等待。
这个方法的好处是你可以设置一个隐式等待,然后就不用再管它了。你不需要一个接一个地指定等待时间。不过问题是,隐式等待和显式等待不能一起使用。为什么它们不能混用的细节在这个回答中解释得很好。根据我的经验,隐式等待只在非常简单的情况下有效。
使用Selenium的显式等待。
如果你的应用是动态的,比如使用了Ajax,或者你在运行时修改了DOM,你迟早需要使用显式等待,到那时你就不能再依赖隐式等待了。缺点是你的代码会变得更冗长。不过你可以通过封装函数来减轻这个问题。下面是一个示例,假设
driver
是一个WebDriver
对象:from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait class MyDriver(object): def __init__(self, driver, default_timeout=2): self.driver = driver self.timeout = default_timeout def find_element(self, locator): return WebDriverWait(self.driver, self.timeout).until( EC.presence_of_element_located(locator)) mydriver = MyDriver(driver) foo = mydriver.find_element((By.CSS_SELECTOR, ".foo"))