-
-
Save matthias-dirickx/0cb7e1a86e9cdcd6d5ce4cd81365afcd to your computer and use it in GitHub Desktop.
/** | |
@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 & 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%\"> </div>") | |
//Set up the fade bar over the width of the td item. | |
overview.append("<div class=\"greenFadeBar\"> </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%\"> </div>") | |
overview.append("<div class=\"redFadeBar\"> </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%\"> </div>") | |
overview.append("<div class=\"orangeFadeBar\"> </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%\"> </div>") | |
overview.append("<div class=\"greenFadeBar\"> </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%\"> </div>") | |
overview.append("<div class=\"redFadeBar\"> </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%\"> </div>") | |
overview.append("<div class=\"orangeFadeBar\"> </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%\"> </div>") | |
overview.append("<div class=\"greenFadeBar\"> </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%\"> </div>") | |
overview.append("<div class=\"redFadeBar\"> </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%\"> </div>") | |
overview.append("<div class=\"orangeFadeBar\"> </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%\"> </div>") | |
overview.append("<div class=\"greenFadeBar\"> </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%\"> </div>") | |
overview.append("<div class=\"redFadeBar\"> </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%\"> </div>") | |
overview.append("<div class=\"orangeFadeBar\"> </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()); | |
} |
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.
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
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.
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.
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.