JUnit 5和Selenium –使用Selenium内置的`PageFactory`实现页面对象模式

Selenium是一组支持浏览器自动化的工具和库,主要用于Web应用程序测试。 Selenium的组件之一是Selenium WebDriver,它提供客户端库,JSON有线协议(与浏览器驱动程序进行通信的协议)和浏览器驱动程序。 Selenium WebDriver的主要优点之一是,它受所有主要编程语言的支持,并且可以在所有主要操作系统上运行。

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

关于本教程

您正在阅读带有Selenium WebDriverJUnit 5的第二部分-教程

本教程中的所有文章:

  • 第1部分– 从头开始设置项目–使用JUnit 5和Jupiter Selenium进行Gradle
  • 第2部分– 使用Selenium内置的PageFactory实现Page Object Pattern

接下来的是:

  • 第3部分–改进项目配置–并行执行测试,测试执行顺序,参数化测试,AssertJ等

该教程的源代码可以在Github上找到

介绍页面对象模式

我们将在以下位置为基于JavaScript的Todo应用程序创建测试: http : //todomvc.com/examples/vanillajs 。 该应用程序被创建为单页应用程序(SPA),并使用本地存储作为任务存储库。 可能实现的方案包括添加和编辑待办事项,删除待办事项,将单个或多个待办事项标记为已完成。 该实现将使用Page Object模式完成。

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

您可以在Martin Fowler的文章中了解有关此模式的更多信息: https : //martinfowler.com/bliki/PageObject.html

页面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详细信息公开给潜在的客户端(在我们的情况下,客户端=测试方法)。 实际上,它与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" ; @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" @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批注注释的WebElementList<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; } @Override public 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对象:

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

PageFactory使用反射初始化对象,然后初始化所有标有@FindBy批注的WebElementList<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" )  @CacheLookup  private WebElement newTodoInput; 

运行测试

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

 ./gradlew clean test --tests *TodoMvcTests 

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

 > Task :test  pl.codeleak.demos.selenium.todomvc.TodoMvcTests > editsTodo() PASSED  pl.codeleak.demos.selenium.todomvc.TodoMvcTests > togglesTodoCompleted() PASSED  pl.codeleak.demos.selenium.todomvc.TodoMvcTests > createsTodo() PASSED  pl.codeleak.demos.selenium.todomvc.TodoMvcTests > removesTodo() PASSED  pl.codeleak.demos.selenium.todomvc.TodoMvcTests > togglesAllTodosCompleted() PASSED  pl.codeleak.demos.selenium.todomvc.TodoMvcTests > createsTodosWithSameName() PASSED  pl.codeleak.demos.selenium.todomvc.TodoMvcTests > clearsCompletedTodos() PASSED  BUILD SUCCESSFUL in 27s  3 actionable tasks: 3 executed 

下一步

在本教程的下一部分中,您将学习如何改善项目配置。 您将学习并行执行测试,测试执行顺序,参数化测试,AssertJ等。

翻译自: https://www.javacodegeeks.com/2019/10/using-pagefactory-implement-page-object-pattern.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/340502.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python 配置文件中密码不能是明文_配置文件中明文密码改为密文密码的方法

我们用java链接数据库&#xff0c;不管是web项目还是小程序&#xff0c;都需要把数据库密码写在配置文件中(当然你要写死在程序里也没有办法)&#xff0c;或者数据库中&#xff0c;通常源代码漏洞扫描都会告诉你不能有明文密码&#xff0c;那么有什么办法可以变为密文呢&#x…

java.lang 源码剖析_java.lang.Void类源码解析

在一次源码查看ThreadGroup的时候&#xff0c;看到一段代码&#xff0c;为以下&#xff1a;/** throws NullPointerException if the parent argument is {code null}* throws SecurityException if the current thread cannot create a* thread in the specified thread group…

vant按需引入没样式_vue vant-ui样式出不来的问题

