skip to content

In a Karate feature file asserting on an XML response, which XPath shapes may sit on the left of a `match` step, and what does a leading `/` alone select?

level: juniorimportance: must knowfreq 62%

answer

  1. The left side is an expression
  2. XML is a first-class Karate value
  3. A leading slash has a meaning
  4. Same idea as $ for JsonPath
  5. response is implicit

basics

~20 s

Any valid XPath sits directly on the left of a Karate match step - no extractor object, no Java code. A leading slash means the response, so match /cat/name == 'Billie' equals match response /cat/name == 'Billie'.

solid answer

~40 s

Karate treats XML as a first-class value, so the left of a `match` is an expression the engine evaluates, and an XPath is one of the shapes it accepts. Three forms cover almost everything: `match response /cat/name == 'Billie'` (a variable, a space, then the path), `match cat/cat/name == 'Billie'` (the variable name running straight into the path, no space), and `match /cat/name == 'Billie'`, where a leading `/` or `//` makes `response` implicit - the XML twin of `$` for JsonPath. A `/` on its own means the whole document: `match response / == <cat><name>Billie</name></cat>` and `match cat / == <cat><name>Billie</name></cat>` both compare the entire node. Predicates and attributes are ordinary XPath 1.0: `match foo //record[@index=2] == 'b'`, `match doc /root/@id == '123'`.

code

gherkin · 11 lines
gherkin
* def foo =
  """
  <records>
    <record index="1">a</record>
    <record index="2">b</record>
    <record index="3" foo="bar">c</record>
  </records>
  """
