Skip to main content

ADVANCED

Debugging and Troubleshooting

What You'll Learn

  • Configure logging levels and output for effective debugging
  • Use dry run mode to validate test structure without execution
  • Control report verbosity and mask sensitive information
  • Debug tests in VS Code with breakpoints and step-through
  • Implement custom logging strategies for CI/CD pipelines
  • Troubleshoot common issues with proven solutions

Overview

Effective debugging and troubleshooting are essential for maintaining robust test suites. Karate provides comprehensive logging capabilities, debugging tools, and configuration options to help you quickly identify and resolve issues in your tests.

Logging Configuration

Basic Logging Setup

Karate uses Logback for logging. Create a logback-test.xml file in your classpath:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>

<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>target/karate.log</file>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>

<logger name="com.intuit.karate" level="DEBUG"/>

<root level="warn">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>

</configuration>
```gherkin

### Logging Levels

Control logging verbosity by adjusting levels:

```xml
<!-- Detailed logging for debugging -->
<logger name="com.intuit.karate" level="DEBUG"/>

<!-- Reduced logging for CI/CD -->
<logger name="com.intuit.karate" level="INFO"/>

<!-- Minimal logging -->
<logger name="com.intuit.karate" level="WARN"/>
```gherkin

### Environment-Specific Logging

Use Janino for conditional logging configuration:

```xml
<configuration>
<if condition='property("karate.env").equals("ci")'>
<then>
<logger name="com.intuit.karate" level="INFO"/>
</then>
<else>
<logger name="com.intuit.karate" level="DEBUG"/>
</else>
</if>
</configuration>
```gherkin

## Logging in Tests

### Using karate.log()

Log messages within your tests:

```gherkin
Feature: Logging examples

Scenario: Basic logging
* karate.log('Starting test execution')
* def data = { id: 123, name: 'test' }
* karate.log('Processing data:', data)

* url apiUrl
* method get
* status 200
* karate.log('Response received:', response)
```gherkin

### Debug vs Print

Choose the appropriate logging method:

```gherkin
Scenario: Logging strategies
# Always appears in console and reports
* print 'This always shows'

# Can be suppressed with configure printEnabled
* karate.log('Standard log message')

# Only shows when logger level is DEBUG
* karate.logger.debug('Debug information')

# Pretty print JSON/XML
* def data = { complex: { nested: 'structure' } }
* print 'Formatted data:', data
```gherkin

### Conditional Logging

Log based on conditions:

```gherkin
Background:
* def debugMode = karate.properties['debug.mode'] || false

* def logDebug =
"""
function(msg, data) {
if (karate.get('debugMode')) {
karate.log('DEBUG:', msg, data);
}
}
"""

Scenario: Conditional logging
* logDebug('Request payload', request)
* url apiUrl
* method post
* logDebug('Response', response)
```gherkin

## Report Verbosity

### Controlling Report Output

Configure what appears in HTML reports:

```gherkin
Feature: Report configuration

Background:
# Full verbose reporting
* configure report = { showLog: true, showAllSteps: true }

Scenario: Detailed reporting
* print 'This appears in report'
* def data = 'internal variable' # This also appears
* match response == '#present'

Scenario: Minimal reporting
* configure report = { showLog: false, showAllSteps: false }
* print 'Only print statements show'
* def hidden = 'not in report' # Hidden from report
```gherkin

### Selective Reporting

Control reporting for specific sections:

```gherkin
Scenario: Selective report control
# Show authentication details
* configure report = true
* url authUrl
* request credentials
* method post
* status 200

# Hide large response payload
* configure report = false
* url dataUrl
* method get
* status 200
* def largeData = response

# Re-enable for important validation
* configure report = true
* match largeData.summary.total == 100
```gherkin

### Feature-Level Report Control

Use tags to hide entire features from reports:

```gherkin
@report=false
Feature: Utility functions
# This entire feature is hidden from HTML reports

