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.
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.
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)
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 |
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 }]
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
}
}
"""
yaml
Parse YAML content into a JSON variable.
* yaml config =
"""
name: John
age: 30
roles:
- admin
- user
"""
* match config.name == 'John'
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' }
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'
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
doc
Add documentation text that appears in HTML reports.
* doc 'This scenario tests the user creation flow'
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' }
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' }
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]
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 |
remove
Remove keys from JSON or nodes from XML.
* remove response.password
* remove response..internal
* remove response.users[0]
if
Conditional execution. The expression must be valid JavaScript.
* if (responseStatus == 200) karate.call('success.feature')
* if (env == 'dev') karate.set('baseUrl', devUrl)
HTTP Keywords
Keywords for building and executing HTTP requests.
url
Set the base URL for subsequent requests.
* url 'https://api.example.com'
* url baseUrl
path
Append path segments to the base URL. Multiple segments are joined with /.
* path 'users'
* path 'users', userId
* path 'users', 1, 'posts'
method
Execute the HTTP request with the given method.
* method get
* method post
* method put
* method patch
* method delete
status
Assert the HTTP response status code.
* status 200
* status 201
* status 404
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 ''
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']
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 }
cookie / cookies
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.
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' }
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' }
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' }
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'
soap action
Set the SOAPAction header and execute a POST request.
Given request read('soap-request.xml')
When soap action 'QueryUsageBalance'
Then status 200
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.
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.
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
match !=
Not-equals assertion.
* match response.status != 'error'
* match response != {}
match contains
Partial match. The actual value must contain the expected subset.
* match response contains { name: 'John' }
* match response.tags contains 'urgent'
match !contains
Negative contains. Asserts the actual value does not contain the expected subset.
* match response !contains { error: true }
* match response.roles !contains 'admin'
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 }]
match contains any
The array contains at least one of the expected elements.
* match response.tags contains any ['urgent', 'high', 'critical']
match contains deep
Deep partial match. Recursively checks nested structures.
* match response contains deep { user: { name: 'John' } }
match contains only deep
Contains exactly these elements (any order) using deep comparison.
* match response.items contains only deep [{ id: 1, data: { x: 1 } }]
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 }
match each contains deep
Each array element must deeply contain the expected subset.
* match each response.orders contains deep { items: [{ price: '#number' }] }
match header
Assert a response header value (case-insensitive header name).
* match header Content-Type contains 'json'
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).
| Marker | Description |
|---|---|
#ignore | Skip comparison for this field |
#null | Value must be null (key must be present) |
#notnull | Value must not be null |
#present | Key must exist (value can be any type, even null) |
#notpresent | Key must not exist at all |
#string | Value must be a string |
#number | Value must be a number |
#boolean | Value must be true or false |
#array | Value must be a JSON array |
#object | Value must be a JSON object |
#uuid | Value must be a valid UUID string |
#regex STR | Value must match the regular expression STR |
#? EXPR | JavaScript expression EXPR must evaluate to true (self-validation) |
#[N] | Array must have exactly N elements |
#[] | Value must be a non-empty array |
## prefix | Makes 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.
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.
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
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')
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 }
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')
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')
| Prefix | Description |
|---|---|
| (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) |
@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'
@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
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
| Key | Type | Default | Description |
|---|---|---|---|
url | string | - | Base URL for all requests |
headers | JSON / function | - | Default headers for every request |
cookies | JSON | - | Default cookies (set to null to clear) |
ssl | boolean / string / JSON | false | Enable HTTPS; true, algorithm string, or X509 config object |
connectTimeout | number | 30000 | Connection timeout in milliseconds |
readTimeout | number | 30000 | Read timeout in milliseconds |
proxy | string / JSON | - | HTTP proxy URI or { uri, username, password, nonProxyHosts } |
followRedirects | boolean | true | Follow HTTP redirects automatically |
charset | string | 'utf-8' | Request charset (set to null to suppress) |
retry | JSON | { count: 3, interval: 3000 } | Default retry settings for retry until |
localAddress | string | - | Local network interface to bind |
httpRetryEnabled | boolean | false | Auto-retry on HTTP connection errors |
lowerCaseResponseHeaders | boolean | false | Lowercase all response header keys |
logPrettyRequest | boolean | false | Pretty-print request payloads |
logPrettyResponse | boolean | false | Pretty-print response payloads |
printEnabled | boolean | true | Enable print output |
report | JSON / boolean | true | Report verbosity, e.g., { showLog: true, showAllSteps: false } |
logModifier | Java object | - | Custom log masking implementation |
afterScenario | JS function | - | Hook run after each scenario |
afterScenarioOutline | JS function | - | Hook run after a scenario outline completes |
afterFeature | JS function | - | Hook run after a feature completes |
cors | boolean | - | Enable CORS on mock server |
responseHeaders | JSON / function | - | Default response headers for mock server |
callSingleCache | JSON | { minutes: 0 } | Cache TTL for karate.callSingle() |
abortedStepsShouldPass | boolean | false | Mark aborted steps as passed instead of skipped |
abortSuiteOnFailure | boolean | false | Stop all tests on first failure |
xmlNamespaceAware | boolean | false | Enable XML namespace handling |
matchEachEmptyAllowed | boolean | false | Allow empty arrays in match each |
pauseIfNotPerf | boolean | false | Enable karate.pause() outside perf mode |
ntlmAuth | JSON | - | NTLM authentication: { username, password, domain, workstation } |
driver | JSON | - | UI automation driver config |
driverTarget | JSON / Java | - | Driver target lifecycle config |
imageComparison | JSON | - | 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) }
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
- They work only within JSON or XML
- They are evaluated on the right-hand side of
def,match,configure, andrequest - They are evaluated when you
read()a JSON or XML file - 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}"`
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.
click
Click an element by locator (CSS, XPath, or wildcard).
* click('#login-button')
* click('//button[text()="Submit"]')
* click('{button}Submit')
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)
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)
waitFor
Wait for an element to appear in the DOM. Returns the element for chaining.
* waitFor('#results').exists
* waitFor('{button}Submit').click()
waitForUrl
Wait for the browser URL to contain a string.
* click('#next-page')
* waitForUrl('/dashboard')
waitForText
Wait for an element to contain specific text (string contains match).
* waitForText('#status', 'Complete')
waitForEnabled
Wait for an element to become enabled (not disabled).
* waitForEnabled('#submit').click()
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.
waitForResultCount
Wait for a specific number of matching elements to exist.
* def rows = waitForResultCount('.result-row', 5)
* match rows.length == 5
waitForAny
Wait for any one of multiple possible elements to appear.
* waitForAny('#success-msg', '#error-msg')
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.
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'
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') })
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()
scroll
Scroll an element into view, then optionally chain an action.
* scroll('#footer')
* scroll('#hidden-button').click()
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)
switchFrame
Switch context to an iframe or back to the main page.
* switchFrame('#editor-iframe')
* input('#editor-input', 'hello')
* switchFrame(null)
switchPage
Switch between browser windows or tabs.
* switchPage(1)
* switchPage('New Page')
* switchPage(0)
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')
screenshot
Capture a screenshot of the page or a specific element.
* screenshot()
* screenshot('#my-element')
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())
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()
highlight
Visually highlight an element (useful for demos and debugging).
* highlight('#important-button')
Locator Syntax Summary
| Syntax | Description |
|---|---|
'#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()
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:
- Karate JS -- the test script engine. This is where
def,match, and all Karate keywords execute. Variables likeresponse,driver, and anything youdeflive here. This engine runs on the JVM. - Browser JS -- the web page's JavaScript runtime. This is where
script(),scriptAll(), andwaitUntil()expressions execute. They have access todocument,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 -- uselocate()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.
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)
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)
Wait API
A comprehensive comparison of all wait and retry mechanisms in Karate UI testing.
Wait Methods Comparison
| Method | Waits For | Returns | Use When |
|---|---|---|---|
waitFor('#id') | Element to exist in DOM | Element | Page loads new content via AJAX |
waitForText('#id', 'text') | Element to contain text (string contains) | - | Status messages, loading indicators |
waitForEnabled('#id') | Element to become enabled | Element | Form validation enables submit button |
waitForUrl('/path') | URL to contain string | - | Page navigation completes |
waitForResultCount('.cls', n) | Exactly n matching elements | Element array | Data tables loading incrementally |
waitForAny('#a', '#b') | Any one of multiple elements | Element | Conditional UI outcomes |
waitUntil('expr') | Browser JS expression to be truthy | truthy value | Custom conditions, complex DOM checks |
waitUntil('#id', '_.prop') | Element property condition | truthy value | Specific element state changes |
retry().click('#id') | Element to exist, then clicks | Element | Same as waitFor + click |
retry(n, ms).waitFor('#id') | Element to exist with custom timeout | Element | Overriding 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'))
Quick Reference Tables
Response Variables
These are automatically populated after every HTTP request.
| Variable | Type | Description |
|---|---|---|
response | any | Response body (auto-parsed as JSON/XML) |
responseStatus | number | HTTP status code |
responseHeaders | object | Headers (values are arrays) |
responseCookies | object | Cookies |
responseTime | number | Response time in milliseconds |
responseBytes | byte[] | Raw response bytes |
responseType | string | Response content type |
requestTimeStamp | number | Request timestamp (epoch ms) |
prevRequest | object | The actual HTTP request sent (uri, method, headers, body) |
Type Conversion Keywords
| Keyword | Input | Output | Example |
|---|---|---|---|
json | string / XML | JSON | * json data = xmlResponse |
xml | string / JSON | XML | * xml doc = '<root/>' |
string | JSON / XML | string | * string text = { a: 1 } |
xmlstring | XML | string | * xmlstring s = xmlData |
bytes | any | byte[] | * bytes b = read('file.pdf') |
yaml | YAML text | JSON | * yaml data = """...""" |
csv | CSV file | JSON array | * def data = read('data.csv') |
Magic Variables
| Variable | Context | Description |
|---|---|---|
__arg | Called feature | The argument passed to call |
__loop | Called feature | Loop iteration index (-1 if not looping) |
__row | Scenario Outline | Current Examples row as JSON |
__num | Scenario Outline | Row index (0-based) |
listenResult | After listen | Result from karate.signal() |
Tags
| Tag | Description |
|---|---|
@ignore | Skip scenario during normal execution |
@setup | Mark as setup-only scenario (called via karate.setup()) |
@parallel=false | Disable parallel execution for this scenario/feature |
@report=false | Hide from HTML reports |
@env=dev | Run only when karate.env matches |
@env=dev,staging | Run when karate.env matches any listed value |
@envnot=prod | Run when karate.env does NOT match |
@timeout=60000 | Custom timeout for a scenario |
See Also
- Keywords Reference -- detailed keyword documentation with the karate object API
- Match Keyword -- all match variants in depth
- Fuzzy Matching -- schema markers and type validation
- Schema Validation -- payload structure validation patterns
- Making Requests -- HTTP request building guide
- Configuration -- all configure options explained
- Calling Features -- feature composition patterns
- UI Testing -- complete browser automation guide
- Karate Object -- all karate.* methods and properties