Extension Step scriptStep
Description
Provides the ability to use scripting code in your tests. Deprecated in favor of using the apache script task.
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/> ).
- 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:
...
<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> </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:
&sharedConfiguration;
<steps>
<invoke description="Load Initial Page"
url="order.html"/>
<scriptStep description="calculate qty and price" language="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_found" text="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.
<invoke description="Load Order Page"
url="order.html"/>
<scriptStep description="check qty and price" language="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:
...
<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:
<property name="image_id" value="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:
<property name="image_id" value="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:
<property name="image_id" value="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="true" 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}"/>
<scriptStep description="test variables are persistent when using keep" language="groovy">
step.setWebtestProperty('stopvar', src2alt["red.gif"]);
</scriptStep>
<verifyProperty name="stopvar" text="stop"/>
</steps>
</webtest>
</target>
Rather than having the scripting code inline, you can place it in a file and reference that file as follows:
<property name="image_id" value="traffic_light"/>
<webtest name="scriptStep: test Groovy Code from file">
&sharedConfiguration;
<steps>
<scriptStep description="use from file like a macro"
language="groovy" src="${basedir}/GMacroSteps.groovy"/>
</steps>
</webtest>
</target>
Fibonacci Example
Consider an HTML page displaying mathematically significant numbers with markup as follows:
<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:
<webtest name="scriptStepManualTests: test numbers page with inlined ruby Script">
&sharedConfiguration;
<steps>
<invoke description="Load Initial Page" url="numbers.html"/>
<storeXPath description="extract number to check"
xpath="//span[@id='fibonacci']/text()"
property="number"/>
<scriptStep description="check if number is indeed in Fibonacci series" language="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="found" text="true" />
</steps>
</webtest>
</target>
Here is the example again using Jython (requires jython.jar in your CLASSPATH):
<webtest name="scriptStepManualTests: test numbers page with inlined jython Script">
&sharedConfiguration;
<steps>
<invoke description="Load Initial Page" url="numbers.html"/>
<storeXPath description="extract number to check"
xpath="//span[@id='fibonacci']/text()"
property="number"/>
<scriptStep description="check if number is indeed in Fibonacci series" language="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="found" text="true" />
</steps>
</webtest>
</target>
Here is the example again using BeanShell (requires bsh-XX.YY.jar in your CLASSPATH):
<webtest name="scriptStepManualTests: test numbers page with inlined BeanShell Script">
&sharedConfiguration;
<steps>
<invoke description="Load Initial Page" url="numbers.html"/>
<storeXPath description="extract number to check"
xpath="//span[@id='fibonacci']/text()"
property="number"/>
<scriptStep description="check if number is indeed in Fibonacci series" language="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="found" text="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.