Monday, November 4, 2013

Gruntfile.js - adding another HTML file to usemin

Adding an HTML to the Gruntfile usemin

Recently I started using node and with it yo, grunt and bower.
It is nice to get a quick kickstart
But now when I have to add/modify something in the build process,
I get stumped a lot.

You usually have a single base html file called index.html
and then you have Angular with ng-view to change content
thus generating a Single Page Application.

However, I always find it necessary to have error pages which are self contained,
which means index.html is not involved.

While yo's generators take care of index.html, your error page does not load correctly.
The reason for this is the usemin task in grunt which turn your
href attributes to point to the minified version of the file.
For example, if index.html has the following in the header

<!-- build:css({.tmp,app}) styles/main.css -->
<link rel="stylesheet" href="styles/main.css">
<link rel="stylesheet" href="styles/page1.css">
<!-- endbuild -->  
  
grunt usemin will turn it into this
<link rel="stylesheet" href="styles/1b62fe48.main.css">
  
note that page1 is not included in the output, and that is because the new main.css
contains them both.

So the question is what should I do if I have index2.html.
How would I get it to work here too.

Solution

The trick is to look at useminPrepare which by default looks like so

useminPrepare: {
    html: '<%= yeoman.app %>/index.html',
    options: {
        dest: '<%= yeoman.dist %>'
    }
},  
  

If you modify the HTML field to include some other file, that file will be picked to the build process too.
You do that by simply turning that field into an array like so:

useminPrepare: {
    html: ['<%= yeoman.app %>/index.html','<%= yeoman.app %>/index2.html'],
    options: {
        dest: '<%= yeoman.dist %>'
    }
},  
  

Assuming your index2.html has something similar to index.html

<!-- build:css({.tmp,app}) styles/main2.css -->
<link rel="stylesheet" href="styles/main2.css">
<link rel="stylesheet" href="styles/page2.css">
<!-- endbuild -->  
  
it will get picked up and processed accordingly.

Monday, October 28, 2013

Configuration Module for NodeJS

configuration Module for NodeJS

NodeJS is great but it lacks settings/configuration mechanisms.
Actually - I understand why it lacks it - configuration nowadays is written in JSON anyway.. so in node you just import it..
But there are some features you still want/need that do not exist yet.

Missing Features

  • Overriding with another JS file
    This means I want the same syntax to override the configuration. Some libraries suggest command line flags.. I do not like it.
  • Front-end support
    I want to change configuration in a single place, and I want to be able to configure by front-end as well.
  • Fail to load if a required configuration is missing

Implementation

var publicConfiguration = {
    "title": "Hello World"
    "errorEmail": "null" // don't send emails if null

};

var privateConfiguration = {
    "port":9040,
    "adminUsername": undefined, 
    "adminPassword": undefined
}        

var meConf = null;
try{
    meConf = require("../conf/dev/meConf");
}catch( e ) { console.log("meConf does not exist. ignoring.. ")}




var publicConfigurationInitialized = false;
var privateConfigurationInitialized = false;

function getPublicConfiguration(){
    if (!publicConfigurationInitialized) {
        publicConfigurationInitialized = true;
        if (meConf != null) {
            for (var i in publicConfiguration) {
                if (meConf.hasOwnProperty(i)) {
                    publicConfiguration[i] = meConf[i];
                }
            }
        }
    }
    return publicConfiguration;
}


function getPrivateConfiguration(){
    if ( !privateConfigurationInitialized ) {
        privateConfigurationInitialized = true;

        var pubConf = getPublicConfiguration();

        if ( pubConf != null ){
            for ( var j in pubConf ){
                privateConfiguration[j] = pubConf[j];
            }
        }
        if ( meConf != null ){
              for ( var i in meConf ){
                  privateConfiguration[i] = meConf[i];
              }
        }
    }
    return privateConfiguration;

}


exports.sendPublicConfiguration = function( req, res ){
    var name = req.param("name") || "conf";

    res.send( "window." + name + " = " + JSON.stringify(getPublicConfiguration()) + ";");
};


var prConf = getPrivateConfiguration();
if ( prConf != null ){
    for ( var i in prConf ){
        if ( prConf[i] === undefined ){

            throw new Error("undefined configuration [" + i + "]");
        }
        exports[i] = prConf[i];
    }
}


