「Selenium」- 正确的休眠(等待)方法

问题描述

当我们加载页面后,可能需要等待页面渲染,等待某个 HTML 元素加载完成。我们经常使用 Thread.sleep() 进行等待,但是具有以下缺点:
1)等待时间过长,而页面已经加载完成;等待时间过短,而页面还未加载完成;
2)我们无法确定要等待的具体时间。如果使用 while 循环检查,程序会显得“不整洁”;
3)每个查找元素的地方都需要等待;
4)必须等待特定时间后,即 Thread.sleep() 设置的时间,才能继续执行后续程序;

解决方案

我们可以使用 Selenium 提供的等待方法:
1)Implicit wait – Provided by Selenium WebDriver
2)Explicit wait (WebDriverWait & FluentWait) Provided by Selenium WebDriver
3)Fluent Wait

Implicit Wait

如下是演示代码(只包含关键部分),我们通过演示代码进行讲解:

WebDriver driver=new ChromeDriver();

driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);

driver.get("https://www.easemytrip.com/");
driver.findElement(By.id("FromSector_show")).sendKeys("Delhi", Keys.ENTER);
driver.findElement(By.id("Editbox13_show")).sendKeys("Mumbai", Keys.ENTER);

如上示例,使用 implicitlyWait 最多 30s 等待,具有以下优势:
1)在 findElement 时,最多 30s 等待,只要找元素就立即向下执行;
2)如果在 30s 内没有找到,则返回 ElementNotVisibleException 异常;
3)全局设置(只需要设置一次,无需在每次查找元素时进行设置);

但是我们会遇到另外场景,比如:虽然 HTML 元素已经找到,但是在页面元素是否可见、是否可以点击,这些会影响自动化测试的进行。针对这个问题,我们可以使用 Explicit wait 等待。

Explicit wait

如下是演示代码(只包含关键部分),我们通过演示代码进行讲解:

WebDriver driver = new ChromeDriver();
driver.get("https://www.rentomojo.com/");

// 等待页面元素可见
WebDriverWait wait = new WebDriverWait(driver, 120);
wait.until(ExpectedConditions.visibilityOf(driver.findElement(By.xpath("//div[@class='Campaign__innerWrapper']/button"))));
driver.findElement(By.xpath("//div[@class='Campaign__innerWrapper']/button")).click();

// 等待 body 中出现内容
// https://stackoverflow.com/questions/15656252/wait-till-text-present-in-text-field/15657053
new WebDriverWait(driver, 120).until(new ExpectedCondition<Boolean>() {
	@Override
	public Boolean apply(WebDriver input) {
		WebElement bodyElement = input.findElement(By.xpath("html/body"));
		return !"".equals(bodyElement.getAttribute("innerHTML").trim());
	}
});

如上程序,通过 visibilityOf 方法等待,直到特定元素可见。通过该方法可以判断某些 HTML 元素是否已经处于特定状态。还有很多其他状态,参考 ExpectedConditions 文档。

Fluent Wait

类似与 Explicit wait 等待,但是更加灵活,可以自定义等待时间粒度、忽略异常等等:

Wait<WebDriver> fluentWait = new FluentWait<WebDriver>(driver)
		.withTimeout(60, TimeUnit.SECONDS) // // this defines the polling frequency
		.pollingEvery(2, TimeUnit.SECONDS)
		.ignoring(NoSuchElementException.class); // this defines the exception to ignore

WebElement foo = fluentWait.until(new Function<WebDriver, WebElement>() {
	// in this method defined your own subjected conditions for which
	// we need to wait for
	public WebElement apply(WebDriver driver) {
		return driver.findElement(By.id("foo"));
	}
});

注意事项,我们没有使用过 Fluent Wait 等待,这里只是简单整理,详细方法需要参考官方文档。

参考文献

Using Thread.sleep() in Selenium WebDriver – Make Selenium Easy