Skip to main content

Syntax Reference

This is a unified, searchable reference for every Karate keyword, operator, and pattern. Use your browser's find (Ctrl+F / Cmd+F) to jump to any keyword. Each entry has a brief description, a minimal example, and a link to the full documentation.

On this page:

  • Core Keywords — def, assert, print, replace, table, text, yaml, csv, copy, eval, doc, listen, signal
  • HTTP Keywords — url, path, method, status, request, param, header, cookie, form field, multipart, soap action, retry until, configure
  • Match and Assertions — match ==, !=, contains, !contains, contains only, contains any, contains deep, each, schema markers
  • Feature Composition — call, callonce, karate.callSingle(), karate.setup(), read(), @ignore, @setup
  • Configuration — configure keys for headers, cookies, ssl, proxy, retry, driver, timeouts, and more
  • Embedded Expressions — #() and ##() templating syntax with rules
  • UI / Driver Keywords — driver, click, input, waitFor, waitUntil, retry, script, mouse, keys, switchFrame, dialog
  • Browser JavaScript Patterns — Karate vs the Browser, function composition, loop until
  • Wait API — waitFor, waitForUrl, waitForText, waitUntil, waitForEnabled, retry comparison table

Core Keywords

These are the fundamental keywords for defining variables, asserting values, and controlling test flow.

def

Define a variable of any type -- string, number, boolean, JSON, XML, JavaScript function, or the result of a call or read().

* def name = 'John'
* def user = { name: 'John', age: 30 }
* def items = [1, 2, 3]
* def result = call read('helper.feature')

Note that url and request are reserved and cannot be used as variable names.

More details

assert

Evaluate a JavaScript expression and fail the test if it returns false. Use for numeric comparisons where match is not needed.

* def count = 5
* assert count > 0
* assert responseTime < 1000

Prefer match for JSON/XML assertions. Reserve assert for boolean expressions like greater-than or less-than.

More details

print

Log values to the console and HTML reports. Use commas for pretty-printing JSON/XML.

* print 'status:', responseStatus
* print 'response:', response
* print karate.pretty(response)

More details

replace

Perform text replacements using token placeholders. Useful for templating strings that are not JSON or XML.

* def text = 'Hello {name}, you are {age} years old'
* replace text.'{name}' = 'John'
* replace text.'{age}' = '30'

Multi-value form:

* replace text
| token | value |
| {name} | John |
| {age} | 30 |

More details

table

Define inline data as a JSON array of objects. Each row becomes an object with the column headers as keys.

* table users
| name | age |
| John | 30 |
| Jane | 25 |
* match users == [{ name: 'John', age: 30 }, { name: 'Jane', age: 25 }]

More details

text

Assign multi-line text to a variable. Preserves formatting. Commonly used for GraphQL queries.

* text query =
"""
query GetUser($id: ID!) {
user(id: $id) {
name
email
}
}
"""

More details

yaml

Parse YAML content into a JSON variable.

* yaml config =
"""
name: John
age: 30
roles:
- admin
- user
"""
* match config.name == 'John'

More details

csv

Read CSV data as a JSON array. Each row becomes an object with the header row as keys. All values are strings.

* def users = read('users.csv')
* match users[0] == { name: 'John', age: '30' }

More details

copy

Deep-clone a variable so that mutations to the copy do not affect the original.

* def original = { name: 'John', scores: [1, 2, 3] }
* copy clone = original
* set clone.name = 'Jane'
# original.name is still 'John'

More details

eval

Execute arbitrary JavaScript. Can be omitted when operating directly on variables.

* eval response.items.forEach(x => x.processed = true)
* eval if (condition) karate.set('flag', true)

Short-cut form (no eval keyword needed):

* def myJson = { a: 1 }
* myJson.b = 2

More details

doc

Add documentation text that appears in HTML reports.

* doc 'This scenario tests the user creation flow'

More details

listen

Block and wait for an async event (from karate.signal()) with a timeout in milliseconds. The result is available in listenResult.

* listen 5000
* match listenResult == { type: 'notification' }

More details

signal

Trigger an async event from a callback or parallel thread. Pairs with listen.

* def handler = function(msg){ karate.signal(msg) }
* def socket = karate.webSocket(wsUrl, handler)
* listen 5000
* match listenResult contains { type: 'response' }

