My favorites | Sign in
Project Home Downloads Wiki Issues Source
Search
for
UserGuide070TelluriumBasics  
Tellurium Basics.
Phase-Support, Phase-Design
Updated Aug 11, 2010 by John.Jian.Fang@gmail.com

(A PDF version of the user guide is available here)

Tellurium Basics

UiID Attribute

In Tellurium, the UI object is referred to by its UiID, i.e., the UI object identifier.

For nested Ui objects, the UiID of the UI Object is a concatenated UI objects' uids along its path to the UI Object.

For example, in the following nested UI Module shown below, the TextBox is referred to as the "parent_ui.child_ui.grand_child.textbox1".

ui.Container(uid: "parent_ui"){
  InputBox(uid: "inputbox1", locator: "...")
  Button(uid: "button1", locator: "...")
  Container(uid: "child_ui){
    Selector(uid: "selector1", locator: "...")
    ...
    Container(uid: "grand_child"){
      TextBox(uid: "textbox1", locator: "...")
      ...
    }
  }
}

The exceptions are tables and lists, which use [x][y] or [x] to reference the elements inside. For example, labels_table[2][1] and GoogleBooksList.subcategory[2]. The Table header can be referred in the format of issueResult.header[2].

More general cases are shown in Figure 2-3 General Cases Search Example:

ui.Form(uid: "A", clocator: [:]){
  InputBox(uid: "B", clocator: [:])
  Container(uid: "C", clocator: [tag: "div"]){
     Selector(uid: "D", clocator: [:])
     List(uid: "E", clocator: [tag: "ul"], separator: "li"){
	UrlLink(uid: "{1} as Link", clocator: [:])
        InputBox(uid: "{all}", clocator: [:])
     }
  }
}

Figure 2-3: General Cases Search Example

For example, the UiID of the List E in the above diagram is A.C.E and the InputButton in the List E is referred by its index n. For example: A.C.En.

Locator Attributes

Tellurium supports two types of UI Object locators:

  1. Base locator
  2. Composite locator

The Base locator is a relative XPath.

The Composite locator, denoted by "clocator", specifies a set of attributes for the UI object. The actual locator is derived automatically by Tellurium at runtime.

The Composite locator is defined as follows:

class CompositeLocator {
   String header
   String tag
   String text
   String trailer
   def position
   boolean direct
   Map<String, String> attributes = [:]
}

To use the Composite locator, use "clocator" with a map as its value.

For example:

clocator: [key1: value1, key2: value2, ...]

The default attributes include "header", "tag", "text", "trailer", "position", and "direct". They are all optional. The "direct" attribute specifies whether this UI object is a direct child of its parent UI, and the default value is "false".

If there are additional attributes, they are defined in the same way as the default attributes. For example:

clocator: [tag: "div", value: "Tellurium home"]

Most Tellurium objects come with default values for certain attributes. For example, the tag attribute.

If these attributes are not specified, the default attribute values are used. In other words, if the default attribute values of a Tellurium UI object are known, omit them in clocator.

For example, if the RadioButton Object’s default tag is "input", and the default type is "radio", omit them and write the clocator as follows:

clocator: [:]

which is equivalent to:

clocator: [tag: "input", type: "radio"]

Group Attribute: Group Locator

In the Tellurium UI module, the "group" attribute is seen often. For example:

ui.Container(uid: "google_start_page", clocator: [tag: "td"], group: "true"){
  InputBox(uid: "searchbox", clocator: [title: "Google Search"])
  SubmitButton(uid: "googlesearch", clocator: [name: "btnG", value: "Google Search"])
  SubmitButton(uid: "Imfeelinglucky", clocator: [value: "I'm Feeling Lucky"])
}

The group attribute is a flag for the Group Locating Concept. Usually, the XPath generated by Selenium IDE, XPather, or other tools is a single path to the target node such as:

//div/table[@id='something']/div[2]/div[3]/div[1]/div[6]

Sibling node information is not used in this example as the XPath depends too much on information from nodes far away from the target node. In Tellurium, every effort is made to localize the information and reduce this dependency by using sibling information or local information.

For example, in the above google UI module example, the group locator concept searches for the location of the "td" tag with its children as "InputBox", "googlesearch" button, and "Imfeelinglucky" button. In this way, the dependencies of the UI elements inside a UI module on external UI elements are reduced, making the UI definition more robust.

self attribute

Some times, elements inside a Table usually are inside its parent tag, for instance, we have the following HTML source.

<div id="table">
   <div>
       <div id="name">
           <div>Data</div>
           <div>
               <img/>
           </div>
       </div>
       <div id="shortname">
           <div>Bezeichnung</div>
           <div>
               <img/>
           </div>
       </div>
       <div id="type">
           <div>Typ</div>
           <div>
               <img/>
           </div>
       </div>
   </div>
   <div id="client-area">
       <div>
           <div>Bildsystem</div>
           <div>Bildsystem</div>
           <div>Bildserver</div>
       </div>
       <div>
           <div>Partner</div>
           <div>Partner</div>
           <div>Bestandssystem</div>
       </div>
       <div>
           <div>MS</div>
           <div>MS</div>
           <div>MS</div>
       </div>
   </div>
</div>

where the "Data" element and many others are inside its parent tag "div". To module this, we added a self attribute to the UiObject class and the default is "false".

To describe the above html, we can define the following UI module.

    ui.StandardTable(uid: "Table", clocator: [id: "table"], bt: "div", brt: "div", bct: "div"){
      TextBox(uid: "{tbody: 1, row: all, column: 1}", clocator: [tag: "div"], self: "true")
      Image(uid: "{tbody: 1, row: all, column: 2}", clocator: [:])
      TextBox(uid: "{tbody: 2, row: all, column: 1}", clocator: [tag: "div"], self: "true")
      TextBox(uid: "{tbody: 2, row: all, column: 2}", clocator: [tag: "div"], self: "true")
      TextBox(uid: "{tbody: 2, row: all, column: 3}", clocator: [tag: "div"], self: "true")
    }

To test the UI module, you can simply call the following api.

   getHTMLSource("Table");

Be aware that the self can be "true" ONLY for UI elements inside a List, a Table, or a StandardTable Object.

Respond Attribute: JavaScript Events

Tellurium provides a "respond" attribute used to define any event requiring the UI object to respond.

Most web applications include Javascript, and thus the web testing framework must be able to handle Javascript events. What is important is firing the appropriate events to trigger the event handlers.

Selenium has already provided methods to generate events such as:

fireEvent(locator, "blur")
fireEvent(locator, "focus")
mouseOut(locator)
mouseOver(locator)

Tellurium was born with Javascript events in mind since it was initially designed to test applications written using the DOJO JavaScript framework.

For example, we have the following radio button:

<input type='radio' name='mas_address_key' value='5779' onClick='SetAddress_5779()'>

Alternately one can define the radio button as follows:

RadioButton(uid: "billing", clocator: [name: 'mas_address_key', value: '5779'])

