使用Selenium内置的PageFactory实现页面对象模式

在这一部分中,将通过Selenium的内置PageFactory支持类来介绍Page Object模式的实现。PageFactory提供一种机制来初始化任何声明WebElementList<WebElement>带有@FindBy注释的字段的Page Object

由于不可描述的原因,我已经将测试网页打包,需要的请留意文末信息。

介绍页面对象模式

页面对象模式的目标是从实际测试中抽象出应用程序页面和功能。页面对象模式提高了代码在测试和固定装置之间的可重用性,但也使代码易于维护。

页面API或页面对象

我们将从将TodoMVC页面建模为Page Object 的项目开始。该对象将表示将在测试中使用的页面API。可以使用接口对API本身进行建模。如果查看以下界面的方法,则会注意到这些方法只是页面上可用的用户功能。用户可以创建待办事项,用户可以重命名待办事项,也可以删除待办事项:

public interface TodoMvc {

    void navigateTo();

    void createTodo(String todoName);

    void createTodos(String... todoNames);

    int getTodosLeft();

    boolean todoExists(String todoName);

    int getTodoCount();

    List<String> getTodos();

    void renameTodo(String todoName, String newTodoName);

    void removeTodo(String todoName);

    void completeTodo(String todoName);

    void completeAllTodos();

    void showActive();

    void showCompleted();

    void clearCompleted();
}

上面的接口隐藏了所有实现细节。实际上,它与Selenium WebDriver无关。因此,从理论上讲,我们可以针对不同的设备(例如移动本机应用程序,桌面应用程序和Web应用程序)使用此页面的不同实现。

创建测试

定义了页面API后,可以直接跳转到创建测试方法。在确认API可用于创建测试之后,再进行页面实现。这种设计模式使测试人员可以专注于应用程序的实际使用,而不必太早掉进细节的坑里。

创建了以下测试:

@ExtendWith(SeleniumExtension.class)
@DisplayName("Managing Todos")
class TodoMvcTests { private TodoMvc todoMvc; private final String buyTheMilk = "Buy the milk";
private final String cleanupTheRoom = "Clean up the room";
private final String readTheBook = "Read the book"; @BeforeEach
void beforeEach(ChromeDriver driver) {
this.todoMvc = null;
this.todoMvc.navigateTo();
} @Test
@DisplayName("Creates Todo with given name")
void createsTodo() { todoMvc.createTodo(buyTheMilk); assertAll(
() -> assertEquals(1, todoMvc.getTodosLeft()),
() -> assertTrue(todoMvc.todoExists(buyTheMilk))
);
} @Test
@DisplayName("Creates Todos all with the same name")
void createsTodosWithSameName() { todoMvc.createTodos(buyTheMilk, buyTheMilk, buyTheMilk); assertEquals(3, todoMvc.getTodosLeft()); todoMvc.showActive(); assertEquals(3, todoMvc.getTodoCount());
} @Test
@DisplayName("Edits inline double-clicked Todo")
void editsTodo() { todoMvc.createTodos(buyTheMilk, cleanupTheRoom); todoMvc.renameTodo(buyTheMilk, readTheBook); assertAll(
() -> assertFalse(todoMvc.todoExists(buyTheMilk)),
() -> assertTrue(todoMvc.todoExists(readTheBook)),
() -> assertTrue(todoMvc.todoExists(cleanupTheRoom))
);
} @Test
@DisplayName("Removes selected Todo")
void removesTodo() { todoMvc.createTodos(buyTheMilk, cleanupTheRoom, readTheBook); todoMvc.removeTodo(buyTheMilk); assertAll(
() -> assertFalse(todoMvc.todoExists(buyTheMilk)),
() -> assertTrue(todoMvc.todoExists(cleanupTheRoom)),
() -> assertTrue(todoMvc.todoExists(readTheBook))
);
} @Test
@DisplayName("Toggles selected Todo as completed")
void togglesTodoCompleted() {
todoMvc.createTodos(buyTheMilk, cleanupTheRoom, readTheBook); todoMvc.completeTodo(buyTheMilk);
assertEquals(2, todoMvc.getTodosLeft()); todoMvc.showCompleted();
assertEquals(1, todoMvc.getTodoCount()); todoMvc.showActive();
assertEquals(2, todoMvc.getTodoCount());
} @Test
@DisplayName("Toggles all Todos as completed")
void togglesAllTodosCompleted() {
todoMvc.createTodos(buyTheMilk, cleanupTheRoom, readTheBook); todoMvc.completeAllTodos();
assertEquals(0, todoMvc.getTodosLeft()); todoMvc.showCompleted();
assertEquals(3, todoMvc.getTodoCount()); todoMvc.showActive();
assertEquals(0, todoMvc.getTodoCount());
} @Test
@DisplayName("Clears all completed Todos")
void clearsCompletedTodos() {
todoMvc.createTodos(buyTheMilk, cleanupTheRoom);
todoMvc.completeAllTodos();
todoMvc.createTodo(readTheBook); todoMvc.clearCompleted();
assertEquals(1, todoMvc.getTodosLeft()); todoMvc.showCompleted();
assertEquals(0, todoMvc.getTodoCount()); todoMvc.showActive();
assertEquals(1, todoMvc.getTodoCount());
}
}

