Showing posts with label java. Show all posts
Showing posts with label java. Show all posts

Thursday, December 11, 2014

Tip: add element to JScrollPane with not specifying it in constructor

Tip: If you created JScrollPane object and didn't set the scrollable client as the constructor parameter, you can add it later by invoking setViewportView method of JScrollPane.

Thursday, December 04, 2014

Caption text disappears on JButton if Action is assigned

While implementing yet another tool helping to automate project processes I faced an issue that took some amount of time to resolve. That might be quite obviouse for those how's been dealing with s
Swing for a long time but I spent a day to find the solution out.

So I had the code to create JButton with caption like

JButton jbPerformSomeJob = new JButton("Perform Some Job");

After I had completed building my UI I ran the app and having no logic assigned to my buttons I got the result I expected to get. I had some UI controls on my JFrame including that my button with the expected caption.

So after that I added some Action to my button as anonymous class object.

jbPerformSomeJob.setAction(new AbstractAction() {

@Override
public void actionPerformed(ActionEvent e) {
performCustomJob();
}
});

After I started the application again I couldn't see the caption on the button any more.

So the solution is not obvious enough as to me. It turned out that the name of the action overrides the button caption so to have my caption back I had to define separate named class like:

class PerformSomeJobAction extends AbstractAction{

public GenerateReportAction(String text) {
super(text);
}

@Override
public void actionPerformed(ActionEvent e) {
performCustomJob();
}

}

and use the following construction for my JButton

jbPerformSomeJob.setAction(new PerformSomeJobAction("Perform Some Job"));

Tuesday, November 20, 2012

How to prepare your Eclipse IDE for web development

Objective

Once you're going to start developing some java-based web-applications or just static web-sites you need to perform some simple steps to configure your development environment. Basically you need three things to start developing:
  1. Eclise IDE (J2EE edition)
  2. Tomcat server (Servlet container and servlet specification implementation)
  3. JDK to support basic java features along with runtime environment
Lets go step-by-step here. At the moment of this post publishing the latest version of Eclipse IDE was Juno release.  Download it from here so that you've got the version suitable for your operating system. I'm using Windows 7 64bit so all further statements will be related to it. 
That will be great to create some sandbox so that you can use it as isolated disk space to have some practice. After you're pretty much familiar with the configuration you can repeat the steps with your own paths. Say, create the following folder where all the magic is going to happen: c:/sandbox. Create Projects subfolder to keep Eclipse project there.

Configure your Eclipse and Tomcat instances

Extract the eclipse archive to c:/sandbox. So you now have your IDE under c:/sandbox/Eclipse. Start it up (lets not talk about performance tuning of your IDE - this is not our topic here). Set up the workspace to Now you have IDE working. Unfortunately that's not enough to start coding. Close welcome screen.
You won't be able to code a piece of java code until you have JDK installed. Follow this link to download J2SE 6 for your operating system (make sure you're registered as oracle user as you will have to input your oracle credentials).

Install the distribution to C:\sandbox\JDK_SE6. Switch to Eclipse.
  • Go to Window\Preferences
  • In the preferences tree go to Java\Installed JREs
  • Click [Add] button. Choose Standard VM in new dialog and click Next
  • Specify folder C:\sandbox\JDK_SE6 for JRE home. Once you do that all other fields are pre-filled automatically
  • Now you may remove all other VMs from the list if they are.
  • Click OK to close preferences dialog
You now can write and execute Java code. However we're going to create web application, aren't we? Hence we need to install servlet implementation. That's also good to have a web server. Fortunately all those stuff is combined under Apache Tomcat server. Lets use the latest version at the moment. You may take it from here. Unpack the content of the archive to our sandbox so that we have Apache Tomcat server under C:\sandbox\apache-tomcat-7.0.32.

Lets bind eclipse to that apache instance.
  • Go to Window\Preferences again. In preferences tree go to Server\Runtime Environments.
  • Click [Add...] button
  • In "New Server Runtime Environment" dialog choose Apache Tomcat v7.0. Check "Create a new local server" cehck-box. Click Next
  • Click [Browse...] button and choose the folder C:\sandbox\apache-tomcat-7.0.32
  • Under JRE you may choose either default JRE of workbench or JDK_SE6. They are all the same. However lets choose the second option to be more defined.
  • Click Finish and then OK to close preferences window.

