Testing WebServices with Squish (Windows, JavaScript)

Last edited on

Introduction

Modern and complex web applications often offer two interfaces: one for humans (GUI) and one for other computers/systems (Web Services Interface).

While Squish is primarily a GUI testing tool, even for some GUI testing scenarios sending a request to a web service may be needed.

Web services can provision test data for a GUI test by changing or verifying a web application state (if not possible via the GUI). For long and complex test scenarios, sending a web service request may also speed-up the total test execution time.

For example, if a pre-condition for every GUI test is to create a specific user for that test, but creating such user is not a goal of our GUI test, then we can create those users using the Web Service Interface (assuming the web application offers such functionality).

Solution

The following solution is designed using JavaScript and Squish running on a Windows environment. Similar solutions can be developed for other Squish-supported platforms and scripting languages.

To test our solution, we are using a public web service offered by http://www.webservicex.net (note: dead link). We will send a SOAP 1.2 web service request via the HTTP POST method to www.webservicex.com/globalweather.asmx with the GetCitiesByCountry SOAP action. As a result we will get a response with the list of airports for given country.

Sending a SOAP Request

JavaScript does not offer built-in support for handling web services. The XMLHttpRequest object is a JavaScript extension provided by web browsers, therefore we cannot use this object in a test case. One solution is to utilize Socket object, however this would require us to first implement an HTTP protocol to capable of sending a SOAP request over HTTP. Another, and more preferred approach, is to call an external program (using JavaScript OS object) to send the SOAP Request.

We developed a simple VBScript for the Windows platform which utilizes the Msxml2.XMLHTTP object to send the SOAP Request. This program can be called from command line:

MS_HTTPXML.vbs <SOAP_REQUEST_FILE> <SOAP_RESPONSE_FILE> <WS_POST> <SOAP_ACTION> <WS_HOST>
MS_HTTPXML.vbs

Where <SOAP_REQUEST_FILE> is an XML file with SOAP request and <SOAP_RESPONSE_FILE> is a filename, where the response from a web service shall be written.

MS_HTTPXML.vbs soap_req.xml soap_resp.xml http://www.webservicex.com/globalweather.asmx http://www.webserviceX.NET/GetCitiesByCountry www.webservicex.com
Example

Next we need to create a function to call in a test case which sends the SOAP request. The function requires two arguments: (a) the filename containing the SOAP Request and (b) a filename where the SOAP response shall be written. All web service parameters will be fetched from an external test configuration file ws_params.js containing the necessary parpameters to send the SOAP request to our test web service.

var post = "http://www.webservicex.com/globalweather.asmx"
var soap_action = "http://www.webserviceX.NET/GetCitiesByCountry"
var ws_host = "www.webservicex.com"
ws_params.js

Since the web service parameters are in an external test configuration file, we can use the Squish findFile function. If the same request is used across multiple test cases, then we can store ws_params.js in our Test Suite Recourses, otherwise we can store it in the Test Case Resources, available only to the applicable test case.

function soap(soap_req,soap_resp){
    source(findFile("scripts","ws_params.js"))
    soap_vbs = findFile("scripts","MS_HTTPXML.vbs")
    cmd = soap_vbs+' '+soap_req+' '+soap_resp+' '+post+' '+soap_action+' '+ws_host
    var result = OS.system(cmd)
    if (result== -1)
        test.fatal("soap(): Fatal error")
}
soap.js

Both MS_HTTPXML.vbs script and soap function need to be placed in the Global Scripts directory, making it accessible to all Test Cases within all Test Suites.

Simple Test Case

First, we need to prepare a SOAP Request to be sent (file SOAP_request.xml) and place it in the Test Case Resources (Test Data).

<s12:Envelope xmlns:s12='http://www.w3.org/2003/05/soap-envelope'>
<s12:Body>
<ns1:GetCitiesByCountry xmlns:ns1='http://www.webserviceX.NET'>
<ns1:CountryName xmlns:ns1='http://www.webserviceX.NET'>Poland</ns1:CountryName>
</ns1:GetCitiesByCountry>
</s12:Body>
</s12:Envelope>
SOAP_request.xml

Below is a simple test case which sends the SOAP request (above). After which we call a waifFor function to wait 30 seconds for the SOAP response to arrive.

source(findFile("scripts","soap.js"))

function main(){

    soap_req = squishinfo.testCase+"\\testdata\\SOAP_request.xml"
    soap_resp = squishinfo.testCase+"\\testdata\\SOAP_resp.xml";

    //Remove existing (old) response
    if (File.exists(soap_resp)) {
        test.log("Removed old SOAP response file: "+soap_resp)
        File.remove(soap_resp)
    }

    soap(soap_req,soap_resp)

    //Wait for SOAP response
    if (waitFor("File.exists(soap_resp)",30000))
        test.pass("SOAP response received")
    else
        test.fatal("Timeout waiting for SOAP response!")

}
Send SOAP Request Test Case

