selenium - the page object pattern

22
The Page Object Pa-ern A basic DRY abstrac-on pa1ern for Web browser automa-on test development, maintenance and versioning Alex Kogon [email protected]

Upload: michael-palotas

Post on 07-Aug-2015

147 views

Category:

Software


2 download

TRANSCRIPT

The  Page  Object  Pa-ern A  basic  DRY  abstrac-on  pa1ern  for  Web  browser  automa-on  test  

development,  maintenance  and  versioning  

Alex  Kogon  

[email protected]  

Basic  Web  Test  Development

• What  do  we  do  in  Selenium  Web  Test  Development?    Just  like  the  user,  we  launch  a  Web  browser,  load  pages,  and  read,  write,  and  click  on  bu1ons  and  links.  

• How  do  we  interact  with  the  Web  browser?    The  Web  browser  is  interface  via  an  Object  in  your  code,  which  can  be  told  to  load  pages,  and  queried  for  Element  Objects  that  are  currently  on  the  displayed  Web  page.  These  Element  Objects  may  be  read  from,  wri1en  to,  or  clicked  on.  

A  First  Basic  Selenium  Script

1.  Load  www.google.com  2.  Find  the  search  text  field  3.  Enter  “Alex  Kogon”  4.  Find  the  search  bu1on  5.  Click  on  it  

A  First  Basic  Selenium  Script  (Java) public  class  BasicWebTest  {  

   private  WebDriver  theSeleniumWebDriver;  

   @Before  

   public  void  setUp()  throws  Exception  {  

       theSeleniumWebDriver  =  new  RemoteWebDriver(new  URL("http://localhost:4444/wd/hub"),  DesiredCapabilities.firefox());  

   }  

   @Test  

   public  void  test()  {  

       theSeleniumWebDriver.get("http://www.google.com");  

       final  WebElement  myGoogleInputElement  =  theSeleniumWebDriver.findElement(By.cssSelector("#lst-­‐ib"));        

       myGoogleInputElement.sendKeys("Alex  Kogon");  

       final  WebElement  mySearchButtonElement  =  theSeleniumWebDriver.findElement(By.cssSelector("#tsf  >  div.tsf-­‐p  >  div.jsb  >  center  >  input[type=\"submit\"]:nth-­‐child(1)"));  

       mySearchButtonElement.click();  

   }  

}  

 

Adding  more  searches

1.  Load  www.google.com  2.  Find  the  search  text  field  3.  Enter  other  varia-ons  on  name  (“Alexander  Kogon”,  “Kogon,  Alex”)  4.  Find  the  search  bu1on  5.  Click  on  it  

Another  Search

 @Test      public  void  anotherTest()  {          theSeleniumWebDriver.get("http://www.google.com");          final  WebElement  myGoogleInputElement  =  theSeleniumWebDriver.findElement(By.cssSelector("#lst-­‐ib"));                myGoogleInputElement.sendKeys("Kogon,  Alex");          final  WebElement  mySearchButtonElement  =  theSeleniumWebDriver.findElement(By.cssSelector("#tsf  >  div.tsf-­‐p  >  div.jsb  >  center  >  input[type=\"submit\"]:nth-­‐child(1)"));          mySearchButtonElement.click();      }  

Et  cetera…    @Test  

       public  void  yetAnotherTest()  {  

           theSeleniumWebDriver.get("http://www.google.com");  

           final  WebElement  myGoogleInputElement  =  theSeleniumWebDriver.findElement(By.cssSelector("#lst-­‐ib"));        

           myGoogleInputElement.sendKeys("Alexander  Kogon");  

           final  WebElement  mySearchButtonElement  =  theSeleniumWebDriver.findElement(By.cssSelector("#tsf  >  div.tsf-­‐p  >  div.jsb  >  center  >  input[type=\"submit\"]:nth-­‐child(1)"));  

           mySearchButtonElement.click();  

       }  

   @Test  

       public  void  andSoAnotherTest()  {  

           theSeleniumWebDriver.get("http://www.google.com");  

           final  WebElement  myGoogleInputElement  =  theSeleniumWebDriver.findElement(By.cssSelector("#lst-­‐ib"));        

           myGoogleInputElement.sendKeys("Kogon,  Alexander");  

           final  WebElement  mySearchButtonElement  =  theSeleniumWebDriver.findElement(By.cssSelector("#tsf  >  div.tsf-­‐p  >  div.jsb  >  center  >  input[type=\"submit\"]:nth-­‐child(1)"));  

           mySearchButtonElement.click();  

       }  

Eureka!

Isn’t  this  great?      With  almost  no  effort  we  can  write  test  scripts  building  new  func-onality  from  what  we’ve  already  done  by  copying  and  pas-ng  the  exis-ng  work  we’ve  done  and  modifying  a  couple  things.  

So  what’s  the  problem?

How  about  if  something  changes?    By  building  a  test  from  an  exis-ng  site  and  copying  the  CSS  selectors,  it  is  easy  to  build  a  large  body  of  func-onality  tests.  However,  what  happens  if  the  CSS  selectors  (or  other  underlying  func-onality)  change?  

