In a Karate feature file, what type of value does an XPath on the left of a match produce for a leaf element, for an element with children, for an attribute, and for count()?
answer
- XML carries no types
- Leaf versus parent decides the shape
- Count the child elements
- One function returns a real number
- Empty is not missing
basics
~20 sA leaf element yields its text as a string, so numbers arrive quoted. An element with children yields an XML chunk compared against an XML literal. An attribute yields its value as a string. XPath count() yields a number.
solid answer
~50 sThe XPath result is converted before the comparison, and the conversion is driven by the **node**, not by what the value looks like. An element with no child elements gives its text as a `String` - which is why `<age>5</age>` reads back as `'5'` and `match doc /root/age == 5` fails with `data types don't match` while `== '5'` passes. An element that has child elements gives a fresh XML document, so the right side should be an XML literal. An attribute such as `/root/@id` gives its value, again a `String`. The one place a real number appears is an XPath function: `match foo count(/records//record) == 3` compares against the number 3. XML has no null, so an empty tag reads back as `''`, and a path that selects nothing is not present rather than null.
code
gherkin · 9 lines* def doc = <root id="7"><name>John</name><age>30</age><meta><k>v</k></meta></root>
# leaf element -> string
* match doc /root/age == '30'
# attribute -> string
* match doc /root/@id == '7'
# element with children -> xml chunk
* match doc /root/meta == <meta><k>v</k></meta>
# xpath function -> number
* match doc count(/root/*) == 3go deeper
Quote your expected values. Text pulled out of XML is a string, so an age of 30 is asserted as '30' and only an XPath count gives you a real number.
Explain the branch: child-element count decides text versus chunk, attributes follow the text branch, and count is parsed back into an integer. Name data types don't match as the failure.
Watch for suites ported from JSON where quoting silently flipped. Decide where numeric coercion lives - in the assertion, in a converted JSON view, or not at all - and apply it consistently.
The strictness is worth defending under pressure to add loose comparison. A test that accepts '30' for 30 will also accept a malformed field, which is the class of bug the suite exists to catch.
## Everything is a string unless the document says otherwise XML carries no type information. `<age>5</age>` and `<age>five</age>` are the same kind of node to a parser, and Karate does not guess. When an XPath on the left of a `match` selects a **leaf** - an element with no child elements - the value handed to the comparison is that element's text content, as a `String`. So: ```gherkin * def doc = <root><name>John</name><age>30</age></root> * match doc /root/name == 'John' * match doc /root/age == '30' # this one fails: data types don't match # * match doc /root/age == 30 ``` This is the single most common surprise on an XML suite, because the JSON habit is the opposite: a JSON `30` really is a number, and asserting `== '30'` there fails. Moving a suite from JSON to XML flips the quoting on every numeric assertion. ## The four shapes, and where each comes from | XPath selects | Value handed to match | Natural right side | |---|---|---| | element, no child elements | text content, a `String` | `'30'` | | element with child elements | a fresh XML document | an XML literal | | attribute node | the attribute value, a `String` | `'123'` | | an XPath function such as `count(...)` | a number | `3` | The first two are one branch, decided by counting the node's **child elements**, not its text. A `<teacher>` holding `<subject>` children is returned as a document you compare structurally: ```gherkin * def item = <item id="123" status="active">content</item> * match item /item/@id == '123' * match item /item/@status == 'active' ``` Attributes go down the leaf branch too, so `/root/@id` gives `'123'`, quoted, for exactly the same reason as `<age>`. ## count() is the exception, and it is deliberate An XPath function that returns a number rather than a node-set cannot be evaluated as a node-set at all. Karate evaluates it as a string and then, for `count`, parses that string back into an integer - so the comparison you write is against a number: ```gherkin * def foo = """ <records> <record index="1">a</record> <record index="2">b</record> </records> """ * match foo count(/records//record) == 2 ``` A function is recognised when the token before the opening parenthesis is lower-case letters or hyphens, so `count(`, `sum(` and `string-length(` all take that path. Only `count` has a guaranteed integer result across both Karate lines; for any other function, assert the shape you actually observe rather than assuming a number. ## Nulls, empties and missing paths Three different situations that people collapse into one: - **An empty element.** `<bar/>` has the text value `''`. XML has no concept of null, so `match foo/root/bar == ''` passes and `match foo/root/bar == '#present'` passes with it. - **A path that selects nothing.** The result is treated as not present: `match foo/root/nope == '#notpresent'` passes. This is a different state from empty, and the two are worth keeping straight in a review. - **A number-shaped string.** `'0'` is a non-empty string, so it is present and it is not `''`. Nothing about the value is coerced. ## The same rule, seen from the JSON side It helps to hold the contrast in mind. A JSON body carries types, so `{ "age": 30 }` gives a real number and `match response.age == '30'` fails. An XML body carries none, so `<age>30</age>` gives a string and `match doc /root/age == 30` fails. Same engine, same strictness, opposite quoting - and a suite that talks to a service offering both representations has to get this right per endpoint. When a service switches its default content type, the numeric assertions are the ones that break first. ## Working with the types instead of against them When an assertion really wants a number, put the conversion where it belongs rather than hoping the engine does it: 1. **Assert the string.** `match doc /root/age == '30'` is honest about what the document contains, and it fails loudly if the element starts carrying `30.0`. 2. **Use an XPath function.** `count(...)` gives a number without any conversion of your own. 3. **Convert the document.** `* json data = doc` produces a map where element text is still a string, but you then have JavaScript available: `* assert data.root.age * 1 == 30` states the coercion explicitly. What you should not do is reach for a loose comparison and hope. Karate's `match` refuses to compare a string to a number and says so - `data types don't match` - and that strictness is the feature. A suite that silently accepted `'30' == 30` would also accept `'30abc'` on a badly-encoded field, which is exactly the bug an API test exists to catch.
- Why does match doc /root/age == 30 fail when the element holds 30?Because the left side is the string `'30'`. A leaf element's value is its text content, and XML has no type information for Karate to read. The comparison then has a string on one side and a number on the other, and `match` reports `data types don't match` rather than coercing. Assert `== '30'`, or count with an XPath function when you want a number.
- How do you tell an empty element apart from a missing one?By the marker you assert against. An empty `<bar/>` has the text value `''`, so `== ''` and `== '#present'` both pass. A path that selects no node at all is not present, so `== '#notpresent'` passes and `== '#present'` fails. XML has no null, so there is no third state to test for.
saying these in an interview costs you the question
- Expects numbers in XML to arrive as numbers
- Says match coerces '30' and 30 to compare equal
- Treats an empty element as null
- Confuses an empty element with a missing path
- Assumes every XPath function returns a number