ADempiere Testing with Sahi

From ADempiere
Revision as of 05:07, 8 February 2014 by MJMcKay (Talk) (Intermediate save)

Jump to: navigation, search
This Wiki is read-only for reference purposes to avoid broken links.

What is Sahi

Sahi (designed by Tyto Software Pvt.[1] in Bangalore, India) is an automated testing tool for web applications. There is a pro version sold by Tyto and an open source version which has fewer features but follows the pro version. The ADempiere test suite uses the open source version. For more information, visit the [Sahi web site].

How to install Sahi

The install is simple: From the Sahi web site find the link to the download for the Sahi Open Source product on sourceforge[2]). Download the latest version of the installation jar file.

For installation instructions - see the older documentation.

Configuration

There are a few items to configure to get the ADempiere Test Suite set up properly:

  1. Configure Sahi to run on your system and with your browsers. You may also find it helpful to configure the timings of the tests in the sahi.properties file.
  2. Add the Adempiere Test Suite to your development environment. This can be found on SourceForge at https://sourceforge.net/p/adempiere/adempiere-test-suite. Clone the read only repository using mercurial with the following command. To get read-write access, you need to be a developer and be logged-in to SourceForge. For more information on becoming a developer, see Becoming a Developer.
hg clone http://hg.code.sf.net/p/adempiere/adempiere-test-suite adempiere-adempiere-test-suite
  1. Add the cloned project to your development environment as a project.
  2. Add pointers to your development environment location in the sahi user properties.
Note.gif Note:

Ensure the version of ADempiere under test uses the web ID class that creates the references in ZK of the form "Field_C_BPartner_ID_...". This feature is configurable after 3.7.1. and the proper class is included in 3.8.0. For other versions, the file may already be there or you will have to add a new class. In the ADempiere repository, the needed file can be copied from commit #64800ef650d5 (AdempiereIdGenerator.java).

To check if everything is working properly, update the utils/test.properties file with suitable references to a running instance and run the run_test.xml file as an Ant build. Logs of the test results will be saved in the utils/target/log directory.

You can configure which test suites run by copying the utils/myTestTemplate.properties file to utils/myTest.properties and changing the SUITE_FILE property to point to the correct suite, scenario or test file (and .sah file will work).

For more control over the test runs, use the Sahi internal controller. See the Sahi documentation on how to use it.

The Sahi Test Framework

The ADempiere Test Suite includes a framework for Sahi tests that includes full support for automated testing as part of the nightly build and eclipse-based test development. The test suite is under the same configuration control as the main ADempiere project. (See Software Development Procedure).

The ADempiere test suite provides wrappers and common functions to that tests can be developed using the concepts familiar to ADempiere developers. Tests are organized into test flows which are one or more test scripts focused on a single feature, bug fix or other functionality. Test flows are grouped in scenarios which would cover a test case, for example. A number of scenarios can be run as part of a test suite.

ADempiere Wrappers for Sahi

Sahi functions refer to very low level aspects of the html in the web interface. For usability, maintainability and ease in creating the tests functions, a set of wrappers has been developed that refer to functional aspects of the ADempiere interface and abstract out the complexity of the Sahi functions. In most cases, testers should be able to use these wrappers to develop the majority of their test code.

The main wrappers are defined in the following files:

  • lib/wrapper/dialogs.sah
  • lib/wrapper/fields.sah
  • lib/wrapper/icons.sah
  • lib/wrapper/info.sah
  • lib/wrapper/lookup.sah
  • lib/wrapper/windows.sah

Additional wrapper functions related to dialog boxes are included in files such as

  • lib/model/VPayment.sah
  • lib/model/VLocationDialog.sah

The wrapper functions follow a general naming convention of x<action><name>() where x can be "i" for icons, "f" for fields or "w" for windows. Action is the action in ADempiere, such as Open, Close, Get, Set, etc... The Name is the name of the function or object. For icons, the name is the function performed by the icon. For example, iCopyRecord() or iSaveRecord(). For fields, the name is the type of the field such as Text, TextArea, List, Search, Amount. The window functions Name is either Window or Tab. For example wOpenWindow("Sales Order") or wCloseWindow("Price List");

Buttons such as the Doc Action button can be clicked and the associated dialog processed with a command like fDocAction("Complete")

With these functions, a simple test of the process to create a sales order, process it and pay it would look like this:

wOpenWindow("Sales Order");
iFormView();
iNewRecord();
fSetSearch("C_BPartner_ID", "Joe Block");
iSaveRecord();
wOpenTab("Order Line");
iFormView();
iNewRecord();
fSetSearch("M_Product_ID", "Azalea Bush")
if(_condition(_exists(_span(/^Insufficient Inventory/)))){
	_log("*** Warning: " + _getText(_span(/^Insufficient Inventory/)),"info");
	iOk();
}					
iSaveRecord();
wOpenTab("Order");
fDocAction("Complete");

var $GrandTotal;
_set($GrandTotal,fGetAmount("GrandTotal"));


// Create the payment data.
$PaymentData = [
                ["Credit Card", "Visa"],
        	["Number", "1234"],
                ["Expires (MMYY)", "1211"],
                ["Amount",$GrandTotal],
                ["Voice authorization code", ""],
        	["",""]
               ];
        	
fPaymentRule("Credit Card",$PaymentData);

Where's the testing? Each low level function includes tests of existence and success that will be reported in the log. For example, take the line

fSetSearch("C_BPartner_ID", "Joe Block");

The fSetSearch() function draws on the following code:

/********************************************************************
 *
 * fSearch($FieldName)
 * 
 * Returns the element for the specified field
 *
 *******************************************************************/
