Handling environments with similar page logic but different locators

1.3k views Asked by At

I have a test suite that currently runs across different development environments. Recently a complete rewrite of the application was done & deployed to a new environment.

The application looks & acts almost identically. Page logic is more or less the same. The big difference is the HTML rewrite has rendered my locators useless. I am unsure how to deal with the locators for this new environment while at the same time adhering to the page object model.

The page object model states that all page logic should be kept in the respective page object class. I am assuming that this includes locators as well.

Following this strategy would leave me with a bloated page object class full of duplicate locators. Is there any recommended best practices or clean solutions to combat this problem ?

The possible solutions i can think of are:

  1. Create separate locator files for each environment and remove them from the page object class.
  2. Create duplicate or similarly named locators in the current page object class
  3. Create seperate page objects for the new environment

Can anyody comment on whether or not these solutions sound ok ? Or offer any alternative suggestions ?

2

There are 2 answers

2
Grasshopper On

Would definitely move the locators outside the pageobject class into two different classes, one for the old locators and one for the new locators. Use public static final String for each locator. The problem you are going to have is that Java annotation values require constant expression, so you cannot use a method to send different locator to the FindBy. But you can use a ternary operator to create a constant expression.

Below I have added code which on the basis of the global flag clicks on a different button from a single WebElement but the locator is changed by an expression on the 'using' value of the FindBy annotation. You can set the value of the global flag on startup when you initialize a driver from a properties file.

This is what you will need to include in the FindBy and locators sent to findElement() - using= GlobalFlag.devEnv ? NewLocators.newLocxpath : OldLocators.newLocxpath. This will be a pain to copy paste everywhere.

You can try out the code as the website is publicly available.

class CartConstant {
    //Old locators
    public static final String cartxpath = "//span[.='Cart']";
}

class AccountConstant {
    //New locators
    public static final String accxpath = "//span[.='Account']";
}

class GlobalFlag {
    //Initialize this at the start
    public static final boolean devEnv = true;
}

public class ChangeAnnotation {
    //Change this in the code to include the choice
    @FindBy(how=How.XPATH, using=GlobalFlag.devEnv ? AccountConstant.accxpath : CartConstant.cartxpath)
    private WebElement butt;

    @Test
    public void demoSQA() throws InterruptedException {

        System.setProperty("webdriver.chrome.driver", "E:/Software Testing/Selenium/Jars/chromedriver.exe");
        ChromeOptions chop =  new ChromeOptions();
        chop.addArguments("test-type");
        chop.addArguments("start-maximized");
        WebDriver driver = new ChromeDriver(chop);

        driver.get("http://store.demoqa.com/products-page/product-category/imacs/");        
        Thread.sleep(3000);     
        PageFactory.initElements(driver, this);     
        butt.click();               
    }
}
0
Sanoj Indrasinghe On

This is how I managed to overcome this.

I kept different element property files for different environments.

Lets say environment A, B In my project I keep two property files named Elements_A.properties and Elements_B.properties These property files has all the page elements. If one element differs from the other, it will not be a problem since when running the script based on the environment you can refer the relevant property file in the script.

Lets say in A and B in HomePage there is a text box with different locators.

So in property file A we can mention the element as HomePage_Name_TextBox = id_NameInA "id_NameInA" is the locator value and "HomePage_Name_TextBox" is the string you going to use to refer that particular element.

Like wise in property file A we can mention the same element as HomePage_Name_TextBox = id_NameInB "id_NameInB" is the locator value and "HomePage_Name_TextBox" is the string you going to use to refer that particular element.

You can notice that both elements are given the same name (HomePage_Name_TextBox) and the locator vales are different.

In every page class I declared a Map and now you have several options to decide how you going to initialize elements for your page.

public class HomePage {

   Map<String, String> elementsMap = new HashMap<String, String>();

   //Option 1
   public HomePage(Map<String, String> elementMapObj) {
      elementsMap = elementMapObj;
   }

   //Option 2
   public HomePage() {
        Properties              prop        = new Properties();
        FileReader              reader;
        HashMap<String, String> propertyMap = new HashMap<String, String>();

        try {
            reader = new FileReader(new File("CommonConfig.properties"));
            prop.load(reader);
            for (String key : prop.stringPropertyNames()) 
            {
                String value        = prop.getProperty(key);
                propertyMap.put(key, value);
            }
        } catch (Exception e) {
            //System.out.println(e.toString());
        }

        try {
            reader = new FileReader(new File(propertyMap.get("ElementPropFilePath")));
            prop.load(reader);
            for (String key : prop.stringPropertyNames()) 
            {
                String value        = prop.getProperty(key);
                elementsMap.put(key, value);
            }
        } catch (Exception e) {
             //System.out.println(e.toString());
        }

   }
}
  1. You can declare a parameterised constructor and pass a map object where you have all the elements read before initializing an object of HomePage (in your main class you can read the matching element property file and pass the map containing all the elements to the constructor of Home Page)
  2. OR you can read a common property file in HomePage constructor itself where you can mention Elements property file path. ex: CommonConfig.properties this file can contains all the configuration details such as in which environment you are running the scripts, and common file paths to read (such as where the element file path) and this file will be read inside the constructor config file will look like this
    ElementPropFilePath = Resources/Elements_A.properties

When you want to run scripts in A, you can change the "ElementPropFilePath" in the common property file to "Resources/Elements_A.properties" before running. When you want to run scripts in B, you can change the "ElementPropFilePath" in the common property file to "Resources/Elements_B.properties" before running (This is the location of the file in the machine).

Simply to say, if you maintain property files containing all the elements for each environment, and provide that property details and fill the elementMap you have inside each and every page class then you will be able to refer that element with the common string you used for both environment (which is HomePage_Name_TextBox in this example)