Scenario: Hidden utility
* def helper = function(x) { return x * 2 }
* def result = helper(5)
```gherkin

## Log Masking

### Masking Sensitive Data

Implement custom log masking for security:

```java
// DemoLogModifier.java
public class DemoLogModifier implements HttpLogModifier {

@Override
public boolean enableForUri(String uri) {
// Only activate for sensitive endpoints
return uri.contains("/auth") || uri.contains("/user");
}

@Override
public String request(String msg) {
// Mask passwords in requests
return msg.replaceAll("\"password\"\\s*:\\s*\"[^\"]+\"",
"\"password\":\"***MASKED***\"");
}

@Override
public String response(String msg) {
// Mask tokens in responses
return msg.replaceAll("\"token\"\\s*:\\s*\"[^\"]+\"",
"\"token\":\"***MASKED***\"");
}
}
```gherkin

Use the log modifier in tests:

```gherkin
Feature: Secure logging

Background:
* def LogModifier = Java.type('com.example.DemoLogModifier')
* configure logModifier = new LogModifier()

Scenario: Login with masked credentials
* url authUrl
* request { username: 'user@example.com', password: 'secret123' }
* method post
* status 200
# Password and token are masked in logs/reports
```gherkin

### Built-in Masking

Use configure to mask specific keys:

```gherkin
Background:
# Mask specific JSON keys
* configure mask = ['password', 'token', 'apiKey', 'secret']

Scenario: Automatic masking
* def sensitive = { password: 'secret', apiKey: 'key123' }
* print sensitive # Values are masked in output
```gherkin

## Dry Run Mode

### Validating Test Structure

Run tests without execution to validate structure:

```gherkin
Feature: Dry run validation

Background:
* configure dryRun = true

Scenario: Structure validation only
* url 'https://api.example.com' # Not executed
* method get # Syntax validated
* status 200 # Structure checked
* match response == { id: '#number' } # Syntax validated
```gherkin

### Programmatic Dry Run

Use dry run in Runner API:

```java
Results results = Runner.path("classpath:features")
.dryRun(true)
.parallel(1);

// Check for syntax errors
assertEquals(0, results.getFailCount());
```gherkin

## VS Code Debugging

### Setting Up Debug Configuration

Create `.vscode/launch.json`:

```json
{
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "Debug Karate Test",
"request": "launch",
"mainClass": "com.intuit.karate.Main",
"args": [
"classpath:features/debug-test.feature",
"--debug"
],
"projectName": "karate-tests"
}
]
}
```gherkin

### Breakpoint Debugging

Add breakpoints in JavaScript functions:

```gherkin
Feature: Debug with breakpoints

Background:
* def calculateTotal =
"""
function(items) {
var total = 0;
for (var i = 0; i < items.length; i++) {
// Set breakpoint here in VS Code
total += items[i].price * items[i].quantity;
}
return total;
}
"""

Scenario: Debug calculation
* def items = [
{ price: 10, quantity: 2 },
{ price: 5, quantity: 3 }
]
* def total = calculateTotal(items)
* match total == 35
```gherkin

### Interactive Debugging

Use karate.pause() for interactive debugging:

```gherkin
Scenario: Interactive debug session
* def data = { complex: 'structure' }
* karate.log('About to pause for debugging')
* eval karate.pause() # Execution pauses here
* print 'Continuing after pause'
```gherkin

## Performance Profiling

### Timing Analysis

Track execution time for performance debugging:

```gherkin
Feature: Performance profiling

Background:
* def timer =
"""
{
start: function(name) {
karate.set(name + '_start', new Date().getTime());
},
end: function(name) {
var start = karate.get(name + '_start');
var duration = new Date().getTime() - start;
karate.log(name + ' took ' + duration + 'ms');
return duration;
}
}
"""

Scenario: Profile API calls
* timer.start('auth')
* call read('auth.feature')
* def authTime = timer.end('auth')

* timer.start('data')
* url apiUrl + '/large-dataset'
* method get
* status 200
* def dataTime = timer.end('data')

