本系列所有代码 https://github.com/zhangting85/simpleWebtest

本文将介绍一个Java+TestNG+Maven+Selenium的web自动化测试脚本环境下selenium页面对象设计下的页面模块的写法,并提供全部代码。

在一个页面上,有的时候,会有一些需要重复利用的模块。

比如,一个电子商务网站上,经常会在页面最顶上有一个搜索框。这个搜索框在几乎所有页面上都会出现。可以随时用它搜索一些商品。

这里,有人用继承,写一个父类,父类提供了这个搜索框的一些功能封装。然后所有页面类都继承这个父类。

这样写一开始是没问题的。但是当这类重用模块增加了,变动了,会造成整个测试代码逻辑结构乱成一团。所以不推荐。

这里介绍一下我的写法:

把这些重复利用的部分作为页面模块。然后我对京东的首页、搜索结果页建立了页面对象模型:

代码如下:

首页

 /**
*京东首页
*/
public class JDHomepage extends Page {
/**
*URL常量,很少用到,一般在起始页用,有时放到配置文件里去统一管理
*/
private static final String URL="http://www.jd.com"; /**
*可供重用的页面模块,作为成员对象在显示这个模块的页面中保存。
*这里用了组合的写法(composite),注意不要滥用继承。
*/
public SearchHeaderModule searchHeader=new SearchHeaderModule(); /**
* 只有homepage之类的起始页才必要有这个init方法用来打开URL。
* return this 表示执行完毕之后页面仍旧在本页。
* 如果留在本页,并有页面刷新,就要return new JDHomepage
* 如果没有页面刷新等页面改变,就return this
* 如果跳转到其他页面,就return new xxxPage
* 这样写的好处,是每个方法的return语句上明确了页面跳转的预期结果
* Only the start page of a test case should has this init method
* @return return this means no page refresh and stay on this page after this method
* return new JDHomepage means stay on this page and has a page refresh
* return new xxxPage means page redirects after this method
*/
public JDHomepage init(){
DriverManager.driver.get(URL);
return this;
} }

在首页里我其实没有封装什么业务逻辑,正常来说如果实际去实现整个京东的测试用例,那么首页这个类会变得比较庞大的。

这里我用下面这段代码创建了SearchHeader这个页面模块

public SearchHeaderModule searchHeader=new SearchHeaderModule();

作为一个成员对象。这个对象的实例会在JDHomepage的构造方法被调用前先被jvm调用。

所以,每个Homepage的实例都会包含一个SearchHeader,然后我们只使用时如下调用即可:

home.init().searchHeader.search("巧克力");

home是一个JDHomepage类的实例,init方法是去打开这个page的URL,我只在首页等起始页上写init方法。

search是searchHeader提供的方法,这样直接连点调用即可。

SearchHeader的实现:

 package simplewebtest.core.page.module.sample.jd;

 import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy; import simplewebtest.core.Page;
import simplewebtest.core.page.sample.jd.JDItemlistPage; /**
* 页面模块。此处表示京东各页面上方共享的搜索条
* 他本身也可以看做是一个页面
* 并以组合(composite)的形式嵌入外部网页,注意不要滥用继承
* this page module is composite to the outer page
*/
public class SearchHeaderModule extends Page { /**
* PageFactory的写法,用标签来定义web elment的查找 define how to find a webelment by
* annotation
*/
@FindBy(id = "key")
WebElement searchInput; @FindBy(xpath = "//input[@value='搜索']")
WebElement searchButton; /**
* 搜索一个关键字,先输入文字,再按搜索按钮 search a keyword
*
* @param keyword
* :搜索关键字
* @return 返回一个JDItemlistPage
*/
public JDItemlistPage search(String keyword) {
searchInput.sendKeys(keyword);
searchButton.click();
return new JDItemlistPage();
}
}

这个SearchHader就是一个普通的页面对象。

注意所有的页面对象里的封装方法我都让他返回类似new JDItemlistPage()之类的页面对象。

这样我们在test case里可以连点。比如

home.init().searchHeader.search("巧克力").getProduct(1).getText();

至于连点造成调试困难?不,由于我们有事件监听和自动log功能,调试不会很困难。

并且我通常是先写线性代码再重构成页面对象,写成这种的都是已经执行通过的代码。

另外,我们不是每次都需要返回新的页面对象实例,因为有时比做一个操作,页面不会跳转也不会变动。这时,return this;返回当前页的实例就行了。

JDItemlistPage

 package simplewebtest.core.page.sample.jd;

 import java.util.List;

 import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy; import simplewebtest.core.Page;
