Extending Selenium Via Javascript

February 8th, 2008 by James Carr

Looks like I’ve already accomplished one of my new year’s resolutions by doing some polygot programming at work today embedding javascript within java. And it happened completely by accident.

Lately we’ve had a handful of Selenium Remote Control based fitnesse fixtures crop up at work… there’s a nice generic one, and several custom ones designed to tackle specific front end domain problems. On our latest project we have your run of the mill tabbed layout, you know… click on tabs and and you see different content, nothing special. What made things interesting, however, is that marketing wanted to track some specific usage information when each tab is clicked. Fine enough, just whip up some ajax and register some event handlers for the click event, and we’re ready to go. Well, hold on… what about testing?

Everything on the server side was unit tested and functionally tested to a T… in fact I am rather proud of the partner I worked with and myself on how well the tests are written and how many scenarios are covered. However, I still wanted to test the front end… I want to be sure that each time someone promotes, they didn’t do something to, as our team’s QA member would say, “jack it up.”

So I quickly wrote some selenium tests in fitnesse (note: this violates everything I believe FIT should be for, but fitneese is a convenient poor man’s application server) to get to the vehicle history report and test it. Speed bump number one. The interface that lets users run reports uses a target attribute on the form to open the report in a new window, and apparently selenium can’t focus on a window unless you explicitly use window.open to open the window. Drats… foiled. Or was I?

After a bit of searching, I found out that selenium remote control has a method called getEval that will simply evaluate any javascript you pass into it, which is handy for checking variable values for example. From there, you can access the runner via this.browserbot, which provides a whole slew of awesome commands you can use for manipulating the runner, including accessing the current window. And that’s where it sinks in… I can do anything in the world (almost) with javascript, and if I can access the window, I can make things happen. Bypassing the earlier speed bump was a simple matter of opening a new window with the same name as the target, and then focusing on it once the form is submitted. Piece of cake.

Since we have a lot of forms like this, I wrote a simple script to open new windows for any forms with a target on it using the name of the target, and wrapped in a java method. So that was step one… I now had a java method in a fitnesse fixture with javascript code inside a string indented all nicely… and evaluated on demand.

Now. at the new tabbed page is where things got immensely interesting… I wanted to intercept xmlHttpRequest calls, capture them, and be able to access them later. Redefining jQuery’s XHR methods were a piece of cake, the code was something like this (and whipped up in about 5 seconds):

var _d = this.browserbot._caughtValues = []
this.browserbot.getCurrentWindow().jQuery.post = function(a,b,c){
	_d[_d.length] = b;
}

the second parameter here was the data packet being passed in, which is what I was interested in the most. Different meta data is fired off on different tab clicks.

Then topping this off was a simple matter. I created a method that would return a ListFixture (because I wanted to loop over the meta-data packets in the order they were fired, and also because it is vastly supperior to RowFixture) and got to work scraping the values out, which became a mix of BOTH java and js, together.

Building a list of java objects from the captured values was a matter of:

  1. Getting the length of the _caughtValues array and converting it to a java int (getEval only returns strings)
  2. iterate a number of times equal to the length to visit each value on the array
  3. do an eval against _caughtValues with an index specified to get the json packet:
    String packet = getSelenium().getEval("this.browserbot._caughtValues["+index+"]");
                   
  4. parse it into a java.util.Map by making a call to a nice JSON parsing library
  5. populate a java object representing the packet with the map, add it to a list, and return a new ListFixture containing that list.

I wrestled a little to get it work, but I’ll have to admit it felt oddly great when I saw the table of expected packets light up green… and then red for the last 4 rows (good to know it was able to catch the invalid packets). There’s also a sense of joy in seeing it run consistently… for a session it worked, but would randomly fail.

It’s pretty interesting, but I think my coworkers might hate me for the embedded javascript code inside of java. So far they either think it’s really cool, or really insane. ;)

