文档章节

Selenium PageObjects and PageFactory

红焖鲤鱼
 红焖鲤鱼
发布于 2016/03/24 15:38
字数 1545
阅读 159
收藏 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/

共有 人打赏支持
红焖鲤鱼
粉丝 113
博文 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
1K
2

没有更多内容

加载失败,请刷新页面

加载更多

linux-scp 远程拷贝报错原因

刚拿到一台重装后的服务器,远程ssh都正常,但是一scp拷贝东西就报错: 本地确定是有scp命令的,而且如果是本地没有scp不会报后面那句lost connection,因此就是远程没有scp这个命令。因此在...

linuxprobe16
6分钟前
0
0
OSChina 周六乱弹 —— 谁小时候没当过熊孩子呀

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @小小编辑:推荐歌曲《行尸走肉》- amazarashi 《行尸走肉》- amazarashi 手机党少年们想听歌,请使劲儿戳(这里) @神话 :周五了,周末干啥...

小小编辑
31分钟前
18
1
docker部署springboot项目

安装docker 菜鸟教程 springboot项目 maven依赖 <?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001......

yimingkeji
今天
14
0
1: Cordova 配置WebView可以打开外部链接

一、问题:在使用Cordova生成的Android App中默认情况下WebView中的超链接,如果不是相对链接,会默认使用浏览器打开。 如果想用默认webview打开 解决方案:修改config.xml文件添加链接配置节...

wecloudnet
今天
1
0
Beetl介绍以及集成SpringBoot2.0 ---《Beetl视频课程》(1)

目的:引导阅读官方文档 目标:实现一个自己的博客 一、Beetl介绍 Beetl目前版本是2.9.3,相对于其他java模板引擎,具有功能齐全,语法直观,性能超高,以及编写的模板容易维护等特点。使得开发...

Gavin-King
今天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部