* match foo //record[@index=2] == 'b'
* match foo //record[@foo='bar'] == 'c'
* match foo count(/records//record) == 3

go deeper

for a junior

Remember two spellings: variable plus space plus XPath, and a bare leading slash that means the response. If you can write the XPath, you can write the assertion.

for a middle

Explain the split: Karate parses the left side into a variable part and a path part, evaluates the variable, then runs the XPath. That is why a leading slash resolves to response and why the path cannot hold a variable.

for a senior

Prefer the named-variable form in long features so a reader knows which payload is asserted, and know the escape hatch for a computed path before someone reaches for string tricks in the step text.

for a principal

The point worth defending is that the assertion lives beside the request. Weigh that against the reviewability of paths that are strings in a feature file rather than compiled expressions a tool can refactor.

## XML is a value, so the path can live in the step Karate has no step-definition layer and no extractor object. A response that looks like XML is parsed into a DOM node and bound to the variable `response`, and the `match` keyword takes an **expression** on its left rather than a string it hands to a helper. That is why an XPath can be written inline, in the feature file, with the assertion on the same line: ```gherkin * def cat = <cat><name>Billie</name><age>5</age></cat> * match cat /cat/name == 'Billie' * match cat /cat/age == '5' ``` The engine splits the left side into a **variable part** and a **path part**, evaluates the variable, then runs the path against it. Because the split is textual, the shapes below are all legal and all mean the same thing. ## The shapes the left side accepts | Left side | Means | |---|---| | `response /cat/name` | XPath on the variable `response` | | `response/cat/name` | the same, with the space closed up | | `/cat/name` | leading `/` or `//`, so `response` is implicit | | `cat /` | the whole document held in the variable `cat` | | `response /` or `response` | the whole response | | `foo //record[@index=2]` | XPath with a predicate on the variable `foo` | | `doc /root/@id` | an attribute node | | `foo count(/records//record)` | an XPath function on the variable `foo` | A leading slash is the XML counterpart of `$` for JsonPath: `match $.name == 'Billie'` and `match /cat/name == 'Billie'` both lean on `response` without naming it. The upstream reference lists `match response / ==`, `match response ==` and `match / ==` as equivalent for a whole-document comparison. ## What the path actually selects The path is handed to the JDK's XPath engine, so **XPath 1.0 rules apply unchanged**: - `//record[@index=2]` compares the attribute against the number 2, so the quotes are optional there; `//record[@foo='bar']` needs them for a string. - Positional predicates are **1-based**: `search/queries/query[1]` is the first `query`. - `/root/@id` selects an attribute node and gives you its value. - `count(...)`, and other XPath functions, are recognised when the token before the parenthesis is lower-case letters or hyphens. What comes back is **not** a DOM node handed to you raw. A path that selects a single element with no child elements yields that element's text; a single element that has children yields an XML chunk you compare to an XML literal; several nodes yield a list you compare to an array. ## Two things that surprise people 1. **The path is literal.** It is read from the step text as written, so you cannot splice a variable into it. `match xml /query/(elementName)/foo` is not a thing. When the path has to be built at runtime, compute it in JavaScript with `karate.xmlPath(xml, '/query/' + elementName + '/foo')` and assert on the result. 2. **A path that selects nothing is not an error.** `match foo/root/nope == '#notpresent'` passes, and `match foo/root/bar == '#present'` is the complement. An XML element with no text is `''`, never `null` - XML has no null - so an empty tag matches `''`. ## Why this is Karate-specific In a step-definition framework the same assertion needs a glue method, a parsed document object, a path API and a matcher; the feature file only names an intent and the real assertion lives in code. Here the feature file *is* the assertion, and the diff you get on failure is produced by the same engine that walks JSON. That is also why the tree of `match` operators (`==`, `!=`, `contains`) works identically over XML and JSON: an XPath result is turned into an ordinary Karate value first, and the comparison never has to know it came from a document. ## How the engine reaches the value The left side is not evaluated as JavaScript first. Karate looks at the leading characters and routes: a `$` starts a JsonPath, a `/` or `//` starts an XPath on `response`, a name followed by a slash starts an XPath on that variable, and anything else falls through to a JavaScript evaluation of the whole expression. That routing table is small, published, and the same in both Karate lines, which is why the shapes in the table above are the ones worth memorising rather than a longer list of special cases. One practical consequence: the routing is decided by punctuation, not by what the variable happens to hold, so a left side that is genuinely an expression should say so. **Anything wrapped in parentheses is evaluated as JavaScript** - `match (someFn(a) + b) == 5` - and that is the documented escape hatch when the left side is not a plain variable-and-path. ## Practical shape Prefer the explicit `match response /Envelope/Body/Result == ...` over the bare `/Envelope/...` form in long features: the variable name tells the next reader which payload is being asserted, and the same step keeps working after you assign the body to a named variable. Reserve the bare form for short, obviously-scoped scenarios.

  • Does the leading-slash short-cut work anywhere other than the left of a match?
    Yes - it is a general Karate-expression short-cut, so it also works on the right of an assignment: `* def teacher = //teacher[@department='science']` runs that XPath against `response` and assigns the result. That is handy for naming a fragment once and asserting on it over several steps, and it is why a `def` whose value starts with a slash is never treated as a string.
  • Can the XPath on the left side contain a variable?
    No. The path is read literally out of the step text, so nothing is interpolated into it. Build the path in JavaScript instead - `karate.xmlPath(xml, '/query/' + elementName + '/foo')` - assign the result with `def`, and assert on that variable. The upstream reference states the same restriction for the named-path form of the left side.
  • What does a match against an XPath that selects nothing do?
    It does not throw. The result is treated as not present, so `match foo/root/nope == '#notpresent'` passes and `match foo/root/nope == '#present'` fails. Note that an empty element is different: XML has no null, so `<bar/>` has the text value `''` and `match foo/root/bar == ''` passes.

saying these in an interview costs you the question

  • Says you need a Java extractor object to read XML
  • Thinks the leading slash is a typo or a division
  • Writes match response.cat.name for an XML payload only
  • Believes a variable can be spliced into the step's XPath
  • Assumes XPath predicates are 0-based like JSON arrays