Adding More Flexibility To FIT Fixtures

April 28th, 2007 by James Carr

There’s one fixture of all the fixtures in FIT that people find the most useful… the ColumnFixture. The simplest of all, just a simple table with columns that map to object properties and methods, with each row setting values, calling methods, and verifying results. Anyhow.. in my time I’ve faced all matter of problems with ColumnFixtures, with tons of nice little smells showing up in our fitnesse tests here and there. So today, for my Friday Personal Development time at Carfax, I decided to tackle some of the problems.

Read More »

Speeding Up Development With Fitnesse

April 5th, 2007 by James Carr

I use fitnesse a lot for acceptance testing, and as an “over optimizer” i was recently bugged by one thing I felt slows me down… having to redeploy over and over when writing fixtures. Sometimes you whip up a fixture that has simple functionality and deploy, only to discover you goofed somewhere and it doesn’t work. Wouldn’t it be nice to just quickly edit and save fixtures?

This is where my current fascination with Groovy intersects with my fixture writing madness. I was toying with Groovy earlier today after work and thought “Wouldn’t it be nice to just edit groovy files and save them, and have fitnesse use them?” And tongiht I’ve done just that (although rather hackishly). I wrote a WikiWidget plugin that basically les you define a groovy source file directory (much like the !path wikiWidget) that essentially pulls them into the classpath. All that is required is to have the groovy.jar in the classpath, and it works flawlessly.

It’s late, and I’m sure there’s bugs, so once I clean it up I’ll make it availble for everyone to use. ;)

Using Value Objects in Fitnesse With Nested Tables

December 3rd, 2006 by James Carr

Today I discovered something rather cool while playing with FitLibrary. Not only can you just pass primitive types to fixtures, you can also pass in value objects as well (at least in DoFixture) by using nested tables.

Nested tables work pretty interesting, and I couldn’t really find much documentation on it outside of a scant bit on the fitnesse mailing list and what I was able to figure out by fiddling around. To start things off, I thought I’d show off an example I have while putting together an Email Fixture for checking emails in a mail box. In this particular case, this is a portion that sends an email for the fit test for the fixture. Below is what the table with the nested table looks like:

To do this, you have to assign the nested table to a variable, then simply reference the variable within the table.


!define inner_table (|subject|This is a test|
|type|text/plain|
|body|some content|
)

|Email Accessor|
|connect to|*****|using password|test123|on server|example.com|
|check for|0|messages with subject|This is a test|
|send email|${inner_table}|
|disconnect|

The code for the EmailAccessor is simple… simply extend a DoFixture and have a method called sendEmail which takes an argument (in our case, of type EmailMessage):


public class EmailAccessor extends DoFixture{
   ...
   public void sendEmail(EmailMessage message){
      this.box.send(message);
   }

Now, how does the inner table map to the domain object it represents? The values in the first column map to setters, with the values in the second or further columns as parameters. Below is the relevant method signatures for EmailMessage:


public class EmailMessage{
	...
	public void setType(String type){
	...
	}
	public void setSubject(String subject){
	...
	}
	public void setBody(String body){
	...
	}
}

You can even use nested tables within nested tables to map to domain objets for arguments to other domain objects, although it may look a bit complicated at that point. It worked well on a personal project I did some time ago however as the documentation for a standard I was implementing contained a set of nested tables that outlined the message format. ;)

Custom Content Fitnesse Plugin

November 12th, 2006 by James Carr

Ever found yourself wanting to add new javascript files to fitnesse for added functionality? Ever wanted to replace the default javascript files with your own, as well as add/or replace css files too?

Sadly, you’ve probably discovered that you can’t, since the html is generated on page request, and the elements in the head are added during the generation process. Further, fitnesse.js is replaced by a copy in the jar each time fitnesse starts. Don’t you wish it could be easier?

