All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gwen.report.HtmlReportFormatter.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2014-2015 Branko Juric, Brady Wood
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package gwen.report

import java.io.File
import java.text.DecimalFormat
import scala.concurrent.duration.Duration
import gwen.dsl.EvalStatus
import gwen.dsl.Failed
import gwen.dsl.StatusKeyword
import gwen.dsl.Step
import gwen.eval.FeatureResult
import gwen.eval.FeatureSummary
import gwen.GwenInfo
import gwen.eval.GwenOptions
import gwen.dsl.Scenario
import gwen.dsl.Tag
import gwen.GwenSettings
import gwen.report.ReportFormat.value2ReportFormat
import gwen.eval.FeatureUnit
import gwen.dsl.FeatureSpec
import gwen.report.HtmlReportFormatter._
import gwen.Predefs.Formatting._
import gwen.Predefs.DurationOps

/** Formats the feature summary and detail reports in HTML. */
trait HtmlReportFormatter extends ReportFormatter {
  
  private val percentFormatter = new DecimalFormat("#.##")
  
  /**
    * Formats the feature detail report as HTML.
    * 
    * @param options gwen command line options
    * @param info the gwen implementation info
    * @param unit the feature input
    * @param result the feature result to report
    * @param breadcrumbs names and references for linking back to parent reports
    * @param reportFiles the target report files (head = detail, tail = metas)
    */
  override def formatDetail(options: GwenOptions, info: GwenInfo, unit: FeatureUnit, result: FeatureResult, breadcrumbs: List[(String, File)], reportFiles: List[File]): Option[String] = {
    
    val reportDir = ReportFormat.html.reportDir(options)
    val metaResults = result.metaResults 
    val featureName = result.spec.featureFile.map(_.getPath()).getOrElse(result.spec.feature.name)
    val title = s"${if(result.isMeta) "Meta" else "Feature"} Detail"
    val status = result.evalStatus.status
    val summary = result.summary
    val screenshots = result.screenshots
    val rootPath = relativePath(reportFiles.head, reportDir).filter(_ == File.separatorChar).flatMap(_ => "../")
    
    Some(s"""

  
    ${formatHtmlHead(s"${title} - ${featureName}", rootPath)}
    ${formatJsHeader(rootPath)}
  
  
    ${formatReportHeader(info, title, featureName, rootPath)}
    ${formatStatusHeader(unit, result, rootPath, breadcrumbs, screenshots)}
    
${if (result.spec.feature.tags.size > 0) s"""

${result.spec.feature.tags.map(t => escapeHtml(t.toString)).mkString("
")}

""" else ""} ${if(result.isMeta) "Meta" else "Feature"} ${escapeHtml(result.spec.feature.name)}${formatDescriptionLines(result.spec.feature.description, None)}
Overhead: ${formatDuration(result.overhead)} ${formatProgressBar("Scenario", summary.scenarioCounts)} ${formatProgressBar("Step", summary.stepCounts)}
${if (!metaResults.isEmpty) { val count = metaResults.size val metaStatus = EvalStatus(metaResults.map(_.evalStatus)) val status = metaStatus.status s"""
  • ${(metaResults.zipWithIndex map { case (result, rowIndex) => formatSummaryLine(result, if(GwenSettings.`gwen.report.suppress.meta`) None else Some(s"meta/${reportFiles.tail(rowIndex).getName()}"), None, rowIndex)}).mkString}
"""} else ""}${(result.spec.scenarios.zipWithIndex map {case (s, idx) => formatScenario(s, s"$idx")}).mkString} """) } private def formatDescriptionLines(description: List[String], status: Option[StatusKeyword.Value]) = { val bgClass = status.map(cssStatus).getOrElse("default") if (!description.isEmpty) s"""

    ${(description map { line => s"""
  • ${line}
  • """}).mkString}

""" else "" } private def formatScenario(scenario: Scenario, scenarioId: String): String = { val status = scenario.evalStatus.status val conflict = scenario.steps.map(_.evalStatus.status).exists(_ != status) val tags = scenario.tags.filter(_ != Tag.StepDefTag ) s"""
  • ${if (tags.size > 0) s"""

    ${tags.map(t => escapeHtml(t.toString)).mkString("
    ")}

    """ else ""} ${if (scenario.isStepDef) "StepDef" else "Scenario"}${if ((scenario.steps.size + scenario.background.map(_.steps.size).getOrElse(0)) > 1) s""" ${durationOrStatus(scenario.evalStatus)}""" else ""} ${escapeHtml(scenario.name)}${formatDescriptionLines(scenario.description, Some(status))}
${(scenario.background map { background => val status = background.evalStatus.status val backgroundId = s"${scenarioId}-background" s"""
  • Background ${durationOrStatus(background.evalStatus)} ${escapeHtml(background.name)}${formatDescriptionLines(background.description, Some(status))}
    ${(background.steps map { step => formatStepLine(step, step.evalStatus.status, s"${backgroundId}-${step.pos.line}")}).mkString}
"""}).getOrElse("")}
    ${(scenario.steps map { step => formatStepLine(step, step.evalStatus.status, s"${scenarioId}-${step.pos.line}")}).mkString}
""" } /** * Formats the feature summary report as HTML. * * @param options gwen command line options * @param info the gwen implementation info * @param summary the accumulated feature results summary */ override def formatSummary(options: GwenOptions, info: GwenInfo, summary: FeatureSummary): Option[String] = { val reportDir = ReportFormat.html.reportDir(options) val title = "Feature Summary"; val status = summary.evalStatus.status Some(s""" ${formatHtmlHead(title, "")} ${formatReportHeader(info, title, if (options.args.isDefined) escapeHtml(options.commandString(info)) else "", "")}
Results
Overhead: ${formatDuration(summary.overhead)} ${formatProgressBar("Feature", summary.featureCounts)} ${formatProgressBar("Scenario", summary.scenarioCounts)} ${formatProgressBar("Step", summary.stepCounts)}
${(StatusKeyword.reportables.reverse map { status => summary.results.zipWithIndex.filter { _._1.evalStatus.status == status } match { case Nil => "" case results => s"""
  • ${status}${ val count = results.size val total = summary.results.size val countOfTotal = s"""${count} ${if (count != total) s" of ${total} features" else s"feature${if (total > 1) "s" else ""}"}""" s"""${countOfTotal}${if (count > 1) s""" ${formatDuration(DurationOps.sum(results.map(_._1.elapsedTime)))}""" else ""}"""}
  • ${ (results.zipWithIndex map { case ((result, resultIndex), rowIndex) => val reportFile = result.reports.get(ReportFormat.html).head formatSummaryLine(result, Some(s"${relativePath(reportFile, reportDir).replace(File.separatorChar, '/')}"), Some(resultIndex + 1), rowIndex) }).mkString}
"""}}).mkString} """) } private def formatHtmlHead(title: String, rootPath: String) = s""" ${title} """ private def formatProgressBar(name: String, counts: Map[StatusKeyword.Value, Int]): String = { val total = counts.map(_._2).sum if (total > 0) {s""" ${total} ${name}${if (total > 1) "s" else ""}
${(StatusKeyword.reportables map { status => val count = counts.get(status).getOrElse(0) val percentage = calcPercentage(count, total) s"""
${count} ${status} - ${percentageRounded(percentage)}%
"""}).mkString}
"""} else "" } private def formatSummaryLine(result: FeatureResult, reportPath: Option[String], sequenceNo: Option[Int], rowIndex: Int): String = s"""
${sequenceNo.map(seq => s"""
${seq}
""").getOrElse("")} ${escapeHtml(result.finished.toString)}
${reportPath.fold(s"${escapeHtml(result.spec.feature.name)}") { rpath => s"""${escapeHtml(result.spec.feature.name)}"""}}
${formatDuration(result.elapsedTime)} ${result.spec.featureFile.map(_.getPath()).getOrElse("")}
""" private def formatStepLine(step: Step, status: StatusKeyword.Value, stepId: String): String = s"""
  • ${durationOrStatus(step.evalStatus)}
    ${if (step.pos.line > 0) step.pos.line else ""}
    ${step.keyword}
    ${(step.stepDef.map { stepDef => if (status == StatusKeyword.Failed) escapeHtml(step.expression) else formatStepDefLink(step, status, s"${stepId}-stepDef")}).getOrElse(escapeHtml(step.expression))} ${formatAttachments(step.attachments, status)} ${(step.stepDef.map { stepDef => formatStepDefDiv(stepDef, status, s"${stepId}-stepDef")}).getOrElse("")}
    ${if (status == StatusKeyword.Failed && !step.stepDef.isDefined) s"""
    • ${status} ${escapeHtml(step.evalStatus.asInstanceOf[Failed].timestamp.toString)} - ${escapeHtml(step.evalStatus.asInstanceOf[Failed].error.getCause().getMessage())}
    """ else ""}
  • """ private def formatStepDefLink(step: Step, status: StatusKeyword.Value, stepDefId: String): String = s"""${escapeHtml(step.expression)}""" private def formatStepDefDiv(stepDef: Scenario, status: StatusKeyword.Value, stepDefId: String): String = s"""
    ${formatScenario(stepDef, stepDefId)}
    """ private def formatAttachments(attachments: List[(String, File)], status: StatusKeyword.Value) = s""" ${if (!attachments.isEmpty) s""" """ else ""}""" private def formatJsHeader(rootPath: String) = s""" """ private def percentageRounded(percentage: Double): String = percentFormatter.format(percentage) private def calcPercentage(count: Int, total: Int): Double = 100 * count.toDouble / total.toDouble private def durationOrStatus(evalStatus: EvalStatus) = evalStatus.status match { case StatusKeyword.Passed | StatusKeyword.Failed => formatDuration(evalStatus.duration) case _ => evalStatus.status } } object HtmlReportFormatter { private val cssStatus = Map( StatusKeyword.Passed -> "success", StatusKeyword.Failed -> "danger", StatusKeyword.Skipped -> "warning", StatusKeyword.Pending -> "info", StatusKeyword.Loaded -> "success") private [report] def formatReportHeader(info: GwenInfo, heading: String, path: String, rootPath: String) = s"""

    ${escapeHtml(heading)}

    ${escapeHtml(path)}

     

    ${escapeHtml(info.implName)}

    ${info.releaseNotesUrl.map(url => s"""""").getOrElse("")}v${escapeHtml(info.implVersion)}${info.releaseNotesUrl.map(_ => "").getOrElse("")}

    """ private [report] def formatStatusHeader(unit: FeatureUnit, result: FeatureResult, rootPath: String, breadcrumbs: List[(String, File)], screenshots: List[File]) = { val status = result.evalStatus.status val renderStatusLink = status != StatusKeyword.Passed && status != StatusKeyword.Loaded s""" """ } private def formatSlideshow(screenshots: List[File], spec: FeatureSpec, unit: FeatureUnit, rootPath: String) = s""" """ }




    © 2015 - 2024 Weber Informatics LLC | Privacy Policy