Selenium——页面对象(Page Object)模式浅析
问题的引出:
我们前面写过的包括元素定位等问题,如果测试的页面ID或者相关定位的文本发生改变,我们也得修改代码,能不能尽量减少代码的修改程度?针对这种问题,引出了这篇内容。
页面对象模式简介
- 使用面向对象的设计模式,页面对象模式将测试代码和被测试页面的页面元素及其操作方法进行分离,以此降低页面元素变化对测试代码的影响。
- 每个测试页面都会被单独定义一个类(也不绝对),类中会定位所有需要参与测试的页面元素对象,并且定义操作每一个页面元素对象的方法。
- Page Object设计模式的特点(优点):
(1)减少代码的重复
(2)提高测试用例的可读性
(3)提高测试用例的可维护性
- 做法分析:
(1)以页面为单位独立建模
(2)隐藏实现细节
(3)本质是面向接口编程
- Page Object五大设计原则:
(1)抽象每一个页面
(2)页面中元素不暴露,仅暴露操作元素的方法
(3)页面不应该有繁琐的继承关系
(4)页面中不是所有元素都需要涉及到,核心业务元素做建模使用
(5)把页面划分功能模块,在Page中实现这些功能方法
Page Object的一个例子
Page Object三种实现
以get***封装页面对象
在此举个例子:
- core包:设置一个BaseTset类,用于初始化浏览器、关闭浏览器操作
- pages包:设置一个AdminLoginPage1类,第一用来定位元素,第二实现相应的页面操作方法(传递参数等)。
- test包:设置一个AdminLoginTest1类,用来写测试方法。
整体效果:
当用户名密码为admin,admin时,可以正常登录(通过看是否有“退出”链接)
当用户名密码是admin, 11212时,密码错误,网页源代码中含有“错误”两个字。
测试用例结果:两个测试方法均正确。
package com.xhx.core;
import java.time.Duration;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
public class BaseTest {
public WebDriver driver;
@BeforeClass
public void initBrowser() {
System.out.println("初始化浏览器");
System.setProperty("webdriver.gecko.driver", "D:\\test\\Selenium\\geckodriver30.exe");
System.setProperty("webdriver.firefox.bin", "D:\\firefox\\firefox.exe");
driver = new FirefoxDriver();
driver.manage().window().maximize();
// 隐式等待
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));
}
@AfterClass
public void quitBrowser() {
driver.quit();
}
}
package com.xhx.pages;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
public class AdminLoginPage1 {
public WebDriver driver;
public AdminLoginPage1(WebDriver driver) {
this.driver = driver;
}
// 定位元素
private WebElement get_username() {
return this.driver.findElement(By.name("username"));
}
private WebElement get_password() {
return this.driver.findElement(By.name("password"));
}
private WebElement get_submit() {
return this.driver.findElement(By.xpath("//input[@type='submit']"));
}
// 实现对应的操作方法
public void login(String name, String password) {
this.driver.get("http://localhost:8032/mymovie/admin.php/Login/index.html");
this.get_username().sendKeys(name);
this.get_password().sendKeys(password);
this.get_submit().click();
}
}
package com.xhx.test;
import static org.testng.Assert.assertTrue;
import org.openqa.selenium.By;
import org.testng.annotations.Test;
import com.xhx.core.BaseTest;
import com.xhx.pages.AdminLoginPage1;
public class AdminLoginTest1 extends BaseTest {
@Test
public void testLoginSuccess() {
AdminLoginPage1 loginPage = new AdminLoginPage1(this.driver);
loginPage.login("admin", "admin");
assertTrue(this.driver.findElement(By.linkText("退出")).isDisplayed());
}
@Test
public void testLoginFail() {
AdminLoginPage1 loginPage = new AdminLoginPage1(this.driver);
loginPage.login("admin", "11212");
assertTrue(this.driver.getPageSource().contains("错误"));
}
}
以@FindBy封装页面对象
设置的三个类含义及效果如与上例一致。注意此代码中的细节:
细节
- 在AdminLoginPage2类中的构造方法,不单单是浏览器的赋值了,还得有
PageFactory.initElements(driver, this);
这句话。(初始化Web元素) - 在AdminLoginTest2中,与上例代码不同的是,我们创建了一个成员变量,两个测试方法用这一个就够了。这解决了冗余的问题,但也引发了两个问题:初始化浏览器(可在类中加一个@BeforeMethod,方法中实现实例化); 两个test方法,会按ascii码的顺序执行,那么登陆成功后,在没有退出的情况下,不会有下一个用例登录的按钮。得在成功登录的用理方法中加上退出的语句。
无意间发现的一个发现。。:当子类、父类中都有AfterClass注解时,会先执行父类的再执行子类的。
package com.xhx.core;
import java.time.Duration;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
public class BaseTest {
public WebDriver driver;
@BeforeClass
public void initBrowser() {
System.out.println("初始化浏览器");
System.setProperty("webdriver.gecko.driver", "D:\\test\\Selenium\\geckodriver30.exe");
System.setProperty("webdriver.firefox.bin", "D:\\firefox\\firefox.exe");
driver = new FirefoxDriver();
driver.manage().window().maximize();
// 隐式等待
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));
System.out.println("父类的");
}
@AfterClass
public void quitBrowser() {
driver.quit();
}
}
package com.xhx.pages;
import java.util.List;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindAll;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.FindBys;
import org.openqa.selenium.support.PageFactory;
public class AdminLoginPage2 {
// @FindAll({@FindBy(name="username"),
// @FindBy(id="username")}) //找到所有满足条件的元素放到一个列表里
// private List<WebElement> input;
//
// @FindBys(value= {@FindBy(name=""), @FindBy(id="")})
// private WebElement u_nameElement; //找到符合条件的第一个元素
WebDriver driver;
public AdminLoginPage2(WebDriver driver) {
this.driver = driver;
PageFactory.initElements(driver, this);
}
@FindBy(name = "username")
private WebElement txt_username;
@FindBy(name = "password")
private WebElement txt_password;
@FindBy(xpath = "//input[@type='submit']")
private WebElement btn_submit;
// 实现对应的操作方法
public void login(String name, String password) {
this.driver.get("http://localhost:8032/mymovie/admin.php/Login/index.html");
this.txt_username.sendKeys(name);
this.txt_password.sendKeys(password);
this.btn_submit.click();
}
}
package com.xhx.test;
import static org.testng.Assert.assertTrue;
import org.openqa.selenium.By;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.xhx.core.BaseTest;
import com.xhx.pages.AdminLoginPage1;
import com.xhx.pages.AdminLoginPage2;
public class AdminLoginTest2 extends BaseTest {
AdminLoginPage2 loginPage;
@BeforeMethod
public void init() {
loginPage = new AdminLoginPage2(this.driver);
System.out.println("子类的");
}
@Test
public void test1() {
loginPage.login("admin", "admin");
assertTrue(this.driver.findElement(By.linkText("退出")).isDisplayed());
this.driver.findElement(By.linkText("退出")).click();
}
@Test
public void test2() {
loginPage.login("admin", "11212");
assertTrue(this.driver.getPageSource().contains("错误"));
}
}
页面类继承LoadableComponent<>
继承LoadableComponent类可以在页面加载的时候判断是否加载了正确的页面
,只需要重写load() 和isLoaded()两个方法。此方式有助于让页面对象的页面访问操作更加健壮
此例还想展示两个页面的关联
,当实现正确登录后(AdminLoginPage3、AdminLoginTest3),AdminMainTest、AdminMainPage实现登陆成功后页面的一些操作(比如点击用户信息、加个用户啥的),两者通过 AdminLoginPage3的login的返回值联系起来
注意点
- 如果页面之间有关系,通过方法的返回值,将页面关联起来
package com.xhx.core;
import java.time.Duration;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
public class BaseTest {
public WebDriver driver;
@BeforeClass
public void initBrowser() {
System.out.println("初始化浏览器");
System.setProperty("webdriver.gecko.driver", "D:\\test\\Selenium\\geckodriver30.exe");
System.setProperty("webdriver.firefox.bin", "D:\\firefox\\firefox.exe");
driver = new FirefoxDriver();
driver.manage().window().maximize();
// 隐式等待
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(3));
System.out.println("父类的");
}
@AfterClass
public void quitBrowser() {
driver.quit();
}
}
package com.xhx.pages;
import static org.testng.Assert.assertEquals;
import java.time.Duration;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.LoadableComponent;
import org.openqa.selenium.support.ui.WebDriverWait;
public class AdminLoginPage3 extends LoadableComponent<AdminLoginPage3>{
private WebDriver driver;
public AdminLoginPage3(WebDriver driver) {
this.driver = driver;
PageFactory.initElements(driver, this);
}
@FindBy(name = "username")
private WebElement txt_username;
@FindBy(name = "password")
private WebElement txt_password;
@FindBy(xpath = "//input[@type='submit']")
private WebElement btn_submit;
// 实现对应的操作方法
public AdminMainPage login(String name, String password) {
this.load();
this.isLoaded();
this.txt_username.sendKeys(name);
this.txt_password.sendKeys(password);
this.btn_submit.click();
return new AdminMainPage(this.driver);
}
@Override
protected void load() {
this.driver.get("http://localhost:8032/mymovie/admin.php/Login/index.html");
//显示等待
//在设置时间内,默认每隔一段时间检测一次当前页面元素是否存在
//如果超过设置时间检测不到则抛出异常
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.presenceOfAllElementsLocatedBy(By.name("username")));
}
@Override
protected void isLoaded() throws Error { //判断页面是否加载完成
assertEquals(this.driver.getTitle(), "基于ThinkPHP & J-UI 框架的CRM 系统");
}
}
package com.xhx.test;
import static org.testng.Assert.assertTrue;
import org.openqa.selenium.By;
import org.testng.annotations.Test;
import com.xhx.core.BaseTest;
import com.xhx.pages.AdminLoginPage3;
public class AdminLoginTest3 extends BaseTest{
@Test
public void test_login_success() {
AdminLoginPage3 page = new AdminLoginPage3(this.driver);
page.login("admin", "admin");
assertTrue(this.driver.findElement(By.linkText("退出")).isDisplayed());
}
}
package com.xhx.test;
import org.testng.annotations.Test;
import com.xhx.core.BaseTest;
import com.xhx.pages.AdminLoginPage3;
import com.xhx.pages.AdminMainPage;
//添加用户信息的操作
public class AdminMainTest extends BaseTest{
@Test
public void test1() {
AdminLoginPage3 loginPage = new AdminLoginPage3(this.driver);
AdminMainPage mainPage = loginPage.login("admin", "admin");
mainPage.addUser();
}
}
package com.xhx.pages;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
public class AdminMainPage {
public AdminMainPage(WebDriver driver) {
PageFactory.initElements(driver, this);
}
@FindBy(linkText="浏览用户信息")
public WebElement listuser;
public void addUser() {
this.listuser.click();
}
}
简单了解maven
本节中,并未清楚的讲解如何配置、使用maven,但将讲述的相关内容记录。!!
仅仅表示,我知道一点点,但并不完全知道。
菜鸡潸然泪下。。
- 环境、工具准备
(1)maven:简单理解成一个项目构建软件。下载地址:链接;
配置指南链接还需要修改一处:<localRepository>D:/maven_tools</localRepository>
(2)eclipse点击help,下载cucumber插件,安装下图:
(3)配置maven:
创建一个maven project文件。
(4)配置maven project中的xml文件链接地址
剩下的我也不知道了,仅仅将相关资源在此做个记录:
cucumber自动化测试官方教程
菜鸡本菜