Geb 是一款面向开发者的页面自动化工具。它借助 Groovy 语言的动态特性构建了一个用于建模可重用的页面内容的功能强大的领域特定语言(DSL);同时借鉴了 JQuery 的关键概念提供了一套功能强大的页面内容检索和遍历 API,使得查找页面内容、和页面内容交互等操作变得非常简单。
让页面自动化更加简单和高效是催生 Geb 产生的主要原因。Geb 利用 Groovy 语言的动态特性来减少噪声和样板代码,使得开发者更加关注于页面自动化的主要方面——页面内容及交互。
Geb 底层基于 WebDriver 浏览器自动化库,这意味着凡是 WebDriver 支持的浏览器,Geb 都能支持。尽管 Geb 在 WebDriver 上包装了一个方便高效极具生产力的功能层,但如果你需要直达底层的 WebDriver 去做一些直接的操作,也是很容易的。
页面对象设计模式是一种建模可重用、可维护页面内容的常用方法。WebDriver wiki 页面上对页面对象模式的解释如下:
“在你的 Web 应用的页面上有些区域是测试脚本需要与其交互的。页面对象简单的把这些区域建模成测试代码内的对象。这有效的减少了重复的代码,并且如果页面发生变化,这也意味着我们只需要修改一处即可”
文档还指出:
“可以认为页面对象同时面向两个方面。对应用测试用例的开发者而言,他们代表了页面所提供出来的服务。对于非开发者而言,页面对象只是让他们更加了解页面 HTML 的组织结构。页面对象上的方法可以简单的认为是页面提供出来的服务,不需要暴露出页面的实现机制和细节。以网页邮件系统为例,它提供的所有服务中比较典型的能力包括:写新邮件,选择阅读某份邮件,列出邮箱中各邮件的主题等。至于这些是如何实现的并不是测试所关心的。”
页面对象模式是一项重要的技术,Geb 通过 page 和 module 模块为它提供了一等公民支持。
JQuery JavaScript 库提供了一套非常优越的 API 用于选择或定位页面元素,以及在页面元素内或元素周围进行遍历操作。Geb 也从 jQuery API 受到了极多的启发。
在 Geb 中,页面元素使用 $ 函数进行选择和定位,它返回一个导航器(Navigator)对象。导航器对象从某种意义上类似于 jQuery 中的 jQuery 数据类型,因为导航器(Navigator)代表了页面上的一个或多个目标元素。
我们来看些例子:
$("div") //1
$("div", 0) //2
$("div", title: "section") //3
$("div", 0, title: "section") //4
$("div.main") //5
$("div.main", 0) //6
//1 :匹配页面上所有的 div 元素
//2 :匹配页面上第一个 div 元素
//3 :匹配页面上所有 title 属性等于 section 的 div 元素
//4 :匹配页面上第一个 title 属性等于 section 的 div 元素
//5 :匹配页面上所有 class 为 main 的 div 元素
//6 :匹配页面上第一个 class 为 main 的 div 元素
所有这些方法都返回 Navigator 对象,该对象可以用来进一步提炼页面内容:
$("p", 0).parent() //1
$("p").find("table", cellspacing: '0') //2
//1 :匹配页面上第一个 p 元素的父元素
//2 :匹配 p 元素下的所有 cellspacing 属性为 0 的 table 元素
这些只是导航器的一些基本介绍,后续导航器章节会进行详细阐明。
我们来看一下访问 Geb 首页,然后导航到本用户手册最新版本的例子。
下面的例子展示了以内联脚本风格(不使用页面对象或预先定义好的页面内容)使用 Geb...
import geb.Browser
Browser.drive {
go "http://gebish.org"
assert title == "Geb - Very Groovy Browser Automation" //1
$("div.menu a.manuals").click() //2
waitFor { !$("#manuals-menu").hasClass("animating") } //3
$("#manuals-menu a")[0].click() //4
assert title.startsWith("The Book Of Geb") //5
}
//1:检查当前处于 Geb 的首页
//2:点击用户手册菜单
//3:等待菜单打开动画完成
//4:点击多个版本的用户手册链接中的第一个
//5:检查当前页面已跳转到 Geb 用户手册页面
这次让我们使用页面对象模型来定义前面访问的页面内容...
import geb.Module
import geb.Page
class ManualsMenuModule extends Module { //1
static content = { //2
toggle { $("div.menu a.manuals") }
linksContainer { $("#manuals-menu") }
links { linksContainer.find("a") } //3
}
void open() { //4
toggle.click()
waitFor { !linksContainer.hasClass("animating") }
}
}
class GebHomePage extends Page {
static url = "http://gebish.org" //5
static at = { title == "Geb - Very Groovy Browser Automation" } //6
static content = {
manualsMenu { module(ManualsMenuModule) } //7
}
}
class TheBookOfGebPage extends Page {
static at = { title.startsWith("The Book Of Geb") }
}
//1:模块 Module 是可以在多个页面间复用的片段(一块页面元素)。这里我们使用一个模块来建模页面上的下拉菜单。
//2:Geb 定义页面内容的领域特点语言(DSL)
//3:定义页面内容时,可以基于其他已定义的页面内容来定义相对的页面内容元素
//4:模块内可以包含方法,这样可以隐藏页面文档结构细节和交互的复杂性
//5:页面可以定义他们的位置(url),url 既可以使用绝对路径也可以使用相对于某个 baseUrl 的相对路径
//6:at 检查器可以用来验证浏览器当前正处于(at)指定的页面,例如此处只要满足页面的 title 等于 Geb - Very Groovy Browser Automation 就认为浏览器当前在 GebHomePage
//7:页面中包含了前面定义的模块,即前面模块 ManualsMenuModule 中定义的内容是 GebHomePage 页面内容的一部分
下面是使用以上定义的页面对象的驱动脚本:
import geb.Browser
Browser.drive {
to GebHomePage //1
manualsMenu.open()
manualsMenu.links[0].click()
at TheBookOfGebPage
}
//1:到 GebHomePage 页面对象的 url 所指定的页面,并且验证页面的 at 检查器,确认浏览器当前的确处于 GebHomePage 页面
Geb 自身并不包含任何测试执行框架。Geb 能和各种流行的测试框架(如:Spock, Junit, TestNg, Cucumber-JVM)协同工作。尽管 Geb 可以很好的和这些测试框架协同工作,我们还是推荐 Spock,因为 Spock 的书写风格和框架关注的焦点和 Geb 是最匹配的。
下面我们使用 Geb 的 Spock 框架集成来书写我们上面关于 Geb 主页的测试用例:
import geb.spock.GebSpec
class GebHomepageSpec extends GebSpec {
def "can access The Book of Geb via homepage"() {
given:
to GebHomePage
when:
manualsMenu.open()
manualsMenu.links[0].click()
then:
at TheBookOfGebPage
}
}
关于使用 Geb 进行 Web 页面功能测试的更多信息,详见后续关于测试的章节。
Geb 自身就是一个独立的 geb-core.jar 的 jar 包,可以从 Maven 中心仓库来获取。想要启动和运行 Geb 测试脚本,我们只需要这个 jar 包,以及一个 WebDriver 实现 和 selenium-support.jar 即可。
使用 @Grab 方式的依赖如下:
@Grapes([
@Grab("org.gebish:geb-core:2.2"),
@Grab("org.seleniumhq.selenium:selenium-firefox-driver:3.12.0"),
@Grab("org.seleniumhq.selenium:selenium-support:3.12.0")
])
import geb.Browser
使用 Maven 方式的依赖如下:
<dependency>
<groupId>org.gebish</groupId>
<artifactId>geb-core</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-firefox-driver</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-support</artifactId>
<version>3.12.0</version>
</dependency>
使用 Gradle 方式的依赖如下:
compile "org.gebish:geb-core:2.2"
compile "org.seleniumhq.selenium:selenium-firefox-driver:3.12.0"
compile "org.seleniumhq.selenium:selenium-support:3.12.0"
此外,如果使用了 Geb 的集成项目如 geb-spock,geb-junit4 等,我们就可以只依赖这些集成项目而不需要直接依赖 geb-core. 可以通过下面的链接查看 org.gebish 组下面可用的各个构建。
快照仓库:
如果你喜爱前沿、喜欢尝鲜,你也可以尝试使用 Geb 的快照构建版本,仓库地址为:https://oss.sonatype.org/content/repositories/snapshots/org/gebish/
附录:
笔者附上一个自己使用的 Maven Geb 自动化测试项目的 pom.xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.uitest.scf</groupId>
<artifactId>scf-ui-test</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.5.2</version>
</dependency>
<dependency>
<groupId>org.gebish</groupId>
<artifactId>geb-spock</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<version>1.2-RC1-groovy-2.5</version>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>1.2-RC1-groovy-2.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-server</artifactId>
<version>3.6.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.28</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.0</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.0.13</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.0.13</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>3.2.16.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>3.2.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>3.2.2.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
<compilerId>groovy-eclipse-compiler</compilerId>
<!--
<verbose>true</verbose>
<fork>true</fork>
<compilerArguments>
<javaAgentClass>lombok.launch.Agent</javaAgentClass>
</compilerArguments>
-->
</configuration>
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-eclipse-compiler</artifactId>
<version>3.0.0-01</version>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-eclipse-batch</artifactId>
<version>2.5.2-01</version>
</dependency>
<!--
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.2</version>
</dependency>
-->
</dependencies>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.20.1</version>
<configuration>
<useFile>false</useFile>
<includes>
<include>**/*Spec.java</include>
<include>**/*Test.java</include>
</includes>
<argLine>-Dfile.encoding=UTF-8</argLine>
</configuration>
</plugin>
</plugins>
</build>
</project>