等待策略
对于浏览器自动化来说,最常见的挑战可能是确保 Web 应用程序处于可以按预期执行特定 Selenium 命令的状态。这些过程常常陷入*竞争条件*,有时浏览器先进入正确的状态(事情按预期工作),有时 Selenium 代码先执行(事情不按预期工作)。这是导致*不稳定测试*的主要原因之一。
所有导航命令都会根据页面加载策略等待特定的 readyState
值(默认等待值为 "complete"
),然后驱动程序才会将控制权返回给代码。readyState
只关心 HTML 中定义的资源加载,但加载的 JavaScript 资源通常会导致站点发生变化,并且在代码准备好执行下一个 Selenium 命令时,需要交互的元素可能尚未出现在页面上。
同样,在许多单页应用程序中,元素会动态添加到页面或根据点击更改可见性。元素必须同时存在于页面上并显示,Selenium 才能与之交互。
以这个页面为例:https://seleniumcn.cn/selenium/web/dynamic.html 点击“添加一个框!”按钮时,将创建一个不存在的“div”元素。点击“显示新输入”按钮时,将显示一个隐藏的文本字段元素。在这两种情况下,转换都需要几秒钟。如果 Selenium 代码要点击其中一个按钮并与生成的元素进行交互,它将在该元素准备就绪之前执行,并导致失败。
许多人首先想到的解决方案是添加一个休眠语句,暂停代码执行一段时间。因为代码无法准确知道需要等待多长时间,所以当它休眠的时间不够长时,可能会失败。或者,如果该值设置得过高,并且在每个需要的地方都添加了休眠语句,则会话的持续时间可能会变得令人难以接受。
Selenium 提供了两种更好的同步机制。
隐式等待
Selenium 有一种内置的方式来自动等待元素,称为*隐式等待*。隐式等待值可以通过浏览器选项中的超时功能设置,也可以使用驱动程序方法设置(如下所示)。
这是一个全局设置,适用于整个会话的每个元素位置调用。默认值为 0
,这意味着如果找不到元素,它将立即返回错误。如果设置了隐式等待,驱动程序将等待提供的持续时间,然后返回错误。请注意,一旦找到该元素,驱动程序将返回元素引用,代码将继续执行,因此更大的隐式等待值不一定会增加会话的持续时间。
警告: 不要混合使用隐式等待和显式等待。这样做可能会导致无法预测的等待时间。例如,设置 10 秒的隐式等待和 15 秒的显式等待可能会导致在 20 秒后发生超时。
使用隐式等待解决我们的示例如下
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(2));
driver.implicitly_wait(2)
driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(2);
driver.manage.timeouts.implicit_wait = 2
await driver.manage().setTimeouts({ implicit: 2000 });
显式等待
显式等待是添加到代码中的循环,它轮询应用程序以查找要评估为 true 的特定条件,然后退出循环并继续执行代码中的下一个命令。如果在指定超时值之前未满足该条件,则代码将给出超时错误。由于应用程序可能未处于所需状态的原因有很多,因此显式等待是在需要时指定要等待的确切条件的好选择。另一个很好的特性是,默认情况下,Selenium Wait 类会自动等待指定元素的存在。
此示例显示了等待的条件为lambda。Java 也支持预期条件
Wait<WebDriver> wait = new WebDriverWait(driver, Duration.ofSeconds(2));
wait.until(d -> revealed.isDisplayed());
此示例显示了等待的条件为lambda。Python 也支持预期条件
wait = WebDriverWait(driver, timeout=2)
wait.until(lambda d : revealed.is_displayed())
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(2));
wait.Until(d => revealed.Displayed);
wait = Selenium::WebDriver::Wait.new
wait.until { revealed.displayed? }
自定义
可以使用各种参数实例化 Wait 类,这些参数将更改条件的评估方式。
这可以包括
- 更改代码的评估频率(轮询间隔)
- 指定应自动处理哪些异常
- 更改总超时时长
- 自定义超时消息
例如,如果默认情况下重试元素不可交互错误,那么我们可以在正在执行的代码中的方法上添加一个操作(我们只需要确保代码在成功时返回 true
)
在 Java 中自定义 Waits 的最简单方法是使用 FluentWait
类
Wait<WebDriver> wait =
new FluentWait<>(driver)
.withTimeout(Duration.ofSeconds(2))
.pollingEvery(Duration.ofMillis(300))
.ignoring(ElementNotInteractableException.class);
wait.until(
d -> {
revealed.sendKeys("Displayed");
return true;
});
errors = [NoSuchElementException, ElementNotInteractableException]
wait = WebDriverWait(driver, timeout=2, poll_frequency=.2, ignored_exceptions=errors)
wait.until(lambda d : revealed.send_keys("Displayed") or True)
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(2))
{
PollingInterval = TimeSpan.FromMilliseconds(300),
};
wait.IgnoreExceptionTypes(typeof(ElementNotInteractableException));
wait.Until(d => {
revealed.SendKeys("Displayed");
return true;
});
errors = [Selenium::WebDriver::Error::NoSuchElementError,
Selenium::WebDriver::Error::ElementNotInteractableError]
wait = Selenium::WebDriver::Wait.new(timeout: 2,
interval: 0.3,
ignore: errors)
wait.until { revealed.send_keys('Displayed') || true }