在上述测试类中,我们看到在每次测试之前,ChromeDriver均已@BeforeEach通过Selenium Jupiter扩展名(@ExtendWith(SeleniumExtension.class))初始化并注入到设置方法中。驱动程序对象将用于初始化页面对象。

页面对象模式很大程度上取决于项目的特征。你可能要经常使用接口,但这不是必需的。你可能要考虑在较低的抽象水平,其中API是暴露的更详细的方法,例如setTodoInput(String value)clickSubmitButton()

使用Selenium内置的PageFactory实现Page Object Pattern

我们已经有一个接口可以对TodoMVC页面的行为进行建模,并且我们有使用API​​的失败测试。下一步是实际实现页面对象。为此,我们将使用Selenium内置PageFactory类及其实用程序。

PageFactory类简化了页面对象模式的实现。该类提供了一种机制来初始化任何声明WebElementList<WebElement>带有@FindBy注释的字段的Page ObjectPageFactory中提供了支持Page Object模式实现的和其他注释。

下面的TodoMvcPage类实现了我们之前创建的接口。它声明了几个带有@FindBy注解的字段。它还声明一个构造函数,该构造WebDriver函数采用工厂使用的用于初始化字段的参数:

public class TodoMvcPage implements TodoMvc {

    private final WebDriver driver;

    private static final By byTodoEdit = By.cssSelector("input.edit");
private static final By byTodoRemove = By.cssSelector("button.destroy");
private static final By byTodoComplete = By.cssSelector("input.toggle"); @FindBy(className = "new-todo")
private WebElement newTodoInput; @FindBy(css = ".todo-count > strong")
private WebElement todoCount; @FindBy(css = ".todo-list li")
private List<WebElement> todos; @FindBy(className = "toggle-all")
private WebElement toggleAll; @FindBy(css = "a[href='#/active']")
private WebElement showActive; @FindBy(css = "a[href='#/completed']")
private WebElement showCompleted; @FindBy(className = "clear-completed")
private WebElement clearCompleted; public TodoMvcPage(WebDriver driver) {
this.driver = driver;
} @Override
public void navigateTo() {
driver.get("***");
} public void createTodo(String todoName) {
newTodoInput.sendKeys(todoName + Keys.ENTER);
} public void createTodos(String... todoNames) {
for (String todoName : todoNames) {
createTodo(todoName);
}
} public int getTodosLeft() {
return Integer.parseInt(todoCount.getText());
} public boolean todoExists(String todoName) {
return getTodos().stream().anyMatch(todoName::equals);
} public int getTodoCount() {
return todos.size();
} public List<String> getTodos() {
return todos
.stream()
.map(WebElement::getText)
.collect(Collectors.toList());
} public void renameTodo(String todoName, String newTodoName) {
WebElement todoToEdit = getTodoElementByName(todoName);
doubleClick(todoToEdit); WebElement todoEditInput = find(byTodoEdit, todoToEdit);
executeScript("arguments[0].value = ''", todoEditInput); todoEditInput.sendKeys(newTodoName + Keys.ENTER);
} public void removeTodo(String todoName) {
WebElement todoToRemove = getTodoElementByName(todoName);
moveToElement(todoToRemove);
click(byTodoRemove, todoToRemove);
} public void completeTodo(String todoName) {
WebElement todoToComplete = getTodoElementByName(todoName);
click(byTodoComplete, todoToComplete);
} public void completeAllTodos() {
toggleAll.click();
} public void showActive() {
showActive.click();
} public void showCompleted() {
showCompleted.click();
} public void clearCompleted() {
clearCompleted.click();
} private WebElement getTodoElementByName(String todoName) {
return todos
.stream()
.filter(el -> todoName.equals(el.getText()))
.findFirst()
.orElseThrow(() -> new RuntimeException("Todo with name " + todoName + " not found!"));
} private WebElement find(By by, SearchContext searchContext) {
return searchContext.findElement(by);
} private void click(By by, SearchContext searchContext) {
WebElement element = searchContext.findElement(by);
element.click();
} private void moveToElement(WebElement element) {
new Actions(driver).moveToElement(element).perform();
} private void doubleClick(WebElement element) {
new Actions(driver).doubleClick(element).perform();
} private void executeScript(String script, Object... arguments) {
((JavascriptExecutor) driver).executeScript(script, arguments);
}
}

