#
XML Assertions
This comprehensive guide explains how to write XML assertions for testing XML responses in Kusho using the new xmlAssert
API.
NOTE: This is available only on our Enterprise plan for select users. If you're interested in enabling this, please reach out to us at enterprise@kusho.ai or your RM.
#
Overview
Kusho now supports dual assertion systems:
- Traditional Chai assertions - For JSON responses (existing functionality, unchanged)
- New xmlAssert functions - For XML responses with XPath support
The system automatically detects assertion type and routes to appropriate handlers, ensuring 100% backward compatibility.
#
Assertion Processing Model
Important: Assertions are processed line by line. Each line is treated as a separate, independent assertion.
#
Assertion Type Detection
- xmlAssert assertions: Lines starting with
xmlAssert.
- routed to XML-specific handler - Chai assertions: All other lines - routed to traditional Chai handler (unchanged)
#
Line-by-Line Processing
// Each line below is processed as a separate assertion:
expect(response.status).to.equal(200) // Line 1: Chai assertion
xmlAssert.assertXPath(response.response, '//catalog', null, 'exists') // Line 2: XML assertion
expect(response.headers['content-type']).to.include('xml') // Line 3: Chai assertion
xmlAssert.assertXPath(response.response, '//product', 2, 'count') // Line 4: XML assertion
#
XML Assertion API
#
Core Function
#
xmlAssert.assertXPath(xmlString, xpathExpression, expectedValue, assertionType)
Purpose: Validate XPath results with automatic error throwing Parameters:
xmlString
: XML content (usuallyresponse.response
)xpathExpression
: XPath query stringexpectedValue
: Expected result (ornull
for existence checks)assertionType
: Type of assertion ('exists'
,'count'
,'text'
,'value'
)
#
Assertion Types
#
1. Existence Assertions ('exists'
)
Purpose: Verify that elements exist in XML
// Check if catalog element exists
xmlAssert.assertXPath(response.response, '//catalog', null, 'exists')
// Check if products with specific attributes exist
xmlAssert.assertXPath(response.response, '//product[@category="electronics"]', null, 'exists')
// Check if nested elements exist
xmlAssert.assertXPath(response.response, '//product/specifications/weight', null, 'exists')
#
2. Count Assertions ('count'
)
Purpose: Validate exact number of matching elements
// Verify total product count
xmlAssert.assertXPath(response.response, '//product', 5, 'count')
// Count products in specific category
xmlAssert.assertXPath(response.response, '//product[@category="electronics"]', 3, 'count')
// Count elements with specific text content
xmlAssert.assertXPath(response.response, '//product[price > 1000]', 2, 'count')
#
3. Text Content Assertions ('text'
)
Purpose: Validate text content of elements
// Check product name
xmlAssert.assertXPath(response.response, '//product[@id="1"]/name', 'iPhone 14', 'text')
// Verify category text
xmlAssert.assertXPath(response.response, '//product[1]/@category', 'electronics', 'text')
// Check nested text content
xmlAssert.assertXPath(response.response, '//catalog/description', 'Electronics Catalog', 'text')
#
4. Value Assertions ('value'
)
Purpose: Validate element values (supports both string and numeric comparisons)
// String value assertions
xmlAssert.assertXPath(response.response, '//product/status', 'active', 'value')
// Numeric value assertions
xmlAssert.assertXPath(response.response, '//product/price', 999.99, 'value')
xmlAssert.assertXPath(response.response, '//product/stock', 25, 'value')
// ID value assertions
xmlAssert.assertXPath(response.response, '//product/@id', '12345', 'value')
#
Advanced XPath Patterns
#
Element Selection
// Select by tag name
xmlAssert.assertXPath(response.response, '//product', null, 'exists')
// Select by attribute
xmlAssert.assertXPath(response.response, '//product[@id="123"]', null, 'exists')
// Select by position
xmlAssert.assertXPath(response.response, '//product[1]', null, 'exists') // First product
xmlAssert.assertXPath(response.response, '//product[last()]', null, 'exists') // Last product
// Select by text content
xmlAssert.assertXPath(response.response, '//product[name="iPhone 14"]', null, 'exists')
#
Attribute Queries
// Check attribute existence
xmlAssert.assertXPath(response.response, '//product[@category]', null, 'exists')
// Check attribute values
xmlAssert.assertXPath(response.response, '//product/@category', 'electronics', 'text')
xmlAssert.assertXPath(response.response, '//product[@id="1"]/@category', 'electronics', 'value')
// Count elements with specific attributes
xmlAssert.assertXPath(response.response, '//product[@available="true"]', 3, 'count')
#
Conditional Expressions
// Elements with numeric conditions
xmlAssert.assertXPath(response.response, '//product[price > 500]', 4, 'count')
xmlAssert.assertXPath(response.response, '//product[stock < 10]', 2, 'count')
// Multiple conditions (AND)
xmlAssert.assertXPath(response.response, '//product[@category="electronics" and price > 1000]', 1, 'count')
// Text contains conditions
xmlAssert.assertXPath(response.response, '//product[contains(name, "iPhone")]', 3, 'count')
// Position-based conditions
xmlAssert.assertXPath(response.response, '//product[position() <= 3]', 3, 'count')
#
Hierarchical Navigation
// Parent-child relationships
xmlAssert.assertXPath(response.response, '//catalog/products/product', 5, 'count')
// Descendant selection
xmlAssert.assertXPath(response.response, '//catalog//product', 5, 'count')
// Following sibling
xmlAssert.assertXPath(response.response, '//product[1]/following-sibling::product', 4, 'count')
// Ancestor selection
xmlAssert.assertXPath(response.response, '//name/ancestor::product', null, 'exists')
#
Complex Query Examples
#
E-commerce XML Response
<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<metadata>
<total>5</total>
<category>Electronics</category>
</metadata>
<products>
<product id="1" category="electronics" available="true">
<name>iPhone 14</name>
<price currency="USD">999.99</price>
<stock>25</stock>
<specifications>
<weight unit="g">172</weight>
<color>Blue</color>
</specifications>
</product>
<product id="2" category="electronics" available="true">
<name>MacBook Pro</name>
<price currency="USD">1999.99</price>
<stock>10</stock>
<specifications>
<weight unit="kg">1.4</weight>
<color>Silver</color>
</specifications>
</product>
</products>
</catalog>
#
Comprehensive Test Suite
Note: Each line below is processed as an independent assertion:
// Basic structure validation - each line is a separate assertion
xmlAssert.assertXPath(response.response, '//catalog', null, 'exists')
xmlAssert.assertXPath(response.response, '//products', null, 'exists')
xmlAssert.assertXPath(response.response, '//metadata', null, 'exists')
// Count validations - independent line-by-line processing
xmlAssert.assertXPath(response.response, '//product', 2, 'count')
xmlAssert.assertXPath(response.response, '//product[@available="true"]', 2, 'count')
// Metadata assertions - each line evaluated separately
xmlAssert.assertXPath(response.response, '//metadata/total', '5', 'text')
xmlAssert.assertXPath(response.response, '//metadata/category', 'Electronics', 'text')
// Product-specific assertions - line-by-line evaluation
xmlAssert.assertXPath(response.response, '//product[@id="1"]/name', 'iPhone 14', 'text')
xmlAssert.assertXPath(response.response, '//product[@id="1"]/price', 999.99, 'value')
xmlAssert.assertXPath(response.response, '//product[@id="2"]/stock', 10, 'value')
// Attribute validations - independent assertions
xmlAssert.assertXPath(response.response, '//product[@id="1"]/@category', 'electronics', 'value')
xmlAssert.assertXPath(response.response, '//price/@currency', 'USD', 'text')
// Nested element assertions - each line is separate
xmlAssert.assertXPath(response.response, '//product[@id="1"]/specifications/weight', '172', 'text')
xmlAssert.assertXPath(response.response, '//specifications/color', 'Blue', 'text')
// Complex queries - single-line XPath expressions
xmlAssert.assertXPath(response.response, '//product[price > 1500]', 1, 'count')
xmlAssert.assertXPath(response.response, '//product[contains(name, "iPhone")]', 1, 'count')
xmlAssert.assertXPath(response.response, '//product[specifications/weight/@unit="kg"]', 1, 'count')
#
Advanced Usage Patterns
#
Single-Line Assertions Only
Important: Each assertion must be a complete statement on one line. Multi-line logic is not supported.
// ✅ CORRECT - Each line is a complete assertion
xmlAssert.assertXPath(response.response, '//product', 2, 'count')
xmlAssert.assertXPath(response.response, '//product[@id="1"]/name', 'iPhone 14', 'text')
expect(response.status).to.equal(200)
// ❌ INCORRECT - Multi-line logic not supported
const result = xmlAssert.xpath(response.response, '//product/price')
if (result.exists) {
// This won't work as expected
}
// ❌ INCORRECT - Variable assignments across lines not supported
const productCount = xmlAssert.xpath(response.response, '//product').count
xmlAssert.assertXPath(response.response, '//metadata/total', productCount, 'value')
#
Working with Single Lines
// ✅ CORRECT - Direct assertions on single lines
xmlAssert.assertXPath(response.response, '//product[price > 500]', 3, 'count')
xmlAssert.assertXPath(response.response, '//product[contains(name, "iPhone")]', 1, 'count')
// ✅ CORRECT - Use XPath expressions for complex logic instead of JavaScript
xmlAssert.assertXPath(response.response, '//product[specifications/weight/@unit="kg"]', 1, 'count')
xmlAssert.assertXPath(response.response, '//product[@category="electronics" and price > 1000]', 1, 'count')
#
Error Handling
Important: Multi-line error handling logic is not supported due to line-by-line processing. Use single-line assertions only.
// ✅ CORRECT - Each assertion is independent and self-contained
xmlAssert.assertXPath(response.response, '//product', 2, 'count')
xmlAssert.assertXPath(response.response, '//required-field', null, 'exists')
// ❌ INCORRECT - Try-catch blocks don't work across line processing
try {
xmlAssert.assertXPath(response.response, '//product', 5, 'count') // Won't work as expected
} catch (error) {
// This logic won't execute properly
}
// ❌ INCORRECT - Conditional logic across lines not supported
const result = xmlAssert.xpath(response.response, '//optional-field')
if (result.exists) {
// This won't work due to line-by-line processing
}
// ✅ CORRECT - Use XPath conditional expressions instead
xmlAssert.assertXPath(response.response, '//element[condition]', expectedValue, 'assertion_type')
#
Backward Compatibility
#
Existing Chai Assertions (Unchanged)
Your existing JSON assertions continue to work exactly as before:
// These work unchanged - routed to traditional Chai handler
expect(response.status).to.equal(200)
expect(response.data).to.have.property('products')
expect(response.data.products).to.have.length(5)
expect(response.data.products[0]).to.have.property('name')
expect(response.data.products[0].name).to.equal('iPhone 14')
#
Mixed Assertion Types
You can use both assertion types in the same test case. Each line is processed independently:
// Each line below is processed as a separate assertion
expect(response.status).to.equal(200) // Line 1: Chai
expect(response.headers['content-type']).to.include('xml') // Line 2: Chai
xmlAssert.assertXPath(response.response, '//catalog', null, 'exists') // Line 3: xmlAssert
xmlAssert.assertXPath(response.response, '//product', 2, 'count') // Line 4: xmlAssert
expect(response.data).to.have.property('timestamp') // Line 5: Chai
#
Best Practices
#
1. Use Specific XPath Expressions
// Good - specific and targeted
xmlAssert.assertXPath(response.response, '//product[@id="1"]/name', 'iPhone 14', 'text')
// Avoid - too broad and potentially fragile
xmlAssert.assertXPath(response.response, '//*[text()="iPhone 14"]', null, 'exists')
#
2. Test Structure Before Content
// First verify structure exists
xmlAssert.assertXPath(response.response, '//products', null, 'exists')
xmlAssert.assertXPath(response.response, '//product', 5, 'count')
// Then test specific content
xmlAssert.assertXPath(response.response, '//product[1]/name', 'iPhone 14', 'text')
#
3. Handle Edge Cases
// Check for empty results
const result = xmlAssert.xpath(response.response, '//optional-element')
if (result.count === 0) {
console.log('Optional element not present - this is acceptable')
} else {
xmlAssert.assertXPath(response.response, '//optional-element', 'expected-value', 'text')
}
#
4. Use Appropriate Assertion Types
// Use 'exists' for presence checks
xmlAssert.assertXPath(response.response, '//required-field', null, 'exists')
// Use 'count' for quantity validation
xmlAssert.assertXPath(response.response, '//item', 10, 'count')
// Use 'text' for exact text matching
xmlAssert.assertXPath(response.response, '//status', 'active', 'text')
// Use 'value' for numeric or flexible comparisons
xmlAssert.assertXPath(response.response, '//price', 99.99, 'value')
#
Troubleshooting
#
Common Issues
1. XPath Expression Errors
- Verify XPath syntax using browser dev tools
- Test expressions in XML validators
- Use browser's
document.evaluate()
to debug
2. Namespace Issues
- XML with namespaces requires namespace-aware XPath
- Consider using local-name() for namespace-agnostic queries
3. Text vs Value Confusion
- Use 'text' for element text content
- Use 'value' for attribute values and numeric comparisons
4. Case Sensitivity
- XML is case-sensitive
- Element names, attribute names, and values must match exactly
#
Error Messages
The system provides descriptive error messages:
XPath '//product' should exist but no matching nodes found
XPath '//product' should match 5 nodes but got 3
XPath '//product/name' should have text 'iPhone 14' but got 'iPhone 13'
#
Limitations
- XPath 1.0 Only: Browser limitations restrict to XPath 1.0 syntax
- Case Sensitive: All XML elements and attributes are case-sensitive
- First Node Priority: Text and value extractions use first matching node only
- No Namespace Prefixes: Complex namespace handling not supported by default
#
Migration Guide
To add XML assertions to existing test cases:
- Keep existing assertions unchanged - they continue to work
- Add xmlAssert assertions for XML-specific validations
- Start with existence checks, then add specific validations
- Test thoroughly to ensure both assertion types work together
This comprehensive system ensures robust XML testing capabilities while maintaining complete backward compatibility with existing Chai-based assertions.