The above code does not respond to the Click event as the Tellurium RadioButton only supports the "check" and "uncheck" actions. This is enough for the normal case. As a result, no "click" event/action is generated during testing.

To address this problem, Tellurium added the "respond" attribute to Tellurium UI objects. The "respond" attribute is used to define any event requiring the UI object to respond. The Radio Button is redefined as:

ui.Container(uid: "form", clocator: [whatever]){
   RadioButton(uid: "billing", clocator: [name: 'mas_address_key', value: '5779'],
        respond: ["click"])
 }

Then issue the following command:

  click "form.billing"

Even if the RadioButton does not have the click method defined by default, it is still able to dynamically add the click method at runtime and call it. Be aware, you have to explicitly call the click method and Event Handler will not automatically trigger the click event.

A more general example is:

 InputBox(uid: "searchbox", clocator: [title: "Google Search"], 
      respond: ["click", "focus", "mouseOver", "mouseOut", "blur"])

Except for the "click" event, all of the "focus", "mouseOver", "mouseOut", and "blur" events are automatically fired by Tellurium during testing. Do not worry about the event order for the respond attribute as Tellurium automatically re-orders the events and then processes them appropriately. That is to say, the Event Handler will automatically trigger these events.

CSS Selector

Use the CSS Selector to improve the test speed in IE as IE lacks native XPath support and the XPath is slow. Tellurium exploits CSS selector capability to improve test speed dramatically.

Tellurium supports both XPath and CSS selector and still uses CSS selector as the default locator.

New DSL Methods

CSS Selector provides the following additional Selenium methods, utilized in DslContext to form a set of new DSL methods:

String getAllText(String locator):

1. Get all the text from the set of elements corresponding to the CSS Selector. String getCSS(String locator, String cssName) :

2. Get the CSS properties for the set of elements corresponding to the CSS Selector. Number getJQuerySelectorCount(String locator) :

3. Get the number of elements matching the corresponding CSS Selector.

Additional CSS Attribute Selectors

jQuery also supports the following attribute selectors:

attribute: have the specified attribute. attribute=value: have the specified attribute with a certain value. attribute!=value: either do not have the specified attribute or have the specified attribute but not with a certain value. attribute^=value: have the specified attribute and it starts with a certain value. attribute$=value: have the specified attribute and it ends with a certain value. attribute*=value: have the specified attribute and it contains a certain value.

Locator Agnostic Methods

Apart from the above, Tellurium provides a set of locator agnostic methods.

For example, the method automatically decides to use XPath or jQuery dependent on the exploreJQuerySelector flag, which can be turned on and off by the following two methods:

  1. public void useCssSelector()
  2. public void disableCssSelector()

Tellurium also provides the corresponding XPath specific and CSS selector specific methods for your convenience. However, it is recommended that you use the locator agnostic methods until there is a good reason not to.

The new XPath and CSS Selector specific methods are as follows:

1. Get the Generated locator from the UI module

Locator agnostic: String getLocator(String uid)

CSS selector specific: String getSelector(String uid)

XPath specific: String getXPath(String uid)

2. Get the Number of Elements matching the Locator

Locator agnostic: Number getLocatorCount(String locator)

CSS selector specific: Number getJQuerySelectorCount(String jQuerySelector)

XPath specific: Number getXpathCount(String xpath)

3. Table Methods

Locator agnostic:

int getTableHeaderColumnNum(String uid)

int getTableFootColumnNum(String uid)

int getTableMaxRowNum(String uid)

int getTableMaxColumnNum(String uid)

int getTableMaxRowNumForTbody(String uid, int ntbody)

int getTableMaxColumnNumForTbody(String uid, int ntbody)

int getTableMaxTbodyNum(String uid)

CSS selector specific:

int getTableHeaderColumnNumBySelector(String uid)

int getTableFootColumnNumBySelector(String uid)

int getTableMaxRowNumBySelector(String uid)

int getTableMaxColumnNumBySelector(String uid)

int getTableMaxRowNumForTbodyBySelector(String uid, int ntbody)

int getTableMaxColumnNumForTbodyBySelector(String uid, int ntbody)

int getTableMaxTbodyNumBySelector(String uid)

XPath specific:

int getTableHeaderColumnNumByXPath(String uid)

int getTableFootColumnNumByXPath(String uid)

int getTableMaxRowNumByXPath(String uid)

int getTableMaxColumnNumByXPath(String uid)

int getTableMaxRowNumForTbodyByXPath(String uid, int ntbody)

int getTableMaxColumnNumForTbodyByXPath(String uid, int ntbody)

int getTableMaxTbodyNumByXPath(String uid)

4. Verify if an Element is Disabled

Locator agnostic: boolean isDisabled(String uid)

CSS selector specific: boolean isDisabledBySelector(String uid)

XPath specific: boolean isDisabledByXPath(String uid)

5. Get the Attribute

Locator agnostic: def getAttribute(String uid, String attribute)

6. Check the CSS Class

Locator agnostic def hasCssClass(String uid, String cssClass)

7. Get CSS Properties

CSS selector specific: String[] getCSS(String uid, String cssName)

8. Get All Data from a Table

CSS selector specific:

String[] getAllTableCellText(String uid)

String[] getAllTableCellTextForTbody(String uid, int index)

9. Get List Size

Locator agnostic: int getListSize(String uid)

CSS selector specific: getListSizeBySelector(String uid)

XPath specific: getListSizeByXPath(String uid)

There are issues to be aware of with CSS Selector:

  • If you have a duplicate "id" attribute on the page, CSS selector always returns the first DOM reference, ignoring other DOM references with the same "id" attribute.
  • Some attributes may not be working in jQuery. For example, the "action" attribute in a form. Tellurium has a black list to automatically filter out the attributes that are not honored by CSS selector.
  • The "src" attribute in Image has to be a full URL such as http://www.google.com. One workaround is to put '*' before the URL.

UI Templates

Tellurium UI templates have two purposes:

  1. When there are many identical UI elements, use one template to represent them all
  2. When there are variable/dynamic sizes of UI elements at runtime, the patterns are known, but not the size.

More specifically, Table and List are two Tellurium objects that define UI templates.

  1. Table defines two dimensional UI templates
  2. List defines one dimensional UI templates

The Template has special UIDs such as "2", "all", or "row: 1, column: 2".

Looking at use case (1), the HTML source is:

  <ul class="a">
    <li>
      <A HREF="site?tcid=a" class="b">
        AA
      </A>
    </li>
    <li>
      <A HREF="site?tcid=b" class="b">
        BB
      </A>
    </li>
    <li>
      <A HREF="site?;tcid=c" class="b">
        CC
      </A>
    </li>
    <li>
      <A HREF="site?tcid=d" class="b">
        DD
      </A>
    </li>
    <li>
      <A HREF="site?tcid=e" class="b">
        EE
      </A>
    </li>
    <li>
      <A HREF="site?tcid=f" class="b">
        FF
      </A>
    </li>
  </ul>

In this example there are six links. Without templates, one would put six UrlLink objects in the UI module. By using the templates, the work is easier and simplified.