If so, then this plugin is for you. It allows you to add your own javascript and css files, with some handy features as well. You can configure explicitly what files you want to include in the plugins.properties file, or you can leave these configuration variables blank and the plugin will scan the /files/javascript and /files/css directories and include all of the files it finds in the head.

Using the plugin is easy… just download it, put it in your classpath, and add this to your plugins.properties file (again… if you don’t have this file, create it and stick it in the directory you run fitnesse from).


HtmlPageFactory=org.jamescarr.fitnesse.html.CustomHtmlPageFactory

# Custom Html Page Configuration
fitnesse_root=/home/jamescarr/fitnesse/FitNesseRoot

As you can see above, I also specify where the FitNesseRoot directory is… as this may be different according to your own configuration. You must have this set! Otherwise, the plugin just flat out won’t work (and you’ll probably get some exception stack traces dumped to the console).

By default, it will include any files it finds directly under css and javascript directories, ignoring subdirectories. To specify exactly what files to use, use something like the following:


javascript_files=dynamicTableMaker/all.js;fitnesse.js
css_files=fitnesse_cold_theme.css;fitnesse_print.css

These are relative to FitNesseRoot/files/javascript/ and FitNesseRoot/files/css/ respectively, using a semicolon to seperate specific files to use. This will only include the files that are specified rather than all of them found in directories, and can make it easier to handle subdirectories.

Well, let me know if anyone finds this useful… I’ve been using it for my dynamic table editor plugin that I’ll (hopefully) be releasing soon.

Fitnesse Date Plugin

November 11th, 2006 by James Carr

A long time ago at work I ran into a small problem during some of our tests with fitnesse. We had a few fitnesse tests that tested time based rules, stuff like a 30 day rule that basically goes something like “if the file was received longer than 60 days ago, do not process it.” This was slightly problematic though as we had to update tests from time to time if we were going to hard code dates in our tests, so I decided to take it upon myself to create a wiki plugin to generate the date time dynamically.

Anyhow, I posted it on the fitnesse mailing list on yahoogroups, but I have neglected to take the time to write up instructions on it. So, without furth ado, here it is… the fitnesse sysdate plugin how to (or you can download it now).

Installation

Installation is rather simple… simply place the SysdatePlugin.jar plugin somewhere in your class path and add it to your plugins.properties file (if you don’t already have one, create it in the same directory you run fitnesse from).

Below is what you need to add to your plugins.properties file:

WikiWidgets=fitnesse.wikitext.widgets.SystemDateWikiWidget

Start fitnesse and you’re ready to go! You should see the following at the console once fitnesse starts up.

Custom wiki widgets loaded:
                fitnesse.wikitext.widgets.SystemDateWikiWidget

Usage

Using the plugin is quite simple. To start using it right away, simply type:

!sysdate
 

This will print the current date in MMddYYYY format. If you want to use a different format, you have the option of passing the format in as a parameter… basically it will accept any format string that SimpleDateFormat takes, so refer to the api documentation for SimpleDateFormat for further information.

For example, if I wanted to print the date for today as something like 11/11/2006 21:15:23, I’d use the following:

!sysdate(MM/dd/yyyy hh:mm:ss)

Of course, it’s not usefull without features to generate a date in the future, or in the past. For example, say I want to have a test that alsways tests a boundry condition, say against a date that is always EXACTLY 30 days in the past.

The following are examples of various formats to use. Simply adding a +number or -number to the sysdate will make n days in the past or future.

!sysdate(MM/dd/yyyy hh:mm:ss)-30
!sysdate-30
!sysdate+30
!sysdate(MM/dd/yyyy hh:mm:ss)+5
 

That’s about it. If there are any features anyone like to have, please feel free to let me know. You can download the plugin here.

The Source

Here is the accompanying unit test:

package fitnesse.wikitext.widgets;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import fitnesse.wiki.*;
import fitnesse.wikitext.WidgetBuilder;