More details

get

Extract values using JsonPath or XPath expressions. The get keyword is optional for simple paths.

* def name = get response.user.name
* def first = get[0] response.users
* def ids = get response..id

Short-cut (the get keyword can be omitted):

* def name = response.user.name
* def first = response.users[0]

More details

set

Set nested values in JSON or XML. Automatically creates intermediate structures.

* set user.name = 'John'
* set user.address.city = 'Boston'
* set items[0].active = true

Multi-value form:

* set user
| path | value |
| name | 'John' |
| age | 30 |

More details

remove

Remove keys from JSON or nodes from XML.

* remove response.password
* remove response..internal
* remove response.users[0]

More details

if

Conditional execution. The expression must be valid JavaScript.

* if (responseStatus == 200) karate.call('success.feature')
* if (env == 'dev') karate.set('baseUrl', devUrl)

More details


HTTP Keywords

Keywords for building and executing HTTP requests.

url

Set the base URL for subsequent requests.

* url 'https://api.example.com'
* url baseUrl

More details

path

Append path segments to the base URL. Multiple segments are joined with /.

* path 'users'
* path 'users', userId
* path 'users', 1, 'posts'

More details

method

Execute the HTTP request with the given method.

* method get
* method post
* method put
* method patch
* method delete

More details

status

Assert the HTTP response status code.

* status 200
* status 201
* status 404

More details

request

Set the request body. Accepts JSON, XML, strings, or a read() expression.

* request { name: 'John', email: 'john@test.com' }
* request read('payload.json')
* request ''

More details

param / params

Set query parameters. A null value causes the parameter to be ignored.

* param page = 1
* param size = 10
* params { page: 1, size: 10, sort: 'name' }

Multi-value params are supported: * param myParam = ['foo', 'bar']

More details

header / headers

Set request headers. Functions are allowed on the right-hand side.

* header Accept = 'application/json'
* header Authorization = 'Bearer ' + token
* headers { 'Content-Type': 'application/json', 'X-Api-Key': apiKey }

More details

Set cookies. Response cookies are automatically carried forward to subsequent requests.

* cookie session = 'abc123'
* cookies { session: 'abc123', userId: '456' }

Clear auto-added cookies with * configure cookies = null.

More details

form field / form fields

Set URL-encoded form data. The Content-Type is automatically set to application/x-www-form-urlencoded.

* form field username = 'john'
* form field password = 'secret'
* form fields { username: 'john', password: 'secret' }

More details

multipart file

Upload files as multipart form data. Takes a JSON argument with read, filename, and contentType.

* multipart file myFile = { read: 'test.pdf', filename: 'test.pdf', contentType: 'application/pdf' }

More details

multipart field / multipart fields

Set multipart form fields (non-file parts of a multipart request).

* multipart field message = 'hello world'
* multipart fields { name: 'John', email: 'john@test.com' }

More details

multipart entity

Set the entire multipart body with a custom content type (e.g., multipart/related).

* multipart entity read('data.json')
* header Content-Type = 'multipart/related'

More details

soap action

Set the SOAPAction header and execute a POST request.

Given request read('soap-request.xml')
When soap action 'QueryUsageBalance'
Then status 200

More details

retry until

Retry an HTTP request until a JavaScript expression evaluates to true. Place before the method step.

* configure retry = { count: 10, interval: 5000 }
Given url baseUrl
And path 'status'
And retry until response.state == 'complete'
When method get
Then status 200

The expression must be pure JavaScript. Karate match syntax (e.g., contains) will not work here. You can reference response, responseStatus, and responseHeaders.

More details

configure

Set runtime configuration. Can appear anywhere in a feature file or globally in karate-config.js.

* configure ssl = true
* configure readTimeout = 10000
* configure headers = { 'X-Api-Key': '#(apiKey)' }
* configure retry = { count: 5, interval: 2000 }

See the full Configuration section below for all keys.

More details


Match and Assertions

The match keyword is Karate's primary assertion mechanism. It performs smart comparison where key order and whitespace do not matter.

match ==

Exact equality. The entire structure must match.

* match response == { id: 1, name: 'John' }
* match response.id == 1

More details

match !=

Not-equals assertion.

* match response.status != 'error'
* match response != {}

More details

match contains

Partial match. The actual value must contain the expected subset.