ui.List(uid: "list", clocator: [tag: "ul", class: "a"], separator:"li")
{
    UrlLink(uid: "{all}", clocator: [class: "b"])
}

For use case (2), a common application is the data grid. Look at the "issueResult" data grid on the Tellurium Issues page for an easy and simplified result as shown below:

ui.Table(uid: "issueResult", clocator: [id: "resultstable", class: "results"], 
     group: "true")
{
    TextBox(uid: "{header: 1", clocator: [:])
    UrlLink(uid: "{header: 2} as ID",  clocator: [text: "*ID"])
    UrlLink(uid: "{header: 3} as Type",  clocator: [text: "*Type"])
    UrlLink(uid: "{header: 4} as Status",  clocator: [text: "*Status"])
    UrlLink(uid: "{header: 5} as Priority",  clocator: [text: "*Priority"])
    UrlLink(uid: "{header: 6} as Milestone",  clocator: [text: "*Milestone"])
    UrlLink(uid: "{header: 7} as Owner",  clocator: [text: "*Owner"])
    UrlLink(uid: "{header: 9} as Summary",  clocator: [text: "*Summary + Labels"])
    UrlLink(uid: "{header: 10} as Extra", clocator: [text: "*..."])

    //define table elements
    //for the border column
    TextBox(uid: "{row: all, column: 1}", clocator: [:])
    //For the rest, just UrlLink
    UrlLink(uid: "{row: all, column: all}", clocator: [:])
}

The resulting definitions shown are very simple, time-saving and easy to use.

If the user has multiple templates such as the "issueResult" shown in the table above, the rule generally applied to the templates is: "specific one first, general one later".

"Include" Frequently Used Sets of Elements in UI Modules

When there is a frequently used set of elements, re-defining them repeatedly in your UI module is not necessary. Simply use the Tellurium "Include" syntax to re-use the pre-defined UI elements.

Include(uid: UID, ref: REFERRED_UID)

Use "ref" to reference the object to be included, then specify the UID for the object. Note: If a different UID is required, there is no need to specify it.

If the Object UID is not the same as the original one, Tellurium clones a new object for users so that multiple objects with different UIDs are available.

For example, first define the following reused UI module:

   ui.Container(uid: "SearchModule", clocator: [tag: "td"], group: "true") {
     InputBox(uid: "Input", clocator: [title: "Google Search"])
     SubmitButton(uid: "Search", clocator: [name: "btnG", value: "Google Search"])
     SubmitButton(uid: "ImFeelingLucky", clocator: [value: "I'm Feeling Lucky"])
   }

Then, include it into the new UI module as follows:

   ui.Container(uid: "Google", clocator: [tag: "table"]) {
     Include(ref: "SearchModule")
     Include(uid: "MySearchModule", ref: "SearchModule")
     Container(uid: "Options", clocator: [tag: "td", position: "3"], group: "true") {
       UrlLink(uid: "LanguageTools", clocator: [tag: "a", text: "Language Tools"])
       UrlLink(uid: "SearchPreferences", clocator: [tag: "a", text: "Search Preferences"])
       UrlLink(uid: "AdvancedSearch", clocator: [tag: "a", text: "Advanced Search"])
     }
   }

Javascript Events

Most web applications include Javascript, and thus the web testing framework must be able to handle Javascript events. What is important is firing the appropriate events to trigger the event handlers.

Selenium has already provided methods to generate events such as:

fireEvent(locator, "blur") fireEvent(locator, "focus") mouseOut(locator) mouseOver(locator)

Tellurium was born with Javascript events in mind since it was initially designed to test applications written using the DOJO JavaScript framework.

For example, the user has the following radio button:

<input type='radio' name='mas_address_key' value='5779' onClick='SetAddress_5779()'>

Alternately one can define the radio button as follows:

RadioButton(uid: "billing", clocator: [name: 'mas_address_key', value: '5779'])

The above code does not respond to the Click event as the Tellurium RadioButton only supports the "check" and "uncheck" actions. This is enough for the normal case. As a result, no "click" event/action is generated during testing.

To address this problem, Tellurium added the "respond" attribute to Tellurium UI objects. The "respond" attribute is used to define any events requiring the UI object to respond. The Radio Button can be redefined as:

 ui.Container(uid: "form", clocator: [whatever]){
     RadioButton(uid: "billing", clocator: [name: 'mas_address_key', value: '5779'], respond: ["click"])
 }

Then issue the following command:

    click "form.billing"

Even if the RadioButton does not have the click method defined by default, it is still able to dynamically add the click method at runtime and call it.

A more general example is:

 InputBox(uid: "searchbox", clocator: [title: "Google Search"], 
          respond: ["click", "focus", "mouseOver", "mouseOut", "blur"])

Except for the "click" event, all of the "focus", "mouseOver", "mouseOut", and "blur" events are automatically fired by Tellurium during testing. Do not worry about the event order for the respond attribute as Tellurium automatically re-orders the events and then processes them appropriately.

Logical Container

The Container object in Tellurium is used to hold child objects that are in the same subtree in the DOM object. However, there are always exceptions. For example, the Logical Container (or Virtual Container - Logical Container is preferred) can violate this rule.

What is a Logic Container? It is a Container with an empty locator. For instance:

Container(uid: "logical"){
   ......
}

But empty != nothing. There are some scenarios where the Logical Container can play an important role. The Container includes an uid for a reference and it logically groups the UI element together.

For example, in the following example the UI elements under the Tag li are different:

    <div class="block_content">
        <ul>
            <li>
                <h5>
                    <a href="" title="">xxx</a>
                </h5>
                <p class="product_desc">
                    <a href=".." title="More">...</a>
                </p>
                <a href="..." title=".." class="product_image">
                    <img src="..." alt="..." height="129" width="129"/>
                </a>
                <p>
                    <a class="button" href="..." title="View">View</a>
                    <a title="Add to cart">Add to cart</a>
                </p>
            </li>
            <li>
                similar UI
            </li>
            <li>
                similar UI
            </li>
        </ul>
    </div>       

The issue is how to write the UI template for them. The logical Container then comes into play. For example, the UI module is written as follows:

ui.Container(uid: "content", clocator: [tag: "div", class: "block_content"]){
     List(uid: "list", clocator: [tag: "ul"], separator:"li") {
         Container("{all}"){
             UrlLink(uid: "title", clocator: [title: "View"])
                      ......
                      other elements inside the li tag
         }
    }
} 

Another usage of the logical Container is to convert the test case recorded by Selenium IDE to Tellurium test cases. For example, using the search UI on the Tellurium download page, first record the following Selenium test case using Selenium IDE:

import com.thoughtworks.selenium.SeleneseTestCase;

public class SeleniumTestCase extends SeleneseTestCase {
        public void setUp() throws Exception {
                setUp("http://code.google.com/", "*chrome");
        }

        public void testNew() throws Exception {
                selenium.open("/p/aost/downloads/list");
                selenium.select("can", "label=regexp:\\sAll Downloads");
                selenium.type("q", "TrUMP");
                selenium.click("//input[@value='Search']");
                selenium.waitForPageToLoad("30000");
        }
}

