Extending Jubula for Custom SWT Controls in RCP applications (Updated for Jubula 1.3)

What to do if Jubula can not map the (custom) SWT control you need for your test?

Jubula is a UI testing tool for eclipse rich client applications. For testing you have to map the SWT controls like text fields, labels, tables, trees or buttons of your application under test (AUT) to Jubula components. After that Jubula is able to find the controls and perform actions like clicking the button or entering text into the textfield. In the end verifications can be done on the controls in the test cases, e.g. whether a label displays the expected text or not.

But what to do if Jubula does not draw this funny green border around the control you need in your test, indicating that you can map it when pressing Ctrl+Shift+Q? This is the moment to extend Jubula by yourself. In this article I describe the steps we have done to include our custom control (a SWT canvas) into Jubula tests. We implemented a general solution to support any SWT control in Jubula, so it might help you with your own controls. To sum it up, all you need is a little fragment for your RCP application (the AUT) and a plug-in for the Jubula client.

The Problem

In the Sophora Deskclient (our RCP application) we developed a search view where users can search for model objects, called documents. Each search result entry has to display several datas of the found documents, e.g. the document's title, identifier, thumbnail, modification date an so on. Instead of composing several SWT labels in a composite we implemented the entries as SWT canvas controls and let the document datas be painted by a PaintListener. We use this approach to reduce the number of USER-objects each SWT control requires in Windows systems, but this is another story. Here is a screenshot of the search view.

Sophora Deskclient Search View

In our test scenario we want to search a certain document and check whether the correct document has been found. After that we want to drag it from the search view to another place in the UI, the bookmarks view.

Because canvas controls are not supported in Jubula by default we can not map them. So it is impossible to drag the search result entry within a Jubula test. Moreover we can not verify the displayed content of the canvas like we could do it with SWT labels because the texts are only drawn by a paint listener and we have no methods to get or check them.

The Solution

First we have to resolve the mapping problem. Jubula can not map the canvas because it finds no Tester Class for this kind of SWT control. The verification problem can be solved by using Control.setData(key, value) on the canvas in the AUT. All we have to do in our application is to call canvas.setData("title", <The Title Of The Document>) to hold the value that is painted on the canvas in the data map of the SWT control. Then we need a verification method in the Tester Class to check the value we expect for the "title" key.

