页面对象(Page Object)模式
内容转载自 https://www.cnblogs.com/yytesting/p/6973474.html
页面对象(Page Object)模式是目前自动化测试领域普遍使用的设计模式之一,此模式可以大大提高测试代码的复用率,提高测试脚本的编写效率和维护效率,是中级自动化测试工程师的必备技能之一。
使用面向对象的设计模式,页面对象模型将测试代码和被测试页面的页面元素及其操作方法进行分离,以此降低页面元素变化对测试代码的影响。每一个被测试页面都会被单独定义为一个类,类中会定位所有需进行测试操作的页面元素对象,并且定义操作每一个页面元素对象的方法。
例如,登录页面包括一个用户输入框和一个密码输入框,还有一个登录按钮。
我们声明一个名为Login的类,并且通过定位表达式找到用户名和密码输入框,并赋予类中的成员变量,分别定义输入用户名的方法、输入密码的方法和单击登录按钮的方法。
测试代码要完成登录测试,只需要调用Login类中输入用户名的方法、输入密码的方法和单击登录按钮的方法即可完成一个登录操作。如果登录页面的用户输入框、密码输入框或者登录按钮发生了位置变化,我们只需要修改Login类中的相关定位表达式和操作方法就可以完成维护,测试逻辑的脚本甚至不需要改变。
如果用户没有使用此模式,那么将登录过程都用相同的代码进行实现。如果在测试过程中需要多次登录操作,那么只能粘贴相同的代码来简化编写工作。但是可怕的情况是一旦页面元素发生了一点点改变,那么测试人员需要人工去把所有涉及变化的逻辑一一修改,会在不同的测试代码中进行搜索和修改,这样不但大大增加了工作量,而且很容易出现修改错误的情况。使用了页面对象模式,只需要修改一下唯一的Login类,就完成了大部分的维护工作。
测试网址:https://www.126.com/
使用PageFactory类
1.使用PageFactory类给测试类提供待操作的页面元素
先在src中新建一个package,名字为pageobjects,在下面新建一个页面对象类LoginPage。新建一个package,名字为testScripts,在下面新建一个测试类Test126mail。
LoginPage类的源代码如下:
public class LoginPage1 { //使用FindBy注解,定位到需要操作的页面元素 @FindBy(name="email") public WebElement userName; @FindBy(name="password") public WebElement password; @FindBy(id="dologin") public WebElement loginButton; public LoginPage1(WebDriver driver) { PageFactory.initElements(driver, this); } }
Test126mail类的源代码如下:
public class Test126mail1 { private WebDriver driver; private String baseUrl = "https://www.126.com/"; @Test public void testLogin() throws InterruptedException { //访问被测试的网址 driver.get(baseUrl); Thread.sleep(5000); driver.switchTo().frame(0); //生成一个LoginPage的实例 LoginPage1 loginPage = new LoginPage1(driver); //直接使用页面对象的用户元素,输入用户名 loginPage.userName.sendKeys("邮箱名"); //直接使用页面对象的用户元素,输入密码 loginPage.password.sendKeys("邮箱密码"); //直接使用页面对象的登录按钮对象,进行单击操作 loginPage.loginButton.click(); //等待5秒 Thread.sleep(5000); driver.switchTo().defaultContent(); //断言登录后的页面是否包含"收件箱"关键字,来验证是否登录成功 Assert.assertTrue(driver.getPageSource().contains("收件箱")); Thread.sleep(3000); } @BeforeMethod public void beforeMethod() { System.setProperty("webdriver.chrome.driver", "C:\\Users\\lenovo\\AppData\\Local\\Google\\Chrome\\chromedriver.exe"); driver = new ChromeDriver(); } @AfterMethod public void afterMethod() { /*driver.quit();*/ } }
从上面的实例中我们可以看到,页面元素的定位均在LoginPage类中实现了。如果页面元素发生了一定程度的调整,测试人员只需要修改LoginPage类中的定位表达式就可以完成基本的维护工作,测试类代码无需进行调整,从而降低了测试代码的维护工作。
2.使用PageFactory类封装页面元素的操作方法
上面我们只为测试类提供了页面元素来进行操作,并没有在页面对象类中实现页面元素的操作方法。
LoginPage类的源代码如下:
public class LoginPage2 { @FindBy(name="email") public WebElement userName; @FindBy(name="password") public WebElement password; @FindBy(id="dologin") public WebElement loginButton; public String url="https://www.126.com/"; public WebDriver driver; //构造函数,生成浏览器对象,初始化PageFactory对象 public LoginPage2() { System.setProperty("webdriver.chrome.driver", "C:\\Users\\lenovo\\AppData\\Local\\Google\\Chrome\\chromedriver.exe"); driver = new ChromeDriver(); PageFactory.initElements(driver, this); } //访问被测试网址的封装方法 public void load() { driver.get(url); } //关闭浏览器的封装方法 public void quit() { driver.quit(); } //登录操作的封装方法 public void login() throws InterruptedException { Thread.sleep(3000); driver.switchTo().frame(0); userName.sendKeys("邮箱用户名"); password.sendKeys("邮箱密码"); loginButton.click(); Thread.sleep(5000); driver.switchTo().defaultContent(); } public WebDriver getDirver() { return driver; } }
Test126mail的源代码如下:
public class Test126mail2 { @Test public void testLogin() throws InterruptedException { //生成一个LoginPage对象实例 LoginPage2 loginpage = new LoginPage2(); //调用登录页面对象的load方法,访问被测网页 loginpage.load(); //调用登录页面对象的login方法,完成登录操作 loginpage.login(); //断言登录后的页面是否包含"收件箱",来验证是否登录成功 //调用登录页面对象的getDriver方法获取浏览器对象,并获取页面源码 Assert.assertTrue(loginpage.getDirver().getPageSource().contains("收件箱")); //调用登录页面对象的quit方法关闭浏览器 loginpage.quit(); } }
在页面对象中封装了页面元素的操作方法,使得在测试代码中实现测试逻辑更加容易。这些封装方法可以被很多测试逻辑重复调用,从而提高了代码编写和维护的效率,实现了一个类维护,很多测试类可被调用的目的,进一步降低了测试代码的维护成本。
3.使用LoadableComponent类
继承LoadableComponent类可以在页面加载的时候判断是否加载了正确的页面,只需要重写isLoaded和load两个方法。此方式有助于让页面对象的页面访问操作更加健壮。
LoginPage类的源代码:
public class LoginPage3 extends LoadableComponent<LoginPage3>{ @FindBy(name="email") public WebElement UserName; @FindBy(name="password") public WebElement password; @FindBy(id="dologin") public WebElement loginButton; public String url ="https://www.126.com/"; private String title="网易邮箱"; public WebDriver driver; //构造函数,生成浏览器对象,初始化PageFactory对象 public LoginPage3() { System.setProperty("webdriver.chrome.driver", "C:\\Users\\lenovo\\AppData\\Local\\Google\\Chrome\\chromedriver.exe"); driver = new ChromeDriver(); PageFactory.initElements(driver, this); } //访问被测试网址的封装方法 @Override public void load() { this.driver.get(url); } //关闭浏览器的封装方法 public void quit() { driver.quit(); } //登录操作的封装方法 public void login() throws InterruptedException { Thread.sleep(3000); driver.switchTo().frame(0); UserName.sendKeys("wuyn1315667459"); password.sendKeys("goodwu"); loginButton.click(); Thread.sleep(5000); driver.switchTo().defaultContent(); } public WebDriver getDriver() { return driver; } @Override public void isLoaded() throws Error { //断言登录后的页面title是否包含"网易邮箱"这几个关键字 Assert.assertTrue(driver.getTitle().contains(title)); } //增加了需要覆盖的方法load //访问被测试网址的封装方法 }
Test126mail类的源代码:
public class Test126mail3 { @Test public void testLogin() throws InterruptedException { //生成一个LoginPage对象实例 LoginPage3 loginpage = new LoginPage3(); // loginpage.load(); //调用登录页面对象的login方法,完成登录操作 loginpage.login(); //断言登录是否成功 loginpage.isLoaded(); //调用登录页面对象的quit方法关闭浏览器 loginpage.quit(); } }
4.多个PageObject的自动化测试实例
本部分主要讲解多个PageObject的使用方法,以及如何基于多个PageObject实现一个相对复杂的自动化测试实例。
自动化测试实现的3个测试用例如下:
a.在126邮箱,使用正确的用户名和错误的密码进行登录,登录失败并在页面显示“帐号或密码错误”关键字。
b.在126邮箱,使用正确的用户名和正确的密码进行登录,登录成功后会跳转到邮箱文件夹列表首页,并且显示出“收件箱”关键字。
c.在126邮箱,登录成功后,单击“写信”链接,给testYY2017@126.com发送一封邮件,邮件发送成功后页面显示“发送成功”关键字。
LoginPage类:
public class LoginPage4 extends LoadableComponent<LoginPage4>{ @FindBy(name="email") public WebElement userName; @FindBy(name="password") public WebElement password; @FindBy(id="dologin") public WebElement loginButton; public String url ="https://www.126.com/"; private String title="网易邮箱"; public WebDriver driver; //构造函数,生成浏览器对象,初始化PageFactory对象 public LoginPage4(WebDriver driver) { this.driver = driver; PageFactory.initElements(driver, this); } //访问被测试网址的封装方法 @Override public void load() { this.driver.get(url); this.driver.manage().window().maximize(); } public void close() { this.driver.close(); } //登录操作的封装方法,函数方法返回一个HomePage对象 public HomePage login() throws InterruptedException { //调用load方法,浏览器访问126邮箱页面 load(); WebDriverWait wait = new WebDriverWait(driver, 10); wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("iframe:first-child"))); driver.switchTo().frame(0); //页面判断是否显示了用户名输入框 wait.until(ExpectedConditions.visibilityOfAllElementsLocatedBy(By.name("email"))); //清除用户名输入框中的字符,保证用户名输入框中为空 userName.clear(); //输入邮箱用户名 userName.sendKeys("wuyn1315667459"); //输入密码 password.sendKeys("goodwu"); //单击登录按钮 loginButton.click(); Thread.sleep(3000); driver.switchTo().defaultContent(); return new HomePage(driver); } //获取页面源码的封装方法 public String getPageSource() { return driver.getPageSource(); } //登录失败的封装方法,函数方法返回一个LoginPage页面对象 public LoginPage4 LoginExceptingFailure() throws InterruptedException { load(); WebDriverWait wait = new WebDriverWait(driver, 10); wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("iframe:first-child"))); driver.switchTo().frame(0); userName.sendKeys("wuyn1315667459"); password.sendKeys("goodw"); loginButton.click(); Thread.sleep(3000); //driver.switchTo().defaultContent(); //登录失败后,页面不会发生跳转,返回一个LoginPage4对象 return new LoginPage4(driver); } @Override protected void isLoaded() throws Error { // 断言登录后的页面标题是否包含"网易邮箱"这几个字 Assert.assertTrue(driver.getTitle().contains(title)); } }
HomePage类:
public class HomePage { public WebDriver driver; //写信 @FindBy(id="_mail_component_59_59") public WebElement writeMailLink; //邮件发送按钮 @FindBy(xpath="//*[contains(@id,'_mail_button_')]/span[contains(.,'发送')]") public WebElement sendMailButton; //收件人输入框 @FindBy(xpath="//*[contains(@id,'_mail_emailtips')]") public WebElement receiver; public HomePage(WebDriver driver) { this.driver = driver; PageFactory.initElements(driver, this); } //写信的封装方法 public SendSuccessPage writeMail(){ WebDriverWait wait = new WebDriverWait(driver, 10); wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("_mail_component_59_59"))); //单击登录成功后页面上的"写信"链接 writeMailLink.click(); //进入写信页面,等待页面中收件人输入框加载出来 //wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//*[contains(@id,'_mail_emailinput_')]/input"))); wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//*[contains(@id,'_mail_emailtips')]"))); //收件人输入框获取焦点 receiver.click(); //调用粘贴函数在收件人输入框中粘贴 setAndCtrlVClipboardData("1315667459@qq.com"); //按tab键,焦点在邮件正文 pressTabKey(); //输入邮件主题 setAndCtrlVClipboardData("邮件主题"); pressTabKey(); //输入邮件正文 setAndCtrlVClipboardData("邮件正文"); //点击发送邮件按钮 sendMailButton.click(); return new SendSuccessPage(driver); } //设定剪切板病进行字符串粘贴的封装方法 public static void setAndCtrlVClipboardData(String string) { //模拟Ctrl+V进行粘贴操作 StringSelection selection = new StringSelection(string); Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, null); Robot robot = null; try { robot = new Robot(); } catch (AWTException e) { // TODO Auto-generated catch block e.printStackTrace(); } robot.keyPress(KeyEvent.VK_CONTROL); robot.keyPress(KeyEvent.VK_V); robot.keyRelease(KeyEvent.VK_CONTROL); robot.keyRelease(KeyEvent.VK_V); try { Thread.sleep(3000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //按tab键的封装方法 public static void pressTabKey() { Robot robot = null; try { robot = new Robot(); } catch (AWTException e) { // TODO Auto-generated catch block e.printStackTrace(); } robot.keyPress(KeyEvent.VK_TAB); robot.keyRelease(KeyEvent.VK_TAB); } public String getPageSource() { return driver.getPageSource(); } }
SendSuccessPage类:
public class SendSuccessPage { public WebDriver driver; public SendSuccessPage(WebDriver driver) { this.driver = driver; PageFactory.initElements(driver, this); } public String getPageSource() { return driver.getPageSource(); } public void close() { this.driver.close(); } }
Test126mail类:
public class Test126mail4 { public WebDriver driver; @BeforeMethod public void beforeMethod() { System.setProperty("webdriver.chrome.driver", "C:\\Users\\lenovo\\AppData\\Local\\Google\\Chrome\\chromedriver.exe"); driver = new ChromeDriver(); } @AfterMethod public void afterMethod() { driver.quit(); } //测试用例登录失败的测试用例 @Test public void testLoginFail() throws InterruptedException { //生成一个LoginPage对象 LoginPage4 loginPage = new LoginPage4(driver); //继承LoadableCompinent类后,只要实现覆盖的load方法 //及时在没有定义get方法的情况下,也可以进行get方法的调用 //get方法会默认调用页面对象类中的load方法 //loginPage.get(); //调用LoginPage类中的登录失败方法 loginPage.LoginExceptingFailure(); //断言登录失败后的源代码中是否包含了'账号或密码错误'的关键字,调用LoginPage类中的getPageSource方法 //(注意:如果内容是在frame里,得切换到frame,才能通过PageSource获取到) Assert.assertTrue(loginPage.getPageSource().contains("帐号或密码错误")); //调用LoginPage对象中的close方法关闭浏览器 } //测试登录成功的测试用例 @Test public void testLoginSuccess() throws InterruptedException { LoginPage4 loginPage = new LoginPage4(driver); //loginPage.get(); //loginPage.login()方法中已经访问了126网页,这边无需再重新访问 //调用LoginPage类中的login方法,登录成功后会跳转到邮箱登录后主页 //login函数会返回一个HomePage对象,以此来实现页面跳转到登录后主页 //以便实现在HomePage对象中进行相关的方法调用 HomePage homePage = loginPage.login(); //等待5秒,等待从登录页面跳转到邮箱登录后主页 Thread.sleep(5000); //断言登录成功后的源代码是否包含了“收件箱”的关键字,来验证是否登录成功 Assert.assertTrue(homePage.getPageSource().contains("收件箱")); loginPage.close(); } //测试发送邮件成功的测试用例 @Test public void testwriteMail() throws InterruptedException { LoginPage4 loginPage = new LoginPage4(driver); HomePage homePage = loginPage.login(); //等待5秒,等待邮件发送完成 Thread.sleep(5000); //调用写邮件方法,完成在页面上的发送邮件操作 SendSuccessPage successPage = homePage.writeMail(); //等到五秒,等待邮件发送成功 Thread.sleep(5000); //断言邮件发送后,是否出二线"发送成功"4个字,以此验证邮件是否发送成功 Assert.assertTrue(successPage.getPageSource().contains("发送成功")); successPage.close(); } }
遇见的问题:
1.登录错误用例中,断言页面是否包含“帐号或密码错误”,报错元素不存在
原因:该元素在iframe中必须切换进入iframe中,pageSource方法才能包含该元素
2.发送邮件成功用例中,定位收件人输入框,使用xpath定位://*[contains(@id,'_mail_emailinput_')]/input" ,报错,元素不可点击
unknown error: Element <input class="nui-editableAddr-ipt" type="text" role="combobox" tabindex="1" aria-label="收件人地址输入框,请输入邮件地址,多人时地址请以分号隔开"> is not clickable at point (125, 163). Other element would receive the click: <label id="_mail_emailtips_0_214" class="js-component-emailtips nui-ipt-placeholder">...</label>
使用覆盖元素,可以实现功能
设计原则
(1)在PageObject类中定义public方法来对外提供服务。
(2)不要暴露PageObject类中的内部逻辑。
(3)不要在PageObject类中进行断言操作。
(4)只需要在PageObject类中定义需要操作的元素和操作方法。
(5)PageObject页面中的相同动作如果会产生多个不同的结果,需要在PageObject类中定义多个操作方法。