Skip to content

Commit

Permalink
Add JUnit reporter (#352)
Browse files Browse the repository at this point in the history
* Add JUnit reporter

* Reformat code
  • Loading branch information
pettero committed Feb 12, 2024
1 parent a0b8af3 commit 29bc0ab
Show file tree
Hide file tree
Showing 8 changed files with 406 additions and 249 deletions.
20 changes: 11 additions & 9 deletions core/src/main/scala/com/karumi/shot/Shot.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import com.karumi.shot.android.Adb
import com.karumi.shot.domain._
import com.karumi.shot.domain.model.{AppId, FilePath, Folder, ScreenshotsSuite}
import com.karumi.shot.json.ScreenshotsComposeSuiteJsonParser
import com.karumi.shot.reports.{ConsoleReporter, ExecutionReporter}
import com.karumi.shot.reports.{ConsoleReporter, HtmlExecutionReporter, ExecutionReporter}
import com.karumi.shot.screenshots.{
ScreenshotsComparator,
ScreenshotsDiffGenerator,
Expand Down Expand Up @@ -33,7 +33,7 @@ class Shot(
screenshotsDiffGenerator: ScreenshotsDiffGenerator,
screenshotsSaver: ScreenshotsSaver,
console: Console,
reporter: ExecutionReporter,
reporters: List[ExecutionReporter],
consoleReporter: ConsoleReporter,
envVars: EnvVars
) {
Expand All @@ -54,7 +54,8 @@ class Shot(
} else {
val screenshots = regularScreenshotSuite.get ++ composeScreenshotSuite.get
console.show("😃 Screenshots recorded and saved at: " + shotFolder.screenshotsFolder())
reporter.generateRecordReport(appId, screenshots, shotFolder)
for (reporter <- reporters)
reporter.generateRecordReport(appId, screenshots, shotFolder)
console.show(
"🤓 You can review the execution report here: " + shotFolder.reportFolder() + "index.html"
)
Expand Down Expand Up @@ -121,12 +122,13 @@ class Shot(
} else {
console.showSuccess("✅ Yeah!!! Your tests are passing.")
}
reporter.generateVerificationReport(
appId,
comparison,
shotFolder,
showOnlyFailingTestsInReports
)
for (reporter <- reporters)
reporter.generateVerificationReport(
appId,
comparison,
shotFolder,
showOnlyFailingTestsInReports
)
console.show(
"🤓 You can review the execution report here: " + shotFolder
.verificationReportFolder() + "index.html"
Expand Down
202 changes: 5 additions & 197 deletions core/src/main/scala/com/karumi/shot/reports/ExecutionReporter.scala
Original file line number Diff line number Diff line change
@@ -1,211 +1,19 @@
package com.karumi.shot.reports
import com.karumi.shot.domain.{ScreenshotsComparisionResult, ShotFolder}
import com.karumi.shot.domain.model.{AppId, ScreenshotsSuite}

import java.io.{File, FileWriter}

import com.karumi.shot.domain._
import com.karumi.shot.domain.model.{AppId, Folder, ScreenshotComparisionErrors, ScreenshotsSuite}
import com.karumi.shot.templates.RecordIndexTemplate.recordIndexTemplate
import com.karumi.shot.templates.VerificationIndexTemplate.verificationIndexTemplate

class ExecutionReporter {
trait ExecutionReporter {

def generateRecordReport(
appId: AppId,
screenshots: ScreenshotsSuite,
shotFolder: ShotFolder
) = {
val reportFileContents = populateRecordTemplate(appId, screenshots)
resetVerificationReport(shotFolder)
val reportFolder = shotFolder.recordingReportFolder()
writeReport(reportFileContents, reportFolder)
}
): Unit

def generateVerificationReport(
appId: AppId,
comparision: ScreenshotsComparisionResult,
shotFolder: ShotFolder,
showOnlyFailingTestsInReports: Boolean = false
) = {
val reportFileContents =
populateVerificationTemplate(appId, comparision, showOnlyFailingTestsInReports)
resetVerificationReport(shotFolder)
val reportFolder = shotFolder.verificationReportFolder()
writeReport(reportFileContents, reportFolder)
}

private def writeReport(
fileContents: String,
reportFolder: String
) = {
val indexFile = new File(reportFolder + "index.html")
new File(reportFolder).mkdirs()
val writer = new FileWriter(indexFile)
writer.write(fileContents)
writer.close()
}

private def resetVerificationReport(shotFolder: ShotFolder) = {
val file = new File(shotFolder.reportFolder() + "index.html")
if (file.exists()) {
file.delete()
}
}

private def populateRecordTemplate(
appId: AppId,
screenshots: ScreenshotsSuite
): String = {
val title = s"Record results: $appId"
val numberOfTests = screenshots.size
val summaryResults =
s"$numberOfTests screenshot tests recorded."
val summaryTableBody = generateRecordSummaryTableBody(screenshots)
recordIndexTemplate(
title = title,
summaryResult = summaryResults,
summaryTableBody = summaryTableBody
)
}

private def generateRecordSummaryTableBody(screenshots: ScreenshotsSuite): String = {
screenshots
.map { (screenshot: Screenshot) =>
val testClass = screenshot.testClass
val testName = screenshot.testName
val originalScreenshot = "./images/recorded/" + screenshot.name + ".png"
val width = (screenshot.screenshotDimension.width * 0.2).toInt
val screenshotName = screenshot.name
"<tr>" +
s"<th> <p>Test class: $testClass</p>" +
s"<p>Test name: $testName</p>" +
s"<p>Screenshot name: $screenshotName</p></th>" +
s"<th> <a href='$originalScreenshot'><img width='$width' src='$originalScreenshot'/></a></th>" +
"</tr>"
}
.mkString("\n")
}

private def populateVerificationTemplate(
appId: AppId,
comparision: ScreenshotsComparisionResult,
showOnlyFailingTestsInReports: Boolean
): String = {
val title = s"Verification results: $appId"
val screenshots = comparision.screenshots
val numberOfTests = screenshots.size
val failedNumber = comparision.errors.size
val successNumber = numberOfTests - failedNumber
val summaryResults =
s"$numberOfTests screenshot tests executed. $successNumber passed and $failedNumber failed."
val summaryTableBody =
generateVerificationSummaryTableBody(comparision, showOnlyFailingTestsInReports)
val screenshotsTableBody =
generateScreenshotsTableBody(comparision, showOnlyFailingTestsInReports)
verificationIndexTemplate(
title = title,
summaryResult = summaryResults,
summaryTableBody = summaryTableBody,
screenshotsTableBody = screenshotsTableBody
)
}

private def getSortedByResultScreenshots(comparison: ScreenshotsComparisionResult) =
comparison.screenshots
.map { (screenshot: Screenshot) =>
val error = findError(screenshot, comparison.errors)
(screenshot, error)
}
.sortBy(_._2.isEmpty)

private def generateVerificationSummaryTableBody(
comparision: ScreenshotsComparisionResult,
showOnlyFailingTestsInReports: Boolean
): String = {
getSortedByResultScreenshots(comparision)
.map { case (screenshot, error) =>
val isFailedTest = error.isDefined
val testClass = screenshot.testClass
val testName = screenshot.testName
val result = if (isFailedTest) "" else ""
val reason = generateReasonMessage(error)
val color = if (isFailedTest) "red-text" else "green-text"
val id = screenshot.name.replace(".", "")
val screenshotName = screenshot.name

if (showOnlyFailingTestsInReports && isFailedTest || !showOnlyFailingTestsInReports) {
"<tr>" +
s"<th><a href='#$id'>$result</a></th>" +
s"<th><a href='#$id'><p class='$color'>Test class: $testClass</p>" +
s"<p class='$color'>Test name: $testName</p></a></th>" +
s"<p class='$color'>Screenshot name: $screenshotName</p></th>" +
s"<th>$reason</th>" +
"</tr>"
} else {
""
}
}
.mkString("\n")
}

private def generateScreenshotsTableBody(
comparision: ScreenshotsComparisionResult,
showOnlyFailingTestsInReports: Boolean
): String = {
getSortedByResultScreenshots(comparision)
.map { case (screenshot, error) =>
val isFailedTest = error.isDefined
val testClass = screenshot.testClass
val testName = screenshot.testName
val originalScreenshot = "./images/recorded/" + screenshot.name + ".png"
val newScreenshot = "./images/" + screenshot.name + ".png"
val diff = if (error.exists(_.isInstanceOf[DifferentScreenshots])) {
screenshot.getDiffScreenshotPath("./images/")
} else {
""
}
val color = if (isFailedTest) "red-text" else "green-text"
val width = (screenshot.screenshotDimension.width * 0.2).toInt
val id = screenshot.name.replace(".", "")
val screenshotName = screenshot.name

if (showOnlyFailingTestsInReports && isFailedTest || !showOnlyFailingTestsInReports) {
"<tr>" +
s"<th id='$id'> <p class='$color'>Test class: $testClass</p>" +
s"<p class='$color'>Test name: $testName</p>" +
s"<p class='$color'>Screenshot name: $screenshotName</p></th>" +
s"<th> <a href='$originalScreenshot'><img width='$width' src='$originalScreenshot'/></a></th>" +
s"<th> <a href='$newScreenshot'><img width='$width' src='$newScreenshot'/></a></th>" +
s"<th> <a href='$diff'><img width='$width' src='$diff'/></a></th>" +
"</tr>"
} else {
""
}
}
.mkString("\n")
}

private def findError(
screenshot: Screenshot,
errors: ScreenshotComparisionErrors
): Option[ScreenshotComparisonError] =
errors.find {
case ScreenshotNotFound(error) => screenshot == error
case DifferentImageDimensions(error, _, _) => screenshot == error
case DifferentScreenshots(error, _) => screenshot == error
case _ => false
}

private def generateReasonMessage(error: Option[ScreenshotComparisonError]): String =
error
.map {
case ScreenshotNotFound(_) =>
"<p class='red-text'>🔎 Recorded screenshot not found.</p>"
case DifferentScreenshots(_, _) =>
"<p class='red-text'>🤔 The application UI has been modified.</p>"
case DifferentImageDimensions(_, _, _) =>
"<p class='red-text'>📱 The size of the screenshot taken has changed.</p>"
case _ =>
"<p class='red-text'>😞 Ups! Something went wrong while comparing your screenshots but we couldn't identify the cause. If you think you've found a bug, please open an issue at https://github.com/karumi/shot.</p>"
}
.getOrElse("")
): Unit
}

0 comments on commit 29bc0ab

Please sign in to comment.