Automated testing: working with static resources

Static resources are those kinds of resources that do not change in the process of testing or working with an application. They include the names and attributes of the page elements, the text inside the elements on the page, the statuses of documents, etc.

An important task when writing automated tests and frameworks is to organize work with static resources, namely the way to access and read the necessary data.

There are two main options for organizing work with static data, which have their own pros and cons:

  1. Writing data inside the code – hardcode (see Using Constants in Java)
  2. Disadvantages:
    • when data changes, it will be necessary to re-assemble tests again
    • all static resources are loaded and stored in memory and cannot be freed.
  3. Benefits:
    • data access is maximally simplified
  4. Getting static values from the code (in the database, files, etc.)
  5. Disadvantages:
    • reading from the database or files takes some time and can significantly slow down the execution of tests
    • a database file or table can be locked in the case of simultaneous use of the same resources.
  6. Benefits:
    • possibility of structuring static resources in a database or file system
    • possibility of changing static resources without subsequent changes in the code and reassembling tests
    • resources from the file can be read, used by the test, deleted from memory, and then read again if necessary

There are enough supporters of this approach and they have good reasons to follow it.

With a sufficient number of developments in using the file system, as a container for storing static resources, we would like to share the following method of working with them based on Java Properties files.

A few words about it.

Properties is a plain text file, the data in which is stored as key = value Example: LoginPage.properties

—————-
field.username.id = UserName
field.password.id = Password
button.login.id = LoginButton
—————-

An example of Java code that reads the LoginPage.properties file and obtains the necessary information:

—————-
// initialization LoginPage.properties
Properties properties = new Properties();
File propertyFile = new File(LoginPage.properties);
properties.load(new FileReader(propertyFile));
// data reading
String userNameID = properties.getProperty(“field.username.id”);
String passwordID = properties.getProperty(“field.password.id”);
String loginButtonID = properties.getProperty(“button.login.id”);
—————-

Of course, we will not be able to get rid of the hardcode by 100%; however, in the latter case, the keys to access the data are strictly prescribed, not the data itself. What is no longer a hardcode, but let’s say “parameterization”.

Now, on the basis of this, we will describe how the test framework is built based on the example described in the previous article PageObjects pattern + Selenium (Java)

The basic principles of working with static resources in the application is that for each page object a separate property file is created with the identical name stored in the project directory resources. Thus, we need to create 3 files and fill them with static information:

LoginPage.properties

—————-
field.username.locator = id
field.username.arg = UserName
field.password.locator = id
field.password.arg = Password
button.login.locator = id
button.login.arg = LoginButton
—————-

HomePage.properties

—————-
text.username.locator = id
text.username.arg = userName
link.logout.locator = id
link.logout.arg = LogoutLink
—————-

ErrorLoginPage.properties

—————-
text.errormessage.locator = id
text.errormessage.arg = ErrorMessage
link.backtologin.locator = id
link.backtologin.arg = BackLink
—————-

Having created all the necessary resources, let’s move on to writing Java code that will load, store and read data. To store the loaded resources, create a class DataStorage. It will store the Map data (Map propertiesMap), the key in which will be the name of the page object, and the value the Properties object with the list of static data.

DataStorage.java

public class DataStorage {
  private Map propertiesMap;

  private DataStorage() {
      this.propertiesMap = new HashMap();
  }

  public static DataStorage getInstance() {
      return new DataStorage();
  }

  public void setProperty(String key, Properties properties) {
      propertiesMap.put(key, properties);
  }

  public Properties getProperty(String key) {
      return propertiesMap.get(key);
  }

  public boolean exists(String key) {
      return propertiesMap.get(key) != null;
  }
}

Add a link to it in the working context, as well as several service methods for working with it (bold in the class listing):

Context.java