第一步&#xff1a;安装vantnpm i vant -S // 或 yarn add vant第二步&#xff1a;配置按需引入// 在 babel.config.js 中配置 module.exports {plugins: [[import, {libraryName: vant,libraryDirectory: es,style: true}, vant]] };第三步&#xff1a;配置vue.config.js&…

javaserver_如何在JavaServer Pages中使用Salesforce REST API

javaserver摘要&#xff1a;本教程提供了一个JSP示例以及如何将其与Salesforce REST API集成。 我们将逐步完成创建外部客户端以使用Force.com &#xff08;同时使用HTTP&#xff08;S&#xff09;和JSON&#xff09;管理数据的分步过程。 在此示例中&#xff0c;我将Mac OS X…

jmeter线程数并发数区别_如何确定Kafka的分区数、key和consumer线程数、以及不消费问题解决...

在Kafak中国社区的qq群中&#xff0c;这个问题被提及的比例是相当高的&#xff0c;这也是Kafka用户最常碰到的问题之一。本文结合Kafka源码试图对该问题相关的因素进行探讨。希望对大家有所帮助。怎么确定分区数&#xff1f;“我应该选择几个分区&#xff1f;”——如果你在Kaf…

简述java规范要注意哪些问题_JAVA学习:JAVA基础面试题(经典)

第一阶段题库基础知识部分&#xff1a;1. JDK是什么&#xff1f;JRE是什么&#xff1f;a) 答&#xff1a;JDK&#xff1a;java开发工具包。JRE&#xff1a;java运行时环境。2. 什么是java的平台无关性&#xff1f;a) 答&#xff1a;Java源文件被编译成字节码的形式&#xff0c;…

我可以/应该在事务上下文中使用并行流吗?

介绍 长话短说&#xff0c;您不应在并行流中使用事务。 这是因为并行流中的每个线程都有其自己的名称&#xff0c;因此它确实参与了事务。 Streams API旨在在某些准则下正常工作。 实际上&#xff0c;为了受益于并行性&#xff0c;不允许每个操作更改共享对象的状态&#xff0…

插入排序java_「Java」各类排序算法

排序大的分类可以分为两种&#xff1a;内排序和外排序。在排序过程中&#xff0c;全部记录存放在内存&#xff0c;则称为内排序&#xff0c;如果排序过程中需要使用外存&#xff0c;则称为外排序。下面讲的排序都是属于内排序。内排序有可以分为以下几类&#xff1a;(1) 插入排…

java object... arguments_Java面试之基础题---对象Object

参数传递&#xff1a;Java支持两种数据类型&#xff1a;基本数据类型和引用数据类型。原始数据类型是一个简单的数据结构&#xff0c;它只有一个与之相关的值。引用数据类型是一个复杂的数据结构&#xff0c;它表示一个对象。原始数据类型的变量将该值直接存储在其存储器地址处…

华为光伏usb适配器_华为系列原装充电器拆解第三弹:比亚迪版华为10W充电器

在对华为18W充电器的比亚迪版和赛尔康版进行拆解之后&#xff0c;充电头网今天继续为大家带来华为10W充电器的比亚迪版和达宏版的拆解。这两种10W规格的华为充电器外观延续了华为原装充电器的风格&#xff0c;而且型号也是一样的。那么&#xff0c;我们先一起来看看比亚迪版华为…

JMetro版本11.5.10和8.5.10发布

在这里&#xff0c;我们再次使用JMetro的另一个版本。 此版本中的新增功能&#xff1a; 工具栏内控件的新样式 新的可编辑组合框样式 对其他样式的一些调整 一些修复 继续阅读以获取详细信息。 可编辑的ComboBox新样式 JMetro早期版本的可编辑ComboBox看起来非常糟糕&am…

1s后跳转 android_优雅保活方案,原来Android还可以这样保活

