Cross-platform test suite notifications for ktestify-cucumber. Sends a rich summary card to Microsoft Teams, Slack, or any HTTP webhook at the end of each test run, with per-tag group breakdowns, CI/Git context, configurable success-rate thresholds, and fully user-overridable templates.
- π£ Four built-in channels, Teams (Adaptive Card), Slack (Block Kit), generic HTTP webhook, and a zero-dependency log channel
- π·οΈ Tag-based scenario grouping, configurable groups (e.g.
@orders,@payments) each get their own success rate and visual style in the card - π€ CI/CD auto-detection, branch, commit, build number and pipeline link are automatically extracted from GitLab CI, GitHub Actions, CircleCI, Jenkins and others (via
cucumber-cienvironment) - π¨ Configurable thresholds, success rate drives visual card styling (
goodβ₯ 75 %,warningβ₯ 50 %,attention< 50 %) - π Fully overridable templates, use HOCON
"""..."""inline strings, a classpath resource, or a filesystem path; fall back to bundled defaults - π Extensible via SPI, custom channels registered in
META-INF/services/β¦NotificationChannelare discovered automatically - β‘ Async & failure-safe, notifications run in a background daemon thread pool; a failing webhook never fails a test
- π Zero-overhead when disabled, the plugin is
enabled = falseby default
<dependency>
<groupId>io.github.ktestify</groupId>
<artifactId>ktestify-plugin-notifications</artifactId>
<version>0.1.1-SNAPSHOT</version>
</dependency>The plugin is disabled by default. Activate it by setting enabled = true and configuring at least one channel:
ktestify.plugins.notifications {
enabled = true
enabled = ${?KTESTIFY_NOTIFICATIONS_ENABLED}
suite {
name = "My Integration Tests"
environment = ${?KTESTIFY_ENV} # e.g. "staging"
report-url = ${?KTESTIFY_REPORT_URL} # link to Allure / GitLab Pages report
}
channels = [
{
type = "teams"
enabled = true
webhook-url = ${?KTESTIFY_TEAMS_WEBHOOK_URL}
}
]
}That's all, the Cucumber hooks are discovered automatically via the KtestifyPlugin SPI. No @RunWith changes or glue package edits are needed.
| Key | Type | Default | Env var | Description |
|---|---|---|---|---|
enabled |
bool | false |
KTESTIFY_NOTIFICATIONS_ENABLED |
Master on/off switch |
on-failure-only |
bool | false |
KTESTIFY_NOTIFICATIONS_ON_FAILURE_ONLY |
Global default; each channel can override |
| Key | Default | Env var | Description |
|---|---|---|---|
name |
"ktestify Test Suite" |
KTESTIFY_NOTIFICATIONS_SUITE_NAME |
Displayed in the notification header |
environment |
"" |
KTESTIFY_ENV |
Environment label (e.g. "staging", "prod") |
report-url |
"" |
KTESTIFY_REPORT_URL |
Link to published HTML/Allure report |
| Key | Default | Description |
|---|---|---|
good |
75 |
Success rate % β₯ this β "good" style (green) |
warning |
50 |
Success rate % β₯ this β "warning" style (yellow); below β "attention" (red) |
Groups scenarios by Cucumber tag for a per-domain breakdown in the notification card.
groups = [
{ tag = "orders", label = "Order Service", emoji = "π¦" },
{ tag = "payments", label = "Payment Service", emoji = "π³" },
{ tag = "inventory",label = "Inventory", emoji = "π" }
]Scenarios that match no configured tag are placed in an automatic Untagged group.
Each entry configures one outbound channel. Common keys:
| Key | Description |
|---|---|
type |
Channel type: "teams", "slack", "webhook", "log" |
enabled |
Whether this channel fires |
on-failure-only |
Only notify when the suite fails (overrides global default) |
webhook-url |
Incoming webhook URL (Teams / Slack) |
url |
Endpoint URL (webhook type) |
method |
HTTP method (webhook type, default "POST") |
headers |
HTTP headers map (webhook type) |
template |
Template source, see Templates |
{
type = "teams"
enabled = true
webhook-url = ${?KTESTIFY_TEAMS_WEBHOOK_URL}
on-failure-only = false
template = "builtin"
}The Adaptive Card renders a colour-coded header (green / yellow / red), a fact set with CI/Git metadata, a section per tag group, and action buttons linking to the report and pipeline.
{
type = "slack"
enabled = true
webhook-url = ${?KTESTIFY_SLACK_WEBHOOK_URL}
on-failure-only = true # only notify on failures
template = "builtin"
}{
type = "webhook"
enabled = true
url = ${?KTESTIFY_WEBHOOK_URL}
method = "POST"
headers {
Content-Type = "application/json"
Authorization = "Bearer "${?KTESTIFY_WEBHOOK_TOKEN}
}
template = "builtin"
}{
type = "log"
enabled = true
}Zero external dependencies. Always available. Writes suite results and per-group breakdowns to the SLF4J logger at INFO.
The notification payload for each channel is driven by a three-slot template system:
| Slot | Variable | Description |
|---|---|---|
suite |
outer card / message body | Rendered once per suite event |
group |
per-group section | Rendered once per tag group, injected via {{groupSections}} |
footer |
action buttons / links | Rendered once, injected via {{footer}} |
- Inline HOCON
"""..."""string intemplate.suite/template.group/template.footer - Classpath resource,
template = "classpath:my-card.json" - Filesystem path,
template = "/opt/config/teams-card.json" - Bundled default,
template = "builtin"(or omitted)
{
type = "teams"
enabled = true
webhook-url = ${?KTESTIFY_TEAMS_WEBHOOK_URL}
template {
suite = """
{
"type": "AdaptiveCard",
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.4",
"body": [
{
"type": "TextBlock",
"text": "π§ͺ **{{suiteName}}**, {{environment}}, {{date}}",
"weight": "Bolder",
"size": "Large"
},
{
"type": "FactSet",
"facts": [
{"title": "Branch", "value": "{{gitBranch}}"},
{"title": "Passed", "value": "{{passedCount}}/{{totalCount}}"},
{"title": "Success", "value": "{{successRate}}%"}
]
},
{{groupSections}}{{footer}}
]
}
"""
group = """
{
"type": "Container",
"style": "{{style}}",
"items": [{"type": "TextBlock", "text": "{{emoji}} **{{groupLabel}}**, {{successRate}}%"}]
}
"""
footer = """
{
"type": "ActionSet",
"actions": [
{"type": "Action.OpenUrl", "title": "π Report", "url": "{{reportUrl}}"},
{"type": "Action.OpenUrl", "title": "π Pipeline", "url": "{{pipelineUrl}}"}
]
}
"""
}
}| Variable | Example |
|---|---|
{{suiteName}} |
"My Integration Tests" |
{{environment}} |
"staging" |
{{date}} |
"2026-06-06" |
{{status}} |
"PASSED" / "FAILED" |
{{overallStyle}} |
"good" / "warning" / "attention" |
{{totalCount}} |
"42" |
{{passedCount}} |
"38" |
{{failedCount}} |
"4" |
{{skippedCount}} |
"0" |
{{successRate}} |
"90" |
{{reportUrl}} |
"https://pages.gitlab.io/β¦" |
{{ciName}} |
"GitHub Actions" |
{{pipelineUrl}} |
"https://github.com/β¦/runs/123" |
{{buildNumber}} |
"456" |
{{gitBranch}} |
"main" |
{{gitRevision}} |
"a1b2c3d" |
{{gitTag}} |
"v1.4.0" |
{{groupSections}} |
(rendered group fragments, comma-joined) |
{{footer}} |
(rendered footer fragment) |
| Variable | Example |
|---|---|
{{groupLabel}} |
"Order Service" |
{{emoji}} |
"π¦" |
{{tag}} |
"orders" |
{{successRate}} |
"67" |
{{style}} |
"warning" |
{{passedCount}} |
"8" |
{{failedCount}} |
"4" |
{{totalCount}} |
"12" |
Implement NotificationChannel and register it via the Java SPI:
// com.example.MyPagerDutyChannel.java
public class MyPagerDutyChannel implements NotificationChannel {
@Override public String getType() { return "pagerduty"; }
@Override
public void sendSuite(SuiteEvent event) {
// send a PagerDuty alert if event.getStatus() == FAILED
}
}# META-INF/services/io.github.ktestify.notifications.channel.NotificationChannel
com.example.MyPagerDutyChannel
Then reference it in your config:
channels = [{ type = "pagerduty", enabled = true }]NotificationHooks (@Before / @After / @AfterAll)
β
ββ @Before β records scenario start time
β
ββ @After β ScenarioEvent.from(scenario, durationMs)
β ββ ScenarioAggregator.record(event)
β ββ NotificationService.dispatch(scenarioEvent) [no-op by default]
β
ββ @AfterAll β CiContextResolver.resolve()
ScenarioAggregator.buildSuiteEvent(config, ci, git)
ββ tag grouping + success-rate + style computation
NotificationService.dispatch(suiteEvent)
ββ CompletableFuture.runAsync() per channel
NotificationService.shutdown(30s)
NotificationService
ββ List<NotificationChannel> β built by NotificationChannelFactory
ββ LogNotificationChannel
ββ TeamsNotificationChannel βββΊ NotificationTemplateEngine.renderSuite()
ββ SlackNotificationChannel β ββ NotificationTemplateResolver (4-level lookup)
ββ WebhookNotificationChannelβ ββ BuiltinTemplates (fallback)
βββΊ HttpClient.send(POST)
sendSuite()must never throw, exceptions are caught, logged atWARN, and swallowed- Notifications are async, test execution is never blocked by a slow webhook
- The plugin has zero overhead when disabled, no channels are instantiated, no threads started
# Compile
mvn compile
# Unit tests (no Docker required)
mvn test
# Integration tests (requires Docker)
mvn verify
# Code formatting
mvn spotless:applyApache License 2.0, see LICENSE.
