Saturday, August 04, 2007

Groovy + Jemmy GUI automation

This is something that I came up at work, I thought other people outside of work could make use of it as well. If you're wondering about the obscure references to BizApp and such, this is because I removed the name of the actual app from the text.

OK, so we established that Groovy rocks. Now, I've worked at the company long enough to know that there exists a GUI application that kinda does what I need. I know it works, since I can build the GUI app, run it, push the buttons on it, and it creates all the outputs that I need. Now, the only problem is that I'd like to generate a decent amount of these outputs so that I can have a realistic testing scenario.

Now, I'm certain that there is a "cleaner" and "more proper" way of creating these test outputs. However, my colleague who wrote the BizApp app already figured out how to accomplish what the application needs to do (including all the nitty gritty technical details). Would it be nice if I had access to a simple action that does that ? Certainly. Do I have it now ? Nope. When do I need the outputs to test my client app ? YESTERDAY !! So, what should I do ???

Enter the combination of Groovy and Jemmy. We know what Groovy is, now what is Jemmy??? From their site, it's a library that allows GUI driven testing. In effect, it allows me to programmatically specify the actions that I need on the GUI, and execute them. Great !! Step 1 compete !

Now, step 2. Jemmy is a Java library, I don't quite know exactly how to use the API, and I don't quite know the exact sequence of actions that I'll need to perform through Jemmy. So, if I had to write a Java app to exercise BizApp through Jemmy, there'll be a lot of trial and error hindered by compilation. So, what should I do ?? Groovy is an excellent way to explore a particular API, but how can I plug in Groovy and Jemmy together in order to get this job done ?

Alright, alright, enough of the round-about way of explaining things, here is what to do:

* Setup a NetBeans Groovy scripting project. For that, you need a recent NetBeans setup, with the Coyote plugin installed. The same can certainly be achieved with an Groovy Eclipse plugin as well, but since I don't dig Eclipse, I'll show it in NetBeans. After Coyote is installed, you just set up a new project


* Add the BizApp dependencies in the project. In my case, they're in d:JDevelopBuildsBizApp Add all jars from that directory into the project build. Notice the Jemmy libary at the bottom of the screenshot : it can be added either using the NetBeans update center, or by just downloading the Jemmy library and adding the jar as a dependency in the project.



* Add the Groovy/Jemmy bootstrap script and add the content below, inserting the correct main class for the application. This script will launch BizApp, and will then fire up a blank Groovy console where we can execute our little script in. Right click on the Coyote project, and select "Run Project".


import org.netbeans.jemmy.*;
import org.netbeans.jemmy.explorer.*;
import org.netbeans.jemmy.operators.*;

// Fire up the BizApp Application
new ClassReference("com.mycompany.MyAppMainClass").startApplication();

// Get a reference to the BizApp JFrame and put it into the console's script binding.
mainFrame = new JFrameOperator("BizApp");
def bind = new Binding()
bind.setProperty("testScript",this)
bind.setProperty("mainFrame",mainFrame)


// Fire up the Groovy console.
def console = new groovy.ui.Console(this.class.classLoader,bind)
console.run()
Thread.sleep(600000)


* Now, we have both BizApp and the Groovy Console running side by side. Inside of the Groovy console, we can use Jemmy to programmatically manipulate the GUI (e.g. press buttons, select from checkboxes, etc). The Jemmy statements would look something like the script below. You can sometimes get away without firing a new thread, but if you don't the Groovy console script might block the UI and prevent Jemmy from executing it's input on BizApp


t = new Thread() {
// do whatever you need to do with Jemmy, push buttons, select combos, type into text fields, etc.
}
t.start()




* Finally, figure out what you need to do with the GUI app you're working with and script it. Pseudocode looks like this:


select from merchant dropdown
select from vendor dropdown
hit the "Process" button
hit the "Back" button twice (to get to the main screen)


* The Groovy setup script looks something like the one below. I usually execute this first, so that my Groovy console has references to the combos and buttons so that I don't have to import the Jemmy classes every time (e.g. the Groovy console doesn't remember the imports that you might have done in previous executions, but it will remember the variables that you might have declared


// Import Jemmy specific classes
import org.netbeans.jemmy.*;
import org.netbeans.jemmy.explorer.*;
import org.netbeans.jemmy.operators.*;

// Get references to all UI widgets so that we don't have to re-import
// the Jemmy classes every time. The names of these variables will be
// stored in the "binding" and can be accessed in subsequent script/snippet
// executions
merchantOp = new JComboBoxOperator(mainFrame,0)
vendorOp = new JComboBoxOperator(mainFrame,1)
processButtonOp = new JButtonOperator(mainFrame,"Process")
backButtonOp = new JButtonOperator(mainFrame,"Back")


* Write put the script that will actually do the work. The script is typically derived by executing the statements one by one and seeing that they do the right things. The script below processes an order for the first merchant and vendor and then comes back. See the Jemmy API for details on available widgets operators, etc.


t = new Thread() {
merchantOp.selectItem(0)
vendorOp.selectItem(0)
processOpButton.push()
backButtonOp.push()
backButtonOp.push()
}
t.start()


* Finally, the last step is to typically put something together that will do what you need repetitively. My script is below, I have it saved in the Coyote project directory, and I load it up from the Groovy console when it fires up. Note that I typically just hightlight and run the first part of the code first (to get references to all the buttons, etc), and then only run the second part when I need to run BizApp for a long time.


// Import Jemmy specific classes
import org.netbeans.jemmy.*;
import org.netbeans.jemmy.explorer.*;
import org.netbeans.jemmy.operators.*;

// Get references to all UI widgets so that we don't have to re-import
// the Jemmy classes every time. The names of these variables will be
// stored in the "binding" and can be accessed in subsequent script/snippet
// executions
merchantOp = new JComboBoxOperator(mainFrame,0)
vendorOp = new JComboBoxOperator(mainFrame,1)
processButtonOp = new JButtonOperator(mainFrame,"Process")
backButtonOp = new JButtonOperator(mainFrame,"Back")




// Creating orders for a merchant involves creating orders for each vendor for the merchant.
def processMerchantVendor(count,merchants) {
t = new Thread() {
count.times {
try {
merchants.each { merchantIndex ->
merchantOp.selectItem(merchantIndex)
if (merchantOp.selectedItem == "some_merchant") return
(0..vendorOp.itemCount).each { vendorIndex ->
if (merchantOp.itemCount > merchantIndex && vendorOp.itemCount > vendorIndex) {
Thread.sleep(50)
vendorOp.selectItem(vendorIndex)
Thread.sleep(50)
processButtonOp.push()
Thread.sleep(50)
backButtonOp.push()
Thread.sleep(50)
backButtonOp.push()
}
}
}
} catch (Exception e) { }
}
}
t.start()
}

// This creates test orders for all merchants, 10 times in a row.
processMerchantVendor(10,(1..merchantOp.itemCount))