Showing posts with label API. Show all posts
Showing posts with label API. Show all posts

Tuesday, May 22, 2012

WebDriver: The way to obtain element hidden under another (but static) one. Or how to fight with elements covering targets of your interaction

UPD: If you'd just like to scroll to the element that's somewhere at the bottom of the page see the solution here.

Assume you have the page with static bar (as in my case). This bar resides at the very bottom ov the page and is actualy stuck there whatever scroll you perform, so the user has the access to some feature permanently. Since such the UX has been introduced in the site I'm currently testing, some of my tests started to fail with the exception like this:
Element is not clickable at point (316, 494). Other element would receive the click
This was caused by the fact that the element does actually exist and is found by corresponding methods of webdriver, however it resides under that bar (that is static and alway at the bottom of the visible screen) element which actually received the click.

That's why we have two ways to resolve here. One - to remove the element which receives the click by javascript. However that is quite non-representative approach so we're NOT going to review it here. Another way is to scroll the page unil our required element becomes visible.

First you need the facility to execute javascript in your browser. The common practice is to have the method like this:
 public void runJS(String jsToRun){
  logger.debug("Running custom java script: " + jsToRun);
  ((JavascriptExecutor)getDriver()).executeScript(jsToRun);
 }

where getDriver() just returns your current WebDriver instance.
So, how to scroll the page in the browser. Again it can be done with certain javascript function execution. Such the script is very simple:
window.scrollBy(0,50);
This script means that we're scrolling the page to 50 px down (vertically) and not scrolling any px horizontally. Obviousely if we scroll 50 px down that does not mean that's enough. Probably we still have to proceed. So, how to design the iterative procedure meeting our expectation and going to be working fine. My suggestion is to introduce additional method. Let it be named scrollUntilVisibleAndClick. Check the source of the method first and then I'll explain how it works.

 public void scrollUntilVisibleAndClick(WebElement element, int maxScrollCout) throws YourOwnAutomationException{
  try{
   element.click();
   logger.debug("Element [" + element + "] is visble and successfully clicked.");
  }catch(WebDriverException e){
   logger.debug("Looks like the element is not clickable. Attempt to scroll down. MaxScrollCount = " + maxScrollCout);
   logger.debug(e.getMessage());
   if(maxScrollCout > 0){
    runJS("window.scrollBy(0,50);");
    scrollUntilVisibleAndClick(element, --maxScrollCout);
   }else{
    throw new YourOwnAutomationException("Couldn't scroll to the requested element");
   }
  }
 }

Let's now see how it works. To make this approach work you have to locate the element first. Once it is done you passes the element to the described method along with the parameter maxScrollcount which restricts the number of tries. The method attepts to click the element (doesn't matter if it is problematic from our standpoint). If it does, WebDriverException exception gets thrown. However we do not pass it on, but catch it and try to scroll down the page. Then we recursively call the same method with decreased "time-to-leave" parameter.
As the result of the method execution we can get the two finals. Either the maxScrollCount will reach zero and script will fail, or some new attempt after certain scroll will be successfull and the element will receive the click. 

Tuesday, March 27, 2012

How to install msi package using ant


Problem:
to install msi package in silent mode using ANT.

Constraints:
1. some prodcuts require system rebooting after installer finishes working. That is represented in some exit code (or a set of possible codes) meaning approximately the following: "It is all okay but still user actions required". So you never get 0 exit code meaning regulary success operation completion.
2. sometimes we need not just click Next> while installing the package but rather specify some custom specifications on installation phase. This is sure our case :) Assume we need to override some public property of msi package. Let it be named SOMEPROPERTY and the value we'd like to assign is SOMEVALUE

Solution:
Actually several solutions could be applied to solve this problem. The simplest one is to set failonerror attribute of ant execute task to false. Such the solution has quite big and quite obviouse disadvantage: your automated procedure will never know any problem has happened. But we would actually like to skip the only one exit code to consider it successfull.
To achieve that we should approach with batch scripting (remember we're acting under windows as we're trying to imstall msi package which is true windows stuff)

so lets introduce the following batch script

msiexec /i %1 /qn SOMEPROPERTY=%2

IF ERRORLEVEL 1642 GOTO ERR

IF ERRORLEVEL 1641 GOTO 1641

IF ERRORLEVEL 1 GOTO ERR


:1641
ECHO.Returned expected error code. System is getting to be rebooted...
EXIT 0

