Writing Concordion Specifications in HTML and Markdown
A Concordion specification is a readable document first and a test harness second. The commands embedded in the HTML are light enough that a product manager can understand the spec without knowing what they do. This post covers the full set of commands you will use day-to-day and shows how to switch to Markdown if HTML feels too heavy.
HTML Spec Structure
Every Concordion HTML file starts with the namespace declaration:
<html xmlns:concordion="http://www.concordion.org/2007/concordion">
<body>
<!-- spec content here -->
</body>
</html>Commands are attributes prefixed with concordion:. They can be applied to any HTML element. The element's text content provides the expected value or the input data depending on the command.
concordion:set
Assigns the element's text to a variable:
<p>
User <span concordion:set="#username">alice</span> submits a request.
</p>After this, #username holds the string "alice". Variables are scoped to the current example block.
concordion:assertEquals
Calls a fixture method and compares the result to the element's text:
<p>
The welcome message is
<span concordion:assertEquals="welcomeMessage(#username)">Welcome, alice!</span>.
</p>If welcomeMessage("alice") returns "Welcome, alice!", the element turns green. Any difference turns it red and shows the actual value.
concordion:execute
Used when you need to call a fixture method that performs an action but does not return a value you want to assert on:
<p concordion:execute="processOrder(#orderId)">
Processing order <span concordion:set="#orderId">ORD-001</span>.
</p>You can also use concordion:execute to capture a return value into a variable for later use:
<span concordion:execute="#result = calculate(#input)">ignored text</span>concordion:verifyRows
This command iterates over an HTML table and checks each row against items returned by a fixture method. It is the right tool when your spec asserts over a list of results.
Spec:
<table concordion:verifyRows="#product : productsInCategory('Electronics')">
<tr>
<th>Name</th>
<th>Price</th>
</tr>
<tr>
<td concordion:assertEquals="#product.name">Laptop</td>
<td concordion:assertEquals="#product.price">999</td>
</tr>
<tr>
<td concordion:assertEquals="#product.name">Phone</td>
<td concordion:assertEquals="#product.price">499</td>
</tr>
</table>Fixture:
public Iterable<Product> productsInCategory(String category) {
return productService.findByCategory(category);
}Concordion checks each row in order. Missing rows and extra rows both cause failures. Use this when order and completeness matter.
Parameterized Examples with Tables
For running the same assertion with multiple inputs, use a table with concordion:execute on the row element:
<table>
<tr>
<th>Input</th>
<th>Expected Output</th>
</tr>
<tr concordion:execute="#result = transform(#input)">
<td concordion:set="#input">hello</td>
<td concordion:assertEquals="#result">HELLO</td>
</tr>
<tr concordion:execute="#result = transform(#input)">
<td concordion:set="#input">world</td>
<td concordion:assertEquals="#result">WORLD</td>
</tr>
</table>Each row is an independent example. In the output report, each row shows its own pass/fail state.
Markdown Alternative
Concordion 2.x supports Markdown specs. The file extension changes to .md and you use a slightly different command syntax with inline attributes.
# Greeting Feature
When a user named [Bob](- '#name') logs in, the greeting is
[Hello, Bob!](- '?=greetingFor(#name)').The link text is the expected value or the variable value. The link target is the Concordion command. The ?= prefix means assertEquals. A bare #name in the target means set.
For execute: [ignored](- 'methodName(#var)').
For verifyRows in Markdown, you write a table with a header row that contains the command:
| Name | Price |
|-----------|-------|
| [Electronics](- '#category') products: | |
| [name][price] |
|---------------|
| Laptop | 999 |
| Phone | 499 |
[productsInCategory(#category)][name]: concordion:assertEquals="#product.name"
[productsInCategory(#category)][price]: concordion:assertEquals="#product.price"The Markdown syntax is less verbose for prose-heavy specs. For table-heavy specs, the HTML approach is easier to read and maintain.
Choosing Between HTML and Markdown
Use HTML when:
- Your spec has complex tables and multiple commands per element
- You want full control over styling in the output report
- Non-developers will be editing the spec in a WYSIWYG editor
Use Markdown when:
- The spec is mostly prose with a few inline assertions
- Your team writes documentation in Markdown and prefers consistency
- You want to render the spec in GitHub or a wiki without Concordion installed
Both formats produce the same annotated HTML output report after a test run.
Next Step
Once you are comfortable with the command set, look at concordion:matchStrategy for controlling how verifyRows handles ordering, and explore the Matchers class in your fixture for partial string matching in assertEquals.