Bri-le  Selectors

Please  give  us  a  unique  selector!    When  construc-ng  selectors,  there  are  a  variety  of  op-ons  on  how  it  may  be  done.  The  best  selectors  reference  unique  iden-fiers  associated  with  the  HTML  element,  like  the  Google  Search  Text  Field  in  our  example  code:  theSeleniumWebDriver.findElement(By                  .cssSelector("#lst-­‐ib"));  However,  quite  oben  the  developers  have  not  inserted  unique  tags,  and  we  get  a  selector  similar  to  what  we  used  for  the  Google  Search  Bu1on  in  our  example  code:  final  WebElement  mySearchButtonElement  =  theSeleniumWebDriver                  .findElement(By                          .cssSelector("#tsf  >  div.tsf-­‐p  >  div.jsb  >  center  >  input[type=\"submit\"]:nth-­‐child(1)"));  

What  Happens  When  Selectors  Change?

Layout  based  selectors  are  a  headache…    Look  at  the  second  selector  again:  cssSelector("#tsf  >  div.tsf-­‐p  >  div.jsb  >  center  >  input[type=\"submit\"]:nth-­‐child(1)"));  With  a  bit  of  knowledge  of  CSS  selectors,  you  can  see  that  this  is  querying  a  subsec-on  of  the  page  for  all  of  it’s  submit  bu1ons,  and  asking  for  the  first  one.  What  if  the  layout  of  the  page  changed,  or  there  was  another  bu1on  inserted  before  it?  Suddenly  either  no  bu1on  will  be  found  or  the  wrong  one  clicked.  Ideally,  the  developers  can  be  convinced  to  insert  unique  selectors  into  every  element  you  need  to  access,  but  oben  this  is  not  the  case.  How  to  handle  the  changes?  

Brute  Force

Search  and  Replace    So  let’s  assume  the  guys  at  Google  put  in  another  bu1on  before  the  search  bu1on.  Our  CSS  selector  now  looks  like:  cssSelector("#tsf  >  div.tsf-­‐p  >  div.jsb  >  center  >  input[type=\"submit\"]:nth-­‐child(2)"));  OK,  well  we’ve  got  four  uses  of  this  selector,  but  they  are  all  in  the  same  file,  so  searching  and  replacing  is  not  the  end  of  the  world.  What  about  if  we  had  hundreds?  And  they  were  in  different  files?    What  if  we  just  had  to  change  it  once?  While  copying  and  pas-ng  code  all  over  is  easy  and  convenient  for  reuse,  it  creates  a  lot  of  extra  busy  work  when  something  needs  to  be  changed.  By  crea-ng  a  “Single  Point  Of  Truth”,  or  a  single  way  in  which  this  selector  is  accessed,  we  only  need  to  change  it  once.  A  bit  of  extra  work  up  front  to  save  a  lot  of  work  in  the  future.  

Don’t  Repeat  Yourself  (DRY)

Encapsulate  code  in  a  single  loca-on    OK,  so  we’ve  copied  the  same  code  in  four  places,  and  now  we  need  to  change  it.  To  do  so  most  easily,  we  would  like  to  only  have  to  change  it  once.  What  do  we  do?  Refactor.  One  of  the  most  powerful  innova-ons  in  modern  IDE’s  is  the  ability  to  automa-cally  have  the  IDE  change  code,  a.k.a.  “refactoring”.  Refactoring  allows  you  to  change  the  architecture  of  your  code  on  the  fly,  adop-ng  more  complicated  structures  as  demanded,  while  keeping  things  as  simple  as  possible  at  the  -me.    There  are  several  possible  solu-ons  for  how  to  refactor  this  code  to  be  DRY;  we  will  work  with  extrac-ng  a  common  method  that  is  accessed  from  all  the  tests.  

What  if  the  Selector  is  used  across  mulOple  script  files?

Share  methods  within  a  u-lity  class    So  now  we’ve  created  a  single  method  containing  the  CSS  Selector  that  can  be  easily  modified  to  update  the  Selector  when  it  changes.  However,  that  method  is  only  in  one  class.  If  the  selector  is  used  from  mul-ple  test  script  class  files,  the  change  must  be  implemented  in  each  one.  Wouldn’t  it  be  easier  to  just  move  the  method  to  another  class  where  it  could  be  shared  by  all  the  different  test  scripts?  The  “Move”  refactor  is  also  quite  helpful  in  doing  this.  

The  Page  Object  Pa-ern

Break  your  u-lity  classes  out  by  Page    The  u-lity  class  we  have  just  created  contains  all  the  logic  to  access  elements  on  the  Web  pages  we  use  in  our  test.  Assuming  there  are  many  Web  pages,  they  are  all  mixed  together  in  our  class.  By  breaking  out  the  u-lity  class  into  separate  classes,  each  with  the  accessors  for  a  single  Web  page,  we  create  a  be1er  abstrac-on  where  we  can  easily  share  func-onality  across  our  test  suite  by  bringing  in  (and  extending)  exis-ng  Page  Object  code  for  each  page  a  test  uses.  