* match response contains { name: 'John' }
* match response.tags contains 'urgent'

More details

match !contains

Negative contains. Asserts the actual value does not contain the expected subset.

* match response !contains { error: true }
* match response.roles !contains 'admin'

More details

match contains only

The array contains exactly these elements, in any order. No extra elements allowed.

* match response.items contains only [{ id: 2 }, { id: 1 }]

More details

match contains any

The array contains at least one of the expected elements.

* match response.tags contains any ['urgent', 'high', 'critical']

More details

match contains deep

Deep partial match. Recursively checks nested structures.

* match response contains deep { user: { name: 'John' } }

More details

match contains only deep

Contains exactly these elements (any order) using deep comparison.

* match response.items contains only deep [{ id: 1, data: { x: 1 } }]

More details

match each

Validate every element in an array against an expected pattern.

* match each response.users == { id: '#number', name: '#string' }
* match each response.users contains { active: true }

More details

match each contains deep

Each array element must deeply contain the expected subset.

* match each response.orders contains deep { items: [{ price: '#number' }] }

More details

match header

Assert a response header value (case-insensitive header name).

* match header Content-Type contains 'json'

More details

Schema Markers

Fuzzy matching markers used within match expressions to validate structure and types instead of exact values. Prefix any marker with ## to make it optional (value can be null or key can be absent).

MarkerDescription
#ignoreSkip comparison for this field
#nullValue must be null (key must be present)
#notnullValue must not be null
#presentKey must exist (value can be any type, even null)
#notpresentKey must not exist at all
#stringValue must be a string
#numberValue must be a number
#booleanValue must be true or false
#arrayValue must be a JSON array
#objectValue must be a JSON object
#uuidValue must be a valid UUID string
#regex STRValue must match the regular expression STR
#? EXPRJavaScript expression EXPR must evaluate to true (self-validation)
#[N]Array must have exactly N elements
#[]Value must be a non-empty array
## prefixMakes any marker optional (e.g., ##string means string-or-null-or-absent)

Example combining multiple markers:

* match response ==
"""
{
id: '#uuid',
name: '#string',
age: '#number',
email: '##string',
role: '#regex (admin|user)',
score: '#? _ > 0',
tags: '#[]',
address: '##object',
created: '#ignore'
}
"""

The ## (double-hash) prefix means the key is optional or the value can be null:

* def foo = { bar: 'hello' }
* match foo == { bar: '#string', ban: '##string' }

Here ban is not present in foo, but ##string allows that.

More details

Self-Validation Expressions

The #? EXPR marker lets you write inline JavaScript validation. The special variable _ refers to the current value being matched.

* match response.age == '#? _ >= 18 && _ <= 120'
* match response.email == '#? _.includes("@")'
* match each response.items == '#? _.price > 0'

Schema Validation Pattern

Combine markers to validate entire response structures. Define reusable schemas as variables:

* def addressSchema = { street: '#string', city: '#string', zip: '#regex \\d{5}' }
* def userSchema = { id: '#uuid', name: '#string', email: '#string', age: '#number', address: '#(addressSchema)' }
* match response == userSchema

Use ##() for optional nested objects:

* def schema = { id: '#string', name: '#string', nickname: '##string', address: '##(addressSchema)' }

Validate arrays with element schemas:

* match response.users == '#[3]'
* match each response.users == userSchema

The #[N] marker checks array length: #[] means non-empty array, #[3] means exactly 3 elements, and #[_ > 2] means length greater than 2.

* match response.items == '#[_ > 0]'
* match response.tags == '#[] #string'

The last form #[] #string validates that the value is an array AND that each element is a string.

More details


Feature Composition

Mechanisms for calling, reusing, and composing feature files and functions.

call

Call another feature file or JavaScript function. Variables defined in the called feature are returned.

* def result = call read('auth.feature')
* def result = call read('auth.feature') { username: 'john' }

Call a specific scenario by tag:

* def token = call read('auth.feature@login')

Call a JavaScript function:

* def adder = function(a, b){ return a + b }
* def sum = call adder 3

When called with an array, the feature is invoked once per element (data-driven):

* def users = [{ name: 'John' }, { name: 'Jane' }]
* def results = call read('create-user.feature') users

More details

callonce

Call a feature file only once per feature, caching the result across all scenarios within the file.

