Skip to main content

CORE SYNTAX

Data Types and Formats

Overview

Karate provides native support for complex data types, eliminating the need for string escaping and enabling seamless integration of JSON, XML, JavaScript functions, and file operations directly within test scripts.

Native Data Types Concept

Native data types mean you can insert them directly into scripts without string enclosure or quote escaping. They integrate seamlessly within your test flow, making scripts more readable and maintainable.

JSON Objects

Lenient JSON Parsing

# Keys don't require quotes (lenient parsing)
* def cat = { name: 'Billie', scores: [2, 5] }
* assert cat.scores[1] == 5

# Complex nested structures
* def user = {
profile: { name: 'John', age: 30 },
preferences: { theme: 'dark', notifications: true },
roles: ['user', 'admin']
}

Special Character Handling

# Hyphens require quotes (interpreted as minus sign)
* def headers = { 'Content-Type': 'application/json', 'X-API-Key': 'secret' }

# Standard JSON when needed
* def strictJson = {
"user-id": 123,
"created-at": "2023-01-01T00:00:00Z"
}

JSON Manipulation and Access

* def cats = [{ name: 'Billie' }, { name: 'Bob' }]
* match cats[1] == { name: 'Bob' }

# Extract parts for reuse
* def first = cats[0]
* match first == { name: 'Billie' }

# Nested property access
* def response = { user: { profile: { name: 'Alice' } } }
* def userName = response.user.profile.name
* match userName == 'Alice'

Best Practice: Always prefer match over assert for JSON/XML comparisons. Match provides better error messages and supports embedded expressions and fuzzy matching.

XML Documents

Basic XML Handling

* def cat = <cat><name>Billie</name><scores><score>2</score><score>5</score></scores></cat>

# XPath indexing (starts from 1)
* match cat/cat/scores/score[2] == '5'

# JSON-like traversal (starts from 0)
* match cat.cat.scores.score[1] == 5

XML Navigation Flexibility

* def xmlDoc =
"""
<user>
<profile>
<name>John</name>
<age>30</age>
</profile>
<permissions>
<role>admin</role>
<role>user</role>
</permissions>
</user>
"""

# XPath style
* match xmlDoc/user/profile/name == 'John'
* match xmlDoc/user/permissions/role[1] == 'admin'

# JSON-like style (more intuitive for array access)
* match xmlDoc.user.permissions.role[0] == 'admin'
* match xmlDoc.user.permissions.role[1] == 'user'

Embedded Expressions

Dynamic Value Injection

* def user = { name: 'john', age: 21 }
* def lang = 'en'

# Inject variables into JSON using #() syntax
* def session = {
name: '#(user.name)',
locale: '#(lang)',
sessionUser: '#(user)',
timestamp: '#(new Date().getTime())'
}

* match session.name == 'john'
* match session.locale == 'en'
* match session.sessionUser == user

XML Embedded Expressions

* def user = <user><name>john</name></user>
* def lang = 'en'

* def session =
"""
<session>
<locale>#(lang)</locale>
<sessionUser>#(user)</sessionUser>
<timestamp>#(new Date().getTime())</timestamp>
</session>
"""

XPath Functions

Basic XPath Operations

Use XPath functions for advanced XML querying and validation:

* def records =
"""
<records>
<record index="1">a</record>
<record index="2">b</record>
<record index="3" foo="bar">c</record>
</records>
"""

