Author:
Alexandre Iline
,
Jiri Skrivanek
Last modified: April 13, 2005
Writing Jelly Tests in NetBeans
Related modules:
Jemmy |
Test Tools |
Jemmy Support |
XTest
Table of Contents:
Introduction
This document describes how to write GUI tests for NetBeans IDE using
the Jelly library. Jelly contains classes to cover most of the
functionality
necessary to work with NetBeans IDE core GUI components and windows.
See Writing
Operators Guide for instructions how to create classes covering
non-core (i.e. module specific) NetBeans GUI parts.
Jelly is based on the Jemmy
library. Jelly provides direct access to Jemmy functionality, so you
can
use Jemmy in your tests directly. (See more info about using Jemmy below). However, it would be highly recommended to
put as much functionality as possible into module specific operators.
To demonstrate Jellytools operators we prepared several examples (examplesProject.zip) which will be used
in this document.
How to execute tests
UI tests based on Jellytools can work only if they are executed in the
same JVM as the tested application. In order to fulfill this requirement
you need to use a special action provided by the JemmySupport module. Steps
to run Jellytools examples are as follows:
- run NetBeans IDE
- install modules Jellytools, Jemmy Module, JemmySupport and NB
JUnit (you can either build these modules yourself or download them
from the NetBeans Update Center - main menu Tools|Update Center - under
category Testing Tools)
- unzip examplesProject.zip
to a working directory
- open the JellytoolsExamples project in the NetBeans IDE
- check that under the Libraries node are Jellytools, Jemmy, JUnit and
NB JUnit libraries
- open or select a test you want to execute
- run the test by using the 'Run Internally' action from the JemmySupport toolbar
(or use the shortcut Ctrl+Alt+F6 or main menu item Tools|Run Internally)
- output is printed to the console (on Windows start NetBeans
with the nb.exe launcher to
open the console)
How to create tests
Tests are based on the
JUnit framework.
We recommend using
JellyTestCase
as a superclass. It performs correct Jemmy initialization and
handles test case exceptions. Tests which should run daily can be
deployed in a test harness. In NetBeans we use the
XTest harness.
Example:
import org.netbeans.jellytools.JellyTestCase;
import org.netbeans.junit.NbTestSuite;
public class EmptyTest extends JellyTestCase {
public EmptyTest(String testName) {
super(testName);
}
public static void main(java.lang.String[] args) {
junit.textui.TestRunner.run(suite());
}
public static NbTestSuite suite() {
NbTestSuite suite = new NbTestSuite();
suite.addTest(new EmptyTest("test1"));
suite.addTest(new EmptyTest("test2"));
return suite;
}
public void test1() {
}
public void test2() {
}
}
This test does not really do anything - working code should be in the
test1(), test2() methods.
Pay attention, though, to the
main(String[])
method. It's optional, however, it's a good idea to have this method - it
allows running the test directly from the IDE (see
above).
To get various ideas you can also take a look at Jellytools
internal tests.
JellyTestCase
JellyTestCase is an extension of NbTestCase intended for Netbeans GUI
testing explicitly.
JellyTestCase initializes Jemmy. Contents of that initialization can
be changed at any time. For now, it initializes output so test output
is
really going to a log provided by NbTestCase and sets some timeouts
necessary for NetBeans GUI testing.
Any JemmyException (which is normally thrown as a result
of an
unsuccessful operation in Jemmy) going from a test is treated by
JellyTestCase as a test failure; any other exception - as a test error.
Using its public switches you can define whether a test should
- close all modal dialogs at the end of the test case (switch jemmy.close.modal
- default true)
- generate component dump (XML file containing components
information) in case of test failure (switch jemmy.screen.xmldump
- default false)
- capture screen into a PNG file in case of test failure (switch jemmy.screen.capture
- default true)
- wait at least 1000 ms between test cases (switch jelly.wait.no.event - default
true)
What to use
Jelly classes could be divided into five structural parts:
Operators
Jemmy operators
is a set of classes which are test-side agents for application
components. Operators provide all possible methods simulating user
action with components, methods to find and wait components and
windows.
Also operators map all components methods through the event queue used
for event dispatching. All Jellytools operators are subclasses of Jemmy
operators.
All of the operators provide access to their subcomponents by
"getters" methods. These methods are implemented using the "lazy
initialization"
technique, so real suboperator instances are not initialized until it's
necessary. All of the suboperators are initialized by verify()
method invocation, so this method guarantees that all
subcomponents are already loaded.
Getters method names usually have a prefix showing component type, the
rest of the names usually repeat component text or text of the
corresponded label: JButtonOperator btCancel(), JRadioButtonOperator
rbSubString(), JTextFieldOperator txtName(). You can see
a full list of prefixes in a table
shown in the Writing Operators Guide.
Some of the operators have an
invoke() method which causes
component displaying using an action. You can use these actions
directly
- their names usually look like
*ViewAction (
RuntimeViewAction
for
RuntimeTabOperator).
Most common Jellytools operators:
- TopComponentOperator
- is an operator for
org.openide.windows.TopComponent component. It represents all dockable
views in the IDE like Projects view, Editor views and others.
TopComponentOperator can be used where no special operator like
ProjectsTabOperator is not implemented.
- NbDialogOperator
- is the common superclass for all
dialog operators in Jelly. It has some common methods for pushing the most used
buttons on dialogs: "OK", "Cancel", "Close", "Help", "Yes", "No".
- TreeTableOperator
- handles the IDE's org.openide.explorer.view.TreeTable component which is
used instead of JTree, for example in the Options dialog and SetupWizard
- MainWindowOperator - handles the NetBeans main window. It
manipulates with toolbars and you can get text from the status bar.
- ProjectsTabOperator,
FilesTabOperator,
FavoritesOperator,
RuntimeTabOperator
- specialized TopComponentOperators for tabs in the IDE
- EditorOperator
- handles an editor top component in the NetBeans IDE. It enables you to
get, select, insert or delete text, move caret, work with annotations
and with toolbar
buttons.
- OutputTabOperator - operator to get text from
the output tab.
Look at OperatorsTest.java file.
Actions
Action classes mission is to replace menu and popup calls.
Most of the actions inside the NetBeans IDE could be done in some
different modes. org.netbeans.jellytools.actions.Action class
is designed to describe all the modes in which the action could be performed:
- by menu
- by popup
- by shortcut
- by direct API call
Not all of the actions can be performed by all modes above, however,
most of the menu/popup operations in Netbeans can also be done by
shortcuts and direct API calls. Keeping that in mind, try to use a
properly created Action instance whenever you want to perform menu or
popup operations. Even if you do care to perform the operation exactly by
menu (popup), create an action. The very same code could be used in a
different place in another mode.
Using the perform() method of any action will perform
the action by the first available mode that could be performed.
The sequence of modes to be tried is defined by the Action.setDefaultMode(int)
value.
Example (from ActionsTest.java):
CopyAction copyAction = new CopyAction();
copyAction.perform(node);
Example above is performed in default mode. Copy action can also be
performed in any of the other available modes.
Example (from ActionsTest.java):
copyAction.performMenu(node);
copyAction.performShortcut(node);
copyAction.performAPI(node);
copyAction.performPopup(node);
Actions can also be performed on a
node, array of
nodes, and ComponentOperator. If the action is performed on a node (nodes),
node
is
selected first.
Action classes are in the org.netbeans.jellytools.actions
package and module specific in org.netbeans.jellytools.modules.<modulename>.actions
packages.
Nodes
Node objects are designed to make tree operations easier. Node
encapsulates information about the tree it's displayed in and about the tree
path. Node classes can be used to perform applicable operations to the
nodes in any tree in the IDE. Nodes can also be passed into
Action.perform*(Node) methods.
There are some useful node types defined in Jellytools, such as: ClassNode,
FolderNode, ProjectRootNode.
Example (from NodesTest.java):
ProjectsTabOperator pto = new ProjectsTabOperator();
Node node = new Node(pto.tree(), "JellytoolsExamples|Source Packages|examples");
Node node1 = new Node(node, "NodesTest.java");
node1.select();
JavaNode javaNode = new JavaNode(node, "NodesTest.java");
javaNode.copy();
Nodes classes are in the
org.netbeans.jellytools.nodes package
and
org.netbeans.jellytools.modules.<modulename>.nodes
packages.
Properties
org.netbeans.jellytools.properties package contains classes to
work with properties. Besides
PropertySheetOperator,
it contains classes - inheritors of
org.netbeans.jellytools.properties.Property
class.
Property value can be changed by inline editors (text field for
string values, check box for boolean values - setValue(String) method and
combo box for list values - setValue(int)
method.
Example (from PropertiesTest.java):
PropertySheetOperator pso = new PropertySheetOperator("PropertiesTest.java");
Property p = new Property(pso, "Encoding");
System.out.println("PROPERTY: "+p.getName()+"="+p.getValue());
p.setValue("ASCII");
Any property may or may not have a "..." button invoking a property
editor. If a property has this button, it can have any number of set*Value(*)
methods using the editor for property editing.
Some properties might be not editable directly (other than by the property
editor). They extend Property and override setValue(*)
methods
to use the property editor. But if a property is editable directly, which
most of them are, setValue(*) methods work directly.
Property editors are another class of operators which are located
inside the org.netbeans.jellytools.properties.editors package and
org.netbeans.jellytools.modules.<modulename>properties.editors
packages.
Examples of property editors are: ColorCustomEditorOperator,
DimensionCustomEditorOperator, FileCustomEditorOperator,
PointCustomEditorOperator, StringCustomEditorOperator.
Property editor can be invoked by the Property.openEditor()
method.
Example (from PropertiesTest.java):
p.openEditor();
StringCustomEditorOperator customEditor = new StringCustomEditorOperator("Encoding");
System.out.println("Encoding value="+customEditor.getStringValue());
customEditor.setStringValue("ASCII");
customEditor.ok();
Wizards
There are some operators in Jelly providing functionality covering
NetBeans wizards:
WizardOperator, NewFileWizardOperator,
NewFileNameLocationStepOperator, NewProjectWizardOperator, NewProjectNameLocationStepOperator,
NewWebProjectNameLocationStepOperator, NewWebProjectSourcesStepOperator.
Example (from WizardsTest.java):
NewFileWizardOperator nfwo = NewFileWizardOperator.invoke();
nfwo.selectProject("JellytoolsExamples");
nfwo.selectCategory("Java Classes");
nfwo.selectFileType("Java Class");
nfwo.next();
NewFileNameLocationStepOperator nfnlso = new NewFileNameLocationStepOperator();
nfnlso.txtObjectName().typeText("MyNewClass");
nfnlso.finish();
Using bundles.
Moving most of the functionality into operators makes tests live longer
with no changes in the tests themselves, because most of the changes in tested
code can be covered by operators changing. However, some of the
functionality still could be uncovered.
One of the places where tests are tied to tested code version is
string resources. Again, most of the resources will be used in
operators. But the rest of them should be taken from NetBeans bundles,
so even if a resource is changed for NetBeans, the tests use the right
resource value anyway.
It can be done using the org.netbeans.jellytools.Bundle
class. To know which bundle and key is used for a particular label,
look at the sources or use other available
methods.
Example:
String confirmTitle = Bundle.getString("org.openide.explorer.Bundle", "MSG_ConfirmDeleteObjectTitle");
String helpItem = Bundle.getStringTrimmed("org.netbeans.core.Bundle", "Menu/Help");
Using a Bundle class automatically makes I18N application
testing possible, because localized resource values will be used instead.
Using Jemmy in tests
Classes provided by Jelly have most methods necessary to perform
operations in the "usual" way. So, if you do not need to do something
"unusual", methods you need are probably there already.
As was mentioned above Jelly operators extend Jemmy operators, so
you
can use them as regular Jemmy operators. Additionally, most of the
Jelly
operators have shortcuts to get operators for most of their
subcomponents. These operators are, again, either Jemmy operators or
Jelly operators. So there is no need to create any operator yourself.