So far, we have seen how the Selenium WebDriver API needs locator information to find the elements on the page. When a large suite of tests is created, a lot of locator information is duplicated in the test code. It becomes difficult to manage locator details when the number of tests increases. If any changes happen in the element locator, we need to find all the tests that use this locator and update these tests. This becomes a maintenance nightmare.
One way to overcome this problem is to use page objects and create a repository of pages as reusable classes.
There is another way to overcome this problem—by using an object map. An object or a UI map is a mechanism that stores all the locators for a test suite in one place for easy modification when identifiers or paths to GUI elements change in the application under test. The test script then uses the object map to locate the elements to be tested.
Object maps help in making test script management much easier. When a locator needs to be edited, there is a central location for easily finding that object, rather than having to search through the test script code. Also, it allows changing the identifier in a single place, rather than having to make the change in multiple places within a test script, or for that matter, in multiple test scripts. The object map files can also be version-controlled.
In this recipe, we will implement the ObjectMap
class to maintain locator details obtained from the tests.
Set up a new Java project for the ObjectMap
class. This class will be used by Selenium tests as an extension to read the ObjectMap
file.
Let's implement an object map to store the locators used in a test with the following steps:
objectmap.properties
. We will add the locators in a key/value pair. The part before the equal-to sign (=
) will be the key or the logical name of the element, and the part after will be the locator details, in the following format:[logical_name]=[locator_type]>[locator_value]
The following code is an example of the object map for the BMI calculator page:
height_field=name>heightCMS weight_field=id>weightKg calculate_button=id>Calculate bmi_field=id>bmi
ObjectMap
class to read the property file and provide the locator information to the test:import java.io.FileInputStream; import java.io.IOException; import java.util.Properties; public class ObjectMap { Properties properties; public ObjectMap(String mapFile) { properties = new Properties(); try { FileInputStream in = new FileInputStream(mapFile); properties.load(in); in.close(); }catch (IOException e) { System.out.println(e.getMessage()); } } }
ObjectMap
class which will read the locator details from the properties file and create and return the locator using the By
class, as shown in following code:public By getLocator(String logicalElementName) throws Exception { //Read value using the logical name as Key String locator = properties.getProperty(logicalElementName); //Split the value which contains locator type and locator value String locatorType = locator.split(">")[0]; String locatorValue = locator.split(">")[1]; //Return a instance of By class based on type of locator if(locatorType.toLowerCase().equals("id")) return By.id(locatorValue); else if(locatorType.toLowerCase().equals("name")) return By.name(locatorValue); else if((locatorType.toLowerCase().equals("classname")) || (locatorType.toLowerCase().equals("class"))) return By.className(locatorValue); else if((locatorType.toLowerCase().equals("tagname")) || (locatorType.toLowerCase().equals("tag"))) return By.className(locatorValue); else if((locatorType.toLowerCase().equals("linktext")) || (locatorType.toLowerCase().equals("link"))) return By.linkText(locatorValue); else if(locatorType.toLowerCase().equals("partiallinktext")) return By.partialLinkText(locatorValue); else if((locatorType.toLowerCase().equals("cssselector")) || (locatorType.toLowerCase().equals("css"))) return By.cssSelector(locatorValue); else if(locatorType.toLowerCase().equals("xpath")) return By.xpath(locatorValue); else throw new Exception("Locator type '" + locatorType + "' not defined!!"); }
package com.secookbook.examples.chapter09; import org.openqa.selenium.WebDriver; import org.openqa.selenium.firefox.FirefoxDriver; import org.openqa.selenium.WebElement; import org.junit.*; import static org.junit.Assert.assertEquals; public class ObjectMapTest { private WebDriver driver; private ObjectMap map; @Before public void setUp() throws Exception { // Create a new instance of the Firefox driver driver = new FirefoxDriver(); driver.get("http:// cookbook.seleniumacademy.com/bmicalculator.html"); } @Test public void testBmiCalculator() { // Get the Object Map File map = new ObjectMap("src/test/resources/objectmap/objectmap.properties"); // Get the Height element WebElement height = driver.findElement(map.getLocator("height_field")); ; height.sendKeys("181"); // Get the Weight element WebElement weight = driver.findElement(map.getLocator("weight_field")); weight.sendKeys("80"); // Click on the Calculate button WebElement calculateButton = driver.findElement(map.getLocator("calculate_button")); calculateButton.click(); // Verify the Bmi WebElement bmi = driver.findElement(map.getLocator("bmi_field")); assertEquals("24.4", bmi.getAttribute("value")); } @After public void tearDown() throws Exception { // Close the browser driver.quit(); } }
First, we created a Java properties file with the key/value pair, storing a logical name for an element and locator value. The properties files are flat text files, and the java.util.Properties
namespace provides the Properties
class to access a property file:
properties = new Properties();
By passing a logical name or key to the getProperty()
method of the Properties
class, we can retrieve a value from the pair.
The getLocator()
method uses the value returned by the getProperty()
method and returns a matching By
locator method, along with the value, to the test.
In the test, we created an instance of an object map and then passed the location of the property file, as shown in the following code:
map = new ObjectMap("src/test/resources/objectmap/objectmap.properties");
We passed the locator value to the findElement()
method by passing the logical name or key of the element to the getLocator()
method of the ObjectMap
class, as follows:
WebElement height = driver.findElement(map.getLocator("height_field"));
We can have a single object map file to store all the locators and can use the same locators in multiple tests.
Object maps can also be created in XML files. The following code is an example of an XML-based object map:
<elements> <element name="HeightField" locator_type="name" locator_value="heightCMS"/> <element name="WeightField" locator_type="id" locator_value="weightKg"/> <element name="CalculateButton" locator_type="xpath" locator_value="//input[@value='Calculate']"/> <element name="BmiField" locator_type="id" locator_value="bmi"/> <element name="BmiCategoryField" locator_type="css" locator_value="#bmi_category"/> </elements>
The following code is the C# implementation of the getLocator()
method:
public By GetLocator(string locatorName) { var element = from elements in _root.Elements("element") where elements.Attributes("name").First().Value == locatorName select elements; try { string locatorType = element.Attributes("locator_type").First().Value.ToString(); string locatorValue = element.Attributes("locator_value").First().Value.ToString(); switch (locatorType.ToLower()) { case "id": return By.Id(locatorValue); case "name": return By.Name(locatorValue); case "classname": return By.ClassName(locatorValue); case "linktext": return By.LinkText(locatorValue); case "partiallinktext": return By.PartialLinkText(locatorValue); case "css": return By.CssSelector(locatorValue); case "xpath": return By.XPath(locatorValue); case "tagname": return By.TagName(locatorValue); default: throw new Exception("Locator Type '" + locatorType + "' not supported!!"); } } catch (Exception) { throw new Exception("Failed to generate locator for '" + locatorName + "'"); } }
A similar approach can be taken with other Selenium WebDriver language bindings, such as Java, Python, or Ruby.
3.21.43.26