作者&#xff1a;NanBox保活现状我们知道&#xff0c;Android 系统会存在杀后台进程的情况&#xff0c;并且随着系统版本的更新&#xff0c;杀进程的力度还有越来越大的趋势。系统这种做法本身出发点是好的&#xff0c;因为可以节省内存&#xff0c;降低功耗&#xff0c;也避免…

java mongo api_MONGODB的javaAPI简单应用

1 建立连接要建立MongoDB的连接&#xff0c;你只要指定要连接到的数据库就可以。这个数据库不一定存在&#xff0c;如果不存在&#xff0c;MongoDB会先为你建立这个库。同时&#xff0c;在连接时你也可以具体指定要连接到的网络地址和端口。下面的是连接本机数据库的一些例子&a…

wordpress致命错误怎么解决_pppoe错误是什么意思 pppoe错误怎么解决

最近有网友反应无线路由器上设置PPPoE拨号上网后&#xff0c;发现PPPoE连接不上&#xff0c;显示pppoe错误是什么意思呢?pppoe错误怎么解决呢?接下来详细为大家介绍&#xff1a;pppoe错误怎么解决无线路由器设置PPPoE拨号后&#xff0c;PPPoE拨号连接不上&#xff0c;不能够上…

java ssm 多租户_(十一)java B2B2C 源码 多级分销springmvc mybatis多租户电子商城系统- SSO单点登录之OAuth2.0登录流程(2)...

上一篇是站在巨人的肩膀上去研究OAuth2.0&#xff0c;也是为了快速帮助大家认识OAuth2.0&#xff0c;闲话少说&#xff0c;我根据框架中OAuth2.0的使用总结&#xff0c;画了一个简单的流程图(根据用户名密码实现OAuth2.0的登录认证)&#xff1a;上面的图很清楚的描述了当前登录…

hibernate 序列_Hibernate身份,序列和表(序列)生成器

hibernate 序列介绍 在上一篇文章中&#xff0c;我谈到了不同的数据库标识符策略。 这篇文章将比较最常见的替代主要关键策略&#xff1a; 身份 序列 表&#xff08;序列&#xff09; 身份 IDENTITY类型&#xff08;包括在SQL&#xff1a;2003标准中&#xff09;受以下支持…

java中date加1s_是否有一个java库将描述时间度量(例如“1d 1m 1s”)的字符串转换为毫秒?...

解析器不是太复杂&#xff1a;public static long parse(String input) {long result 0;String number "";for (int i 0; i < input.length(); i) {char c input.charAt(i);if (Character.isDigit(c)) {number c;} else if (Character.isLetter(c) &&…

几何画板200个经典课件_项目制学科联动 | 金芬娥首席工作室:灵动“画板”,研修创新,协同进步...

西湖区成立115个“项目制首席教师工作室”&#xff0c;建立中小学、幼儿园学科联动机制&#xff0c;以专业发展为目标&#xff0c;以教育问题为导向&#xff0c;整合发挥学科教研员、学科带头人和名师工作室领衔人的智力资源&#xff0c;助推教师的专业成长及区域的学科建设。西…

通过这些简单的步骤从头开始学习Java

Java是用于软件开发的最流行的编程语言之一。 无论您的最终目标或技能水平如何&#xff0c;学习和掌握Java都将为您作为开发人员打开大门。 今天&#xff0c;我们将讨论一些原因&#xff0c;我们认为您应该开始学习Java&#xff0c;然后提供有关入门的深入路线图。 为什么要学…

vs 服务容器中已存在服务_敏捷基础设施和公共基础服务

敏捷基础设施和公共基础服务敏捷基础设施和公共基础服务是微服务架构的有力支撑&#xff1b;能够简化业务开发&#xff0c;提升架构能力的基线。Cloud Native的基石是微服务架构、敏捷基础设施和公共基础服务。敏捷基础设施 - 通过容器封装环境&#xff0c;开发人员可以直接将所…