Concordion Extensions: Screenshots, Logging, Excel
Concordion ships with a small set of extensions that add screenshots, structured logging, and alternative spec formats to your output reports. You can also write your own. This post covers the four you will reach for most often.
The @Extensions Annotation
Extensions are declared on the fixture class using @Extensions. The annotation accepts one or more extension classes:
import org.concordion.api.extension.Extensions;
import org.concordion.ext.ScreenshotExtension;
import org.concordion.ext.LoggingTooltipExtension;
@RunWith(ConcordionRunner.class)
@Extensions({ScreenshotExtension.class, LoggingTooltipExtension.class})
public class CheckoutTest {
// fixture methods
}Extensions that require configuration can be declared as fields with @Extension (singular) instead:
@RunWith(ConcordionRunner.class)
public class CheckoutTest {
@Extension
private final ScreenshotExtension screenshot =
new ScreenshotExtension().setScreenshotTaker(new AwtScreenshotTaker());
// fixture methods
}The field-level @Extension approach lets you pass constructor arguments and configure the extension before the test runs.
ScreenshotExtension
The ScreenshotExtension captures a screenshot when an assertion fails and embeds it directly in the HTML output report. No separate artifact collection step needed.
Add the dependency:
<dependency>
<groupId>org.concordion</groupId>
<artifactId>concordion-screenshot-extension</artifactId>
<version>1.1.2</version>
<scope>test</scope>
</dependency>Configure it with a screenshot taker. For Selenium WebDriver:
import org.concordion.ext.ScreenshotExtension;
import org.concordion.ext.selenium.SeleniumScreenshotTaker;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
@RunWith(ConcordionRunner.class)
public class LoginTest {
private WebDriver driver = new ChromeDriver();
@Extension
private final ScreenshotExtension screenshot =
new ScreenshotExtension()
.setScreenshotTaker(new SeleniumScreenshotTaker(driver))
.setScreenshotOnAssertionFailure(true)
.setScreenshotOnAssertionSuccess(false);
public String loginAs(String username, String password) {
driver.get("http://localhost:8080/login");
driver.findElement(By.id("username")).sendKeys(username);
driver.findElement(By.id("password")).sendKeys(password);
driver.findElement(By.id("submit")).click();
return driver.findElement(By.id("welcome-message")).getText();
}
}When loginAs returns an unexpected value, the extension captures the current browser state and adds it to the report. The screenshot appears inline next to the failing assertion.
LoggingTooltipExtension
This extension intercepts Java logging calls and displays them as tooltips on the corresponding spec elements in the output report. It is useful for debugging failures without adding print statements.
<dependency>
<groupId>org.concordion</groupId>
<artifactId>concordion-logging-tooltip-extension</artifactId>
<version>1.1.1</version>
<scope>test</scope>
</dependency>import org.concordion.ext.LoggingTooltipExtension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@RunWith(ConcordionRunner.class)
@Extensions(LoggingTooltipExtension.class)
public class PaymentTest {
private static final Logger log = LoggerFactory.getLogger(PaymentTest.class);
public String processPayment(String cardNumber, double amount) {
log.info("Processing payment of {} for card ending in {}", amount, cardNumber.substring(cardNumber.length() - 4));
PaymentResult result = paymentGateway.charge(cardNumber, amount);
log.info("Payment result: {}", result.getStatus());
return result.getStatus();
}
}In the output report, hover over any spec element that triggered logging and the log messages appear as a tooltip. This gives you a timeline of what happened during each assertion without cluttering the spec document itself.
ExcelExtension
The ExcelExtension lets you write Concordion specs in Excel spreadsheets instead of HTML or Markdown. This is useful when business analysts maintain test data in Excel and want to own the spec format directly.
<dependency>
<groupId>org.concordion</groupId>
<artifactId>concordion-excel-extension</artifactId>
<version>1.0.0</version>
<scope>test</scope>
</dependency>The spec file becomes a .xlsx file with the same name as the fixture class. Concordion commands go in cell comments rather than HTML attributes. The cell value serves as the expected value or input, and the comment specifies the command:
- Cell A1 value:
alice, comment:concordion:set #username - Cell B1 value:
Welcome, alice!, comment:concordion:assertEquals welcomeMessage(#username)
The Excel output report annotates cells green or red based on assertion results, which makes it easy for business analysts to review failures in a format they are already comfortable with.
Writing a Custom Extension
Custom extensions implement the ConcordionExtension interface. The interface has one method: addTo(ConcordionExtender extender). You use the extender to register listeners for events in the test lifecycle.
Here is an extension that records the timestamp of each assertion failure to a file:
import org.concordion.api.extension.ConcordionExtension;
import org.concordion.api.extension.ConcordionExtender;
import org.concordion.api.listener.AssertFailureEvent;
import org.concordion.api.listener.AssertEqualsListener;
import java.io.FileWriter;
import java.io.IOException;
import java.time.Instant;
public class FailureLogExtension implements ConcordionExtension, AssertEqualsListener {
private final String logFile;
public FailureLogExtension(String logFile) {
this.logFile = logFile;
}
@Override
public void addTo(ConcordionExtender extender) {
extender.withAssertEqualsListener(this);
}
@Override
public void failureReported(AssertFailureEvent event) {
try (FileWriter fw = new FileWriter(logFile, true)) {
fw.write(Instant.now() + " FAIL: expected=" + event.getExpected()
+ " actual=" + event.getActual() + "\n");
} catch (IOException e) {
// non-fatal
}
}
@Override
public void successReported(AssertSuccessEvent event) {
// not interested
}
}Declare it on the fixture:
@RunWith(ConcordionRunner.class)
public class SalesTest {
@Extension
private final FailureLogExtension failureLog =
new FailureLogExtension("/tmp/concordion-failures.log");
}The extension API exposes listeners for every stage of the test lifecycle: spec parsing, example start and end, assertion success and failure, and spec completion. Use the appropriate listener interface for the hook you need.
Next Step
Once extensions are in place, look at how the ScreenshotExtension integrates with a Selenium Grid setup for parallel test runs. The extension is thread-safe by default when you use field-level @Extension per fixture instance.