# Count nodes
* match records count(/records//record) == 3

# Attribute selection
* match records //record[@index=2] == 'b'
* match records //record[@foo='bar'] == 'c'

# Text content
* match records //record[@index='1']/text() == 'a'

Advanced XPath Patterns

Node-Set Returns

XPath expressions returning multiple nodes work with match assertions:

* def teachers =
"""
<teachers>
<teacher department="science">
<subject>math</subject>
<subject>physics</subject>
</teacher>
<teacher department="arts">
<subject>political education</subject>
<subject>english</subject>
</teacher>
</teachers>
"""

# Returns array of matching nodes
* match teachers //teacher[@department='science']/subject == ['math', 'physics']
* match teachers //teacher[@department='arts']/subject == ['political education', 'english']

# All subjects across teachers
* match teachers //subject == ['math', 'physics', 'political education', 'english']

Dynamic XPath with karate.xmlPath()

Build XPath expressions dynamically:

* def xml = <query><name><foo>bar</foo></name></query>
* def elementName = 'name'

# Dynamic path construction
* def name = karate.xmlPath(xml, '/query/' + elementName + '/foo')
* match name == 'bar'

# Extract complex nodes
* def queryName = karate.xmlPath(xml, '/query/' + elementName)
* match queryName == <name><foo>bar</foo></name>

# With variables and conditions
* def department = 'science'
* def teachers = read('teachers.xml')
* def scienceTeachers = karate.xmlPath(teachers, "//teacher[@department='" + department + "']")

Complex XPath Queries

* def catalog =
"""
<catalog>
<book id="bk101" available="true">
<author>Gambardella, Matthew</author>
<title>XML Developer's Guide</title>
<price>44.95</price>
</book>
<book id="bk102" available="false">
<author>Ralls, Kim</author>
<title>Midnight Rain</title>
<price>5.95</price>
</book>
</catalog>
"""

# Books under $10
* match catalog //book[price < 10]/title == ['Midnight Rain']

# Available books
* match catalog //book[@available='true']/@id == ['bk101']

# Contains text
* match catalog //book[contains(title, 'XML')]/author == ['Gambardella, Matthew']

# Position-based
* match catalog //book[position()=1]/title == 'XML Developer\'s Guide'
* match catalog //book[last()]/title == 'Midnight Rain'

XPath with Namespaces

# For namespace-aware XML
* configure xmlNamespaceAware = true
* def soap =
"""
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<m:GetPrice xmlns:m="http://example.com/prices">
<m:Item>Apple</m:Item>
</m:GetPrice>
</soap:Body>
</soap:Envelope>
"""

# Access with namespace prefixes
* def item = karate.xmlPath(soap, "//m:Item")
* match item == 'Apple'

XPath Best Practices

# ✅ Good: Specific paths
* match xml //book[@id='bk101']/title == 'XML Developer\'s Guide'

# ❌ Avoid: Overly broad paths
* match xml //title == 'Some Title'

# ✅ Good: Use predicates for filtering
* match xml //book[price > 10 and @available='true']

# ✅ Good: Leverage XPath functions
* match xml count(//book) == 2
* match xml sum(//book/price) == 50.90

Cross-Reference: For XML validation patterns, see Match Keyword

Embedded Expression Rules

Embedded expressions work:

  • Within JSON or XML declarations
  • On the right-hand side of def, match, configure
  • When reading JSON/XML files with read()
  • Must be enclosed in #( and )

String Concatenation in Embedded Expressions

# ❌ Wrong - concatenation outside expression
* def foo = { bar: 'hello #(name)' }

# ✅ Correct - full expression within #()
* def name = 'World'
* def foo = { bar: '#("hello " + name)' }
* match foo.bar == 'hello World'

# ✅ Alternative - two-step approach
* def greeting = 'hello ' + name
* def foo = { bar: '#(greeting)' }

Match Statement Convenience

# Embedded expressions work in match statements
* def foo = 'a1'
* match foo == '#("a" + 1)'

# ES6 string interpolation (Karate 1.0+)
* def todaysDate = '2023-01-01'
* param filter = `ORDER_DATE:"${todaysDate}"`

Enclosed JavaScript

Alternative to Embedded Expressions

* def user = { name: 'john', age: 21 }
* def lang = 'en'

# Embedded expressions approach
* def embedded = {
name: '#(user.name)',
locale: '#(lang)',
sessionUser: '#(user)'
}

# Enclosed JavaScript approach (parentheses around entire payload)
* def enclosed = ({
name: user.name,
locale: lang,
sessionUser: user
})

* match embedded == enclosed

When to Use Each Approach

Use CaseRecommended ApproachReason
File-based JSON templatesEmbedded expressionsMaintains valid JSON for IDE editing
Complex dynamic generationEnclosed JavaScriptMore natural for JS developers
Validation scenariosEmbedded expressionsBetter for schema-like patterns
Simple variable substitutionEitherPersonal preference

Multi-line Expressions

Readable Complex Structures

# Instead of single-line (hard to read)
* def cat = <cat><name>Billie</name><scores><score>2</score><score>5</score></scores></cat>

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

SOAP Request Example

* request
"""
<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:QueryUsageBalance xmlns:ns2="http://www.mycompany.com/usage/V1">
<ns2:UsageBalance>
<ns2:LicenseId>12341234</ns2:LicenseId>
</ns2:UsageBalance>
</ns2:QueryUsageBalance>
</S:Body>
</S:Envelope>
"""

Inline Validation

* match response ==
"""
{
id: { domain: "DOM", type: "entityId", value: "#ignore" },
created: { on: "#ignore" },
lastUpdated: { on: "#ignore" },
entityState: "ACTIVE"
}
"""

JavaScript Functions

Inline Function Definition

# Simple function
* def greeter = function(title, name) {
return 'hello ' + title + ' ' + name
}
* assert greeter('Mr.', 'Bob') == 'hello Mr. Bob'

# Function with complex logic
* def calculateDiscount = function(price, customerType) {
if (customerType === 'premium') {
return price * 0.8
} else if (customerType === 'regular') {
return price * 0.9
} else {
return price
}
}
* def discountedPrice = calculateDiscount(100, 'premium')
* match discountedPrice == 80

Multi-line Function Definition

* def dateStringToLong =
"""
function(s) {
var SimpleDateFormat = Java.type('java.text.SimpleDateFormat');
var sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
return sdf.parse(s).time;
}
"""
* assert dateStringToLong("2016-12-24T03:39:21.081+0000") == 1482550761081

Java Interop

# Calling Java classes from JavaScript
* def generateUuid =
"""
function() {
var UUID = Java.type('java.util.UUID');
return UUID.randomUUID().toString();
}
"""
* def uuid = generateUuid()
* match uuid == '#regex [a-f0-9-]{36}'

# Working with Java collections
* def createList =
"""
function(items) {
var ArrayList = Java.type('java.util.ArrayList');
var list = new ArrayList();
items.forEach(function(item) {
list.add(item);
});
return list;
}
"""

Function Call Alternatives

* def timeLong = call dateStringToLong '2016-12-24T03:39:21.081+0000'
* assert timeLong == 1482550761081

# JSON argument example
* def greeter = function(name) {
return 'Hello ' + name.first + ' ' + name.last + '!'
}
* def greeting = call greeter { first: 'John', last: 'Smith' }
* match greeting == 'Hello John Smith!'

Reading Files

File Type Auto-detection

# JSON files (auto-parsed)
* def someJson = read('some-json.json')
* def moreJson = read('classpath:more-json.json')

# XML files (auto-parsed)
* def someXml = read('../common/my-xml.xml')

# YAML to JSON conversion
* def jsonFromYaml = read('some-data.yaml')

# CSV to JSON conversion
* def jsonFromCsv = read('some-data.csv')

# Plain text
* def someString = read('classpath:messages.txt')

# JavaScript (evaluated)
* def someValue = read('some-js-code.js')

Path Prefixes

PrefixDescriptionUse Case
classpath:Relative to classpath rootReusable across project
this:Relative to current featureCalled feature resources
file:Absolute file pathDevelopment only
(none)Relative to current featureLocal resources

JavaScript Function Files

# If JS file evaluates to function, it can be reused
* def someFunction = read('classpath:some-reusable-code.js')
* def result1 = call someFunction
* def result2 = someFunction() # Direct invocation

# One-liner function call
* def result = call read('some-js-code.js')

Feature File Reuse

# Perfect for authentication or setup flows
* def result = call read('classpath:some-reusable-steps.feature')

Dynamic File Selection

# Choose file based on condition
* def environment = karate.env || 'dev'
* def config = read('my-config-' + environment + '.json')

# Combine multiple files
* def combinedString = read('first.txt') + read('second.txt')

Binary Files and Streams

# Files without recognized extensions treated as streams
* def pdfStream = read('some-pdf.pdf')
* def imageBytes = read('screenshot.png')

# GraphQL files (treated as text)
* def query = read('user-query.graphql')

Request and Response Usage

# Use file as request body
* request read('some-big-payload.json')

# Use file in match validation
* match response == read('expected-response-payload.json')

# Raw string reading (bypasses auto-conversion)
* def csvString = karate.readAsString('classpath:my.csv')
* header Content-Type = 'text/csv'
* request csvString

Embedded Expressions in Files

JSON and XML files support embedded expressions when loaded:

user-template.json:

{
"id": "#(userId)",
"name": "#(userName)",
"email": "#(userName + '@example.com')",
"timestamp": "#(new Date().getTime())"
}

Usage:

* def userId = 123
* def userName = 'alice'
* def user = read('user-template.json')
* match user.id == 123
* match user.email == 'alice@example.com'

Type Conversion

String to Data Type Conversion

# YAML string to JSON
* text yamlString =
"""
name: John
age: 30
skills:
- java
- javascript
"""
* yaml userData = yamlString
* match userData.skills[1] == 'javascript'

# CSV string to JSON array
* text csvString =
"""
name,age,role
Alice,25,developer
Bob,30,manager
"""
* csv users = csvString
* match users[0] == { name: 'Alice', age: 25, role: 'developer' }

Raw Text Handling

# Disable auto-parsing with 'text' keyword
* text graphqlQuery =
"""
{
user(id: "<userId>") {
name
email
}
}
"""
# Use in request without JSON parsing
* def requestBody = { query: '#(graphqlQuery)' }

Advanced Data Patterns

Conditional Data Structures

* def includeAddress = true
* def user = {
name: 'John',
age: 30
}
* if (includeAddress) user.address = { city: 'New York', zip: '10001' }

Dynamic Property Names

* def fieldName = 'customField'
* def value = 'customValue'
* def data = {}
* eval data[fieldName] = value
* match data.customField == 'customValue'

Complex Nested Structures

* def buildNestedObject =
"""
function(levels, value) {
if (levels.length === 1) {
var obj = {};
obj[levels[0]] = value;
return obj;
}
var obj = {};
obj[levels[0]] = buildNestedObject(levels.slice(1), value);
return obj;
}
"""
* def nested = buildNestedObject(['a', 'b', 'c'], 'deep value')
* match nested == { a: { b: { c: 'deep value' } } }

Best Practices

File Organization

src/test/java/
├── data/
│ ├── users/
│ │ ├── valid-user.json
│ │ └── invalid-user.json
│ └── responses/
│ ├── success-response.json
│ └── error-response.json
├── utils/
│ ├── common-functions.js
│ └── data-generators.js
└── features/
└── user-tests.feature

Data Reusability

# ✅ Good: Reusable templates with embedded expressions
* def userTemplate = read('classpath:data/user-template.json')
* def testUser1 = { userId: 1, userName: 'alice' }
* def user1 = call read('classpath:utils/merge-template.js') { template: userTemplate, data: testUser1 }

# ✅ Good: Environment-specific data
* def environment = karate.env || 'dev'
* def config = read('classpath:config-' + environment + '.json')

Performance Considerations

# ✅ Good: Cache expensive operations
Background:
* def commonData = callonce read('classpath:setup-data.feature')
* def utilities = callonce read('classpath:utils/common-functions.js')

# ✅ Good: Reuse parsed data
* def baseRequest = read('classpath:data/base-request.json')
* def request1 = karate.merge(baseRequest, { id: 1 })
* def request2 = karate.merge(baseRequest, { id: 2 })

Next Steps