Skip to content

Instantly share code, notes, and snippets.

@matthias-dirickx
Last active April 12, 2022 11:09
Show Gist options
  • Save matthias-dirickx/0cb7e1a86e9cdcd6d5ce4cd81365afcd to your computer and use it in GitHub Desktop.
Save matthias-dirickx/0cb7e1a86e9cdcd6d5ce4cd81365afcd to your computer and use it in GitHub Desktop.
SoapUI HTML report
/**
@Author : Matthias Dirickx
@Description : Collecting statuses and copying them to a file - based on the 'set endpoint' script from Pradeep Bishnoi
https://learnsoapui.wordpress.com/2012/04/27/want-to-change-endpoInteger-url-in-a-projecttestsuitestestcases/
* This script retrieves the static statuses from all items in a project and then exports those results to a simple html file.
* There is no extra css file or other stuff included. So you can happily pass around the html without worrying about making available additional resources.
*Assumptions:
* - there is a variable testDataPath
* - The variable testDataPath is set as JAVA String (backslashes are doubled because of escape function)
* - The path ends with a double backslash
* - Alternatively you can just use forward slashes of course. And then you normally don't have to double them.
* - The path results are written to the 'Execution Results' folder
* - Name of file is set automatically to soapUIProjectName+date time stamp
* - When all steps are passed, the test case is VALID
* - When one step fails, the test case is set to FAILED
* - In any other case the test case result is set to UNKNOWN
* - There is a count summary. Items that are disabled are not taken into account for the totals of this statistic.
@Usage : Make the script available through a SoapUi Interface implementation or put it in a project tear-down script.
Alternatively you may add it to a SoapUI custom action.
Example implementation: https://fuckagile.wordpress.com/2016/08/17/soapui-action-architecture-and-the-groovy-script-engine/
*/
import java.awt.Desktop;
import java.lang.Math;
import com.eviware.soapui.support.UISupport;
/*Properties
--> wrap in function because otherwise soapui complains about variable not found in class.
*/
private String getHeaderImageUrl() {
return "https://cdn-images-1.medium.com/max/2600/1*wfGf2KKJjSmsIG_P5Xgb4w.png"
}
///////////////////////////////////////////////////
// LAUNCH SCRIPT //
/////////////////////////////////////////////////
writeReportToDisk(buildHtmlPage());
///////////////////////////////////////////////////
// HELPER METHODS //
/////////////////////////////////////////////////
public boolean isAssertable(Class<?> claz) {
boolean isAssertable = com.eviware.soapui.model.testsuite.Assertable.class.isAssignableFrom(claz);
return isAssertable;
}
private String deriveTestCaseStatus(tc) {
def tsStatusList = [];
def tcStatus;
testStepList = tc.getTestStepList();
testStepList.each {
if(isAssertable(it.class) && it.getAssertionStatus()!=null) {
tsStatusList.add(it.getAssertionStatus().toString());
}
}
if(tsStatusList.contains('FAILED')) {
tcStatus = 'FAILED';
} else if(tsStatusList.every{it.equals('VALID');}){
tcStatus = 'VALID';
} else {
tcStatus = 'UNKNOWN';
}
return tcStatus;
}
private String deriveTestSuiteStatus(tcStatusCollection) {
def testSuiteStatus;
if(tcStatusCollection.contains('FAILED')) {
tcStatus = 'FAILED';
} else if(tcStatusCollection.every{it.equals('VALID');}){
tcStatus = 'VALID';
} else {
tcStatusCollection = 'UNKNOWN';
}
return testSuiteStatus;
}
private String getStatusClass(String status) {
statusClass = null;
switch (status) {
case 'VALID' : statusClass = 'pass';
break;
case 'FAILED' : statusClass = 'fail';
break;
default : statusClass = 'defaultStatus';
}
return statusClass;
}
///////////////////////////////////////////////////
// Build Table Methods //
/////////////////////////////////////////////////
/*
The details table contains the item by item information.
- Title with testsuite name
- Per test case:
- The title
- A table per step with:
- assertion type
- assertion name
- assertion status
- assertion message
*/
//////////DETAILS//////////////////////////////////////////////////////
public String getDetailTableData() {
def StringBuilder detail = new StringBuilder()
//get test suites
testSuites = project.getTestSuiteList()
//iterate over suites
testSuites.each {
if(!it.isDisabled()) {
testsuiteName = it.getName()
testsuiteId = it.getId()
testsuiteDescription = it.getDescription()
detail.append("<h3><a name=\"$testsuiteId\">$testsuiteName</a></h3>")
detail.append("<div class=\"testCaseDescription\">")
detail.append("<span style=\"vertical-align:middle;\">")
detail.append("<b>Test Suite Description:</b><br>")
detail.append("$testsuiteDescription")
detail.append("</span>")
detail.append("</div>")
//get test cases
testCases = it.getTestCaseList()
//iterate over test cases
testCases.each {
if(!it.isDisabled()) {
testcaseName = it.getName()
testcaseId = it.getId()
testcaseDescription = it.getDescription()
detail.append("<h4><a name=\"$testcaseId\">$testcaseName</a></h4>")
detail.append("<div class=\"testCaseDescription\">")
detail.append("<span style=\"vertical-align:middle;\">")
detail.append("<b>Test Case Description:</b><br>")
detail.append("$testcaseDescription")
detail.append("</span>")
detail.append("</div>")
testSteps = it.getTestStepList()
detail.append("<fieldset>")
detail.append("<legend>Test Case Steps</legend>")
testSteps.each {
teststepName = it.label
teststepType = it.getClass().getSimpleName()
detail.append("<table>")
detail.append("<caption><h5>$teststepName<span style=\"font-weight:normal;font-size:italic;\"> ($teststepType)</span></h5></caption>")
detail.append("<tr>")
detail.append("<th>Type</th>")
detail.append("<th>Name</th>")
detail.append("<th>Status</th>")
detail.append("<th>Errors</th>")
detail.append("</tr>")
if(!it.isDisabled()) {
//assertable content check
if(isAssertable(it.class)) {
testStepStatus = it.getAssertionStatus()
assertionList = it.getAssertionList()
//testAssertion list
if(assertionList.size()>0){
assertionList.each {
assertionName = it.label
assertionStatus = it.status
assertionType = it.getClass().getSimpleName()
StringBuilder assertionInfoSb = new StringBuilder()
it.getErrors().each {
assertionInfoSb.append(it.getMessage().toString())
}
assertionInfo = assertionInfoSb.toString()
assertionStatusHtmlClass = getStatusClass(assertionStatus.toString())
detail.append("<tr>")
detail.append("<td>$assertionType</td>")
detail.append("<td>$assertionName</td>")
detail.append("<td class=\"$assertionStatusHtmlClass\">$assertionStatus</td>")
detail.append("<td>$assertionInfo</td>")
detail.append("</tr>")
}
//If no assertions are available
} else {
detail.append("<tr><td colspan=\"4\">There are no assertions available for this test step.</td></tr>")
}
//If the test step is not assertable
} else {
detail.append("<tr><td colspan=\"4\">This test step is not assertable.</td></tr>")
}
}
detail.append("</table>")
}
detail.append("</fieldset>")
}
}
}
}
return detail
}
///////////////////////////////////////////////////////////////////////
//////////OVERVIEW/////////////////////////////////////////////////////
public String getOverviewTableData() {
def StringBuilder overview = new StringBuilder()
overview.append("<table class=\"listOverview\">")
overview.append("<caption>Test Suites &amp; Test Cases</caption>")
overview.append("<tbody>")
overview.append("<tr>")
overview.append("<th>Test Suite Name</th>")
overview.append("<th>Test Case Name</th>")
overview.append("<th>Test Case Status</th>")
overview.append("</tr>")
//get test suites
testSuites = project.getTestSuiteList()
//iterate over suites
testSuites.each {
//Reset test case status collector
tcStatusCollector = [];
if(!it.isDisabled()) {
testsuiteName = it.label
testsuiteId = it.getId()
//get test cases
testCases = it.getTestCaseList()
//iterate over test cases
firstTestCase = true
Integer testCaseCount=0
testCases.each {
if(!it.isDisabled()) {
testCaseCount++
}
}
testCases.each {
if(!it.isDisabled()) {
testcaseName = it.label
testcaseId = it.getId()
//get the test case status (indirect - binary pass fail)
testcaseStatus = deriveTestCaseStatus(it)
tcStatusCollector.add(testcaseStatus)
statusHtmlClass = getStatusClass(testcaseStatus)
overview.append("<tr>")
if(firstTestCase) {
overview.append("<td class=\"hooverable\" rowspan=\"$testCaseCount\"><a href=\"#$testsuiteId\"><div class=\"linkBlock\">$testsuiteName</div></a></td>")
}
overview.append("<td class=\"hooverable\"><a href=\"#$testcaseId\"><div class=\"linkBlock\">$testcaseName</div></a></td>")
overview.append("<td class=\"$statusHtmlClass\">$testcaseStatus</td>")
overview.append("</tr>")
firstTestCase = false
}
}
testSuiteStatus = deriveTestSuiteStatus(tcStatusCollector)
}
}
overview.append("</tbody>")
overview.append("</table>")
return overview
}
///////////////////////////////////////////////////////////////////////////////////
//////////////NUMERIC OVERVIEW TABLE///////////////////////////////////////////////
/**
* The setup is as follows:
* Initialize the counters ( initialize at value 0 )
*
* Next, there are a series of nested iterations.
*
* Test suites --> Test cases --> Test steps --> Assertions
*
* At each level:
*
* 1. Add to the total
* 2. Initialize a list for statuses if needed
* 3. Check if enabled
* 3.1 If enabled --> Dive deeper and start at 1
* 3.2 If not enabled --> Add 1 to 'disabled' counter for:
* - the item in question
* - each child item down to the level of assertions
* 4. Add 1 to correct counter
*
* After these counter updates, these are used in generating the table.
*
* The table is generated rediculously verbose in order to make the code accesible.
**/
private String getCountTable() {
// Initialize the count
Integer testSuitePass = 0;
Integer testSuiteFail = 0;
Integer testSuiteUnknown = 0;
Integer testSuiteDisabled = 0;
Integer testSuiteTotal = 0;
Integer testCasePass = 0;
Integer testCaseFail = 0;
Integer testCaseUnknown = 0;
Integer testCaseDisabled = 0;
Integer testCaseTotal = 0;
Integer testStepPass = 0;
Integer testStepFail = 0;
Integer testStepUnknown = 0;
Integer testStepDisabled = 0;
Integer testStepTotal = 0;
Integer testAssertionPass = 0;
Integer testAssertionFail = 0;
Integer testAssertionUnknown = 0;
Integer testAssertionDisabled = 0;
Integer testAssertionTotal = 0;
// Get the test suites
testSuites = project.getTestSuiteList();
// For each suite, check suite status and dive into child items.
testSuites.each {
if(!it.isDisabled()) {
// Add 1 to total of test suites.
// Added here after isDisabled check because only enabled are counted for stats.
// Choice, change if you like.
testSuiteTotal++;
def caseStatusList = [];
testCases = it.getTestCaseList();
testCases.each {
def testcaseStatus = "UNKNOWN";
if(!it.isDisabled()) {
testCaseTotal++;
def stepStatusList = [];
testSteps = it.getTestStepList();
testSteps.each {
if(!it.isDisabled()) {
testStepTotal++;
if(isAssertable(it.class) && it.getAssertionStatus() != null) {
def testStepStatus = it.getAssertionStatus().toString();
if (testStepStatus == 'VALID') {
testStepPass++;
} else {
if(testStepStatus == 'FAILED') {
testStepFail++;
} else {
testStepUnknown++;
}
}
stepStatusList.add(testStepStatus);
def testAssertions = it.getAssertionList();
testAssertions.each {
if(!it.isDisabled()) {
testAssertionTotal++;
def assertionStatus = it.status;
if (assertionStatus.toString() == "VALID") {
testAssertionPass++;
} else {
if(assertionStatus.toString() == "FAILED") {
testAssertionFail++;
} else {
testAssertionUnknown++;
}
}
} else {
testAssertionDisabled++;
}
}//end of testassertions.each
} else {
//skip this case (not assertable) --> do not do anything
}
} else {//test step else
testStepDisabled++;
if(isAssertable(it.class)) {
it.getAssertionList().each {
testAssertionDisabled++;
}
}
}
}//End of teststeps.each
if(stepStatusList.contains('FAILED')) {
testcaseStatus = 'FAILED';
testCaseFail++;
} else if(stepStatusList.every{it.equals('VALID');}){
testcaseStatus = 'VALID';
testCasePass++;
} else {
testcaseStatus = 'UNKNOWN';
testCaseUnknown++;
}
caseStatusList.add(testcaseStatus);
} else {//test case else
testCaseDisabled++;
it.getTestStepList().each {
testStepDisabled++;
if(isAssertable(it.class)) {
it.getAssertionList().each {
testAssertionDisabled++;
}
}
}
}
}//end of testcases.each
if(caseStatusList.contains('FAILED')) {
testSuiteFail++;
} else if(caseStatusList.every{it.equals('VALID');}){
testSuitePass++;
} else {
testSuiteUnknown++;
}
} else {//test suite else
testSuiteDisabled++;
it.getTestCaseList().each {
testCaseDisabled++;
it.getTestStepList().each {
testStepDisabled++;
if(isAssertable(it.class)) {
it.getAssertionList().each {
testAssertionDisabled++;
}
}
}
}
}//end of else
}//end of testsuites.each
//Initialize stringbuilder
def StringBuilder overview = new StringBuilder();
//Build html - No loops and iteration because then there needs to be an object instead of just x integers we can use.
overview.append("<table class=\"numOverview\">")
overview.append("<caption>Numeric Overview<div class=\"testCaseDescription\">")
overview.append("<b>Info: </b>This table shows actual counts of the statuses. VALID, FAILED and UNKNOWN are shown here. The disabled items are not counted. The colored bar is an indicator for the relative amount as a percentage. The relative amount is weighted against the sum of the items with status VALID, FAILED and UNKNOWN.</div>")
overview.append("</caption>")
overview.append("<tbody>")
overview.append("<tr>")
overview.append("<th>Level</th>")
overview.append("<th>Passed</th>")
overview.append("<th>Failed</th>")
overview.append("<th>Unknown</th>")
overview.append("<th>Total</th>")
overview.append("</tr>")
overview.append("<tr>")
overview.append("<th>Test Suite</th>")
//This is repeated x times for each status on each level
overview.append("<td>")
//Center overlay is to display the count (transparant, only text visible)
overview.append("<div class=\"centerOverLay\">$testSuitePass</div>")
//Calculate the % width of the overlay to hide the fade bar.
//The fade bar is put on screen in full, and a white rectangle covers the part we don't need.
//Hence 100 - x
width = 100 - Math.round((testSuitePass / testSuiteTotal) * 100)
//Set the div to hide the part of the fade we don't need
overview.append("<div class=\"coverFade\" style=\"width:$width%\">&nbsp;</div>")
//Set up the fade bar over the width of the td item.
overview.append("<div class=\"greenFadeBar\">&nbsp;</div>")
overview.append("</td>")
overview.append("<td>")
overview.append("<div class=\"centerOverLay\">$testSuiteFail</div>")
width = 100 - Math.round((testSuiteFail / testSuiteTotal) * 100)
overview.append("<div class=\"coverFade\" style=\"width:$width%\">&nbsp;</div>")
overview.append("<div class=\"redFadeBar\">&nbsp;</div>")
overview.append("</td>")
overview.append("<td>")
overview.append("<div class=\"centerOverLay\">$testSuiteUnknown</div>")
width = 100 - Math.round((testSuiteUnknown / testSuiteTotal) * 100)
overview.append("<div class=\"coverFade\" style=\"width:100%\">&nbsp;</div>")
overview.append("<div class=\"orangeFadeBar\">&nbsp;</div>")
overview.append("</td>")
overview.append("<td class=\"defaultStatus\">$testSuiteTotal</td>")
overview.append("</tr>")
overview.append("<tr>")
overview.append("<th>Test Case</th>")
overview.append("<td>")
overview.append("<div class=\"centerOverLay\">$testCasePass</div>")
width = 100 - Math.round((testCasePass / testCaseTotal) * 100)
overview.append("<div class=\"coverFade\" style=\"width:$width%\">&nbsp;</div>")
overview.append("<div class=\"greenFadeBar\">&nbsp;</div>")
overview.append("</td>")
overview.append("<td>")
overview.append("<div class=\"centerOverLay\">$testCaseFail</div>")
width = 100 - Math.round((testCaseFail / testCaseTotal) * 100)
overview.append("<div class=\"coverFade\" style=\"width:$width%\">&nbsp;</div>")
overview.append("<div class=\"redFadeBar\">&nbsp;</div>")
overview.append("</td>")
overview.append("<td>")
overview.append("<div class=\"centerOverLay\">$testCaseUnknown</div>")
width = 100 - Math.round((testCaseUnknown / testCaseTotal) * 100)
overview.append("<div class=\"coverFade\" style=\"width:$width%\">&nbsp;</div>")
overview.append("<div class=\"orangeFadeBar\">&nbsp;</div>")
overview.append("</td>")
overview.append("<td class=\"defaultStatus\">$testCaseTotal</td>")
overview.append("</tr>")
overview.append("<tr>")
overview.append("<th>Test Step</th>")
overview.append("<td>")
overview.append("<div class=\"centerOverLay\">$testStepPass</div>")
width = 100 - Math.round((testStepPass / testStepTotal) * 100)
overview.append("<div class=\"coverFade\" style=\"width:$width%\">&nbsp;</div>")
overview.append("<div class=\"greenFadeBar\">&nbsp;</div>")
overview.append("</td>")
overview.append("<td>")
overview.append("<div class=\"centerOverLay\">$testStepFail</div>")
width = 100 - Math.round((testStepFail / testStepTotal) * 100)
overview.append("<div class=\"coverFade\" style=\"width:$width%\">&nbsp;</div>")
overview.append("<div class=\"redFadeBar\">&nbsp;</div>")
overview.append("</td>")
overview.append("<td>")
overview.append("<div class=\"centerOverLay\">$testStepUnknown</div>")
width = 100 - Math.round((testStepUnknown / testStepTotal) * 100)
overview.append("<div class=\"coverFade\" style=\"width:$width%\">&nbsp;</div>")
overview.append("<div class=\"orangeFadeBar\">&nbsp;</div>")
overview.append("</td>")
overview.append("<td class=\"defaultStatus\">$testStepTotal</td>")
overview.append("</tr>")
overview.append("<tr>")
overview.append("<th>Test Assertion</th>")
overview.append("<td>")
overview.append("<div class=\"centerOverLay\">$testAssertionPass</div>")
width = 100 - Math.round((testAssertionPass / testAssertionTotal) * 100)
overview.append("<div class=\"coverFade\" style=\"width:$width%\">&nbsp;</div>")
overview.append("<div class=\"greenFadeBar\">&nbsp;</div>")
overview.append("</td>")
overview.append("<td>")
overview.append("<div class=\"centerOverLay\">$testAssertionFail</div>")
width = 100 - Math.round((testAssertionFail / testAssertionTotal) * 100)
overview.append("<div class=\"coverFade\" style=\"width:$width%\">&nbsp;</div>")
overview.append("<div class=\"redFadeBar\">&nbsp;</div>")
overview.append("</td>")
overview.append("<td>")
overview.append("<div class=\"centerOverLay\">$testAssertionUnknown</div>")
width = 100 - Math.round((testAssertionUnknown / testAssertionTotal) * 100)
overview.append("<div class=\"coverFade\" style=\"width:$width%\">&nbsp;</div>")
overview.append("<div class=\"orangeFadeBar\">&nbsp;</div>")
overview.append("</td>")
overview.append("<td class=\"defaultStatus\">$testAssertionTotal</td>")
overview.append("</tr>")
overview.append("</tbody>")
overview.append("</table>")
return overview;
}//end of method
//////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////
// Build Page Methods //
/////////////////////////////////////////////////
public String buildHtmlPage() {
StringBuilder htmlPage = new StringBuilder()
//Start page + markup (css)
htmlPage.append("<html>")
htmlPage.append("<head>")
htmlPage.append("<style>")
htmlPage.append('body {counter-reset: h3;}')
htmlPage.append('div.parentContainer {width: 70%;margin-left: auto;margin-right: auto; margin-top: 100px;flex:1;}')
htmlPage.append('div.banner {position:fixed;top: 0;bottom: 0;left: 0;right: 0;background: linear-gradient(to right, DodgerBlue, white, DodgerBlue); height: 90px; width: 100%; z-index: 1;}')
htmlPage.append('div.defaultContainer {width: 100%;overflow: auto;}')
htmlPage.append('img.headerImage {position:absolute;display:table;top: 0;bottom: 0;left: 0;right: 0;height: 60px;width: auto;margin:auto;margin-left: auto;margin-right: auto;z-index:2;}')
htmlPage.append('div.testCaseDescription {border-radius: 10px;background-color: #cccccc;padding: 8px;margin-bottom: 10px;font-weight:normal;white-space:pre-wrap;}')
htmlPage.append('h1 {text-align: center;font-weight: bold;}')
htmlPage.append('h3 {text-indent: 20px;counter-reset: h4;}')
htmlPage.append('h4 {text-indent: 40px;counter-reset: h5;}')
htmlPage.append('h5 {text-indent: 60px;}')
htmlPage.append('h3:before {content:counter(h3,decimal)" ";counter-increment: h3;}')
htmlPage.append('h4:before {content:counter(h3,decimal)"."counter(h4,decimal)" ";counter-increment: h4;}')
htmlPage.append('h5:before {content:counter(h3,decimal)"."counter(h4,decimal)"."counter(h5,decimal)" ";counter-increment: h5;}')
htmlPage.append('table {font-family: arial, sans-serif;border-collapse: collapse;width: 100%;min-width:400px;margin-bottom:20px;border:1px solid;}')
htmlPage.append('table.testDetail {}')
htmlPage.append('table.testDetail col.type {width:150px;}')
htmlPage.append('table.testDetail col.name {width:350px;}')
htmlPage.append('table.testDetail col.status {width:120px;}')
htmlPage.append('table.testDetail col.error {}')
htmlPage.append('table.listOverview {width: auto;min-width: 33%;max-width: 66%;border:none;display:inline-block;}')
htmlPage.append('table.numOverview {max-width:30%;vertical-align: top;border:none; float: right;}')
htmlPage.append('caption {text-align: left;font-size: 14px;font-weight: bold;padding-bottom: 8px}')
htmlPage.append('tr {height: 100%;}')
htmlPage.append('td, th {padding: 8px;border: 1px solid black;font-size: 12px;position:relative;}')
htmlPage.append('td.pass {background-color:green;color:white;font-weight: bold;text-align: center;}')
htmlPage.append('td.fail {background-color: red;color: white;font-weight: bold;text-align: center;}')
htmlPage.append('td.defaultStatus {text-align: center;}')
htmlPage.append('td.hooverable{}')
htmlPage.append('table.numOverview th {}')
htmlPage.append('table.numOverview td {width:18%;padding-left:0px;padding-right:0px;}')
htmlPage.append('div.linkBlock {width:100%;height:100%;padding:0px;display:inline-block;}')
htmlPage.append('div.greenFadeBar {position:absolute;top: 0;bottom: 0;left: 0;right: 0;height:100%;background:linear-gradient(to right, white, green 85%);z-index: -2;width: 100%;}')
htmlPage.append('div.redFadeBar {position:absolute;top: 0;bottom: 0;left: 0;right: 0;height:100%;background: linear-gradient(to right, white, red 25%);z-index: -2;width: 100%;}')
htmlPage.append('div.orangeFadeBar {position:absolute;top: 0;bottom: 0;left: 0;right: 0;height:100%;background: linear-gradient(to right, white, orange 50%);z-index: -2;width: 100%;}')
htmlPage.append('div.coverFade {position:absolute;right: 0;top:0;height:100%;background-color: white;z-index: -1;float:right;}')
htmlPage.append('div.centerOverLay {position:absolute;display:table;top: 0;bottom: 0;left: 0;right: 0;margin:auto;z-index: 0;width:20px;font-weight:bold;font-size:14px;}')
htmlPage.append('table.listOverview td.hooverable:hover {background-color: #cccccc;}')
htmlPage.append('@media screen and (max-width: 1400px) {table.numOverview {width: 100%;float:none;}')
htmlPage.append("</style>")
htmlPage.append("</head>")
htmlPage.append("<body>")
htmlPage.append("<div class=\"banner\">")
htmlPage.append("<img class=\"headerImage\" src=\"" + getHeaderImageUrl() + "\"/>")
htmlPage.append("</div>")
htmlPage.append("<div class=\"parentContainer\">")
//retrieve project name
projectName = project.getName()
projectPath = project.getPath()
projectDescription = project.getDescription().replace(/(?:\r\n|\r|\n)/, '<br>')
//Set main title (project name)
htmlPage.append("<h1>$projectName</h1>")
htmlPage.append("<div class=\"testCaseDescription\">")
htmlPage.append("<span style=\"vertical-align:middle;\">")
htmlPage.append("<b>Project file location:</b> $projectPath")
htmlPage.append("</span>")
htmlPage.append("</div>")
htmlPage.append("<div class=\"testCaseDescription\">")
htmlPage.append("<span style=\"vertical-align:middle;\">")
htmlPage.append("<b>Project Description:</b><br>")
htmlPage.append("$projectDescription")
htmlPage.append("</span>")
htmlPage.append("</div>")
//set subtitle 'Overview' + add overview table
htmlPage.append("<h2>Overview</h2>")
htmlPage.append(getCountTable())
htmlPage.append(getOverviewTableData())
//set subtitle 'Details' + add details table
htmlPage.append("<h2>Details</h2>")
htmlPage.append(getDetailTableData())
//End page (close main div, body and html tags)
htmlPage.append("</div>")
htmlPage.append("</body>")
htmlPage.append("</html>")
return htmlPage
}
///////////////////////////////////////////////////
// WRITE FILE TO DISK //
/////////////////////////////////////////////////
public void writeReportToDisk(String htmlPage) {
//set up variables for writing the file
Date now = new Date()
def timestamp = now.toTimestamp().toString().replaceAll(':','')
projectName = project.getName()
String baseLoc = project.getPropertyValue("testDataPath");
if (baseLoc == null || "".equals(baseLoc)) {
baseLoc = "C:\\SoapUI Reports\\"
UISupport.showInfoMessage('There was not project-level property "testDataPath" defined.\nThe report will be saved in C:/Tools/Reports.')
}
// Create a File object representing the target folder
def folder = new File( baseLoc + "Execution results" )
// If it doesn't exist
if( !folder.exists() ) {
// Create all folders
folder.mkdirs()
}
def fileName = projectName + "_" + timestamp + ".html"
// Then, write to the file inside the folder
def targetFile = new File( folder, fileName )
targetFile.withWriterAppend { w -> w << htmlPage}
//Now open the report in the standard browser (only on windows - commandline command)
Desktop.getDesktop().browse(targetFile.toURI());
}
@matthias-dirickx
Copy link
Author