* assert authTime < 1000 # Auth should be fast
* assert dataTime < 5000 # Data fetch within limits
```gherkin

### Memory Usage Tracking

Monitor memory consumption:

```gherkin
Background:
* def memory =
"""
function() {
var runtime = java.lang.Runtime.getRuntime();
var used = runtime.totalMemory() - runtime.freeMemory();
return Math.round(used / 1024 / 1024) + ' MB';
}
"""

Scenario: Memory monitoring
* def initialMemory = memory()
* print 'Initial memory:', initialMemory

# Load large dataset
* def largeData = read('classpath:large-file.json')

* def afterLoadMemory = memory()
* print 'After load:', afterLoadMemory

# Process data
* def processed = largeData.map(x => x.value * 2)

* def finalMemory = memory()
* print 'Final memory:', finalMemory
```gherkin

## Troubleshooting Common Issues

### Connection Issues

Debug connection problems:

```gherkin
Feature: Connection debugging

Background:
* configure connectTimeout = 30000
* configure readTimeout = 30000
* configure ssl = true
* configure logPrettyRequest = true
* configure logPrettyResponse = true

Scenario: Debug connection
* karate.log('Testing connection to:', apiUrl)
* url apiUrl + '/health'
* method get
* retry until responseStatus == 200 || karate.log('Retry attempt')
```gherkin

### JSON Path Issues

Debug JSONPath expressions:

```gherkin
Scenario: JSONPath debugging
* def data = read('complex.json')

# Debug JSONPath step by step
* def level1 = data.root
* print 'Level 1:', level1

* def level2 = level1.nested
* print 'Level 2:', level2

* def result = karate.jsonPath(data, '$.root.nested[?(@.active==true)]')
* print 'JSONPath result:', result
```gherkin

### Match Failures

Debug match expressions:

```gherkin
Scenario: Match debugging
* def actual = { id: 123, name: 'test', extra: 'field' }
* def expected = { id: '#number', name: '#string' }

# Use karate.match for detailed error info
* def matchResult = karate.match(actual, expected)
* if (!matchResult.pass) karate.log('Match failed:', matchResult.message)

# Alternative: use contains for partial matching
* match actual contains expected
```gherkin

## Best Practices

### Logging Strategy

```gherkin
# ✅ Good: Structured logging
Background:
* def log =
"""
{
info: function(msg, data) {
karate.log('[INFO]', msg, data || '');
},
error: function(msg, error) {
karate.log('[ERROR]', msg, error);
},
debug: function(msg, data) {
if (karate.properties['debug']) {
karate.log('[DEBUG]', msg, data || '');
}
}
}
"""

# ❌ Avoid: Excessive logging
* print request
* print response
* print headers
* print cookies
```gherkin

### Error Handling

```gherkin
# ✅ Good: Informative error messages
* if (response.error) karate.fail('API Error: ' + response.error.message + ' (Code: ' + response.error.code + ')')

# ❌ Avoid: Generic failures
* if (response.error) karate.fail('Error occurred')
```gherkin

## CI/CD Integration

### Pipeline-Friendly Logging

Configure logging for CI/CD environments:

```javascript
// karate-config.js
function fn() {
var config = {};

if (karate.env === 'ci') {
// Minimal logging in CI
karate.configure('printEnabled', false);
karate.configure('report', { showLog: false, showAllSteps: false });

// But capture failures
karate.configure('afterScenario', function() {
if (karate.info.errorMessage) {
karate.log('FAILED:', karate.info.scenarioName);
karate.log('ERROR:', karate.info.errorMessage);
}
});
}

return config;
}
```gherkin

## Next Steps

- Review [Hooks and Lifecycle](/docs/advanced/hooks) for test orchestration
- Explore [Parallel Execution](/docs/running-tests/parallel-execution) debugging
- Learn about [Test Reports](/docs/running-tests/parallel-execution#test-reports) configuration