Skip to main content

Jemmy 3 user tutorial

Note 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

Lookup

Jemmy2 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 Parent<SomeControlType> which would be some class which is aware of a set of controls of SomeControlType 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, because Parent instance knows what is it dealing with.

Having the Parent, you could proceed to getting Lookup<SomeControlType> instance by calling, what would you think, lookup(LookupCriteria<SomeControlType>) method.
		// this gives an instance of Lookup<SomeControlType>
        someParent.lookup(new SomeLookupCriteria<SomeControlType>())
        
Jemmy2 LookupCriteria used to be called ComponentChooser.

So, out of those components which someParent is aware about, you so far picked those which pass the SomeLookupCriteria. Similarly, you could narrow he search with a LookupCriteria and the control type:
		// this gives an instance of Lookup<SomeControlSubType>
        someParent.lookup(Class<SomeControlSubType>, new SomeLookupCriteria<SomeControlSubType>())
        
where SomeControlSubType extends SomeControlType

Lookup<SomeControlType> represents a set of controls, so you could proceed directly to examining one of those by index. You could use get(int) method to get one of this. It is important to notice that the method returns an instance of SomeControlType, so you do not need to cast it.

You could also check how many are there (size()) or ask the lookup to wait for at least a certain number of them (wait(int)) to be present.

You could also get one of the controls wrapped for you (wrap(int)) 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 Lookup is that it itself is a Parent<SomeControlType> (or Parent<SomeControlSubType>). Thus, you could proceed with narrowing down the search further with lookup(LookupCriteria) method (or lookup(Class, LookupCriteria)).

So, finally, looking up for a control gets to ...
        someParent.lookup([Class, ]LookupCriteria)
		    [.lookup([Class, ]LookupCriteria) ...]
		    .(get(int)|control()|size()|wait(int))
        
It is also worthy to note that, if the default abstract Jemmy Core's implementation of Lookup is used, the actual component hierarchy is not examined up to the moment it has to be. That is, lookup(...) 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 get(int)|control()|size()|wait(int) 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.

Wrapping

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.

Jemmy2 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 wrap(int) 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 Wrap<CONTROL> class, which, itself, provides core functionality to perform mouse and keyboard operations on the control.

Interfaces

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 type(String) method for a text field of any kind.

That's where the ControlInterface concept comes to the game. Having a wrapper, gotten by wrap(int) method, you could proceed to treating is as MyInterface (which is a class/interface implementing/extending ControlInterface):
        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 MyInterface by calling myControl.is(MyInterface.class), 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");
        

Parameterised interfaces

Some interfaces could itself be parameterised by a type. One example of such is Parent<CONTROL> which is itself an interface.

To decrease amount of necessary casting you could use is(Class interfaceClass, Class parameterClass) and 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.
	someParent.lookup(new SomeCriteria<SomeControlType>()).wrap(0)
	    .as(Parent.class, SomeOtherControlType.class)
	    .lookup(new SomeOtherCriteria<SomeOtherControlType>).get(0)
        
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 wait(int) on a lookup, it waits for a given number of controls to exist which pass the criteria, when you call get(int) or wrap(int), it first calls wait(int) 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 Waiter supplied by Jemmy.

If the time expires, TimeoutExpiredException 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 Environment instance.

Environment

Any Parent or Lookup always have an instance of Environment associated with it. Whenever Wrap is created, Environment is cloned (i.e. child Environment is created) and assigned to the Wrap.

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 Wrap (by changing its environment) or the while hierarchy (by changing Environment associated with a Parent).

 
 
Close
loading
Please Confirm
Close