Creating a template project.

Lets now create some project to get aware how to create the project each time you want to start trying new create something new.
  • In Eclipse press Ctrl+N so that "New" dialog is opened. 
  • Choose "Dynamic Web Project" and click Next
  • There type some project name (note that the project path is attached automatically to our sandbox)
  • Choose Apache Tomcat v7.0 as target runtime
  • Set dynamic web module version to 3.0
  • Click Next until it gets disabled :)
  • There check the check-box "Generate web.xml deployment descriptor"
  • Finally click [Finish]
So.. Congratulations! You've now got web project configured. To make sure it's working lets do the following.
  • In your Project Explorer expand WebContent folder
  • Right-click the folder and choose New->Html file
  • Name it index.html and click okay
  • Within <body>...</body> enclosure  add piece of html-code like <h1>It really works!</h1>. Save the changes
  • Right click the project root in project explorer. Select "Run as -> Run on Server"
  • You should now see your html page which was deployed to the web-server and now loaded from it
That's all

Thursday, July 19, 2012

Selenium 2 Java client: How to resolve apostrophes problem in xPath

It's pretty much common problem how to handle apostrophes if you have to use them in attribute values or any text which you somehow  use to narrow the search scope.

Assume we need to find the element having name attribute containing pattern "Someone's". Like

<file name="Someone's name"/>

The obvious way is to use the xPath like this: //file[contains(@name,'Someone's')]. However such the xPath won't be parsed properly as we've just broken the language syntax by placing three sequential single-quotes.

The good approach to resolve such the problem is to use the concat construction so that the example above will look like this:

//file[contains(@name,concat('Someone',"'",'s'))]

Here we split the string by three parts and enclosed the single-quote with double-quotes. This is the good solution, however we still need the approach to build such the constructions whatever the number of single-quotes have the place and whatever combination with regular text they have as well

Fortunately Java provides convenient facility to work with the strings and here I suggest the function helping to handle such the problem in your Selenium scenario.

 String resolveAprostophes(String item){
  if(!item.contains("'")){
   return "'" + item + "'";
  }
  StringBuilder finalString = new StringBuilder();
  finalString.append("concat('");
  finalString.append(item.replace("'", "',\"'\",'"));
  finalString.append("')");
  return finalString.toString();
 }
So you can use it in such the way:

String safeXPathToLocateTheElement = "//file[contains(@name," + resolveAprostophes("Someone's") + ")]";

And then use it in your regular look-up patterns

Thursday, July 05, 2012

How To Dump File Structure To XML. Part 3 (Final Part)

In Part1 we learned how to operate with XSD schemas and design you XML-based DSL. Then in Part 2 we learned how to build the parser and apply the business logic to owr application. We made our application capable to store the file structure in XML file.

Now we need the oposite operation. We need to restore file structure from XML file. Remember that that's only a structure, not the content, so every file will not hold a byte of information, just a name (aka record in file system).

Validating XML file with XSD schema


So, check this snippet:

public class XMLToFS {

 public static void reproduce(String sourceFile) throws SAXException, IOException, JAXBException{
  JAXBContext context = JAXBContext.newInstance(XTFSFolderRoot.class);
  Unmarshaller reader = context.createUnmarshaller();
  File schemaFile = new File("conf/fstoxml.xsd");
  File sampleFile = new File(sourceFile);
  validateAgainstSchema(schemaFile, sampleFile);
  XTFSFolderRoot rootNode = (XTFSFolderRoot) reader.unmarshal(sampleFile);
  long startTime = System.currentTimeMillis();
  new FSCreator(rootNode).create();
  System.out.println("Finished in " + String.valueOf(System.currentTimeMillis() - startTime) + " milliseconds");
  
 }

 static void validateAgainstSchema(File schema, File targetXml) throws SAXException, IOException{
  SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
  Schema referenceSchema = schemaFactory.newSchema(schema);
  Validator schemaValidator = referenceSchema.newValidator();
  schemaValidator.validate(new StreamSource(targetXml));
  System.out.println("Xml file conforms specified schema.");
 }
}

XML recursive going round