Do not be confused by the locator "can" and "q", as they are UI element IDs and are easily expressed in XPath. The "label=regexp:\\sAll Downloads" part shows that Selenium uses regular express to match the String and the "\s" stands for a space. As a result, write the UI module based on the above code.

public class TelluriumDownloadPage extends DslContext {

  public void defineUi() {
    ui.Container(uid: "downloads") {
      Selector(uid: "downloadType", locator: "//*[@id='can']")
      InputBox(uid: "input", locator: "//*[@id='q']")
      SubmitButton(uid: "search", locator: "//input[@value='Search']")
    }
  }

  public void searchDownload(String downloadType, String searchKeyWords) {
    selectByLabel "downloads.downloadType", downloadType
    keyType "downloads.input", searchKeyWords
    click "downloads.search"
    waitForPageToLoad 30000
  }
}

The Tellurium test case is created accordingly:

public class TelluriumDownloadPageTestCase extends TelluriumJavaTestCase {

    protected static TelluriumDownloadPage ngsp;

    @BeforeClass
    public static void initUi() {
        ngsp = new TelluriumDownloadPage();
        ngsp.defineUi();
    }

    @Test
    public void testSearchDownload(){
        connectUrl("http://code.google.com/p/aost/downloads/list"); 
        ngsp.searchDownload(" All Downloads", "TrUMP");
    }
}

toggle

Tellurium 0.7.0 provides a toggle method to animate the UI element on the web page. For example, you can the following commands to show the UI element under testing.

    toggle "Form.Username.Input"
    pause 500
    toggle "Form.Username.Input"

getHTMLSource

Use getHTMLSource, users can get back the runtime html source for a UI module. Tellurium provided two methods for this purpose.

public Map getHTMLSourceResponse(String uid);

public void getHTMLSource(String uid);

The first method get back the html source as a uid-to-html map and the second one simply print out the html source on the console. Be aware, getHTMLSource is only available in Tellurium new APIs.

Example:

    @Test
    public void testGetHTMLSource(){
        useEngineLog(true);
        useTelluriumApi(true);
        useCache(true);
        connect("JettyLogon");
        jlm.getHTMLSource("Form");
    }

and the output is as follows:

Form: 

<form method="POST" action="j_security_check">
    <table border="0" cellpadding="1" cellspacing="2">
        <tbody><tr>
            <td>Username:</td>
            <td><input size="12" value="" name="j_username" maxlength="25" type="text"></td>
        </tr>
        <tr>
            <td>Password:</td>
            <td><input size="12" value="" name="j_password" maxlength="25" type="password"></td>
        </tr>
        <tr>
            <td colspan="2" align="center">
                <input name="submit" value="Login" type="submit">
            </td>
        </tr>
    </tbody></table>
</form>

Form.Username: 

	<tr>
            <td>Username:</td>
            <td><input size="12" value="" name="j_username" maxlength="25" type="text"></td>
        </tr>

Form.Username.Label: 

	<td>Username:</td>

Form.Username.Input: 

	<input size="12" value="" name="j_username" maxlength="25" type="text">

Form.Password: 

	<tr>
            <td>Password:</td>
            <td><input size="12" value="" name="j_password" maxlength="25" type="password"></td>
        </tr>

Form.Password.Label: 

	<td>Password:</td>

Form.Password.Input: 

	<input size="12" value="" name="j_password" maxlength="25" type="password">

Form.Submit: 

	<input name="submit" value="Login" type="submit">

type and keyType

type and keyType now accept different types of objects and convert them to a String automatically by calling the toString() method. For example, you can use the following commands:

    type "Google.Input", "Tellurium"

    type "Google.Input", 12.15

    type "Google.Input", true

Customize Individual Test Settings Using setCustomConfig

TelluriumConfig.groovy provides project level test settings. Use setCustomConfig to customize individual test settings.

public void setCustomConfig(boolean runInternally, int port, String browser,
                            boolean useMultiWindows, String profileLocation)

public void setCustomConfig(boolean runInternally, int port, String browser,
                  boolean useMultiWindows, String profileLocation, String serverHost)

For example, specify custom settings as follows:

public class GoogleStartPageJavaTestCase extends TelluriumJavaTestCase {
    static{
        setCustomConfig(true, 5555, "*chrome", true, null);
    }

    protected static NewGoogleStartPage ngsp;

    @BeforeClass
    public static void initUi() {
        ngsp = new NewGoogleStartPage();
        ngsp.defineUi();
        ngsp.useJavascriptXPathLibrary();
    }

    @Test
    public void testGoogleSearch() {
        connectUrl("http://www.google.com");
        ngsp.doGoogleSearch("tellurium selenium Groovy Test");
    }

    ......

}

User Custom Selenium Extensions

To support user custom selenium extensions, Tellurium Core adds the following configurations to TelluriumConfig.groovy.

  embeddedserver {

    ......

    //user-extension.js file, for example "target/user-extensions.js"
    userExtension = ""
  }
  connector{

    ......

    //user's class to hold custom selenium methods associated with user-extensions.js
    //should in full class name, for instance, "com.mycom.CustomSelenium"
    customClass = ""
  }

Where the userExtension points to the user's user-extensions.js file. For example, use the following user-extensions.js:

Selenium.prototype.doTypeRepeated = function(locator, text) {
    // All locator-strategies are automatically handled by "findElement"
    var element = this.page().findElement(locator);

    // Create the text to type
    var valueToType = text + text;

    // Replace the element text with the new text
    this.page().replaceText(element, valueToType);
};

The customClass is the user's class for custom Selenium methods, extending Tellurium org.telluriumsource.component.connector.CustomCommand class:

public class MyCommand extends CustomCommand{

    public void typeRepeated(String locator, String text){
                String[] arr = [locator, text];
                commandProcessor.doCommand("typeRepeated", arr);
       }
   } 
}

Tellurium core automatically loads up user-extensions.js and custom commands. The n, user uses:

customUiCall(String uid, String method, Object[] args)

to call the custom methods. For instance:

customUiCall "Google.Input", typeRepeated, "Tellurium Groovy"

The customUiCall method handles all the UI to locator mapping for users. Tellurium also provides the following method for users to make direct calls to the Selenium server.

customDirectCall(String method, Object[] args)

The Dump Method

Tellurium Core added a dump method to print out the UI object's and its descendants' runtime locators that Tellurium Core generates.

public void dump(String uid)

where the uid is the UI object id to be dumped. An important feature is that the dump() method does not require the user to run the actual tests. That is to say, the user does not need to run the Selenium server and launch the web browser. Simply define the UI module, then call the dump() method.

For example, to define the UI module for Tellurium Issue Search module, complete as follows:

public class TelluriumIssueModule extends DslContext {

