Getting started User guide Extending IeUnit

IeUnit

The Test Framework

With IeUnit framework a typical unit test for a web page comprises the following steps:

  1. Load a web page into the Internet Explorer browser
  2. Verify the content of the page.
  3. Change the value of some elements on the page.
  4. Submit the page.
  5. Repeat the step 2 to 4 when necessary.
  6. Close the page and report the test result.

For the step 1 and 6 we use methods openWindow() and closeWindow().  Also, the method seekWindow() and seekAndSetWindow() allow a test script to attach to an existing  browser window. For the step 3 to 4 we normally first find a DHTML element in the page then use DHTML APIs to access the object. In the following sections we'll discuss these tasks in more details.

Finding Elements in HTML Pages

According the DHTML model a web page is represented by a document object. The visual elements of a web page are represented by child objects (also called child elements) of the document object. The document object together with its child objects forms the so called DOM tree for the web page. The most frequent operations in an IeUnit test script are localizing objects in the DOM tree for visual elements on a web page.

IeUnit framework makes the root document object available for test scripts through the property this.doc within a test case that assimilated a IeUnit object. Out-going from this root document object we can find child elements through the DHTML APIs. There are often many different ways to find child objects. It is however not always simple to find those elements we are interested in.

IeUnit framework provides many methods to help find elements on web pages. Before we get into details of different searching methods let us clarify an important quality concept of unit test. We say that a test suite is stable if small changes of the tested application cause only few unit tests to fail. Higher stability means higher quality and usefulness. Nothing is more frustrating for a software project if a large number of unit tests fail everyday.  In order to create stable test suites we should make sure that each unit test focuses on an individual feature. There more independent the unit tests are from each other, the more stable is the test suite.

The following are some guides to find elements with IeUnit framework:

  1. Direct IeDhtml methods: The IeDhtml class provides a set of methods for most frequently used searching operations. All those methods have names starting with the prefix find, e.g. findLink(), findButton(), etc.. Most of these methods accept an optional index argument which can be used to specify the index of the object in case there are more than one object satisfying the search criterion. For instance findButton("login", 2) finds the 3rd button object whose label contains the sub string "login". Notice:  all indices used in IeUnit framework are zero based, thus the 3rd element has the index 2.

  2. Find an object by its name or id:  If an element has a name or id attribute, it is very straightforward to find the object through the method findByObjId(). For instance if  a HTML page has the following item:

        <input type="text" id="password"></input>

    We can get the corresponding DHTML object by this.findObjById("password"). This method is the most stable way to find an object as we always get the same object regardless what changes has been made to the rest of the page. However, this method has the disadvantage that the element of interest must have  an unique id. Notice that findObyById() also finds the element with given name if the element has unique name attribute.

  3. Find an object by tag name and attribute value:  The findByTagAndAttr() method provides an easy way find an element with specific tag name and specific attribute. For instance to find the element
       <input type="text" id="password"></input>
    we can call findByTagAndAttr("input", "type~text"). If there are more than one items with the same tag name and attribute in the page we can specify a third index argument to the findByTagAndAttr call to get the one with particular index. For instance findByTagAndAttr("input", "type~text", 3) will return the 4-th input-item with the type 'text'. 

  4. Search by text:  DHTML provides the textual view of a web page through the text range concept. All visible text of a page is included in the text range object. With help of the text range object we can easily find objects whose visual representation contain specific text. The IeDhtml class supports this kind of search with the method findByText() and findParent(). findByText() finds the smallest object that contains particular text. findParent() finds the closest parent element with given tag name. For instance, for the following table:
    <table>
        <tr><td>User Name</td><td><input type='text' name='username'></input><td><tr>
        <tr><td>Password</td><td><input type='text' name='pwd'></input><td><tr>
    </table>

    we can get the object for the td element containing text "User Name" with findByText("User").  We can  get the table object with the call findParent(findByText("User"), "TABLE").  Notice that findByText() also accepts an optional index argument that can be used to specify the index of the object in case there are more than one object containing the specified text.

  5. Search by restricting the search range: Most find-methods of IeDhtml search within the find scope object which is by default the whole document object. The find scope can be changed by the method setFindScope(). By restricting the find scope we can do more stable and precise search operations. For instance, many web pages contain multiple tables for different sections of the page (e.g. one for navigation, one for advertisement, etc.). If a test script is only interested in a particular section the script can set the find scope to the corresponding table object,  so that all find-operations will be done within that table. In this way all changes in the rest of the page won't affect the test script.

