junit5_JUnit 5和Selenium –使用Selenium内置的`PageFactory`实现页面对象模式
junit5
Selenium是一组支持浏览器自动化的工具和库,主要用于Web应用程序测试。 Selenium的组件之一是Selenium WebDriver,它提供客户端库,JSON有线协议(与浏览器驱动程序进行通信的协议)和浏览器驱动程序。 Selenium WebDriver的主要优点之一是,它受所有主要编程语言的支持,并且可以在所有主要操作系统上运行。
在带有Selenium WebDriver的JUnit 5的这一部分中,我将通过Selenium的内置PageFactory支持类来实现Page Object模式的实现。 PageFactory
提供了一种机制,用于初始化任何声明了带@FindBy
批注的WebElement
或List<WebElement>
字段的Page Object。
关于本教程
您正在阅读带有Selenium WebDriver的JUnit 5的第二部分-教程。
本教程中的所有文章:
- 第1部分–从头开始设置项目–与JUnit 5和Jupiter Selenium结合使用
- 第2部分–使用Selenium内置的
PageFactory
实现页面对象模式
接下来的是:
- 第3部分–改进项目配置–并行执行测试,测试执行顺序,参数化测试,AssertJ等
本教程的源代码可以在Github上找到
介绍页面对象模式
我们将在以下位置为基于JavaScript的Todo应用程序创建测试: http : //todomvc.com/examples/vanillajs 。 该应用程序被创建为单页应用程序(SPA),并使用本地存储作为任务存储库。 可能要实现的方案包括添加和编辑待办事项,删除待办事项,将单个或多个待办事项标记为已完成。 该实现将使用“页面对象”模式完成。
页面对象模式的目标是从实际测试中抽象出应用程序页面和功能。 页面对象模式提高了代码在测试和固定装置之间的可重用性,但也使代码易于维护。
您可以在Martin Fowler的文章中了解有关此模式的更多信息: https : //martinfowler.com/bliki/PageObject.html
页面API aka页面对象
我们将从将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详细信息暴露给潜在的客户端(在我们的情况下,客户端=测试方法)。 实际上,它与Selenium WebDriver无关。 因此,从理论上讲,我们可以针对不同的设备(例如移动本机应用程序,桌面应用程序和Web应用程序)使用此页面的不同实现。
创建测试
定义了页面API后,我们可以直接跳转到创建测试方法。 在确认API可用于创建测试之后,我们将进行页面实现。 这种设计技术使您可以专注于应用程序的实际使用,而不必太早进入实现细节。
创建了以下测试:
@ExtendWith (SeleniumExtension. class )@DisplayName ( "Managing Todos" @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" ; @BeforeEachvoid 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" @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" @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" @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());}}
更多:如果您不熟悉JUnit 5,可以在我的博客上阅读此介绍: https : //blog.codeleak.pl/2017/10/junit-5-basics.html 。 本文还有另一种波兰语版本: https : //blog.qalabs.pl/junit/junit5-pierwsze-kroki/ 。
在上面的测试类中,我们看到在每次测试之前,ChromeDriver均已初始化,并通过Selenium Jupiter扩展名(因此@ExtendWith(SeleniumExtension.class)
)注入到设置方法( @BeforeEach
)中。 驱动程序对象将用于初始化页面对象。
页面对象建模技术不同,并且很大程度上取决于您正在处理的项目的特征。 您可能要使用接口,但这不是必需的。 您可能需要考虑在较低的抽象级别上进行建模,在该级别上,API公开了更详细的方法,例如setTodoInput(String value)
, clickSubmitButton()
。
使用Selenium内置的PageFactory实现Page Object Pattern
到现在为止,我们已经有一个接口可以对TodoMVC页面的行为进行建模,并且我们有使用API的失败测试。 下一步是实际实现页面对象。 为此,我们将使用Selenium内置的PageFactory
类及其实用程序。
PageFactory
类简化了Page Object模式的实现。 该类提供了一种机制,用于初始化任何声明了用@FindBy
注释注释的WebElement
或List<WebElement>
字段的Page Object。 可以在org.openqa.selenium.support
包中找到PageFactory
和支持页面对象模式实现的所有其他注释。
下面的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;}@Overridepublic void navigateTo() {driver.get( " http://todomvc.com/examples/vanillajs " );}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!" "Todo with name " " 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
标记。 在此示例中,Selenium将在id = "menu"
的元素内搜索class = "button"
的元素:
@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 Objects。 在我们的测试中,在beforeEach()
方法中,我们需要初始化TodoMvcPage
对象:
@BeforeEachvoid beforeEach(ChromeDriver driver) {this .todoMvc = PageFactory.initElements(driver, TodoMvcPage. class );this .todoMvc.navigateTo();}
PageFactory
使用反射初始化对象,然后初始化所有标有@FindBy
批注的WebElement
或List<WebElement>
字段( @FindBy
不进行任何查找,而是对字段进行代理)。 使用此方法要求Page Object具有接受WebDriver
对象的单个参数构造函数。
定位元素
那么元素何时定位? 每次访问该字段都会进行查找。 因此,例如,当我们执行代码时: newTodoInput.sendKeys(todoName + Keys.ENTER);
在createTodo()
方法中,实际执行的指令是: driver.findElement(By.className('new-todo')).sendKeys(todoName + Keys.ENTER)
。 我们可以预期,不是在对象初始化期间而是在第一次元素查找期间引发了未找到元素的潜在异常。
Selenium使用代理模式来实现所描述的行为。
@CacheLookup
在某些情况下,每次访问带注释的字段时都不需要查找元素。 在这种情况下,我们可以使用@CacheLookup
批注。 在我们的示例中,输入字段在页面上没有更改,因此可以缓存其查找:
@FindBy (className = "new-todo" )@CacheLookupprivate WebElement newTodoInput;
运行测试
现在是执行测试的时候了。 可以从IDE或使用终端来完成:
./gradlew clean test --tests *TodoMvcTests
通过所有测试,构建成功:
> Task :test pl.codeleak.demos.selenium.todomvc.TodoMvcTests > editsTodo() PASSEDpl.codeleak.demos.selenium.todomvc.TodoMvcTests > togglesTodoCompleted() PASSEDpl.codeleak.demos.selenium.todomvc.TodoMvcTests > createsTodo() PASSEDpl.codeleak.demos.selenium.todomvc.TodoMvcTests > removesTodo() PASSEDpl.codeleak.demos.selenium.todomvc.TodoMvcTests > togglesAllTodosCompleted() PASSEDpl.codeleak.demos.selenium.todomvc.TodoMvcTests > createsTodosWithSameName() PASSEDpl.codeleak.demos.selenium.todomvc.TodoMvcTests > clearsCompletedTodos() PASSEDBUILD SUCCESSFUL in 27s3 actionable tasks: 3 executed
下一步
在本教程的下一部分中,您将学习如何改善项目配置。 您将学习并行执行测试,测试执行顺序,参数化测试,AssertJ等。
翻译自: https://www.javacodegeeks.com/2019/10/using-pagefactory-implement-page-object-pattern.html
junit5
junit5_JUnit 5和Selenium –使用Selenium内置的`PageFactory`实现页面对象模式相关推荐
- idea内置junit5_JUnit 5和Selenium –使用Selenium内置的`PageFactory`实现页面对象模式
idea内置junit5 Selenium是一组支持浏览器自动化的工具和库,主要用于Web应用程序测试. Selenium的组件之一是Selenium WebDriver,它提供客户端库,JSON有线 ...
- JUnit 5和Selenium –使用Selenium内置的`PageFactory`实现页面对象模式
Selenium是一组支持浏览器自动化的工具和库,主要用于Web应用程序测试. Selenium的组件之一是Selenium WebDriver,它提供客户端库,JSON有线协议(与浏览器驱动程序进行 ...
- Druid 配置及内置监控,Web页面查看监控内容 【我改】
转: Druid 配置及内置监控,Web页面查看监控内容 1.配置Druid的内置监控 首先在Maven项目的pom.xml中引入包 1 2 3 4 5 <dependency> ...
- 实现:您必须使用微信内置浏览器访问本页面! 的功能
有些活动页面,只能通过手机,且在微信公众号里点击打开,不能用ie.火狐.谷歌等浏览器打开,请添加如下代码: <script type="text/javascript"> ...
- 以下哪个不是python的内置函数_以下哪个 Python 内置函数可以返回列表对象中元素个数。...
[多选题]假设 x=[0,1,2,3],执行哪些语句之后,x 的值为[0, 1, 2]. [多选题]以下哪些对象的分隔符为逗号. [单选题]已知列表 x=[0,1,2,1,4],那么执行语句 del ...
- Win10 权限问题:用于内置管理员账户的管理员批准模式
需求背景 为了安全考虑微酷将 Win10 系统电脑各盘符安全设置为只保留Administrators和SYSTEM两个用户组可完全访问,然后平时用电脑都是用内置管理员administrator来使用, ...
- 对象的内置属性和js的对象之父Object()
js中对象有constructor,valueOf(),toString()等内置属性和方法; 创建一个空对象的方法: var o = {}; 或者 var o= new Object(); o.co ...
- 《C++ Primer 第五版》(第3.5-3.6节) ——C++中的内置数组类型,string对象和C风格字符串
1.C++中的内置数组类型 C++中的数组是内置数据类型,但不是基础数据类型而是构造数据类型,是有限个相同数据.占据着连续物理存储空间的有序集合.而内置数据类型定义的下标运算符可以处理负值运算(只要在 ...
- Druid 配置及内置监控,Web页面查看监控内容
1.配置Druid的内置监控 首先在Maven项目的pom.xml中引入包 <dependency><groupId>com.alibaba</groupId>&l ...
最新文章
- Exchange 发送方式(Send As)和代表发送(Send on behalf)的区别
- 飞桨全新发布,核心框架首次完整公开解读
- 《数据结构》c语言版学习笔记——线性表的顺序存储结构
- 直观理解神经网络和梯度下降
- 3个常考的SQL数据分析题(含数据和代码)
- 邮政计算机网络,邮政计算机网络论文(共2018字).doc
- Eclipse设置server Locations及getServletContext().getRealPath获取到的工程目录路径
- linux安装运行redis
- webpack打包vue项目IE报错,“对象不支持“use”属性或方法”
- 管理感悟:软件公司不加班还搞什么软件
- Python分析王者峡谷中英雄信息
- linux如何卸载干净mysql
- 软件开发生命周期及文档
- Tiny6410 初体验
- 有个exe文件删不掉怎么办
- 电子学会青少年软件编程 Python编程等级考试三级真题解析(选择题)2021年3月
- 如何用计算机弹出斗地主的声音,电脑为什么只有斗地主的时候有声音?
- 原生JavaScript批量下载文件压缩包
- 【牛客练习赛13】 A B C D【康拓展开】 E【DP or 记忆化搜索】 F 【思维】
- python文本文件对比_Python-文件差异对比
热门文章
- Apache Shiro教程
- 浅谈Java内存泄漏问题
- 单链表——判断一个单链表中是否有环
- 安装Oracle 10g sys,system 密码忘记设置解决办法
- 【转】SQL存储结构(页)
- 请注意Tokyo Tyrant (ttserver)在大数据量下的不稳定
- vector机器人 WHERE TO USE VECTOR 在哪里使用 VECTOR
- golang 解析html
- python3 python2 字符串与hex互转区别
- linux 错误 kernel panic not syncing vfs unable to mount root fs on unknown-block 0 0