public class Context {
public static final String BROWSER_IE = “*iexplore”;
public static final String BROWSER_FF = “*firefox”;
public static final String BROWSER_CH = “*chrome”;
private static final String RESOURCES_PATH = “resources/${NAME}.properties”;

public static String siteUrl;
private static Context context;
private DataStorage dataStorage;
private Selenium selenium;
private SeleniumServer seleniumServer;

private Context() {
this.setDataStorage(DataStorage.getInstance());
}

public static void initInstance(String browserType, String siteURL) {
context = new Context();
siteUrl = siteURL;
context.setSelenium(new DefaultSelenium(“localhost”, 4444, browserType, siteURL));
context.start();
}

public static Context getInstance() {
if (context == null) {
throw new IllegalStateException(“Context is not initialized”);
}
return context;
}

public Selenium getSelenium() {
if (selenium != null) {
return selenium;
}
throw new IllegalStateException(“WebBrowser is not initialized”);
}

public void start() {
try {
seleniumServer = new SeleniumServer();
seleniumServer.start();
} catch (Exception e) {
e.printStackTrace();
}
selenium.start();
}

public void close() {
selenium.close();
selenium.stop();
seleniumServer.stop();
}

public String getSiteUrl() {
return siteUrl;
}

public void setSelenium(Selenium selenium) {
this.selenium = selenium;
}

public String getResourcesPath(String name) {
return RESOURCES_PATH.replaceAll(“\\$\\{NAME\\}”, name);
}

private void setDataStorage(DataStorage dataStorage) {
this.dataStorage = dataStorage;
}

public DataStorage getDataStorage() {
return dataStorage;
}
}

Now add to the Page class resource initialization and service methods for loading and reading data from files. The most interesting of these will be the initProperties () method, which, analyzing the class hierarchy of the page object, loads the required property file.

Page.java

public abstract class Page {
  private Context context;
  private String currentPage;
  private Properties properties;
  
  protected Page(String pageUrl) {
      this.currentPage = pageUrl;
      setContext(Context.getInstance());
      initProperties();
init();
      parsePage();
  }

  private void initProperties() {
String className = getClass().getSimpleName();
if (!getContext().getDataStorage().exists(className)) {
this.properties = new Properties();
List superClasses = ClassUtils.getAllSuperclasses(getClass());
File file = null;
for (int i = superClasses.size() – 2; i >= 0 ; i–) {
Class aClass = (Class) superClasses.get(i);
file = new File(getResourcesPath(aClass.getSimpleName()));
if (getContext().getDataStorage().getProperty(aClass.getSimpleName())== null) {
if (file.exists()) {
putAllProperties(file);
updateStogare(aClass.getSimpleName(), getProperties());
}
} else {
putAllProperties(getContext().getDataStorage().getProperty(aClass.getSimpleName()));
}
}
file = new File(getResourcesPath(className));
putAllProperties(file);
updateStogare(this, getProperties());
} else {
setProperties(getContext().getDataStorage().getProperty(getClass().getSimpleName()));
}
}
  
  protected abstract void init();
  protected abstract void parsePage();

  private void setContext(Context instance) {
      this.context = instance;
  }

public Context getContext() {
return context;
}

public String getCurrentPage() {
return context.getSiteUrl() + this.currentPage;
}

protected Selenium getSelenium() {
return context.getSelenium();
}

private String getResourcesPath(String name) {
return getContext().getResourcesPath(name);
}

private Properties getProperties() {
return properties;
}

private void setProperties(Properties properties) {
this.properties = properties;
}

protected String getProperty(String key) {
return properties.getProperty(key);
}

private void putAllProperties(File proertiesFile) {
try {
this.properties.load(new FileReader(proertiesFile));
} catch (IOException e) {
e.printStackTrace();
}
}

private void putAllProperties(Properties properties) {
this.properties.putAll(properties);
}

private void updateStogare(Object parentKeyObj, Properties properties) {
updateStogare(parentKeyObj.getClass().getSimpleName(), properties);
}

private void updateStogare(String className, Properties properties) {
getContext().getDataStorage().setProperty(className, (Properties)properties.clone());
}

protected String buildLocator(String type, String arg) {
// Service method to create Selenium locator
// type and argument by two parameters
return type + “=” + arg;
}