return exports;

Explanations

sendPublicConfiguration
a route to send the public configuration that can be exposed in the front-end.
You should write something like
app.get("/backend/conf", require("conf.js").sendPublicConfiguration);
                
and then you can get the configuration by importing a script
<script src="/backend/conf?name=myConf"></script>                 
                
The name decides the global variable name that will include the configuration.
undefined
Means a configuration is required
null
Means the configuration is optional
myConf
The overrides file. The code above assumes it is under app folder and that meConf is under conf/dev folder. You can easily change that so you can pass the overrides path when you require the module.
Overrides logic
Private configuration simply takes all values from public configuration and then uses meConf for overrides.
Public configuration filters meConf and overrides only keys it already has.
This way we make sure nothing private is exposed.

Environment Support

Some people find it nice if they can run node with a flag stating if they are running in production or development.
I am not one of those people.
However, my code answers their needs as well.
You can have multiple meConf.js file

meConf.production.js
meConf.development.js
        
and so on, and import the right one using the environment flag.

You can also have a single meConf file which has fields for production, development and such and invoke the same logic.

            meConf = require("../conf/dev/meConf")[environmentFlag];
        
Use it as you see fit.

Monday, October 21, 2013

When Play Framework 2.X Sass plugin says "Sass compiler: ruby.exe: No such file or directory -- project_root/sass (LoadError)"

The Problem

Running Play!Framework (I am using 2.0.4) with Sass plugin throws a LoadError message.

Sass compiler: ruby.exe: No such file or directory -- project_root/sass (LoadError)

About Sass Plugin

Jlitola's Play!Framework SASS plugin is a nifty piece of code that enables SASS in your Play!Framework project.
It requires that Ruby and SASS would be already installed on your computer.
The SASS gem creates to files in your RUBY_HOME\bin folder.
The first is sass.bat (for windows) and the other is sass which is the gem implementation in Ruby. This is a Ruby file.

Proper Folder Structure in Play Project

This plugin expects you to have scss files under your

app/assets
directory, and it will copy them under
public
directory.
I write my scss files under
app/assets/styles
and so the plugin outputs them under
public/styles
.
I can then link them in my HTML with the path
/public/styles

What does the error mean?

It seems that Ruby is trying to find the SASS gem file in your project's root folder.
People might think it is looking for a folder since the error says

No such file or directory
and it is easier to create a directory - is it not?

A Work-Around

A quick work around would be to copy that sass file to your project's root folder.
This does not resolve the problem that Ruby is looking for that file there instead of under Ruby's bin directory, but it will enable you to work on your project.

Reference

Monday, October 14, 2013

Improving Play!Framework 2.x Configuration

Play!Framework 2.0 developer-friendly configuration

Play!Framework 1.0 had a nasty configuration mechanism.
To make a long story short - it was a properties file.
Play!Framework 2.0 came out, and even though the configuration section
got some big improvements, there are still things that annoy me.
In this post I will show you what annoys me and how to resolve it.

This post refers to Play!Framework 2.0.4.
Play!Framework 2.X is still growing and changing, so this might not be relevant for the latest version.
However, the general concept is very useful and can be applied to almost every software.

The Pros

Play!Framework uses typesafe configuration library.
Basically this library, written in scala - so we will see some scala object conversion soon
lets you write in HOCON syntax which is a combination of key-value and JSON-like syntax.
It also allows you to import other configuration files and override values.

The Cons

With all the nice features,
the API play provides is pretty disappointing.
For example, this is how you get a boolean

Play.current().configuration().getBoolean("isNiceApi");

There are several things I do not like in this API

  • It requires hard-coded strings - and no, final static String with CAPITAL_LETTERS is even uglier..
  • Play.current().configuration() is simply ugly and too coupled to Play.
  • getBoolean is better than (Boolean)Play.conf.get("key") but still ugly
Ugly in computers means that the more you use it, the more you work..

Fast Way To Go Around Ugly API - Wrappers

If you do not wrap this API, you either have a really small application (which is no excuse)
or you are a novice developer.
( Or you are an evil developer, but then you would not be reading this blog.. )
So, lets say we have a configuration that includes

  • Timeout
  • Support Email
  • New Feature On/Off