Code  

Page  Object  Pa-ern  Architecture

Google  Home  Page  

Google  Search  Result  Page  

Google  Home  Page  Object  

Google  Search  Result  Page  Object  

Page  Objects Web  Pages

Test  1  

Test  2  

Test  3  

Test  Scripts

Maven  and  Page  Objects

Store  each  page  as  a  library  in  Maven    Now  that  we’ve  broken  up  our  accessors  into  Page  Objects  so  they  can  be  easily  shared  across  test  scripts,  why  not  put  them  into  a  separate  module  so  that  they  can  be  easily  shared  across  test  projects  as  well?  By  pugng  each  Page  Object  into  a  unique  Maven  module,  tests  can  simply  define  all  the  Page  Objects  they  need  to  reference  in  their  Maven  configura-on  (pom.xml)  file,  and  have  access  to  the  Page  Objects  without  needing  to  copy  the  Page  Objects  into  mul-ple  projects  (another  DRY  viola-on)  or  have  them  all  in  a  single  project.  

Maven,  Page  Objects,  and  Versioning

Added  benefits  of  using  Maven    OK,  now  we’ve  encapsulated  each  of  our  CSS  selectors  in  a  single  accessor  method,  stored  each  of  these  in  a  unique  Object  represen-ng  each  Web  page  used,  and  created  a  Maven  library  for  this  Page  Object.  What  next?  When  running  Web  automa-on  tes-ng  in  a  large,  dynamic,  organiza-on,  you  will  find  quite  oben  that  there  are  mul-ple  versions  of  each  Web  page  that  must  be  tested  simultaneously.  Reliability  tes-ng  on  the  live  site  requires  the  version  currently  in  live  is  tested;  integra-on  tes-ng  on  imminent  releases  require  the  version  ready  for  release;  and  development  tes-ng  on  new  versions  of  the  Web  page  (perhaps  a  pipeline  of  mul-ple  future  releases)  require  the  version  for  each  of  those  pages.  Luckily  Maven  already  provides  us  with  a  solu-on.  Maven  libraries  are  easily  versioned  for  deployment,  such  that  many  versions  of  the  library  can  be  available  for  use  by  the  tests  depending  on  which  version  of  each  page  is  to  be  tested.  A  single  test  script  is  s-ll  able  to  be  used  on  the  various  versions,  as  the  differences  in  the  implementa-on  in  each  version  is  abstracted  behind  the  Page  Object.  

Code  

Page  Object  Pa-ern  Versioning

Google  Home  Page  0.0.1  

Google  Home  Page  0.0.2  

Google  Home  Page  Object  V  0.0.1  

Google  Home  Page  Object  V  0.0.2  

Page  Objects Web  Pages

Test  1  

Test  2  

Test  3  

Test  Scripts

ConOnuous  Deployment  and  IntegraOon

The  full  enchilada    Now  that  we’ve  got  reusable,  versioned  Page  Objects  referenced  from  our  Web  script  code  via  Maven,  and  automated  tests  which  leverage  the  page  objects  by  version,  let’s  see  how  this  works  in  our  Automa-on  solu-ons.  A  Con-nuous  Deployment  and  Integra-on  system  can  be  easily  leveraged  to  provide  tes-ng  across  all  the  mul-ple  versions  in  real  -me.  By  allowing  the  defini-on  of  the  version  of  each  page  to  be  deployed  for  each  tes-ng  to  be  defined,  the  correct  version  of  each  Page  can  be  deployed  into  the  Web  applica-on  servers  for  the  test  run,  and  the  correct  version  of  each  Page  Object  corresponding  to  that  Page  version  used  (by  the  iden-cal  test)  by  dynamically  instruc-ng  Maven  to  use  the  same  version  of  the  Page  Object  that  was  just  deployed  for  tes-ng.  In  this  way  it  is  very  easy  to  test  mul-ple  integra-on  scenarios  in  real  -me  (produc-on,  integra-on,  development,  etc.),  and  to  easily  test  any  possible  integra-on  of  versions  by  simply  defining  the  versions  to  be  used  for  an  Automa-on  run.  Try  doing  that  with  hard  coding.  

ConOnuous  IntegraOon  with  Versioning

Jenkins  

ConOnuous  IntegraOon  Server

Trigger  0.0.1  Build  

Checkout  0.0.1  Branch  

Git  

Version  Control Server

Build  0.0.1  Branch  

Nexus  

Maven  Deployment  

Server

Tomcat  

Web  ApplicaOon  Server

Deploy  0.0.1  Branch  

Run  0.0.1  Test  

Maven  

Test Runner

Run  With  0.0.1  Page  Object  

Selenium  Test  Run   Firefox  

Drive  Browser  

Deliver  0.0.1  Page  Objects  

Deliver  0.0.1  Pages  

?