Jemmy 3 user tutorial
This tutorial is complementary to javadoc
it only describes principles of Jemmy API usage - not the API itself.
Jemmy 3 have several fundamental principles which everybody should get himself
familiar with before starting test implementation.
Relations to Jemmy2
. If you are familiar with the Jemmy 2 API
(TBD link), you would find this totally different. Everything is
implemented in a different way and there are good reasons for that.
During the tutorial, I will sometimes compare Jemmy 3 & 2 design
principles. If you are not familiar with Jemmy2, just ignore those paragraphs.
Jemmy3, firstly, consist of Jemmy Core module which provide functionality
not dependent on any particular UI library weather AWT or Swing or
anything else. Even java.awt.Robot is abstracted from the core so
that mobile testing is possible.
The three main aspects of UI testing are:
- find the control you will be dealing with
- simulate user input
- wait for something to happen
So, in Jemmy2, the lookup functionality was implemented through
"operator"'s constructor and also not-so-convenient static methods.
This (the constructors) is acceptable and proven to be good approach
for every particular UI component library. It is possible to implement
the lookup functionality in a similar way with Jemmy 3. The "generic"
Jemmy 3 approach is quite different, however.
So, how do you go about finding a control to deal with. First you have
to know some
which would be some class which is aware of a set of controls of
type. The set could either be hierarchical,
which is typically the case - take AWT or Scenegraph or just about anything,
or a plain structure - it does not matter from the usage point of view,
instance knows what is it dealing with.
, you could proceed to getting
instance by calling, what would you think,
// this gives an instance of Lookup<SomeControlType>
used to be called
So, out of those components which
is aware about, you so far picked those which pass the
. Similarly, you could narrow he search with a
and the control type:
// this gives an instance of Lookup<SomeControlSubType>
someParent.lookup(Class<SomeControlSubType>, new SomeLookupCriteria<SomeControlSubType>())
represents a set of controls, so you could proceed directly to examining
one of those by index. You could use
method to get one of this. It is important to notice that the method
returns an instance of
, so you do not need to cast it.
You could also check how many are there (
or ask the lookup to wait for at least a certain number of them
) to be present.
You could also get one of the controls wrapped for you
so that you could use some test logic of dealing with the control.
The wrapping will be described in details later in the tutorial.
Perhaps, the most important feature of
is that it
itself is a
). Thus, you could
proceed with narrowing down the search further with
So, finally, looking up for a control gets to ...
[.lookup([Class, ]LookupCriteria) ...]
It is also worthy to note that, if the default abstract Jemmy Core's implementation of
is used, the actual component hierarchy is not examined up to the moment it has to be. That is,
methods itself do not go through the list of controls trying to apply the criteria, when they called. It is only when one of the
methods called, the hierarchy is explored. Hence, there is neither memory spend for creating intermediate lists nor performance is wasted.
For more information check lookup tutorial
Now, finding out the control is a necessary step to do. However,
in order to deal with the control - to simulate user input,
such as text typing, mouse pressing, whatnot ... you need to wrap
the control into something which knows how to threat it.
Such classes were the "operators". Wrapping happened naturally as the lookup was implemented through the operators' constructors.
In Jemmy 3, the wrapping happens when you call the
methods of a lookup. Naturally, the wrappers are UI library specific, so, Jemmy Core could not possible be aware about them. The library specific Jemmy Core extension is taking care about supplying them.
All of the wrapper classes extend
class, which, itself, provides core functionality to perform mouse and keyboard operations on the control.
Mouse and keyboard operations is only the basis for user interaction simulation. It is also the only functionality which is applicable to every control, independently of its type. There is much more to user input than this. Good example is text typing. The test developer would expect something in a way of
method for a text field of any kind.
That's where the
concept comes to the game. Having a wrapper, gotten by
method, you could proceed to treating is as
(which is a class/interface implementing/extending
myControl.as(MyInterface.class) /// this would be an instance of MyInterface
You could verify in the test code if the wrapper actually could be used as
, however generally it is up to Jemmy Core extension developer which interfaces the wrapper implement.
Getting back to our example of the text typing, the test developer would be able to use code like this:
myControl.as(Text.class).type("some new text");
Some interfaces could itself be parameterised by a type. One example of such is
which is itself an interface.
To decrease amount of necessary casting you could use
is(Class interfaceClass, Class parameterClass)
as(Class interfaceClass, Class parameterClass)
methods. It could look like this:
// this would give an instance of SomeOtherControlType which leaves within
// some instance of SomeControlType.
For more information on Jemmy interfaces check interfaces tutorial
Waiting and timing
It is important to understand that just about any call in Jemmy involves some time checking. When you call
on a lookup, it waits for a given number of controls to exist which pass the criteria, when you call
, it first calls
method to ensure that big enough number of controls are there.
In addition, when you call just about any operation on a control, it has its own time limit to be completed within.
Should you need to implement your own waiting or time check of any kind, it is a good idea to use
supplied by Jemmy.
If the time expires,
is thrown. That allows not to worry about time checking within the test itself. If something goes wrong, test will simply fail with the exception. It also allows to check the negative scenarios by catching the exception.
All the timeouts are specified through
always have an instance of
associated with it. Whenever
is cloned (i.e. child
is created) and assigned to the
Environment hierarchy is build the way that you could always change an environment instance not worrying about other parts of the test behaving differently. All the child environments for the one you are changing will behave differently, which gives you the power to customise behaviour of single
(by changing its environment) or the while hierarchy (by changing
associated with a