A wrapper would look like this Pseudo-Code
public class ConfWrapper{
    
    public Configuration getConf(){
        return Play.current().application().configuration()
    }

    public long getTimeout(){
        return getConf().getLong("timeout");
    }

    public String getSupportEmail(){
        return getConf().getString("supportEmail");
    }

    public boolean isNewFeatureOn(){
        return getConf().getBoolean("newFeatureOn");
    }


}            
        
This is nice, but not practical.

Why Wrapper Is Not Practical?

  • You work hard
  • I access Play.current().application().configuration() every time instead of caching values
  • No default value support - Number 1 problem in my opinion
You could work harder to get the above features..
But let me show you a very simple solution to get it all and more..

Wish List

I want the code to look like this

public class ApplicationConf{
    public long timeout = 10000; // 10 seconds
    public String supportEmail = "support@example.com";
    public boolean newFeatureOn = true;
}        
        
This is not a joke - this class will include the values from the properties file once we are done.
This is my design :)
and that's it!!! Can you believe it? Well, you should not because we will have another line of code when Play application starts
Lets see how it is done.

Implementing Our Design

To implement our design, all we need is to initialize our configuration class.
Lets call that class ConfigurationInitializer (brilliant!)

public class ConfigurationInitializer {

    private static Logger logger = LoggerFactory.getLogger(ConfigurationInitializer.class);
    private final Application application;

    public ConfigurationInitializer(Application application) {
        this.application = application;
    }


    public <T> T populate( T confObj ){
        try{
        Set<Field> allFields = ReflectionUtils.getAllFields(confObj.getClass(), Predicates.alwaysTrue());
        for ( Field f : allFields ){
            logger.info( "initializing [{}#{}]", confObj.getClass().getSimpleName(), f.getName());
            if (application.configuration().keys().contains(f.getName())) {

                if (Boolean.class.isAssignableFrom(f.getType())) {
                    f.set( confObj, application.configuration().getBoolean(f.getName() ));
                } else if (String.class.isAssignableFrom(f.getType())) {
                    f.set( confObj, application.configuration().getString(f.getName() ));
                } else if (Long.class.isAssignableFrom(f.getType())) {
                    f.set( confObj, application.configuration().getMilliseconds(f.getName() ));
                }
            }
        }
        return confObj;
        }catch(Exception e){
            logger.error("unable to populate configuration",e);
            throw new RuntimeException("unable to populate configuration",e);
        }
    }
}        
    

This is pretty Naive

The above implementation is pretty naive but(!) still much better than a wrapper
The only thing left is to execute this code.
If you add a class named Global to Play!Framework (under the app folder, no package)
Play provides global hooks you can use to initialize this configuration.

public class Global extends GlobalSettings {
    private static Logger logger = LoggerFactory.getLogger(Global.class);
    @Override
    public void onStart(Application application) {
        logger.info("loading configuration");
        ApplicationConfiguration conf = new ConfigurationInitializer( application ).populate( new ApplicationConfiguration() );
        logger.info(Json.stringify(Json.toJson(conf)));

        super.onStart(application);
    }
}
    

The above code currently prints the configuration to the console.
To override the values in the object, all you need to do is have keys that match the fields' name.
For example

timeout=5000
supportEmail=noreplay@example.com
newFeatureOn=false        
        

Now, all you need to do is save some reference this configuration object which will be reachable to you.
You can use a static field, a singleton pattern.
I use Spring - which is basically the singleton pattern.

What Now?

Next post I will show you how to write the code above in a better more modular/generic way
and we will add some small stuff to make this algorithm much more configurable and powerful.
For example:

  • Support nested configuration objects
  • Support lists!!! - Did you know Play!Framework 2.0.4 does not support list in configuration even though typesafe does?
  • Override the configuration key

Monday, October 7, 2013

Adding IFrame Support To Our Selenium Framework - Part 2

Adding IFrame support to our Selenium Extension Library - Fixing Implementation

This is another post in our "Selenium Extension Library" serie.
So far we :

  • Introduced "Compoents" that are basically smart WebElements
  • Allowed components to embed one another
  • Add IFrame support - but for only a single level
Now, it is time to fix our IFrame support so that we support IFrames that are in other IFrames.

Problem and Wishlist

