webdirver中pagefactory和pageObjects的区别

http://relevantcodes.com/pageobjects-and-pagefactory-design-patterns-in-selenium/


Selenium PageObjects and PageFactory

This article discusses the PageObject design pattern and a factory class from WebDriver support libraries. I believe this is a very robust way to model your tests. The way this design pattern is formulated, it becomes easier making changes to the flow, logic or the UI test code. Let’s now cover the concepts behind the design pattern and its factory class.

I have used the NewTours Flight Application, .NET 4.0 with Visual Express 2010 and WebDriver 2.20 API for writing examples in this article.

PageObject Design Pattern

The PageObject design pattern models areas of a UI as objects within test code. The functionality classes (PageObjects) in this design represent a logical relationship between the pages of the application. Each class is referred to as a PageObject and returns other PageObjects to facilitate the flow between pages. Because PageObjects are returned, it becomes necessary to model both successful and unsuccessful events that can occur when interacted with a page. For example, consider logging into Gmail. After entering the user details, the step either passes and navigates to the Inbox page or stays on the Login page possibly due to invalid input parameters. A pass would then return the Inbox PageObject whereas a fail would return the Login PageObject.

This means better tests, exception handling and reporting. It may sound a little confusing, but its quite a simple yet an elegant approach to write your tests. Let’s break down the above explanation into actual PageObjects.

In the code below, the following events occur:

  1. Constructor verifies if page is valid
  2. Attempts login to the Flights application
  3. (If successful) Returns the FindFlights PageObject
class LoginPage
{
    private IWebDriver driver;
 
    public LoginPage(IWebDriver driver)
    {
        this.driver = driver;
 
        // 1. verify if page is valid
        if (driver.Title != "Welcome: Mercury Tours")
            throw new NoSuchWindowException("This is not the Login page");
    }
 
    // return FindFlightsPage PageObject
    public FindFlightsPage Do(string UserName, string Password)
    {
        // 2. steps to login to the Flights application
        driver.FindElement(By.Name("userName")).SendKeys(UserName);
        driver.FindElement(By.Name("password")).SendKeys(Password);
        driver.FindElement(By.Name("login")).Click();
 
        // 3. return FindFlights PageObject
        return new FindFlightsPage(driver);
    }
}

For the above to work, the FindFlightsPage PageObject must be created. In the code below, the FindFlightsPage PageObject is modeled to perform the following actions:

  1. Constructor verifies if page is valid
  2. A method is called to find a flight
  3. The Logout method is called and actions performed
  4. Return the LoginPage PageObject
class FindFlightsPage
{
    private IWebDriver driver;
 
    public FindFlightsPage(IWebDriver driver)
    { 
        this.driver = driver;
 
        // 1. verify if page is valid
        if (driver.Title != "Find a Flight: Mercury Tours:")
            throw new NoSuchWindowException("This is not the FindFlights page");
    }
 
    // 2. method/code-block to find a flight
    public void Do()
    {
        Console.WriteLine("In FindFlightsPage.Do [Checking for Flights]");
    }
 
    // returns LoginPage PageObject
    public LoginPage Logout()
    {
        // 3. log-off and return to LoginPage
        driver.FindElement(By.LinkText("SIGN-OFF")).Click();
        driver.FindElement(By.LinkText("Home")).Click();
 
        // 4. return the LoginPage object
        return new LoginPage(driver);
    }
}

Main execution entry point (the test code) below.

using OpenQA.Selenium;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.Support.UI;
using System;
 
class Program
{
    static void Main()
    {
        // instantiate FirefoxDriver and navigate to NewTours flight app
        IWebDriver driver = new FirefoxDriver();
 
        // navigate to NewTours app
        driver.Navigate().GoToUrl("http://newtours.demoaut.com");
 
        // instantiate LoginPage
        LoginPage Login = new LoginPage(driver);        
 
        // Login.Do returns the FindFlightsPage PageObject
        FindFlightsPage FindFlights = Login.Do("test", "test");
 
        if (FindFlights != null) 
        { 
            // perform steps to find a flight
            FindFlights.Do(); 
 
            // FindFlights.Logout returns LoginPage
            Login = FindFlights.Logout(); 
        }
 
        Console.ReadLine();
        driver.Quit();
    }
}