@FindBy不是用于在Page Object中查找元素的唯一注释。也有@FindBys@FindAll

@FindBys

@FindBys批注用于标记Page Object上的字段,以指示查找应使用一系列@FindBy标签。在这个例子中,硒将搜索元件与class = "button"是内与元件id = "menu":

@FindBys({
@FindBy(id = "menu"),
@FindBy(className = "button")
})
private WebElement element;

@FindAll

@FindAll批注用于标记Page Object上的字段,以指示查找应使用一系列@FindBy标记。在此示例中,Selenium将搜索带有class = "button" 和的所有元素id = "menu"。不保证元素按文档顺序排列:

FindAll({
@FindBy(id = "menu"),
@FindBy(className = "button")
})
private List<WebElement> webElements;

PageFactory初始化Page对象

PageFactory提供了几种静态方法来初始化Page Objects。在我们的测试中,在beforeEach()方法中,我们需要初始化TodoMvcPage对象:

@BeforeEach
void beforeEach(ChromeDriver driver) {
this.todoMvc = PageFactory.initElements(driver, TodoMvcPage.class);
this.todoMvc.navigateTo();
}

PageFactory使用反射初始化对象,然后将其初始化所有WebElementList<WebElement>标有字段@FindBy注释。使用此方法要求Page Object具有单个参数构造函数接受WebDriver对象。

定位元素

那么元素何时定位?每次访问该字段都会进行查找。例如,当我们执行代码:new TodoInput.sendKeys(todoName + Keys.ENTER);in createTodo()方法时,实际执行的指令是:driver.findElement(By.className('new-todo')).sendKeys(todoName + Keys.ENTER)。不是在对象初始化期间而是在第一个元素查找期间引发未找到元素的潜在异常。Selenium使用代理模式来实现所描述的行为。

@CacheLookup

在某些情况下,每次访问带注释的字段时都不需要查找元素。在这种情况下,我们可以使用@CacheLookup注释。在示例中,输入字段在页面上没有更改,因此可以缓存查找结果:

@FindBy(className = "new-todo")
@CacheLookup
private WebElement newTodoInput;

运行测试

现在是执行测试的时候了。可以从IDE或使用终端来完成:

./gradlew clean test --tests *TodoMvcTests

通过所有测试,构建成功:

> Task :test

demos.selenium.todomvc.TodoMvcTests > editsTodo() PASSED

demos.selenium.todomvc.TodoMvcTests > togglesTodoCompleted() PASSED

demos.selenium.todomvc.TodoMvcTests > createsTodo() PASSED

demos.selenium.todomvc.TodoMvcTests > removesTodo() PASSED

demos.selenium.todomvc.TodoMvcTests > togglesAllTodosCompleted() PASSED

demos.selenium.todomvc.TodoMvcTests > createsTodosWithSameName() PASSED

demos.selenium.todomvc.TodoMvcTests > clearsCompletedTodos() PASSED

BUILD SUCCESSFUL in 27s
3 actionable tasks: 3 executed

微信公众号后台回复“测试网页”,获取测试网页下载地址。


  • 郑重声明:文章首发于公众号“FunTester”,禁止第三方(腾讯云除外)转载、发表。

技术类文章精选

非技术文章精选

