skip to content

A SOAP response is wrapped in namespace prefixes such as soapenv: and acc:. How do you write the XPath on the left of a Karate match step against it, and what happens to those prefixes on the right side?

level: seniorimportance: should knowfreq 42%

answer

  1. Do not copy prefixes into the path
  2. The parser is namespace-unaware
  3. Both sides ignore prefixes
  4. xmlns attributes are dropped
  5. local-name() is the escape hatch

basics

~20 s

Write the XPath with no prefixes at all. Karate parses XML namespace-unaware by default, so /Envelope/Body/getAccountByPhoneNumber reaches soapenv:Envelope and acc:getAccountByPhoneNumber. On the right side, prefixes and xmlns attributes are stripped from both documents before they are compared.

solid answer

~50 s

Drop the prefixes. Karate parses XML with a namespace-unaware document builder by default, so element names carry no bound namespace and an XPath step written as `Envelope` reaches `soapenv:Envelope`. Repeating the prefixes in the path is what breaks it - `/soapenv:Envelope/soapenv:Body` selects nothing, because there is no namespace bound to that prefix for the XPath engine to resolve. The right side is handled separately and just as forgivingly: when XML is compared to XML, both documents are flattened to maps with the **prefix stripped off every element name** and every `xmlns` / `xmlns:*` attribute removed, so an expected fragment using `foo:` matches an actual using `acc:`. Practically that means one SOAP assertion works across environments that differ only in their prefix choices - and that a prefix mismatch is never the reason a SOAP match fails.

code

gherkin · 15 lines
gherkin
* def response =
  """
  <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                    xmlns:acc="http://foo/bar">
    <soapenv:Body>
      <acc:getAccountByPhoneNumber>
        <acc:phoneNumber>123456</acc:phoneNumber>
      </acc:getAccountByPhoneNumber>
    </soapenv:Body>
  </soapenv:Envelope>
  """
# no prefixes in the path
* match response /Envelope/Body/getAccountByPhoneNumber/phoneNumber == '123456'
# a different prefix on the right still matches
* match response /Envelope/Body/getAccountByPhoneNumber == <foo:getAccountByPhoneNumber xmlns:foo="http://foo/bar"><foo:phoneNumber>123456</foo:phoneNumber></foo:getAccountByPhoneNumber>

go deeper

for a junior

Take the rule at face value: leave namespace prefixes out of the XPath. The path /Envelope/Body/Result reaches a soapenv-prefixed envelope without any extra setup.

for a middle

Explain both halves - a namespace-unaware parse on the left, and prefix plus xmlns stripping when XML is compared to XML on the right - and why a prefixed path therefore selects nothing.

for a senior

Recognise the symptom in a live SOAP suite: nothing selected usually means a prefix in the path, not a missing element. Know that colliding local names across namespaces are the one case this tolerance cannot serve.

for a principal

Decide whether namespace-insensitive matching is acceptable for the contracts you own. It buys resilience to cosmetic drift and gives up the ability to detect a genuine namespace change, which for some enterprise integrations is a real signal.