Making the Test Case Data Driven

In the above example we store the SOAP request in the Test Case Resources using a hardcoded country name <ns1:CountryName xmlns:ns1='http://www.webserviceX.NET'>Poland</ns1:CountryName>. This solution is sufficient if we only have a few test cases sending requests to a web service. For more extensive use a more robust solution is recommended.

*First, we would like to avoid fixing multiple test cases when a SOAP request format changes.

*Second, we would like to make our test case data driven, therefore we should separate the SOAP request itself from the test data ("Poland").

Preparing a SOAP Request Template

We can achieve both by preparing a SOAP request template and storing it in the Test Suite Resources (Test Data). The request contains a CountryName place holder which is filled with the test case provided country name before sending the SOAP request.

<s12:Envelope xmlns:s12='http://www.w3.org/2003/05/soap-envelope'>
<s12:Body>
<ns1:GetCitiesByCountry xmlns:ns1='http://www.webserviceX.NET'>
<ns1:CountryName xmlns:ns1='http://www.webserviceX.NET'>{CountryName}</ns1:CountryName>
</ns1:GetCitiesByCountry>
</s12:Body>
</s12:Envelope>
SOAP_request_template.xml

Creating a Test Data Resource

Next create a new test data resource with only one column CountryName.

Sending Multiple SOAP Requests

Finally we can create a test case which sends multiple SOAP requests depending on entries in the test data table as demonstrated in the code snippet below. The code snippet iterates over all table entries in the test data and replaces CountryName with the country name value from the table. After the test case executes, all sent and received SOAP requests are stored in Test Case Recourses for further analysis.

source(findFile("scripts","soap.js"))

function main(){

    // Read template for SOAP Request
    var file = File.open(findFile("testdata","SOAP_request_template.xml"))
    var soap_template = file.read()
    file.close()

    var dataset = testData.dataset("data_1.tsv")
    var i=0
    for (var record in dataset) {
        i++

        // Prepare SOAP Request
        var countryname = testData.field(dataset[record], "CountryName");
        soap_req_content = soap_template.replace(/{CountryName}/g,countryname)
        soap_req = squishinfo.testCase+"\\testdata\\SOAP_request_"+i+".xml"
        var file2 = File.open(soap_req, "w")
        file2.write(soap_req_content)
        file2.close()

        // Send SOAP Request
        soap_resp = squishinfo.testCase+"\\testdata\\SOAP_resp_"+i+".xml"

        //Remove existing (old) response
        if (File.exists(soap_resp)) {
            test.log("Removed old SOAP response file: "+soap_resp)
            File.remove(soap_resp)
        }

        soap(soap_req,soap_resp)

        //WaitFor SOAP response
        if (!(waitFor("File.exists(soap_resp)",30000)))
                test.fatal("Timeout waiting for SOAP response!")

    }

}
Sending multiple SOAP Request from a template

Parsing A SOAP Response

So far in our examples we only checked if a SOAP response arrived, but did not verify its content. We can parse the SOAP response using the JavaScript XML Object.

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
	<soap:Body>
		<GetCitiesByCountryResponse xmlns="http://www.webserviceX.NET">
			<GetCitiesByCountryResult>
				<NewDataSet>
  					<Table>
    					    <Country>Poland</Country>
    					    <City>Gdansk-Rebiechowo</City>
  					</Table>
  					<Table>
   		 			    <Country>Poland</Country>
    					    <City>Krakow</City>
  					</Table>
  					<Table>
    					    <Country>Poland</Country>
    					    <City>Warszawa-Okecie</City>
  					</Table>
				</NewDataSet>
			</GetCitiesByCountryResult>
		</GetCitiesByCountryResponse>
	</soap:Body>
</soap:Envelope>
SOAP_resp.xml

The following code snippet demonstrates how to parse the XML file (above).

  1. First we call XML.parse(soap_xml) to obtain a documentNode.

  2. Then we can traverse the file using the firstChild, nextSibling and textContent properties of the XMLNode object.

// Read SOAP request file
var file = File.open(soap_resp)
var soap_xml = file.read()
file.close()

// Parse SOAP XML
var documentNode = XML.parse(soap_xml)
var soap_body = documentNode.firstChild.nextSibling.firstChild
test.compare(soap_body.nodeName,"soap:Body")
var data_set = soap_body.firstChild.firstChild.firstChild
test.compare(data_set.nodeName,"NewDataSet")

// answ = <Table>
var answ = data_set.firstChild
test.compare(answ.isNull,false,"<Table> element exists.")

test.compare(answ.firstChild.nextSibling.textContent,"Gdansk-Rebiechowo","First is Gdansk?")
answ = answ.nextSibling;
test.compare(answ.firstChild.nextSibling.textContent,"Krakow","Second is Krakow?")
Read and Parse SOAP Request

Sample Attached

A Test Suite containing the example test cases, global script directory and necessary scripts and functions used in Test Suite is attached to this article.