public class SystemDateWikiWidgetTest extends WidgetTest
{
    private WikiPage root;
    private PageCrawler crawler;
    private WikiPage page;
    private WidgetRoot widgetRoot;

    public void setUp() throws Exception
    {
        root = InMemoryPage.makeRoot("root");
        crawler = root.getPageCrawler();
        page = crawler.addPage(root, PathParser.parse("MyPage"));
        widgetRoot = new WidgetRoot("", page);
    }

    public void testMatches() throws Exception
    {
        assertMatches("!sysdate");
        assertMatches("!sysdate(mmmddyyyy)");
        assertMatches("!sysdate(MM-dd-yy)");
        assertMatches("!sysdate(MM-dd-yy)-1");
        assertMatches("!sysdate(MM-dd-yy)-22");
        assertMatches("!sysdate(MM-dd-yy)-300");
        assertMatches("!sysdate-30");
    }

    protected String getRegexp()
    {
        return SystemDateWikiWidget.REGEXP;
    }

    /**
     * Allow adding of sysdate special variable
     *
     * @author jamescarr
     * @throws Exception
     */
    public void testSysdateVariable() throws Exception{
        WikiPage parent = crawler.addPage(root, PathParser.parse("ParentPage"), "!sysdate\n");
        WikiPage child = crawler.addPage(parent, PathParser.parse("ChildPage"), "ick");

        WidgetRoot widgetRoot = new WidgetRoot("", child, WidgetBuilder.htmlWidgetBuilder);
        SystemDateWikiWidget w = new SystemDateWikiWidget(widgetRoot, "!sysdate");
        Date date = new Date(System.currentTimeMillis());
        SimpleDateFormat format = new SimpleDateFormat("MMddyyyy");
        System.out.println(format.format(date));
        assertEquals(format.format(date), w.render());
    }

    public void testSysdateVariableWithFormat() throws Exception{
        WikiPage parent = crawler.addPage(root, PathParser.parse("ParentPage"), "!sysdate(MMM-dd-yyyy)\n");
        WikiPage child = crawler.addPage(parent, PathParser.parse("ChildPage"), "ick");

        WidgetRoot widgetRoot = new WidgetRoot("", child, WidgetBuilder.htmlWidgetBuilder);
        SystemDateWikiWidget w = new SystemDateWikiWidget(widgetRoot, "!sysdate(MMM-dd-yyyy)");
        Date date = new Date(System.currentTimeMillis());
        SimpleDateFormat format = new SimpleDateFormat("MMM-dd-yyyy");
        System.out.println(format.format(date));
        assertEquals(format.format(date), w.render());
    }
    public void testSysdateVariableWithBadFormat() throws Exception{
        WikiPage parent = crawler.addPage(root, PathParser.parse("ParentPage"), "!define var {!sysdate(shithead)}\n");
        WikiPage child = crawler.addPage(parent, PathParser.parse("ChildPage"), "ick");

        WidgetRoot widgetRoot = new WidgetRoot("", child, WidgetBuilder.htmlWidgetBuilder);
        SystemDateWikiWidget w = new SystemDateWikiWidget(widgetRoot, "!sysdate(shithead)");

        // default
        SimpleDateFormat format = new SimpleDateFormat("MMddyyyy");
        Date date = new Date(System.currentTimeMillis());
        assertEquals( format.format(date), w.render());
    }

