作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Dejan是一名资深QA工程师,在web和移动测试方面拥有丰富的经验. 他还广泛从事Android应用程序开发.
14
编者注:本文由我们的编辑团队于2022年11月18日更新. 它已被修改,以包括最近的来源,并与我们目前的编辑标准保持一致.
对于任何敏捷软件开发团队来说,编写自动化测试不仅仅是一件奢侈的事情. 它是在软件开发周期的早期阶段快速发现bug的必要工具. 当有一个新特性仍处于开发阶段时, 开发人员可以运行自动化测试,并查看这些更改如何影响系统的其他部分. 本文将解释如何使用测试自动化来加速这个过程 Selenium, using the Page Object model.
Through test automation,就有可能降低bug修复的成本并提高整体质量 software quality assurance (QA) process. 通过适当的测试,开发人员可以在QA之前找到并解决bug. Test automation further helps us to automate test cases and features that are constantly regressing. 这样,QA工程师就有更多的时间来测试应用程序的其他部分. 此外,这有助于确保产品版本中的产品质量. As a result, 我们得到了更稳定的产品, and a QA process that is more efficient.
尽管对于开发人员和工程师来说,编写自动化测试似乎是一项简单的任务, 仍然有可能以实现糟糕的测试和糟糕的代码可维护性而告终. 在任何敏捷开发项目中,当涉及到测试时,尝试不断地交付变更或特性可能会被证明是昂贵的. 改变网页上20个测试所依赖的一个元素将需要通过这20个测试例程,并更新每个测试例程以适应这种变化. 这是非常耗时的,并且阻碍了开发人员尽早实现自动化测试.
但是,如果我们可以只在一个地方进行更改,并让每个相关的测试例程都使用它呢? 我们将研究一下Selenium中的自动化测试, 以及我们如何利用Page Object模型最佳实践来编写可维护和可重用的测试例程.
Page Object model is an object design pattern in Selenium, where webpages are represented as classes, 页面上的各种元素被定义为类上的变量. 所有可能的用户交互都可以作为类的方法来实现:
clickLoginButton();
setCredentials(user_name,user_password);
因为类中命名良好的方法易于阅读, 这是一种实现测试例程的优雅方式,既可读又易于维护或在将来更新. For example:
In order to support Page Object model, we use Page Factory. Selenium中的页面工厂是页面对象的扩展,可以以各种方式使用. 在这种情况下,我们将使用页面工厂来初始化在网页类或页面对象中定义的web元素.
包含web元素的网页类或页面对象需要在使用web元素变量之前使用页面工厂进行初始化. This can be done through the use of initElements
function on Page Factory:
LoginPage page = new LoginPage(driver);
PageFactory.initElements(driver, page);
Or, even simpler:
LoginPage page = PageFactory.intElements(driver,LoginPage.class)
Or, inside the webpage class constructor:
public LoginPage(WebDriver driver) {
this.driver = driver;
PageFactory.initElements(driver, this);
}
Page Factory will initialize every WebElement
变量,并根据配置的“定位器”引用实际网页上的相应元素.” This is done through the use of @FindBy
annotations. With this annotation, 我们可以定义一个查找元素的策略, 连同识别它的必要资料:
@FindBy(how=How.NAME, using="username")
private WebElement user_name;
Every time a method is called on this WebElement
变量,驱动程序将首先在当前页面上找到它,然后模拟交互. In case we are working with a simple page, 我们知道每次在页面上查找元素时都会找到它, 我们也知道,我们最终会离开这一页,不再返回. 在这里,我们可以通过使用另一个简单的注释来缓存查找的字段:
@FindBy(how=How.NAME, using="username")
@CacheLookup
private WebElement user_name;
This entire definition of the WebElement
变量可以用更简洁的形式替换:
@FindBy(name="username")
private WebElement user_name;
The @FindBy
注释支持一些其他策略,这些策略使事情变得更简单,包括 id
, name
, className
, css
, tagName
, linkText
, partialLinkText
, and xpath
.
@FindBy(id="username")
private WebElement user_name;
@FindBy(name="passsword")
private WebElement user_password;
@FindBy(className="h3")
private WebElement label;
@FindBy(css=”#content”)
private WebElement text;
Once initialized, 然后可以使用这些WebElement变量与页面上相应的元素进行交互. 例如,我们可以将给定的击键序列发送到页面上的密码字段:
user_password.sendKeys(password);
This is equivalent to the following:
driver.findElement(By.name(“user_password”)).sendKeys(password);
Moving on, 您经常会遇到需要在页面上查找元素列表的情况, and that is when @FindBys
comes in handy:
@FindBys (@FindBy (css = " div[类=“yt-lockup-tile yt-lockup-video“]”)))
private List videoElements;
The above code will find all the div
elements having two class names, yt-lockup-tile
and yt-lockup-video
. 我们可以用下面的代码来简化它:
@FindBy(how=How.CSS,使用= " div[类= ' yt-lockup-tile yt-lockup-video“]”)
private List videoElements;
Additionally, you can use @FindAll
with multiple @FindBy
用于查找与任何给定定位符匹配的元素的注释:
@FindAll({@FindBy(how=How.ID, using=”username”),
@FindBy(className=”username-field”)})
private WebElement user_name;
现在我们可以将网页表示为Java类并使用页面工厂进行初始化 WebElement
variables easily, 让我们看看如何在Selenium中使用页面对象模式和页面工厂编写简单的测试.
For our Selenium Page Object model tutorial, 让我们自动化Toptal的开发人员注册过程, which includes the following steps:
Visit antennas.dzpages.com
Click on the Apply As A Developer button
Check if the portal page is opened
Click on the Join Toptal button
Fill out the form
Submit the form by clicking on the Join Toptal button
要执行此自动化,我们必须设置Java项目.
Download and install Java JDK and InteliJ Idea, and then we can proceed:
Create a new Maven project
Link Project SDK
to your JDK, e.g.: on Windows C:\Program Files\Java\jdkxxx
Setup groupId (
) and artifactId (
)
Add dependencies for Selenium and JUnit Maven in your project POM file; make sure to update selenium.version
and junit.version
JUnit Maven和Selenium的最新版本号:
junit
junit
${junit.version}
test
org.seleniumhq.selenium
selenium-firefox-driver
${selenium.version}
org.seleniumhq.selenium
selenium-support
${selenium.version}
org.seleniumhq.selenium
selenium-java
${selenium.version}
此时,如果启用了自动构建,则依赖项应该开始自动下载. If not, just activate Plugins > install > install:install 在IntelliJ Idea IDE右侧的Maven项目面板下.
一旦项目启动,我们就可以开始创建我们的测试包 src/test/java
. Name the package com.toptal
, and create two more packages under it: com.toptal.webpages
and com.toptal.tests
.
我们将保持我们的页面对象/页面工厂类 com.toptal.webpages
and the test routines under com.toptal.tests
.
We will have three Page Object classes:
Class | Description |
---|---|
HomePage | Represents Toptal's homepage, antennas.dzpages.com |
DeveloperPortalPage | Represents Toptal's developer portal page |
DeveloperApplyPage | Represents Toptal's developer application form |
现在,我们准备创建Page Object项.
我们需要实现的第一个对象是Toptal的主页(www.toptal).dzpages.com). Create a class under com.toptal.webpages
and name it HomePage
.
package com.toptal.webpages;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.How;
import org.openqa.selenium.support.PageFactory;
public class HomePage {
private WebDriver driver;
//Page URL
private static String PAGE_URL="http://antennas.dzpages.com";
//Locators
//Apply as Developer Button
@FindBy(how = How.LINK_TEXT, using = "APPLY AS A DEVELOPER")
private WebElement developerApplyButton;
//Constructor
public HomePage(WebDriver driver){
this.driver=driver;
driver.get(PAGE_URL);
//Initialise Elements
PageFactory.initElements(driver, this);
}
public void clickOnDeveloperApplyButton(){
developerApplyButton.click();
}
}
在Toptal的主页上,我们对一个元素特别感兴趣,那就是 Apply as a Developer button. 我们可以通过匹配文本找到这个元素,这就是我们上面所做的. While modeling webpages as Page Object classes, 查找和识别元素通常会变成一件苦差事. With Firefox Developer Tools or Chrome DevTools, this task can be made easier. 右键单击页面上的任何元素,可以激活 Inspect Element 选项,以查找有关元素的详细信息.
By copying the xpath
我们可以在Page Object中为它创建一个WebElement字段,如下所示:
@FindBy(xpath = "/html/body/div[1]/div/div/header/div/h1")
WebElement heading;
Or, to keep things simple, we can use the tag name “h1” here, 只要它唯一地标识我们感兴趣的元素:
@FindBy(tagName = "h1")
WebElement heading;
Next, 我们需要一个代表开发人员门户页面的Page Object, the one that we can reach by clicking on the Apply As A Developer button.
On this page, we have two elements of interest. 要确定页面是否已加载,我们需要验证标题是否存在. And we also want a WebElement
field for the Join Toptal button.
package com.toptal.webpages;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
public class DeveloperPortalPage {
private WebDriver driver;
@FindBy(xpath = "/html/body/div[1]/div/div/header/div/h1")
private WebElement heading;
@FindBy(linkText = "JOIN TOPTAL")
private WebElement joinToptalButton;
//Constructor
public DeveloperPortalPage (WebDriver driver){
this.driver=driver;
//Initialise Elements
PageFactory.initElements(driver, this);
}
//We will use this boolean for assertion. To check if page is opened
public boolean isPageOpened(){
return heading.getText().toString().contains("Developer portal");
}
public void clikOnJoin(){
joinToptalButton.click();
}
}
用于此项目的第三个也是最后一个页面对象, 我们定义一个对象来表示包含开发人员应用程序表单的页面. 由于这里必须处理多个表单字段,所以我们定义一个 WebElement
variable for every form field. We find each field by its id
, 我们为每个字段定义了特殊的setter方法来模拟对应字段的击键.
package com.toptal.webpages;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
public class DeveloperApplyPage {
private WebDriver driver;
@FindBy(tagName = "h1")
WebElement heading;
@FindBy(id="developer_email")
WebElement developer_email;
@FindBy(id = "developer_password")
WebElement developer_password;
@FindBy(id = "developer_password_confirmation")
WebElement developer_password_confirmation;
@FindBy(id = "developer_full_name")
WebElement developer_full_name;
@FindBy(id = "developer_skype")
WebElement developer_skype;
@FindBy(id ="save_new_developer")
WebElement join_toptal_button;
//Constructor
public DeveloperApplyPage(WebDriver driver){
this.driver=driver;
//Initialise Elements
PageFactory.initElements(driver, this);
}
public void setDeveloper_email(String email){
developer_email.clear();
developer_email.sendKeys(email);
}
公共无效setDeveloper_password(字符串密码){
developer_password.clear();
developer_password.sendKeys(password);
}
setDeveloper_password_confirmation(字符串password_confirmation){
developer_password_confirmation.clear();
developer_password_confirmation.sendKeys(password_confirmation);
}
public void setDeveloper_full_name (String fullname){
developer_full_name.clear();
developer_full_name.sendKeys(fullname);
}
public void setDeveloper_skype (String skype){
developer_skype.clear();
developer_skype.sendKeys(skype);
}
public void clickOnJoin(){
join_toptal_button.click();
}
public boolean isPageOpened(){
//Assertion
return heading.getText().toString().包含(“申请以开发者身份加入我们的网络”);
}
}
用页面对象类表示我们的页面, and user interactions as their methods, 现在,我们可以将测试例程编写为一系列简单的方法调用和断言.
package com.toptal.tests;
import com.toptal.webpages.DeveloperApplyPage;
import com.toptal.webpages.DeveloperPortalPage;
import com.toptal.webpages.HomePage;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import java.net.URL;
import java.util.concurrent.TimeUnit;
public class ApplyAsDeveloperTest {
WebDriver driver;
@Before
public void setup(){
//use FF Driver
driver = new FirefoxDriver();
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
}
@Test
public void applyAsDeveloper() {
//Create object of HomePage Class
HomePage home = new HomePage(driver);
home.clickOnDeveloperApplyButton();
//Create object of DeveloperPortalPage
DeveloperPortalPage = new DeveloperPortalPage(driver);
//Check if page is opened
Assert.assertTrue(devportal.isPageOpened());
//Click on Join Toptal
devportal.clikOnJoin();
//Create object of DeveloperApplyPage
DeveloperApplyPage applyPage =new DeveloperApplyPage(driver);
//Check if page is opened
Assert.assertTrue(applyPage.isPageOpened());
//Fill up data
applyPage.setDeveloper_email("dejan@dzpages.com");
applyPage.setDeveloper_full_name(“Dejan Zivanovic自动化测试”);
applyPage.setDeveloper_password("password123");
applyPage.setDeveloper_password_confirmation(“password123”);
applyPage.setDeveloper_skype("automated_test_skype");
//Click on join
//applyPage.clickOnJoin();
}
@After
public void close(){
driver.close();
}
}
此时,你的项目结构应该是这样的:
If you want to run the test, select ApplyAsDeveloperTest from the tree, right-click on it, and select Run ‘ApplyAsDeveloperTest’.
测试运行后,您可以在IDE的左下角看到测试结果:
Selenium中的页面对象和页面工厂使建模和自动测试网页变得容易, and make the lives of both developers and QA engineers much simpler. When done right, 这些Page Object类可以在整个测试套件中重用,并且可以在早期为项目实现自动化的Selenium测试, without compromising agile development. 通过抽象页面对象模型中的用户交互,并保持测试例程轻巧和简单, 您可以毫不费力地使您的测试套件适应任何需求变化.
我希望我已经设法向您展示了如何编写易于维护的漂亮而干净的测试代码. 记住我最喜欢的QA格言:“三思而后行,编码一次!”
页面对象模型是Selenium中的一种对象设计模式. Webpages are represented as classes, 页面上的元素被定义为类上的变量, 因此,用户交互可以作为类上的方法实现.
Selenium is designed to automate web browsers, 从而使软件工程师能够大大加快和自动化测试. While test automation is its primary use, Selenium还可以用于自动化某些重复操作, such as basic administrative tasks.
Selenium测试是使用Selenium软件工具来促进测试自动化的过程. In most cases, 软件工程师选择一到两个Selenium工具来完成这项任务, 但是可以使用其他工具来满足不同的需求.
Page Object model is a design pattern. Page Factory通过引入更高级的特性来扩展Page Object模型的功能. 它允许用户使用注解来初始化Page Object模型中的特定元素.
Located in Belgrade, Serbia
Member since September 3, 2014
Dejan是一名资深QA工程师,在web和移动测试方面拥有丰富的经验. 他还广泛从事Android应用程序开发.
14
World-class articles, delivered weekly.
World-class articles, delivered weekly.
Join the Toptal® community.