Background:
* def auth = callonce read('auth.feature')

More details

karate.callSingle()

Call a feature file once across the entire test suite, typically used in karate-config.js.

* def config = karate.callSingle('classpath:setup.feature')

Configure caching with configure callSingleCache:

* configure callSingleCache = { minutes: 10 }

More details

karate.setup() / karate.setupOnce()

Invoke a @setup tagged scenario. setupOnce() caches the result within the feature.

* def data = karate.setup('prepareData')
* def data = karate.setupOnce('prepareData')

More details

read()

Read a file relative to the feature, from the classpath, or from an absolute path. The content type is auto-detected from the file extension.

* def data = read('data.json')
* def template = read('classpath:templates/request.xml')
* def config = read('this:config.json')
PrefixDescription
(none)Relative to the current feature file
classpath:From classpath (typically src/test/resources)
file:Absolute file path
this:Relative to the current feature file (explicit)

More details

@ignore

Tag a scenario to skip it during normal execution. Used for helper scenarios meant to be call-ed.

@ignore
Scenario: Helper - not run directly
* def token = 'abc123'

More details

@setup

Tag a scenario as a setup scenario. Invoked via karate.setup() and not run as a normal test.

@setup
Scenario: prepareData
* def users = [{id: 1}, {id: 2}]

Consume in another scenario:

Scenario: Use setup data
* def data = karate.setup('prepareData')
* match data.users.length == 2

More details


Configuration

The configure keyword sets runtime options. These can be set in feature files or globally in karate-config.js using karate.configure(key, value).

Configuration Keys Reference

KeyTypeDefaultDescription
urlstring-Base URL for all requests
headersJSON / function-Default headers for every request
cookiesJSON-Default cookies (set to null to clear)
sslboolean / string / JSONfalseEnable HTTPS; true, algorithm string, or X509 config object
connectTimeoutnumber30000Connection timeout in milliseconds
readTimeoutnumber30000Read timeout in milliseconds
proxystring / JSON-HTTP proxy URI or { uri, username, password, nonProxyHosts }
followRedirectsbooleantrueFollow HTTP redirects automatically
charsetstring'utf-8'Request charset (set to null to suppress)
retryJSON{ count: 3, interval: 3000 }Default retry settings for retry until
localAddressstring-Local network interface to bind
httpRetryEnabledbooleanfalseAuto-retry on HTTP connection errors
lowerCaseResponseHeadersbooleanfalseLowercase all response header keys
logPrettyRequestbooleanfalsePretty-print request payloads
logPrettyResponsebooleanfalsePretty-print response payloads
printEnabledbooleantrueEnable print output
reportJSON / booleantrueReport verbosity, e.g., { showLog: true, showAllSteps: false }
logModifierJava object-Custom log masking implementation
afterScenarioJS function-Hook run after each scenario
afterScenarioOutlineJS function-Hook run after a scenario outline completes
afterFeatureJS function-Hook run after a feature completes
corsboolean-Enable CORS on mock server
responseHeadersJSON / function-Default response headers for mock server
callSingleCacheJSON{ minutes: 0 }Cache TTL for karate.callSingle()
abortedStepsShouldPassbooleanfalseMark aborted steps as passed instead of skipped
abortSuiteOnFailurebooleanfalseStop all tests on first failure
xmlNamespaceAwarebooleanfalseEnable XML namespace handling
matchEachEmptyAllowedbooleanfalseAllow empty arrays in match each
pauseIfNotPerfbooleanfalseEnable karate.pause() outside perf mode
ntlmAuthJSON-NTLM authentication: { username, password, domain, workstation }
driverJSON-UI automation driver config
driverTargetJSON / Java-Driver target lifecycle config
imageComparisonJSON-Image comparison options

Common Configuration Examples

# SSL
* configure ssl = true
* configure ssl = { keyStore: 'classpath:certs.pfx', keyStorePassword: 'secret', keyStoreType: 'pkcs12' }
* configure ssl = { trustAll: true }

# Proxy
* configure proxy = 'http://proxy:8080'
* configure proxy = { uri: 'http://proxy:8080', username: 'user', password: 'pass' }

# Headers (static JSON)
* configure headers = { 'Content-Type': 'application/json', 'X-Api-Key': '#(apiKey)' }