<browser>
function fSearch($FieldName){
	/* Double underscores required to prevent scheduling */
	__assertExists(__textbox(/^zk/, __in(__div("/^Field_" + $FieldName + "/"))), "Error: fSearch() can't find field " + $FieldName);
	return __textbox(/^zk/, __in(__div("/^Field_" + $FieldName + "/")));
}
</browser>

/********************************************************************
 *
 * fGetSearch($FieldName)
 * 
 * Get the value/contents of a search field.
 *
 *******************************************************************/
<browser>
function fGetSearch($FieldName) {
	return __getText(fSearch($FieldName));
}
</browser>

/********************************************************************
*
* fRequerySearch($FieldName)
* 
* Requery a record
*
*******************************************************************/
function fRequerySearch($FieldName){
	_rightClick(fSearch($FieldName));
	_click(_link("Re&Query")); //Requery
}

/********************************************************************
 *
 * fSetSearchRq($FieldName, $Value, $Requery)
 * 
 * Set a search field to a value.  Optionally, requery the field
 * if $Requery = true
 *
 *******************************************************************/
function fSetSearchRq($FieldName, $Value, $Requery){
	
	if($Requery == "Y") {
		fRequerySearch($FieldName);
	}
	_setValue(fSearch($FieldName), $Value);
	_removeFocus(fSearch($FieldName));
	_assertEqual($Value, fGetSearch($FieldName), "Error: fSetSearch() failed to set field " + $FieldName + " to value = " + $Value);
}

/********************************************************************
 *
 * fSetSearch($FieldName, $Value)
 * 
 * Set a search field to a value.  No requery.
 *
 *******************************************************************/
function fSetSearch($FieldName, $Value){
	
	fSetSearchRq($FieldName, $Value, "N");
}

The identity function fSearch abstracts the low level html required to identify the field and returns the element ID that can be used to find the field on the open tab. This is the only place such code exists, making it easy to maintain the test software in case the ZK ID generation methods are changed. The identity function also tests for existence of the field.

The fSetSearch() function simply calls another function. fSetSearchRq() which includes the option to requery the field before the value is set. This is useful in cases where new data has been added to the database since the last login. The function fSetSearchRq requeries the field from the right-click pop-up menu, sets the field value and then tests the value to ensure that there is a match. Note that this implementation will flag as errors partial text entries where the application can find a match in the underlying field - "Block" instead of "Joe Block".

Also note the following code:

fSetSearch("M_Product_ID", "Azalea Bush")
if(_condition(_exists(_span(/^Insufficient Inventory/)))){
	_log("*** Warning: " + _getText(_span(/^Insufficient Inventory/)),"info");
	iOk();
}					

After running this test a number of times, you will exhaust the supply of Azalea Bushes, causing a warning to pop up. The if() statement captures this warning condition and makes a note in the log.

Test Flows

A Test Flow is a file where the test code resides. A test flow are not limited in what they can contain but they should follow these rules:

  • The file name should start with "tf_" and the rest of the name should be as descriptive as possible. If written for features that appear in a particular version for that relate to a particular feature, the test should include that info. For example tf_380_ADEMPIERE-72_wip_assignment_info.sah.
  • Test Flows should not include other files as the flows themselves will likely be included in Scenario files that will perform the includes.
  • Function names used in the test flow file should be unique across the system as the scope of the functions will be global. Function names that use the elements of the file name are a good idea.
  • The main test in a test flow is a function that will be called by the scenario to run the tests. There can be more than one but all the test functions need to start with the name "test_". For example function test_wip_product_info(){}.
  • Test flow files should be stored under the project "test" directory tree where they make the most sense. The directories are arranged according to the main functional areas of ADempiere.
  • Test flows should not be run directly but called through a scenario.

Scenarios

A scenario is a collection of test flows and the supporting files. It is the file that is called to execute the tests. The structure of a scenario file is rather simple as follows:

// Scenario template.  Copy this file and replace the sections as required.
// Optional: Call the scenario from a suite file and execute through the utils/run_tests.xml ant build 

// Global Variable declarations 

var $release = "Release 3.6.0LTS";
var $usr = "GardenAdmin";
var $pwd = "GardenAdmin";

// Includes - common functions
//This file includes all other supporting files so you only need to add the one in each scenario
_include("../lib/common_functions.sah");  

// Includes - test flows - as many as required.  Each should include one or more functions 
// called test_*() which will execute the test.
_include("../test/material_management/tf_fr3004020_allocation.sah");

// Setup - called before each test	
function setUp(){

versionTest($release); loginDefault($usr, $pwd); //defaults

}

// Tear down - called after each test
function tearDown() {
//	logout();
}

// Run the tests - anything included that starts with "test_".  The order of execution is undefined.
_runUnitTests();

_log("Scenario completed.", "info"); // Test Completed.
// End of test

There are four parts to the scenario file to note:

  1. The file includes the function "../lib/common_functions.sah" where all the main elements of the wrappers and supporting functions reside.
  2. Multiple test flow files can be included.
  3. Common setup and tear-down functions can be defined for the tests so the starting conditions are known.
  4. The function call _runUnitTests(); will call all the included functions that start with the name "test_" in a random order.

Scenario files should be stored in the adempiere-test-suite/sahi/scenario directory.

Suites

Suite files are simply a list of scenarios to include in a larger test run. The suites are execute by a batch process in sahi (test_runner) or through the Ant build run_test.xml in the utils directory. Create and modify the myTest.properties file based on the myTestTemplate.properties to customize which suite is run and then execute run_test.xml as an ant build.

The format of a suite file is trivial:

//  ADempiere test suite - bugs and feature tests related to release 3.7.0

../scenario/scenario_370_bugfix_tests.sah
../scenario/scenario_370_FR3208588_Rounding.sah
...

Store the suite files in the suite directory.

Developing Tests