This is the main acting class to reproduce the file system from an XML file. It requires the schema designed in previous posts to parse the input content genereated by previousely described functionality. This snippet is also the example of how to validate xml file against xsd schema. As it is seen it's quite simple - just use the pattern.
After we have validated our input data, we should process the data to get the file structure. First we should create the root object from the data from file (aka unmarshall) - see line #9. Once we have the object we can pass it to the dedicated code to reporoduce the structure as the set of files on your hard drive. Such the functioality I incapsulated in FSCreator class. Here it is:


public class FSCreator {

 XTFSFolderRoot rootNode;
 
 public FSCreator(XTFSFolderRoot rootNode){
  this.rootNode = rootNode;
 }
 
 public void create() throws IOException{
  File rootFolder = new File(rootNode.getPath());
  if(!rootFolder.isDirectory()){
   boolean isSuccess = rootFolder.mkdir();
   if(!isSuccess){
    throw new IOException("Root folder failed to get created");
   }
  }
  reproduceFileFolderStructure(rootNode.getFolderOrFile(), rootFolder);
 }
 
 private void reproduceFileFolderStructure(List<XTFSFile> currentInput, File parent){
  for(XTFSFile item: currentInput){
   if(item instanceof XTFSFolder){
    System.out.println("Processing folder " + parent.getAbsolutePath() + "\\" + item.getName());
    File newParent = new File(parent, item.getName());
    newParent.mkdir();
    reproduceFileFolderStructure(((XTFSFolder) item).getFolderOrFile(), newParent);
   }else if(item instanceof XTFSFile){
    System.out.println("Processing file " + parent.getAbsolutePath() + "\\" + item.getName());
    try {
     new File(parent, item.getName()).createNewFile();
    } catch (IOException e) {
     System.out.println("Skipping due to IO error");
    }
   }else{
    throw new IllegalStateException("Unrecognized class of the object");
   }
  }
 }
 
}


As you can see it uses recursive function to walk through a tree and process every node depending on its type. Point the attention to lines #22, #27. We have to check which type exactly the node implements as we process files and folders in different way. It is also very important how you order type check (it's important to check if the object is of Folder type first).

The new and finalized application sources you can find here. It now has combined dump/reproduce features with single entry point. You also may download the built solution which is ready to use.

Wednesday, June 27, 2012

How To Dump File Structure To XML. Part 2.

So, here in Part 1 of the post we prepared XSD schema keeping the data-model of our data we're going to store. Now we're ready to prepare parser, wrap it with some 'business-code' and try out.

Generating JAXB parser classes

Let's do that. First of all locate your JRE (I'm sure you have one). There in /bin folder you should find xjc executable. Copy your schema there (let its name be fstoxml.xsd) and run the command 'xjc fstoxml.xsd'. What's it done? It converted the types described in your schema to the parser classes and put it to the package generated basing on the schema namespace. Check the previous part of the post to see the namespace. It is "http://www.notifymeplease.org/fstoxml/schema" which means the package will be "org.notifymeplease.fstoxml.schema". Refer to xjc help to know how to place the classes to different package.

After xjc finishes processing you find the classes under the folders corresponding to the mentioned package. These are your XML parser. Add them to sources of your Java project and get ready to write business wrapper. One important thing to note: after you have added generated classes to your sources go to XTFSFolderRoot.java and add @XmlRootElement(name = "root") annotation just before class definition. That will indicate that the element is devoted to be the root one.

Dump File Structure To XML

Remind that our business-need is to scan the file-structure (starting from some root) and dump it to XML file, so we need the recursive procedure to walk through the tree and the approach to save it. At the bottom of the page you may find the simple code with the comments describing everything that happens there.

The code fetches the value of root property from the resource bundle. The thing to know about resource bundles is that
- the file extension should always be 'properties'
- if no language code specified in the property file name as the suffix, the default one will be used.
- property file should be placed somewhere in classpath
So for current example we should have conf.properties file under the classpath

Then code creates 'root' object and fills it with the help of recursive procedure that goes across the underlying file system and populates the object dependencies.

P.S. - You may find the project sources here.
P.P.S. - You may find the built application here.
P.P.P.S. - Here is the Part 3 where I'm telling how to reproduce file system from generated XML file
To use the built application you should do the following