JUnit 5和Selenium基础(二)的更多相关文章

  1. JUnit 5和Selenium基础(一)

    Gradle.JUnit 5和Jupiter Selenium Selenium是一组支持浏览器自动化的工具,主要用于Web应用程序测试.Selenium的组件之一是Selenium WebDrive ...

  2. JUnit 5和Selenium基础(三)

    在这一部分教程中,将介绍JUnit 5的其他功能,这些功能将通过并行运行测试,配置测试顺序和创建参数化测试来帮助减少测试的执行时间.还将介绍如何利用Selenium Jupiter功能,例如通过系统属 ...

  3. 深入理解基于selenium的二次开发

    对于做web端自动化测试的人来说,可能接触selenium比QTP还要多,但是我们在做基于selenium的二次开发的时候,经常会说到二次开发是为了易于维护,很多人可能不懂得维护的价值是什么,和到底要 ...

  4. selenium基础-图形验证码

    selenium基础-图形验证码 一.图形验证码作用 设计的初衷其实就是为了防自动化,防止一些人利用自动工具恶意攻击网站 二.图形验证码是由客户端生成还是由服务器端生成的? 图形验证码是由服务器端生成 ...

  5. selenium基础-跳过验证码

    selenium基础-跳过验证码 一.方法 设置万能验证码或者屏蔽验证码(最常用的方法) 使用验证码识别工具识别验证码 通过selenium操作cookies 直接使用配置文件的webdriver 二 ...

  6. Python+Selenium基础入门及实践

    Python+Selenium基础入门及实践 32018.08.29 11:21:52字数 3220阅读 23422 一.Selenium+Python环境搭建及配置 1.1 selenium 介绍 ...

  7. 「译」JUnit 5 系列:基础入门

    原文地址:http://blog.codefx.org/libraries/junit-5-basics/ 原文日期:25, Feb, 2016 译文首发:Linesh 的博客:JUnit 5 系列: ...

  8. Python全栈开发【基础二】

    Python全栈开发[基础二] 本节内容: Python 运算符(算术运算.比较运算.赋值运算.逻辑运算.成员运算) 基本数据类型(数字.布尔值.字符串.列表.元组.字典) 其他(编码,range,f ...

  9. Bootstrap <基础二十九>面板(Panels)

    Bootstrap 面板(Panels).面板组件用于把 DOM 组件插入到一个盒子中.创建一个基本的面板,只需要向 <div> 元素添加 class .panel 和 class .pa ...

随机推荐

  1. windows下如何安装Composer?

    Composer 不是一个包管理器,它仅仅是一个依赖管理工具.它涉及 "packages" 和 "libraries",但它在每个项目的基础上进行管理,在你项目 ...

  2. 一个基于 Slab 缓存的 scull: scullc

    是时候给个例子了. scullc 是一个简化的 scull 模块的版本, 它只实现空设备 -- 永久 的内存区. 不象 scull, 它使用 kmalloc, scullc 使用内存缓存. 量子的大小 ...

  3. Spring Boot 各Starter介绍

    原文链接:https://blog.csdn.net/u014430366/article/details/53648139 Spring-Boot-Starters 最通俗的理解- jar 包,引用 ...

  4. vue-learning:31 - component - 组件间通信的6种方法

    vue组件间通信的6种方法 父子组件通信 prop / $emit 嵌套组件 $attrs / $liteners 后代组件通信 provide / inject 组件实例引用 $root / $pa ...

  5. hdu 6852Path6(最短路+最小割)

    传送门 •题意 有n个城市,标号1-n 现花费最小的代价堵路 使得从1号城市到n号城市的路径边长 (注意只是变长不是最长) 堵一条路的代价是这条路的权值 •思路 在堵路以前,从1到n的最小路径当然是最 ...

  6. qt添加cef库嵌入web,linux 下Qt WebEngine 程序打包简单记录

    http://www.cnblogs.com/oloroso/p/6051631.html http://www.cnblogs.com/oloroso/p/6149000.html

  7. 怎么安装GUI

    python安装easygui的过程中,下载的是0.97.安装的时候提示setuptools模块不存在.然后又去安装setuptools等等, 真麻烦.也没有成功.后来又下载了0.96的.才成功.下面 ...

  8. ASP.Net MVC SignalR的应用

    ASP.Net MVC SignalR的应用 最近做的一个MVC项目有个模块是要使用即时通信实现弹幕效果.既要考虑通信的实时性也要考虑服务器性能和资源消耗,所幸项目对浏览器的版本没有要求.所以我最先想 ...

  9. Redis事务、持久化、发布订阅

    一.Redis事物 1. 概念 Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证: 事务是一个单独的隔离操作:事务中的所有命令都会序列化.按顺序地执行.事务在执行的过程中,不会被其他 ...

  10. 洛谷$P3647\ [APIO2014]$连珠线 换根$dp$

    正解:换根$dp$ 解题报告: 传送门! 谁能想到$9102$年了$gql$居然还没写过换根$dp$呢,,,$/kel$ 考虑固定了从哪个点开始之后,以这个点作为根,蓝线只可能是直上直下的,形如&qu ...