# Headers (dynamic function, called per request)
* configure headers = function(){ return { Authorization: 'Bearer ' + karate.get('token') } }

# Report verbosity
* configure report = { showLog: true, showAllSteps: false }
* configure report = false

# Hooks
* configure afterScenario = function(){ karate.log('done:', karate.scenario.name) }

More details


Embedded Expressions

Embedded expressions let you dynamically insert variable values into JSON or XML payloads using #() syntax. This is Karate's templating mechanism.

Basic Syntax

Within JSON or XML, any string value enclosed in #( and ) is evaluated as a JavaScript expression:

* def name = 'John'
* def age = 30
* def payload = { name: '#(name)', age: '#(age)', label: '#(name + " (" + age + ")")' }
* match payload == { name: 'John', age: 30, label: 'John (30)' }

For XML:

* def lang = 'en'
* def session = <session><locale>#(lang)</locale></session>

Remove-if-Null with ##()

The double-hash ##() removes the key entirely if the expression evaluates to null:

* def middle = null
* def person = { first: 'John', middle: '##(middle)', last: 'Doe' }
* match person == { first: 'John', last: 'Doe' }

This is useful for building requests where optional fields should be omitted rather than sent as null.

Rules for Embedded Expressions

  1. They work only within JSON or XML
  2. They are evaluated on the right-hand side of def, match, configure, and request
  3. They are evaluated when you read() a JSON or XML file
  4. The expression must start with #( and end with ) -- partial embedding does not work:
# WRONG - partial embedding
* def foo = { bar: 'hello #(name)' }
# RIGHT - entire value is an expression
* def foo = { bar: '#("hello " + name)' }

Embedded Expressions in read() Files

Embedded expressions are evaluated when you read() a JSON or XML file. This makes files reusable templates:

// request-template.json
{
"name": "#(name)",
"email": "#(email)",
"role": "#(role)",
"metadata": "##(metadata)"
}
* def name = 'John'
* def email = 'john@test.com'
* def role = 'admin'
* def metadata = null
* def payload = read('request-template.json')
# metadata key is removed because ##() evaluates to null
* match payload == { name: 'John', email: 'john@test.com', role: 'admin' }

Referring to the JSON Root

The special variable $ refers to the JSON root in self-referencing embedded expressions:

* match response == { a: '#number', b: '#? _ > $.a' }

Here $.a refers to the a field of the same JSON being matched.

Enclosed JavaScript (Alternative)

For pure JSON, you can use parentheses to evaluate the entire structure as JavaScript:

* def user = { name: 'John', age: 21 }
* def lang = 'en'
* def session = ({ name: user.name, locale: lang, sessionUser: user })

This is equivalent to using embedded expressions but can be more natural for JavaScript developers. Choose embedded expressions when working with file-based templates; choose enclosed JavaScript for dynamic inline construction.

ES6 Template Literals

Karate supports backtick string interpolation for simple variable substitution:

* def name = 'John'
* def greeting = `Hello ${name}, welcome!`
* param filter = `ORDER_DATE:"${todaysDate}"`

More details


UI / Driver Keywords

Karate provides built-in browser automation. These keywords are available after configuring a driver.

driver

Navigate to a URL. On first use, this launches the browser.

* configure driver = { type: 'chrome' }
* driver 'https://github.com/login'
* match driver.title contains 'GitHub'

Driver properties: driver.title, driver.url, driver.dimensions.

More details

click

Click an element by locator (CSS, XPath, or wildcard).

* click('#login-button')
* click('//button[text()="Submit"]')
* click('{button}Submit')

More details

input

Type text into a form field. Supports special keys and delayed input.

* input('#username', 'john@example.com')
* input('#search', 'karate' + Key.ENTER)
* input('#autocomplete', 'new york', 100)

More details

select

Select an option from a native HTML <select> dropdown.

* select('select[name="country"]', '{}United States')
* select('select[name="state"]', 'CA')
* select('select[name="city"]', 2)

More details

waitFor

Wait for an element to appear in the DOM. Returns the element for chaining.

* waitFor('#results').exists
* waitFor('{button}Submit').click()

More details

waitForUrl

Wait for the browser URL to contain a string.

* click('#next-page')
* waitForUrl('/dashboard')

More details

waitForText

Wait for an element to contain specific text (string contains match).

* waitForText('#status', 'Complete')

