zoukankan      html  css  js  c++  java
  • selenium从入门到应用

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

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

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

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

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

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

    这里介绍一下我的写法:

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

    代码如下:

    首页

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

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

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

    public SearchHeaderModule searchHeader=new SearchHeaderModule();

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

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

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

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

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

    SearchHeader的实现:

     1 package simplewebtest.core.page.module.sample.jd;
     2 
     3 import org.openqa.selenium.WebElement;
     4 import org.openqa.selenium.support.FindBy;
     5 
     6 import simplewebtest.core.Page;
     7 import simplewebtest.core.page.sample.jd.JDItemlistPage;
     8 
     9 
    10 /**
    11  * 页面模块。此处表示京东各页面上方共享的搜索条
    12  * 他本身也可以看做是一个页面
    13  * 并以组合(composite)的形式嵌入外部网页,注意不要滥用继承
    14  * this page module is composite to the outer page
    15  */
    16 public class SearchHeaderModule extends Page {
    17 
    18     /**
    19      * PageFactory的写法,用标签来定义web elment的查找 define how to find a webelment by
    20      * annotation
    21      */
    22     @FindBy(id = "key")
    23     WebElement searchInput;
    24 
    25     @FindBy(xpath = "//input[@value='搜索']")
    26     WebElement searchButton;
    27 
    28     /**
    29      * 搜索一个关键字,先输入文字,再按搜索按钮 search a keyword
    30      * 
    31      * @param keyword
    32      *            :搜索关键字
    33      * @return 返回一个JDItemlistPage
    34      */
    35     public JDItemlistPage search(String keyword) {
    36         searchInput.sendKeys(keyword);
    37         searchButton.click();
    38         return new JDItemlistPage();
    39     }
    40 }
    View Code

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

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

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

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

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

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

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

    JDItemlistPage

     1 package simplewebtest.core.page.sample.jd;
     2 
     3 import java.util.List;
     4 
     5 import org.openqa.selenium.By;
     6 import org.openqa.selenium.WebElement;
     7 import org.openqa.selenium.support.FindBy;
     8 
     9 import simplewebtest.core.Page;
    10 /**
    11  *京东搜索商品结果页
    12  */
    13 public class JDItemlistPage extends Page {
    14     
    15     
    16     /**
    17      *先找所有商品的父亲节点plist
    18      */
    19     @FindBy(id = "plist")
    20     public WebElement productList;
    21     
    22     /**
    23      *直接找第一个商品,XPATH表达式过长,无法阅读。(你会看得头疼吗?我会。。。)
    24      *注意这个xpath是由firepath自动生成的,冗余过度。如果你要用xpath,一定要会自己写
    25      *插件太傻,别依赖他。
    26      */
    27     @FindBy(xpath = ".//*[@id='plist']/ul/li[1]/div/div[2]/a")
    28     public WebElement firstproduct;
    29     
    30     /**
    31      *预先定位所有product
    32      *get all products, suggested to use this way
    33      */
    34     @FindBy(xpath = ".//*[@id='plist']//li")
    35     public List<WebElement> products;
    36     
    37     /**
    38      *先找父亲plist,让父亲来找儿子,这种写法也是可以的,但是也不是特别好(这一定不是强迫症)
    39      *但是这个方法只能找第一个商品,想找第二个商品要再写一个方法。不推荐。     
    40      */
    41     public String getFirstProductName() {
    42         return productList.findElement(By.xpath("//div[@class='p-name'][1]//a")).getText();
    43     }
    44     
    45     /**
    46      *先找父亲plist,让父亲来找儿子,但是加了一个传入参数告诉父亲要找第几个儿子,也就是第几个商品。(圣斗士吗,这么多儿子)
    47      *这样我写一次可以找到这个页面上任意一个商品了,京东的网页设计特别适合自动化,可能你要测的网站不是这么工整。
    48      *这里的重点是:Xpath表达式是一个字符串,你可以随意拼接。所以传入参数number可以插进去。
    49      *suggested
    50      */
    51     public String getProductNameByIndexMethodOne(int number) {
    52         return productList.findElement(By.xpath("//div[@class='p-name']["+number+"]//a")).getText();
    53     }
    54 
    55     /**
    56      *一次性找出所有product,然后取第几个,我喜欢从1开始所以number-1,仅个人喜好。
    57      *接着对找到的product执行getProductNameOf方法来获取名字
    58      *suggested
    59      */
    60     public String getProductNameByIndexMethodTwo(int number) {    
    61         return getProductNameOf(products.get(number-1));
    62     }
    63     
    64     private String getProductNameOf(WebElement product)
    65     {
    66         return product.findElement(By.className("p-name")).getText();
    67         
    68     }
    69     
    70     
    71 }
    View Code

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

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

            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里没有这个需要。

  • 相关阅读:
    Fixed Function Shader
    sqlserver 2014 数据库作业 通过脚本创建注意事项
    块存储、文件存储、对象存储意义及差异
    程序员如何成为架构师
    那些编程水平很高的程序员是怎么练成的?
    在ASP.NET Core调用WebService
    .net core 调用webservice同步方法
    Sqlserver中如何创建链接服务器
    JWT实现鉴权
    JWT原理实现代码
  • 原文地址:https://www.cnblogs.com/sdet/p/3649096.html
Copyright © 2011-2022 走看看