1package org.testng.reporters;
2
3import org.testng.ITestContext;
4import org.testng.ITestNGMethod;
5import org.testng.ITestResult;
6import org.testng.Reporter;
7import org.testng.TestListenerAdapter;
8import org.testng.internal.Utils;
9
10import java.io.Serializable;
11import java.util.Collection;
12import java.util.Collections;
13import java.util.Comparator;
14import java.util.Date;
15import 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 */
24public 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
34  public void onStart(ITestContext context) {
35    m_testContext = context;
36  }
37
38  @Override
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
54  private static String getOutputFile(ITestContext context) {
55    return context.getName() + ".html";
56  }
57
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
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
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
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;
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
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
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