  public void defineUi() {

    //define UI module of a form include issue type selector and issue search
    ui.Form(uid: "issueSearch", clocator: [action: "list", method: "get"], group: "true"){
      Selector(uid: "issueType", clocator: [name: "can", id: "can"])
      TextBox(uid: "searchLabel", clocator: [tag: "span", text: "*for"])
      InputBox(uid: "searchBox", clocator: [type: "text", name: "q"])
      SubmitButton(uid: "searchButton", clocator: [value: "Search"])
    }
  }
}

The user can use the dump method in the following way:

  TelluriumIssueModule tisp = new TelluriumIssueModule();
  tisp.defineUi();
  tisp.dump("issueSearch");

The above code prints out the runtime XPath locators.

Dump locator information for issueSearch is as follows:

-------------------------------------------------------
issueSearch: //descendant-or-self::form[@action="list" and @method="get"]
issueSearch.issueType: //descendant-or-self::form[descendant::select[@name="can" and 
     @id="can"] and descendant::span[contains(text(),"for")] and 
     descendant::input[@type="text" and @name="q"] and 
     descendant::input[@value="Search" and @type="submit"] and
     @action="list" and @method="get"]/descendant-or-self::select
                           [@name="can" and @id="can"]
issueSearch.searchLabel: //descendant-or-self::form[descendant::select[@name="can" 
     and @id="can"] and descendant::span[contains(text(),"for")] and
     descendant::input[@type="text" and @name="q"] and descendant::input[
     @value="Search" and @type="submit"] and @action="list" and @method="get"]
     /descendant-or-self::span[contains(text(),"for")]
issueSearch.searchBox: //descendant-or-self::form[descendant::select[@name="can" 
     and @id="can"] and descendant::span[contains(text(),"for")] and 
     descendant::input[@type="text" and @name="q"] and descendant::input[
     @value="Search" and @type="submit"] and @action="list" and @method="get"]
     /descendant-or-self::input[@type="text" and @name="q"]
issueSearch.searchButton: //descendant-or-self::form[descendant::select[@name="can" 
     and @id="can"] and descendant::span[contains(text(),"for")] and 
     descendant::input[@type="text" and @name="q"] and descendant::input[
     @value="Search" and @type="submit"] and @action="list" and @method="get"]
     /descendant-or-self::input[@value="Search" and @type="submit"]
-------------------------------------------------------

Engine State Offline Update

For some reasons, you may need to make Engine state calls before you actually connect to the Engine. For examples, call one of the following methods.

   public void enableCache();
   public void disableCache();
   public void useTelluriumApi(boolean isUse);
   public void useClosestMatch(boolean isUse);

We added an Engine State tracer in the bundle tier to record all the requests if the Engine is not connected and aggregate them. Once the Engine is connected, Tellurium will automatically send out the Engine state update request.

For example, the following test code works fine in 0.7.0.

    @Test
    public void testOfflineEngineStateUpdate(){
        JettyLogonModule jlm = new  JettyLogonModule();
        jlm.defineUi();
        useCssSelector(true);
        useTrace(true);
        //Engine state offline update
        useTelluriumApi(true);
        useCache(true);
        connectSeleniumServer();
        connectUrl("http://localhost:8080/logon.html");
        jlm.logon("test", "test");
        //Back to state online update
        useClosestMatch(true);
        connectUrl("http://localhost:8080/logon.html");
        jlm.plogon("test", "test");
    }

Testing Support

In the package org.telluriumsource.test, Tellurium provides three different ways to write Tellurium tests:

  1. TelluriumJUnitTestCase
  2. TelluriumTestNGTestCase
  3. TelluriumGroovyTestCase
  4. TelluriumMockJUnitTestCase
  5. TelluriumMockTestNGTestCase

Tellurium JUnit Test Case

Used for JUnit, and supports the following JUnit 4 annotations: JUnit is upgraded to 4.7 since it provides more features. One such a good feature is the Rule annotation.

To be consistent with TestNG test case, the class TelluriumJavaTestCase is deprecated and you should use TelluriumJUnitTestCase instead.

  • @BeforeClass
  • @AfterClass
  • @Before
  • @After
  • @Test
  • @Ignore

Tellurium TestNG Test Case

Used for TestNG. Similarly, using the following annotations:

  • @BeforeSuite
  • @AfterSuite
  • @BeforeTest
  • @AfterTest
  • @BeforeGroups
  • @AfterGroups
  • @BeforeClass
  • @AfterClass
  • @BeforeMethod
  • @AfterMethod
  • @DataProvider
  • @Parameters
  • @Test

TelluriumTestNGTestCase was changed to allow the setup and teardown procedures only work once for all the tests. The magic are the @BeforeTest and @AfterTest annotations. See the following code for more details,

public abstract class TelluriumTestNGTestCase extends BaseTelluriumJavaTestCase {

    @BeforeTest(alwaysRun = true)
    public static void setUpForTest() {
        tellurium = TelluriumSupport.addSupport();
        tellurium.start(customConfig);
        connector = (SeleniumConnector) tellurium.getConnector();
    }

    @AfterTest(alwaysRun = true)
    public static void tearDownForTest() {
        if(tellurium != null)
            tellurium.stop();
    }

Tellurium Groovy Test Case

Used for Groovy test cases.

Data Driven Testing: Tellurium provides the class TelluriumDataDrivenModule for users to define data driven testing modules.

Class TelluriumDataDrivenTest is used to drive the actual tests.

Tellurium also provides users the capability of writing Tellurium tests and Tellurium data driven tests in pure DSL scripts. The DslScriptExecutor is used to run the .dsl files.

TelluriumMockJUnitTestCase and TelluriumMockTestNGTestCase

TelluriumMockJUnitTestCase and TelluriumMockTestNGTestCase incorporated the Mock Http Server so that you can load up a html web page and test against it with minimal configuration.

One example is as follows,

public class JettyLogonJUnitTestCase extends TelluriumMockJUnitTestCase {
    private static JettyLogonModule jlm;

    @BeforeClass
    public static void initUi() {
        registerHtmlBody("JettyLogon");

        jlm = new  JettyLogonModule();
        jlm.defineUi();
        useCssSelector(true);
        useTelluriumApi(true);
        useTrace(true);
        useCache(true);
    }

    @Before
    public void connectToLocal() {
        connect("JettyLogon");
    }

    @Test
    public void testJsonfyUiModule(){
        String json = jlm.jsonify("Form");
        System.out.println(json);
    }

    @AfterClass
    public static void tearDown(){
        showTrace();
    }
}

where the html file "JettyLogon" lives in the class path "org/telluriumsource/html". You can change this by the following method:

public static void setHtmlClassPath(String path);

The implementation is simple as shown as follows.

public class TelluriumMockJUnitTestCase extends TelluriumJUnitTestCase {
    protected static MockHttpServer server;

    @BeforeClass
    public static void init(){
        server = new MockHttpServer(8080);
        server.start();
        connectSeleniumServer();
    }

    public static void registerHtml(String testname){
       server.registerHtml("/" + testname + ".html", server.getHtmlFile(testname));
    }