1. Unpack the archive to somewhere
2. Make sure you have JRE installed and JRE bin folder in your environment PATH variable
3. In the folder you've unpacked the archive to set up the root folder to scan in conf.properties
4. Run the command 'java -jar fstoxml.jar'. You should now see the generated file. For example the generated file for this project src folder looks like
.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xtfsFolderRoot xmlns="http://www.notifymeplease.org/fstoxml/schema" path="C:\FSToXML\src">
    <folder name="org">
        <folder name="notifymeplease">
            <folder name="fstoxml">
                <file name="FSToXML.java"/>
                <folder name="schema">
                    <file name="ObjectFactory.java"/>
                    <file name="package-info.java"/>
                    <file name="XTFSFile.java"/>
                    <file name="XTFSFolder.java"/>
                    <file name="XTFSFolderRoot.java"/>
                </folder>
            </folder>
        </folder>
    </folder>
</xtfsFolderRoot>

.

Code Snippet.

This is the main code utilizing the parser.

package org.notifymeplease.fstoxml;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;

import org.notifymeplease.fstoxml.schema.XTFSFile;
import org.notifymeplease.fstoxml.schema.XTFSFolder;
import org.notifymeplease.fstoxml.schema.XTFSFolderRoot;

public class FSToXML {

 public static void main(String[] arg) {

  ResourceBundle bundle = ResourceBundle.getBundle("conf");
  File root = new File(bundle.getString("root"));

  try {
   // Creating JAXBContext to handle the classes from specified package
   JAXBContext jaxbContext = JAXBContext.newInstance("org.notifymeplease.fstoxml.schema");
   // Create marshaller to save data to disk
   Marshaller marshaller = jaxbContext.createMarshaller();
   marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, new Boolean(true));
   // We should not start from not a directory
   if (!root.isDirectory()) {
    throw new IllegalStateException(
      "Root should represent a folder. Current root: " + root.getCanonicalPath());
   }
   // Creating the main Node. The root of further xml output
   XTFSFolderRoot rootNode = new XTFSFolderRoot();
   // Specify attribute
   rootNode.setPath(root.getCanonicalPath());
   // Populate the content
   long start = System.currentTimeMillis();
   rootNode.getFolderOrFile().addAll(populateBody(root));
   // Save to disk
   marshaller.marshal(rootNode, new FileOutputStream("jaxbOutput.xml"));
   System.out.println("Time spent(ms): " + String.valueOf(System.currentTimeMillis() - start));
  } catch (IOException e) {
   e.printStackTrace();
  } catch (JAXBException e) {
   e.printStackTrace();
  }
 }
 
 /**
  * Recursive method of populating the list 
  * @param root
  * @return
  */
 
 public static List<XTFSFile> populateBody(File root){
  File[] newSet = root.listFiles();
  ArrayList<XTFSFile> newList = new ArrayList<XTFSFile>();
  
  // Having the sequential file (which actually may appear to be a folder)
  for(File item: newSet){
   // Check if it is a file
   if(!item.isDirectory()){
    /**
     * It's a file, so just create new object,
     * set the corresponding name
     * and add it to the list
     */
    XTFSFile newFile = new XTFSFile();
    newFile.setName(item.getName());
    newList.add(newFile);
   }else{
    /**
     * It's a folder. so create folder object,
     * set the name of the folder, and populate the content
     * using the same method we're in now
     */
    XTFSFolder newFolder = new XTFSFolder();
    newFolder.setName(item.getName());
    newFolder.getFolderOrFile().addAll(populateBody(item));
    newList.add(newFolder);
   }
  }
  
  // After all has finished, return the result
  return newList;
 }

}

.

How To Dump File Structure To XML. Part 1.

Goal: Having XSD grammar to describe file structure

Assume you want to reproduce (replicate) the file structure of some host you have no the direct access to. Assume you just want to save the file structure to perform some evaluation against it. The best way is to store it in XML file. This post is about how to do that. Also as a side effect the following topics're going to be slightly touched:

- JAXB: How to generate xml file from scratch
- How to apply XSD to build convenient XML parser via xjc.

So first of all we need to design the schema of the data we're going to keep. Having such the schema will allow us to build the parser which will be used to write the data to xml and to read them from it in a very simple way. One may develop the schema by using either DTD or XSD specifications. The second one allows to use xjc translator from j2SE distribution to translate the schema to parser classes.