    public void testMultipleSysDateCalls() throws Exception{
        String dates = "!sysdate(MMMddyyyy)\n!sysdate\n!sysdate(MM-dd-yyyy)\n!sysdate(yyyy-MMM-dd)\n!sysdate(MM/dd/yyyy)";
        WikiPage parent = crawler.addPage(root, PathParser.parse("ParentPage"), dates);
        WikiPage child = crawler.addPage(parent, PathParser.parse("ChildPage"), "ick");

        WidgetRoot widgetRoot = new WidgetRoot("", child, WidgetBuilder.htmlWidgetBuilder);
        SystemDateWikiWidget w = new SystemDateWikiWidget(widgetRoot, dates);

        // expect only the first date
        SimpleDateFormat format = new SimpleDateFormat("MMMddyyyy");
        Date date = new Date(System.currentTimeMillis());

        assertEquals(format.format(date), w.render());
    }
    public void testSubtractZeroDays() throws Exception{
       String dates = "!sysdate(MM/dd/yyyy)-0";
       WikiPage parent = crawler.addPage(root, PathParser.parse("ParentPage"), dates);
       WikiPage child = crawler.addPage(parent, PathParser.parse("ChildPage"), "ick");

       WidgetRoot widgetRoot = new WidgetRoot("", child, WidgetBuilder.htmlWidgetBuilder);
       SystemDateWikiWidget w = new SystemDateWikiWidget(widgetRoot, dates);

       // expect only the first date
       SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy");
       Calendar cal = Calendar.getInstance();

       Date date = new Date(System.currentTimeMillis());

       String dateString = format.format(date);
       assertEquals(dateString, w.render());
    }
    public void testSubtractTwentyDays() throws Exception{
       String dates = "!sysdate(MM/dd/yyyy)-20";
       WikiPage parent = crawler.addPage(root, PathParser.parse("ParentPage"), dates);
       WikiPage child = crawler.addPage(parent, PathParser.parse("ChildPage"), "ick");

       WidgetRoot widgetRoot = new WidgetRoot("", child, WidgetBuilder.htmlWidgetBuilder);
       SystemDateWikiWidget w = new SystemDateWikiWidget(widgetRoot, dates);

       // expect only the first date
       SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy");
       Calendar cal = Calendar.getInstance();

       Date date = new Date(System.currentTimeMillis());
       cal.setTime(date);
       cal.add(Calendar.DATE, -20);
       date = cal.getTime();
       String dateString = format.format(date);
       assertEquals(dateString, w.render());
    }

    public void testAddTwentyDays() throws Exception{
       String dates = "!sysdate(MM/dd/yyyy)+20";
       WikiPage parent = crawler.addPage(root, PathParser.parse("ParentPage"), dates);
       WikiPage child = crawler.addPage(parent, PathParser.parse("ChildPage"), "ick");

       WidgetRoot widgetRoot = new WidgetRoot("", child, WidgetBuilder.htmlWidgetBuilder);
       SystemDateWikiWidget w = new SystemDateWikiWidget(widgetRoot, dates);

       // expect only the first date
       SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy");
       Calendar cal = Calendar.getInstance();

       Date date = new Date(System.currentTimeMillis());
       cal.setTime(date);
       cal.add(Calendar.DATE, 20);
       date = cal.getTime();
       String dateString = format.format(date);
       assertEquals(dateString, w.render());
    }

    public void testAddThreeHundredDays() throws Exception{
       String dates = "!sysdate(MM/dd/yyyy)+300";
       WikiPage parent = crawler.addPage(root, PathParser.parse("ParentPage"), dates);
       WikiPage child = crawler.addPage(parent, PathParser.parse("ChildPage"), "ick");

       WidgetRoot widgetRoot = new WidgetRoot("", child, WidgetBuilder.htmlWidgetBuilder);
       SystemDateWikiWidget w = new SystemDateWikiWidget(widgetRoot, dates);

       // expect only the first date
       SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy");
       Calendar cal = Calendar.getInstance();

       Date date = new Date(System.currentTimeMillis());
       cal.setTime(date);
       cal.add(Calendar.DATE, 300);
       date = cal.getTime();
       String dateString = format.format(date);
       assertEquals(dateString, w.render());
    }
}

And here is the source for the date widget class:

package fitnesse.wikitext.widgets;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import fitnesse.wikitext.WikiWidget;

