Skip to main content

CORE SYNTAX

Variables and Assignment

Overview

Karate provides powerful variable management capabilities for dynamic test scenarios. Variables can store strings, numbers, JSON objects, XML documents, and even JavaScript functions. Understanding variable scope, assignment, and manipulation is essential for effective test automation.

Variable Declaration with def

Basic Assignment

# String assignment
* def myVar = 'world'
* def message = 'Hello ' + myVar

# Number assignment
* def myNum = 5
* def calculation = myNum * 2

# Boolean assignment
* def isValid = true

JSON and XML Assignment

# JSON object (lenient parsing - quotes optional for keys)
* def user = { name: 'John', age: 30, active: true }
* def scores = [85, 92, 78]

# XML document
* def xmlDoc = <user><name>John</name><age>30</age></user>

Variable Overwriting and Restrictions

def overwrites any existing variable with the same name. Configuration variables may already exist before script execution.

Reserved Variable Names:

  • url and request are not allowed as variable names to prevent confusion with built-in keywords
# ❌ Invalid - will not set the request body
* def request = { name: 'test' }

# ✅ Valid - proper request body assignment
* def requestBody = { name: 'test' }
* request requestBody

Data Assertion with assert

Boolean Expression Validation

* def color = 'red '
* def num = 5
* assert color + num == 'red 5'

# Complex assertions
* def user = { name: 'John', age: 30 }
* assert user.age >= 18
* assert user.name.length > 0

Best Practice: Use match instead of assert for JSON/XML comparisons. Match provides better error messages and supports fuzzy matching.

Console Output with print

Basic Printing

# Simple variable printing
* def userName = 'Alice'
* print 'User name is:', userName

# Multiple expressions (comma-separated for pretty-printing)
* def userdata = { name: 'Bob', age: 25 }
* print 'User data:', userdata

Pretty-Printing JSON and XML

* def myJson = { foo: 'bar', baz: [1, 2, 3] }
* print 'JSON structure:', myJson

Output:

20:29:11.290 [main] INFO  com.intuit.karate - [print] JSON structure: {
"foo": "bar",
"baz": [
1,
2,
3
]
}

Using Karate Helper Methods

# Manual pretty-printing with line breaks
* print 'Formatted JSON:\n' + karate.pretty(myJson)

# Pretty-print XML
* def xmlData = <root><item>value</item></root>
* print 'Formatted XML:\n' + karate.prettyXml(xmlData)

Data Manipulation with set

Simple Property Updates

* def myJson = { foo: 'bar' }
* set myJson.foo = 'world'
* match myJson == { foo: 'world' }

# Add new properties
* set myJson.newProp = 'value'
* match myJson == { foo: 'world', newProp: 'value' }

JsonPath Expressions

* def data = { users: [] }

# Use JsonPath with $ notation
* set data $.users[0] = { name: 'Alice' }
* set data $.users[] = { name: 'Bob' } # Append to array
* match data.users.length == 2

Nested Object Creation

* def result = {}
* set result.user.profile.name = 'John'
* set result.user.profile.age = 30
* set result.user.permissions = ['read', 'write']

* match result == {
user: {
profile: { name: 'John', age: 30 },
permissions: ['read', 'write']
}
}

Multiple Property Assignment

* def cat = { name: '' }

* set cat
| path | value |
| name | 'Bob' |
| age | 5 |
| tags | ['cute', 'fluffy'] |

* match cat == { name: 'Bob', age: 5, tags: ['cute', 'fluffy'] }

Array Creation with Multiple Columns

* set users
| path | 0 | 1 | 2 |
| name | 'Alice' | 'Bob' | 'Charlie'|
| age | 25 | 30 | 35 |
| role | 'user' | 'admin' | 'user' |

* match users == [
{ name: 'Alice', age: 25, role: 'user' },
{ name: 'Bob', age: 30, role: 'admin' },
{ name: 'Charlie', age: 35, role: 'user' }
]

Nested Path Assignment

# Move parent path next to 'set' to reduce typing
* set user.profile
| path | value |
| firstName | 'John' |
| lastName | 'Doe' |
| contacts[0] | 'john@example.com' |
| contacts[1] | '+1234567890' |