More details

waitForEnabled

Wait for an element to become enabled (not disabled).

* waitForEnabled('#submit').click()

More details

waitUntil

Wait for a JavaScript expression to become truthy, evaluated in the browser context.

* waitUntil("document.readyState == 'complete'")
* waitUntil('#progress', "_.style.width == '100%'")
* waitUntil('#submit', '!_.disabled')

When used with a locator, _ refers to the matched DOM element.

More details

waitForResultCount

Wait for a specific number of matching elements to exist.

* def rows = waitForResultCount('.result-row', 5)
* match rows.length == 5

More details

waitForAny

Wait for any one of multiple possible elements to appear.

* waitForAny('#success-msg', '#error-msg')

More details

retry (UI)

Override retry/wait settings for the next action. Three forms: retry(), retry(count), retry(count, interval).

* retry().click('#slow-button')
* retry(5).click('#very-slow-button')
* retry(5, 10000).waitFor('#dynamic-content')

For actions like click() and input(), retry() implies a waitFor() before the action.

More details

script

Execute JavaScript in the browser and return the result.

* def result = script('document.title')
* script("document.querySelector('#hidden').style.display = 'block'")
* match script('#myDiv', '_.innerHTML') contains 'Hello'

More details

scriptAll

Execute JavaScript on all elements matching a locator. Returns an array.

* def texts = scriptAll('table td', '_.textContent')

With a filter function:

* def filtered = scriptAll('td', '_.textContent', function(x){ return x.contains('data') })

More details

mouse

Perform complex mouse interactions: hover, drag, double-click, right-click.

* mouse('.menu-trigger').move()
* mouse('#item').doubleClick()
* mouse('#file').rightClick()
* mouse(100, 200).click()
* mouse('.draggable').down()
* mouse('.drop-zone').move().up()

More details

scroll

Scroll an element into view, then optionally chain an action.

* scroll('#footer')
* scroll('#hidden-button').click()

More details

Key

Object with constants for special keyboard keys.

* input('#search', 'query' + Key.ENTER)
* input('#editor', Key.CONTROL + 'a')
* input('#field', Key.TAB)
* input('body', Key.ESCAPE)

More details

switchFrame

Switch context to an iframe or back to the main page.

* switchFrame('#editor-iframe')
* input('#editor-input', 'hello')
* switchFrame(null)

More details

switchPage

Switch between browser windows or tabs.

* switchPage(1)
* switchPage('New Page')
* switchPage(0)

More details

close

Close the current browser tab.

* close()

dialog

Handle browser dialogs (alert, confirm, prompt). In Karate v2, dialogs are auto-accepted by default.

* dialog(true)
* dialog(false)
* dialog(true, 'prompt input text')

More details

screenshot

Capture a screenshot of the page or a specific element.

* screenshot()
* screenshot('#my-element')

More details

locate / locateAll

Find one or all elements matching a locator. Returns element objects with properties and chainable methods.

* def el = locate('#my-element')
* def items = locateAll('.list-item')
* items.forEach(item => item.click())

More details

exists / optional

Check if an element exists without failing. optional() safely performs actions if the element is present.

* def found = exists('#maybe-present')
* optional('#close-banner').click()

More details

highlight

Visually highlight an element (useful for demos and debugging).

* highlight('#important-button')

Locator Syntax Summary

SyntaxDescription
'#id'CSS selector (ID)
'.class'CSS selector (class)
'[data-testid="x"]'CSS selector (attribute)
'//button[text()="X"]'XPath (starts with /)
'{button}Submit'Wildcard: <button> with exact text "Submit"
'{}Submit'Wildcard: any element with exact text "Submit"
'{^}Sub'Wildcard: any element containing text "Sub"
'{^span}Sub'Wildcard: <span> containing text "Sub"

Friendly Locators

Find elements relative to other elements using positional relationships.

* rightOf('{}Username').input('john')
* leftOf('{}Remember me').click()
* below('{}Country').select('United States')
* above('{}Password').input('secret')
* near('{}Submit').click()

By default, friendly locators search for <input> elements. Override with .find():

* rightOf('{}Label').find('span').click()

More details


Browser JavaScript Patterns

Understanding the boundary between Karate's JavaScript engine and the browser's JavaScript engine is key to effective UI testing.

