Selenium 是 ThroughtWorks 一个强大的基于浏览器的开源自动化测试工具,它通常用来编写 Web 应用的自动化测试。随着 Selenium 团队发布 Selenium 2(又名 WebDriver)之后,本应该退役的 Selenium 1 却还在为很多人应用,这究竟是什么原因呢?Webdriver 又有什么优势可以击败 Selenium 1 并让大家选择它呢?
追踪溯源,WebDriver 和 Selenium 本是两个独立的项目,实现机制也是不同的。那 Selenium 团队为什么会在 Selenium 2 中将两者合并,这究竟有什么用意呢?WebDriver 比 Selenium 又有什么优势呢?我们该如何选择使用 Selenium 还是 WebDriver 呢?别着急,您将在本文中找到答案,并将了解一些 WebDriver 的基本知识和使用方法。
为方便表述,在本文中,我们称 Selenium 2 为 WebDirver,Selenium 为 Selenium 1.x(因为 Selenium1.x 时通常指的是 Selenium RC,所以 Selenium 也指 Selenium RC)。
WebDriver 是… …?
Selenium 2,又名 WebDriver,它的主要新功能是集成了 Selenium 1.0 以及 WebDriver(WebDriver 曾经是 Selenium 的竞争对手)。也就是说 Selenium 2 是 Selenium 和 WebDriver 两个项目的合并,即 Selenium 2 兼容 Selenium,它既支持 Selenium API 也支持 WebDriver API。
那 Selenium 团队为什么会将两个项目合并呢?我们通常认为其中部分原因是 WebDriver 解决了 Selenium 存在的缺点(比如,能够绕过 JS 沙箱),部分原因是 Selenium 解决了 WebDriver 存在的问题(比如,支持更广泛的浏览器和编程语言),不论真正的原因是什么两个项目的合并为用户提供了一个优秀的自动化测试框架。
现在让我们看看两个工具有什么具体的不同。在开始之前,我们首先看一下用 Selenium 和用 Webdriver 构建出来的测试工程是什么样的,后文会在这个基础上阐述 Webdriver 和 Selenium 的异同。
说明:因为现在 WebDriver 还在改进和优化过程中,所以我们以下的举例和说明都是基于版本 selenium-2.28.0 的基础上。
构建一个 Selenium 测试工程
Selenium API 则支持更多的编程语言,这里我们还是以 Java 为例。
图 1. Selenium 测试工程
![Selenium 测试工程](https://i-blog.csdnimg.cn/blog_migrate/ec108b3ee62dcd2a2e3fbda75b84011a.png)
清单 1. 使用 Selenium API 的脚本 - 登录 SmartCloud iNotes
package demo; import com.thoughtworks.selenium.DefaultSelenium; import com.thoughtworks.selenium.Selenium; public class SeleniumDemo { public static void main(String[] args) throws InterruptedException { // 创建一个 Selenium 实例 Selenium selenium = new DefaultSelenium("localhost", 4444, \ "*firefox", "https://apps.na.collabserv.com/"); // 启动 selenium session selenium.start(); // 打开测试网页 selenium.open("https://apps.lotuslive.com/"); // 输入用户名,密码 selenium.type("//input[@id='username']", \ "autouser01@e3yunmail.mail.lotuslive.com"); selenium.type("//input[@id='password']", "test"); // 登录 selenium.click("//input[@id='submit_form']"); // 等待直到页面出现 Mail 链接 int count = 60; while(count > 0){ if(selenium.isElementPresent("//a[contains(text(),'Mail')]")){ break; }else{ Thread.sleep(1000); count--; } } // 登出 selenium.click("//a[contains(text(),'Log Out')]"); // 测试结束后,终止 selenium session selenium.stop(); } }
构建一个 WebDriver 测试工程
WebDriver API 可以通过 Python、Ruby、Java 和 C#访问,支持开发人员使用他们偏爱的编程语言来创建测试。这里我们以 Java 为例。首先需要准备好自己的 Eclipse 环境,并在 selenium 的官方网站下载 Selenium 2 的 Jar 包。
图 2. WebDriver 测试工程
![WebDriver 测试工程](https://i-blog.csdnimg.cn/blog_migrate/ccf35b5b002e23149f3be18084acaaea.png)
清单 2. 使用 WebDriver API 的脚本 - 登录 SmartCoud iNotes
package demo; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.firefox.FirefoxDriver; import org.openqa.selenium.support.ui.ExpectedCondition; import org.openqa.selenium.support.ui.WebDriverWait; public class WebDriverDemo { public static void main(String[] args) { //创建一个 firefox driver 实例 WebDriver driver = new FirefoxDriver(); //打开测试网址 driver.get("https://apps.na.collabserv.com/"); //定义用户名和密码文本框 WebElement username=driver.findElement(By.id("username")); WebElement password=driver.findElement(By.id("password")); //输入用户名和密码 username.sendKeys("autouser01@e3yunmail.mail.lotuslive.com"); password.sendKeys("test"); //点击 login 登录 WebElement login=driver.findElement(By.id("submit_form")); login.click(); //设置页面等待直到出现 Mail 链接 (new WebDriverWait(driver, 500)).until(new ExpectedCondition<WebElement>(){ public WebElement apply(WebDriver dr) { return dr.findElement(By.linkText("Mail")); } }); //登出 WebElement logout=driver.findElement(By.linkText("Log Out")); logout.click(); //关闭浏览器 driver.quit(); } }
Selenium vs WebDriver
从上述用 Selenium 和 WebDriver 构建的两个测试工程来看,WebDriver 工程在构建之后不需要其他的配置我们便可以直接使用,这一点和 Selenium 是截然不同的。因为 Selenium 还需要安装并启动 Selenium Server 才能运行测试程序。
另外,我们可以看出 WebDriver 是基于面向对象的 API,它更多的是从用户角度出发。反之 Selenium 提供的是基于字典的 API,用户可以很方便的看到所以支持的方法。毋庸置疑的是,WebDriver 提供的 API 更为简洁,对用户更加的友好。但从另一个角度来看,就是用户不能很直观的看到 WebDriver 提供了哪些 API,可能需要通过官网提供的 JavaDoc(来源:http://selenium.googlecode.com/svn/trunk/docs/api/java/index.html)的协助来找寻一些方法。
下面我们看下 WebDriver 支持的浏览器类型,以及一些基本操作的使用方法。
支持的浏览器
Selenium 是由一堆 JavaScript 实现的,所以只要支持 JavaScript 的浏览器 Selenium 都可以做到很好的支持,理论上来讲它比 WebDriver 能支持更多的浏览器而且不需要做额外的开发。但是因为每个浏览器对于 JavaScript 的执行有着不同程序的安全限制,以防止用户被恶意攻击,所以 Selenium 存在一些不能处理的情况,例如本机的鼠标和键盘事件,弹出框等等。WebDriver 则通过不同的方法来解决 Selenium 面临的一些问题,不单单使用 JavaScript ,WebDriver 会使用任何一种合适的机制来操作浏览器。WebDriver 提供了FirefoxDriver、InternetExplorerDriver、OperaDriver、 ChromeDriver、SafariDriver、HtmlUnitDriver,以及 RemoteWebDriver 来支持不同的浏览器。另外为了支持移动设备它提供了 AndroidDriver 和 IPhoneDriver。其中很特别的是 HtmlUnitDriver,它的无界面实现不会实际打开浏览器,运行速度很快,所以用户可以使用它来做一些单元测试。对于用 FireFox 或 Internet Explorer 等浏览器来做测试的自动化测试用例,运行速度通常很慢,HtmlUnit Driver 无疑是可以很好地解决这个问题。
那么既然 Selenium 和 WebDriver 支持的浏览器差不多,我们究竟应该选择 Selenium 还是 WebDriver 呢?虽然现在 WebDriver 还在处在开发和测试当中,有些功能还存在一些缺陷,但随着 Webdriver 的不断完善,它替代 Selenium 终将是大势所趋。所以笔者还是建议选择使用 WebDriver,如果确实 WebDriver 还不能支持你的测试工作,用 Selenium 来做一个过渡也是可以的。当然选用不同的测试工具可能对代码维护有一定的挑战,毕竟 Selenium 和 WebDriver 的 API 是不同的,幸好 WebDriver 还提供了另外一种方法。例如在笔者现在的项目中要支持 Safari,但是使用 SafariDriver 测试时非常的不稳定以致测试不能顺利的进行。所以使用了 SeleneseCommandExecutor,这种方法也可以让 WebDriver 支持更多的浏览器并还是使用 WebDriver API,这样也会降低维护代码的工作量。需要注意的是,这种方法需要安装并启动 Selenium Server。
清单 3. 使用 SelenseCommandExecutor 使 WebDriver 支持更多的浏览器
Capabilities capabilities = new DesiredCapabilities() capabilities.setBrowserName( "safari" ); CommandExecutor executor = new SeleneseCommandExecutor( "http:localhost:4444/",/ "http://WebDriver driver = new RemoteWebDriver(executor, capabilities);
定位页面元素
Selenium 对元素的处理是基于 Action,Object(操作,对象)的方式,使用 id、name,或是 XPath 来定位页面元素。当使用的是 XPath 时在某种程度上对 Internet Explorer 的测试速度有很大的影响,因为 IE 浏览器没有内置对 XPath 的支持,所以在 IE 上测试时会特别慢,当然现在已经有办法解决这个问题了,虽然比不上 Firefox 的测试速度但也是有了相当大的提高。
WebDriver 则使用 findElement 方法来查找到页面元素,它定位页面元素可以通过 id、name、xpath、className、link text、CSS 等等查找。
表 1. WebDriver 查找页面元素
查找方式 | 描述 | 举例 (Java) |
---|---|---|
By.id | 以元素的 id 属性来定位页面元素 | By.id(“username”) |
By.name | 以元素的 name 属性来定位页面元素 | By.name(“username”) |
By.xpath | 以 XPath 来定位页面元素 | By.xpath(“//*[@id="username"]) |
By.className | 以元素的 class 属性来定位页面元素 | By.className(“even-table-row”) |
By.cssSelector | 基于 CSS 选择器引擎定位页面元素 | By.cssSelector(“#username”) |
By.LinkText By.partialLinkText | 查找包含链接字串的页面元素 | By.linkText(“Click Me!”) By.partialLinkText(“ck M”) |
By.TagName | 以元素的标签名称查找页面元素 | By.tagName(“td”) |
有人研究过,By CSS 方式是最快的查找方式,所以在做测试时,我们可以尽量使用 By.cssSelector 的方式。(来源:http://sauceio.com/index.php/2011/05/why-css-locators-are-the-way-to-go-vs-xpath/)
页面等待
在 Web UI 的自动化测试中,一种常见的不稳定是页面上的元素加载时间不固定,比如在 Ajax,或者 JS 延迟加载等情况下,页面元素出现的时间短的几毫秒,长的几秒钟。这个时候在读取页面元素就会一些麻烦。等得时间短了话找不到页面元素,测试 fail;但等得时间过长,又会增加测试的时间造成效率低下。
在 Selenium 中,用户只能估算一个时间使用 Selenium.waitForPageToLoad()或者 Thread.sleep()的方式来等待页面加载时间,要不就是使用自己写的 Wait 方法,比如:
清单 4. 使用 Selenium API 的脚本 – 等待页面元素加载
public static void WaitForElementPresent(String elementName) { int maxTry = 60; if(selenium.isElementPresent(elementName))return; for (int second = 0; second < maxTry; second++) { if (selenium.isElementPresent(elementName)) break; Thread.sleep(1000); //等待一秒钟 }
但在 Webdriver 中,它提供了 Explicit and Implicit Waits 方法,使得等待的方法更加灵便使用,在下面的例子就是一个封装好的等待页面加载的方法,在 20 秒钟内等待页面元素出现,如果超过 20 秒就会抛出超时异常:
清单 5. 使用 WebDriver API 的脚本 – 等待页面元素加载
public boolean waitForElementDisplayed(WebElement el, boolean shouldBeDisplayed) { boolean displayed = false; if (shouldBeDisplayed) { try { WebDriverWait wait = new WebDriverWait(driver, 20); wait.until(visibilityOfElementLocated(el)); displayed = true; } catch (Exception e) { displayed = false; } } else { try { displayed = el.isDisplayed(); } catch (NoSuchElementException e) { displayed = false; } } return displayed; }
更多关于 Explicit and Implicit Waits 的内容,请参考 http://seleniumhq.org/docs/04_webdriver_advanced.jsp#implicit-waits
并行测试支持
在 Web UI 自动化测试中,执行效率是非常重要的,尤其是在测试脚本比较多的情况下。所以我们有必要支持并行化测试,并行化测试需要两个条件:
- 在运行测试用例端,需要支持并行运行。例如,写一个多线程来支持同时运行多个测试用例
- 测试用例之间,需要减除在并行运行时的依赖关系。例如,使用不同的用户,以免发生测试冲突
- 在 Selenium Server 端,要能够同时打开多个浏览器窗口,并使得各个浏览器窗口之间的测试不受干扰,各自运行
对于条件 1,我们可以自己写一个多线程来支持并行运行,支持并行的自动化测试,或者使用 JUnit 或者 TestNG;
对于条件 2,我们在编测试用户脚本的时候,需要考虑到在在并行测试的时候可能存在的测试冲突,比如写日志,测试用户等等,可以参考文章(用 STAF+Selenium 实现并行的测试化框架,来源:http://www.ibm.com/developerworks/cn/java/j-lo-parallelautotest/index.html);
而对于条件 3,我们需要借助 Selenium Grid。利用 Selenium Grid,我们可以很方便的同时在多台测试机器上和异构环境中并行地运行多个测试实例以加快 Web-app 的测试。
Selenium RC 的并行测试支持
在 Selenium 时代,我们使用的 Selenium Grid 是一个单独的项目(http://selenium-grid.seleniumhq.org/)。Selenium Grid 的机制是启动一个 hub,然后启动多个 Selenium RC 注册到 hub 上,当测试请求到 hub 时,hub 会将测试分发给 Selenium RC, Selenium RC 会实际的启动一个浏览器完成测试。在这笔者们对它的使用不再做过多的描述,只是想分享一个之前在测试项目中遇到的问题。我们知道,有些情况下一些软件缺陷可能发生在同一个浏览器的不同操作系统环境上,或者是测试需要在特定的浏览器版本上进行,那我们在使用 Selenium Grid 的情况下,如何能够确认测试是运行在什么指定的环境上呢?在我们的项目里面是通过更改配置文件 grid_configuration.xml 来实现的,例如我们有 Win7、WinXP 以及 Linux,上面分别装有 Firefox17 或者 Firefox18,那我们可以使用下面的配置信息:
清单 6. 自定制的 Selenium Grid 配置文件
hub: port: 4444 remoteControlPollingIntervalInSeconds: 180 sessionMaxIdleTimeInSeconds: 300 environments: - name: "Firefox 17 on Win7" browser: "*firefox" - name: "Firefox 17 on WinXP" browser: "*firefox" - name: "Firefox 18 on Linux" browser: "*firefox"
这样我们在三台需要注册 Selenium RC 的机器上运行下列命令并指定相应的参数:
ant -Dport=<port> -Dhost=<hostname> -DhubURL=<hub url>\ -Denvironment="Firefox 17 on Win7" launch-remote-control
其中,-Dport 是指定 Selenium RC 的端口号,-DHost 指定 Selenium RC 所在的机器的主机名,-DhubURL 指定 hub 的 URL,-Denvironment 表示注册的 Selenium RC 的浏览器,在这里我们可以替换成相应的浏览器、版本已经操作系统类型。完成注册之后,在 Selenium Grid 的控制台中可以看到可用的 Selenium RC:
图 3. 使用自定制配置文件的 Selenium Grid 控制台
![使用自定制配置文件的 Selenium Grid 控制台](https://i-blog.csdnimg.cn/blog_migrate/ace0886279b58332e06b7f99580de7d0.png)
最后,我们在代码中指定要测试的环境,hub 就可以将测试转发到相应的环境上去运行,这样我们就可以灵活的按照测试需要在预期的环境上面完成测试。
WebDriver 的并行测试支持
在 Selenium 2 中,Selenium Grid 被集成到了 Selenium Server 中,即是包含在 selenium-server-standalone-x-x-x.jar 包中,好处就是更简洁更方便了!Selenium Grid 包含有两种角色,hub 和 node,其中 hub 是用来接收所有的请求,并将请求分发给不同的 node;node 指的便是实际执行测试的节点,它包含 Selenium 和 WebDriver 两种类型,其中 Selenium 是兼容 Selenium 1 中的 Selenium RC。下面我们看下如何启动 hub 和 node:
启动 Selenium Grid hub:
#启动 Selenium Grid 的 hub,指定端口号 4444,最多运行 40 个测试 session java -jar selenium-server-standalone-2.28.0.jar -role hub -maxSession 40 -port 4444
挂载 node,在缺省情况下,会分别为 Selenium 和 Webdriver 的测试引擎启动 5 个 firefox,5 个 googlechrome,和 1 个 internet explorer。但也可以通过参数的方式来定制启动 node 所支持的浏览器,例如:
#挂载 node,支持启动 20 个 session 的版本号为 17.0.1 的 firefox 浏览器,指定端口号为 5555,指定启动 firefox 时使用的 profile java -jar selenium-server-standalone-2.28.0.jar -role node \ -hub http://{grid_server}:4444/grid/register -port 5555 \ -firefoxProfileTemplate llinotes.profile -maxSession 20 \ -browser "browserName=firefox,version=17,platform=WINDOWS,maxInstances=20"
客户端的调用,在客户端向 Selenium Grid 的 hub 来提交一个测试请求时,对于申请 Selenium 的 node 是和使用 Selenium 1 的方法是一样的,直接使用 DefaultSelenium 就可以了:
Selenium selenium = new DefaultSelenium(“{grid_server}”, \ 4444, “*firefox”, “https://apps.na.collabserv.com/”);
对于 WebDriver 的 node 的话,我们需要使用 RemoteDWebriver 和 DesiredCapabilities 来定义你想向 Selenium Grid 申请的浏览器的类型,版本号等等,例如下面的 capability 就可以匹配到browserName=firefox,version=17,platform=WINDOWS
的浏览器:
DesiredCapabilities capability = DesiredCapabilities.firefox(); capability.setBrowserName(“firefox” ); capability.setPlatform(“WINDOWS”); capability.setVersion(“17.0.1”); WebDriver driver = new RemoteWebDriver(\ new URL("http://{grid_driver}:4444/wd/hub"), capability);
其实,除了将 Selenium Grid 合并到 Selenium 2 中以及加入对 WebDriver 的支持之外,没有太大的改变,Selenium Grid 的使用方面没有太大的区别,可以参考官方网站获得更多信息:http://code.google.com/p/selenium/wiki/Grid2
小结
WebDriver 有更简洁的 API,它针对各个浏览器而开发,取代了嵌入到被测 Web 应用中的 JavaScript。它与浏览器的紧密集成支持创建更高级的测试,避免了 JavaScript 安全模型导致的限制。除了来自浏览器厂商的支持之外,WebDriver 还可以利用操作系统级的调用模拟用户输入,例如鼠标和键盘操作,这些都是 Selenium 所不能比拟的。
而 Selenium 可以使用任何支持 HTTP 的编程语言,它基于 Javasript 并支持多数浏览器,但它也不是完美的,由于浏览器对于 Javascript 的安全策略的加强导致某些情况下不能使用 Selenium。而且随着 WebDriver 的逐步完善,Selenium 可能将完全不受支持,所以选择 WebDriver 必将成为一个必然趋势。但毋庸置疑的是两个项目的将为用户提供了一个更为通用的强大的 Web 测试框架。
从 Selenium RC 迁移到 WebDriver
步骤
在 Selenium 2 发布之前,很多测试团队已经开始使用 Selenium 的 Selenium RC 并构建了成熟的测试框架,那么重构并将原有的测试脚本迁移到 Selenium 2 上无疑是一项非常巨大的工作。幸好 Selenium 2 提供了相应的 WebDriver 做为支持。
WebDriverBackendSelenium 为用户提供 Selenium 的实现,即是,我们已经使用 Selenium API 编写的测试脚本,可以在 WebDriver 下面使用。比如我们要在 Firefox 上面进行测试,在创建 selenium 实例的时候,可以修改为:
WebDriver driver = new FirefoxDriver(); Selenium selenium = new WebDriverBackedSelenium(driver, "https://apps.na.collabserv.com/");
编译并运行测试,会发现还会存在一些 fail 的地方,这意味着你需要重新调试代码,使已有的测试脚本完全的支持 WebDriver。下面是笔者在做移植的时候遇到的一些问题:
- waitForPageToLoad():在移植到 WebDriver 后,会 fail 到测试脚本,所以需要重写来支持页面等待
- fireEvent():例如实现 mouseover 的功能,在移植后可以使用 Action 类来实现,它给用户提供了一些模拟用户交互的方法,比如 mouse_to 等
- keyUp,keyDown,KeyPress:在原来的 Selenium 的代码中大量应用了这几个方法,在移植后统一用 sendKeys()来代替了,这也正正说明了 WebDriver 有着更为简洁的 API
在移植的时候不同的自动化测试项目遇到的问题可能是不同的,但基本都是可以在 WebDriver 中找到相应的解决方法。事实上,单单完成上面的步骤是非常容易使原有使用 Selenium 的代码运行在 WebDriver 上面,但完成移植还有重要的一步就是重写代码使它使用 WebDriver API,这个就比较依赖于原来的代码的设计,如果调用 Selenium API 的方法遍布于 testcase、task 层等等,那么可能就需要比较长的时间来完全使用 WebDriver API 了。所以一个自动化测试项目的设计也是非常重要的。
使测试脚本兼容 Selenium 和 WebDriver
既然现在 Selenium 和 WebDriver 还各有优势,我们可以灵活设计自己的自动化测试项目,使其能够根据测试需求来选择测试工具。比如,测试 Safari,我们可以选择 Selenium,测试 Firefox,Chrome 等等就选择使用 WebDriver。这样的话,就需要抽象出更加灵活的自动化测试框架,比如,我们可以根据页面测试元素类型来抽象出不同的 widgets,例如 WebLink,WebText 等等,对这个元素的操作都封装在 widget 之内,那么在编写测试脚本的时候可以很大程度的使测试脚本和测试工具相分离,不仅可以使得测试脚本能够支持不同的测试工具,也使得在移植测试脚本的时候可以最大限度的降低工作量。
总结
通过上文我们可以大致了解 Selenium、WebDriver 以及它的一些普通用法,希望本文对您的自动化测试项目有所帮助。
参考资料
学习
- Selenium官方网站,了解和下载 Selenium 系列工具
- Selenium Grid,了解 Selenium 1 中的应用
- Selenium Grid2,了解 Selenium Grid 在 Selenium 2 中的应用
- 为什么要选用 CSS Selector,为什么 css locator 好过使用 xpath
- 用 STAF+Selenium 实现并行的测试化框架,了解一个实现并行测试的测试框架
- developerWorks Web development 专区:通过专门关于 Web 技术的文章和教程,扩展您在网站开发方面的技能。
- developerWorks Ajax 资源中心:这是有关 Ajax 编程模型信息的一站式中心,包括很多文档、教程、论坛、blog、wiki 和新闻。任何 Ajax 的新信息都能在这里找到。
- developerWorks Web 2.0 资源中心,这是有关 Web 2.0 相关信息的一站式中心,包括大量 Web 2.0 技术文章、教程、下载和相关技术资源。您还可以通过 Web 2.0 新手入门 栏目,迅速了解 Web 2.0 的相关概念。
- 查看 HTML5 专题,了解更多和 HTML5 相关的知识和动向。
讨论
- 加入 developerWorks 中文社区。查看开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户交流。
come from: http://www.ibm.com/developerworks/cn/web/1309_fengyq_seleniumvswebdriver/