What meta information do we need?

So let's consider the data structure we're going to process. This should represent the minimal meta-information of the file system. So it should support having the root node, files and folders which in turn should allow holding files and folders inside. Also the structural elements (files and folders) should have the attributes like "name" (mandatory one), "isHidden", "isArchived" etc.

XSD is the format of describing the rules certain XML should be built with. It's a sort of grammar for a language which extends XML. XSD is also the XML so it can represent hierarchical structure where elements have the types dictating the way how one can (and allowed to) use them in their document meeting the schema. For example you cannot assign literal value to the attribute if you specified numeric type for it in the schema.

XSD is the declarative language so that you describe the types (which btw can extend other ones), the relationships, restrictions etc. and then use this information depending on your current need (recall that our need is to build the parser)

Such the declarative nature allows to process XSD with dedicated processors (aka translators) which can generate Java classes representing the types, dependencies and relationships of the entities described in the schema. Such the classes are marked with special annotations and getter methods so that they are easy to use to parse XML via JAXB (JavaXML Binding) technology.

Let's check an example

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.notifymeplease.org/fstoxml/schema"
 xmlns:tns="http://www.notifymeplease.org/fstoxml/schema" elementFormDefault="qualified">

 <xs:element name="root" type="tns:XTFSFolderRoot"/>
 
 <xs:complexType name="XTFSFolderRoot">
  <xs:sequence minOccurs="0" maxOccurs="unbounded">
   <xs:choice>
    <xs:element name="folder" type="tns:XTFSFolder"/>
    <xs:element name="file" type="tns:XTFSFile"/>
   </xs:choice>
  </xs:sequence>
  <xs:attribute name="path" use="required"/>
 </xs:complexType>
 
 <xs:complexType name="XTFSFolder">
  <xs:complexContent>
   <xs:extension base="tns:XTFSFile">
    <xs:sequence minOccurs="0" maxOccurs="unbounded">
     <xs:choice>
      <xs:element name="folder" type="tns:XTFSFolder"/>
      <xs:element name="file" type="tns:XTFSFile"/>
     </xs:choice>
    </xs:sequence>
   </xs:extension>
  </xs:complexContent>
 </xs:complexType>

 <xs:complexType name="XTFSFile">
  <xs:attribute name="name" type="xs:string" use="required" />
  <xs:attribute name="isHidden" type="xs:boolean" use="optional"
   default="false" />
  <xs:attribute name="isReadOnly" type="xs:boolean" use="optional"
   default="false" />
  <xs:attribute name="isArchived" type="xs:boolean" use="optional"
   default="false" />
 </xs:complexType>

</xs:schema>

Mapping "types" to "tags"

This simple schema represents the data structure we're going to use. It consists of three types description: XTFSFile, XTFSFolder (which extends XTFSFile) and XTFSFolderRoot. These types will be then translated to corresponding Java classes and will contain getter methods for each the attribute returning the values of specified types for those attributes.

Note that the types do not mean tags in your xml which you're going to build according to this schema. To describe the rules of how to arrange the tags you should come up with so called "elements". Here in the example schema it is seen that we have element "root" (which describes how to use root tag in your XML). This element is of type XTFSFolderRoot. Notice that when we refer to this type we have to specify the namespace of the document we're currently developing. This is required to distinguish your custom types from the types of "http://www.w3.org/2001/XMLSchema" namespace. 

XTFSFolderRoot implies that we can have the sequence of  "folder" or "file" tags under the "root" one. This sequence can be infinite or it can not exist at all (minOccurs="0" maxOccurs="unbounded"). Folder and file tags should meet the rules described in XTFSFolder and XTFSFile types correspondingly. Note that folder type implies holding elements of either files or folders which is actually the  recursive dependency.

Types also contain the attribute description with flags of whether they are required or not, and if not then which value to use by default if they are not specified in the document.

So since we have this schema we can create some sample xml file and validate it to ensure the schema works correctly and catches all the places where the document doesn't meet it. However we are not going to use the schema to read the files but rather to write ones. Anyway we should translate it to the Java classes, include them to classpath and write the code to utilize them. 

All this stuff in the Part 2 of this post.

Monday, June 04, 2012

How to easily load web page straight to the DOM

