什么是POM(page object model)?从英文解释为页面对象模式,说的很清楚,玩过QTP的朋友就有点懵懂了;不错,就是有点对象库的意思。还记得前面说的QTP故事的时候说到对象库编程与描述性编程吗?可以回去看看。对象库就更好理解了,就是管理对象的一个仓库,而从selenium的POM模式来说,是每个页面的对象分为一个对象库;再说明白点就是每个页面的对象定位管理起来。
一、自定义POM模式
1.基类BasePage
public class BasePage {
private static Logger logger = Logger.getLogger(BasePage.class);
public WebDriver driver;
public BasePage(){
this.driver = WebDriverMethod.ThreadDriver.get();
logger.info("使用BasePage类初始化driver对象");
}
/**
* 智能,元素不可见时,最多等待10S,10S内找到元素立即返回
* @param locator
* @return
*/
public WebElement AutoWait(By locator){
WebElement webElement = null;
try {
WebDriverWait wait = new WebDriverWait(driver,10);
wait.until(ExpectedConditions.visibilityOfElementLocated(locator));
logger.info("元素位置"+locator+"可见");
webElement = driver.findElement(locator);
webElement.isEnabled();
} catch (Exception e) {
e.printStackTrace();
logger.error("元素位置"+locator+"不可见"+e.getMessage());
}
return webElement;
}
/**
* 找到元素并点击操作
* @param elment 传参为By元素locator
*/
public void FindElementClick(By locator){
WebElement element = driver.findElement(locator);
if(element != null){
element.click();
logger.info("元素点击操作,位置"+locator);
}
}
/**
* 找到元素并输入内容
* @param elment 传参为By元素locator
* @param text 传参输入的内容
*/
public void FindElementSendKeys(By locator,String text){
WebElement element = driver.findElement(locator);
if(element != null){
element.clear();
element.sendKeys(text);
logger.info("元素输入内容"+text);
}
}
/**
*找到元素并取出元素文本值返回
* @param elment 传参为By元素locator
* @return 返回文本值,没有文本值返回null
*/
public String FindElementGetText(By locator){
String result = null;
WebElement element = driver.findElement(locator);
if(element != null){
result = element.getText();
logger.info("获取元素文本值:"+result);
}
return result;
}
}
上面代码仅仅列出Click、sendKey、getText三个方法,三个方法都加了显示等待的处理,并使用isEnabled()方法处理,元素可见并可用时方操作,保证了操作元素的强壮性;其他方法就不列出了。Driver的获取是通过ThreadLocal管理,实现多线程运行。
2.新建LoginPage类,是登录页的元素管理,继承基类使用基类封装的操作方法。
public class LoginPage extends BasePage{
private static Logger logger = Logger.getLogger(LoginPage.class);
/**
* 用户名输入框
* @param username
*/
public void usernameTextBox(String username){
FindElementSendKeys(By.id("username"),username);
logger.info("登录用户名输入:"+username);
}
/**
* 密码输入框
* @param pw
*/
public void passwordTextBox(String pw){
FindElementSendKeys(By.id("userpwd"),pw);
logger.info("登录密码输入:"+pw);
}
/**
* 登录按钮
*/
public void login_Button(){
FindElementClick(By.name("button"));
logger.info("点击登录按钮");
}
/**
* 登录失败时的提示语
* @return
*/
public String error_Warn(){
String errorWarn = FindElementGetText(By.cssSelector(".content"));
logger.info("登录失败提示语:"+errorWarn);
return errorWarn;
}
/**
* 登录操作
* @param list 入参参数
* @return 返回验证的结果
*/
public String login(List<String> list){
String act = null;
String startTitle = WebDriverMethod.ThreadDriver.get().getTitle();
usernameTextBox(list.get(0));
passwordTextBox(list.get(1));
login_Button();
String stopTitle = WebDriverMethod.ThreadDriver.get().getTitle();
if(startTitle.equals(stopTitle)){
act = error_Warn();
logger.info("登录失败,页面停留在本页,错误内容:"+act);
}else{
act = "登录成功";
}
return act;
}
}
通过上面的LoginPage可以看出,每个元素都独立管理,使用元素直接调用即可;login作为登录操作方法,入参是list集合主要是解决多参数的场景;LoginPage在POM中称为pageObject页面对象,当元素定位路径有改变只需要单独改变元素的定位,这完全符合java的封装思想也达到了解耦的效果。
二、PageFactory
对于POM模式,selenium提供了Selenium PageFactory,意思就是页面工厂模式;主要也是对元素进行分离管理;使用方法是@FindBy注解描述元素定位,如:
@FindBy(id=”username”) WebElement username
@FindBy(xpath=”//*[@id=’username’]”) WebElement username
同样的也是支持id、css、xpath、className、tagName等元素定位方式的,这里就不一一举例啦。使用注解只是描述元素位置,并没有发现使用findElement方法定位实例化WebElement对象。而是要使用PageFactory.initElements(driver,page)方法来初始化实例化WebElement。调用initElements()有两种方法,建议加上隐式等待的的方式。
(1)PageFactory.initElements(driver, XXX.class);
(2)PageFactory.initElements(new AjaxElementLocatorFactory(driver, 10) ,XXX.class);
public class LoginPage {
public WebDriver driver;
@FindBy(id="username")WebElement username;
@FindBy(id=" userpwd ")WebElement password;
@FindBy(name="button")WebElement loginButton;
LoginPage(WebDriver driver){
this.driver = driver;
PageFactory.initElements(new AjaxElementLocatorFactory(driver,10 ),this);
}
public void login(String uname,String pw){
username.sendKeys(uname);
password.sendKeys(pw);
loginButton.click();
}
}
三、POM模式优点
1.首先来两种看看使用POM的好处:
(1)每个元素的管理是独立,修改的时候只修改单个元素的定位
(2)元素与操作是分离的,修改元素不影响逻辑操作
(3)代码美观、可读性提高
2.自定义POM与selenium提供的PageFactory的区别
首先自定义POM模式自由,可根据自己要求编写逻辑与判断;而对于PageFactory初始化时只提供了隐式等待,但这不能说PageFactory就处于劣势;那到底使用谁更好呢?下面来见分晓。
四、PageFactory优势
首先PageFactory的原理是通过反射以及代理方式实现,当使用@FindBy注解描述元素定位,如@FindBy(xpath=”//*[@id=’username’]”)这只是描述元素位置,并没有发现使用findElement方法定位实例化WebElement对象。而是当使用PageFactory.initElements(driver,page)方法时才会通过动态代理、反射去调用findElement()方法实例化WebElement对象。而为什么叫动态代理呢?我们平时使用的findElement()方法实例化WebElement对象时,在调用这个对象才会执行,而这带来的坏处就是如果调用这个对象后,页面DOM刷新后,不重新再使用findElement()方法查找就会报错。而使用Factory工厂模式的proxy之后每次调用都会自动查找,这样就减少很多代码。下面写段代码演示一下它的强大之处:
1.新建一个html文件,随意取名为1.html
<html>
<head>
<script type="text/javascript">
function reloadPage()
{
window.location.reload()
}
</script>
</head>
<body>
<input type="button" value="点击我"
onclick="reloadPage()" name="button"/>
</body>
</html>
Html代码,点击按钮dom重新去请求服务器拿文件加载,当然这种操作在实际使用中是很少用的,因为会造成页面加载慢,影响用户体验。要说到dom重新加载为什么会影响页面加载?这又涉及到浏览器工作,简单的说就是响应拿到html,dom树解析形成一个架子,然后遇到图片,去请求一次图片地址得到响应后显示,遇到javascript执行javascript;如果这时候有个按钮点击会触发dom的reload()事件,好啦,之前做的都白干了,重头再来一遍,又开始解析html形成dom树,然后继续往下。这样耗时可想而之。下面主要是为了演示,不代表实际就真的会出现,但不排除被使用,不然reload()事件就没有任何存在的价值了。
2.简单的findElement()查找,自定义的POM模式与也是使用findElement()这种方式去定位实例化WebElement。
public class Animal {
public static void main(String[] args) {
WebDriver driver = new ChromeDriver();
driver.get("file:///D:/1.html");
WebElement element =driver.findElement(By.name("button"));
System.out.println(element.getAttribute("value"));
element.click();
System.out.println(element.getAttribute("value"));
driver.quit();
}
}
运行结果,报错,大概意思就是旧的元素,在新页面中找不到啦。
Exception in thread "main" org.openqa.selenium.StaleElementReferenceException: stale element reference: element is not attached to the page document
3.使用PageFactory注解的方式:
public class Animal {
@FindBy(name="button")
public static WebElement test;
public static void main(String[] args) {
WebDriver driver = new ChromeDriver();
driver.get("file:///D:/1.html");
Animal animal = PageFactory.initElements(driver, Animal.class);
System.out.println(test.getAttribute("value"));
test.click();
System.out.println(test.getAttribute("value"));
driver.quit();
}
}
运行结果全部正确,打印两个“点击我”文字。所以这就能很明显的体现出PageFactory的优势啦。但是,又要但是了,因为dom重载在实际使用中并不多,所以使用哪一种根据自己喜爱决定啦,我个人比较偏爱自定义,一个喜欢自由的人;所以对于PageFactory的代码讲解比较少,使用PageFactory可以自己修改代码,有问题随时交流。