Karate vs the Browser

Karate runs two separate JavaScript environments, and understanding this boundary is the single most important concept for UI testing:

  1. Karate JS -- the test script engine. This is where def, match, and all Karate keywords execute. Variables like response, driver, and anything you def live here. This engine runs on the JVM.
  2. Browser JS -- the web page's JavaScript runtime. This is where script(), scriptAll(), and waitUntil() expressions execute. They have access to document, window, and the DOM, but NOT to Karate variables. This engine runs inside Chrome/Firefox/etc.

Data crosses this boundary in specific ways:

  • Karate to Browser: String arguments to script() are sent as text. You must build the full JS expression as a string in Karate before sending it.
  • Browser to Karate: Return values from script() are serialized (JSON-safe values only). DOM elements cannot be returned directly -- use locate() instead.

You cannot directly reference Karate variables inside browser JavaScript:

# WRONG - 'name' is a Karate variable, not available in browser JS
* script("document.getElementById('input').value = name")

# RIGHT - use Karate's input() method instead
* input('#input', name)

# RIGHT - if you must use script(), build the string in Karate
* def js = "document.getElementById('input').value = '" + name + "'"
* script(js)

The same boundary applies to waitUntil(). The expression string is evaluated in the browser:

# This JS runs in the browser, not in Karate
* waitUntil("document.readyState == 'complete'")

# To use a Karate variable, interpolate it into the string
* def expected = 'ready'
* waitUntil("document.getElementById('status').textContent == '" + expected + "'")

Function Composition

Create reusable functions that combine Karate driver actions:

Background:
* def getTableData =
"""
function() {
return scriptAll('table tr', '_.textContent')
}
"""
* def clickAndWait =
"""
function(locator) {
click(locator);
delay(500);
}
"""

Scenario: Use composed functions
* driver 'https://example.com'
* def data = getTableData()
* clickAndWait('#next-page')

Note that within these functions, you can call Karate driver methods directly (click, input, waitFor, etc.) because they are available in Karate's JS scope.

More details

Loop Until Pattern

Repeat an action until a condition is met, using waitUntil() with a JavaScript function. This is a common pattern for cleaning up test state or paginating through results:

* def deleteAllRows =
"""
function() {
if (!exists('.data-row')) return true;
click('.delete-button');
delay(500);
}
"""
* waitUntil(deleteAllRows)

The function returns true to stop the loop, or any falsy value to continue retrying. The function runs in Karate JS (not the browser), so you have access to all driver methods like exists(), click(), and delay().

Another common example -- pagination:

* def collectAllPages =
"""
function() {
var rows = locateAll('tr.data-row');
karate.appendTo('allRows', rows);
if (!exists('{a}Next')) return true;
click('{a}Next');
delay(1000);
}
"""
* def allRows = []
* waitUntil(collectAllPages)

More details

scriptAwait

Execute async JavaScript in the browser and wait for the promise to resolve. Useful for calling browser-side async APIs:

* def result = scriptAwait("fetch('/api/data').then(r => r.json())")
* match result.status == 'ok'

Locator Lookup Pattern

Store locators in a JSON file for reusability, avoiding the complexity of traditional Page Object Models:

# locators.json: { "login": { "user": "#username", "pass": "#password", "submit": "[data-testid='login-btn']" } }
Background:
* def loc = read('locators.json')

Scenario: Login
* driver 'https://example.com/login'
* input(loc.login.user, 'john')
* input(loc.login.pass, 'secret')
* click(loc.login.submit)

More details


Wait API

A comprehensive comparison of all wait and retry mechanisms in Karate UI testing.

Wait Methods Comparison

MethodWaits ForReturnsUse When
waitFor('#id')Element to exist in DOMElementPage loads new content via AJAX
waitForText('#id', 'text')Element to contain text (string contains)-Status messages, loading indicators
waitForEnabled('#id')Element to become enabledElementForm validation enables submit button
waitForUrl('/path')URL to contain string-Page navigation completes
waitForResultCount('.cls', n)Exactly n matching elementsElement arrayData tables loading incrementally
waitForAny('#a', '#b')Any one of multiple elementsElementConditional UI outcomes
waitUntil('expr')Browser JS expression to be truthytruthy valueCustom conditions, complex DOM checks
waitUntil('#id', '_.prop')Element property conditiontruthy valueSpecific element state changes
retry().click('#id')Element to exist, then clicksElementSame as waitFor + click
retry(n, ms).waitFor('#id')Element to exist with custom timeoutElementOverriding default wait time

