package net.thucydides.core.reports.html; import com.beust.jcommander.internal.Lists; import net.thucydides.core.ThucydidesSystemProperties; import net.thucydides.core.ThucydidesSystemProperty; import net.thucydides.core.guice.Injectors; import net.thucydides.core.issues.IssueTracking; import net.thucydides.core.model.NumericalFormatter; import net.thucydides.core.model.Release; import net.thucydides.core.model.TestResult; import net.thucydides.core.model.TestTag; import net.thucydides.core.releases.ReleaseManager; import net.thucydides.core.reports.*; import net.thucydides.core.reports.csv.CSVReporter; import net.thucydides.core.requirements.RequirementsProviderService; import net.thucydides.core.requirements.RequirementsService; import net.thucydides.core.requirements.model.Requirement; import net.thucydides.core.requirements.model.RequirementsConfiguration; import net.thucydides.core.requirements.reports.RequirementOutcome; import net.thucydides.core.requirements.reports.RequirementsOutcomes; import net.thucydides.core.requirements.reports.RequirmentsOutcomeFactory; import net.thucydides.core.util.EnvironmentVariables; import net.thucydides.core.util.Inflector; import net.thucydides.core.util.VersionProvider; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.nio.file.*; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Generates an aggregate acceptance test report in HTML form. * Reads all the reports from the output directory to generates aggregate HTML reports * summarizing the results. */ public class HtmlAggregateStoryReporter extends HtmlReporter implements UserStoryTestReporter { private static final Logger LOGGER = LoggerFactory.getLogger(HtmlAggregateStoryReporter.class); private static final String HISTORY_TEMPLATE_PATH = "freemarker/history.ftl"; private static final String TEST_OUTCOME_TEMPLATE_PATH = "freemarker/home.ftl"; private static final String RELEASES_TEMPLATE_PATH = "freemarker/releases.ftl"; private static final String RELEASE_TEMPLATE_PATH = "freemarker/release.ftl"; private static final String TAGTYPE_TEMPLATE_PATH = "freemarker/results-by-tagtype.ftl"; private static final String REQUIREMENT_TYPE_TEMPLATE_PATH = "freemarker/requirement-type.ftl"; // private TestHistory testHistory; private String projectName; private String relativeLink; private ReportNameProvider reportNameProvider; private final IssueTracking issueTracking; private final RequirementsService requirementsService; private final RequirmentsOutcomeFactory requirementsFactory; private final HtmlRequirementsReporter htmlRequirementsReporter; private final RequirementsConfiguration requirementsConfiguration; private final EnvironmentVariables environmentVariables; private FormatConfiguration formatConfiguration; public HtmlAggregateStoryReporter(final String projectName) { this(projectName, ""); } public HtmlAggregateStoryReporter(final String projectName, final String relativeLink) { this(projectName, relativeLink, Injectors.getInjector().getInstance(IssueTracking.class), Injectors.getInjector().getInstance(RequirementsService.class), Injectors.getInjector().getProvider(EnvironmentVariables.class).get() ); } public HtmlAggregateStoryReporter(final String projectName, final IssueTracking issueTracking) { this(projectName, "", issueTracking, Injectors.getInjector().getInstance(RequirementsService.class), Injectors.getInjector().getProvider(EnvironmentVariables.class).get() ); } public HtmlAggregateStoryReporter(final String projectName, final String relativeLink, final IssueTracking issueTracking, final RequirementsService requirementsService, final EnvironmentVariables environmentVariables) { this.projectName = projectName; this.relativeLink = relativeLink; this.issueTracking = issueTracking; // this.testHistory = testHistory; this.reportNameProvider = new ReportNameProvider(); this.htmlRequirementsReporter = new HtmlRequirementsReporter(relativeLink); RequirementsProviderService requirementsProviderService = Injectors.getInjector().getInstance(RequirementsProviderService.class); this.requirementsFactory = new RequirmentsOutcomeFactory(requirementsProviderService.getRequirementsProviders(), issueTracking); this.requirementsService = requirementsService; this.requirementsConfiguration = new RequirementsConfiguration(getEnvironmentVariables()); this.environmentVariables = environmentVariables; this.formatConfiguration = new FormatConfiguration(environmentVariables); } public OutcomeFormat getFormat() { return formatConfiguration.getPreferredFormat(); } public String getProjectName() { return projectName; } private void addFormattersToContext(final Map<String, Object> context) { Formatter formatter = new Formatter(issueTracking); context.put("formatter", formatter); context.put("formatted", new NumericalFormatter()); context.put("inflection", Inflector.getInstance()); context.put("relativeLink", relativeLink); context.put("reportOptions", new ReportOptions(getEnvironmentVariables())); } public TestOutcomes generateReportsForTestResultsFrom(final File sourceDirectory) throws IOException { TestOutcomes allTestOutcomes = loadTestOutcomesFrom(sourceDirectory); copyScreenshotsFrom(sourceDirectory); generateReportsForTestResultsIn(allTestOutcomes); return allTestOutcomes; } private void copyScreenshotsFrom(File sourceDirectory) { if ((getOutputDirectory() != null) && (getOutputDirectory() != sourceDirectory)) { CopyOption[] options = new CopyOption[]{ StandardCopyOption.COPY_ATTRIBUTES }; Path targetPath = Paths.get(getOutputDirectory().toURI()); Path sourcePath = Paths.get(sourceDirectory.toURI()); try { DirectoryStream<Path> directoryContents = Files.newDirectoryStream(sourcePath); for(Path sourceFile : directoryContents) { Path destinationFile = targetPath.resolve(sourceFile.getFileName()); if (Files.notExists(destinationFile)) { Files.copy(sourceFile, destinationFile, options); } } } catch (IOException e) { LOGGER.error("Error during copying files to the target directory", e); } } } public void generateReportsForTestResultsIn(TestOutcomes testOutcomes) throws IOException { RequirementsOutcomes requirementsOutcomes = requirementsFactory.buildRequirementsOutcomesFrom(testOutcomes.withRequirementsTags()); copyResourcesToOutputDirectory(); copyTestResultsToOutputDirectory(); generateAggregateReportFor(testOutcomes); generateTagReportsFor(testOutcomes); generateTagTypeReportsFor(testOutcomes); for (String name : testOutcomes.getTagNames()) { generateTagTypeReportsFor(testOutcomes.withTag(name), new ReportNameProvider(name)); } generateRequirementTypeReports(requirementsOutcomes); generateResultReportsFor(testOutcomes); // generateHistoryReportFor(testOutcomes); // generateCoverageReportsFor(testOutcomes); generateRequirementsReportsFor(requirementsOutcomes); generateReleasesReportFor(testOutcomes, requirementsOutcomes); } private void generateRequirementTypeReports(RequirementsOutcomes requirementsOutcomes) throws IOException { List<String> requirementTypes = requirementsOutcomes.getTypes(); for (String requirementType : requirementTypes) { generateRequirementTypeReportFor(requirementType, requirementsOutcomes.requirementsOfType(requirementType), new ReportNameProvider()); } } private void generateRequirementTypeReportFor(String requirementType, RequirementsOutcomes requirementsOutcomes, ReportNameProvider reporter) throws IOException { Map<String, Object> context = buildContext(requirementsOutcomes.getTestOutcomes(), getReportNameProvider()); context.put("report", ReportProperties.forAggregateResultsReport()); context.put("requirementType", requirementType); context.put("requirements", requirementsOutcomes); String reportName = reporter.forRequirementType(requirementType); generateReportPage(context, REQUIREMENT_TYPE_TEMPLATE_PATH, reportName); } private void generateCSVReportFor(TestOutcomes testOutcomes, String reportName) throws IOException { CSVReporter csvReporter = new CSVReporter(getOutputDirectory(), getEnvironmentVariables()); csvReporter.generateReportFor(testOutcomes, reportName); } List<Requirement> reportTally = Lists.newArrayList(); public void generateRequirementsReportsFor(RequirementsOutcomes requirementsOutcomes) throws IOException { htmlRequirementsReporter.setOutputDirectory(getOutputDirectory()); htmlRequirementsReporter.generateReportFor(requirementsOutcomes); // htmlProgressReporter.setOutputDirectory(getOutputDirectory()); // htmlProgressReporter.generateReportFor(requirementsOutcomes); clearReportTally(); generateRequirementsReportsForChildRequirements(requirementsOutcomes); } private void clearReportTally() { reportTally.clear(); } private void generateRequirementsReportsForChildRequirements(RequirementsOutcomes requirementsOutcomes) throws IOException { List<RequirementOutcome> requirementOutcomes = requirementsOutcomes.getRequirementOutcomes(); for (RequirementOutcome outcome : requirementOutcomes) { Requirement requirement = outcome.getRequirement(); if (!reportTally.contains(requirement)) { TestOutcomes testOutcomesForThisRequirement = outcome.getTestOutcomes().withTag(requirement.asTag()); RequirementsOutcomes requirementOutcomesForThisRequirement = requirementsFactory.buildRequirementsOutcomesFrom(requirement, testOutcomesForThisRequirement); generateNestedRequirementsReportsFor(requirement, requirementOutcomesForThisRequirement); } } } private void generateNestedRequirementsReportsFor(Requirement parentRequirement, RequirementsOutcomes requirementsOutcomes) throws IOException { htmlRequirementsReporter.setOutputDirectory(getOutputDirectory()); String reportName = reportNameProvider.forRequirement(parentRequirement); if (!reportTally.contains(parentRequirement)) { reportTally.add(parentRequirement); htmlRequirementsReporter.generateReportFor(requirementsOutcomes, requirementsOutcomes.getTestOutcomes(), reportName); } generateRequirementsReportsForChildRequirements(requirementsOutcomes); } private TestOutcomes loadTestOutcomesFrom(File sourceDirectory) throws IOException { return TestOutcomeLoader.loadTestOutcomes().inFormat(getFormat()).from(sourceDirectory).withHistory().withRequirementsTags(); } private void generateAggregateReportFor(TestOutcomes testOutcomes) throws IOException { ReportNameProvider defaultNameProvider = new ReportNameProvider(); Map<String, Object> context = buildContext(testOutcomes, defaultNameProvider, true); context.put("report", ReportProperties.forAggregateResultsReport()); context.put("csvReport", "results.csv"); generateReportPage(context, TEST_OUTCOME_TEMPLATE_PATH, "index.html"); generateCSVReportFor(testOutcomes, "results.csv"); } private ReleaseManager releaseManager; private ReleaseManager getReleaseManager() { if (releaseManager == null) { ReportNameProvider defaultNameProvider = new ReportNameProvider(); releaseManager = new ReleaseManager(getEnvironmentVariables(), defaultNameProvider); } return releaseManager; } private ReportNameProvider defaultNameProvider; private ReportNameProvider getReportNameProvider() { if (defaultNameProvider == null) { defaultNameProvider = new ReportNameProvider(); } return defaultNameProvider; } private void generateReleasesReportFor(TestOutcomes testOutcomes, RequirementsOutcomes requirementsOutcomes) throws IOException { Map<String, Object> context = buildContext(testOutcomes, getReportNameProvider()); context.put("report", ReportProperties.forAggregateResultsReport()); List<Release> releases = getReleaseManager().getReleasesFrom(testOutcomes); LOGGER.info("Generating release reports for: " + releases); if (!releases.isEmpty()) { String releaseData = getReleaseManager().getJSONReleasesFrom(testOutcomes); context.put("releases", releases); context.put("releaseData", releaseData); context.put("requirements", requirementsOutcomes); generateReportPage(context, RELEASES_TEMPLATE_PATH, "releases.html"); generateReleaseDetailsReportsFor(testOutcomes, requirementsOutcomes); } } private void generateReleaseDetailsReportsFor(TestOutcomes testOutcomes, RequirementsOutcomes requirementsOutcomes) throws IOException { List<Release> allReleases = getReleaseManager().getFlattenedReleasesFrom(testOutcomes); List<String> requirementsTypes = getRequirementTypes(); String topLevelRequirementType = requirementsTypes.get(0); String secondLevelRequirementType = ""; String secondLevelRequirementTypeTitle = ""; String topLevelRequirementTypeTitle = Inflector.getInstance().of(topLevelRequirementType) .inPluralForm().asATitle().toString(); if (requirementsTypes.size() > 1) { secondLevelRequirementType = requirementsTypes.get(1); secondLevelRequirementTypeTitle = Inflector.getInstance().of(secondLevelRequirementType) .inPluralForm().asATitle().toString(); } for (Release release : allReleases) { RequirementsOutcomes releaseRequirements = requirementsOutcomes.getReleasedRequirementsFor(release); Map<String, Object> context = buildContext(testOutcomes, getReportNameProvider()); context.put("report", ReportProperties.forAggregateResultsReport()); context.put("release", release); context.put("releaseData", getReleaseManager().getJSONReleasesFrom(release)); context.put("releaseRequirementOutcomes", releaseRequirements.getRequirementOutcomes()); context.put("releaseTestOutcomes", testOutcomes.withTag(release.getReleaseTag())); context.put("requirementType", topLevelRequirementTypeTitle); if (StringUtils.isNotBlank(secondLevelRequirementTypeTitle)) { context.put("secondLevelRequirementType", secondLevelRequirementTypeTitle); } // capability | features | total automated tests | %automated pass | total manual | % manual String reportName = getReportNameProvider().forRelease(release); generateReportPage(context, RELEASE_TEMPLATE_PATH, reportName); } } private void generateTagReportsFor(TestOutcomes testOutcomes) throws IOException { for (TestTag tag : testOutcomes.getTags()) { generateTagReport(testOutcomes, reportNameProvider, tag); generateAssociatedTagReportsForTag(testOutcomes.withTag(tag), tag.getName()); } } private void generateTagTypeReportsFor(TestOutcomes testOutcomes) throws IOException { generateTagTypeReportsFor(testOutcomes, reportNameProvider); } private void generateTagTypeReportsFor(TestOutcomes testOutcomes, ReportNameProvider reportNameProvider) throws IOException { for (String tagType : testOutcomes.getTagTypes()) { generateTagTypeReport(testOutcomes, reportNameProvider, tagType); } } private void generateResultReportsFor(TestOutcomes testOutcomes) throws IOException { generateResultReports(testOutcomes, reportNameProvider); for (TestTag tag : testOutcomes.getTags()) { generateResultReports(testOutcomes.withTag(tag), new ReportNameProvider(tag.getName()), tag); } } // private void generateCoverageReportsFor(TestOutcomes testOutcomes) throws IOException { // // for (String tagType : testOutcomes.getTagTypes()) { // generateCoverageData(testOutcomes, tagType); // } // } private void generateResultReports(TestOutcomes testOutcomes, ReportNameProvider reportName) throws IOException { generateResultReports(testOutcomes,reportName, TestTag.EMPTY_TAG); } private void generateResultReports(TestOutcomes testOutcomesForThisTag, ReportNameProvider reportName, TestTag tag) throws IOException { if (testOutcomesForThisTag.getTotalTests().withResult(TestResult.SUCCESS) > 0) { generateResultReport(testOutcomesForThisTag.getPassingTests(), reportName, tag, "success"); } if (testOutcomesForThisTag.getTotalTests().withResult(TestResult.PENDING) > 0) { generateResultReport(testOutcomesForThisTag.getPendingTests(), reportName, tag, "pending"); } if (testOutcomesForThisTag.getTotalTests().withResult(TestResult.FAILURE) > 0) { generateResultReport(testOutcomesForThisTag.getFailingTests(), reportName, tag, "failure"); } if (testOutcomesForThisTag.getTotalTests().withResult(TestResult.ERROR) > 0) { generateResultReport(testOutcomesForThisTag.getErrorTests(), reportName, tag, "error"); } if (testOutcomesForThisTag.getTotalTests().withResult(TestResult.IGNORED) > 0) { generateResultReport(testOutcomesForThisTag.havingResult(TestResult.IGNORED), reportName, tag, "ignored"); } if (testOutcomesForThisTag.getTotalTests().withResult(TestResult.SKIPPED) > 0) { generateResultReport(testOutcomesForThisTag.havingResult(TestResult.SKIPPED), reportName, tag, "skipped"); } } private void generateResultReport(TestOutcomes testOutcomes, ReportNameProvider reportName, TestTag tag, String testResult) throws IOException { Map<String, Object> context = buildContext(testOutcomes, reportName); context.put("report", ReportProperties.forTestResultsReport()); context.put("currentTagType", tag.getType()); context.put("currentTag", tag); String csvReport = reportName.forCSVFiles().forTestResult(testResult); context.put("csvReport", csvReport); String report = reportName.withPrefix(tag).forTestResult(testResult); generateReportPage(context, TEST_OUTCOME_TEMPLATE_PATH, report); generateCSVReportFor(testOutcomes, csvReport); } private void generateTagReport(TestOutcomes testOutcomes, ReportNameProvider reportName, TestTag tag) throws IOException { TestOutcomes testOutcomesForTag = testOutcomes.withTag(tag); Map<String, Object> context = buildContext(testOutcomesForTag, reportName); context.put("report", ReportProperties.forTagResultsReport()); context.put("currentTagType", tag.getType()); context.put("currentTag", tag); String csvReport = reportName.forCSVFiles().forTag(tag); context.put("csvReport", csvReport); String report = reportName.forTag(tag); generateReportPage(context, TEST_OUTCOME_TEMPLATE_PATH, report); generateCSVReportFor(testOutcomesForTag, csvReport); } private void generateTagTypeReport(TestOutcomes testOutcomes, ReportNameProvider reportName, String tagType) throws IOException { TestOutcomes testOutcomesForTagType = testOutcomes.withTagType(tagType); Map<String, Object> context = buildContext(testOutcomesForTagType, reportName); context.put("report", ReportProperties.forTagTypeResultsReport()); context.put("tagType", tagType); String csvReport = reportName.forCSVFiles().forTagType(tagType); context.put("csvReport", csvReport); String report = reportName.forTagType(tagType); generateReportPage(context, TAGTYPE_TEMPLATE_PATH, report); generateCSVReportFor(testOutcomesForTagType, csvReport); } private void generateAssociatedTagReportsForTag(TestOutcomes testOutcomes, String sourceTag) throws IOException { ReportNameProvider reportName = new ReportNameProvider(sourceTag); for (TestTag tag : testOutcomes.getTags()) { generateTagReport(testOutcomes, reportName, tag); } } private Map<String, Object> buildContext(TestOutcomes testOutcomesForTagType, ReportNameProvider reportName) { return buildContext(testOutcomesForTagType, reportName, false); } private Map<String, Object> buildContext(TestOutcomes testOutcomesForTagType, ReportNameProvider reportName, boolean useFiltering) { Map<String, Object> context = new HashMap<String, Object>(); TagFilter tagFilter = new TagFilter(getEnvironmentVariables()); context.put("testOutcomes", testOutcomesForTagType); context.put("allTestOutcomes", testOutcomesForTagType.getRootOutcomes()); if (useFiltering) { context.put("tagTypes", tagFilter.filteredTagTypes(testOutcomesForTagType.getTagTypes())); } else { context.put("tagTypes", testOutcomesForTagType.getTagTypes()); } context.put("currentTag", TestTag.EMPTY_TAG); context.put("reportName", reportName); context.put("absoluteReportName", new ReportNameProvider()); context.put("reportOptions", new ReportOptions(getEnvironmentVariables())); //context.put("timestamp", timestampFrom(testOutcomesForTagType.getRootOutcomes())); context.put("timestamp", timestampFrom(currentTime())); context.put("requirementTypes", requirementsService.getRequirementTypes()); addFormattersToContext(context); VersionProvider versionProvider = new VersionProvider(environmentVariables); context.put("thucydidesVersionNumber", versionProvider.getVersion()); context.put("buildNumber", versionProvider.getBuildNumberText()); return context; } private void generateReportPage(final Map<String, Object> context, final String template, final String outputFile) throws IOException { String htmlContents = mergeTemplate(template).usingContext(context); writeReportToOutputDirectory(outputFile, htmlContents); } protected ThucydidesSystemProperties getSystemProperties() { return ThucydidesSystemProperties.getProperties(); } public void setIssueTrackerUrl(String issueTrackerUrl) { if (issueTrackerUrl != null) { getSystemProperties().setValue(ThucydidesSystemProperty.THUCYDIDES_ISSUE_TRACKER_URL, issueTrackerUrl); } } public void setJiraUrl(String jiraUrl) { if (jiraUrl != null) { getSystemProperties().setValue(ThucydidesSystemProperty.JIRA_URL, jiraUrl); } } public void setJiraProject(String jiraProject) { if (jiraProject != null) { getSystemProperties().setValue(ThucydidesSystemProperty.JIRA_PROJECT, jiraProject); } } public void setJiraUsername(String jiraUsername) { if (jiraUsername != null) { getSystemProperties().setValue(ThucydidesSystemProperty.JIRA_USERNAME, jiraUsername); } } public void setJiraPassword(String jiraPassword) { if (jiraPassword != null) { getSystemProperties().setValue(ThucydidesSystemProperty.JIRA_PASSWORD, jiraPassword); } } public List<String> getRequirementTypes() { List<String> types = requirementsService.getRequirementTypes(); if (types.isEmpty()) { LOGGER.warn("No requirement types found in the test outcome requirements: using default requirements"); return requirementsConfiguration.getRequirementTypes(); } else { return types; } } /** * Check the test outcomes for failures or errors, and throw an appropriate exception if one is found. */ public void checkOutcomes() { } }