从 RC 迁移到 WebDriver
如何迁移到 Selenium WebDriver
采用 Selenium 2 时,一个常见的问题是,在现有测试集中添加新测试时,什么是正确做法?刚接触该框架的用户可以开始使用新的 WebDriver API 来编写他们的测试。但是对于已经有现有测试套件的用户呢?本指南旨在演示如何将现有测试迁移到新的 API,允许使用 WebDriver 提供的新功能编写所有新测试。
这里介绍的方法描述了逐步迁移到 WebDriver API 的过程,而无需一次性进行大规模的重构。这意味着您可以为迁移现有测试留出更多时间,这可能会让您更容易决定将精力投入到哪里。
本指南使用 Java 编写,因为 Java 对迁移提供了最好的支持。随着我们为其他语言提供更好的工具,本指南将扩展到包括这些语言。
为什么要迁移到 WebDriver
将测试套件从一个 API 迁移到另一个 API 需要付出巨大的努力。您和您的团队为什么要考虑进行此迁移?以下是您应该考虑迁移 Selenium 测试以使用 WebDriver 的一些原因。
- 更小巧紧凑的 API。WebDriver 的 API 比原来的 Selenium RC API 更面向对象。这可以使它更容易使用。
- 更好地模拟用户交互。在可能的情况下,WebDriver 使用原生事件与网页进行交互。这更密切地模拟了您的用户使用您的网站和应用程序的方式。此外,WebDriver 还提供了高级用户交互 API,允许您对网站进行复杂的交互建模。
- 浏览器供应商的支持。Opera、Mozilla 和 Google 都是 WebDriver 开发的积极参与者,并且都有工程师致力于改进该框架。通常,这意味着对 WebDriver 的支持已嵌入到浏览器本身中:您的测试运行得尽可能快且稳定。
开始之前
为了使迁移过程尽可能轻松,请确保所有测试都使用最新的 Selenium 版本正确运行。这听起来可能很明显,但最好还是说出来!
入门
开始迁移的第一步是更改获取 Selenium 实例的方式。当使用 Selenium RC 时,它是这样完成的
Selenium selenium = new DefaultSelenium("localhost", 4444, "*firefox", "http://www.yoursite.com");
selenium.start();
应该像这样替换
WebDriver driver = new FirefoxDriver();
Selenium selenium = new WebDriverBackedSelenium(driver, "http://www.yoursite.com");
下一步
一旦您的测试执行没有错误,下一步是将实际的测试代码迁移到使用 WebDriver API。根据您的代码抽象程度,这可能是一个简短的过程,也可能是一个漫长的过程。无论哪种情况,方法都是相同的,并且可以简单地总结为:在您编辑代码时,修改代码以使用新的 API。
如果您需要从 Selenium 实例中提取底层 WebDriver 实现,您可以简单地将其强制转换为 WrapsDriver
WebDriver driver = ((WrapsDriver) selenium).getWrappedDriver();
这允许您像往常一样继续传递 Selenium 实例,但可以根据需要解包 WebDriver 实例。
在某个时候,您的代码库将主要使用较新的 API。此时,您可以翻转关系,始终使用 WebDriver,并按需实例化 Selenium 实例
Selenium selenium = new WebDriverBackedSelenium(driver, baseUrl);
常见问题
幸运的是,您不是第一个经历此迁移的人,所以这里有一些其他人遇到的常见问题以及如何解决它们。
点击和输入更完整
Selenium RC 测试中的常见模式是看到类似这样的内容
selenium.type("name", "exciting tex");
selenium.keyDown("name", "t");
selenium.keyPress("name", "t");
selenium.keyUp("name", "t");
这依赖于这样一个事实:“type”只是替换了已识别元素的内容,而不会同时触发用户与页面交互时通常会触发的所有事件。“key*”的最终直接调用会导致 JS 处理程序按预期触发。
当使用 WebDriverBackedSelenium 时,填写表单字段的结果将是“exciting texttt”:这不是您所期望的!原因在于 WebDriver 更准确地模拟了用户行为,因此会一直触发事件。
同样的事实有时可能会导致页面加载比 Selenium 1 测试中更早地触发。如果 WebDriver 抛出“StaleElementException”,则可以判断是否发生了这种情况。
WaitForPageToLoad 返回过早
确定页面加载何时完成是一件棘手的事情。我们指的是“当 load 事件触发时”、“当所有 AJAX 请求完成时”、“当没有网络流量时”、“当 document.readyState 更改时”还是其他完全不同的东西?
WebDriver 试图模拟原始的 Selenium 行为,但这并不总是由于各种原因而完美工作。最常见的原因是,很难区分页面加载尚未开始,以及页面加载在方法调用之间已完成。这有时意味着在页面完成(甚至开始!)加载之前,控制权会返回到您的测试。
解决此问题的方法是等待特定内容。通常,这可能是您接下来要交互的元素,或者是将某些 Javascript 变量设置为特定值。一个例子是
Wait<WebDriver> wait = new WebDriverWait(driver, Duration.ofSeconds(30));
WebElement element= wait.until(visibilityOfElementLocated(By.id("some_id")));
其中“visibilityOfElementLocated”实现为
public ExpectedCondition<WebElement> visibilityOfElementLocated(final By locator) {
return new ExpectedCondition<WebElement>() {
public WebElement apply(WebDriver driver) {
WebElement toReturn = driver.findElement(locator);
if (toReturn.isDisplayed()) {
return toReturn;
}
return null;
}
};
}
这看起来可能很复杂,但几乎都是样板代码。唯一有趣的部分是,“ExpectedCondition”将重复评估,直到“apply”方法返回既不是“null”也不是 Boolean.FALSE 的值。
当然,添加所有这些“wait”调用可能会使您的代码混乱。如果情况如此,并且您的需求很简单,请考虑使用隐式等待
driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
这样做,每次定位元素时,如果元素不存在,则会重试该位置,直到它存在或直到过去 30 秒。
使用 XPath 或 CSS 选择器并不总是有效,但在 Selenium 1 中有效
在 Selenium 1 中,xpath 通常使用捆绑的库而不是浏览器本身的功能。除非没有其他选择,否则 WebDriver 将始终使用原生浏览器方法。这意味着复杂的 xpath 表达式可能会在某些浏览器上中断。
在 Selenium 1 中,CSS 选择器是使用 Sizzle 库实现的。这个库实现了 CSS 选择器规范的超集,并且你并不总是清楚自己是否越界使用了 Sizzle 的特性。如果你在使用 WebDriverBackedSelenium 时,使用 Sizzle 定位器而不是 CSS 选择器来查找元素,控制台会记录一个警告。花时间查找这些警告是值得的,特别是当测试因为无法找到元素而失败时。
没有 Browserbot
Selenium RC 是基于 Selenium Core 的,因此当你执行 Javascript 时,你可以访问 Selenium Core 的部分内容以简化操作。由于 WebDriver 不是基于 Selenium Core 的,因此这种方式不再可行。如何判断你是否正在使用 Selenium Core 呢?很简单!只需查看你的 “getEval” 或类似调用中,所执行的 Javascript 是否使用了 “selenium” 或 “browserbot”。
你可能正在使用 browserbot 来获取测试的当前窗口或文档的句柄。幸运的是,WebDriver 始终在当前窗口的上下文中评估 JS,因此你可以直接使用 “window” 或 “document”。
或者,你可能正在使用 browserbot 来定位元素。在 WebDriver 中,执行此操作的惯用方法是首先定位元素,然后将其作为参数传递给 Javascript。因此
String name = selenium.getEval(
"selenium.browserbot.findElement('id=foo', browserbot.getCurrentWindow()).tagName");
变成
WebElement element = driver.findElement(By.id("foo"));
String name = (String) ((JavascriptExecutor) driver).executeScript(
"return arguments[0].tagName", element);
请注意,传入的 “element” 变量是如何作为 JS 标准 “arguments” 数组中的第一个项目出现的。
执行 Javascript 不返回任何内容
WebDriver 的 JavascriptExecutor 会包装所有 JS 并将其评估为匿名表达式。这意味着你需要使用 “return” 关键字
String title = selenium.getEval("browserbot.getCurrentWindow().document.title");
变成
((JavascriptExecutor) driver).executeScript("return document.title;");