WebDriver PageObjects & Large Amounts of Locators

2019-05-22 12:30发布

Over the past year I built a very nice WebDriver framework for my group. Pretty standard fare: Page classes typically encapsulate the full functionality of a page in on our platform (controls as By objects, methods as needed) & extend a base class with some global methods, multiple pages are invoked within JUnit test classes, typical assertion methods, yada yada. Nothing extravagant-very by the books but very functional and flexible.

Recently, however, I have been asked to use the framework to automate a large number of pages with forms that can hold 100 or even more inputs, selects, options, etc. This is causing me some serious consternation. Moreover, the id's of many of the custom form controls change from environment to environment (luckily they remain static per environment after the initial pushes). Anyway...

Lets look at a typical PageObject structure that I use every day:

/** Constructor*/
public someConstructor(WebDriver driver){
   super(driver, pageTitle);
}

/**Locators*/
By searchBoxLocator = By.id("phSearchInput");
By searchBoxClearLocator = By.id("phSearchClearButton");
By searchButtonLocator = By.cssSelector("input[value='Search']");


/**Some Methods*/
public void selectFromTabBar(String tabName){
   driver.findElement(By.linkText(tabName)).click();
}

public static void enterInputValue(WebDriver driver, By by, String val){

   List<WebElement> elements = null;
   elements = driver.findElements(by);

   if(!elements.isEmpty()){
     driver.findElement(by)).sendKeys(val);     
   }else{
     throw new NoSuchElementException("Message");
   }
}

Like I mentioned, nothing exotic here, but now to my question to everyone. Take a situation where I won't have the traditional 5-15 By's per PageObject, but require possibly over 100 for 10's of Pages. Throw in the complication that they will also have unique values per environment, which is totally out of everyones hands and cannot be reconciled. So what is the appropriate approach here...Do I bite the bullet and hard code 100" By's per page, do I externalize all By's to, say, an Excel doc and pull everything in via, I duno, JXL or whatever, or do I hard code By's that I know will be static and throw everything in an external file?

At the end of the day I know there is no right answer, but I'm just curious as to others approach to this. Right now I've externalized the By's for the pages that have so many form elements and define controls that wont change within the PageObject still, but it feels clunky. Perhaps I am striving for too much elegance, but any thoughts would be really fantastic.

1条回答
祖国的老花朵
2楼-- · 2019-05-22 13:04

This is primarily opinion based but interesting question.

My suggestion would be modularize your page.

For example, you have a page with 100 locators, but surely they can't be 100 similar textboxes to let users to fill out. (If so, probably the solution here is to re-design your UI first)

If this 100 elements belong to different components, say 20 elements in top toolbar, 20 elements in list view, 20 elements in a form and 20 in bottom toolbar. Then you might want to create different classes for each of the components.

For example, here is an ordinary page object.

public class FooPage {

    private IWebDriver driver;

    public FooPage(IWebDriver driver) {
         this.driver = driver;
    }

    public IWebElement Element1 {
         get { return driver.FindElement(By.CssSelector(".something1")); }
    }

    // ...
    public IWebElement Element100 {
         get { return driver.FindElement(By.CssSelector(".something100")); }
    }

    public void AddItem(){
    }

    public void ClearList(){
    }
}

You might separate those elements into different components, for example, FooPageTopToolbar,FooPageList, etc.

public class FooPageTopToolbar {

    private IWebDriver driver;

    public FooPageTopToolbar(IWebDriver driver) {
         this.driver = driver;
    }

    // ...
    public IWebElement ToolbarElement10 {
         get { return driver.FindElement(By.CssSelector(".something100")); }
    }

    public void AddItem(){
    }
}

public class FooPageList {

    private IWebDriver driver;

    public FooPageList(IWebDriver driver) {
         this.driver = driver;
    }

    // ...
    public IWebElement ListElement10 {
         get { return driver.FindElement(By.CssSelector(".something100")); }
    }

    public void ClearList(){
    }
}

Now your FooPage will look like

public class FooPage {

    private IWebDriver driver;

    public FooPage(IWebDriver driver) {
         this.driver = driver;
    }

    public FooPageTopToolbar {
         get { return new FooPageTopToolbar(driver); }
    }

    // other components as well
    public FooPageList {
         get { return new FooPageList(driver); }
    }
}

Note this is just a demo, you might also consider get inheritance, interfaces and abstraction involved to build your page objects. For example, if TopToolbar is shared by many other pages, then you only need one class for all. If different pages have similar but slightly different toolbars, then create an abstract toolbar class of some kind.

Also, you mentioned you have different locators for different environment, the best solution in my opinion would be to add element identifiers into your source code (by "identifiers", I mean any identifiable parts, maybe HTML id, but most commonly unique class for testing purpose). In this case, you will only need one set of testing locator that are not frequently changed by UI developers.

查看更多
登录 后发表回答