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