Dealing with Frames

If page is built with frames each frame contains a DHTML document object. Those document objects are, more or less, independent from each other. Most APIs of the IeDhtml class work on a single document object at a time. When the tested page is a document with frames, the test script must call setFrame(idx_or_id) to select one default frame (therefor the default document) before perform any particular testing task. The selection of the default frame is persistent in the sense that after each submission task the this.doc object will automatically set to the document object of the selected frame by the checkSubmit() method (more information on checkSubmit() latter).

Normally, a page has a top-level document that specifies the hierarchical frameset+frame structure. Each frame itself refers to non-frame HTML document. In this case we just need to call setFrame() with the index or the id of the frame regardless of how the frame is nested in the structure. Sometimes there are framed pages whose frame elements refers to another framed document. In this case we can't use a single index or id to locate a frame, since the index and id are only valid within the document containing it.  We have to use a second index or id to address the frame nested in the second document. The syntax to use multiple indices or ids is this.setFrame("idx_or_id_1/idx_or_id_2") where idx_or_id_1 is the index or id of the frame that contains the second document; idx_or_id_2 is the index or id of the target frame within the second document.

It should be pointed out that, for security reasons, Internet Explorer only allows cross-frame scripting if the frames are from the same domain (i.e. ieunit.sourceforge.net). This restriction leads the restriction for IeUnit that all frames of a page and the parent window must come from one domain.  Let's hope that the future version of Internet Explorer will provide certain mechanism to allow trusted scripts to access the document model located in foreign frames (the Internet Explorer itself obviously can already directly access all frames).

Submit Operation and Timeout Control

An IeUnit test script triggers submit operations either by methods like clickLink() and clickButton(), or directly by calling DHTML APIs like form.submit(). Since a JavaScript script runs asynchronously to the browser own process a script normally has to wait till the response page has been received and processed by the browser. To simplify the coding IeUnit provides the method checkSubmit()to do all those synchronization works. Methods like clickLink() and clickButton() implicitly call the checkSubmit() so that we don't need call checkSubmit(). If we call DHTML APIs directly to submit a page, e.g. call form.submit(),  we need to call checkSubmit() method afterwards.

When a script calls checkSubmit() this method basically does the following:

  1. Wait for a short time till the submission has been sent out to the web server.
  2. Periodically check the state of the page till the page's state becomes ready, or a pre-set timeout value has expired in this case an exception will be thrown.
  3. Update some internal session objects and states (like doc, win and findScope() ) with the response page.
  4. Wait for another short of time to allow possible embedded JavaScript code to complete.

The short time interval for the step 1 is controlled by the IeUnit property submitPause. The timeout value for step 2 is controlled by the property findTimeout. The two properties submitPause and findTimeout can be changed with the method setTime().  The wait time for step 2 is set to 0.2*submitPause. Notice that findTimeout is also used by other methods like waitForSuccess() and seekWindow().

In general we can roughly say that submitPause determines the minimal pause for each submit operation whereas findTimeout determines the maximal waiting time.

The default values for submitPause and findTimeout are 1 and 20 seconds respectively which can be changed through the configuration file Config.wjs.

The waitForSuccess() method provides a convenient way to repeatedly try certain function till that function has succeeded (i.e. no exception gets raised). For instance, some web page periodically refreshes its content, say every 5 seconds. A test script can use waitForSuccess to wait till certain content appears in the page.

The IeUnit framework also provides the sleep() method for more direct synchronization control. Scripts using a lot of sleep based synchronization are in general difficult to maintain and are instable against changes. 

Class, Object and Assimilation