Our major problem is that Selenium/Web-Driver's API only supports 2 methods

  • switchTo.frame (enter)- which goes into an iframe
  • switchTo.defaultContent (leaveAll)- leaves all iframes and goes back to top window
And what we are missing is
  • switchTo.parent (exit)- leaves current iframe and goes back to parent iframe
The names in the (brackets) are simpler names I chose for our scenarios.
I will use these names in my new API.

Implementation

The way I chose to implement this is to write SwitchManager with the following api

  • enter( List<WebElement> framePath )
  • enter(WebElement frame)
  • exit()
  • List<WebElement> getCurrentPath()
And the data set I chose is a FIFO.
Since I am using Java, I used java.util.Stack
The important thing to note about it is that iteration is according to order of insertion.
This means we will not have any problems iterating the Stack.

The code you are about to see does not use interfaces and abstractions
in some foolish(?) hope this will help you stay focused on the target.
In the following post I will use this code as an example where you should use frameworks like Spring
or Guice to perform Dependency Injection (DI) and use interfaces.

Our Test

The test is simple, we will have iframe2 inside iframe1, and we will get the text in iframe2.
We aim to reach the following code

WebDriver firefoxDriver = new FirefoxDriver();
firefoxDriver.get(myUrl);
MyPage myPage = new MyPage();
SwitchManager switchManager = new SwitchManager().setWebDriver(firefoxDriver);
PageFactory.initElements(new MograblogFieldDecorator(firefoxDriver, firefoxDriver).setSwitchManager( switchManager ), myPage);
FramePage2 framePage2 = myPage.framePage1.framePage2;
System.out.println("myPage.framePage.getText() = " + framePage2.getText());
 

Please note the code above does not actually test anything as there are no assertions.

Algorithm

The idea in our implementation will be to drill down all required iframes before each method,
and leaving all iframes when the method exists.
This is the same idea we used for a single layer, we only had to add the entire frame path each time.

SwitchManager code

public class SwitchManager {

    private static Logger logger = LoggerFactory.getLogger(SwitchManager.class);

    private Stack<WebElement> switchStack = new Stack<WebElement>();

    private WebDriver webDriver;



    public void enter(WebElement e) {
        switchStack.push(e);
        enterStack();
    }

    private void enterStack( ){
        webDriver.switchTo().defaultContent();

        Set<WebElement> memory = new HashSet<WebElement>();

        for (WebElement webElement : switchStack) {
            if ( !memory.contains(webElement)){
                memory.add(webElement);
                try{
                    webDriver.switchTo().frame( webElement );
                }catch(Exception e){
                    logger.error("nable to switch to element",e);
                }
            }
        }
    }



    public SwitchManager enter(List<WebElement> handler) {
        switchStack.clear();
        switchStack.addAll(handler);
        enterStack();
        return this;
    }

    public SwitchManager goToDefaultContent(){
        webDriver.switchTo().defaultContent();
        return this;
    }

    public void exit() {
        switchStack.pop();
        webDriver.switchTo().defaultContent();
        enterStack();
    }

    public List<WebElement> getCurrentPath() {
        return new LinkedList<WebElement>(switchStack);
    }

    public SwitchManager setWebDriver(WebDriver webDriver) {
        this.webDriver = webDriver;
        return this;
    }
}

 

Please note the enterStack algorithm.
It does not assume that framePath is friendly.
It might have WebElements repeating themselves.
This is useful in case we want to enter an iframe and not leave straightaway.

Using the SwitchManager

The places we should look for are the same places we changed before:

  • MethodInterceptor, MograblogLocator.ElementHandler - responsible for invoking each method and to switch iframes before and after
  • MograblogFieldDecorator - responsible for initializing everything

Changing the Decorator

// allow infinite layers of wrapping - initialize components with PageFactory.
private void initializeElement( Field field, MograblogElement enhancedObject ) {
    field.setAccessible(true);
    logger.debug("loading field :  " + field.getName());
    try {
        // NOTE : use the getter function so that CGLIB will intercept it and inject the root element.
        if ( field.isAnnotationPresent( SwitchTo.class )){
            WebElement rootElement = enhancedObject.getRootElement();

           switchManager.enter( getElementHandler(field).getSwitchTo() );

            PageFactory.initElements(new MograblogFieldDecorator( rootElement , webDriver).setSwitchManager(switchManager), enhancedObject);

            switchManager.exit();
        }else{
            PageFactory.initElements(new MograblogFieldDecorator( enhancedObject.getRootElement(), webDriver).setSwitchManager( switchManager ), enhancedObject);
        }
    } catch (RuntimeException e) {
        String msg = String.format("problems loading field [%s]", field.getClass().getName() + "#" + field.getName());
        logger.info(msg, e);
        throw new RuntimeException(msg, e);
    }
} 
 