    public static void registerHtmlBody(String testname){
       server.registerHtmlBody("/" + testname + ".html", server.getHtmlFile(testname));
    }

    public static void setHtmlClassPath(String path){
        server.setHtmlClassPath(path);
    }

    public static void connect(String testname){
        connectUrl("http://localhost:8080/" + testname + ".html");
    }

    @AfterClass
    public static void tearDown(){
        server.stop();
    }
}

Tellurium Configuration

The configuration parser has been refactored. The configuration file name is stored in the Environment class as follows.

@Singleton
public class Environment implements Configurable{

  protected String configFileName = "TelluriumConfig.groovy";

  protected String configString = "";

  public static Environment getEnvironment(){
    return Environment.instance;
  }

  public void useConfigString(String json){
    this.configString = json;
  }

  ...

That is to say, you can get the Environment singleton instance and change the file name before Tellurium core is loaded up if you have a good reason to do that. In the meanwhile, we add support to load the configuration from a JSON string, which will be stored in the configString} variable in the Environment.

If the configString variable is not null or empty, Tellurium core will honor the JSON string and will ignore the configuration file. One way to use the JSON string is illustrated by the following example.

public class JettyLogonJUnitTestCase extends TelluriumMockJUnitTestCase {
   private static JettyLogonModule jlm;
   static{
       Environment env = Environment.getEnvironment();
       env.useConfigString(JettyLogonModule.JSON_CONF);
   }

   ...

where variable JSON_CONF is the JSON configuration string.

public class JettyLogonModule extends DslContext {
 public static String JSON_CONF = """{"tellurium":{"test":{"result":{"reporter":"XMLResultReporter","filename":"TestResult.output","output":"Console"},"exception":{"filenamePattern":"Screenshot?.png","captureScreenshot":false},"execution":{"trace":false}},"accessor":{"checkElement":false},"embeddedserver":{"port":"4444","browserSessionReuse":false,"debugMode":false,"ensureCleanSession":false,"interactive":false,"avoidProxy":false,"timeoutInSeconds":30,"runInternally":true,"trustAllSSLCertificates":true,"useMultiWindows":false,"userExtension":"","profile":""},"uiobject":{"builder":{}},"eventhandler":{"checkElement":false,"extraEvent":false},"i18n":{"locale":"en_US"},"connector":{"baseUrl":"http://localhost:8080","port":"4444","browser":"*chrome","customClass":"","serverHost":"localhost","options":""},"bundle":{"maxMacroCmd":5,"useMacroCommand":true},"datadriven":{"dataprovider":{"reader":"PipeFileReader"}},"widget":{"module":{"included":""}}}}""";

...
}

If we want a pretty look of the JSON String, we can use JSON Visualization to format it as follows.

{
    "tellurium": {
        "test": {
            "result": {
                "reporter": "XMLResultReporter",
                "filename": "TestResult.output",
                "output": "Console"
            },
            "exception": {
                "filenamePattern": "Screenshot?.png",
                "captureScreenshot": false
            },
            "execution": {
                "trace": false
            }
        },
        "accessor": {
            "checkElement": false
        },
        "embeddedserver": {
            "port": "4444",
            "browserSessionReuse": false,
            "debugMode": false,
            "ensureCleanSession": false,
            "interactive": false,
            "avoidProxy": false,
            "timeoutInSeconds": 30,
            "runInternally": true,
            "trustAllSSLCertificates": true,
            "useMultiWindows": false,
            "userExtension": "",
            "profile": ""
        },
        "uiobject": {
            "builder": { }
        },
        "eventhandler": {
            "checkElement": false,
            "extraEvent": false
        },
        "i18n": {
            "locale": "en_US"
        },
        "connector": {
            "baseUrl": "http://localhost:8080",
            "port": "4444",
            "browser": "*chrome",
            "customClass": "",
            "serverHost": "localhost",
            "options": ""
        },
        "bundle": {
            "maxMacroCmd": 5,
            "useMacroCommand": true
        },
        "datadriven": {
            "dataprovider": {
                "reader": "PipeFileReader"
            }
        },
        "widget": {
            "module": {
                "included": ""
            }
        }
    }
}

If the JSON String is empty or null, Tellurium loads the configuration file TelluriumConfig.groovy first from the project root. If not found, it loads it from the class path. As a result, you can put the TelluriumConfig.groovy file under the resources directory if you use Maven.

The configuration file TelluriumConfig.groovy includes three extra sections. That is to say, the bundle tier,

    //the bundling tier
    bundle{
        maxMacroCmd = 5
        useMacroCommand = false
    }

the i18n,

    i18n{
        //locale = "fr_FR"
        locale = "en_US"
    }

and the trace.

    test{
        execution{
            //whether to trace the execution timing
            trace = false
        }      

The sample configure file for 0.7.0 is available here.

Tellurium Core 0.7.0 added support for Configuration file check as suggested by our user. That can be easily demonstrated by the following example, if we comment out the following section in TelluriumConfig.groovy

      //the bundling tier
//    bundle{
//        maxMacroCmd = 5
//        useMacroCommand = false
//    }

What will happen? Tellurium will throw the following exception

java.lang.ExceptionInInitializerError
	at java.lang.Class.forName0(Native Method)
	at java.lang.Class.forName(Class.java:169)
	at org.telluriumsource.bootstrap.TelluriumSupport.class$(TelluriumSupport.groovy)
	at org.telluriumsource.bootstrap.TelluriumSupport.$get$$class$org$telluriumsource$framework$TelluriumFrameworkMetaClass(TelluriumSupport.groovy)
	at org.telluriumsource.bootstrap.TelluriumSupport.addSupport(TelluriumSupport.groovy:17)
	at org.telluriumsource.test.java.TelluriumTestNGTestCase.setUpForTest(TelluriumTestNGTestCase.java:22)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.testng.internal.MethodHelper.invokeMethod(MethodHelper.java:607)
	at org.testng.internal.Invoker.invokeConfigurationMethod(Invoker.java:417)
	at org.testng.internal.Invoker.invokeConfigurations(Invoker.java:154)
	at org.testng.internal.Invoker.invokeConfigurations(Invoker.java:88)
	at org.testng.TestRunner.beforeRun(TestRunner.java:510)
	at org.testng.TestRunner.run(TestRunner.java:478)
	at org.testng.SuiteRunner.runTest(SuiteRunner.java:332)
	at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:327)
	at org.testng.SuiteRunner.privateRun(SuiteRunner.java:299)
	at org.testng.SuiteRunner.run(SuiteRunner.java:204)
	at org.testng.TestNG.createAndRunSuiteRunners(TestNG.java:867)
	at org.testng.TestNG.runSuitesLocally(TestNG.java:832)
	at org.testng.TestNG.run(TestNG.java:748)
	at org.testng.remote.RemoteTestNG.run(RemoteTestNG.java:73)
	at org.testng.remote.RemoteTestNG.main(RemoteTestNG.java:124)
Caused by: org.telluriumsource.exception.ConfigNotFoundException: Cannot find Tellurium Configuration tellurium.bundle.maxMacroCmd, please check http://code.google.com/p/aost/wiki/TelluriumConfig070 for updated TelluriumConfig.groovy, or report to Tellurium user group at http://groups.google.com/group/tellurium-users
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
	at org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:77)
	at org.codehaus.groovy.runtime.callsite.ConstructorSite$ConstructorSiteNoUnwrapNoCoerce.callConstructor(ConstructorSite.java:107)
	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:52)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:192)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:200)
	at org.telluriumsource.config.TelluriumConfigurator.checkConfig(TelluriumConfigurator.groovy:41)