Inheritance has been an essential concept in most object oriented languages, and it has been proven to be a very useful concept. JavaScript does not support inheritance, but it provides some dynamic features with which we can achieve similar effect. IeUnit introduced the concept assimilation as a replacement for the inheritance concept. Both assimilation and inheritance aim to reuse code. They, however, have some subtle differences:

In IeUnit we let an object a assimilate another object b by calling the function assimilate(a, b). All properties and methods of b will be added to the object a.  Thereafter object  b should be considered assimilated  and should not be used standalone.

The function assimilate() is implemented in IeUnit.js as a top level JavaScript function. The complete interface of assimilate() is as follows:

function assimilate(dstObj, srcObj, suffix, policy)

Where dstObj is the assimilating object, srcObj is the object to be assimilated. suffix  is an optional argument, it specifies a string suffix to be appended to member names when adding members from srcObj to dstObj. policy is an optional argument to resolve member name conflicts. If a member's name of srcObj after appending possible suffix already exists in the object dstObj  we say there is name conflict. The value of policy controls how to resolve the conflict as follows: 0: a name conflict will cause an exception to be thrown. 1: the conflicting member in dstObj will silently overwritten by the conflicting member of srcObj. 2: the conflicting member of srcObj will be silently ignored. If policy is not specified the default value 0 will be used.

assimilate() returns the newly extended dstObj  object to the caller.

To illustrate assimilation concept let us consider the following example:

function BigCompany(companyName,  headerQuarter) {
    this.companyName = companyName;
    this.
headerQuarter  = headerQuarter;
}

function SmallCompany(companyName, softwareProduct) {
    this.companyName = companyName;
    this.softwareProduct = softwareProduct;
}

bigCompany = new BigCompany("MegaSystems",  "CityA");
smallCompany = new SmallCompany("Super QA",  "Super Web Tester");

If we simply call assimilate(bigCompany, smallCompany) an exception will be raised because of  the name conflict on the member companyName.

If we call newCompany = assimilate(bigCompany, smallCompany, "Sub") the object newCompany will have the following properties:

newCompany.companyName = "MegaSystems"
newCompany.
headerQuarter = "CityA";
newCompany.companyNameSub ="Super QA";
newCompany.softwareProductSub = "Super Web Test";

If we call newCompany=assimilate(bigCompany, smallCompany, "", 1) the object newCompany will have the following properties:

newCompany.companyName = "Super QA"
newCompany.
headerQuarter = "CityA";
newCompany.softwareProduct = "Super Web Test";

If we call newCompany=assimilate(bigCompany, smallCompany, "", 2) the object newCompany will have the following properties:
newCompany.companyName = "MegaSystems"
newCompany.headerQuarter = "CityA";
newCompany.softwareProduct = "Super Web Test";

It should be pointed out that assimilation is only possible in dynamic language like JavaScript that allow run-time modification of object structure. Conventional  objected oriented languages like C++ or Java don't allow this, at least not in such a simple way. On the other hand, the use of this kind of dynamic features makes some work like debugging more difficult.

The Test Runner

According to the xUnit framework a test runner is a program that helps the user to run a set of unit tests. IeUnit provides a text based test runner IeTextRunner.wsf that is invoked through the script engine cscrtip.exe as follows:
   cscript.exe IeTextRunner.wsf <options>

The IeTextRunner.wsf program has the following options:

  -help