## Two independent mechanisms, both forgiving A SOAP assertion touches namespaces twice: once when the XPath on the **left** walks the document, and once when the XML on the **right** is compared to what the path selected. Karate ignores prefixes on both sides, but by different means, and knowing which is which is what lets you debug a failing SOAP step. ```gherkin * def response = read('soap1.xml') * match response /Envelope/Body/getAccountByPhoneNumber/phoneNumber == '123456' ``` That file's root is `<soapenv:Envelope xmlns:soapenv="..." xmlns:acc="...">` and the leaf is `<acc:phoneNumber>`. The path names neither prefix, and it works. ## The left side: namespace-unaware parsing Karate builds the DOM with a document builder configured **namespace-unaware** by default. In that mode the parser does not split a name into prefix and local part or bind it to a URI - the tree simply carries names, and the XPath engine derives a local name by taking everything after the colon. So the node test `Envelope` matches `soapenv:Envelope`, and `getAccountByPhoneNumber` matches `acc:getAccountByPhoneNumber`. The consequence people get wrong is the **other direction**: - `/Envelope/Body/getAccountByPhoneNumber` - works. - `/soapenv:Envelope/soapenv:Body/acc:getAccountByPhoneNumber` - does not. The XPath engine treats `soapenv:` as a namespace prefix it must resolve, no namespace is bound, and the path selects nothing. So the instinct to copy the prefixes out of the payload into the path is precisely the thing that breaks a SOAP assertion, and the failure looks like a missing element rather than a namespace error. When a SOAP match reports nothing selected, the first thing to check is whether a prefix crept into the path. When you genuinely need to be explicit - a document where two prefixes map to different namespaces and the local names collide - fall back to standard XPath 1.0 rather than to a Karate feature: `//*[local-name()='outer']` names the local part without depending on any binding at all. ## The right side: prefixes stripped before the compare When `match` has XML on both sides, each document is converted into a map first, and that conversion is run in **prefix-removing** mode: - every element name has everything up to and including the last colon removed, so `acc:phoneNumber` and `foo:phoneNumber` both become `phoneNumber`; - every `xmlns` and `xmlns:*` attribute is dropped, so a namespace declaration is never counted as an attribute difference; - if a namespace declaration was an element's **only** attribute, the element compares as a plain value rather than as an attribute wrapper. That is why an expected fragment can declare its own prefix and still match: ```gherkin * def expected = """ <foo:phoneNumberSearchOption xmlns:foo="http://foo/bar"> <foo:searchWirelessInd>true</foo:searchWirelessInd> </foo:phoneNumberSearchOption> """ * match response /Envelope/Body/getAccountByPhoneNumber/phoneNumberSearchOption == expected ``` The actual document uses `acc:`; the expected uses `foo:`; both flatten to the same map. ## What this buys, and what it costs **Buys.** A SOAP suite survives a service that changes its prefix conventions, a gateway that rewrites them, or a fixture captured from a different tool. It also means the expected fragments in your feature files can be pasted straight out of a captured payload without editing. **Costs.** You lose the ability to distinguish two elements that share a local name and differ only by namespace - a real, if uncommon, shape in enterprise WSDLs. If that is your document, `local-name()` with an explicit `namespace-uri()` predicate is the escape hatch, and the assertion becomes noticeably less readable. ## The asymmetry nobody expects: attributes keep their prefix The stripping applies to **element names only**. When each document is flattened, an attribute is keyed by its full name as written - so `xsi:nil` stays `xsi:nil`, prefix included. The only attributes touched at all are the namespace declarations themselves, which are deleted outright. So the rule reads: - `<acc:phoneNumber>` and `<foo:phoneNumber>` are the **same element** - prefix removed; - `xsi:nil="true"` and `x:nil="true"` are **different attributes** - prefix kept; - `xmlns:acc="…"` is **not an attribute at all** for comparison purposes - removed. That means a captured fragment can be pasted into a feature file with whatever element prefixes it arrived with, but a prefixed *attribute* has to be spelled the way the service spells it. It is a small asymmetry with a large debugging cost, because the failure reads as a plain attribute mismatch and gives no hint that a prefix is involved. If you are diffing two payloads that look identical, compare the attribute names character by character before anything else. ## Where this is *not* the answer Namespace tolerance is about matching the shape of a document. It says nothing about whether the document is **valid** against a contract - Karate has no schema matcher, and asserting a payload against a published schema is a different job with different tooling. Keep the two apart in an interview answer: this mechanism makes an example-based assertion robust to cosmetic namespace differences, and that is the whole of its claim. ## The checklist 1. Write paths with **no prefixes**. 2. If nothing is selected, look for a prefix in the path before you suspect the payload. 3. Paste expected fragments as captured; their **element** prefixes are ignored, but a prefixed attribute name is not. 4. Reach for `local-name()` only when local names genuinely collide across namespaces.

  • A SOAP match selects nothing and you are sure the element is there. What do you check first?
    Whether a namespace prefix crept into the XPath. A path such as `/soapenv:Envelope/soapenv:Body` asks the XPath engine to resolve a prefix that is not bound, and it selects nothing - the same symptom as a genuinely missing element. Strip the prefixes, re-run, and only then start doubting the payload.
  • How do you assert on an element when two namespaces use the same local name?
    Fall back to plain XPath 1.0 predicates rather than a Karate feature: `//*[local-name()='total' and namespace-uri()='http://…']`. Because element names are compared with prefixes stripped, no Karate-level mechanism can separate the two for you. Expect the assertion to be noticeably less readable, and consider narrowing the path by ancestor instead when that reads better.
  • Does an xmlns declaration count as an attribute when two XML documents are compared?
    No. Every `xmlns` and `xmlns:*` attribute is removed before the comparison. If a namespace declaration was an element's only attribute, that element then compares as a plain value rather than as an attribute wrapper, so declaring a namespace on the expected fragment never introduces a difference of its own.

saying these in an interview costs you the question

  • Copies the payload's prefixes into the XPath
  • Says you must register a namespace context first
  • Blames the payload when a prefixed path selects nothing
  • Thinks xmlns declarations count as attribute differences
  • Confuses namespace tolerance with validating a contract