Very useful and light-weight package you may find here. It allows to wrap any html page from the server straight to DOM with no cost at all. Like this one:

org.jsoup.nodes.Document doc = Jsoup.connect("http://yourpage.own").get();

After that you may operate with doc like with just regular document. Another useful thing is locating element using css selector syntax. Here is the example from the official page

Document doc = Jsoup.connect("http://en.wikipedia.org/").get();
Elements newsHeadlines = doc.select("#mp-itn b a");
Powerful tool with broad functionality which is better to get familiar with through the official page. 

Good Practice: How to store the configuration of your Java-based testing framework. Part I.

Some day your framework (or any other somehow complicated project) will become so big that it will not be convenient to keep the configuration hard-coded or even in the property files. I'm probably saying not even about the configuration in regular meaning but rather about project-specific meta-information. 

What values are we looking for...

You will probably need to store:

  • Credentials
  • Some steps to perform on some stage
  • Resources and some extra attributes for them
  • Connection settings for set of databases
  • Rules for parsing certain data
  • etc.
All this stuff requires certain capabilities from your configuration storage. If you'd like to have easy usage you'd probably expect the following features from that:
  • Nice to keep all such the information in the minimum of files
  • Nice if your storage supports structural data
  • Nice if you have convenient and effective facility to read and write data out of/in that storage
  • Nice if such the format is highly standardized
  • Nice if such the format has the facility to validate the stored data for at least syntax.
  • Great if such the format won't require extra effort for infrastructure setting-up from you
  • Great if the storage keeps the data in the format which can be understood by the human
  • Great if the format allows applying of different IDEs to introduce changes in stored data manually

XML - nice alternative

Well if to take a look onto the items above the two ideas comes on my mind. First one - the idea which addresses all the Nice items. This idea is certainly relational database. However it is hard and heavy solution and may cause pretty annoying problems related to the connectivity. 
Another way is both Nice and Great. I'm talking about storing your meta-information in xml file. If you're going to use xml in your project you would probably like to apply good practice of such the pattern.

If you decided to use xml to store your data it's great to have the understanding of which exactly data you're going to store there. The very good way to understand it and to keep acting according that understanding is to design the xml-schema which does actually represent the model of data you're going to store and use.
Once you have such the schema you'll be able not to understand and troubleshoot any data-structure-related problems of your project but also will be able to use such the sweet features like validating your files against that schema and using dedicated IDEs so that you'll have the code assistance feature enabled.

Xml-schema is the sort of grammar constraints of the language your going to describe your project's meta-information with. Applying such the grammar to your configuration file will allow to catch wide set of improper data problems until your main code gets executed. 

Here you may find small but effective tutorial on how to understand and to use the schema file.

Later on I'll tell how to use all the features I told above for the real example. 

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, May 15, 2012

Selenium Java client: proper way of how to know that the certain element got disappeared

Im one my previous posts I suggested the way on how to ensure the certain element disappeated in your test scenario code. That usually the case of Web UI testing as it is not only important that something is shown on your click but also that something stopped being shown. Now let me introduce the right way on how to do that :)

So assume you have the following function devoted to looking up the fact the element on the page disappeared. You call it somewhere from your scenario code.

public boolean lookupXPathDoesNotExist(final String xpath) throws CustomAutomationException {
 
try{
 new WebDriverWait(getDriver(), AWAITING_THRESHOLD_MS/1000, 1000).until(
  new ExpectedCondition<Boolean>(){
   @Override
   public Boolean apply(WebDriver d) {
    if (d.findElements(By.xpath(xpath)).isEmpty()){
     return new Boolean(true);
    }else{
     return new Boolean(false);
    }
   }
  }
 );;
}catch(org.openqa.selenium.TimeoutException e){
 throw new CustomAutomationException("Timeout exceeded - however the element is still visible");
}
return true;
}

To make it work you should consider the following:
AWAITING_THRESHOLD_MS - is the constant holding the timeout you'preffer to fail your scenario after in case the element is still on the page
getDriver() - is the function that somehow returns the current acting webdriver
1000 (the last parameter of WebDriverWait method) - the interval between repeated tries

Read also How To Check That Element Is Present In Another Element

Monday, February 27, 2012

Lazy way on how to generate pretty huge set of distinct (unique) hex values