Changing the Decorator


private boolean isSwitchTo = false;
private SwitchManager switchManager;

private List<WebElement> framePath;

public ElementHandler(Field field, ElementLocator locator, WebDriver webDriver) {
    this.locator = locator;
    this.webDriver = webDriver;
    this.field = field;
    this.isSwitchTo = field.isAnnotationPresent( SwitchTo.class );
    logger.debug("created handler for [{}]", field);
}

private WebElement locateElement() {
    if ( isSwitchTo ){
        switchManager.enter(framePath);
    }
    return locator.findElement();
} 

public ElementHandler setSwitchManager( SwitchManager switchManager ){
    this.switchManager = switchManager;
    framePath = switchManager.getCurrentPath();
    return this;
}


public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    if ( method.isAnnotationPresent( NoEnhancement.class )){
        return  methodProxy.invokeSuper(o, objects);
    }

    logger.debug("[{}] intercepted method [{}] on object [{}]", field, method, o);
    if (o instanceof MograblogElement) {
            MograblogElement comp = (MograblogElement) o;

            WebElement element = locateElement();

            if ( isSwitchTo ){
                switchManager.enter( element );
                comp.setRootElement( webDriver.findElement(By.cssSelector("body")));
            }else{
                comp.setRootElement(element);
            }


            comp.setWebDriver(webDriver);

        try {
            return methodProxy.invokeSuper(o, objects);
        } catch (InvocationTargetException e) {
            throw e.getCause();
        } finally{

            if ( isSwitchTo ){
                switchManager.exit();
            }
        }

    } else if (o instanceof WebElement ) {// only handle first displayed
        WebElement displayedElement = locateElement();

        if (displayedElement != null) {
            logger.info("found first displayed. invoking method");
            return method.invoke(displayedElement, objects);
        } else {
            logger.info("unable to detect first displayed");
        }
    }

    return null;

}
 

Conclusions

Last time we taught Selenium to switch to an iframe on every method call.
This solution did not work if that iframe had another iframe in it.
In this post we added support for that scenario and more.
We now support infinite number of iframes hierarchy.
It is time we move to something else, so next time we will
we will talk about making play!framework configuration user friendly.

Monday, September 30, 2013

Adding IFrame Support To Our Selenium Framework

Adding IFrame support to our Selenium Extension Library

So far we have written a nifty selenium extension library that allows you to treat the pages as if they were built by components rather than the primitive WebElement.
This brought your page design and development closer to your Selenium test code.
It also allows you a high abstraction and re-usability of your test code which is always good.

So we have components, and pages.
But what about IFrames?
Frames are basically pages.
So technically, if we taught Selenium to embed components, teaching Selenium about iframes means teaching it embed pages.
However, IFrames get a special treatment.

How to use IFrame with Selenium - the unpleasant way

In this post I will assume I have a page represented by the class MyPage and a frame represented by FramePage.
MyPage HTML looks like so:

<html>
 <body>
  This is my page
  <iframe src="framepage.html"></iframe>
 </body>
</html>
  
and FramePage HTML looks like so:
<html>
 <<body>
  This is framepage
 </body>
</html>  
  

If I were to write a test without the framework, it would look like so

WebDriver firefoxDriver = new FirefoxDriver();
WebElement myFrame = firefoxDriver.findElement(By.cssSelector("iframe"));
firefoxDriver.switchTo().frame( myFrame );
String frameText = myFrame.findElement(By.cssSelector("body")).getText();
Assert.assertTrue(frameText.contains("framepage"));
firefoxDriver.switchTo().defaultContent(); // get out of the frame
  

Down Sides

