skip to content

A Karate step `* path 'search?q=cats'` produces a request to `/search%3Fq=cats` and the endpoint returns an empty result instead of failing. Why does Karate encode the question mark, and how should a query string and a multi-segment path be written instead?

level: seniorimportance: should knowfreq 41%

answer

  1. It is not string concatenation
  2. Each argument is treated structurally
  3. Delimiters get encoded, not honoured
  4. The query string has its own keyword

basics

~20 s

The path keyword treats its argument as path segments and percent-encodes each one, so a question mark becomes %3F and lands in the path rather than starting a query string. Query parameters belong in the param keyword, or in the url text itself.

solid answer

~50 s

`path` is not string concatenation onto the URL. Each argument becomes one or more **path segments**, and every segment is percent-encoded when the final URI is assembled — so `?`, `&` and `#` end up as `%3F`, `%26` and `%23` inside the path. The endpoint then sees a resource name it does not have, and a permissive search endpoint answers 200 with nothing rather than 404, which is why this survives review. Query parameters go in `param name = value` (or `params` from JSON), or into the `url` string, which Karate takes as written. Two related `path` rules are worth carrying: an argument containing `/` is **split** into several segments, and a comma-delimited `path 'cats', id, 'photo'` is the idiomatic multi-segment form; and a trailing slash is dropped unless the last `path` step is exactly `'/'`.

code

gherkin · 10 lines
gherkin
Scenario: build the URL with the right keyword for each part
  * def catId = 42
  Given url 'https://api.example.com/v1'
  And path 'cats', catId, 'photos'
  And param size = 'large'
  And param tag = ['tabby', 'ceiling']
  And param missing = null
  When method get
  Then status 200
  # -> /v1/cats/42/photos?size=large&tag=tabby&tag=ceiling

go deeper

for a junior

Keep the split straight: everything before the question mark is built with path, everything after it with param.

for a middle

Explain that path arguments are segments that get percent-encoded and split on slashes, and that url text is taken as written.

for a senior

Diagnose from the logged request URL rather than the steps: an encoded delimiter in the path is proof, and a permissive endpoint will hide the mistake behind a 200.

for a principal

Worth a lint or review rule across a suite, because this failure mode is silent on exactly the endpoints — search and list — where an empty result reads as a legitimate answer.

## `path` builds segments, not a string The mental model that causes this bug is that `path` appends text to the URL. What it actually does is add entries to a list of path segments; when the request is finally built, those segments are appended to whatever path the `url` already carried and the whole URI is assembled with proper encoding. That is normally exactly what you want. An identifier with a space, a slash or an accent lands in the path correctly encoded without you thinking about it. It becomes a trap only when the argument contains a character that is *structural* in a URL: | Written | Ends up as | Because | |---|---|---| | `path 'search?q=cats'` | `/search%3Fq=cats` | `?` is encoded inside the segment | | `path 'a&b'` | `/a%26b` | `&` is encoded inside the segment | | `path 'cats/42'` | `/cats/42` | an unescaped `/` **splits** into two segments | | `path 'cats\/42'` | `/cats%2F42` | an escaped slash stays one segment | The first row is the one that ships. The others are usually what the author intended. ## Why it survives review A request to `/search%3Fq=cats` is a well-formed request for a resource that does not exist. Against a strict service you get a 404 and find it in minutes. Against a permissive one — a search or list endpoint that treats an unknown trailing segment as no filter — you get a 200 and an empty or unfiltered body. The test then passes if its assertions are loose, or fails with a message about the payload that points nowhere near the real cause. The diagnostic habit is to read the URL Karate logs for the call rather than the steps that built it. A `%3F`, `%26` or `%23` in the logged path is conclusive. ## The right tools 1. **`param` for query parameters.** `* param q = 'cats'` adds `?q=cats`, and Karate inserts the `?` and `&` separators itself. `params` does the same from a JSON object, which suits data-driven tests. A multi-valued parameter takes a list: `* param tag = ['a', 'b']`. 2. **`url` when the query string is genuinely part of the fixed address.** The `url` text is used as written, so `* url 'https://api.example.com/v1?debug=true'` keeps its `?`. 3. **Comma-delimited `path` for multi-segment paths.** `* path 'cats', id, 'photo'` is the idiomatic form; the separators are added for you and each segment is encoded independently. It is equivalent to three separate `path` steps. One convenience worth knowing about `param`: a `null` value is silently dropped rather than sent as an empty parameter, which is what makes an optional filter easy to express in a data-driven scenario without an `if`. ## Trailing slashes Karate strips a trailing slash from the assembled path. If the service genuinely distinguishes `/documents` from `/documents/` — some do, and some frameworks redirect between them — make the **last** `path` step exactly a single slash: ```gherkin Given url 'https://api.example.com' And path 'documents', '/' When method get Then status 200 ``` Without that, the request goes to `/documents`, and a service that answers a 301 to the slashed form turns one call into two, or into a failure if redirects are not being followed. ## `path` appends, and it resets Two more properties round out the picture, and both follow from the builder model: - **`path` appends to the URL's own path.** With `url 'https://api.example.com/v1'` and `path 'cats'`, the request goes to `/v1/cats`. The base path in the `url` is not replaced. - **`path` is cleared after every call, while `url` is not.** That is what makes the resource-focused idiom work: set `url` once, then write one `path` per call. It also means a path segment can never leak from one call in a scenario into the next. ## The rule to carry away Everything before the `?` is `path`; everything after it is `param`. If a step argument contains a URL delimiter, that is the signal you have reached for the wrong keyword — and the resulting request is not malformed, it is merely wrong, which is why it needs to be caught by reading the URL rather than by waiting for an error.

  • How do you make a Karate request end with a trailing slash?
    Make the last `path` step exactly a single slash: `* path 'documents', '/'`. Karate otherwise strips the trailing slash from the assembled path, and a service that distinguishes `/documents` from `/documents/` will either redirect or answer differently.
  • What is the difference between `path 'cats/42'` and `path 'cats\/42'`?
    The first is split on the slash into two segments and produces `/cats/42`. The escaped form is kept as one segment and encoded, producing `/cats%2F42` — which is what you want when an identifier genuinely contains a slash and the service expects it encoded.
  • Does `path` replace the path already present in the `url`?
    No, it appends to it. With `url 'https://api.example.com/v1'` and `path 'cats'`, the request goes to `/v1/cats`. That is why the common idiom puts the stable prefix in `url` — which survives each call — and only the varying part in `path`, which is cleared after every call.

saying these in an interview costs you the question

  • Treats path as string concatenation onto the url
  • Builds a query string with the path keyword
  • Expects an unescaped slash to be encoded as %2F
  • Cannot say why a trailing slash disappears
  • Assumes a wrong URL always produces an error status
  • Thinks path replaces the base path set by url