retry() vs waitFor()

Both retry().click('#id') and waitFor('#id').click() accomplish the same thing. The difference is:

  • waitFor() uses the globally configured retry settings (default: 3 attempts, 3000ms interval)
  • retry(count, interval) overrides those settings for this one action
# These are equivalent (using default retry settings)
* retry().click('#button')
* waitFor('#button').click()

# Use retry() only when you need a different timeout
* retry(10, 1000).click('#slow-loading-button')

Choosing the Right Wait Strategy

For 95% of cases: Use waitFor() for the first element on a new page, then chain actions:

* click('#load-data')
* waitFor('#results').exists
* match text('#first-result') == 'Expected'

For slow operations: Override the retry window:

* retry(10, 2000).waitFor('#report-data')

For complex conditions: Use waitUntil() with browser JavaScript:

* waitUntil("document.querySelectorAll('.row').length > 5")

For conditional flows: Use waitForAny() when multiple outcomes are possible:

* waitForAny('#success-page', '#error-page')
* if (exists('#error-page')) karate.fail('Unexpected error')

For polling an API (not UI): Use retry until on the HTTP request:

* configure retry = { count: 10, interval: 5000 }
Given url baseUrl
And path 'jobs', jobId
And retry until response.status == 'done'
When method get

Default Retry Configuration

The global defaults apply to both HTTP retry until and UI wait methods:

# In karate-config.js or feature Background
* configure retry = { count: 3, interval: 3000 }

Total maximum wait time = count x interval. For example, { count: 10, interval: 1000 } means up to 10 seconds.

Common Wait Patterns

Wait for page load after form submit:

* click('#submit')
* waitForUrl('/confirmation')
* match text('#message') contains 'Success'

Wait for AJAX content to load:

* click('#load-more')
* waitForResultCount('.item', 20)

Wait for element state change:

* input('#email', 'valid@example.com')
* waitForEnabled('#submit').click()

Poll an element until its text changes:

* retry(10, 1000).waitForText('#status', 'Complete')

Handle slow single-page app transitions:

* click('#navigate')
* retry(10, 2000).waitFor('#new-page-content')

Conditional branching after wait:

* waitForAny('#success', '#error')
* def succeeded = exists('#success')
* if (!succeeded) karate.fail('Operation failed: ' + text('#error'))

More details


Quick Reference Tables

Response Variables

These are automatically populated after every HTTP request.

VariableTypeDescription
responseanyResponse body (auto-parsed as JSON/XML)
responseStatusnumberHTTP status code
responseHeadersobjectHeaders (values are arrays)
responseCookiesobjectCookies
responseTimenumberResponse time in milliseconds
responseBytesbyte[]Raw response bytes
responseTypestringResponse content type
requestTimeStampnumberRequest timestamp (epoch ms)
prevRequestobjectThe actual HTTP request sent (uri, method, headers, body)

More details

Type Conversion Keywords

KeywordInputOutputExample
jsonstring / XMLJSON* json data = xmlResponse
xmlstring / JSONXML* xml doc = '<root/>'
stringJSON / XMLstring* string text = { a: 1 }
xmlstringXMLstring* xmlstring s = xmlData
bytesanybyte[]* bytes b = read('file.pdf')
yamlYAML textJSON* yaml data = """..."""
csvCSV fileJSON array* def data = read('data.csv')

More details

Magic Variables

VariableContextDescription
__argCalled featureThe argument passed to call
__loopCalled featureLoop iteration index (-1 if not looping)
__rowScenario OutlineCurrent Examples row as JSON
__numScenario OutlineRow index (0-based)
listenResultAfter listenResult from karate.signal()

More details

Tags

TagDescription
@ignoreSkip scenario during normal execution
@setupMark as setup-only scenario (called via karate.setup())
@parallel=falseDisable parallel execution for this scenario/feature
@report=falseHide from HTML reports
@env=devRun only when karate.env matches
@env=dev,stagingRun when karate.env matches any listed value
@envnot=prodRun when karate.env does NOT match
@timeout=60000Custom timeout for a scenario

More details


See Also

On This Page