Tuesday, 9 September 2025

Create Email Body In Jenkins Using Editable Email Notification Plugin

 Problem Statement - 

After publishing test result in Jenkin's post build action any xunit plugin such as publish Junit/TestNG restult. One may want to create simple mail to stake holders quantifying test execution status. 

Email body may be typically in html format with rows and columns with columns for total tests/pass/fail/skip...

Solution #1 - Use of Default Content (Simple Information Without Test Count Status - Total/Pass/Fail/Skip) -

Challenge with default content approach - 

While default content can write Job/build/Jenkins URL seamlessly, it struggles to get details regarding test success count (Total/Pass/Fail/Skip).

content of default content can be like below - 

"""

$JOB_NAME ($BUILD_NUMBER) - $BUILD_URL

<!DOCTYPE html>

<html>

<head>

<style>

table {

  font-family: arial, sans-serif;

  border-collapse: collapse;

  width: 100%;

}

td, th {

  border: 1px solid #dddddd;

  text-align: left;

  padding: 8px;

}

tr:nth-child(even) {

  background-color: #dddddd;

}

</style>

</head>

<body>

<h3>$JOB_NAME - $BUILD_NUMBER - $BUILD_STATUS!</h3>

<table>

  <tr>

    <th>Job Name</th>

    <th>Build Number</th>

    <th>Jenkins URL</th>

  </tr>

  <tr>

    <td>$JOB_NAME</td>

    <td>$BUILD_NUMBER</td>

    <td>$BUILD_URL</td>

  </tr>

</table>

</body>

</html>

"""

Solution #2 - Use Of Pre-Send Script (Under Advanced Section Of Editable Email Notificaction) to read job/build/test status programmatically using Jenkins hudson library)

Following groovy script has to be added - 

/***
Challenge - Since, script interacts with hudson library Jenkins restricts accessing hudson layer directly. 
Script and various hudson methods have to be explicitly approved in Jenkins in-process Script Approval plugin

***/

def testResult = build.getAction(hudson.tasks.junit.TestResultAction.class)
def totalCount = "NA", failCount = "NA", skipCount = "NA", passCount = "NA", fail_per = "NA", skip_per = "NA", pass_per = "NA"
def job = build.getProject().getName()
def build_number = build.getNumber()
def build_status = build.getResult()
def build_url = build.getUrl()

if (testResult != null) {
   logger.println("test result found")
    totalCount = testResult.getTotalCount()
    failCount = testResult.getFailCount()
    skipCount = testResult.getSkipCount()
    passCount = totalCount - failCount - skipCount
    
    fail_per = (totalCount > 0) ? ((failCount / totalCount) * 100).intValue() : 0
    skip_per = (totalCount > 0) ? ((skipCount / totalCount) * 100).intValue() : 0
    pass_per = 100 - (fail_per + skip_per)

   
    
    def failed_tests_refs_rows = ""
    def text_to_strip = "Ubicquia_Tests.Test_Components.Framework_Update."
    def counter = 0
    if(failCount>0) {
     logger.println("---------------------------------------------------------------Failed Tests Details For ${failCount} Tests Are Printed Below---------------------------------------------------------------------------------------------------------------:")
    def failedTests = testResult.getFailedTests()
        failedTests.each { test ->
        counter = counter + 1        
        def name = test.getName() // Test method name
        def className = test.getClassName() // Fully qualified class name
        className = className - text_to_strip
        def errorDetails = test.getErrorDetails() // Error message
        logger.println("${counter } -> Test Fails -> ${className}  \nError Details -> ${errorDetails}")
        def row_background = (counter %2 ==0)? "style='background-color: #e6f3ff;'": "" //set light blue for alternate row starting from
        failed_tests_refs_rows = failed_tests_refs_rows + "<tr ${row_background}><td>${counter}</td><td>${name}</td><td style='text-align: left;'>${className}</td><td style='text-align: left;'>${errorDetails}</td></tr>"
    }
 logger.println("---------------------------------------------------------------Failed Tests Details For ${failCount} Tests Ends---------------------------------------------------------------------------------------------------------------:")
}

    def summary = """

<!DOCTYPE html>
<html>
<head>
<style>
table {
  font-family: arial, sans-serif;
  border-collapse: collapse;
  width: 100%;
}

td, th {
  border: 2px solid #dddddd;
  text-align: center;
  vertical-align: middle;
  padding: 8px;
}


</style>
</head>
<body>

<table>
<table>
<tr><td><h3>${job} - ${build_number} - ${build_status}</h3></td></tr>
</table>

<table>
  <tr><th>Jenkins URL</th><th>Total Tests</th><th>Total Pass</th><th>Total Fail</th><th>Total Skip</th></tr>
  <tr><td style='text-align: left;'><a href='${build_url}'>${build_url}</td><td>${totalCount}</td><td>${passCount} ( ${pass_per}% )</td><td>${failCount} ( ${fail_per}% )</td><td>${skipCount} ( ${skip_per}% )</td></tr>
</table>

<table class='details-table'>
<tr><th>Serial Number</th><th>Failed Tests</th><th>Class Name</th><th>Error Details</th></tr>${failed_tests_refs_rows}

</table>
</table>
</body>
</html>
   """.toString()
    
msg.setContent(summary, "text/html")
   
}
else{
logger.println("test result not found")
}
 logger.println("Test results: Total Test =${totalCount}, Total Passed=${passCount} (${pass_per}%), Total Failed=${failCount} (${fail_per}%), Total Skipped = ${skipCount} (${skip_per}%)")

Solution #3 - Using Macro/Environment Variable Set By test is published. (Yet To Be Verified For Test Counts...)


Job Name: $JOB_NAME
Build Number: $BUILD_NUMBER
Build URL: $BUILD_URL
Total Tests: ${TEST_COUNTS,var="total"}
Passed: ${TEST_COUNTS,var="pass"}
Failed: ${TEST_COUNTS,var="fail"}
Skipped: ${TEST_COUNTS,var="skip"}

No comments:

Post a Comment