4

New to C#, started learning in order to do test automation using Selenium C# with MS Visual Studio. Currently a manual tester.

I've been building a testing framework using TDD and the POM pattern. I'm still in the very early stages. I have about a dozen classes so far in 2 projects, the one project is dedicated to webdrivers with a single class called WebDriverFactory, the other project is broken into several folders where I have page classes, test classes, and a new folder I'm working on where there will be universal elements.

The application I'm automating has SO many elements which are on many different pages using the exact same selector and functioning in the exact same way. Also, there are many forms, selection boxes, inputs, etc., which use different locators, but behave in the exact same way - so I started to make a universal locators class and a universal methods class. The Locators class might be divided into several classes depending on arrangement on the pages or type of element. The Methods class will have methods which take the iwebelement locators and a variables to enter as arguments.

Here's an test method which currently runs and passes. I'm in the middle of refactoring it, using it as a guinea pig. As you can see, it uses a method from the Batches class to search for a provider, and then verifies that the correct provider is pulled using a method from the universal search methods class. Once I finish refactoring, it won't use the Batches class's method to search for a provider, but will also use a universal search class's method: because that style of search field is used a dozen times on each of a dozen pages for different search parameters... and there are several different styles of search fields as well...

 [TestMethod]
        [Description("Search by a single Provider and verify results match")]
        public void SingleProviderSearch()
        {
            var searchResultsActions = new SearchResultsActions(Driver);
            var searchResultsLocators = new SearchResultsLocators(Driver);

            var batches = new Batches(Driver);
            string provider = "Geisinger Medical Center";
            batches.GoTo();
            batches.EnterProvider(provider);
            batches.Search();
            //var batchesResults = new BatchesResults(Driver);
            //Assert.IsTrue(batchesResults.VerifyAllProvidersOfResults(provider), "The Provider column didn't match the searched-for Provider");
            Assert.IsTrue(searchResultsActions.VerifyTableColumn(provider, searchResultsLocators.ProviderColumn), "The Provider column didn't match the searched-for Provider");
            // Confirm Fails
            //Assert.IsTrue(batchesResults.VerifyAllProvidersOfResults("test"), "The Provider column didn't match the searched-for Provider");

How can I stick to the POM, when such a huge portion of the functionality is universal across the application? Don't get me wrong - there's unique stuff too.

One of the ideas I had was, which you see the beginnings of above, was to create a universal locators class which inherits from my base application class, then create a universal methods class which inherits from the universal locators class, then create the specific pages classes which inherit from the universal methods class.

Does that sound like a decent idea which will adhere to good design principles? It's the best compromise I've been able to come up with to balance DRY and POM.

So the question is: since I don't have programming experience, is the design pattern i've illustrated above, using a combination of POM but with a couple huge universal classes, feasible? Or is there another design pattern which would match the application better?

In order not to get this question closed - i'm not asking for opinions on that is the best, but simple practical ideas and solutions to overcome this design problem. Thanks!

5 Answers 5

2

What you are encountering is basically developers re-using components.

We are applying the same principle in our framework, so as to have only one extension method per type of component.

Some (pseudo) code to illustrate how we handle things:

Testmethod

PageX.EnterForm(data);
PageX.Save();

PageX page object class

public void EnterForm(data)
{
    // the variables below are simply WebElements initialize via PageFactory
    NameField.SetText(data);
    AddressField.SetText(data);
    PhoneField.SetText(data);
    BirthDateField.SetDate(data); // notice specific method for Date component
    StateField.SelectValue(data); // same for (custom) dropdowns
}

Framework method It's not really part of our Selenium framework, but rather a separate package consisting of extension methods on WebElement. You can think of it as directives. The code below is of course heavily abstracted, but if you have good framework methods you can do all kinds of things here with child elements, waiting, ...

If the component ever changes its behavior, you only need to update this method!

public void SetText(this IWebElement textbox, data)
{
    // Here you can have all kinds of logic used for all textboxes, for example:
    textbox.ShouldBeEditable();
    textbox.SendKeys();
    textbox.WaitForAutocomplete();
    textbox.ClickFirstResult();
    textbox.WaitForAutocompleteInvisible();
}
2
  • Hi FDM, thanks very much! So essentially, you've abstracted common reused components into a separate package and can call them on page object classes with the using statement? But you keep it all within the same solution, correct? I like this idea a lot.
    – SnowBranch
    Apr 16, 2018 at 16:56
  • That is the idea. They are different solutions. There is one solution having the framework and component extensions for two different types of applications (3 packages in total). Then we have our test suites and page objects, each in the solution of the respective project.
    – FDM
    Apr 16, 2018 at 19:19
2

For this kind of situation where your developers are reusing components and/or there are a lot of common components in your application, you might want to consider using pattern objects instead of page objects.

You might have a pattern object of textField with a findBy property that lets you uniquely identify the field by doing something like

myTextField = textField.findBy("id", "Search");

It won't matter what page is involved - you use the normal methods to set search text and run the search, then you have a pattern object that you might call searchResults which could be a table that has specific headings and expected text if no results are found.

For more information there's a good article on TechBeacon covering the principle. (I have no affiliation: I read the article and was impressed)

2
  • Hi Kate, thanks! If I'm understanding, say you have a selector box where as you type, you can select enums from the search suggestions. Then say you have 5 of these selectors, where each one has a different locator and is used for different enums, but functions the same way. With a pattern object, you just write one method, which takes the locator and value as arguments? This seems like a perfect idea to blend with FDM's idea of a separate package for the common components.
    – SnowBranch
    Apr 16, 2018 at 17:05
  • @SnowBranch - yes, that is the principle. It certainly saves a lot of extra complexity.
    – Kate Paulk
    Apr 16, 2018 at 18:27
1

I have been using a "Fluent POM" pattern as shown in my answer here.

App is a wrapper for the WebDriver. A Page class is a property in the app. And elements are properties in the Page. In addition to having properties they also have methods that pertain to that level. Like OpenUrl() is in the App class and FillOutForm() is in the login page class. I also add a lot of logging in my wrapper classes. I have one for NUnit's asserts, too.

class App {
    HomePage HomePage { return new HomePage(); }
}

class BasePage {
    IWebElement H1PageHeader { return new IWebElement(By.TagName("h1")); }
}

class HomePage : BasePage {
    HomePage HomePage(){ /* empty constructor */ }
    IWebElement MyElement { return new IWebElement(By.Id("foo")); }
}

class Test {
    [Test]
    Example01() {
        App.HomePage.H1PageHeader.WaitForElement();
        App.HomePage.MyElement.Click();
    }
}
3
  • Hi kirbycope, thanks for the answer. I like ability of not having to instantiate the HomePage class when you want to use MyElement in the test method. However, I'm not sure how this relates to common reused elements. This seems rather like a convenient way to use the regular POM design.
    – SnowBranch
    Apr 16, 2018 at 17:25
  • Use inheritance. A page can inherit from a base page with rather generic or global selectors. As a terrible example h1 only really needs to be defined once.
    – kirbycope
    Apr 17, 2018 at 1:27
  • Updated my answer to show inheriting a base object.
    – kirbycope
    Apr 17, 2018 at 1:34
0

Seems like you have acute case of Object Patterns Poisoning. :-)

You don't have much experience, so IMHO you should focus less on patterns and factories creating new classes, and more about parametrizable components. You should refactor your code only after you have 3-4-5 different use cases. Don't try to make your classes generic too soon. Such early generalization invites over-engineering. Composition is easier than inheritance, and inheritance is easier than factory pattern.

There are other design principles beyond DRY: KISS, YAGNI. If you want to read more about patterns and anti-patterns, you might want to look at the first original wiki: http://wiki.c2.com/ - still around. Also, you might consider looking around at https://softwareengineering.stackexchange.com/ - they have plenty of debates about system design.

1
  • Hi Peter, thanks for the suggestion. Although in my case, I've already come across enough of these similar usages to see a clear pattern, and I've explored the site using xpath in developer tools to verify they take the same locators. I'm afraid if I don't incorporate a scalable design early on, it will be harder and harder to refactor down the line.
    – SnowBranch
    Apr 16, 2018 at 17:09
0

Disclaimer: I am not an expert. I am still learning this craft.

Just like you I was also testing an application which had multiple functionality universal in nature. I also tried to implement POM + PageFactory but the framework was becoming too complex.

Instead of using @FindBy I store the locators as By Class object. I also changed few class and package structure to make my framework more.

  1. Modular
  2. Easy to Understand
  3. Easy to Maintain

Below Framework is just for reference:

I divided the project mainly in 4 parts(package).

  1. BaseFramework (Containing the GlobalVariables and Wrapper Class)
  2. PageWise Class (in this example LoginPage)
  3. Utilities (ex: YMLWrapper and ExcelWrapper used to Read and Write files)
  4. Test Cases (containing the various test cases)

Folder Structure:

FrameWork_Structure

Hierarchy of the classes in the BaseFramework package is as below: So the BaseClass ends up having all the methods of the above inherited classes.

Hierarchy of the BaseClass

GlobalVariable Class holds variables like

  • WebDriver driver;
  • WebDriverWait wait; etc.

SeleniumWrapper Class holds methods like

  • openBrowser(String browserName);
  • click(By locator);
  • enterText(By locator, String text); etc.

DatabaseWrapper Class holds methods to initial database connection.

  • initDataBaseConnection(String databaseURL , String databaseUserName , String databasePassword)

BaseClass does not have any methods just acts as the class containing all the inherited methods in one place.

LoginPage Class code will look something like this.

public class LoginPage {

    By byUsernameTextBoxID = By.id("id_of_the_Username_Textbox");
    By byPasswordTextBoxID = By.id("id_of_the_Password_Textbox");
    By bySignInButtonName  = By.id("Name_of_the_Username_Textbox");
    
    BaseClass loginPageObject = new BaseClass();

    
    public void openBrowserAndLoginIntoWebsite() {
        //write the code to open Browser
        //write the code to Login into the application
    }
    
}

TestLoginFunctionality Class code will look something like this:

public class TestLoginFunctionality extends LoginPage {

        @Test
        public void testLoginFunctionality() {
            openBrowserAndLoginIntoWebsite();
            //Assert something on the page(ex: copyright statement, logo) 
        }
        
}

This is not the perfect framework but I hope it gives you some idea.

You can add more Wrapper to deal with REST API Testing, or Read and Write .XML, .properties files, etc.

Any kind of feedback is most welcomed. :-)

2
  • Wow, I think this post really sums up the other 3 answers: You have FDM's separate framework, Kate Paulk's object patterns in the Selenium Wrapper, and kirbycope's Fluent POM structure within the baseframework. However, I'm confused about something: In the LoginPage class, you locate several elements by id, but in the SeleniumWrapper, you have the enterText(By locator, String text) method. Wouldn't you need to have all the locators in the SeleniumWrapper class as well in order to use that method? I.e. I don't think enterText(byUsernameTextBoxID, "username") would work in the test class.
    – SnowBranch
    Apr 16, 2018 at 17:37
  • 1. No, you don't really need to have the all the locators in the SeleniumWrapper. As it would defeat the purpose of modularity and easy of maintainence. 2. Yes, you will not be able to call enterText() method directly in the Test Class, But in most of the cases you won't have to. what I prefer is to club the actions like click, entering text,etc should be performed in the LoginPage Class and should be masked (abstracted) in their own methods. Even if you still want to call enterText() in Test Class, then one way would be to make the SeleniumWrapper methods and Locotors static in nature.
    – Alok
    Apr 17, 2018 at 0:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.