selenium从入门到应用 - 5,页面对象设计模式下

系统 1868 0

本系列所有代码 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里没有这个需要。

 

selenium从入门到应用 - 5,页面对象设计模式下的页面模块


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论