/**
*京东搜索商品结果页
*/
public class JDItemlistPage extends Page { /**
*先找所有商品的父亲节点plist
*/
@FindBy(id = "plist")
public WebElement productList; /**
*直接找第一个商品,XPATH表达式过长,无法阅读。(你会看得头疼吗?我会。。。)
*注意这个xpath是由firepath自动生成的,冗余过度。如果你要用xpath,一定要会自己写
*插件太傻,别依赖他。
*/
@FindBy(xpath = ".//*[@id='plist']/ul/li[1]/div/div[2]/a")
public WebElement firstproduct; /**
*预先定位所有product
*get all products, suggested to use this way
*/
@FindBy(xpath = ".//*[@id='plist']//li")
public List<WebElement> products; /**
*先找父亲plist,让父亲来找儿子,这种写法也是可以的,但是也不是特别好(这一定不是强迫症)
*但是这个方法只能找第一个商品,想找第二个商品要再写一个方法。不推荐。
*/
public String getFirstProductName() {
return productList.findElement(By.xpath("//div[@class='p-name'][1]//a")).getText();
} /**
*先找父亲plist,让父亲来找儿子,但是加了一个传入参数告诉父亲要找第几个儿子,也就是第几个商品。(圣斗士吗,这么多儿子)
*这样我写一次可以找到这个页面上任意一个商品了,京东的网页设计特别适合自动化,可能你要测的网站不是这么工整。
*这里的重点是:Xpath表达式是一个字符串,你可以随意拼接。所以传入参数number可以插进去。
*suggested
*/
public String getProductNameByIndexMethodOne(int number) {
return productList.findElement(By.xpath("//div[@class='p-name']["+number+"]//a")).getText();
} /**
*一次性找出所有product,然后取第几个,我喜欢从1开始所以number-1,仅个人喜好。
*接着对找到的product执行getProductNameOf方法来获取名字
*suggested
*/
public String getProductNameByIndexMethodTwo(int number) {
return getProductNameOf(products.get(number-1));
} private String getProductNameOf(WebElement product)
{
return product.findElement(By.className("p-name")).getText(); } }

这个页面就是一个标准的页面对象了

为了扩展一下,我增加了一些内容,比如寻找第一个商品的四种方法:

        JDHomepage home = new JDHomepage();

        //结果页面the expected result page
JDItemlistPage resultPage=home.init().searchHeader.search("巧克力");
//actual result: 用四种方法找出第一个商品名字,作为实际结果.(回字有五种写法:P) String product_1= resultPage.firstproduct.getText();//不推荐,但偶尔有适用场景
String product_2= resultPage.getFirstProductName();//不推荐,但偶尔有适用场景
String product_3= resultPage.getProductNameByIndexMethodOne(1);//推荐写法,但你方法名字不要这么长
String product_4= resultPage.getProductNameByIndexMethodTwo(1);//推荐写法,但你方法名字不要这么长

如上代码中,(和JDItemlistPage的代码结合起来看)

方法1直接用PageObject.WebElement来获取商品,缺点是每个商品我都要定义一个WebElement

方法2先找到product list,再用一句写死的Xpath来寻找第一个商品,缺点是个商品我都要写一段写死的Xpath

方法3先找到product list,再通过传入参数来组合一段可用的Xpath,优点是我只要写一次Xpath

方法4先找到所有product:

@FindBy(xpath = ".//*[@id='plist']//li")
public List<WebElement> products;

然后再葱存放WebElement的List里取第一个元素。我同样要写一次By.className定位。

对于寻找商品这样的例子来说,推荐用方法3或4。对于一般的页面元素推荐用方法1。对于一些其他特殊的场景,看情况使用方法2。

另外,JDItemListPage里也可以像首页一样加入一个SearchHeader的定义,这里没加只是因为目前我用到的test case里没有这个需要。