matthias-dirickx commented Apr 4, 2019

It's esthaetically unpleasing and verbose, but it does the job and it is, in my opinion, relatively accesible because there's no 'extras' used. Just JAVA libraries and some syntax specific to groovy.

Place in teardown script to automatically generate it after executing the project.

Alternatively you can wrap it in an action as suggested in the description.

@matthias-dirickx
Copy link
Author

Added a frame to the testcase steps (because I wanted to visually group them a bit)
Added the descriptions for the test suite and test case objects.

@shaliniselvim
Copy link

got this error, when i placed this code in my testsuite teardown script, and ran testsuite
ERROR:groovy.lang.MissingPropertyException: No such property: project for class: Script17
pls help

@matthias-dirickx
Copy link
Author

Late to the party here. This answer hasn't been tested - I'll verify as soon as I get SoapUI installed again.

Did you run it from the the Project-level tear-down script? In the tear-down script on the project level there should be property 'project'. If you put it in a different level like Test Suite or Test Case then this property will not be available in the context of the script.

@matthias-dirickx
Copy link
Author

Just checked and in the teardown it does work. However... You will need at least 1 assertion or it will fail on an arithmeticexception - Never had a report with zero assertions, so never stumbled over it. Also the CSS is off in Chrome / chromium. I used to tailor it solely to firefox because I was the only user. Somehting to look at in the not too distant future. But now I'm leaving it as is. If you have a recommandation, feel free to reference it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment