Canoo Webtest WebTest Canoo

Extension Step scriptStep

Description

Provides the ability to use scripting code in your tests.

ANT has a normal style requiring declarative specification of your build tasks. If you find this too limiting in certain scenarios, Ant provides a script task which lets you dive into (non-declarative) scripting code. The same need occurs within your Canoo WebTest steps; perhaps you need to do something not supported by the standard steps, or perhaps you wish to perform some tests in a way better-suited to programmatic rather than declarative means.

This step helps in these scenarios by providing a wrapper around the ANT script task. Before providing the supplied script to the ANT script task, scriptStep expands any WebTest dynamic properties and defines some useful variables. Results from running the script can be made available for subsequent steps to use.

The language must be one of those supported by the Bean Scripting Framework (BSF) which means one of JavaScript, Python (using either Jython or JPython), Tcl (using Jacl), NetRexx, XSLT Stylesheets, Java (using BeanShell), JRuby, Groovy, ObjectScript, and JudoScript. See also the BSF documentation for other languages which BSF has supported in the past - you might be able to get other languages working with a bit of work.

WebTest has all the necessary files to support Groovy and JavaScript natively (and also XSLT but it isn't usually used directly). For other languages you will need to download the necessary support files - usually just one jar - and update your classpath appropriately.

Note: although scriptStep does support JavaScript, at the moment, it doesn't provide the mechanism to call JavaScript functions in your HTML pages under test.

Parameters

description
Required? no
The description of this test step.
keep
Required? no, default is false
Indicates that the script engine should be kept for future steps. Variables created during one script step will remain available.
language
Required? yes/no
The scripting language to use. Required unless using the keep attribute in which case the value is optional but must agree with the original language if used. The value can be any language supported by the Bean Scripting Framework (BSF), e.g. javascript, jacl, netrexx, java, javaclass, bml, vbscript, jscript, perlscript, perl, jpython, jython, lotusscript, xslt, pnuts, beanbasic, beanshell, ruby, judoscript, groovy.
src
Required? yes/no
The name of the file containing the scripting code. You may omit this parameter if you have embedded script code.

Inline Text

The inline text is all the text between the start tag ( <scriptStep> ) and the end tag ( </scriptStep> ), including blanks, tabs or newlines. Using a pair of start/end tags ( <scriptStep> </scriptStep> ) has not the same behavior than the seemingly equivalent empty element tag ( <scriptStep/> )! See this issue for an example.

Required? yes/no
The nested script code. You may omit this if you use the parameter src.

Details

In order for the script to access its environment, the step sets some variables. These variables are set to Java objects in WebTest model. The API of WebTest will tell what these objects provide, but the scripting language used determine how the objects are accessed.

step
The scriptStep enclosing the script.
response
The last response received from the server.
document
The html or xml DOM for the last response.

Order Example

Consider the following fictitious order information embedded in an HTML page as follows:

OrderPage
<html>
...
<h1>Your Order</h1>
<table border='1' width='60%'>
  <tr><th>Item</th><th>Qty</th><th>Unit Price</th><th>Amount</th></tr>
  <tr class='lineitem'><td>Mouse Pad</td><td>3</td><td>5</td><td>15</td></tr>
  <tr class='lineitem'><td>USB Memory Stick 512Mb</td><td>1</td><td>50</td><td>50</td></tr>
  <tr class='total'><td>Total</td><td>4</td><td>&nbsp;</td><td>65</td></tr>
</table>
...
</html>

Suppose we are testing that this order contains the correct information. For each line item, we want to check that the quantity times the unit price equals the total price for that line and that the total quantity and total price values in the final row actually agree with the values summed from previous line item rows. Here is some code we might use to perform these checks:

JavascriptOrderTest
<webtest name="scriptStep: test fictitious order page with inlined JavaScript script">
&sharedConfiguration;
<steps>
<invoke description="Load Initial Page"
 url="order.html"/>
<scriptStep description="calculate qty and pricelanguage="javascript">
   calc_qty = 0;
   calc_price = 0;
   items = document.getHtmlElementsByAttribute('tr', 'class', 'lineitem').iterator();
   while (items.hasNext()) {
       table_cells = items.next().getHtmlElementsByTagName('td');
       qty = parseInt(table_cells.get(1).asText());
       unit_price = parseInt(table_cells.get(2).asText());
       total_line_price = parseInt(table_cells.get(3).asText());
       calc_qty += qty;
       calc_price += total_line_price;
       if (qty * unit_price != total_line_price) {
         step.setWebtestProperty('calc_error_found', 'true');
       }
   }
   step.setWebtestProperty('calc_qty', calc_qty);
   step.setWebtestProperty('calc_price', calc_price);
</scriptStep>
<not>
   <verifyProperty name="calc_error_foundtext="true" />
</not>
<verifyXPath description="check total qty"
   xpath="//tr[@class='total']/td[2]"
   text="#{calc_qty}"/>
<verifyXPath description="check total price"
   xpath="//tr[@class='total']/td[4]"
   text="#{calc_price}"/>
</steps>
</webtest>

We could have done most of this using XPath but the simple approach of hard-coding an XPath statement for each line item would make our tests brittle if the number of line items could change in future tests.

Here is the same example again using Groovy and making use of Groovy's assert functionality.

GroovyOrderTest
<steps>
    <invoke description="Load Order Page"
        url="order.html"/>
    <scriptStep description="check qty and pricelanguage="groovy">
        calc_qty = 0
        calc_price = 0
        document.getHtmlElementsByAttribute('tr', 'class', 'lineitem').each{
            table_cells = it.getHtmlElementsByTagName('td')
            qty = table_cells.get(1).asText().toInteger()
            unit_price = table_cells.get(2).asText().toInteger()
            total_line_price = table_cells.get(3).asText().toInteger()
            calc_qty += qty
            calc_price += total_line_price
            assert qty * unit_price == total_line_price
        }
        root = new XmlSlurper().parseText(document.asXml())
        totalCols = root.depthFirst().findAll{ it.name() == "tr" }.find{ it['@class'] == 'total' }.td
        // slightly shorter alternative to above if you don't mind explicitly specifying tr
        // totalCols = root.body.table.tbody.tr.find { it['@class'] == 'total' }.td
        assert calc_qty == totalCols[1].text().trim().toInteger()
        assert calc_price == totalCols[3].text().trim().toInteger()
    </scriptStep>
</steps>

Traffic Light Example

Here is an example which shows some script code used to calculate what the alt text should be for a particular image. It assumes your page has HTML similar to the following:

TrafficLightPage
<html>
...
<img id='traffic_light' src='green.gif' alt='go'>
...
</html>

We want to test that the correct alt text is used for the correct image. Here is the test code using JavaScript:

JavascriptTrafficLightTest
<target name="testInlineJavaScript">
  <property name="image_idvalue="traffic_light"/>
  <webtest name="scriptStep: test scriptStep with inlined JavaScript script">
    &sharedConfiguration;
    <steps>
      <invoke description="Load Initial Page"
        url="trafficlight.html"/>
        <storeXPath description="extract src attribute from image with id=${image_id}"
          xpath="//img[@id='${image_id}']/@src"
          property="imagesrc"/>
        <scriptStep description="calculate expected alt text for src=#{imagesrc}language="javascript">
          var src2alt = new Array();
          src2alt["red.gif"] = "stop";
          src2alt["orange.gif"] = "wait";
          src2alt["green.gif"] = "go";
          step.setWebtestProperty('image_alt', src2alt["#{imagesrc}"]);
        </scriptStep>
        <verifyXPath description="check alt value"
          xpath="//img[@id='${image_id}']/@alt"
          text="#{image_alt}"/>
    </steps>
  </webtest>
</target>

In this case, scripting is not strictly necessary because we could have used XPath but it provides a useful simple example.

Here is the same example using Groovy:

GroovyTrafficLightTest
<target name="testInlineGroovy">
  <property name="image_idvalue="traffic_light"/>
  <webtest name="scriptStep: test scriptStep with inlined groovy Script">
    &sharedConfiguration;
    <steps>
      <invoke description="Load Initial Page"
        url="trafficlight.html"/>
        <storeXPath description="extract src attribute from image with id=${image_id}"
          xpath="//img[@id='${image_id}']/@src"
          property="imagesrc"/>
        <scriptStep description="calculate expected alt text for src=#{imagesrc}language="groovy">
          src2alt = ['red.gif':'stop', 'orange.gif':'wait', 'green.gif':'go']
          step.setWebtestProperty('image_alt', src2alt[step.webtestProperties.imagesrc])
        </scriptStep>
        <verifyXPath description="check alt value"
          xpath="//img[@id='${image_id}']/@alt"
          text="#{image_alt}"/>
    </steps>
  </webtest>
</target>

And the same again illustrating the keep attribute:

GroovyTrafficLightWithKeepTest
<target name="testInlineGroovyWithKeep">
  <property name="image_idvalue="traffic_light"/>
  <webtest name="scriptStep: test scriptStep with inlined groovy Script and using keep">
    &sharedConfiguration;
    <steps>
      <invoke description="Load Initial Page"
        url="trafficlight.html"/>
        <storeXPath description="extract src attribute from image with id=${image_id}"
          xpath="//img[@id='${image_id}']/@src"
          property="imagesrc"/>
        <scriptStep description="calculate expected alt text for src=#{imagesrc}"
                keep="truelanguage="groovy">
          src2alt = ['red.gif':'stop', 'orange.gif':'wait', 'green.gif':'go']
          step.setWebtestProperty('image_alt', src2alt[step.webtestProperties.imagesrc])
        </scriptStep>
        <verifyXPath description="check alt value"
          xpath="//img[@id='${image_id}']/@alt"
          text="#{image_alt}"/>
                <scriptStep description="test variables are persistent when using keeplanguage="groovy">
                    step.setWebtestProperty('stopvar', src2alt["red.gif"]);
                </scriptStep>
                <verifyProperty name="stopvartext="stop"/>
    </steps>
  </webtest>
</target>

Rather than having the scripting code inline, you can place it in a file and reference that file as follows:

GroovyFileTrafficLightTest
<target name="testFileGroovy">
  <property name="image_idvalue="traffic_light"/>
  <webtest name="scriptStep: test Groovy Code from file">
    &sharedConfiguration;
    <steps>
      <scriptStep description="use from file like a macro"
        language="groovysrc="${basedir}/GMacroSteps.groovy"/>
    </steps>
  </webtest>
</target>

Fibonacci Example

Consider an HTML page displaying mathematically significant numbers with markup as follows:

FibonacciPage
<html>
<head>
<title>Special Numbers</title>
</head>
<body>
<h1>Today's Special Numbers</h1>
<p>The prime number of the day is <span id='prime'>233</span>
and the fibonacci number of the day is <span id='fibonacci'>233</span>.
</body>
</html>

Here is a JRuby example (requires jruby.jar in your CLASSPATH), showing how to test that a web page displaying a Fibonacci number does in fact display a correct value:

RubyFibonacciTest
<target name="testFibonacciRuby">
  <webtest name="scriptStepManualTests: test numbers page with inlined ruby Script">
    &sharedConfiguration;
    <steps>
      <invoke description="Load Initial Pageurl="numbers.html"/>
      <storeXPath description="extract number to check"
        xpath="//span[@id='fibonacci']/text()"
        property="number"/>
      <scriptStep description="check if number is indeed in Fibonacci serieslanguage="ruby"><![CDATA[
        def isFib(n)
          a, b = 0, 1
          a, b = b, a + b while b < n
          return n == 0 || b == n
        end
        $bsf.lookupBean("step").setWebtestProperty("found", "true") if isFib(#{number})
      ]]></scriptStep>
      <verifyProperty name="foundtext="true" />
    </steps>
  </webtest>
</target>

Here is the example again using Jython (requires jython.jar in your CLASSPATH):

JythonFibonacciTest
<target name="testFibonacciJython">
  <webtest name="scriptStepManualTests: test numbers page with inlined jython Script">
    &sharedConfiguration;
    <steps>
      <invoke description="Load Initial Pageurl="numbers.html"/>
      <storeXPath description="extract number to check"
        xpath="//span[@id='fibonacci']/text()"
        property="number"/>
      <scriptStep description="check if number is indeed in Fibonacci serieslanguage="jython"><![CDATA[
        def isFib(n):
          a,b = 0,1
          while b < n:
            a,b = b,a+b
          if b == n: return 1
          return 0

        if isFib(#{number}): step.setWebtestProperty("found", "true")
      ]]></scriptStep>
      <verifyProperty name="foundtext="true" />
    </steps>
  </webtest>
</target>

Here is the example again using BeanShell (requires bsh-XX.YY.jar in your CLASSPATH):

BeanShellFibonacciTest
<target name="testFibonacciBeanShell">
  <webtest name="scriptStepManualTests: test numbers page with inlined BeanShell Script">
    &sharedConfiguration;
    <steps>
      <invoke description="Load Initial Pageurl="numbers.html"/>
      <storeXPath description="extract number to check"
        xpath="//span[@id='fibonacci']/text()"
        property="number"/>
      <scriptStep description="check if number is indeed in Fibonacci serieslanguage="beanshell"><![CDATA[
isFib(n) {
    a = 0;
    b = 1;
    while (b < n) {
      tempa = a;
      a = b;
      b = tempa + b;
    }
    return b == n;
}

if (isFib(#{number})) step.setWebtestProperty("found", "true");
      ]]></scriptStep>
      <verifyProperty name="foundtext="true" />
    </steps>
  </webtest>
</target>

See also: the groovy step which offers the same functionality but specifically for the Groovy language. The groovy functionality also allows test scripts to be written in groovy and call back into ANT and WebTest.

news

Latest build: development
Posted: 19-Jul-2016 17:36

WebTest 3.0 released, featuring upgrades to Java 5, Groovy 1.6, and HtmlUnit 2.4.
The release includes support for maven integration, IDE-integration like for unit tests, capturing of background JavaScript errors, new steps for mouseOver and mouseOut events, better parallel execution of tests and - as usual - lots of handling improvements.
Posted: 5 March 2009

WebTest @ JavaOne
Dierk König presented "Functional testing of web applications: scaling with Java" on Wed May 7, 13:30 at JavaOne 2008 in the Tools and Scripting Languages track.
Posted: 8 May 2008

New WebTest screencast available:
Data Driven WebTest
Posted: 13 November 2007

First WebTest screencast available:
Creating a first Webtest Project

Extend WebTest with Groovy! Groovy in Action is available in every good bookstore.
Groovy in Action
Posted: 29 January 2007