* match user == {
profile: {
firstName: 'John',
lastName: 'Doe',
contacts: ['john@example.com', '+1234567890']
}
}

XML Manipulation

* def cat = <cat><name>Billie</name></cat>
* set cat /cat/name = 'Jean'
* match cat / == <cat><name>Jean</name></cat>

# Set XML fragments
* def xml = <foo><bar>baz</bar></foo>
* set xml/foo/bar = <hello>world</hello>
* match xml == <foo><bar><hello>world</hello></bar></foo>

Data Extraction with get

JsonPath Extraction

* def cat = {
name: 'Billie',
kittens: [
{ id: 23, name: 'Bob' },
{ id: 42, name: 'Wild' }
]
}

# Extract arrays using JsonPath
* def kittenIds = get cat.kittens[*].id
* match kittenIds == [23, 42]

# Alternative JsonPath syntax
* def kittenNames = get cat $.kittens[*].name
* match kittenNames == ['Bob', 'Wild']

JsonPath Short-cuts

# Short-cut form using $ prefix
* def kittenIds = $cat.kittens[*].id
* def kittenNames = $cat.kittens[*].name

# Even shorter - direct match without intermediate variables
* match cat.kittens[*].id == [23, 42]
* match cat.kittens[*].name == ['Bob', 'Wild']

Single Element Extraction

# Get first element from array result
* def firstKittenId = get[0] cat.kittens[*].id
* match firstKittenId == 23

# Equivalent to:
* def allIds = get cat.kittens[*].id
* match allIds[0] == 23

JsonPath Filtering

# Find elements matching criteria
* def bob = get[0] cat.kittens[?(@.id==23)]
* match bob.name == 'Bob'

# Dynamic filtering using karate.jsonPath()
* def searchName = 'Bob'
* def result = karate.jsonPath(cat, "$.kittens[?(@.name=='" + searchName + "')]")[0]
* match result == bob

Data Removal with remove

Property Removal

* def json = { foo: 'world', hey: 'ho', zee: [1, 2, 3] }
* remove json.hey
* match json == { foo: 'world', zee: [1, 2, 3] }

# Remove array elements by index
* remove json $.zee[1]
* match json == { foo: 'world', zee: [1, 3] }

XML Element Removal

* def xml = <foo><bar><hello>world</hello></bar></foo>
* remove xml/foo/bar/hello
* match xml == <foo><bar/></foo>

* remove xml /foo/bar
* match xml == <foo/>

Dynamic Property Removal

# Using JavaScript delete operator
* def key = 'unwanted'
* def data = { wanted: 'keep', unwanted: 'remove' }
* delete data[key]
* match data == { wanted: 'keep' }

Data Table Creation

Table to JSON Array

* table cats
| name | age |
| 'Bob' | 2 |
| 'Wild' | 4 |
| 'Nyan' | 3 |

* match cats == [
{ name: 'Bob', age: 2 },
{ name: 'Wild', age: 4 },
{ name: 'Nyan', age: 3 }
]

Dynamic Table Values

* def one = 'hello'
* def two = { baz: 'world' }

* table json
| foo | bar |
| one | { baz: 1 } |
| two.baz | ['baz', 'ban'] |

* match json == [
{ foo: 'hello', bar: { baz: 1 } },
{ foo: 'world', bar: ['baz', 'ban'] }
]

Handling Null Values

* def nullValue = { baz: null }

* table data
| name | value |
| 'test1' | | # Empty cell = key omitted
| 'test2' | (null) | # Explicit null = null value
| 'test3' | nullValue.baz| # Expression = null value

* match data == [
{ name: 'test1' },
{ name: 'test2', value: null },
{ name: 'test3', value: null }
]

Text Handling

Raw Text Assignment

# Disable JSON/XML parsing with 'text' keyword
* text query =
"""
{
hero(name: "<name>") {
height
mass
}
}
"""

# Useful for GraphQL, templates, or malformed JSON-like text
* def requestBody = { query: '#(query)' }

Placeholder Replacement

# Simple replacement
* def text = 'hello <foo> world'
* replace text.foo = 'bar'
* match text == 'hello bar world'

