1 package org.testng.reporters; 2 3 import org.testng.ITestContext; 4 import org.testng.ITestNGMethod; 5 import org.testng.ITestResult; 6 import org.testng.Reporter; 7 import org.testng.TestListenerAdapter; 8 import org.testng.internal.Utils; 9 10 import java.io.Serializable; 11 import java.util.Collection; 12 import java.util.Collections; 13 import java.util.Comparator; 14 import java.util.Date; 15 import java.util.List; 16 17 18 /** 19 * This class implements an HTML reporter for individual tests. 20 * 21 * @author Cedric Beust, May 2, 2004 22 * @author <a href='mailto:the_mindstorm@evolva.ro'>Alexandru Popescu</a> 23 */ 24 public class TestHTMLReporter extends TestListenerAdapter { 25 private static final Comparator<ITestResult> NAME_COMPARATOR= new NameComparator(); 26 private static final Comparator<ITestResult> CONFIGURATION_COMPARATOR= new ConfigurationComparator(); 27 28 private ITestContext m_testContext = null; 29 30 ///// 31 // implements ITestListener 32 // 33 @Override onStart(ITestContext context)34 public void onStart(ITestContext context) { 35 m_testContext = context; 36 } 37 38 @Override onFinish(ITestContext context)39 public void onFinish(ITestContext context) { 40 generateLog(m_testContext, 41 null /* host */, 42 m_testContext.getOutputDirectory(), 43 getConfigurationFailures(), 44 getConfigurationSkips(), 45 getPassedTests(), 46 getFailedTests(), 47 getSkippedTests(), 48 getFailedButWithinSuccessPercentageTests()); 49 } 50 // 51 // implements ITestListener 52 ///// 53 getOutputFile(ITestContext context)54 private static String getOutputFile(ITestContext context) { 55 return context.getName() + ".html"; 56 } 57 generateTable(StringBuffer sb, String title, Collection<ITestResult> tests, String cssClass, Comparator<ITestResult> comparator)58 public static void generateTable(StringBuffer sb, String title, 59 Collection<ITestResult> tests, String cssClass, Comparator<ITestResult> comparator) 60 { 61 sb.append("<table width='100%' border='1' class='invocation-").append(cssClass).append("'>\n") 62 .append("<tr><td colspan='4' align='center'><b>").append(title).append("</b></td></tr>\n") 63 .append("<tr>") 64 .append("<td><b>Test method</b></td>\n") 65 .append("<td width=\"30%\"><b>Exception</b></td>\n") 66 .append("<td width=\"10%\"><b>Time (seconds)</b></td>\n") 67 .append("<td><b>Instance</b></td>\n") 68 .append("</tr>\n"); 69 70 if (tests instanceof List) { 71 Collections.sort((List<ITestResult>) tests, comparator); 72 } 73 74 // User output? 75 String id = ""; 76 Throwable tw = null; 77 78 for (ITestResult tr : tests) { 79 sb.append("<tr>\n"); 80 81 // Test method 82 ITestNGMethod method = tr.getMethod(); 83 84 String name = method.getMethodName(); 85 sb.append("<td title='").append(tr.getTestClass().getName()).append(".") 86 .append(name) 87 .append("()'>") 88 .append("<b>").append(name).append("</b>"); 89 90 // Test class 91 String testClass = tr.getTestClass().getName(); 92 if (testClass != null) { 93 sb.append("<br>").append("Test class: " + testClass); 94 95 // Test name 96 String testName = tr.getTestName(); 97 if (testName != null) { 98 sb.append(" (").append(testName).append(")"); 99 } 100 } 101 102 // Method description 103 if (! Utils.isStringEmpty(method.getDescription())) { 104 sb.append("<br>").append("Test method: ").append(method.getDescription()); 105 } 106 107 Object[] parameters = tr.getParameters(); 108 if (parameters != null && parameters.length > 0) { 109 sb.append("<br>Parameters: "); 110 for (int j = 0; j < parameters.length; j++) { 111 if (j > 0) { 112 sb.append(", "); 113 } 114 sb.append(parameters[j] == null ? "null" : parameters[j].toString()); 115 } 116 } 117 118 // 119 // Output from the method, created by the user calling Reporter.log() 120 // 121 { 122 List<String> output = Reporter.getOutput(tr); 123 if (null != output && output.size() > 0) { 124 sb.append("<br/>"); 125 // Method name 126 String divId = "Output-" + tr.hashCode(); 127 sb.append("\n<a href=\"#").append(divId).append("\"") 128 .append(" onClick='toggleBox(\"").append(divId).append("\", this, \"Show output\", \"Hide output\");'>") 129 .append("Show output</a>\n") 130 .append("\n<a href=\"#").append(divId).append("\"") 131 .append(" onClick=\"toggleAllBoxes();\">Show all outputs</a>\n") 132 ; 133 134 // Method output 135 sb.append("<div class='log' id=\"").append(divId).append("\">\n"); 136 for (String s : output) { 137 sb.append(s).append("<br/>\n"); 138 } 139 sb.append("</div>\n"); 140 } 141 } 142 143 sb.append("</td>\n"); 144 145 146 // Exception 147 tw = tr.getThrowable(); 148 String stackTrace = ""; 149 String fullStackTrace = ""; 150 151 id = "stack-trace" + tr.hashCode(); 152 sb.append("<td>"); 153 154 if (null != tw) { 155 String[] stackTraces = Utils.stackTrace(tw, true); 156 fullStackTrace = stackTraces[1]; 157 stackTrace = "<div><pre>" + stackTraces[0] + "</pre></div>"; 158 159 sb.append(stackTrace); 160 // JavaScript link 161 sb.append("<a href='#' onClick='toggleBox(\"") 162 .append(id).append("\", this, \"Click to show all stack frames\", \"Click to hide stack frames\")'>") 163 .append("Click to show all stack frames").append("</a>\n") 164 .append("<div class='stack-trace' id='" + id + "'>") 165 .append("<pre>" + fullStackTrace + "</pre>") 166 .append("</div>") 167 ; 168 } 169 170 sb.append("</td>\n"); 171 172 // Time 173 long time = (tr.getEndMillis() - tr.getStartMillis()) / 1000; 174 String strTime = Long.toString(time); 175 sb.append("<td>").append(strTime).append("</td>\n"); 176 177 // Instance 178 Object instance = tr.getInstance(); 179 sb.append("<td>").append(instance).append("</td>"); 180 181 sb.append("</tr>\n"); 182 } 183 184 sb.append("</table><p>\n"); 185 186 } 187 arrayToString(String[] array)188 private static String arrayToString(String[] array) { 189 StringBuffer result = new StringBuffer(""); 190 for (String element : array) { 191 result.append(element).append(" "); 192 } 193 194 return result.toString(); 195 } 196 197 private static String HEAD = 198 "\n<style type=\"text/css\">\n" + 199 ".log { display: none;} \n" + 200 ".stack-trace { display: none;} \n" + 201 "</style>\n" + 202 "<script type=\"text/javascript\">\n" + 203 "<!--\n" + 204 "function flip(e) {\n" + 205 " current = e.style.display;\n" + 206 " if (current == 'block') {\n" + 207 " e.style.display = 'none';\n" + 208 " return 0;\n" + 209 " }\n" + 210 " else {\n" + 211 " e.style.display = 'block';\n" + 212 " return 1;\n" + 213 " }\n" + 214 "}\n" + 215 "\n" + 216 "function toggleBox(szDivId, elem, msg1, msg2)\n" + 217 "{\n" + 218 " var res = -1;" + 219 " if (document.getElementById) {\n" + 220 " res = flip(document.getElementById(szDivId));\n" + 221 " }\n" + 222 " else if (document.all) {\n" + 223 " // this is the way old msie versions work\n" + 224 " res = flip(document.all[szDivId]);\n" + 225 " }\n" + 226 " if(elem) {\n" + 227 " if(res == 0) elem.innerHTML = msg1; else elem.innerHTML = msg2;\n" + 228 " }\n" + 229 "\n" + 230 "}\n" + 231 "\n" + 232 "function toggleAllBoxes() {\n" + 233 " if (document.getElementsByTagName) {\n" + 234 " d = document.getElementsByTagName('div');\n" + 235 " for (i = 0; i < d.length; i++) {\n" + 236 " if (d[i].className == 'log') {\n" + 237 " flip(d[i]);\n" + 238 " }\n" + 239 " }\n" + 240 " }\n" + 241 "}\n" + 242 "\n" + 243 "// -->\n" + 244 "</script>\n" + 245 "\n"; 246 generateLog(ITestContext testContext, String host, String outputDirectory, Collection<ITestResult> failedConfs, Collection<ITestResult> skippedConfs, Collection<ITestResult> passedTests, Collection<ITestResult> failedTests, Collection<ITestResult> skippedTests, Collection<ITestResult> percentageTests)247 public static void generateLog(ITestContext testContext, 248 String host, 249 String outputDirectory, 250 Collection<ITestResult> failedConfs, 251 Collection<ITestResult> skippedConfs, 252 Collection<ITestResult> passedTests, 253 Collection<ITestResult> failedTests, 254 Collection<ITestResult> skippedTests, 255 Collection<ITestResult> percentageTests) 256 { 257 StringBuffer sb = new StringBuffer(); 258 sb.append("<html>\n<head>\n") 259 .append("<title>TestNG: ").append(testContext.getName()).append("</title>\n") 260 .append(HtmlHelper.getCssString()) 261 .append(HEAD) 262 .append("</head>\n") 263 .append("<body>\n"); 264 265 Date startDate = testContext.getStartDate(); 266 Date endDate = testContext.getEndDate(); 267 long duration = (endDate.getTime() - startDate.getTime()) / 1000; 268 int passed = 269 testContext.getPassedTests().size() + 270 testContext.getFailedButWithinSuccessPercentageTests().size(); 271 int failed = testContext.getFailedTests().size(); 272 int skipped = testContext.getSkippedTests().size(); 273 String hostLine = Utils.isStringEmpty(host) ? "" : "<tr><td>Remote host:</td><td>" + host 274 + "</td>\n</tr>"; 275 276 sb 277 .append("<h2 align='center'>").append(testContext.getName()).append("</h2>") 278 .append("<table border='1' align=\"center\">\n") 279 .append("<tr>\n") 280 // .append("<td>Property file:</td><td>").append(m_testRunner.getPropertyFileName()).append("</td>\n") 281 // .append("</tr><tr>\n") 282 .append("<td>Tests passed/Failed/Skipped:</td><td>").append(passed).append("/").append(failed).append("/").append(skipped).append("</td>\n") 283 .append("</tr><tr>\n") 284 .append("<td>Started on:</td><td>").append(testContext.getStartDate().toString()).append("</td>\n") 285 .append("</tr>\n") 286 .append(hostLine) 287 .append("<tr><td>Total time:</td><td>").append(duration).append(" seconds (").append(endDate.getTime() - startDate.getTime()) 288 .append(" ms)</td>\n") 289 .append("</tr><tr>\n") 290 .append("<td>Included groups:</td><td>").append(arrayToString(testContext.getIncludedGroups())).append("</td>\n") 291 .append("</tr><tr>\n") 292 .append("<td>Excluded groups:</td><td>").append(arrayToString(testContext.getExcludedGroups())).append("</td>\n") 293 .append("</tr>\n") 294 .append("</table><p/>\n") 295 ; 296 297 sb.append("<small><i>(Hover the method name to see the test class name)</i></small><p/>\n"); 298 if (failedConfs.size() > 0) { 299 generateTable(sb, "FAILED CONFIGURATIONS", failedConfs, "failed", CONFIGURATION_COMPARATOR); 300 } 301 if (skippedConfs.size() > 0) { 302 generateTable(sb, "SKIPPED CONFIGURATIONS", skippedConfs, "skipped", CONFIGURATION_COMPARATOR); 303 } 304 if (failedTests.size() > 0) { 305 generateTable(sb, "FAILED TESTS", failedTests, "failed", NAME_COMPARATOR); 306 } 307 if (percentageTests.size() > 0) { 308 generateTable(sb, "FAILED TESTS BUT WITHIN SUCCESS PERCENTAGE", 309 percentageTests, "percent", NAME_COMPARATOR); 310 } 311 if (passedTests.size() > 0) { 312 generateTable(sb, "PASSED TESTS", passedTests, "passed", NAME_COMPARATOR); 313 } 314 if (skippedTests.size() > 0) { 315 generateTable(sb, "SKIPPED TESTS", skippedTests, "skipped", NAME_COMPARATOR); 316 } 317 318 sb.append("</body>\n</html>"); 319 320 Utils.writeFile(outputDirectory, getOutputFile(testContext), sb.toString()); 321 } 322 ppp(String s)323 private static void ppp(String s) { 324 System.out.println("[TestHTMLReporter] " + s); 325 } 326 327 private static class NameComparator implements Comparator<ITestResult>, Serializable { 328 private static final long serialVersionUID = 381775815838366907L; compare(ITestResult o1, ITestResult o2)329 public int compare(ITestResult o1, ITestResult o2) { 330 String c1 = o1.getMethod().getMethodName(); 331 String c2 = o2.getMethod().getMethodName(); 332 return c1.compareTo(c2); 333 } 334 335 } 336 337 private static class ConfigurationComparator implements Comparator<ITestResult>, Serializable { 338 private static final long serialVersionUID = 5558550850685483455L; 339 compare(ITestResult o1, ITestResult o2)340 public int compare(ITestResult o1, ITestResult o2) { 341 ITestNGMethod tm1= o1.getMethod(); 342 ITestNGMethod tm2= o2.getMethod(); 343 return annotationValue(tm2) - annotationValue(tm1); 344 } 345 annotationValue(ITestNGMethod method)346 private static int annotationValue(ITestNGMethod method) { 347 if(method.isBeforeSuiteConfiguration()) { 348 return 10; 349 } 350 if(method.isBeforeTestConfiguration()) { 351 return 9; 352 } 353 if(method.isBeforeClassConfiguration()) { 354 return 8; 355 } 356 if(method.isBeforeGroupsConfiguration()) { 357 return 7; 358 } 359 if(method.isBeforeMethodConfiguration()) { 360 return 6; 361 } 362 if(method.isAfterMethodConfiguration()) { 363 return 5; 364 } 365 if(method.isAfterGroupsConfiguration()) { 366 return 4; 367 } 368 if(method.isAfterClassConfiguration()) { 369 return 3; 370 } 371 if(method.isAfterTestConfiguration()) { 372 return 2; 373 } 374 if(method.isAfterSuiteConfiguration()) { 375 return 1; 376 } 377 378 return 0; 379 } 380 } 381 382 } 383