We sometimes need to generate huge set of quite short distinct identifiers. Another requirement is that the identifiers generated with the chosen method (despite of a huge generated set) are still from the much bigger set and actually represent a small subset of that (this for example could help when you generate the number which you would like is hard to guess. probably to generate the invites for your site).

Here is the lazy way on how to do that using java. Using it I generated 500K distinct hex values (8-digit in a maximum) from 4.3 bil total space in a few seconds. That means the user will have to try to guess the right value for approximately 9K times.

So.. this is the code

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.UUID;


public class Generator {
 
 public class DummyStringKeeper {
  
  String string;
  
  public DummyStringKeeper(String string){
   this.string = string;
  }
 }

 public static void main(String args[]) throws IOException{
  BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("c:/temp/items_expoted.txt"));
  ArrayList<DummyStringKeeper> list = new ArrayList<DummyStringKeeper>();
  for (int i=0; i<500000; i++){
   DummyStringKeeper randomItem = new Generator().new DummyStringKeeper(UUID.randomUUID().toString());
   list.add(randomItem);
   String randomItemString = randomItem.toString().substring(randomItem.toString().indexOf("@")+1) + "\n";
   bos.write(randomItemString.getBytes());
  }
  bos.flush();
  bos.close();
 }
 
}

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);

For those who experience problems with Selenium 2 hovering

The common practice of simulating mouse hovering in Selenium 2 is to move the mouse cursor to the element located and wrapped with WebElement. This should look like this.
            Actions builder = new Actions(driver);
            builder.moveToElement(lookupXPathExists(xPath)).build().perform();
Where driver is your current WebDriver implementation and  lookupXPathExists(xPath) is a kind of procedure returning you the WebElement object by xPath (see my older posts for more details)

However a lot of people trying to apply such the approach for both FF and IE browsers experience the same issue: once your scenario tries to perform some next action after hovering (ex. click the button appeared after you have hovered the element) the hovering gets canceled.

So the only way for us is to wait until this issue is resolved. Use Chrome driver so far.. :)

Friday, January 06, 2012

Understand JMeter RMI properties

As you probably know JMeter allows to set up distributed cluster to distribute loading among several peaces of hardware resources. To make it available JMeter uses RMI (remote method invocation) technology. That means jmeter parses the scenario and sends the dedicated commands to the loading nodes (injectors) replicating the scenario among them.
RMI communication is built over the network. When JMeter master sends the command to injector it sends the command to invoke injector method to the certain port (RMI port) and awaits the execution results back to the own ports it is listening (local port). The local port is usually not a constant and may vary within certain bounds. It might cause the problems when you use firewall rules blocking the port which is chosen by jmeter as the local port. That's why jmeter gives you the capability of setting up such the port number explicitly.