The following steps suppose that you have

  • installed the Jubula Standalone version (we use version 1.3, see http://eclipse.org/jubula/download.php),
  • added the required Jubula extension to your RCP application (your AUT) for testing it with Jubula (rcp-support.zip),
  • some knowledge about developing RCP plug-ins and fragments (of course you should have this, you want to test your RCP application!),
  • some knowledge about creating UI tests in Jubula.

Before you start you have to configure the target platform in your Eclipse IDE to Jubula. Simply select the folder <jubula-install-directory>/jubula/plugins as target platform in the preferences. If you use Jubula 1.2 (or higher) you also have to select an Eclipse 3.7.2 installation to resolve the dependencies to org.eclipse.ui plug-ins.

The next two sections describe how to build the Tester Class for the control and a toolkit that provides the Tester Class to the Jubula client.

The Tester Class for the Custom Control

1. Create a new fragment project for the host plug-in org.eclipse.jubula.rc.rcp (we call the fragment com.subshell.jubula.extension.rcp.aut).

2. Create a new lib-folder within the fragment and add the files

  • org.eclipse.jubula.rc.common.jar,
  • org.eclipse.jubula.rc.swt.jar and
  • org.eclipse.jubula.tools.jar.

You can get these files from the rcp-support.zip file or from the lib-folder within \jubula\plugins\org.eclipse.jubula.rc.rcp_<version>.jar in your Jubula install directory. Finally add the files to the classpath on the Runtime tab of the MANIFEST.MF editor.

3. Create a new package org.eclipse.jubula.rc.swt.implclasses and locate your Tester Class there. The name of the package is important. Each Tester Class has to extend AbstractSwtImplClass for handling the corresponding control. Every public method of this class may be called in a test step by Jubula to perform an action on the control or to do verifications on it. In our case we simply extend SimpleExtendedComponentImplClass and add a public method to verify the data that has been set on the control. The control and its data is fetched in a Runnable to avoid invalid thread access exceptions. The check for the expected value is done by the Verifier. It throws a StepVerifyFailedException if the equality check fails. The following code sample demonstrates the new Tester Class (ExtendedControlImplClass):

package org.eclipse.jubula.rc.swt.implclasses;
import org.eclipse.jubula.rc.common.driver.IRunnable;
import org.eclipse.jubula.rc.common.implclasses.Verifier;
public class ExtendedControlImplClass extends SimpleExtendedComponentImplClass {
	
	/**
	 * Verifies if the <code>expectedValue</code> is set by <code>control.setData(key, value)</code>
	 * for the given <code>key</code>.
	 * @param key the data key
	 * @param expectedValue the expected value
	 */
	public void verifyControlData(final String key, final String expectedValue) {
		String actualValue = (String) getEventThreadQueuer().invokeAndWait(
            "getDataFromControl", new IRunnable() {
                public Object run() {
                	Object data = getComponent().getData(key);
    				if (data != null) {
    					return data.toString();
    				} else {
    					return "";
    				}
                }
            });
	    Verifier.equals(expectedValue, actualValue);
	}
	
}

4. Now the fragment should look like this. Configure the binary build properties to contain the folders META-INF and lib on the Build tab of the build.properties editor.

Jubula RCP AUT Extension Project

5. Build the fragment by using the export wizard: Open the context menu of the project and select Export... > Deployable plug-ins and fragments. On the next wizard page enter a target directory and deselect the Package plug-ins as individual JAR archives checkbox on the Options tab. Then finish the wizard.

6. Finally copy the resulting jar file into the plugins folder of your AUT.

The Toolkit Provider

At this point we have a Tester Class for our control. Now we need a toolkit provider that introduces this Tester Class to the Jubula client.

1. Create a new simple plug-in project (we call the plug-in com.subshell.jubula.extension.client).

2. On the Dependencies tab of the MANIFEST.MF editor add the bundles

  • org.eclipse.core.runtime,
  • org.eclipse.ui and
  • org.eclipse.jubula.toolkit.common

to the required plug-ins list.

3. The Activator of the plug-in should look like this:

package com.subshell.jubula.extension.client;
import org.eclipse.core.runtime.Plugin;
import org.osgi.framework.BundleContext;
public class Activator extends Plugin {
	
    /** The plug-in ID */
    public static final String PLUGIN_ID = "com.subshell.jubula.extension.client";
    /** The shared instance */
    private static Activator plugin;
    /**
     * The constructor
     */
    public Activator() {
        plugin = this;
    }
    /**
     * {@inheritDoc}
     */
    public void start(BundleContext context) throws Exception {
        super.start(context);
    }
    /**
     * {@inheritDoc}
     */
    public void stop(BundleContext context) throws Exception {
        plugin = null;
        super.stop(context);
    }
    /**
     * Returns the shared instance
     * 
     * @return the shared instance
     */
    public static Activator getDefault() {
        return plugin;
    }
}

4. Create a new folder resources/xml and place the following ComponentConfiguration.xsd and ComponentConfiguration.xml files there.

Content of ComponentConfiguration.xsd:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:include schemaLocation="../../../ToolkitSupportPlugin/resources/xml/ToolkitComponentConfiguration.xsd"/>
</xs:schema>

Content of ComponentConfiguration.xml:

<?xml version="1.0" encoding="ISO-8859-1"?>
<compSystem xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ComponentConfiguration.xsd" basicTesterClass="org.eclipse.jubula.rc.swt.implclasses.SimpleExtendedComponentImplClass">
	<configVersion>
		<majorVersion>1</majorVersion>
		<minorVersion>0</minorVersion>
	</configVersion>
	
	<!-- Concrete components -->
	
	<toolkitComponent type="org.eclipse.swt.widgets.Control">
		<realizes>guidancer.abstract.Widget</realizes>
		<testerClass>org.eclipse.jubula.rc.swt.implclasses.ExtendedControlImplClass</testerClass>
		<componentClass name="org.eclipse.swt.widgets.Control" />
		<action name="Control.VerifyControlData">
			<method>verifyControlData</method>
			<param name="Control.Key">
				<type>java.lang.String</type>
			</param>
			<param name="Control.ExpectedValue">
				<type>java.lang.String</type>
			</param>
		</action>
	</toolkitComponent>
	
</compSystem>

As you can see, the component configuration tells Jubula which tester class it has to use for which type of control. In our case it is the ExtendedControlImplClass for any org.eclipse.swt.widgets.Control. (As well as declaring the ExtendedControlImplClass for any Control we could reduce it to org.eclipse.swt.widgets.Canvas at this place.) The XML also defines which actions can be performed on the concrete Tester Class. Here it is the public method verifyControlData() we have implemented in the ExtendedControlImplClass above. The method has two string parameters, "key" and "expectedValue". The declared names "Control.xyz" are placeholders for internationalization in the UI of Jubula. We will create the I18N file in the next step.

5. Create a new package com.subshell.jubula.extension.client.i18n and add an i18n.properties file where you map the placeholders from the ComponentConfiguration.xml to the correct labels:

Control.ExpectedValue=Expected Value
Control.Key=Key
Control.VerifyControlData=Verify Control Data
org.eclipse.swt.widgets.Control=Control

6. Now create another package com.subshell.jubula.extension.client.provider and locate your ToolkitProvider class there. The toolkit provider will tell Jubula where to find the ComponentConfiguration.xml and i18n.properties file.

package com.subshell.jubula.extension.client.provider;
import java.net.URL;
import java.util.Map;
import java.util.ResourceBundle;
import org.eclipse.jubula.toolkit.common.IToolKitProvider;
import org.eclipse.jubula.toolkit.common.exception.ToolkitPluginException;
import org.eclipse.jubula.toolkit.common.utils.ToolkitUtils;
import org.eclipse.swt.widgets.Composite;
import com.subshell.jubula.extension.client.Activator;
public class ToolkitProvider implements IToolKitProvider {
    /** the bundle location */
    public static final String BUNDLE = "com.subshell.jubula.extension.client.i18n.i18n";
	
    public URL getComponentConfigurationFileURL() {
        return ToolkitUtils.getURL(Activator.getDefault(), "resources/xml/ComponentConfiguration.xml");
    }
    public ResourceBundle getI18nResourceBundle() {
        return ResourceBundle.getBundle(BUNDLE);
    }
    public Composite getAutConfigDialog(Composite parent, int style, Map<String, String> autConfig, String autName) throws ToolkitPluginException {
        return null;
    }
}

7. Finally create an extension for the extension point org.eclipse.jubula.toolkit.common.toolkitsupport to add the ToolkitProvider to Jubula.

Jubula Toolkit Provider Extension

8. That's it! The plugin should look like this. The plug-in is built by using the export wizard in the same way as the fragment. Do not forget to configure the binary build in the build.properties to include META-INF, resources and plugin.xml.

Jubula Client Extension Project

9. Afterwards copy the resulting plug-in jar file into the directory <jubula-install-directory>/jubula/plugins.
If you use Jubula 1.2 (or higher) you have to add an additional line to <jubula-install-directory>\jubula\configuration\org.eclipse.equinox.simpleconfigurator\bundles.info with the following structure:
<Bundle-SymbolicName>,<Bundle-Version>,plugins/<deployedBundleName[.jar]>,4,false
e.g.
com.subshell.jubula.extension.client,1.3.0.201302081745,plugins/com.subshell.jubula.extension.client_1.3.0.201302081745.jar,4,false
(see Jubula Bug 337)

The Jubula Test

(Re-)Start the Jubula client and your AUT. In mapping mode you will now be able to select your (and any other) control in the RCP application:

Search Result Mapped By Jubula

To use the new extension, you have to create a new Test Case (e.g. Verify control data). Open this new Test Case and add a new Test Step to it. In the opening dialog you should be able to select the test action Verify Control Data for the component type Control:

New Test Step Dialog

Now you can set the parameters "key" and "expectedValue" by selecting the new Test Step:

Jubula Test Case for Custom SWT Control

Finally run the test!

Troubleshooting

If the Jubula test fails with a configuration error, take a look at the log files of jubula placed in <user>\.jubula\logs. The rc_rcp.log seems to be the most relevant log file because the really interesting and most helping exceptions are logged there.

Conclusion

With this simple Jubula extionsion we are now able to test our search view that uses canvas controls which Jubula does not support by default. A simple Test Step verifies the documents title we expect and it is possible to drag the search result entry into the bookmarks view.

Note that you can add more custom actions (public methods) to the Tester Class and declare them in the ComponentConfiguration.xml file to call these methods in Test Steps. You are also able to create a more specific Tester Class for your special custom control and involve it to Jubula by declaring it in the ComponentConfiguration.xml file for your control type instead of the general type org.eclipse.swt.widgets.Control. For example, we could define a more special CanvasImplClass only for org.eclipse.swt.widgets.Canvas.

Download

The following zip file contains the built fragment and plug-in projects we created above, including the sources. Just add them to your Jubula installation and RCP AUT for using them or take a look at the code to develop your own Jubula extension.

Using Groovy in Eclipse RCP

How to use Groovy code in Eclipse RCP applications? more

Eclipse RCP: Hiding a View inside the View Dialog

more

Working with Eclipse, Maven and WTP

more

Sophora CMS: A New Take on Content Management

Fast. Powerful. Flexible. Easy to use. Comfortable. We built our CMS Sophora with these key challenges in mind. more

Torsten Witte

2013-02-11 • 11:30 上午

Java, Eclipse