public class SystemDateWikiWidget extends WikiWidget {
   private static final String SUBTRACT = "-";
   private static final String ADD = "+";
   private static final String DATE_FORMAT_REGEX = "\\(\\w+.*\\)";
   public static final String REGEXP = "!sysdate(\\(\\w+.*?\\))?(-\\d+|\\+\\d+)?";
   public static final Pattern pattern = Pattern.compile(REGEXP);

   private static final Pattern dateMethodPattern = Pattern.compile(DATE_FORMAT_REGEX);
   private static final Pattern subtractionPattern = Pattern.compile("(-\\d+|\\+\\d+)$");
   private String originalText = "";

   private String token = null;

   public SystemDateWikiWidget(ParentWidget parent, String text) {
      super(parent);
      originalText = text;
      Matcher match = pattern.matcher(text);
      if (match.find()) {
         token = match.group(0);
      }
   }

   private String getCurrentDate(String token) {

      SimpleDateFormat formatter = getDateFormatter(token);
      Date date = getTheDate(token);
      return formatter.format(date);
   }

   private Date getTheDate(String token) {
      Matcher match = subtractionPattern.matcher(token);
      Date date = new Date(System.currentTimeMillis());
      int days = 0;

      if (match.find()) {
         String result = match.group(0);
         if (0 == result.indexOf(SUBTRACT)) {
            days = getIntegerValue(result, SUBTRACT);
         }
         else if (0 == result.indexOf(ADD)) {
            days = getIntegerValue(result, ADD);
         }
         date = getDateDaysFromDate(date, days);
      }
      return date;
   }

   private Date getDateDaysFromDate(Date date, int days) {
      Calendar cal = Calendar.getInstance();
      cal.setTime(date);
      cal.add(Calendar.DATE, days);
      return cal.getTime();
   }

   private SimpleDateFormat getDateFormatter(String token) {
      SimpleDateFormat formatter = new SimpleDateFormat("MMddyyyy");
      Matcher match = dateMethodPattern.matcher(token);
      if (match.find()) {
         String format = match.group(0);
         format = removeParenthesis(format);
         try {
            formatter = new SimpleDateFormat(format);
         }
         catch (IllegalArgumentException e) {

         }

      }

      return formatter;
   }

   private int getIntegerValue(String result, String modifier) {
      result = result.replaceAll("\\" + modifier, "");
      return (SUBTRACT.equals(modifier)) ? -Integer.parseInt(result) : Integer.parseInt(result);
   }

   private String removeParenthesis(String format) {
      return format.replaceAll("\\(", "").replaceAll("\\)", "");
   }

   public String render() throws Exception {
      return (token != null) ? getCurrentDate(token) : originalText;
   }
}

Fitnesse Commandline Fixture

November 11th, 2006 by James Carr

Last month I spent quite a bit of time whipping up and perfecting my very own CommandLineFixture that allowed me to have a FIT table that interacted with the commandline. This was quite handy as the system under test was using a language there is no fitnesse implementation for, so I whipped up a commandline interface to the program and used my custom CommandLineFixture to interact with it.

Then today I discovered that good old Bob Martin had already made something similar. The interesting thing is his is basically the same. Guess I should have searched first. ;)

My First Teaching Experience

August 19th, 2006 by James Carr

Today I taught my very first class, which was the first in a series of training sessions I plan on presenting at my workplace on fitnesse. Fitnesse (and acceptance testing) is a very interesting topic to discuss on my team, as unfortunately the team was mandated to use it without being given any genuine, formal training on the subject.

The result has been much much grief. This week I and a few others were tasked with fixing broken acceptance tests, which tested the right thing, but were very fragile. For example, due to the lack of knowledge of symbols or fixtures for flow processes like DoFixture or ActionFixture, alot of the tests contain ad hoc ColumnFixtures that use static members to communicate with each other, and 7 levels of heiarchies between fixtures to reuse logic, despite how unrelated. This has led to problems as the tests themselves are fragile and don’t work well when ran together in suites (thanks to shared static members).

Read More »