selenium从入门到应用 - 5,页面对象设计模式下的页面模块的更多相关文章

  1. selenium从入门到应用 - 4,页面对象设计模式的实现

    本系列所有代码 https://github.com/zhangting85/simpleWebtest 本文将介绍一个Java+TestNG+Maven+Selenium的web自动化测试脚本环境下 ...

  2. Java入门——(3)面对对象(下)

    关键词:  类的继承.final关键字.多态.接口.异常.包.访问控制 一.类的继承       1.类的继承是指在一个现有类的基础上去构建一个新的类,构建出来的新类被称作子类,现有类被称作父类,子类 ...

  3. PageObject 页面对象模式

    一.PageObject 页面对象设计模式  (一个页面建一个类,即对象,页面对象) 每个页面都建对应的class,类中包含了页面的输入框.标题.元素等元素,测试代码中测试这个页面时,只需要调用这个页 ...

  4. [译]Selenium Python文档:六、页面对象

    本章是介绍页面对象设计模式的教程.一个页面对象代表了web应用用户接口的一片区域,你的测试代码将与之交互的. 使用页面对象模式的好处: 可以创建在多个测试样例中都可使用的可重用代码 减少重复性代码 如 ...

  5. 页面对象(Page Object)模式

    内容转载自 https://www.cnblogs.com/yytesting/p/6973474.html 页面对象(Page Object)模式是目前自动化测试领域普遍使用的设计模式之一,此模式可 ...

  6. 5.8 页面对象(Page Object)模式

    页面对象(Page Object)模式是目前自动化测试领域普遍使用的设计模式之一,此模式可以大大提高测试代码的复用率,提高测试脚本的编写效率和维护效率,是中级自动化测试工程师的必备技能之一. 1.页面 ...

  7. 微信小程序使用页面栈改变上一页面的数据

    微信小程序中如果从一个页面中进入下一个页面,如果下个页面的数据有删除或者增加再返回上一个页面的时候,就会导致页面不刷新(数据加载函数在onload中),从而造成数据不一致的情况.其实在微信小程序中是可 ...

  8. selenium测试框架篇,页面对象和元素对象的管理

    前期已经做好使用Jenkins做buildhttp://www.cnblogs.com/tobecrazy/p/4529399.html 做自动化框架,不可避免的就是对象库. 有一个好的对象库,可以让 ...

  9. Selenium自动化中DOM,XPATH,CSS定位Web页面对象的优劣性分析

    加速IE浏览器自动化执行效率:Selenium自动化中DOM,XPATH,CSS定位Web页面对象的优劣性分析 1.技术背景       在Web应用中,用户通过键盘在输入框中输入值和鼠标点击按钮,链 ...

随机推荐

  1. mysql的mysqladmin的用法

    mysqladmin 适合于linux和windows系统 linux下:mysqladmin -u[username] -p[password] status windows下:先在安装目录找到my ...

  2. vi相关内容

    vi显示行号: 第一种是,手动显示:在vim命令行模式下输入 :set nu 取消显示:在vim命令行模式下输入: set nonu 第二种是,永久自动显示:我们修改一个配置文件. 我们输入命令:vi ...

  3. 执行程序(例如UltraEdit)在WIN7下添加到右键菜单

    把下面提供的代码复制到记事本,保存为注册表文件(*.reg),右键合并即可.注意把最后一行换成你自己的路径. Windows Registry Editor Version 5.00 [HKEY_CL ...

  4. MySQL的数据类型和建库策略详解

    无论是在小得可怜的免费数据库空间或是大型电子商务网站,合理的设计表结构.充分利用空间是十分必要的.这就要求我们对数据库系统的常用数据类型有充分的认识.下面我就将我的一点心得写出来跟大家分享. 一.数字 ...

  5. Dart类

    Dart中没有访问控制符,无论类还是方法默认都是public 1.构造函数 构造函数可以没有方法体,并且this可以直接在传参时直接对实例赋值 Bicycle(this.cadence, this.s ...

  6. 哦这。。!C语言scanf输入的坑爹之处

    一. 今天闲来无事,跑去A题,本想3sA了poj1004,结果搞了10分钟,最令人困惑的问题就是为什么定义了double类型的变量,但是用scanf输入的时候标识符用%f的话,输入并不能完成,也就是说 ...

  7. POJ 1722 SUBTRACT

    给定一个数组a[1,2,..,n] .定义数组第i位上的减操作:把ai和ai+1换成ai - ai+1.输入一个n位数组以及目标整数t,求一个n-1次操作序列,使得最后剩下的数等于t最后输出依此操作的 ...

  8. Linux查看内核信息或系统信息

    先说说为什么会写这个.这是我去面试的时候面试官问的一个问题,我感觉是一个普遍会被问到的问题.为了让我自己记住,也便于收集下Linux运维方向考官的题目. 第一,查看内核信息 cat /proc/ver ...

  9. Don't Be a Subsequence

    问题 F: Don't Be a Subsequence 时间限制: 1 Sec  内存限制: 128 MB提交: 33  解决: 2[提交] [状态] [讨论版] [命题人:] 题目描述 A sub ...

  10. 【C++】const 常引用的用法

    用法: const int & a; 定义了一个整型常引用变量a. 特点: 1.不能够通过常引用变量来修改其值,如: const int & a = 10; a = 12;//编译报错 ...