:ERR
ECHO.Uexpected error code: %ERRORLEVEL%
ECHO.failing..
EXIT %ERRORLEVEL%

So, what we expect from this script? First of all we expect it to install the package we propagate via script argument %1 and set the property SOMEPROPERTY to the value we specify in script argument %2. However we would like our script behave in certain way to address the constraint #1 (see above)

In the script we call msiexec tool with the keys /i (install the package) and /qn meaning silent non-gui installation. The lines below address the problem of exit codes. We should catch the code meaning "Its okay but the system's going to be rebooted now". This is encoded with the exit code sequence 1641. However the called process may return some other code which we should consider as not successful. This is achieved by "IF ERRORLEVEL" and "GOTO" batch language construction.

IF ERRORLEVEL N returns true if the returned exit code equals or grated than N. That is why we try to catch all errors higher than N (=1641)


IF ERRORLEVEL 1642 GOTO ERR


... and all errors lower than N

IF ERRORLEVEL 1 GOTO ERR
The order of instructions invocation makes us sure we'll get to :1641 label if only the expected installation state will take place
So now we just should call the batch from ANT passing the corresponding cmd arguments like this:
<exec executable="install-msi.bat" failonerror="true" >
 <arg value="&quot;PACKAGETOINSTALL.msi&quot;"/>
 <arg value="&quot;SOMEVALUE&quot;"/>
</exec> 

Sunday, March 04, 2012

When WebElement.click() doesn't work in Selenium2 WebDriver

Sometimes I face the problems when regular WebElement click() method does not work. For example I met such the problem when tried to locate the link under H2 like the following snippet:

<h2>
 <a href="something" title="something">Some text</a>
</h2>

The regular method of locating like driver.findElement(By.xpath("somexpath")).click() led to the exception saying some other element will get the click instead of one located by me. So I applied the following work-around:

 public void safeClick(WebElement element){
  Actions builder = new Actions(getDriver());
  builder.moveToElement(element).click().build().perform();
 }
where getDriver() just means your current WebDriver implementation. This made me capable to work-around the problem from one hand and even made click action closer to the actual action sequence performed by the user clicking anything on the page. 

Wednesday, February 15, 2012

Another view on how to simulate multi-user interaction in your Selenium 2 scenarios

I heard from a lot of people that the best pattern for keeping your web driver in the project is singleton. That looks doubtful. 
Since I got the requirement of multi-user interacting in my scenarios I used singleton pattern for webdriver and I had to use the following workflow for interaction
1. Log in with user A
2. Send message to user B
3. Log out with user A
4. Log in with user B
5. Handle the request from user A

That is not quite obvious and organic workflow. The much more convenient is to do something like this

1. Log in with user A
2. Send message to user B
3. Log in with user B
4. Handle the request from user A--- Finish the test or:
5. Log out with user A
6. Log out with user B

Add support for simultaneous work of several browsers

This cannot be implemented having singleton pattern as the one for your webdriver. This is my way of how to support several browsers simultaneously

First of all we need the factory to easily create new browser instances (aka new drivers)

 private static class WDFactory{
  public static WebDriver createWebDriver(Properties properties){
   String browser = properties.getProperty("browser");
   WebDriver driverToCreate;
   if(browser.toLowerCase().equals("*firefox")){
    driverToCreate = new FirefoxDriver();
   }else if(browser.toLowerCase().equals("*googlechrome")){
    driverToCreate = new ChromeDriver();
   }else{
    throw new UnsupportedOperationException("Not supported browser yet");
   }
   driverToCreate.get(properties.getProperty("domain"));
   driverToCreate.manage().timeouts().implicitlyWait(500, TimeUnit.MILLISECONDS);
   return driverToCreate;
  }
 }

Also we sure need the pool where we're going to store all the browser instances we're going to work with

public class WDPool {

 private HashMap<String, WebDriver> pool;
 
 public WDPool(){
  pool = new HashMap<String, WebDriver>();
 }
 
 public void closeDrivers(){
  for (String key: pool.keySet()){
   getDriver(key).close();
  }
 }
 
 public void addDriver(String id, WebDriver driver){
  ARTestCase.logger.debug("Adding driver with id [" + id + "]");
  driver.manage().deleteAllCookies();
  pool.put(id, driver);
 }
 
