package com.gorillalogic.monkeytalk.processor.report.detail; import java.io.IOException; import java.io.StringReader; import java.util.Date; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.Node; import org.dom4j.io.SAXReader; import com.gorillalogic.monkeytalk.processor.PlaybackResult; import com.gorillalogic.monkeytalk.utils.FileUtils; import com.gorillalogic.monkeytalk.utils.TimeUtils; public class DetailReportHtml { private static final String DETAIL = "/templates/DetailHtmlTemplate.html"; private static final String ROW = "/templates/DetailRowTemplate.html"; private static final String EXTRA = "/templates/DetailExtraTemplate.html"; private static final String SCREENSHOTS = "/templates/DetailScreenshotsTemplate.html"; private static final String METRICS = "/templates/DetailMetricsTemplate.html"; /** Custom bootstrap.css with 24 cols */ private static final int SPAN_MAX = 24; /** Max offset allowed (so we don't indent too far) */ private static final int OFFSET_MAX = 15; private String rowTmpl; private String extraTmpl; private String screenshotsTmpl; private String metricsTmpl; public DetailReportHtml() { } /** Build the html detail report directly from the given xml detail report. */ public String createDetailReportHtml(PlaybackResult result, String xmlReport) throws Exception { rowTmpl = FileUtils.readStream(getClass().getResourceAsStream(ROW)); extraTmpl = FileUtils.readStream(getClass().getResourceAsStream(EXTRA)); screenshotsTmpl = FileUtils.readStream(getClass().getResourceAsStream(SCREENSHOTS)); metricsTmpl = FileUtils.readStream(getClass().getResourceAsStream(METRICS)); String html = FileUtils.readStream(getClass().getResourceAsStream(DETAIL)); String title = "DETAIL-" + result.getScope().getFilename() + ".html"; SAXReader reader = new SAXReader(); Document doc = reader.read(new StringReader(xmlReport)); Element detail = doc.getRootElement(); String generated = detail.attributeValue("generated", TimeUtils.formatDate(new Date())); String content = getContent(detail, 0); String agent = detail.attributeValue("agent", "<i>unknown agent</i>"); agent = agent.replaceFirst(" - .*$", ""); String runner = detail.attributeValue("runner", "<i>unknown runner</i>"); runner = runner.replaceFirst(" - .*$", ""); html = html.replace("${title}", title).replace("${title}", title); // replace title twice html = html.replace("${generated}", generated); html = html.replace("${content}", content); html = html.replace("${raw_xml}", xmlReport.replaceAll("<", "<")); html = html.replace("${agent}", agent); html = html.replace("${runner}", runner); return html; } /** * Helper that outputs the children of the given root element as pretty html (and then * recursively outputs all of children's children as well). */ private String getContent(Element elem, int level) throws Exception { StringBuilder sb = new StringBuilder(); if (elem != null) { // compute offset and span int offset = (level > OFFSET_MAX ? OFFSET_MAX : level); for (int i = 0; i < elem.nodeCount(); i++) { Node node = elem.node(i); if (node instanceof Element) { Element child = (Element) node; // output it String html = getHtmlRow(child, offset); if (html.length() > 0) { sb.append('\n').append(html).append('\n'); } // output any of its child nodes via recursion sb.append(getContent(child, level + 1)); } } } return sb.toString(); } /** * Helper that populates the html row template with the given element. * * @throws IOException */ private String getHtmlRow(Element elem, int offset) throws IOException { String comp = elem.attributeValue("comp"); if (comp == null || comp.length() == 0) { return ""; } // grab row template & fill in all variables String contents = rowTmpl; // compute the span String span = (offset > 0 ? "offset" + offset + " " : "") + "span" + (SPAN_MAX - offset); // monkeyId might contain quotes that need html escaping (also monkeyId must be quoted if it // contains a space) String monkeyId = elem.attributeValue("id", "").replaceAll("\"", """); if (monkeyId.contains(" ")) { monkeyId = """ + monkeyId + """; } // args might contain quotes that need html escaping String args = elem.attributeValue("args", "").replaceAll("\"", """); // compute result and resultClass String result = elem.attributeValue("result", "unknown"); String resultClass = ("error".equalsIgnoreCase(result) ? "text-error" : ("failure" .equalsIgnoreCase(result) ? "text-warning" : ("ok".equalsIgnoreCase(result) ? "text-success" : "text-info"))); String idx = elem.attributeValue("idx", "0"); contents = contents.replaceAll("\\$\\{idx\\}", idx); contents = contents.replace("${comp}", comp); contents = contents.replace("${monkeyId}", monkeyId); contents = contents.replace("${action}", elem.attributeValue("action", "")); contents = contents.replace("${args}", args); contents = contents.replace("${rightSide}", getRightSide(elem)); contents = contents.replace("${span}", span); contents = contents.replace("${resultClass}", resultClass); contents = contents.replace("${result}", result); contents = contents.replace("${rowClass}", getRowClass(elem)); // screenshots & metrics String extra = getExtra(elem, offset + 1); if (extra.length() > 0) { contents = contents.replace("${onclick}", " onclick=\"toggle_extra('" + idx + "');\""); contents += extra; } else { contents = contents.replace("${onclick}", ""); } return contents; } /** Helper to get the "rightSide" attributes of the row (often suite info). Returns pretty html. */ private String getRightSide(Element elem) { String right = ""; if (elem != null && "suite".equalsIgnoreCase(elem.getName())) { String tests = getIntAttrAsHtml(elem, "tests", "test", null); String errors = getIntAttrAsHtml(elem, "errors", "error", "text-error"); String failures = getIntAttrAsHtml(elem, "failures", "failure", "text-warning"); String skipped = getIntAttrAsHtml(elem, "skipped", "skipped", "text-info"); String duration = (elem.attributeValue("duration") != null ? elem .attributeValue("duration") + "s — " : ""); right = tests + ", " + errors + ", " + failures + ", " + skipped + " — " + duration; } else if (elem != null) { right = (elem.attributeValue("duration") != null ? elem.attributeValue("duration") + "s — " : ""); } return right; } /** * Helper to add the before/after screenshots and system metrics * * @throws IOException */ private String getExtra(Element elem, int offset) { if (elem == null) { return ""; } if (elem.attributeValue("beforeScreenshot") == null && elem.attributeValue("afterScreenshot") == null && elem.attributeValue("battery") == null) { return ""; } String contents = extraTmpl; String idx = elem.attributeValue("idx", "0"); contents = contents.replaceAll("\\$\\{idx\\}", idx); contents = contents.replace("${metrics}", getMetrics(elem, offset)); contents = contents.replace("${screenshots}", getScreenshots(elem, offset)); return "\n\n" + contents; } private String getMetrics(Element elem, int offset) { if (elem == null) { return ""; } if (elem.attributeValue("battery") == null) { return ""; } String contents = metricsTmpl; // compute the offset & span contents = contents.replace("${span}", "offset" + offset + " span" + (SPAN_MAX - offset)); contents = contents.replace("${memory}", elem.attributeValue("memory", "??")); contents = contents.replace("${cpu}", elem.attributeValue("cpu", "??")); contents = contents.replace("${diskspace}", elem.attributeValue("diskspace", "??")); contents = contents.replace("${battery}", elem.attributeValue("battery", "??")); return "\n" + contents; } private String getScreenshots(Element elem, int offset) { if (elem == null) { return ""; } if (elem.attributeValue("beforeScreenshot") == null && elem.attributeValue("afterScreenshot") == null) { return ""; } String contents = screenshotsTmpl; // compute the offset & span int span = Double.valueOf(Math.floor(0.5f * (SPAN_MAX - offset))).intValue(); contents = contents.replace("${span1}", "offset" + offset + " span" + span); contents = contents.replace("${span2}", "span" + (SPAN_MAX - offset - span)); String before = "??"; if (elem.attributeValue("beforeScreenshot") != null) { before = "<img src=\"" + elem.attributeValue("beforeScreenshot") + "\" title=\"before\" class=\"screenshot\" />"; } String after = "??"; if (elem.attributeValue("afterScreenshot") != null) { after = "<img src=\"" + elem.attributeValue("afterScreenshot") + "\" title=\"after\" class=\"screenshot\" />"; } contents = contents.replace("${beforeScreenshot}", before); contents = contents.replace("${afterScreenshot}", after); return "\n" + contents; } /** * Helper to compute the row color -- red for error, yellow for failure, etc. */ private String getRowClass(Element elem) { if ("test".equalsIgnoreCase(elem.getName())) { // test row color reflects the result: // (error=red, failure=yellow, success=green, skipped=blue) String result = elem.attributeValue("result", "unknown"); return ("error".equalsIgnoreCase(result) ? " pbox-error" : ("failure" .equalsIgnoreCase(result) ? " pbox-warning" : ("ok".equalsIgnoreCase(result) ? " pbox-success" : " pbox-info"))); } else if ("suite".equalsIgnoreCase(elem.getName())) { // suite row color reflects the aggregate results: // (any errors = red, any failures = yellow, otherwise green) int tests = getIntAttr(elem, "tests"); int errors = getIntAttr(elem, "errors"); int failures = getIntAttr(elem, "failures"); return (errors > 0 ? " pbox-error" : (failures > 0 ? " pbox-warning" : (tests > 0 ? " pbox-success" : ""))); } return ""; } /** Helper to convert an integer attribute into a pretty html span. */ private String getIntAttrAsHtml(Element elem, String attr, String singular, String klass) { int i = getIntAttr(elem, attr); return "<span" + (i == 0 || klass != null ? " class=\"" + (i == 0 ? "muted" : klass) + "\">" : ">") + i + " " + (i == 1 ? singular : attr) + "</span>"; } /** Helper to get an integer attribute. */ private int getIntAttr(Element elem, String attr) { int i = 0; try { i = Integer.parseInt(elem.attributeValue(attr, "0")); } catch (NumberFormatException ex) { i = 0; } return i; } }