Run DSL Script

Tellurium 0.7.0 provides a rundsl.groovy script for users to run DSL test script. The rundsl.groovy uses Groovy Grape to automatically download all dependencies and then run DSL script.

First, you need to configure Grape. Put the following grapeConfig.xml file into your home/.groovy/.

<ivysettings>
  <settings defaultResolver="downloadGrapes"/>
  <property
    name="local-maven2-pattern"
    value="${user.home}/.m2/repository/[organisation]/[module]/[revision]/[module]-[revision](-[classifier]).[ext]"
    override="false" />
  <resolvers>
    <chain name="downloadGrapes">
      <filesystem name="cachedGrapes">
        <ivy pattern="${user.home}/.groovy/grapes/[organisation]/[module]/ivy-[revision].xml"/>
        <artifact pattern="${user.home}/.groovy/grapes/[organisation]/[module]/[type]s/[artifact]-[revision].[ext]"/>
      </filesystem>
      <filesystem name="local-maven-2" m2compatible="true" local="true">
        <ivy pattern="${local-maven2-pattern}"/>
        <artifact pattern="${local-maven2-pattern}"/>
      </filesystem>
      <!-- todo add 'endorsed groovy extensions' resolver here -->
      <ibiblio name="kungfuters.3rdparty" root="http://maven.kungfuters.org/content/repositories/thirdparty/" m2compatible="true"/>
      <ibiblio name="codehaus" root="http://repository.codehaus.org/" m2compatible="true"/>
      <ibiblio name="ibiblio" m2compatible="true"/>
      <ibiblio name="java.net2" root="http://download.java.net/maven/2/" m2compatible="true"/>
      <ibiblio name="openqa" root="http://archiva.openqa.org/repository/releases/" m2compatible="true"/>
      <ibiblio name="kungfuters.snapshot" root="http://maven.kungfuters.org/content/repositories/snapshots/" m2compatible="true"/>
      <ibiblio name="kungfuters.release" root="http://maven.kungfuters.org/content/repositories/releases/" m2compatible="true"/>
    </chain>
  </resolvers>
</ivysettings>

Then run

groovy rundsl.groovy -f DSL_script_name

If you are behind a firewall

groovy -Dhttp.proxyHost=proxy_host -Dhttp.proxyPort=proxy_port rundsl.groovy -f DSL_script_name

If you defined custom UI objects in your project, you should first build the jar artifact, and install it to your local Maven repo. Then update the rundsl.groovy file to add the new dependency. For example, in the tellurium-website reference project, we defined custom UI objects, we added the following two lines into the rundsl.groovy script.

Grape.grab(group:'org.telluriumsource', module:'tellurium-website', version:'0.7.0-SNAPSHOT', classLoader:this.class.classLoader.rootLoader)

...

   @Grab(group='org.telluriumsource', module='tellurium-website', version='0.7.0-SNAPSHOT')

An example rundsl.groovy script is as follows:

import groovy.grape.Grape;

Grape.grab(group:'org.telluriumsource', module:'tellurium-core', version:'0.8.0-SNAPSHOT', classLoader:this.class.classLoader.rootLoader)
Grape.grab(group:'org.stringtree', module:'stringtree-json', version:'2.0.10', classLoader:this.class.classLoader.rootLoader)
Grape.grab(group:'caja', module:'json_simple', version:'r1', classLoader:this.class.classLoader.rootLoader)
Grape.grab(group:'org.seleniumhq.selenium.server', module:'selenium-server', version:'1.0.1-te4-SNAPSHOT', classLoader:this.class.classLoader.rootLoader)
Grape.grab(group:'org.seleniumhq.selenium.client-drivers', module:'selenium-java-client-driver', version:'1.0.1', classLoader:this.class.classLoader.rootLoader)
Grape.grab(group:'org.apache.poi', module:'poi', version:'3.0.1-FINAL', classLoader:this.class.classLoader.rootLoader)
Grape.grab(group:'junit', module:'junit', version:'4.7', classLoader:this.class.classLoader.rootLoader)

import org.telluriumsource.dsl.DslScriptExecutor

@Grapes([
   @Grab(group='org.codehaus.groovy', module='groovy-all', version='1.7.0'),
   @Grab(group='org.seleniumhq.selenium.server', module='selenium-server', version='1.0.1-te4-SNAPSHOT', transitive=false),
   @Grab(group='org.seleniumhq.selenium.client-drivers', module='selenium-java-client-driver', version='1.0.1'),
   @Grab(group='junit', module='junit', version='4.7'),
   @Grab(group='caja', module='json_simple', version='r1'),
   @Grab(group='org.apache.poi', module='poi', version='3.0.1-FINAL'),
   @Grab(group='org.stringtree', module='stringtree-json', version='2.0.10'),
   @Grab(group='org.telluriumsource', module='tellurium-core', version='0.8.0-SNAPSHOT'),
])

def runDsl(String[] args) {
  def cli = new CliBuilder(usage: 'rundsl.groovy -[hf] [scriptname]')
  cli.with {
    h longOpt: 'help', 'Show usage information'
    f longOpt: 'scriptname',   'DSL script name'
  }
  def options = cli.parse(args)
  if (!options) {
    return
  }
  if (options.h) {
    cli.usage()
    return
  }
  if (options.f) {
    def extraArguments = options.arguments()
    if (extraArguments) {
      extraArguments.each {String name ->
        def input = [name].toArray(new String[0])
        DslScriptExecutor.main(input)
      }
    }
  }

}

println "Running DSL test script, press Ctrl+C to stop."

runDsl(args)

useTelluriumEngine

To make it convenient for users, Tellurium provides a useTelluriumEngine command as follows,

    void useTelluriumEngine(boolean isUse){
        useCache(isUse);
        useMacroCmd(isUse);
        useTelluriumApi(isUse);
    }   

As you can see, this command actually consists of three commands. In DslContext, Tellurium also provides two handy DSL style methods.

   void enableTelluriumEngine();

   void disableTelluriumEngine();

Trace

Tellurium 0.7.0 provides built-in support for the command execution time including

  • Execution time for each command
  • Total run time
  • Aggregated times for each command

For example, you can see the output as follows,

