package net.thucydides.core.reports.html; import ch.lambdaj.function.convert.Converter; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import net.thucydides.core.guice.Injectors; import net.thucydides.core.images.ResizableImage; import net.thucydides.core.issues.IssueTracking; import net.thucydides.core.model.Screenshot; import net.thucydides.core.model.TestOutcome; import net.thucydides.core.model.TestStep; import net.thucydides.core.model.TestTag; import net.thucydides.core.reports.AcceptanceTestReporter; import net.thucydides.core.reports.OutcomeFormat; import net.thucydides.core.reports.ReportOptions; import net.thucydides.core.reports.TestOutcomes; import net.thucydides.core.reports.html.screenshots.ScreenshotFormatter; import net.thucydides.core.requirements.RequirementsService; import net.thucydides.core.util.EnvironmentVariables; import net.thucydides.core.util.Inflector; import net.thucydides.core.util.VersionProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import static ch.lambdaj.Lambda.convert; import static com.google.common.collect.Iterables.any; import static net.thucydides.core.ThucydidesSystemProperty.THUCYDIDES_KEEP_UNSCALED_SCREENSHOTS; import static net.thucydides.core.model.ReportType.HTML; /** * Generates acceptance test results in HTML form. * */ public class HtmlAcceptanceTestReporter extends HtmlReporter implements AcceptanceTestReporter { private static final String DEFAULT_ACCEPTANCE_TEST_REPORT = "freemarker/default.ftl"; private static final String DEFAULT_ACCEPTANCE_TEST_SCREENSHOT = "freemarker/screenshots.ftl"; private static final int MAXIMUM_SCREENSHOT_WIDTH = 1000; private static final Logger LOGGER = LoggerFactory.getLogger(HtmlAcceptanceTestReporter.class); private String qualifier; private final IssueTracking issueTracking; private RequirementsService requirementsService; public void setQualifier(final String qualifier) { this.qualifier = qualifier; } public HtmlAcceptanceTestReporter() { super(); this.issueTracking = Injectors.getInjector().getInstance(IssueTracking.class); this.requirementsService = Injectors.getInjector().getInstance(RequirementsService.class); } public HtmlAcceptanceTestReporter(final EnvironmentVariables environmentVariables, final IssueTracking issueTracking) { super(environmentVariables); this.issueTracking = issueTracking; this.requirementsService = Injectors.getInjector().getInstance(RequirementsService.class); } public String getName() { return "html"; } /** * Generate an HTML report for a given test run. */ public File generateReportFor(final TestOutcome testOutcome, TestOutcomes allTestOutcomes) throws IOException { Preconditions.checkNotNull(getOutputDirectory()); TestOutcome storedTestOutcome = testOutcome.withQualifier(qualifier); Map<String,Object> context = new HashMap<String,Object>(); addTestOutcomeToContext(storedTestOutcome, allTestOutcomes, context); if (containsScreenshots(storedTestOutcome)) { generateScreenshotReportsFor(storedTestOutcome, allTestOutcomes); } addFormattersToContext(context); addTimestamp(testOutcome, context); String htmlContents = mergeTemplate(DEFAULT_ACCEPTANCE_TEST_REPORT).usingContext(context); copyResourcesToOutputDirectory(); String reportFilename = reportFor(storedTestOutcome); LOGGER.info("GENERATING HTML REPORT FOR " + storedTestOutcome.getCompleteName() + (qualifier != null? "/" + qualifier : "") + " => " + reportFilename); return writeReportToOutputDirectory(reportFilename, htmlContents); } private boolean containsScreenshots(TestOutcome testOutcome) { return any(testOutcome.getFlattenedTestSteps(), hasScreenshot()); } private Predicate<TestStep> hasScreenshot() { return new Predicate<TestStep>() { public boolean apply(TestStep testStep) { return ((testStep.getScreenshots() != null) && (!testStep.getScreenshots().isEmpty())); } }; } private void addTestOutcomeToContext(final TestOutcome testOutcome, final TestOutcomes allTestOutcomes, final Map<String,Object> context) { context.put("allTestOutcomes", allTestOutcomes); context.put("testOutcome", testOutcome); context.put("currentTag", TestTag.EMPTY_TAG); context.put("inflection", Inflector.getInstance()); context.put("parentRequirement", requirementsService.getParentRequirementFor(testOutcome)); context.put("featureOrStory", Optional.fromNullable(testOutcome.getUserStory())); context.put("requirementTypes", requirementsService.getRequirementTypes()); addTimestamp(testOutcome, context); } private void addFormattersToContext(final Map<String,Object> context) { Formatter formatter = new Formatter(issueTracking); context.put("reportOptions", new ReportOptions(getEnvironmentVariables())); context.put("formatter", formatter); context.put("reportName", new ReportNameProvider()); context.put("absoluteReportName", new ReportNameProvider()); context.put("reportOptions", new ReportOptions(getEnvironmentVariables())); VersionProvider versionProvider = new VersionProvider(getEnvironmentVariables()); context.put("thucydidesVersionNumber", versionProvider.getVersion()); context.put("buildNumber", versionProvider.getBuildNumberText()); } private void generateScreenshotReportsFor(final TestOutcome testOutcome, final TestOutcomes allTestOutcomes) throws IOException { Preconditions.checkNotNull(getOutputDirectory()); List<Screenshot> screenshots = expandScreenshots(testOutcome.getScreenshots()); String screenshotReport = testOutcome.getReportName() + "_screenshots.html"; Map<String,Object> context = new HashMap<String,Object>(); addTestOutcomeToContext(testOutcome, allTestOutcomes, context); addFormattersToContext(context); context.put("screenshots", screenshots); context.put("narrativeView", testOutcome.getReportName()); String htmlContents = mergeTemplate(DEFAULT_ACCEPTANCE_TEST_SCREENSHOT).usingContext(context); writeReportToOutputDirectory(screenshotReport, htmlContents); } private List<Screenshot> expandScreenshots(List<Screenshot> screenshots) throws IOException { return convert(screenshots, new ExpandedScreenshotConverter(maxScreenshotHeightIn(screenshots))); } private class ExpandedScreenshotConverter implements Converter<Screenshot, Screenshot> { private final int maxHeight; public ExpandedScreenshotConverter(int maxHeight) { this.maxHeight = maxHeight; } public Screenshot convert(Screenshot screenshot) { try { return ScreenshotFormatter.forScreenshot(screenshot) .inDirectory(getOutputDirectory()) .keepOriginals(shouldKeepOriginalScreenshots()) .expandToHeight(maxHeight); } catch (IOException e) { LOGGER.warn("Failed to write scaled screenshot for {}: {}", screenshot, e); return screenshot; } } } private boolean shouldKeepOriginalScreenshots() { return getEnvironmentVariables().getPropertyAsBoolean(THUCYDIDES_KEEP_UNSCALED_SCREENSHOTS, false); } private int maxScreenshotHeightIn(List<Screenshot> screenshots) throws IOException { int maxHeight = 0; for (Screenshot screenshot : screenshots) { File screenshotFile = new File(getOutputDirectory(),screenshot.getFilename()); if (screenshotFile.exists()) { maxHeight = maxHeightOf(maxHeight, screenshotFile); } } return maxHeight; } private int maxHeightOf(int maxHeight, File screenshotFile) throws IOException { int height = ResizableImage.loadFrom(screenshotFile).getHeight(); int width = ResizableImage.loadFrom(screenshotFile).getWitdh(); if (width > MAXIMUM_SCREENSHOT_WIDTH) { height = (int) ((height * 1.0) * (MAXIMUM_SCREENSHOT_WIDTH * 1.0 / width)); } if (height > maxHeight) { maxHeight = height; } return maxHeight; } private String reportFor(final TestOutcome testOutcome) { return testOutcome.withQualifier(qualifier).getReportName(HTML); } @Override public Optional<OutcomeFormat> getFormat() { return Optional.of(OutcomeFormat.HTML); } }