# Multiple replacements
* def template = 'hello <one> world <two> bye'
* replace template
| token | value |
| one | 'cruel' |
| two | 'good' |
* match template == 'hello cruel world good bye'

# Dynamic replacements with expressions
* def name = 'John'
* def data = { greeting: 'Hi' }
* def text = '<greeting> there <name>!'
* replace text
| token | value |
| greeting | data.greeting |
| name | name |
* match text == 'Hi there John!'

Multi-line Expressions

Readable Complex Data

# Instead of single-line XML
* def cat = <cat><name>Billie</name><scores><score>2</score><score>5</score></scores></cat>

# Use multi-line for better readability
* def cat =
"""
<cat>
<name>Billie</name>
<scores>
<score>2</score>
<score>5</score>
</scores>
</cat>
"""

# Multi-line JSON
* def complexUser =
"""
{
"profile": {
"name": "John Doe",
"preferences": {
"theme": "dark",
"notifications": true
}
},
"permissions": ["read", "write", "admin"]
}
"""

Data Format Conversion

YAML to JSON

# Convert YAML string to JSON
* text yamlString =
"""
name: John
input:
id: 1
subType:
name: Smith
deleted: false
"""
* yaml jsonData = yamlString
* match jsonData == {
name: 'John',
input: {
id: 1,
subType: { name: 'Smith', deleted: false }
}
}

CSV to JSON

# Convert CSV string to JSON array
* text csvString =
"""
name,type
Billie,LOL
Bob,Wild
"""
* csv jsonArray = csvString
* match jsonArray == [
{ name: 'Billie', type: 'LOL' },
{ name: 'Bob', type: 'Wild' }
]

Variable Scope and Lifecycle

Background Variables

Background:
* def globalConfig = { apiUrl: 'https://api.example.com' }
* def authToken = call read('get-auth-token.feature')

Scenario: First test
# globalConfig and authToken are available
* url globalConfig.apiUrl
* header Authorization = 'Bearer ' + authToken

Scenario: Second test
# Fresh copies of Background variables
# Any modifications in previous scenarios are reset
* url globalConfig.apiUrl

Variable Precedence

  1. Configuration variables (from karate-config.js)
  2. Background variables (re-initialized per scenario)
  3. Scenario variables (local to current scenario)
  4. Called feature variables (returned from feature calls)

Advanced Variable Patterns

Conditional Assignment

* def environment = karate.env || 'dev'
* def baseUrl = environment == 'prod' ? 'https://api.prod.com' : 'https://api.dev.com'
* def timeout = environment == 'prod' ? 30000 : 5000

Variable Templates

# Template with embedded expressions
* def userTemplate = {
id: '#(userId)',
name: '#(userName)',
email: '#(userName + "@example.com")',
timestamp: '#(new Date().getTime())'
}

# Use template with different values
* def userId = 123
* def userName = 'alice'
* def user1 = userTemplate

* def userId = 456
* def userName = 'bob'
* def user2 = userTemplate

Best Practices

Variable Naming

# ✅ Good: Descriptive names
* def userAuthToken = 'bearer-token-123'
* def customerOrderData = { orderId: 'ORD-001' }

# ❌ Avoid: Generic names
* def data = { orderId: 'ORD-001' }
* def temp = 'bearer-token-123'

Data Organization

# ✅ Good: Grouped related data
* def testConfig = {
api: { baseUrl: 'https://api.example.com', timeout: 5000 },
user: { name: 'testuser', role: 'admin' },
features: { enableNewUi: true, enableBeta: false }
}

# ✅ Good: Use meaningful prefixes
* def apiBaseUrl = testConfig.api.baseUrl
* def userRole = testConfig.user.role

Performance Considerations

# ✅ Good: Use callonce for expensive operations
Background:
* def authData = callonce read('expensive-auth-setup.feature')
* def staticConfig = callonce read('load-config.feature')

# ✅ Good: Reuse complex objects
* def baseRequest = { headers: commonHeaders, timeout: 5000 }
* def request1 = karate.merge(baseRequest, { body: data1 })
* def request2 = karate.merge(baseRequest, { body: data2 })

Next Steps