 public WebDriver getDriver(String id){
  ARTestCase.logger.debug("Returning driver with id [" + id + "]");
  return pool.get(id);
 }
}

Okay. Now you should consider your design. I have the class laying between the browser control engine (like selenium) and the business logic of the scenarios. So if you have one introduce the following method there. Otherwise introduce it into the class where your actual scenario is described

 public void pushDriver(String id){
  driver = WDFactory.createWebDriver(properties);
  stack.addDriver(id, driver);
 }

Here and after stack represents the instance of WDPool.

It will be used on the stage the new user is logging in.
and the facility to obtain the driver instance for the current user

 private WebDriver getDriver(){
  return stack.getDriver(ARTestCase.getCurrentUser());
 }

So now we're ready to instantiate the new browser each time the new user gets logged in. The browser instance is stored into the hashmap with the id holding the user's nick-name. There are only two things to do:

1. Change your log-in functionality of the scenario so that it requests pushDriver. In my case it has the following look:

    public void logIn(String username) throws ARAutomationException {
     getTestCase().getAdapter().pushDriver(username);
     getTestCase().setCurrentUser(username);
 // some actions to perform
    }

Where getTestCase().getAdapter() returns the interlayer of the test case we're currently executing holding all the stuff I'm writing here about.

2. Wherever  you used the pattern like driver.someMethod() you now should use getDriver().someMethod() described in two snippets above. That will make your framework to switch the browsers each time you either log in with new user or call

getTestCase().setCurrentUser(existingUserNameHoldingOneOfTheBrowserInstances);

Thursday, February 09, 2012

Selenium antipatterns or What is Selenium the worst for (or 'donts' for Selenium)

You probably know a lot of cases when selenium will help you a lot and will beat any competitor which means it is really the best solution on the market. However I'd like to share the set of the problems the selenium is better to put away from (selenium anti-patterns).

  • Prepare the base state. If you include the base state preparation procedures in your selenium-based framework, be ready you may get them failed which will cause the test fails as well. That will not mean your product functionality does not work in proper way. That will probably mean your base state generation procedure failed due to some instability which is certainly still the sort of seleniums feature combined with the specifics of http servers or network collisions :)
  • Run load testing. You definitely can simulate some load using the selenium just because you can simulate user's work flow with the help of it. Selenium drives the GUI browsers which consumes plenty of resources so even if you use HtmlUnit driver that won't help you much. More over the load generating framework supposes strong discipline of multi-threading beacuse if you have no such one, you may measure your own leaks aka the problems in your framework performance, not of your tested application's. Do not invent the wheel - use JMeter.
  • Accuracy testing. Once your web application delivers some analytics you probably will want to automatically check if the values in your tables are correct. That is bad practice for sure. Depending on your browser's locale the numbers may have different representation, so that is might cause the problems in string casting to number formats. The same is true for date and currency date types. Moreover you likely will have to maintain such the tests quite frequently due to HTML dynamic structure changes because in the most cases such the tables are drawn with the help of dedicated GUI framework which may vary the HTML representation slightly keeping GUI look unchanged for the user. And do not forget that being introducing accuracy assertions you will have to make sure your last assertion procedure matches the business logic of  the tested application
  • Third-party graphics testing like one provided by adobe flash, MS silverlight etc. Use dedicated products to test such the interactions
So those are the tings I'd not recommend to use selenium for. All other web testing stuff matches selenium capabilities pretty good.

Wednesday, August 24, 2011

P4 Java API. How to work with temporary clients.

Check new series of the articles. Review and user experience on test management systems. Functionality and usability.
-------------------

There is not a lot of information about perforce Java API. However you may find the examples of how to apply the common use cases in your code. I tried to touch surrounding cases and faced the problem that had been resolved with the only decompilation help.

So assume you do not want to use the existing client and you do want to use the temporary one. How to address such the requirement. Here is the code from me.


package ar.p4apihelpers;

import java.io.IOException;
import java.net.URISyntaxException;
import java.util.List;
import java.util.UUID;

import com.perforce.p4java.client.IClient;
import com.perforce.p4java.core.IMapEntry.EntryType;
import com.perforce.p4java.core.file.FileSpecBuilder;
import com.perforce.p4java.core.file.IFileSpec;
import com.perforce.p4java.exception.P4JavaException;
import com.perforce.p4java.impl.generic.client.ClientView;
import com.perforce.p4java.impl.generic.client.ClientView.ClientViewMapping;
import com.perforce.p4java.impl.mapbased.client.Client;
import com.perforce.p4java.server.IServer;
import com.perforce.p4java.server.IServerInfo;
import com.perforce.p4java.server.ServerFactory;

