Skip to main content

Notifications Plugin

ktestify-plugin-notifications is a first-party KTestify plugin that sends a rich test suite summary at the end of each run. It posts a colour-coded card to Microsoft Teams, Slack, or any HTTP webhook, with per-tag group breakdowns, CI/Git context, configurable success-rate thresholds, and fully user-overridable templates.

Work in progress

This plugin is actively being developed. The API surface, configuration keys, and template variables may evolve before the first stable release.


When to Use​

Use the Notifications plugin when your team,

  • Wants a summary card in Teams or Slack as soon as a test run finishes
  • Needs per-domain breakdowns grouped by Cucumber tag (e.g. @orders, @payments)
  • Runs tests in CI and wants branch, commit, build number, and pipeline links auto-attached
  • Needs to drive alerting based on the suite success rate
  • Wants to deliver results to a custom endpoint via a generic webhook or a custom channel

Installation​

Maven Dependency​

<dependency>
<groupId>io.github.ktestify</groupId>
<artifactId>ktestify-plugin-notifications</artifactId>
<version>0.1.1-SNAPSHOT</version>
<scope>test</scope>
</dependency>

With ktestify-cucumber (Docker)​

The Notification plugin is considered as a first-party plugin and is bundled in the ktestify-cucumber fat JAR. No extra steps are required to use it.


Configuration​

The plugin reads settings from ktestify.plugins.notifications in your HOCON configuration file. Place your configuration in application.conf or override via environment variables.

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}
}
]
}

Configuration Priority​

  1. System properties (e.g. -Dktestify.plugins.notifications.enabled=true)
  2. Environment variables (e.g. KTESTIFY_NOTIFICATIONS_ENABLED)
  3. application.conf in classpath
  4. Plugin's reference.conf (defaults)

Configuration Reference​

Top-level Keys​

KeyTypeDefaultEnv varDescription
enabledboolfalseKTESTIFY_NOTIFICATIONS_ENABLEDMaster on/off switch
on-failure-onlyboolfalseKTESTIFY_NOTIFICATIONS_ON_FAILURE_ONLYGlobal default; each channel can override

suite Block​

KeyDefaultEnv varDescription
name"ktestify Test Suite"KTESTIFY_NOTIFICATIONS_SUITE_NAMEDisplayed in the notification header
environment""KTESTIFY_ENVEnvironment label (e.g. "staging", "prod")
report-url""KTESTIFY_REPORT_URLLink to the published HTML/Allure report

thresholds Block​

The success rate drives the visual styling of the card.

KeyDefaultDescription
good75Success rate % at or above this value uses the "good" style (green)
warning50Success rate % at or above this value uses the "warning" style (yellow); below uses "attention" (red)

groups List​

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.

channels List​

Each entry configures one outbound channel. Common keys,

KeyDescription
typeChannel type, "teams", "slack", "webhook", or "log"
enabledWhether this channel fires
on-failure-onlyOnly notify when the suite fails (overrides the global default)
webhook-urlIncoming webhook URL (Teams / Slack)
urlEndpoint URL (webhook type)
methodHTTP method (webhook type, default "POST")
headersHTTP headers map (webhook type)
templateTemplate source, see Templates

Channels​

Microsoft Teams​

