The abstract object based Selenium Test framework (AOST) is designed not only for user acceptance test (UAT) but also for more general UI tests for developers since nowadays more and more people work in an agile environment. The requirement keeps changing and people keep refactoring and testing the UI. Selenium tests are usually created by using Selenium IDE to record Web UI Events and then replay them. The disadvantages include Fragile, not really work in a dynamic environment. It is not module based and hence difficult to refactor and maintain.
The AOST test framework does not depend on Selenium IDE at all. It uses UI components to modularize and to abstract the UIs. Furthermore, you can start to write UI tests while you are doing code development. Other advantages include reusable, expressive, easier to refactor and maintain.
The AOST framework introduces a new Domain Specific Language (DSL) and an object to locator mapping (OLM) framework to make it much easier for users to write UI tests. The framework has the following features:
- Include a Domain Specific Language (DSL) to define UI objects and write selenium tests
- Written in Groovy, test cases can be written in Java or Groovy
- Include an Object to Locator Mapping framework (OLM)
- Make it possible for users to write Selenium tests when they start coding
- Reusable, expressive, easier to refactor and maintain
- Provide DSL script executor so that non-developers can write Selenium tests in DSL
The project is located at
http://code.google.com/p/aost
For detailed introduction, please see
http://code.google.com/p/aost/wiki/Introduction
July 13, 2008 at 5:24 am
The 0.3.0 verion is just out, which includes an Object to Locator Mapping framework (OLM),. With that, AOST can automatically generate the UI object locator for you. AOST also implements the group locating concept to utilize a group of UI objects to help locating their locators. The new version also add the composite locator so that user specifies a set of parameters for the object and the actual locator will be derived automatically by AOST. The new version also supports multiple UI modules in a single DslContext.
The Group Locating Concept (GLC) concept comes from the simple observation, i.e., it is much easier to locate a collection of UI objects than a single UI object. The reason is multiple UI objects provide more search criteria to help you to locate the UIs. This concept is implemented in AOST. All collection type object, i.e., Container and its extended classes have an option to use the group locating concept.
Let’s see the differences between regular locating and group locating. Still take the google start page as an example:
ui.Container(uid: “google_start_page”, clocator: [tag: "td"], group: “true”){
InputBox(uid: “searchbox”, clocator: [title: "Google Search"])
SubmitButton(uid: “googlesearch”, clocator: [name: "btnG", value: "Google Search"])
SubmitButton(uid: “Imfeelinglucky”, clocator: [value: "I'm Feeling Lucky"])
}
Here, clocator stands for composite locator. Without group locating, I can only use the given criteria
tag = “td”
to find the container locator, i.e.,
I am looking for a td html tag in the DOM
which is quite difficult. If we use the group locating, the Container can use the group information provided by its children. Now, the problem becomes:
I am looking for a td html tag in the DOM and its children including an input box with
title “Google Search”, a submit button with name “btnG” and value “Google Search”, and
another submit button with value “I’m Feeling Lucky”
Woo! I have a lot of information to help me to locate the container. Once the container is found, all its children can be found very easily.
The direct result of group locating is that for most case, the information defined in the UI module itself is enough for you to locate all UI objects in that UI module. That is to say, your test code only depends on your own UI module, not its location and its outside. Even we change the UI, the test may still work.
Another advantage is that you may be able to map your JSP, PHP, ASP,…, file directly to the UI module in AOST. This could help developers a lot for writing Selenium tests because they do not need to manually find the locator for an UI object.
I added FAQ at:
http://code.google.com/p/aost/wiki/FAQ
Introduction at
http://code.google.com/p/aost/wiki/Introduction
and Tutorial at:
http://code.google.com/p/aost/wiki/Tutorial
July 19, 2008 at 3:29 pm
Groovy Patterns Used in AOST
In AOST(http://code.google.com/p/aost), we used many Groovy patterns. I like to list some of them as follows,
1) BuildSupport
BuildSupport is very powerful for parsing nested definitions, such as XML markup. In AOST, the buildsupport is used as UI object definition parser and it is one of the bases for our internel DSL.
UiDslParser extends BuilderSupport{
protected void setParent(Object parent, Object child) {
if(parent instanceof Container){
parent.add(child)
child.parent = parent
}
}
protected Object createNode(Object name) {
def builder = builderRegistry.getBuilder(name)
if(builder != null){
def obj = builder.build(null, null)
return obj
}
return null
}
//should not come here for Our DSL
protected Object createNode(Object name, Object value) {
return null
}
protected Object createNode(Object name, Map map) {
def builder = builderRegistry.getBuilder(name)
if(builder != null){
def obj = builder.build(map, null) return obj
}
return null
}
protected Object createNode(Object name, Map map, Object value) {
def builder = builderRegistry.getBuilder(name)
if(builder != null){
def obj = builder.build(map, (Closure)value)
return obj
}
return null
}
protected void nodeCompleted(Object parent, Object node) {
//when the node is completed and its parent is null, it means this node is at the top level
if(parent == null){
UiObject uo = (UiObject)node
//only put the top level nodes into the registry
registry.put(uo.uid, node)
}
}
}
2) Dynamic Scripting
In Groovy, you can define Groovy code as a String and then run them at run time, i.e, code generates code, which makes pure DSL tests possible. Our DslScriptExecutor? is a good example and can demonstrate the power of the dynamic scripting.
class DslScriptExecutor {
static void main(String[] args){
if(args != null && args.length == 1){
def dsl = new File(args[0]).text
def script = “”"
import aost.dsl.DslScriptEngine
class DslTest extends DslScriptEngine{
def test(){
init()
${dsl}
shutDown()
}
}
DslTest instance = new DslTest()
instance.test()
“”"
new GroovyShell().evaluate(script)
}else{
println(“Usage: DslScriptExecutor dsl_file”)
}
}
}
3) GroovyInterceptable
GroovyInterceptable can intercept all method calls so that you can do some processing before or after the invocation. Or you can delegate the method calls to another class.
For example, The AOST dispatcher will delegate all method calls it received to Selenium Client as follows,
class Dispatcher implements GroovyInterceptable{
private SeleniumClient sc = new SeleniumClient()
def invokeMethod(String name, args)
{
return sc.client.metaClass.invokeMethod(sc.client, name, args)
}
}
4) methodMissing
In Groovy, you can use “methodMissing” to intercept and delegate undefined method calls. For example, in DslScriptEngine?, the “methodMissing” method will catch and delegate all methods to DslAostSeleneseTestCase? except the “init”, “openUrl”, and “shutDown” methods:
class DslScriptEngine extends DslContext{
protected def methodMissing(String name, args) {
if(name == “init”)
return init()
if(name == “openUrl”)
return openUrl(args)
if(name == “shutDown”)
return shutDown()
if(DslAostSeleneseTestCase.metaClass.respondsTo(aost, name, args)){
return aost.invokeMethod(name, args)
}
throw new MissingMethodException(name, DslScriptEngine.class, args)
}
}
5) Singleton
You can Use Groovy MetaClass to define a singleton, which is the right Groovy way to define a singleton. For example, we have the EventHandler class
class EventHandler{
}
Then, we can define its MetaClass as follows,
class EventHandlerMetaClass extends MetaClassImpl{
private final static INSTANCE = new EventHandler()
EventHandlerMetaClass() { super(EventHandler) }
def invokeConstructor(Object[] arguments) { return INSTANCE }
}
After that, we register the metaClass:
def registry = GroovySystem.metaClassRegistry
registry.setMetaClass(EventHandler, new EventHandlerMetaClass())
In this way, all
Eventhandler handler = new EventHandler()
will return the same Instance, i.e., it is a singleton. Our AOST framework heavily depends on this pattern so that we do not need to use another framework to wiring different object together.
5) GString
GString can be used to embed variable in a String and the value will be resolved at run time. This makes it very convenient to write XPath template. For example, In the Selector object, we have
class Selector extends UiObject {
def selectByLabel(String target, Closure c){
c(locator, “label=${target}”)
}
def selectByValue(String target, Closure c){
c(locator, “value=${target}”)()
}
}
6) Optional Type
In Groovy you can specify a variable type if you know the type and you want to do Type check at compile time. If you do not care about the type or want the method to be more flexible, you do not need to define the type and just use “def” to define a variable without specifying its type.
For example, in the composite locator class we specified the Type of all variables except the position. Because type of UI component position might be a single type.
class CompositeLocator {
String header
String tag
String text
String trailer
def position
Map attributes = [:]
}
7) Groovy Syntax
In Groovy, the syntax is very expressive, for example, the DslContext? class defines a lot of methods such as:
def doubleClick(String uid){}
and it can be written as
doubleClick uid
and the above is one of the basic DSLs for AOST.
The Map definition is also very expressive and useful. AOST locators use map to define xpath or UI component attributes. For example:
ui.Container(uid: “google_start_page”, clocator: [tag: "td"], group: “true”){
InputBox(uid: “searchbox”, clocator: [title: "Google Search"])
SubmitButton(uid: “googlesearch”, clocator: [name: "btnG", value: "Google Search"])
SubmitButton(uid: “Imfeelinglucky”, clocator: [value: "I'm Feeling Lucky"])
}