So you will need to configure the following properties which can be found in /bin/jmeter.properties (do not forget to remove commenting character #). All this is correct for build 2.5.1 r1177103

server_port=1099
That means jmeter master will communicate with injectors using this port as default one

server.rmi.localport=4000
This port is used to get the response on the method that is executed from the server elsewhere.

client.rmi.localport=4000
This parameter is responsible for setting up the port the client (master node) will get the responses from the samplers been running on a server (slave aka injectors).

You may also find the parameter server.rmi.port but it actually does nothing (according to jmeter source code) and is overwritten with server_port parameter in case it is uncommented and has non-zero value when jmeter starts as the server.

The one important thing to know: always specify remote_hosts parameter (on the client side) with the servers in following notation: SERVER_NAME:SERVER_PORT where SERVER_PORT is the one set up with server_port parameter on the certain server (aka slave aka injector). The build I have used for this post (see above) had the defect (aka undocumented feature) in node looking up procedure in the registry.

That seems to be it.

Wednesday, November 30, 2011

Another robust way of how to locate ajax elements using Selenium 2 + webdriver

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

Once you're going to test somehow complicated web-site you should investigate the way of what to consider as the succeess of your another action?

As it looks to me we have two kinds of success indicators of our action on UI
1. You boserve the element that is expected to be displayed (ex.: you're clicking the button and expect to see the dialog)
2. You do not observe the element that is not expected to be displayed. (ex.: you're closing the dialog and expect it disappears)

Also you should take into account that the elements on your page may appear and disappear without page re-loading. That's why I'd like to suggest the following design addressing such the points:

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

Introduce two methods:

public WebElement lookupXPathExists(String xpath) throws MyAutomationException{
  WebElement handledElement = repeatableLookupExists(xpath);
  if (handledElement!=null) {
   return handledElement;
  } else {
   throw new MyAutomationException("Lookup for element ["
     + xpath + "]" + " failed after "
     + AWAITING_THRESHOLD_MS + " ms awaiting.");
   
  }
 }
 
 public boolean lookupXPathDoesNotExist(String xpath) throws MyAutomationException {
  if (repeatableLookupDoesNotExist(xpath)) {
   return true;
  }else{
   throw new MyAutomationException("The xpath ["+xpath+"] is still observed after "+AWAITING_THRESHOLD_MS + " ms awaiting.");
  }
 }

Where AWAITING_THRESHOLD_MS is static final variable holding the period you'd like to wait until the element will appear. The first method returns the element found using your locator. The second one just checks if the element you're searching does not present on the page. Both of them call their repeatable helpers which are shown below:

private void validateFoundElements(List<webelement> elementList) throws SeleniumException{
  
  MyTestCase.logger.debug(testCase.wrapMessage("Validating found elements..."));
  
  if(elementList.isEmpty()){
   throw new SeleniumException("Cant find specified element..");
  }
  
  if(countVisibleItems(elementList) > 1){
   throw new SeleniumException("Specified xpath found several elements. Please concretize.");
  }
 }
 
 private int countVisibleItems(List<webelement> elementList){
  int visibleItemsNumber = 0;
  for(WebElement element: elementList){
   if(element.isDisplayed()){
    visibleItemsNumber++;
   }
  }
  return visibleItemsNumber;
 }
 
 private WebElement repeatableLookupExists(String xpath) {
  long start = System.currentTimeMillis();
  while (true) {
   try{
    List<webelement> listOfFoundElements = driver.findElements(By.xpath(xpath));
    validateFoundElements(listOfFoundElements);
    if(driver.findElement(By.xpath(xpath)).isDisplayed()){
     MyTestCase.logger.debug(testCase.wrapMessage("Found xPath [" + xpath + "]. Creating object.."));
     return driver.findElement(By.xpath(xpath));
    }
   }catch(SeleniumException se){
    try {
     Thread.sleep(AWAITING_UNIT_LENGTH_MS);
     MyTestCase.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.findElements(By.xpath(xpath)).isEmpty() ? false : true;
   if (!isPresent) {
    Statistics.totalLatency += System.currentTimeMillis() - start;
    MyTestCase.logger.debug(testCase.wrapMessage("Not found xPath [" + xpath + "]. Success.."));
    return true;
   }
   else{
    if(countVisibleItems(driver.findElements(By.xpath(xpath)))==0){
     MyTestCase.logger.debug(testCase.wrapMessage("Found xPath [" + xpath + "]. However it is not visible. Success.."));
     return true;
    }
    try {
     Thread.sleep(AWAITING_UNIT_LENGTH_MS);
     MyTestCase.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;
 }

Where AWAITING_UNIT_LENGTH_MS is final variable saying how often you'd like to check if the element appeared/disappeared.
That is how the solution work. The only thing you should always remember about is that once you want to check if the element is not present on the page and you realize that it isn't you should make sure it is not visible on the page because of the proper working of the tested product but not because of the page hasn't been completely loaded. To make that sure try to determine the checking element that should be obligatory visible while that one you do not expect to see isn't. So use the following pattern to ensure the element is not visible as expected:
1. lookupXPathExists("xpath of the element saying the page is loaded")
2. lookupXPathDoesNotExist("xpath you're expecting not to see on the page").

That is it.

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

Saturday, August 13, 2011

Selenium 2 tried released version

Okay. AFAIR I said that selenium 2 was not ready for enterprise testing. That time I used either beta or even alpha version of the webdriver. So today I decided to check out what's been changed since that time and probably to unsay :)
However I still cant do that :) What does prevent me now? First of all there is still lack of multiple-browser support. Internet explorer driver couldn't see the input elements for some reason however other drivers did so I couldn't even log in. Opera driver cant close the browser window on the test shut-down... So the latest build version is pretty much raw yet. Still don't recommend to move your frameworks to WebDriver.

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.