public class P4APISyncUp{
 public static void main(String[] args) throws URISyntaxException, IOException, P4JavaException {
  // Generating the files to sync-up
  String[] pathsUnderDepot = new String[]{
    "//depot/path1/file1.java#head",
    "//depot/path1/file2.java#head",
    "//depot/path1/path2/file1.java#head"
  };
  // Instantiating the server
  IServer p4Server = ServerFactory.getServer("p4java://p4.fakeserver.com:1666", null);
  p4Server.connect();
  // Authorizing
  p4Server.setUserName("secretname");
  p4Server.login("secretpassword");
  // Just check you are connected successfully
  IServerInfo serverInfo = p4Server.getServerInfo();
  System.out.println(serverInfo.getServerLicense());
  // Creating new client
  IClient tempClient = new Client();
  // Setting up the name and the root folder
  tempClient.setName("tempClient" + UUID.randomUUID().toString().replace("-", ""));
  tempClient.setRoot("c:/temp");
  tempClient.setServer(p4Server);
  // Setting the client as the current one for the server
  p4Server.setCurrentClient(tempClient);
  // Creating Client View entry
  ClientViewMapping tempMappingEntry = new ClientViewMapping();
  // Setting up the mapping properties
  tempMappingEntry.setLeft("//depot/...");
  tempMappingEntry.setRight("//" + tempClient.getName() + "/...");
  tempMappingEntry.setType(EntryType.INCLUDE);
  // Creating Client view
  ClientView tempClientView = new ClientView();
  // Attaching client view entry to client view
  tempClientView.addEntry(tempMappingEntry);
  tempClient.setClientView(tempClientView);
  // Registering the new client on the server
  System.out.println(p4Server.createClient(tempClient));
  // Surrounding the underlying block with try as we want some action
  // (namely client removing) to be performed in any way 
  try{
   // Forming the FileSpec collection to be synced-up
   List<ifilespec> fileSpecsSet = FileSpecBuilder.makeFileSpecList(pathsUnderDepot);
   // Syncing up the client
   tempClient.sync(FileSpecBuilder.getValidFileSpecs(fileSpecsSet), true, false, false, false);
  }finally{
   // Removing the temporary client from the server
   System.out.println(p4Server.deleteClient(tempClient.getName(), false));
  }
 }
}


Please point the attention to the lines #42 and #50 of this code. Forgetting to call #42 leads to the following exception on execution #64:
Exception in thread "main" java.lang.NullPointerException
at com.perforce.p4java.impl.mapbased.client.Client.sync(Client.java:496)
at com.perforce.p4java.impl.mapbased.client.Client.sync(Client.java:477)
at ar.p4apihelpers.P4APISyncUp.main