{
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.

Slack​

{
type = "slack"
enabled = true
webhook-url = ${?KTESTIFY_SLACK_WEBHOOK_URL}
on-failure-only = true # only notify on failures
template = "builtin"
}

The Block Kit message mirrors the Teams card, a header, a summary section, per-group blocks, and footer links.

Generic HTTP Webhook​

{
type = "webhook"
enabled = true
url = ${?KTESTIFY_WEBHOOK_URL}
method = "POST"
headers {
Content-Type = "application/json"
Authorization = "Bearer "${?KTESTIFY_WEBHOOK_TOKEN}
}
template = "builtin"
}

Log (debug / local dev)​

{
type = "log"
enabled = true
}

Zero external dependencies. Always available. Writes suite results and per-group breakdowns to the SLF4J logger at INFO.


Templates​

The notification payload for each channel is driven by a three-slot template system,

SlotVariableDescription
suiteouter card / message bodyRendered once per suite event
groupper-group sectionRendered once per tag group, injected via {{groupSections}}
footeraction buttons / linksRendered once, injected via {{footer}}

Resolution Order (highest to lowest priority)​

  1. Inline HOCON """...""" string in template.suite / template.group / template.footer
  2. Classpath resource, template = "classpath:my-card.json"
  3. Filesystem path, template = "/opt/config/teams-card.json"
  4. Bundled default, template = "builtin" (or omitted)

Inline Template Example​

{
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}}"}
]
}
"""
}
}

Template Variable Reference​

Suite-level variables​

VariableExample
{{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)

Group-level variables (template.group only)​

VariableExample
{{groupLabel}}"Order Service"
{{emoji}}"πŸ“¦"
{{tag}}"orders"
{{successRate}}"67"
{{style}}"warning"
{{passedCount}}"8"
{{failedCount}}"4"
{{totalCount}}"12"

Complete Configuration Example​

ktestify.plugins.notifications {
enabled = true

suite {
name = "Order Pipeline Tests"
environment = "staging"
report-url = ${?KTESTIFY_REPORT_URL}
}

thresholds {
good = 80
warning = 60
}

groups = [
{ tag = "orders", label = "Order Service", emoji = "πŸ“¦" },
{ tag = "payments", label = "Payment Service", emoji = "πŸ’³" }
]

channels = [
{
type = "teams"
enabled = true
webhook-url = ${?KTESTIFY_TEAMS_WEBHOOK_URL}
},
{
type = "slack"
enabled = true
webhook-url = ${?KTESTIFY_SLACK_WEBHOOK_URL}
on-failure-only = true
},
{
type = "log"
enabled = true
}
]
}

CI/CD Auto-detection​

Branch, commit, build number, and pipeline link are automatically extracted from the running environment, no manual configuration required. The plugin detects GitLab CI, GitHub Actions, CircleCI, Jenkins, and others via cucumber-cienvironment, then exposes the values through the {{ciName}}, {{pipelineUrl}}, {{buildNumber}}, {{gitBranch}}, {{gitRevision}}, and {{gitTag}} template variables.

When running outside a recognised CI system, these variables fall back to empty strings and the card still renders cleanly.


Implementation Structure​

The plugin follows the layered architecture defined by ktestify-core,

ktestify-plugin-notifications/
└── src/main/java/io/github/ktestify/notifications/
β”œβ”€β”€ NotificationsPlugin.java ← KtestifyPlugin SPI impl
β”œβ”€β”€ config/
β”‚ β”œβ”€β”€ NotificationsConfig.java ← reads ktestify.plugins.notifications HOCON
β”‚ β”œβ”€β”€ SuiteConfig.java ← suite block model
β”‚ β”œβ”€β”€ ThresholdsConfig.java ← success-rate thresholds
β”‚ β”œβ”€β”€ TagGroupConfig.java ← tag group definitions
β”‚ └── ChannelConfig.java ← per-channel settings
β”œβ”€β”€ hooks/
β”‚ └── NotificationHooks.java ← @Before / @After / @AfterAll glue
β”œβ”€β”€ model/
β”‚ β”œβ”€β”€ ScenarioEvent.java ← single scenario result
β”‚ β”œβ”€β”€ SuiteEvent.java ← aggregated suite result
β”‚ β”œβ”€β”€ GroupResult.java ← per-tag breakdown
β”‚ β”œβ”€β”€ CiContext.java ← CI metadata
β”‚ β”œβ”€β”€ GitContext.java ← Git metadata
β”‚ └── NotificationStatus.java ← PASSED / FAILED enum
β”œβ”€β”€ service/
β”‚ β”œβ”€β”€ ScenarioAggregator.java ← collects scenarios, builds SuiteEvent
β”‚ β”œβ”€β”€ CiContextResolver.java ← resolves CI/Git context
β”‚ └── NotificationService.java ← async dispatch + shutdown
β”œβ”€β”€ channel/
β”‚ β”œβ”€β”€ NotificationChannel.java ← channel SPI
β”‚ β”œβ”€β”€ NotificationChannelFactory.java ← builds channels from config
β”‚ └── impl/
β”‚ β”œβ”€β”€ LogNotificationChannel.java
β”‚ β”œβ”€β”€ TeamsNotificationChannel.java
β”‚ β”œβ”€β”€ SlackNotificationChannel.java
β”‚ └── WebhookNotificationChannel.java
└── template/
β”œβ”€β”€ NotificationTemplateEngine.java ← renders suite/group/footer
β”œβ”€β”€ NotificationTemplateResolver.java ← 4-level template lookup
└── BuiltinTemplates.java ← bundled fallback templates

Execution flow :

NotificationHooks (@Before / @After / @AfterAll)
β”‚
β”œβ”€ @Before β†’ records scenario start time
β”‚
β”œβ”€ @After β†’ ScenarioEvent.from(scenario, durationMs)
β”‚ └─ ScenarioAggregator.record(event)
β”‚
└─ @AfterAll β†’ CiContextResolver.resolve()
ScenarioAggregator.buildSuiteEvent(config, ci, git)
└─ tag grouping + success-rate + style computation
NotificationService.dispatch(suiteEvent)
└─ CompletableFuture.runAsync() per channel
NotificationService.shutdown(30s)

Key layers :

  • Aggregation, ScenarioAggregator collects each scenario result and builds the final SuiteEvent
  • Resolution, CiContextResolver extracts CI and Git metadata for the card
  • Dispatch, NotificationService fans out the event to each channel asynchronously
  • Rendering, NotificationTemplateEngine resolves and fills templates per channel

Custom Channels (SPI)​

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 }]

Custom channels registered in META-INF/services/…NotificationChannel are discovered automatically alongside the built-in ones.


Error Handling​

The plugin is designed to be failure-safe, a broken webhook must never fail a test run.

Channel Send Failure​

If a channel throws while sending, the exception is caught, logged at WARN, and swallowed,

WARN io.github.ktestify.notifications.service.NotificationService -
Channel 'teams' failed to send suite notification: 404 Not Found

The remaining channels still fire, and the test result is unaffected.

No Channel Configured​

If enabled = true but the channels list is empty (or every channel is disabled), nothing is sent and a single diagnostic line is logged. Add at least one enabled channel to receive notifications.

Notifications Not Arriving​

Check that,

  • enabled = true is set (the plugin is off by default)
  • At least one channel has enabled = true
  • The webhook-url (or url) resolves to a non-empty value
  • on-failure-only is not suppressing a passing run

Advanced Topics​

Failure-only Notifications​

Set on-failure-only = true globally, or per channel, to only notify when the suite fails,

ktestify.plugins.notifications {
enabled = true
on-failure-only = true # global default
channels = [
{ type = "teams", enabled = true, webhook-url = ${?KTESTIFY_TEAMS_WEBHOOK_URL} },
{ type = "log", enabled = true, on-failure-only = false } # always log
]
}

Threshold-driven Styling​

Tune thresholds.good and thresholds.warning to match your team's quality bar. The computed style (good, warning, attention) is exposed as {{overallStyle}} at the suite level and {{style}} per group, so custom templates can react to it.


Key Design Rules​

  • sendSuite() must never throw, exceptions are caught, logged at WARN, 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 and no threads are started

Troubleshooting​

IssueSolution
Plugin not discoveredEnsure the JAR is on the classpath or in /workspace/plugins, check logs for ServiceLoader errors
No notification sentVerify enabled = true and that at least one channel is enabled
Webhook returns 404 / 401Verify the webhook-url / url and any auth headers, regenerate the incoming webhook if needed
CI fields are emptyConfirm the run is inside a supported CI provider; locally these fields are blank by design
Card looks wrongValidate your inline template JSON, or fall back to template = "builtin"

Contributing​

The Notifications plugin is actively maintained. Report issues and contribute improvements at,

ktestify-plugin-notifications on GitHub


See Also​