Skip to content

SOAP and XML load testing

SOAP (Simple Object Access Protocol) is an XML messaging protocol used in enterprise web services. Many organizations still run SOAP services in finance, healthcare, and government systems. SOAP is HTTP POST with an XML body, so MaxoPerf load-tests it with the same HTTP mechanisms you use for REST.

  • Read HTTP and REST load testing. SOAP load testing is a special case of HTTP POST.
  • Get the WSDL for the service you test. It gives you the endpoint URL, action headers, and message structure.

SOAP requests are HTTP POST requests with:

  • Content-Type: text/xml; charset=utf-8 (SOAP 1.1) or application/soap+xml (SOAP 1.2).
  • A SOAPAction header identifying the operation (SOAP 1.1 only).
  • An XML SOAP envelope in the request body.

The response is a 200 OK with an XML SOAP envelope body, or a 500 with a SOAP Fault envelope when the operation fails.

execution:
- concurrency: 20
ramp-up: 30s
hold-for: 5m
scenario: soap-account-service
scenarios:
soap-account-service:
default-address: https://services.example.com
requests:
- label: GetAccountBalance
url: /AccountService
method: POST
headers:
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://example.com/AccountService/GetAccountBalance"
body: |
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:acc="http://example.com/AccountService">
<soap:Header/>
<soap:Body>
<acc:GetAccountBalance>
<acc:AccountId>${account_id}</acc:AccountId>
</acc:GetAccountBalance>
</soap:Body>
</soap:Envelope>
assert:
- equals:
subject: http-code
value: '200'
- not-contains:
subject: body
value: '<soap:Fault>'
- label: TransferFunds
url: /AccountService
method: POST
headers:
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://example.com/AccountService/TransferFunds"
body: |
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:acc="http://example.com/AccountService">
<soap:Header>
<acc:AuthToken>${session_token}</acc:AuthToken>
</soap:Header>
<soap:Body>
<acc:TransferFunds>
<acc:FromAccount>${account_id}</acc:FromAccount>
<acc:ToAccount>TARGET-001</acc:ToAccount>
<acc:Amount>100.00</acc:Amount>
</acc:TransferFunds>
</soap:Body>
</soap:Envelope>
assert:
- not-contains:
subject: body
value: '<soap:Fault>'

SOAP errors return HTTP 200 with a <soap:Fault> element in the body (SOAP 1.1) or HTTP 500. Add a not-contains assertion for <soap:Fault> to catch application-level errors:

assert:
- not-contains:
subject: body
value: '<soap:Fault>'

k6 sends SOAP requests with its http.post() function, the SOAP headers, and an XML body string:

import http from 'k6/http';
import { check } from 'k6';
const SOAP_URL = 'https://services.example.com/AccountService';
function buildGetBalanceEnvelope(accountId) {
return `<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:acc="http://example.com/AccountService">
<soap:Header/>
<soap:Body>
<acc:GetAccountBalance>
<acc:AccountId>${accountId}</acc:AccountId>
</acc:GetAccountBalance>
</soap:Body>
</soap:Envelope>`;
}
export const options = {
vus: 20,
duration: '5m',
};
export default function () {
const accountId = 'ACC-0001';
const res = http.post(
SOAP_URL,
buildGetBalanceEnvelope(accountId),
{
headers: {
'Content-Type': 'text/xml; charset=utf-8',
SOAPAction: '"http://example.com/AccountService/GetAccountBalance"',
},
tags: { name: 'GetAccountBalance' }, // shows in per-endpoint breakdown
}
);
check(res, {
'status 200': (r) => r.status === 200,
'no soap fault': (r) => !r.body.includes('<soap:Fault>'),
'balance present': (r) => r.body.includes('<acc:Balance>'),
});
}

SOAP 1.2 uses a different namespace and content type:

AspectSOAP 1.1SOAP 1.2
Envelope namespacehttp://schemas.xmlsoap.org/soap/envelope/http://www.w3.org/2003/05/soap-envelope
Content-Typetext/xml; charset=utf-8application/soap+xml; charset=utf-8
SOAPAction headerRequiredOptional (moved to Content-Type parameter)
Fault element<soap:Fault><soap12:Fault>

Update your assertions to match:

assert:
- not-contains:
subject: body
value: '<soap12:Fault>' # SOAP 1.2 fault element name

When each VU needs its own account IDs, transaction amounts, or user credentials, use a CSV data entity in MaxoPerf:

scenarios:
parameterized-soap:
data-sources:
- path: accounts.csv
variable-names: account_id, session_token
requests:
- label: GetBalance
url: /AccountService
method: POST
headers:
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://example.com/AccountService/GetAccountBalance"
body: |
<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<GetAccountBalance xmlns="http://example.com/AccountService">
<AccountId>${account_id}</AccountId>
</GetAccountBalance>
</soap:Body>
</soap:Envelope>

See Cookbook: CSV data-driven test for the full CSV setup.

SOAP results appear in the standard HTTP result view. Label each operation with label: (Taurus) or tags: { name: '…' } (k6) so the per-endpoint breakdown shows one row per SOAP operation instead of all requests under the /AccountService URL.

Watch for:

  • Error rate spike: often <soap:Fault> responses. Check the captured response samples on the Errors tab.
  • Latency increase: XML parsing costs more CPU than JSON. Under high concurrency the server’s XML parser can become the bottleneck and latency rises.

Do:

  • Assert on <soap:Fault> in the response body as well as the HTTP status. Many SOAP faults are HTTP 200.
  • Give each SOAP operation its own label so the MaxoPerf per-endpoint panel shows one row per operation.
  • Use MaxoPerf secrets for auth tokens that appear in the SOAP header.

Don’t:

  • Use XML entities that could trigger XXE parsing. MaxoPerf’s bundle assembler rejects unsafe paths, but the target SOAP service may be vulnerable.
  • Include real customer account IDs or financial data in uploaded test files. Use synthetic test data.