There are a lot of bad things in the code above,
but allow me to concentrate on a particular one that annoys me.
The test knows how the HTML page looks like..
If I change the page, I have to change the test.
There is simply no abstraction, and no re-usability.
So again, we face a major problem with Selenium's primitive API.
And that's a shame because Selenium is a great library.
But are in a position where we can easily improve this.

My Wishlist

This is how I want the test code to look like

WebDriver firefoxDriver = new FirefoxDriver();
MyPage myPage = new MyPage();
PageFactory.initElements(new MograblogFieldDecorator(firefoxDriver, firefoxDriver), myPage);
Assert.assertTrue(myPage.framePage.getText().contains("framepage")); // when I am done, I am out of the frame
  
This is about 2/3 of the amount of code used previously.
I have an abstraction to MyPage which is aware of FramePage and I don't need to get in/out of the frame.

Lets start with something easy

I will first show you how to implement this for a single IFrame, and then (next post) I will show you how to do this for infinite number of embedded IFrames.

Introducing @SwitchTo annotation

First lets build our pages class

public class FramePage extends MograblogElement{
    public String getText(){
        return webDriver.findElement(By.cssSelector("body")).getText();
    }
}


public class MyPage {
    @SwitchTo
    @FindBy( css="iframe" )
    public FramePage framePage;
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SwitchTo {
}
  
As you can see we added an annotation to mark that this is a new iframe.
However, regarding abstraction, it could have been in the same page. (the naming would be wrong then, wouldn't they? it's no longer framePage.)

Now, all we need to do, is to tell selenium to switch to the iframe.
We will use our decorator and enhancer to achieve this.
Our intercept method will look like so (new lines are highlighted)

@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    if ( method.isAnnotationPresent( NoEnhancement.class )){
        return  methodProxy.invokeSuper(o, objects);
    }

    logger.debug("[{}] intercepted method [{}] on object [{}]", field, method, o);
    if (o instanceof MograblogElement) {
            MograblogElement comp = (MograblogElement) o;

            WebElement element = locateElement();

            if ( field.isAnnotationPresent( SwitchTo.class )){
                webDriver.switchTo().frame( element );
                comp.setRootElement( webDriver.findElement(By.cssSelector("body")));
            }else{
                comp.setRootElement(element);
            }


            comp.setWebDriver(webDriver);

        try {
            return methodProxy.invokeSuper(o, objects);
        } catch (InvocationTargetException e) {
            throw e.getCause();
        } finally{

            if ( field.isAnnotationPresent( SwitchTo.class )){
                webDriver.switchTo().defaultContent();
            }
        }

    } else if (o instanceof WebElement ) {// only handle first displayed
        WebElement displayedElement = locateElement();

        if (displayedElement != null) {
            logger.info("found first displayed. invoking method");
            return method.invoke(displayedElement, objects);
        } else {
            logger.info("unable to detect first displayed");
        }
    }

    return null;

}
 

As you can see, our enhancer switches to the frame before it invokes the methodProxy and switches back afterwards.
As you can also see, when it "switches back", it simply goes to the "defaultContent" which is the top window.
This is actually bad because it means we do not support embedded iframes (an iframe within another iframe).
Selenium does not provide a "go back" feature. We will need to add it later.
The only thing left to take care of is our initialization which happens in the decorator.
This is how it will look like

// allow infinite layers of wrapping - initialize components with PageFactory.
private void initializeElement( Field field, MograblogElement enhancedObject ) {
    field.setAccessible(true);
    logger.debug("loading field :  " + field.getName());
    try {
        // NOTE : use the getter function so that CGLIB will intercept it and inject the root element.
        if ( field.isAnnotationPresent( SwitchTo.class )){
            WebElement rootElement = enhancedObject.getRootElement();
            webDriver.switchTo().frame(getElementHandler(field).getSwitchTo());
            PageFactory.initElements(new MograblogFieldDecorator( rootElement , webDriver), enhancedObject);
            webDriver.switchTo().defaultContent();
        }else{
            PageFactory.initElements(new MograblogFieldDecorator( enhancedObject.getRootElement(), webDriver), enhancedObject);
        }
    } catch (RuntimeException e) {
        String msg = String.format("problems loading field [%s]", field.getClass().getName() + "#" + field.getName());
        logger.info(msg, e);
        throw new RuntimeException(msg, e);
    }
} 
 