If you forget to call #50 you will get the exception like this:
Exception in thread "main" com.perforce.p4java.exception.RequestException: Error in client specification.
Error detected at line 10.
Null directory (//) not allowed in 'null//depot/...'.

at com.perforce.p4java.impl.mapbased.server.Server.handleErrorStr(Server.java:3834)
at com.perforce.p4java.impl.mapbased.server.Server.createClient(Server.java:2099)
at ar.p4apihelpers.P4APISyncUp.main

Friday, July 08, 2011

Custom way to look-up ajax elements in Selenium. Pattern.

Hi!

Looking through a lot of forums I noticed that some people face problems when use native selenium instructions to wait for element appearance on the page. That become pretty big problem when we're talking about dynamic HTML content. In my practice I met the close problems and the way I resolved them is the following pattern. This class controls the look-up procedures on the page. I designed to be capable to configure the duration and number of repeat lookups depending on the channel bandwidth and other factors. Also it has the method allowing us to wait while the required element disappears. That is pretty much common situation in web testing as well. So the class looks like this:


UPD: Here is the sophisticated and of-good-practice way of how to to that in Selenium 2.


package ar.example;

import java.util.HashMap;
import java.util.Properties;

import com.thoughtworks.selenium.DefaultSelenium;
import com.thoughtworks.selenium.Selenium;
import com.thoughtworks.selenium.SeleniumException;

public class SeleniumAdapter {
 
 private final int AWAITING_UNIT_LENGTH_MS = 300;
 private final long AWAITING_THRESHOLD_MS = 10000;
 private Selenium driver;
 

    public static String getRelativePath(String testedDomain, String alteredPath){
   return testedDomain + alteredPath;
 }
 
 public SeleniumAdapter(Properties properties) {
  driver = new DefaultSelenium(properties.getProperty("host"),  Integer.parseInt(properties.getProperty("port")), properties.getProperty("browser"), properties.getProperty("domain"));
  driver.start();
  driver.open(properties.getProperty("domain"));
  driver.setSpeed("500");
 }
 
 public void closeDriver(){
  driver.stop();
 }
  
 public boolean ifXPathExists(String xpathExpression){
  try {
   lookupXPathExists(xpathExpression);
  } catch (AutomationException e) {
   return false;
  }
  return true;
 }
  
 private WebElementEmulator repeatableLookupExists(String xpath) {
  long start = System.currentTimeMillis();
  while (true) {
   try{
    if(driver.isVisible("xpath=" + xpath)){
     TTestCase.logger.debug(testCase.wrapMessage("Found xPath [" + xpath + "]. Creating object.."));
     return new WebElementEmulator(xpath, driver);
    }
   }catch(SeleniumException se){
    try {
     Thread.sleep(AWAITING_UNIT_LENGTH_MS);
     TTestCase.logger.debug(testCase.wrapMessage("Searching xPath [" + xpath + "]. Retry.."));
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
   }
   if (System.currentTimeMillis()-start > AWAITING_THRESHOLD_MS){
    break;
   }
  }
  Statistics.totalLatency += System.currentTimeMillis() - start;
  return null;
 }

 private boolean repeatableLookupDoesNotExist(String xpath) {
  long start = System.currentTimeMillis();
  while (true) {
   boolean isPresent = driver.isElementPresent("xpath="+xpath);
   if (!isPresent) {
    Statistics.totalLatency += System.currentTimeMillis() - start;
    TTestCase.logger.debug(testCase.wrapMessage("Not found xPath [" + xpath + "]. Success.."));
    return true;
   }
   else{
    if(!driver.isVisible("xpath="+xpath)){
     TTestCase.logger.debug(testCase.wrapMessage("Found xPath [" + xpath + "]. However it is not visible. Success.."));
     return true;
    }
    try {
     Thread.sleep(AWAITING_UNIT_LENGTH_MS);
     TTestCase.logger.debug(testCase.wrapMessage("XPath [" + xpath + "] still can be found. Retry.."));
     if (System.currentTimeMillis()-start > AWAITING_THRESHOLD_MS){
      break;
     }
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
   }
  }
  Statistics.totalLatency += System.currentTimeMillis() - start;
  return false;
 }

 public WebElementEmulator lookupXPathExists(String xpath) throws AutomationException{
  WebElementEmulator handledElement = repeatableLookupExists(xpath);
  if (handledElement!=null) {
   if (handledElement.getQuantity()>1){
    throw new AutomationException("Several elements can be located using specified xpath [" 
      + xpath + "] You should concretize.");
   }
   return handledElement;
  } else {
   throw new AutomationException("Lookup for element ["
     + xpath + "]" + " failed after "
     + AWAITING_THRESHOLD_MS + " ms awaiting.");
   
  }
 }
 
 public boolean lookupXPathDoesNotExist(String xpath) throws AutomationException {
  if (repeatableLookupDoesNotExist(xpath)) {
   return true;
  }else{
   throw new AutomationException("The xpath ["+xpath+"] is still observed after "+AWAITING_THRESHOLD_MS + " ms awaiting.");
  }
 }
}

That's pretty much it.

Tuesday, July 05, 2011

How to get instant updates of configuration file of your testing framework from svn repository

Hi.

Here is one more pattern intended for operating with the latest versions of configuration files. That was originated from the following. The project under testing behaves pretty much agile so to hold such agile things up to date we want to extract the relevant configs from some centralized storage that is easy to maintain.
To support such functionality I prepared small utility class to communicate with svn repository. The pattern looks like this:

P.S. - you should use svnkit in order to keep the code working.

package test.svn.client;

import java.io.File;

import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.wc.SVNClientManager;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc.SVNUpdateClient;
import org.tmatesoft.svn.core.wc.SVNWCUtil;

public class Communicator {
 
 static final String syncDest = "/Storage";
 
 SVNUpdateClient uc;
 SVNURL repositoryURL;
 
 public Communicator(String user, String password, String repository) throws SVNException{
  repositoryURL = SVNURL.parseURIEncoded(repository);
  DAVRepositoryFactory.setup();
  SVNRepository svnRepository = SVNRepositoryFactoryImpl.create(repositoryURL);
  ISVNAuthenticationManager manager = SVNWCUtil.createDefaultAuthenticationManager(user, password);
  svnRepository.setAuthenticationManager(manager);
  SVNClientManager cm = SVNClientManager.newInstance();
  cm.setAuthenticationManager(manager);
  uc = cm.getUpdateClient();
 }
 
 public void syncUp(String segment) throws SVNException{
  uc.doCheckout(repositoryURL.appendPath(segment, true), new File(syncDest), SVNRevision.UNDEFINED, SVNRevision.HEAD, SVNDepth.INFINITY, true);
  System.out.println("Done");
 }
 
 public String getResourceFolder(){
  return syncDest;
 }
 
 /**
  * Just to check how it really works :)
  * @param arg
  * @throws SVNException
  */
 public static void main(String[] arg) throws SVNException{
  Communicator driver = new Communicator("secret", "secret", "https://my.svnserver.fake/trunk");
  driver.syncUp("/resources/config");
 }
}

Then we should write the support for fetching the certain values from the resource files

package com.somefake.pkg;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.InvalidPropertiesFormatException;
import java.util.Properties;

import com.somefake.pkg.TAE;

public class ResourceProvider {
 
 private static final Communicator resourceActualyzer = new Communicator("testuser", "testpassword", "https://fakesvnserver.com/trunk");
 
 private static Locale currentLocale = Locale.RU;
 
 private static final HashMap<Locale, Properties> map = new HashMap<Locale, Properties>();
 
 
 static{
  TipsteryTestCase.logger.info("Initializing SVN client...");
  try {
   HashSet<String> localeRu = new HashSet<String>();
   HashSet<String> localeEn = new HashSet<String>();
   localeRu.add(resourceActualyzer.getResourceFolder() + "/Resources_ru.xml");
   localeRu.add(resourceActualyzer.getResourceFolder() + "/jsResources_ru.xml");
   localeEn.add(resourceActualyzer.getResourceFolder() + "/Resources.xml");
   localeEn.add(resourceActualyzer.getResourceFolder() + "/jsResources.xml");
   resourceActualyzer.syncUp("/resources");
   map.put(Locale.RU, loadProperties(localeRu));
   map.put(Locale.EN, loadProperties(localeEn));
  } catch (Exception e) {
   resourceActualyzer.setSuccessful(false, e);
   TipsteryTestCase.logger.error("Error while loading resources.", e);
  }
 }
 
 private static Properties loadProperties(HashSet<String> sourceFiles) throws InvalidPropertiesFormatException, FileNotFoundException, IOException{
  Properties stagingProperties = new Properties();
  for(String sourceFile: sourceFiles){
   stagingProperties.loadFromXML(new FileInputStream(sourceFile));
  }
  return stagingProperties;
 }
 
 public static String getProperty(String key) throws TAE{
  if(!resourceActualyzer.isSuccessful()){
   String message = "Looks like you had problems with " 
     + "resource files syncronization\n"
     + "Check if you set valid credentials and specified valid resource file names";
   TipsteryTestCase.error(message);
   resourceActualyzer.getException().printStackTrace();
   throw new TAE(message);
  }
  return map.get(currentLocale).getProperty(key);
 }
 
 public static void setLocale(Locale newLocale){
  currentLocale = newLocale;
 }
 
 /**
  * Just to check if it works
  * @param arg
  * @throws TAE 
  */
 public static void main(String[] arg) throws TAE{
  System.out.println(ResourceProvider.getProperty("some.property"));
  ResourceProvider.setLocale(Locale.EN);
  System.out.println(ResourceProvider.getProperty("some.property"));
 }
}

Locale here means just the enum, and do not curse me for the approach to the errors handling.

Wednesday, June 22, 2011

How to hide some data in log4j log you consider to be secured

Once we get the requirement saying the customer does not want some data gets transfered to the log file since it looks secure for them. We even should have the capability of choosing whether to show such data in logs. The quick way to address the requirement is to override one method in PatternLayout class. This will look like:

import org.apache.log4j.PatternLayout;
import org.apache.log4j.spi.LoggingEvent;

public class SecureLayout extends PatternLayout{

                static String pattern;
                
                public static void setPattern(String pattern){
                                SecureLayout.pattern = pattern;
                }

                @Override
                public String format(LoggingEvent event) {
                                String string = super.format(event);
                                return pattern == null ?  string : string.replaceAll(pattern, "NotForYourEyes");
                }

}

Such the way will allow us to set which data we consider to be secured from any place of the code. So you should only place the class under the classpath and use the following construction in log4j.xml

    <appender name="FILE" class="org.apache.log4j.RollingFileAppender">
        <param name="File" value="logfile.log"/>
        <param name="Append" value="true"/>
        <param name="MaxFileSize" value="1000KB"/>
        <param name="MaxBackupIndex" value="1000"/>
        <layout class="some.package.SecureLayout">
            <param name="ConversionPattern" value="%-5p [%d{ISO8601}] - %m%n"/>
        </layout>
    </appender>

However such solution of described engineering problem has some drawback. It means we are not capable to hide the data in exception description as exceptions are handled in another way. However that problem can be solved by introducing the changes in Exception class.

Friday, June 17, 2011

How to check if sub-string belongs to the string in batch script

Check new series of the articles. Review and user experience on test management systems. Functionality and usability.
-------------------

Recently I was in need to prepare the batch file taking the sting and some pattern as the parameters. They required that file to check if that string contains the certain substring and basing on the result execute either one functionality or another. So the solution is below:

rem first parameter should hold the main string
rem second one should hold the substring to be found

SETLOCAL ENABLEDELAYEDEXPANSION 
set "ol=%1"
set handledString=!ol:%2=!
echo."%handledString%"
IF %1 == %handledString% GOTO wayone
IF %1 NEQ %handledString% GOTO waytwo

:wayone
echo."substring not found"
goto end

:waytwo
echo."substring found"
goto end

:end

UPD: Here is more sophisticated way how to do this.

Tuesday, June 07, 2011

Ant custom task in application to the real purpose. Part Two.

Hi!

Here here I started the set of posts about how to apply the customized tasks in Ant to address some real requirement.

So there is a lot of information about how to create custom task in ant. But we're talking about some production requirements. As you may discover in previous post we're trying to create the framework allowing to roll back the changes which have been made anywhere in the ant script, and it doesn't matter whether they were made within one target or the changes were distributed among the several ones. So, to achieve that I decided to extend the native ant tasks performing file operations and I started from Replace task. However I still needed the infrastructure to support this functionality. So the whole workflow looks now like the following:

- We take care of reseting the storage holding the information about which file is mapped to which backup.
- Then we perform safe operations with our files
- Finally we take care of cleaning up the backups being sure all has finished successfully

The particular ANT script will look like this
<project name="Safe replace" default="do-all" basedir=".">

 <taskdef name="safereplace" classname="com.enkata.ant.tasks.SafeReplaceTask">
  <classpath>
   <pathelement path="C:/temp/safereplace.jar" />
  </classpath>
 </taskdef>

 <taskdef name="initstorage" classname="com.enkata.ant.tasks.InitializeStorageTask">  <classpath>
   <pathelement path="C:/temp/safereplace.jar" />
  </classpath>
 </taskdef>

 <taskdef name="cleartemporary" classname="com.enkata.ant.tasks.ClearTempFilesTask">  <classpath>
   <pathelement path="C:/temp/safereplace.jar" />
  </classpath>
 </taskdef>

 <target name="do-all">
  <initstorage/>
  <antcall target="valid"/>
  <antcall target="notvalid"/>
  <cleartemporary/>
 </target>

 <target name="valid">
  <safereplace dir="c:/test_safereplace" token="The" value="HRENAR">
   <include name="**/*.txt" />
  </safereplace>
 </target>

 <target name="notvalid">
  <safereplace file="abc" token="0" value="0">
  </safereplace>
 </target>

</project>

So here are several important points we should pay attention for. First of all it is seen that we use three custom tasks here. Each task has the path to the jar it is represented in. Also we have the general target where we take care of the items I have specified above and call the some valid target (not producing any errors) and some not valid one (that should always fail)

As most of us already know to create the custom task we have to extend ANT native class Task or any class that is the extension of it. The simple custom task that I use is the following:

public class InitializeStorageTask extends Task {

 @Override
 public void execute() throws BuildException {
  File storage = new File (SafeTasksUtils.BACKUP_STORAGE_FILE_NAME);
  if (storage.exists()){
   FileUtils.delete(storage);
  }
 }

}

I'm not including the default overridden methods not to waste the space. Anyway the IDE will remind you. So we should to implement our own execute() method to make it work.

In the third part I will describe the core of the example means how the extended Replace task works and then in the fourth part the step-by-step implementation workflow will be described using Eclipse IDE.

Thank you!

Monday, May 30, 2011

That awful WebDriver or Selenium 2 disadvantages

Actually I disappointed. Selenium 2 seems to be not ready for enterprise testing. I had to move my test engine back to Selenium RC. Certainly its a pity I have to renounce the object-oriented model, but I still keep the design (actually this has nothing to do with real OO design) with the help of set of adapters to make me get back to Selenium 2 since it will become somehow  finalized.
The features of Selenium 2 that I liked a lot and I feel the most sorry for:
- selecting only visible elements by xPath
- capability of retrieving the list of the objects representing html elements
- controlling of key sending. The capability of sending the symbols one-by-one

Monday, May 16, 2011

Ant custom task in application to the real purpose. Part One.

I found that a lot of ant tasks examples face the difficulties when they are got handled by the newbies. So, I decided to provide the detailed instruction on how to implement your own task and the example will be based on the real requirement that I had to create the solution for few days ago.
The problem we faced in our project can be described in the following way:
1. We have some set of files in different folders and of different formats (.properties, .xml, .somethingelse)
2. We need to customize some  values there
3. We use ANT to process those files
4. ANT script is divided in several targets
5. Once some target gets failed the previous ones which have been completed cause changes in some sub-set initial set
6. We require the simple way to re-run the scenario so we won't have the "incremental customization". I mean if we added the string to the file and some further customization failed, we should not have two identical strings after the second run after something went wrong

So the way I have chosen is to determine which ANT standard (or well-known packages) tasks are used  in our customization scripts. The actual set appeared to be not so huge. Like: replace, copy, xmltask. I prototyped the solution for the replace task. Some extended class called SafeReplaceTask was created. It implemented the following functionality:

1. To determine the set of files to be handled in the task
2. To back-up the files
3. To save the mapping of original files - back-up files (the most simple way is to serialize HashMap object)
4. To call the original execute() method of the Class we're extending
5. On fail to restore all the back-ups using the information from de-serialized object

That was the problem I had to solve, and the technical details of this solution you will find in further posts.

Thank you!

-- PART II --

Tuesday, February 15, 2011

Selenium 2 + WebDriver. Test drive and first impression

Several months ago I started implementation of automation framework intended for certain social network automated testing. The first time I set the requirement so the framework should be run against Win OS to save the time on setting up the automation context. However I faced the problem with starting selenium server over min Win7 machine so I had to explore the new ways of "click-engine" application. That way became Selenium 2 powered by WebDriver.

The obvious advantages are:
  • Local usage, so less configuration effort required and risks are estimated (may also be considered as disadvantage)
  • Automatic recognition of element type
  • Backward compatibility with Selenium
Disadvantages:
  • Yet not perfect functionality. Even using selenium emulation.
  • Buggy integration with the latest Microsoft products
  • API differs from the classic selenium one so it is pretty much hard to port the Selenium 2 tests to Selenium
Yeah.. So actually I had to move the framework to Linux platform after all as WebDriver stuck on sending long strings to the textboxes. However Linux framework had no such issues at all and it still works fine there.

Thank you!

UPD here

Saturday, February 12, 2011

Curse on record/playback approach

If you test your homepage or the web site of your rock-band you may use record-playback and feel happiness. But once you're moving to something serious you feel only the need to get help, right? :) The obvious disadvantages of record-playback are:

  • The resulting script requires corrections anyway
  • You have to be sure that you have no dynamic locators in your html. Like dynamic IDs.
  • You are restricted in using flow control of your scenario
  • You are restricted in driving your test with the custom data
  • You are restricted in verifying the result of your scenarios
  • You are restricted in embedding your test suites into some more complicated solutions
So what to say. Just learn the programming languages and switch to testing tools' API. That is less comfortable but gives really wide capabilities and flexibility.