  // ….
  // service methods…
  // ….
}

Replacing the concrete values of the static resources in the objects of the pages by reading their values from the Properties objects we get their following implementation:

LoginPage.java

public class LoginPage extends Page {
  public static final String PAGE_URL = “http://www.testlogin.com/login.html”;

   protected LoginPage() {
       super(PAGE_URL);
   }

   public static LoginPage openLoginPage() {
       LoginPage loginPage = new LoginPage();
       loginPage.getSelenium().open(PAGE_URL);
       return loginPage;
   }

   private void setUserName(String userName) {
       // the code to fill in the Username field
       getSelenium().type(buildLocator(getProperty(“field.username.locator”),getProperty(“field.username.arg”)), userName);
}

private void setPassword(String password) {
// the code to fill in the Password field
getSelenium().type(buildLocator(getProperty(“field.password.locator”), getProperty(“field.password.arg”)), password);
}

private void pushLoginButton() {
// code to click on the Login button
getSelenium().click(buildLocator(getProperty(“button.login.locator”), getProperty(“button.login.arg”)));
}

protected void parsePage() {
// Parsing page elements

// Filling the necessary variables with data from the page
}

protected void init() {
// Page Initialization
// Check for correct download
if(!getSelenium().getLocation().equals(PAGE_URL)) {
throw new IllegalStateException(“Invalid page is opened”);
}
}

private void loginAs(String userName, String password) {
setUserName(userName);
setPassword(password);
pushLoginButton();
}

public HomePage login(String userName, String password) {
loginAs(userName, password);
return new HomePage();
}

public ErrorLoginPage loginInvalid(String userName, String password) {
loginAs(userName, password);
return new ErrorLoginPage();
   }
}

HomePage.java

public class HomePage extends Page {
   public static final String PAGE_URL = “http://www.testlogin.com/home.html”;
   private String loggedinUserName;

   protected HomePage() {
       super(PAGE_URL);
   }

   protected void init() {
// Page Initialization
   }

   protected void parsePage() {
// Parsing page elements
       this.loggedinUserName = getSelenium().getText(buildLocator(getProperty(“text.username.locator”), getProperty(“text.username.id”)));
  }

   public String getLoggedinUserName() {
       return loggedinUserName;
   }

   public LoginPage logout() {
       getSelenium().click(buildLocator(getProperty(“link.logout.locator”), getProperty(“link.logout.id”)));
      return new LoginPage();
   }
}

ErrorLoginPage.java

public class ErrorLoginPage extends Page {
   public static final String PAGE_URL = “http://www.testlogin.com/loginError.html”;
   private String errorMessage;

   protected ErrorLoginPage() {
       super(PAGE_URL);
   }

   protected void init() {
// Page Initialization
   }

   protected void parsePage() {
       this.errorMessage = getSelenium().getText(buildLocator(getProperty(“text.errormessage.locator”), getProperty(“text.errormessage.id”)));
  }

   public String getErrorMessage() {
       return this.errorMessage;
   }

   public LoginPage backToLoginPage() {
       getSelenium().click(buildLocator(getProperty(“link.backtologin.locator”), getProperty(“link.backtologin.id”)));
      return new LoginPage();
   }
}

 

Please note that in the current implementation all static resources are moved to the files. And now if some data is changed, for example, the id “UserName” changes to “user_name” and you need to change the locator type to search from “id” to “xpath”, then you just need to replace the value in the file , leaving the framework code unchanged:

field.username.locator = xpath
field.username.arg = //input[@id=’user_name’]

The approach described above when working with static resources has been used for two years now, and it means that it has proved its right to exist in the field. We hope that after a careful reading, someone also wants to realize something similar, or absolutely the same.

 

Leave a Reply

Your email address will not be published. Required fields are marked *