Prints out some brief help information.

 -run [<caseA>[:<test1>:<test2>:...] caseB[:<tst1>:<tst2>... ]

Runs selected test cases or tests. The argument is list of test case names, each may optionally be followed by a list of test names separated by columns. For instance, the following command  executes the case AssimilationTest and the test testCheckValue of the case CaseFixtureTest:

    IeTextRunner.wsf -run AssimilationTest CaseFixtureTest:testCheckValue

If no argument is provided all test cases in the current directory will be executed.

 -runfiles [<a.jst> <b.jst> ...]

Runs the test cases defined in a list of  .jst files. IeTextRunner will scan the .jst files and take the first function name as test case name for a .jst file. The function declaration must be a single line that starts with the keyword function.

 -orgsrc <jst-file-path>

specifies the path of original source .jst file. This option is used for debugging purpose, it is only valid when a single .jst file has been specified through the -runfiles option.

  -I <LibPath>

Specifies a ";" separated list of directories for the location of test scripts. The runner will load all  .js scripts from the following directories: (1) The directory %IEUNIT_LIB%\lib.  (2) The directories specified by the environment variable IEUNIT_LIB if it is defined. IEUNIT_LIB must point to a ";" separated list of directories. (3) The directories specified by the -I option. (4) The directory where the test case source is located. After loaded the .js files, IeTextRunner will load those .jst files from above directories which implement the test cases selected by the option -run or -runfiles. The default value of  this option is %IEUNIT_HOME%/local as specified in Config.wjs file.

  -l

Lists all test cases in the complete library path.  See the option -I for more information about how the runner finds test cases.

  -n <n>

Repeat the test <n> times.

  -xml <result-xml-file-path>

This will still print out the standard test results to the console, but in addition will create an Xml file at the specified location, that conforms to the same schema used by NUnit. This enables automated testing with Nant using the Nunit2Report task

  -v false

This enables running the tests without displaying the IE window. The only valid option is false. true is the default value.


For the sake of convenience IeUnit also provides some batch scripts as shortcuts for above commands:

StartTest.bat <test-casse-name|jst-file>

Execute a test case or the test case implemented in a jst file.
DebugTest.bat <jst-file|smart-bookmark-file>
Start the a test case script or smart-bookmark script under the debugger. If we need to stop at certain line in the test script we have to insert the statement "debugger" at that line.

The configuration file Config.wjs


The configuration file Config.wjs enables the user to set default values for most of command-line options. Config.wjs provides also control over other default settings like default timeout values. Config.wjs is installed as permanent file so that it won't be removed when you uninstall IeUnit package nor overwritten by new installation.  Config.wjs is just another script that get loaded before all other script files. 

The Smart Bookmark

Smart bookmark is a special feature provided by IeUnit as an extension to web site bookmarks.  Normal bookmarks provided by Internet Explorer are static: they don't allow us, for instance, to bookmark web pages that require the user to pass login page.  With the smart bookmark a user can create a simple bookmark script (with the extension .sbk) that automates the login procedure and then navigate to the page he/she wanted to visit.  When the user clicks on the script through Windows Explorer or Windows Desktop the browser will automatically login to the desired web site and then navigate to the wanted page.

The format of smart bookmark script is just a list of JavaScript statements which are valid within the context of a test case. Smart bookmark script is executed by the program SmartBookmark.wsf  that first wraps the statements into a temporary test case, then execute it like an IeUnit test case.  During the start SmartBookmark.wsf also tries to find the file SmarktBookmark.init in the working directory. If such a file is found the file will be loaded and executed by JavaScript engine before the smart bookmark script.

The example LoginBookmark.sbk is located in the directory samples\LoginDemo, it reads as follows:

    this.openWindow("http://ieunit.sourceforge.net/samples/LoginDemo/Login.html");
this.setField(0, "IeUnit");
this.setField(1, "DemoPwd");
this.clickButton("Login");

This script demonstrates how to automatically login into a web site that requires user name and password.

IeUnit Specific File Types

The installation procedure of IeUnit creates two Windows file extensions .jst and .sbk.  jst files will be associated with type IeUnit.JavaScriptTest. Opening a file of this type will automatically execute the test case.  sbk files will be associated with the type IeUnit.SmartBookmark. Opening a file of this type will automatically execute the smart bookmark script.

Running Tests from Windows Explorer

Windows Explorer provides a convient environment to run and test IeUnit scripts. In order to run a jst or sbk script from Windows Explorer we just need to double click the script file in Explorer.

In order to debug a IeUnit script we first right mouse click the script file to be debugged, then choose the "Debug Script" entry from the context menu.

We can also execute a set of jst files by selecting them in the Explorer window, then send them to the IeUnit target by choosing the "IeUnit" entry in the context menu.

Getting started User guide Extending IeUnit