As you can see, I added a function getSwitchTo on the handler. It simply returns getElement.

Next Time...

In my next post, I will extend this solution to support multiple iframes embedded one inside the other.

Wednesday, September 25, 2013

Extending Selenium Even More

Extending Selenium Even More

My last post was about how we can easily correct the faults in Selenium's Page Model
by constructing smarter components that can expose better API than WebElement.
We got rid of the impotency of the WebElement class by writing our own Decorator and Locator
and thus allowing ourselves to define Components that wrap WebElement in the most natural way you can think.
As an example, we showed a better way to implement a Select rather than exposing some utility function as proposed by Selenium.

However, currently we can only have a single layer of wrapping, which means one components cannot have another.
Using our Select example from before, we cannot reuse this component to construct a shuttle component.
The shuttle component as showed below is pretty simple - it has 2 select elements and buttons that move one option to another.

Our Spec

So basically, we would like a class to look something like the following

public class MograblogShuttle extends MograblogElement{

    @FindBy( css = ".available-options" )
    public MograblogSelect available;

    @FindBy( css = ".enabled-options" )
    public MograblogSelect enabled;

    @FindBy( css = ".enable-options")
    public WebElement enable;

    @FindBy( css = ".disable-options")
    public WebElement disable;

    public void enable( String val ){
        available.val( val );
        enable.click();
    }

    public void disable( String val ){
        enabled.val( val );
        disable.click();
    }

}
  

Are you finally convinced that extending Selenium is worth your while?
Can you imagine this code with only WebElements to work with? How about utility functions?

Allowing Infinite Layers of Wrapping

If you look at the code from the previous post you will see that the Decorator is the best place to add this new functionality.
When I first wrote this implementation, I chose to write a load function in MograblogElement and add it there
so you can choose the one easier for you.

Implementing it in the decorator looks like this

 // allow infinite layers of wrapping - initialize components with PageFactory.
    private void initializeElement( Field field, MograblogElement enhancedObject ) {
        field.setAccessible(true);
        logger.debug("loading field :  " + field.getName());
        try {
            // NOTE : use the getter function so that CGLIB will intercept it and inject the root element.
            PageFactory.initElements(new MograblogFieldDecorator( enhancedObject.getRootElement(), webDriver), enhancedObject);
        } catch (RuntimeException e) {
            String msg = String.format("problems loading field [%s]", field.getClass().getName() + "#" + field.getName());
            logger.info(msg, e);
            throw new RuntimeException(msg, e);
        }
    }   
  

We call this method from our decorate method which now looks like this (new line is highlighted)

style>
@Override
public Object decorate( ClassLoader loader, Field field ) {
    if ( MograblogElement.class.isAssignableFrom( field.getType() )  && field.isAnnotationPresent( FindBy.class )) {
        final MograblogElement enhancedObject =  (MograblogElement) getEnhancedObject( field.getType(), getElementHandler( field ) );

        initializeElement( field, enhancedObject );

        return enhancedObject;

    }else{
        return defaultFieldDecorator.decorate( loader, field );
    }
}

  

Testing Ourselves

So far we have seen how the Component/Page approach we adapted helped us make Selenium developer friendly.
We wrote a shuttle component in seconds and, most importantly, we used abstractions. If the "select" component is to be changed in the future, we would only have to modify its implementation as its API should remain the same
Plus, we can reuse the shuttle and select components in all pages.

Bigger projects should use a higher level of abstractions. Instead of writing a class MograblogSelect, it should be an interface, and its implementation
should be decided by an dependency injection framework. (for example spring).

Writing the test becomes easy even with big components in hand.
The following code doesn't really test anything (no assert statements) but it goes to show you how easy it should be.

@Test
public void shouldEnableDisableElements(){
    WebDriver firefoxDriver = new FirefoxDriver();


    firefoxDriver.get("http://www.mograblog.com/2013/09/extending-selenium-even-more.html"); // enter the post's URL here. 
    ShuttlePage page = new ShuttlePage();

    PageFactory.initElements(new MograblogFieldDecorator(firefoxDriver, firefoxDriver), page);
    page.enable( "option1", "option2");

    try{ // normally we don't need this, but we want to see it change this time, so I added sleep
        Thread.sleep(3000);
    }catch(Exception e){}

}