[跟着官方文档学Selenium][学习笔记][七][WebDriver的等待]
WebDriver通常可以说有一个阻塞API。因为它是一个指示浏览器做什么的进程外库,而且web平台本质上是异步的,所以WebDriver不跟踪DOM的实时活动状态。这伴随着一些我们将在这里讨论的挑战。
根据经验,大多数由于使用Selenium和WebDriver而产生的间歇性问题都与浏览器和用户指令之间的竞争条件有关。例如,用户指示浏览器导航到一个页面,然后再试图查找元素时得到一个no such element的错误。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Race Condition Example</title>
<script>
var initialised = false;
window.addEventListener("load", function () {
var newElement = document.createElement("p");
newElement.textContent = "Hello from JavaScript!";
document.body.appendChild(newElement);
initialised = true;
});
</script>
</head>
<body>
</body>
</html>
这个WebDriver的说明可能看起来很简单:
import org.junit.Assert;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.edge.EdgeDriver;
public class demo1
{
public static void main(String[] args)
{
WebDriver webDriver = new EdgeDriver();
webDriver.get("XXX\\race_condition.html");
WebElement element = webDriver.findElement(By.tagName("p"));
Assert.assertEquals(element.getText(),"Hello from JavaScript");
/*
* Exception in thread "main" org.junit.ComparisonFailure:
* expected:<...ello from JavaScript[!]> but was:<...ello from JavaScript[]>
* */
}
}
这里的问题是WebDriver中使用的默认页面加载策略听从document.readyState
在返回调用navigate之前将状态改为complete
。因为p元素是在文档完成加载之后添加的,所以这个WebDriver脚本可能是间歇性的。它可能间歇性是因为无法做出保证说异步触发这些元素或事件不需要显式等待或阻塞这些事件。
幸运的是,WebElement接口上可用的正常指令集-例如WebElement.click
和WebElement.sendKeys
是保证同步的,因为直到命令在浏览器中被完成之前函数调用是不会返回的(或者回调是不会在回调形式的语言中触发的)。高级用户交互APIs,键盘和鼠标例外,因为它们被明确地设计为"按我说的做"的异步命令。
等待是在继续下一步之前会执行一个自动化任务来消耗一定的时间。
为了克服浏览器和WebDriver脚本之间的竞争问题,大多数Selenium客户都附带了一个wait
包。在使用等待时,使用的是通常所说的显式等待。
显式等待
显式等待是Selenium客户可以使用的命令式过程语言。它们允许你的代码暂停程序执行,或冻结线程,直到满足通过的条件。这个条件会以一定频率一直被调用,直到等待超时。这意味着只要条件返回一个假值,它就会一直尝试和等待。
由于显式等待允许你等待条件的发生,所以它们非常适合在浏览器及其DOM和WebDriver脚本之间同步状态。
为了弥补我们之前的错误指令集,可以使用等待来让findElement
调用等待直到脚本中动态添加的元素被添加到DOM中。
import org.openqa.selenium.By;
import org.openqa.selenium.Keys;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.edge.EdgeDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
public class demo2
{
public static void main(String[] args)
{
WebDriver webDriver = new EdgeDriver();
webDriver.get("https://www.baidu.com");
webDriver.findElement(By.id("kw")).sendKeys("Selenium"+ Keys.ENTER);
//初始化等待时间为10秒,直到按键可以点击
WebElement firstResult = new WebDriverWait(webDriver, Duration.ofSeconds(5))
.until(ExpectedConditions.elementToBeClickable(By.id("su")));
//打印结果
System.out.println(firstResult.getAttribute("value"));//百度一下
}
}
我们将条件作为函数引用传递,等待将会重复运行直到其返回值为true。"truthful"返回值是在当前语言中计算为boolean true的任何值,例如字符串、数字、boolean、对象(包括WebElement)或填充(非空)的序列或列表。这意味着空列表的计算结果为false。当条件为true且阻塞等待终止时,条件的返回值将成为等待的返回值。
有了这些知识,并且因为等待实用程序默认情况下会忽略no such element
的错误,所以我们可以重构我们的指令使其更简洁。
import org.junit.Assert;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.edge.EdgeDriver;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
public class demo3
{
public static void main(String[] args)
{
WebDriver webDriver = new EdgeDriver();
webDriver.get("XXX\\race_condition.html");
WebElement foo = new WebDriverWait(webDriver, Duration.ofSeconds(6))
.until(webDriver1 -> webDriver1.findElement(By.tagName("p")));
//Variable 'webDriver' is already defined in the scope
Assert.assertEquals(foo.getText(),"Hello from JavaScript!");
}
}
在这个示例中,我们传递一个匿名函数(但是我们也可以像前面那样显式地定义它,以便重用它)。传递给我们条件的第一个,也是唯一的一个参数始终是对驱动程序对象WebDriver的引用。在多线程环境中,应该小心操作传入条件的驱动程序引用,而不是外部范围中对驱动程序的引用。
因为等待将会吞没在没有找到元素时引发的no such element
的错误,这个条件会一直重试直到找到元素为止。然后它将获取一个WebElement的返回值,并将其传递回我们的脚本。
如果条件失败,例如从未得到条件为真实的返回值,等待将会抛出/引发一个叫timeout error
的错误/异常。
选项
等待条件可以根据你的需要进行定制。有时候是没有必要等待缺省超时的全部范围,因为没有达到成功条件的代价可能很高。
等待允许你传入一个参数来覆盖超时:
new WebDriverWait(driver, Duration.ofSeconds(3)).until(ExpectedConditions.elementToBeClickable(By.xpath("//a/h3")));
预期的条件
由于必须同步DOM和指令是相当常见的情况,所以大多数客户端还附带一组预定义的预期条件。顾名思义,它们是为频繁等待操作预定义的条件。
不同的语言绑定提供的条件各不相同,可以参开每个客户端绑定的API文档:
- Java:https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/support/ui/ExpectedConditions.html
- Python:https://seleniumhq.github.io/selenium/docs/api/py/webdriver_support/selenium.webdriver.support.expected_conditions.html?highlight=expected
隐式等待
还有第二种区别于显示等待类型的隐式等待。通过隐式等待,WebDriver在试图查找任何元素时在一定时间内轮询DOM。当网页上的某些元素不是立即可用并且需要一些时间来加载时是很有用的。
默认情况下隐式等待元素出现是禁用的,需要在单个会话的基础上手动启用。将显示等待和隐式等待混合在一起会导致意想不到的结果,就是说即使元素可用或条件为真也要等待睡眠的最长时间。
警告:不要混合使用隐式和显式等待。这样做会导致不可预测的等待时间。例如:将隐式等待设置为10秒,将显式等待设置为15秒,可能会导致在20秒后发生超时。
隐式等待是告诉WebDriver如果在查找一个或多个不是立即可用的元素时轮询DOM一段时间。默认设置为0,表示禁用。一旦设置好,隐式等待就被设置为会话的生命周期。
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.edge.EdgeDriver;
import java.time.Duration;
public class demo5
{
public static void main(String[] args)
{
WebDriver driver = new EdgeDriver();
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
driver.get("http://somedomain/url_that_delays_loading");
WebElement myDynamicElement = driver.findElement(By.id("myDynamicElement"));
}
}
流畅等待
流畅等待实例定义了等待条件的最大时间量,以及检查条件的频率。
用户可以配置等待来忽略等待时出现的特定类型的异常,例如在页面上搜索元素时出现的NoSuchElementException
。
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.edge.EdgeDriver;
import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.Wait;
import java.time.Duration;
import java.util.function.Function;
public class demo6
{
public static void main(String[] args)
{
WebDriver webDriver = new EdgeDriver();
webDriver.get("https://www.example.com");
// Waiting 10 seconds for an element to be present on the page, checking
// for its presence once every 2 seconds.
Wait<WebDriver> wait = new FluentWait<WebDriver>(webDriver)
.withTimeout(Duration.ofSeconds(10))
.pollingEvery(Duration.ofSeconds(2))
.ignoring(NoSuchElementException.class);
WebElement foo = wait.until(new Function<WebDriver, WebElement>()
{
@Override
public WebElement apply(WebDriver webDriver)
{
return webDriver.findElement(By.id("foo"));
}
});
}
}
- 点赞
- 收藏
- 关注作者
评论(0)