文档章节

Selenium PageObjects and PageFactory

红焖鲤鱼
 红焖鲤鱼
发布于 2016/03/24 15:38
字数 1545
阅读 166
收藏 0

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 theFindFlights.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 userNamepassword 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 FindsByattribute 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 useFindElement 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();
    }}


本文转载自:http://relevantcodes.com/pageobjects-and-pagefactory-design-patterns-in-selenium/

共有 人打赏支持
红焖鲤鱼
粉丝 114
博文 40
码字总数 29610
作品 0
浦东
QA/测试工程师
私信 提问
webdirver中pagefactory和pageObjects的区别

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

jeffsui
2014/03/14
0
0
Android UI自动化测试框架--zinc30

Zinc30是一个强大的Android UI自动化测试框架,支持建立健壮、可维护的黑盒测试用例。RD或者QA能够基于场景设计功能级和系统级测试。Zinc30符合Webdriver的 API规范,以更好地面向对象编程的...

keenz
2012/12/03
2.6K
0
WebDriver对象管理之PageObject与PageFactory对比

在之前的自动化框架搭建文章中有提到过对象管理(或元素管理),而WebDriver给我们提供了两种对象管理思路PageObject与PageFactory,今天我就对比下两种方式的异同~ 关于PageObject 将一个页...

测试开发栈
2017/06/08
0
0
Top 15 UI Test Automation Best Practices You Should Follow

In the past several years, I have heard many engineers from various projects complain about the stability and the reliability of UI automation tests. But are they really so unst......

Yuri Bushnev
2017/12/14
0
0
Android UI测试框架zinc30

Zinc30是一个强大的Android UI自动化测试框架,支持建立健壮、可维护的黑盒测试用例。RD或者QA能够基于场景设计功能级和系统级测试。Zinc30符合Webdriver的 API规范,以更好地面向对象编程的...

keenz
2012/12/03
2.2K
2

没有更多内容

加载失败,请刷新页面

加载更多

OSChina 周三乱弹 —— 风扇写着先生请自爱

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @蚂蚁哈哈哈 :分享陈奕迅的单曲《落花流水》 《落花流水》- 陈奕迅 手机党少年们想听歌,请使劲儿戳(这里) @车谷 :我发现每天上班都好困 ...

小小编辑
今天
315
6
centos7重置密码、单用户模式、救援模式、ls命令、chmod命令

在工作当中如果我们错误的配置了文件使服务器不能正常启动或者忘记密码不能登录系统,如何解决这些问题呢?重装系统是可以实现的,但是往往不能轻易重装系统的,下面用忘记密码作为例子讲解如...

李超小牛子
今天
4
0
Python如何开发桌面应用程序?Python基础教程,第十三讲,图形界面

当使用桌面应用程序的时候,有没有那么一瞬间,想学习一下桌面应用程序开发?行业内专业的桌面应用程序开发一般是C++,C#来做,Java开发的也有,但是比较少。本节课会介绍Python的GUI(图形用...

程序员补给栈
今天
12
0
kafka在的使用

一、基本概念 介绍 Kafka是一个分布式的、可分区的、可复制的消息系统。它提供了普通消息系统的功能,但具有自己独特的设计。 这个独特的设计是什么样的呢? 首先让我们看几个基本的消息系统...

狼王黄师傅
今天
3
0
Android JNI总结

0x01 JNI介绍 JNI是Java Native Interface的缩写,JNI不是Android专有的东西,它是从Java继承而来,但是在Android中,JNI的作用和重要性大大增强。 JNI在Android中起着连接Java和C/C++层的作...

天王盖地虎626
昨天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部