As we saw above, the Login.Do(args) method returns the FindFlights PageObject whereas the FindFlights.Logout() method returns the LoginPage PageObject. We saw that the public methods of each class represent the functionality offered by the page. The real-world application of this concept will certainly contain more actions against the UI and may return a large number of PageObjects.

PageFactory Class

The PageFactory Class is an extension to the PageObject design pattern. It is used to initialize the elements of the PageObject or instantiate the PageObject itself (not in C# though – see the Notes section below). Annotations for elements can also be created (and recommended) as the describing properties may not always be descriptive enough to tell one object from the other.

The InitElements method of PageFactory initializes the elements of the PageObject. The code below shows PageFactory usage in detail.

using OpenQA.Selenium;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.Support.PageObjects; // *
using System;
 
class LoginPage
{
    private IWebDriver driver;
 
    [FindsBy(How = How.Name)]
    private IWebElement userName; // How.NAME = userName
 
    [FindsBy(How = How.Name)]
    private IWebElement password; // How.NAME = password
 
    [FindsBy(How = How.Name)]
    private IWebElement login; // How.NAME = login
 
    public LoginPage(IWebDriver driver)
    {
        this.driver = driver;
    }
 
    public FindFlightsPage Do(string UserName, string Password)
    {    
        userName.SendKeys(UserName);
        password.SendKeys(Password);
        login.Click();
 
        PageFactory.InitElements(driver, (new FindFlightsPage(this.driver)));
        return new FindFlightsPage(driver);
    }
}
 
class Program
{
    static void Main()
    {
        IWebDriver driver = new FirefoxDriver();
        driver.Navigate().GoToUrl("http://newtours.demoaut.com");
 
        LoginPage Login = new LoginPage(driver);
 
        // initialize elements of the LoginPage class
        PageFactory.InitElements(driver, Login);
        // all elements in the 'WebElements' region are now alive!
        // FindElement or FindElements no longer required to locate elements
 
        FindFlightsPage FindFlights = Login.Do("User", "Pass");
        driver.Quit();
    }
}

The WebElements userName, password and login are not explicitly defined using property-value pairs. However, if you execute the code above, ‘UserName’ and ‘Password’ strings will be supplied to the relevant text fields. The WebElement variable names were enough to identify the controls.

In the above example, PageFactory.InitElements facilitates searching for elements marked with the FindsBy attribute by using the NAME property (notice: How = How.Name) to find the target element. There are other ways of object identification though and it is not required to use the object property as the variable name to identify it (as shown next).

The How parameter of FindsBy attribute is used for the object property (html tag). Using= then defines the corresponding value of the How= parameter.

Until now, NAME property has been directly used as the variable name. This is not very flexible approach, and I was only using it to provide a quick overview. Annotations are possible, too. The WebElements can be defined by any descriptive name. In the code below, userName, password and login have been modified to txtUserName, txtPassword and txtLogin respectively.

class LoginPage
{
    private IWebDriver driver;
 
    [FindsBy(How = How.XPath, Using = "//input[@type='text' and @name='userName']")]
    private IWebElement txtUserName;
 
    [FindsBy(How = How.Name, Using = "userName")]
    private IWebElement txtPassword;
 
    [FindsBy(How = How.Name, Using = "login")]
    private IWebElement btnLogin;
 
    public LoginPage(IWebDriver driver)
    {
        this.driver = driver;
    }
 
    public FindFlightsPage Do(string UserName, string Password)
    {
        txtUserName.SendKeys(UserName);
        txtPasswowrd.SendKeys(Password);
        btnLogin.Click();
 
        return new FindFlightsPage(driver);
    }
}

In summary, PageFactory class can be used to initialize elements of a Page class without having to use FindElement or FindElements. Annotations can be used to supply descriptive names of target objects in the AUT to improve code readability. There are however a few differences between C# and Java implementation – Java provides greater flexibility with PageFactory (see Notes).

CacheLookup

One last thing that remains with PageFactory is the CacheLookupAttribute. This is important because it can be used to instruct the InitElements method to cache the element once its located. In other words, any attribute marked [CacheLookup] will not be searched over and over again – this is especially useful for elements that are always going to be there (not always true for AJAX apps). So, we can search once and cache. All elements used in this article can be defined by this declarative tag as they are static and are always present. Our LoginPage class then becomes:

class LoginPage
{
    private IWebDriver driver;
 
    [FindsBy(How = How.Name)][CacheLookup]
    private IWebElement userName;
 
    [FindsBy(How = How.Name)][CacheLookup]
    private IWebElement password; 
 
    [FindsBy(How = How.Name)][CacheLookup]
    private IWebElement login; 
 
    public LoginPage(IWebDriver driver)
    {
        this.driver = driver;
    }
 
    public FindFlightsPage Do(string UserName, string Password)
    {
        userName.SendKeys(UserName);
        password.SendKeys(Password);
        login.Click();
 
        return new FindFlightsPage(driver);
    }
}

Notes – Differences between C# and Java Implementation

There are 3 discrepencies I found in the PageFactory documentation at Google Code between the Java and C# implementation.

The first discrepancy is that in Java, the PageFactory.InitElements can return the PageObject. In C#, this is not the case as InitElements returns void. View this image for a snapshot from Google Code documentation showing Java returning the PageObject.

For the 2nd discrepancy, let’s refer to the documentation:

… It [PageFactory] does this by first looking for an element with a matching ID attribute. If this fails, the PageFactory falls back to searching for an element by the value of its “name” attribute.

The above is not the case for C# – a NoSuchElementException is thrown. The PageFactory implentation for C# only searches for elements using the ID and does not locate the elements using the NAME property, unless How = How.Name is explicitly specified.

class LoginPage
{
    private IWebDriver driver;
 
    [FindsBy]
    private IWebElement userName;
 
    public LoginPage(IWebDriver driver) { this.driver = driver; }
 
    public void Do(string UserName, string Password)
    {
        // userName is the NAME property, not ID
        // element will not be located
        // will throw a NoSuchElementException
        userName.SendKeys(UserName); 
    }
}
 
class Program
{
    static void Main()
    {
        IWebDriver driver = new FirefoxDriver();
        driver.Navigate().GoToUrl("http://newtours.demoaut.com");
 
        LoginPage Login = new LoginPage(driver);
        PageFactory.InitElements(driver, Login);
        Login.Do("theUserName", "thePassword");
 
        driver.Quit();
    }
}

The 3rd discrepancy I found was in the initial part of the same document and noticed the same behavior when testing with Eclipse. The Java implementation can locate the element even without the FindsBy attribute – this isn’t the case for C#. View this image that shows this feature with Java. The below code fails to work for Gmail page for Passwd textBox since the [FindsBy] attribute is not specified.

class GmailLoginPage
{
    private IWebDriver driver;
 
    [FindsBy]
    private IWebElement Email;
 
    // element will not initialize because [FindsBy] attribute is missing
    private IWebElement Passwd;
 
    public GmailLoginPage(IWebDriver driver) { this.driver = driver; }
 
    public void Do(string UserName, string Password)
    {
        Email.SendKeys(UserName);
 
        // fail here - NullReferenceException
        Passwd.SendKeys(Password);
    }
}
 
class Program
{
    static void Main()
    {
        IWebDriver driver = new FirefoxDriver();
        driver.Navigate().GoToUrl("http://gmail.com");
 
        GmailLoginPage GmailLogin = new GmailLoginPage(driver);
        PageFactory.InitElements(driver, GmailLogin);
        GmailLogin.Do("theUserName", "thePassword");
 
        driver.Quit();
    }
}

NullReferenceException

Leave a Comment

25 comments… add one

  • nayab July 1, 2014, 1:46 am

    thanks alot …

  • Pravakar Panigrahi April 4, 2014, 7:45 pm

    Hi Anshu,

    Thanks for the article. It is very informative.

    I have a doubt. I am designing a selenium based framework for our application using PageObjectModel and Cucumber-JVM. However, in that application, I have a bunch of common functionalities across pages. So for each page, if I try to define the code, it increases my duplication. Is there a way in POM, where I can have a set of common functions and call from any page objects?

  • pepe February 2, 2014, 2:32 pm

    Not working for me. First example (not using PageFactory) is able to log in, but unable to click on sign-off link on the next page. No exception (like element could not be found) is thrown.
    Values of element before clicking:
    element.Selected=false
    element.TagName=”a”
    element.Text=”SIGN-OFF”

    Any idea?

  • Sriram Angajala June 7, 2013, 10:38 am

    Very good article and thanks for giving the difference btn Java and C# implenetations

  • Nishanth June 3, 2013, 6:15 am

    I have just started using Selenium web driver and C# for testing my application with Page factory approach.
    Good to go now.
    Good read.

  • v. October 4, 2012, 8:05 am

    There is another discrepancy:
    Selenium wiki page (http://code.google.com/p/selenium/wiki/PageFactory) says it’s possible to reduce verbosity saying:
    @FindBy(name = “q”), which is not the case in C#.

  • Piotr August 7, 2012, 3:23 am

    Hi Anshoo,

    Great article! But can you help me with one thing. How can I run this test? I’m using MsTest and I would like to use this Page Object but I can’t…

    • Anshoo Arora August 7, 2012, 10:55 am

      Piotr, I believe you mean Visual Studio Test Edition. To run the test from there, simply copy/paste the code into a Console Application project.

  • Vitali July 5, 2012, 3:04 am

    Why don’t you initialize the FindFlightsPage using the PageFactory in this example:

    class LoginPage
    {
        private IWebDriver driver;
     
        [FindsBy(How = How.Name)]
        private IWebElement userName; // How.NAME = userName
     
        [FindsBy(How = How.Name)]
        private IWebElement password; // How.NAME = password
     
        [FindsBy(How = How.Name)]
        private IWebElement login; // How.NAME = login
     
        public LoginPage(IWebDriver driver)
        {
            this.driver = driver;
        }
     
        public FindFlightsPage Do(string UserName, string Password)
        {    
            userName.SendKeys(UserName);
            password.SendKeys(Password);
            login.Click();
     
            return new FindFlightsPage(driver);
        }
    }
    

    It seems that if we’re going to use this pattern it makes sense to use it everywhere (if possible). So, ideally, it will look like:

    public FindFlightsPage Do(string UserName, string Password)
        {    
            userName.SendKeys(UserName);
            password.SendKeys(Password);
            login.Click();
     
            return PageFactory.InitElements(driver, FindFlightsPage);
        }
    

    Will it be correct?

    • karthik July 5, 2012, 3:20 am

      vitali that is correct. I had the same question so emailed anshoo abt it. he hasn’t updated the article yet it seems.

    • Vitali July 5, 2012, 4:29 am

      Thanks!

    • karthik July 5, 2012, 4:31 am

      no problem. seems the code was taken from the previous example but this change was not made…

    • Anshoo Arora August 25, 2012, 1:18 am

      Vitali / Karthik,

      You can’t use return PageFactory.InitElements(driver, FindFlightsPage); because with C#, until WebDriver version 2.21, InitElements returns void. The above would be true for Java though.

    • Karthik August 25, 2012, 9:40 am

      you are right Anshoo.. i just read the article again and its listed under #1 descripency. thanks.

    • cori January 2, 2013, 5:56 pm

      FWIW, even in 2.28, InitElements still returns void in the .Net bindings.

    • Anonymous April 9, 2013, 12:06 am

      hi reader,

      information give by poster.very useful

      by
      karthick

  • Karthik June 3, 2012, 7:32 am

    Anshoo

    Wanted to ask another question: should I stick to C# or learn Java?

    • Anshoo Arora June 9, 2012, 6:32 am

      You can learn either. I have been using .NET for quite sometime so choosing C# was an obvious choice. Also, I love the awesomeness of Visual Studio.

  • Susan June 3, 2012, 7:30 am

    Hey Anshoo,

    Can you please explain when CacheLookup should be used?

    • Anshoo Arora June 9, 2012, 6:31 am

      Susan: you can use CacheLookup in any case where you know the field will be repeated. If the use of fields in your app is once, then using CacheLookup is not necessary. Its just an added feature. Your scripts will still work without using it but in cases of repeated fields, it can help increase performance.

    • Susan June 10, 2012, 9:30 am

      Thanks Anshoo

  • Karthik Bhatnagar June 2, 2012, 10:28 am

    hi Anshoo

    i implemented your article at home. this is my first time using visual studio. it is very different from QTP and the C++ editors i have used. great article btw! very long :( and detailed.

    do you plan to release a framework for selenium as well?

    • Anshoo Arora June 9, 2012, 6:30 am

      LOL yeah this article was a bit long. I do not have any immediate plans to write any more frameworks. They consume a ton of time and I am working on several projects because of which, being very active here hasn’t been possible.

  • Paul Murray June 2, 2012, 6:11 am

    Anshoo,

    A very detailed and clear explanation of the PageObject pattern. I use Java and your observation is correct. Implementation of C# and other bindings seems to vary.

    -Paul


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值