TE: Name: getCurrentCachePolicy, start: 1260496075484, duration: 28ms
TE: Name: useDiscardNewCachePolicy, start: 1260496075514, duration: 51ms
TE: Name: getCurrentCachePolicy, start: 1260496075566, duration: 74ms
TE: Name: useDiscardOldCachePolicy, start: 1260496075642, duration: 35ms
TE: Name: getCurrentCachePolicy, start: 1260496075678, duration: 42ms
TE: Start Time: 1260496060373
End Time: 1260496075720
Total Runtime: 15347ms
Name: keyPress, count: 24, total: 1277ms, average: 53ms
Name: getCurrentCachePolicy, count: 5, total: 222ms, average: 44ms
Name: useDiscardOldCachePolicy, count: 1, total: 35ms, average: 35ms
Name: useDiscardInvalidCachePolicy, count: 1, total: 33ms, average: 33ms
Name: enableCache, count: 2, total: 151ms, average: 75ms
Name: click, count: 3, total: 194ms, average: 64ms
Name: isElementPresent, count: 2, total: 100ms, average: 50ms
Name: useDiscardLeastUsedCachePolicy, count: 1, total: 39ms, average: 39ms
Name: type, count: 1, total: 81ms, average: 81ms
Name: typeKey, count: 3, total: 124ms, average: 41ms

We added the following settings to TelluriumConfig.groovy

    test{
        execution{
            //whether to trace the execution timing
            trace = false
        }      

You can use the follow methods in DslContext to turn on or off the trace, and get the trace data.

  public void enableTrace();

  public void disableTrace();
  
  public void showTrace();

Methods Accessible in Test Cases

There are many Tellurium APIs that used to be available only in DslContext. That is to say, you have to extend DslContext to use them. For example, you often see code in a test case likes this,

GoogleSearchModule gsm = new GoogleSearchModule();
gsm.defineUi();
gsm.usejQuerySelector();
gsm.registerNamespace("te", te_ns);

Now, many of them, which are not really tied to a specific UI module, are made available in Tellurium test cases. For example, the above code can be changed as follows,

GoogleSearchModule gsm = new GoogleSearchModule();
gsm.defineUi();
useCssSelector();
registerNamespace("te", te_ns);

New TelluriumGroovyTestCase provides the following list of new APIs for your convenience.

    public void useCssSelector(boolean isUse);

    public void useCache(boolean isUse);

    public void cleanCache();

    public boolean isUsingCache();

    public void setCacheMaxSize(int size);

    public int getCacheSize();

    public int getCacheMaxSize();

    public Map<String, Long> getCacheUsage();

    public void useCachePolicy(CachePolicy policy);

    public String getCurrentCachePolicy();

    public void useDefaultXPathLibrary();

    public void useJavascriptXPathLibrary();

    public void useAjaxsltXPathLibrary();

    public void registerNamespace(String prefix, String namespace);

    public String getNamespace(String prefix);

    public void pause(int milliseconds);

    public void useMacroCmd(boolean isUse);

    public void setMaxMacroCmd(int max);

    public int getMaxMacroCmd();

    public boolean isUseTelluriumApi();

    public void useTelluriumApi(boolean isUse);
  
    public void useTrace(boolean isUse);

    public void showTrace();

    public void setEnvironment(String name, Object value);

    public Object getEnvironment(String name);

    public void allowNativeXpath(boolean allow);

    public void addScript(String scriptContent, String scriptTagId);

    public void removeScript(String scriptTagId);

    public void EngineState getEngineState();

    public void useEngineLog(boolean isUse);

    public void useTelluriumEngine(boolean isUse);

    public void dumpEnvironment();

Tellurium Java test cases provide the same APIs and the only difference is that the APIs in Tellurium Java test cases are static.

Environment

We added an Environment class to Tellurium Core so that you can change the runtime environment. The Environment class is defined as follows,

public class Environment {
  def envVariables = [:];
  public boolean isUseCssSelector();

  public boolean isUseCache();

  public boolean isUseBundle();

  public boolean isUseScreenshot();

  public boolean isUseTrace();

  public boolean isUseTelluriumApi();

  public boolean isUseEngineLog();

  public void useCssSelector(boolean isUse);

  public void useCache(boolean isUse);

  public void useBundle(boolean isUse);

  public void useScreenshot(boolean isUse);

  public void useTrace(boolean isUse);

  public void useTelluriumApi(boolean isUse);

  public void useEngineLog(boolean isUse);

  public useMaxMacroCmd(int max);

  public int myMaxMacroCmd();

  public String myLocale();
  
  public void setCustomEnvironment(String name, Object value);

  public Object getCustomEnvironment(String name);
}

where setCustomEnvironment and getCustomEnvironment can be used to pass user defined environment variables.

Tellurium provides the following method for users to dump out all the current environment variables.

    public void dumpEnvironment();

The output is like the following.

    Environment Variables:
      useEngineCache: true
      useClosestMatch: false
      useMacroCommand: true
      maxMacroCmd: 5
      useTelluriumApi: true
      locatorWithCache: true
      useCSSSelector: true
      useTrace: true
      logEngine: false
      locale: en_US

If any exception is thrown at the dispatcher tier, the environment variable will also be printed out.

Get UIs by Tag Name

As requested by users, Tellurium 0.7.0 added the following two methods.

UiByTagResponse getUiByTag(String tag, Map filters);

void removeMarkedUids(String tag);

The first method passes in the tag name and the attributes as filters and get back the UI elements associated with the tag. The response object is defined as

class UiByTagResponse {
  //tag name
  String tag;

  Map filters

  //temporally assigned IDs
  String[] tids;
}

where the tids are assigned by Tellurium Engine. Under the hood, Tellurium core creates one AllPurposeObject for each tid in the tids array and registers it to Core so that users can use the tid as the UID to act on the UI object.

The second method cleans up all the temporally assigned IDs by the Tellurium Engine.

Example:

In UI module JettyLogonModule, we define the following method:

  public String[] getInputBox(){
     def attrs = ["type" : "text"];
     UiByTagResponse resp = getUiByTag("input", attrs);
    
     return resp.tids;
  }

The test case is as follows

    @Test
    public void testGetUiByTag(){
        useEngineLog(true);
        useTelluriumApi(true);
        useCache(true);
        String[] teuids = jlm.getInputBox();
        assertNotNull(teuids);
        for(String teuid: teuids){
            jlm.keyType(teuid, "Tellurium Source");
        }
        jlm.removeMarkedUids("input");
    }

Misc

The default locator in Tellurium Engine is CSS selector by default. That is to say, we set

    exploitCssSelector = true

in the Environment class and you can use

    disableCssSelector()

or

    useCssSelector(false)

to switch back to xpath if you like to.

Exposed the following Selenium APIs to DslContext.

  • void addScript(String scriptContent, String scriptTagId)
  • void removeScript(String scriptTagId)
  • void captureEntirePageScreenshot(String filename, String kwargs)
  • String captureScreenshotToString()
  • String captureEntirePageScreenshotToString(String kwargs)
  • String retrieveLastRemoteControlLogs()

The following methods in DslContext have been renamed

  • String jsonify(String uid) is renamed to String toString(String uid)
  • String generateHtml(String uid) is renamed to String toHTML(String uid)


Sign in to add a comment
Powered by Google Project Hosting