ScriptReporter.cpp 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110
  1. /*
  2. * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
  3. * its licensors.
  4. *
  5. * For complete copyright and license terms please see the LICENSE at the root of this
  6. * distribution (the "License"). All use of this software is governed by the License,
  7. * or, if provided, by the license below or the license accompanying this file. Do not
  8. * remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
  9. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. *
  11. */
  12. #include <Automation/ScriptReporter.h>
  13. #include <Utils/Utils.h>
  14. #include <Atom/Utils/PpmFile.h>
  15. #include <imgui/imgui.h>
  16. #include <Atom/RHI/Factory.h>
  17. #include <AzFramework/API/ApplicationAPI.h>
  18. #include <AzFramework/StringFunc/StringFunc.h>
  19. #include <AzFramework/IO/LocalFileIO.h>
  20. #include <AzCore/IO/SystemFile.h>
  21. #include <AzCore/Utils/Utils.h>
  22. namespace AtomSampleViewer
  23. {
  24. // Must match ScriptReporter::DisplayOption Enum
  25. static const char* DiplayOptions[] =
  26. {
  27. "All Results", "Warnings & Errors", "Errors Only",
  28. };
  29. namespace ScreenshotPaths
  30. {
  31. AZStd::string GetScreenshotsFolder(bool resolvePath)
  32. {
  33. AZStd::string path = "@user@/scripts/screenshots/";
  34. if (resolvePath)
  35. {
  36. path = Utils::ResolvePath(path);
  37. }
  38. return path;
  39. }
  40. AZStd::string GetLocalBaselineFolder(bool resolvePath)
  41. {
  42. AZStd::string path = AZStd::string::format("@user@/scripts/screenshotslocalbaseline/%s", AZ::RHI::Factory::Get().GetName().GetCStr());
  43. if (resolvePath)
  44. {
  45. path = Utils::ResolvePath(path);
  46. }
  47. return path;
  48. }
  49. AZStd::string GetOfficialBaselineFolder(bool resolvePath)
  50. {
  51. AZStd::string path = "scripts/expectedscreenshots/";
  52. if (resolvePath)
  53. {
  54. path = Utils::ResolvePath(path);
  55. }
  56. return path;
  57. }
  58. AZStd::string GetLocalBaseline(const AZStd::string& forScreenshotFile)
  59. {
  60. AZStd::string localBaselineFolder = GetLocalBaselineFolder(false);
  61. AzFramework::StringFunc::Replace(localBaselineFolder, "@user@/", "");
  62. AZStd::string newPath = forScreenshotFile;
  63. if (!AzFramework::StringFunc::Replace(newPath, "scripts/screenshots", localBaselineFolder.c_str()))
  64. {
  65. newPath = "";
  66. }
  67. return newPath;
  68. }
  69. AZStd::string GetOfficialBaseline(const AZStd::string& forScreenshotFile)
  70. {
  71. AZStd::string path = forScreenshotFile;
  72. const AZStd::string userPath = Utils::ResolvePath("@user@");
  73. // make the path relative to the user folder
  74. if (!AzFramework::StringFunc::Replace(path, userPath.c_str(), ""))
  75. {
  76. return "";
  77. }
  78. // After replacing "screenshots" with "expectedscreenshots", the path should be a valid asset path, relative to asset root.
  79. if (!AzFramework::StringFunc::Replace(path, "scripts/screenshots", "scripts/expectedscreenshots"))
  80. {
  81. return "";
  82. }
  83. // Turn it back into a full path
  84. path = Utils::ResolvePath("@assets@" + path);
  85. return path;
  86. }
  87. }
  88. AZStd::string ScriptReporter::ImageComparisonResult::GetSummaryString() const
  89. {
  90. AZStd::string resultString;
  91. if (m_resultCode == ResultCode::ThresholdExceeded || m_resultCode == ResultCode::Pass)
  92. {
  93. resultString = AZStd::string::format("Diff Score: %f", m_finalDiffScore);
  94. }
  95. else if (m_resultCode == ResultCode::WrongSize)
  96. {
  97. resultString = "Wrong size";
  98. }
  99. else if (m_resultCode == ResultCode::FileNotFound)
  100. {
  101. resultString = "File not found";
  102. }
  103. else if (m_resultCode == ResultCode::FileNotLoaded)
  104. {
  105. resultString = "File load failed";
  106. }
  107. else if (m_resultCode == ResultCode::WrongFormat)
  108. {
  109. resultString = "Format is not supported";
  110. }
  111. else if (m_resultCode == ResultCode::NullImageComparisonToleranceLevel)
  112. {
  113. resultString = "ImageComparisonToleranceLevel not provided";
  114. }
  115. else if (m_resultCode == ResultCode::None)
  116. {
  117. // "None" could be the case if the results dialog is open while the script is running
  118. resultString = "No results";
  119. }
  120. else
  121. {
  122. resultString = "Unhandled Image Comparison ResultCode";
  123. AZ_Assert(false, "Unhandled Image Comparison ResultCode");
  124. }
  125. return resultString;
  126. }
  127. void ScriptReporter::SetAvailableToleranceLevels(const AZStd::vector<ImageComparisonToleranceLevel>& toleranceLevels)
  128. {
  129. m_availableToleranceLevels = toleranceLevels;
  130. }
  131. void ScriptReporter::Reset()
  132. {
  133. m_scriptReports.clear();
  134. m_currentScriptIndexStack.clear();
  135. m_invalidationMessage.clear();
  136. }
  137. void ScriptReporter::SetInvalidationMessage(const AZStd::string& message)
  138. {
  139. m_invalidationMessage = message;
  140. // Reporting this message here instead of when running the script so it won't show up as an error in the ImGui report.
  141. AZ_Error("Automation", m_invalidationMessage.empty(), "Subsequent test results will be invalid because '%s'", m_invalidationMessage.c_str());
  142. }
  143. void ScriptReporter::PushScript(const AZStd::string& scriptAssetPath)
  144. {
  145. if (GetCurrentScriptReport())
  146. {
  147. // Only the current script should listen for Trace Errors
  148. GetCurrentScriptReport()->BusDisconnect();
  149. }
  150. m_currentScriptIndexStack.push_back(m_scriptReports.size());
  151. m_scriptReports.push_back();
  152. m_scriptReports.back().m_scriptAssetPath = scriptAssetPath;
  153. m_scriptReports.back().BusConnect();
  154. }
  155. void ScriptReporter::PopScript()
  156. {
  157. AZ_Assert(GetCurrentScriptReport(), "There is no active script");
  158. if (GetCurrentScriptReport())
  159. {
  160. GetCurrentScriptReport()->BusDisconnect();
  161. m_currentScriptIndexStack.pop_back();
  162. }
  163. if (GetCurrentScriptReport())
  164. {
  165. // Make sure the newly restored current script is listening for Trace Errors
  166. GetCurrentScriptReport()->BusConnect();
  167. }
  168. }
  169. bool ScriptReporter::HasActiveScript() const
  170. {
  171. return !m_currentScriptIndexStack.empty();
  172. }
  173. bool ScriptReporter::AddScreenshotTest(const AZStd::string& path)
  174. {
  175. AZ_Assert(GetCurrentScriptReport(), "There is no active script");
  176. ScreenshotTestInfo screenshotTestInfo;
  177. screenshotTestInfo.m_screenshotFilePath = path;
  178. GetCurrentScriptReport()->m_screenshotTests.push_back(AZStd::move(screenshotTestInfo));
  179. return true;
  180. }
  181. void ScriptReporter::TickImGui()
  182. {
  183. if (m_showReportDialog)
  184. {
  185. ShowReportDialog();
  186. }
  187. }
  188. bool ScriptReporter::HasErrorsAssertsInReport() const
  189. {
  190. for (const ScriptReport& scriptReport : m_scriptReports)
  191. {
  192. if (scriptReport.m_assertCount > 0 || scriptReport.m_generalErrorCount > 0 || scriptReport.m_screenshotErrorCount > 0)
  193. {
  194. return true;
  195. }
  196. }
  197. return false;
  198. }
  199. void ScriptReporter::ShowDiffButton(const char* buttonLabel, const AZStd::string& imagePathA, const AZStd::string& imagePathB)
  200. {
  201. if (ImGui::Button(buttonLabel))
  202. {
  203. if (!Utils::RunDiffTool(imagePathA, imagePathB))
  204. {
  205. m_messageBox.OpenPopupMessage("Can't Diff", "Image diff is not supported on this platform, or the required diff tool is not installed.");
  206. }
  207. }
  208. }
  209. const ImageComparisonToleranceLevel* ScriptReporter::FindBestToleranceLevel(float diffScore, bool filterImperceptibleDiffs) const
  210. {
  211. float thresholdChecked = 0.0f;
  212. bool ignoringMinorDiffs = false;
  213. for (const ImageComparisonToleranceLevel& level : m_availableToleranceLevels)
  214. {
  215. AZ_Assert(level.m_threshold > thresholdChecked || thresholdChecked == 0.0f, "Threshold values are not sequential");
  216. AZ_Assert(level.m_filterImperceptibleDiffs >= ignoringMinorDiffs, "filterImperceptibleDiffs values are not sequential");
  217. thresholdChecked = level.m_threshold;
  218. ignoringMinorDiffs = level.m_filterImperceptibleDiffs;
  219. if (filterImperceptibleDiffs <= level.m_filterImperceptibleDiffs && diffScore <= level.m_threshold)
  220. {
  221. return &level;
  222. }
  223. }
  224. return nullptr;
  225. }
  226. void ScriptReporter::ShowReportDialog()
  227. {
  228. if (ImGui::Begin("Script Results", &m_showReportDialog) && !m_scriptReports.empty())
  229. {
  230. const ImVec4& bgColor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
  231. const bool isDarkStyle = bgColor.x < 0.2 && bgColor.y < 0.2 && bgColor.z < 0.2;
  232. const ImVec4 HighlightPassed = isDarkStyle ? ImVec4{0.5, 1, 0.5, 1} : ImVec4{0, 0.75, 0, 1};
  233. const ImVec4 HighlightFailed = isDarkStyle ? ImVec4{1, 0.5, 0.5, 1} : ImVec4{0.75, 0, 0, 1};
  234. const ImVec4 HighlightWarning = isDarkStyle ? ImVec4{1, 1, 0.5, 1} : ImVec4{0.5, 0.5, 0, 1};
  235. // Local utilities for setting text color
  236. bool colorHasBeenSet = false;
  237. auto highlightTextIf = [&colorHasBeenSet](bool shouldSet, ImVec4 color)
  238. {
  239. if (colorHasBeenSet)
  240. {
  241. ImGui::PopStyleColor();
  242. colorHasBeenSet = false;
  243. }
  244. if (shouldSet)
  245. {
  246. ImGui::PushStyleColor(ImGuiCol_Text, color);
  247. colorHasBeenSet = true;
  248. }
  249. };
  250. auto highlightTextFailedOrWarning = [&](bool isFailed, bool isWarning)
  251. {
  252. if (colorHasBeenSet)
  253. {
  254. ImGui::PopStyleColor();
  255. colorHasBeenSet = false;
  256. }
  257. if (isFailed)
  258. {
  259. ImGui::PushStyleColor(ImGuiCol_Text, HighlightFailed);
  260. colorHasBeenSet = true;
  261. }
  262. else if (isWarning)
  263. {
  264. ImGui::PushStyleColor(ImGuiCol_Text, HighlightWarning);
  265. colorHasBeenSet = true;
  266. }
  267. };
  268. auto resetTextHighlight = [&colorHasBeenSet]()
  269. {
  270. if (colorHasBeenSet)
  271. {
  272. ImGui::PopStyleColor();
  273. colorHasBeenSet = false;
  274. }
  275. };
  276. auto seeConsole = [](uint32_t issueCount, const char* searchString)
  277. {
  278. if (issueCount == 0)
  279. {
  280. return AZStd::string{};
  281. }
  282. else
  283. {
  284. return AZStd::string::format("(See \"%s\" messages in console output)", searchString);
  285. }
  286. };
  287. auto seeBelow = [](uint32_t issueCount)
  288. {
  289. if (issueCount == 0)
  290. {
  291. return AZStd::string{};
  292. }
  293. else
  294. {
  295. return AZStd::string::format("(See below)");
  296. }
  297. };
  298. uint32_t totalAsserts = 0;
  299. uint32_t totalErrors = 0;
  300. uint32_t totalWarnings = 0;
  301. uint32_t totalScreenshotsCount = 0;
  302. uint32_t totalScreenshotsFailed = 0;
  303. uint32_t totalScreenshotWarnings = 0;
  304. for (ScriptReport& scriptReport : m_scriptReports)
  305. {
  306. totalAsserts += scriptReport.m_assertCount;
  307. // We don't include screenshot errors and warnings in these totals because those have their own line-items.
  308. totalErrors += scriptReport.m_generalErrorCount;
  309. totalWarnings += scriptReport.m_generalWarningCount;
  310. totalScreenshotWarnings += scriptReport.m_screenshotWarningCount;
  311. totalScreenshotsFailed += scriptReport.m_screenshotErrorCount;
  312. // This will catch any false-negatives that could occur if the screenshot failure error messages change without also updating ScriptReport::OnPreError()
  313. for (ScreenshotTestInfo& screenshotTest : scriptReport.m_screenshotTests)
  314. {
  315. if (screenshotTest.m_officialComparisonResult.m_resultCode != ImageComparisonResult::ResultCode::Pass &&
  316. screenshotTest.m_officialComparisonResult.m_resultCode != ImageComparisonResult::ResultCode::None)
  317. {
  318. AZ_Assert(scriptReport.m_screenshotErrorCount > 0, "If screenshot comparison failed in any way, m_screenshotErrorCount should be non-zero.");
  319. }
  320. }
  321. }
  322. ImGui::Separator();
  323. if (HasActiveScript())
  324. {
  325. ImGui::PushStyleColor(ImGuiCol_Text, HighlightWarning);
  326. ImGui::Text("Script is running... (_ _)zzz");
  327. ImGui::PopStyleColor();
  328. }
  329. else if (totalErrors > 0 || totalAsserts > 0 || totalScreenshotsFailed > 0)
  330. {
  331. ImGui::PushStyleColor(ImGuiCol_Text, HighlightFailed);
  332. ImGui::Text("(>_<) FAILED (>_<)");
  333. ImGui::PopStyleColor();
  334. }
  335. else
  336. {
  337. if (m_invalidationMessage.empty())
  338. {
  339. ImGui::PushStyleColor(ImGuiCol_Text, HighlightPassed);
  340. ImGui::Text("\\(^_^)/ PASSED \\(^_^)/");
  341. ImGui::PopStyleColor();
  342. }
  343. else
  344. {
  345. ImGui::Text("(-_-) INVALID ... but passed (-_-)");
  346. }
  347. }
  348. if (!m_invalidationMessage.empty())
  349. {
  350. ImGui::Separator();
  351. ImGui::PushStyleColor(ImGuiCol_Text, HighlightFailed);
  352. ImGui::Text("(%s)", m_invalidationMessage.c_str());
  353. ImGui::PopStyleColor();
  354. }
  355. ImGui::Separator();
  356. ImGui::Text("Test Script Count: %zu", m_scriptReports.size());
  357. highlightTextIf(totalAsserts > 0, HighlightFailed);
  358. ImGui::Text("Total Asserts: %u %s", totalAsserts, seeConsole(totalAsserts, "Trace::Assert").c_str());
  359. highlightTextIf(totalErrors > 0, HighlightFailed);
  360. ImGui::Text("Total Errors: %u %s", totalErrors, seeConsole(totalErrors, "Trace::Error").c_str());
  361. highlightTextIf(totalWarnings > 0, HighlightWarning);
  362. ImGui::Text("Total Warnings: %u %s", totalWarnings, seeConsole(totalWarnings, "Trace::Warning").c_str());
  363. resetTextHighlight();
  364. ImGui::Text("Total Screenshot Count: %u", totalScreenshotsCount);
  365. highlightTextIf(totalScreenshotsFailed > 0, HighlightFailed);
  366. ImGui::Text("Total Screenshot Failures: %u %s", totalScreenshotsFailed, seeBelow(totalScreenshotsFailed).c_str());
  367. highlightTextIf(totalScreenshotWarnings > 0, HighlightWarning);
  368. ImGui::Text("Total Screenshot Warnings: %u %s", totalScreenshotWarnings, seeBelow(totalScreenshotWarnings).c_str());
  369. resetTextHighlight();
  370. if (ImGui::Button("Update All Local Baseline Images"))
  371. {
  372. m_messageBox.OpenPopupConfirmation(
  373. "Update All Local Baseline Images",
  374. "This will replace all local baseline images \n"
  375. "with the images captured during this test run. \n"
  376. "Are you sure?",
  377. [this]() {
  378. UpdateAllLocalBaselineImages();
  379. });
  380. }
  381. int displayOption = m_displayOption;
  382. ImGui::Combo("Display", &displayOption, DiplayOptions, AZ_ARRAY_SIZE(DiplayOptions));
  383. m_displayOption = (DisplayOption)displayOption;
  384. bool showWarnings = (m_displayOption == DisplayOption::AllResults) || (m_displayOption == DisplayOption::WarningsAndErrors);
  385. bool showAll = (m_displayOption == DisplayOption::AllResults);
  386. ImGui::Separator();
  387. const ImGuiTreeNodeFlags FlagDefaultOpen = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_DefaultOpen;
  388. const ImGuiTreeNodeFlags FlagDefaultClosed = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;
  389. for (ScriptReport& scriptReport : m_scriptReports)
  390. {
  391. const bool scriptPassed = scriptReport.m_assertCount == 0 && scriptReport.m_generalErrorCount == 0 && scriptReport.m_screenshotErrorCount == 0;
  392. const bool scriptHasWarnings = scriptReport.m_generalWarningCount > 0 || scriptReport.m_screenshotWarningCount > 0;
  393. // Skip if tests passed without warnings and we don't want to show successes
  394. bool skipReport = (scriptPassed && !scriptHasWarnings && !showAll);
  395. // Skip if we only have warnings only and we don't want to show warnings
  396. skipReport = skipReport || (scriptPassed && scriptHasWarnings && !showWarnings);
  397. if (skipReport)
  398. {
  399. continue;
  400. }
  401. ImGuiTreeNodeFlags scriptNodeFlag = scriptPassed ? FlagDefaultClosed : FlagDefaultOpen;
  402. AZStd::string header = AZStd::string::format("%s %s",
  403. scriptPassed ? "PASSED" : "FAILED",
  404. scriptReport.m_scriptAssetPath.c_str()
  405. );
  406. highlightTextFailedOrWarning(!scriptPassed, scriptHasWarnings);
  407. if (ImGui::TreeNodeEx(&scriptReport, scriptNodeFlag, "%s", header.c_str()))
  408. {
  409. resetTextHighlight();
  410. // Number of Asserts
  411. highlightTextIf(scriptReport.m_assertCount > 0, HighlightFailed);
  412. if (showAll || scriptReport.m_assertCount > 0)
  413. {
  414. ImGui::Text("Asserts: %u %s", scriptReport.m_assertCount, seeConsole(scriptReport.m_assertCount, "Trace::Assert").c_str());
  415. }
  416. // Number of Errors
  417. highlightTextIf(scriptReport.m_generalErrorCount > 0, HighlightFailed);
  418. if (showAll || scriptReport.m_generalErrorCount > 0)
  419. {
  420. ImGui::Text("Errors: %u %s", scriptReport.m_generalErrorCount, seeConsole(scriptReport.m_generalErrorCount, "Trace::Error").c_str());
  421. }
  422. // Number of Warnings
  423. highlightTextIf(scriptReport.m_generalWarningCount > 0, HighlightWarning);
  424. if (showAll || (showWarnings && scriptReport.m_generalWarningCount > 0))
  425. {
  426. ImGui::Text("Warnings: %u %s", scriptReport.m_generalWarningCount, seeConsole(scriptReport.m_generalWarningCount, "Trace::Warning").c_str());
  427. }
  428. resetTextHighlight();
  429. // Number of screenshots
  430. if (showAll || scriptReport.m_screenshotErrorCount > 0 || (showWarnings && scriptReport.m_screenshotWarningCount > 0))
  431. {
  432. ImGui::Text("Screenshot Test Count: %zu", scriptReport.m_screenshotTests.size());
  433. }
  434. // Number of screenshot failures
  435. highlightTextIf(scriptReport.m_screenshotErrorCount > 0, HighlightFailed);
  436. if (showAll || scriptReport.m_screenshotErrorCount > 0)
  437. {
  438. ImGui::Text("Screenshot Tests Failed: %u %s", scriptReport.m_screenshotErrorCount, seeBelow(scriptReport.m_screenshotErrorCount).c_str());
  439. }
  440. // Number of screenshot warnings
  441. highlightTextIf(scriptReport.m_screenshotWarningCount > 0, HighlightWarning);
  442. if (showAll || (showWarnings && scriptReport.m_screenshotWarningCount > 0))
  443. {
  444. ImGui::Text("Screenshot Warnings: %u %s", scriptReport.m_screenshotWarningCount, seeBelow(scriptReport.m_screenshotWarningCount).c_str());
  445. }
  446. resetTextHighlight();
  447. for (ScreenshotTestInfo& screenshotResult : scriptReport.m_screenshotTests)
  448. {
  449. const bool screenshotPassed = screenshotResult.m_officialComparisonResult.m_resultCode == ImageComparisonResult::ResultCode::Pass;
  450. const bool localBaselineWarning = screenshotResult.m_localComparisonResult.m_resultCode != ImageComparisonResult::ResultCode::Pass;
  451. // Skip if tests passed without warnings and we don't want to show successes
  452. bool skipScreenshot = (screenshotPassed && !localBaselineWarning && !showAll);
  453. // Skip if we only have warnings only and we don't want to show warnings
  454. skipScreenshot = skipScreenshot || (screenshotPassed && localBaselineWarning && !showWarnings);
  455. if (skipScreenshot)
  456. {
  457. continue;
  458. }
  459. AZStd::string fileName;
  460. AzFramework::StringFunc::Path::GetFullFileName(screenshotResult.m_screenshotFilePath.c_str(), fileName);
  461. AZStd::string headerSummary;
  462. if (!screenshotPassed)
  463. {
  464. headerSummary = "(" + screenshotResult.m_officialComparisonResult.GetSummaryString() + ") ";
  465. }
  466. if (localBaselineWarning)
  467. {
  468. headerSummary += "(Local Baseline Warning)";
  469. }
  470. ImGuiTreeNodeFlags screenshotNodeFlag = FlagDefaultClosed;
  471. AZStd::string screenshotHeader = AZStd::string::format("%s %s %s", screenshotPassed ? "PASSED" : "FAILED", fileName.c_str(), headerSummary.c_str());
  472. highlightTextFailedOrWarning(!screenshotPassed, localBaselineWarning);
  473. if (ImGui::TreeNodeEx(&screenshotResult, screenshotNodeFlag, "%s", screenshotHeader.c_str()))
  474. {
  475. resetTextHighlight();
  476. ImGui::Text(("Screenshot: " + screenshotResult.m_screenshotFilePath).c_str());
  477. ImGui::Spacing();
  478. highlightTextIf(!screenshotPassed, HighlightFailed);
  479. ImGui::Text(("Official Baseline: " + screenshotResult.m_officialBaselineScreenshotFilePath).c_str());
  480. // Official Baseline Result
  481. ImGui::Indent();
  482. {
  483. ImGui::Text(screenshotResult.m_officialComparisonResult.GetSummaryString().c_str());
  484. if (screenshotResult.m_officialComparisonResult.m_resultCode == ImageComparisonResult::ResultCode::ThresholdExceeded ||
  485. screenshotResult.m_officialComparisonResult.m_resultCode == ImageComparisonResult::ResultCode::Pass)
  486. {
  487. ImGui::Text("Used Tolerance: %s", screenshotResult.m_toleranceLevel.ToString().c_str());
  488. const ImageComparisonToleranceLevel* suggestedTolerance = ScriptReporter::FindBestToleranceLevel(
  489. screenshotResult.m_officialComparisonResult.m_finalDiffScore,
  490. screenshotResult.m_toleranceLevel.m_filterImperceptibleDiffs);
  491. if(suggestedTolerance)
  492. {
  493. ImGui::Text("Suggested Tolerance: %s", suggestedTolerance->ToString().c_str());
  494. }
  495. if (screenshotResult.m_toleranceLevel.m_filterImperceptibleDiffs)
  496. {
  497. // This gives an idea of what the tolerance level would be if the imperceptible diffs were not filtered out.
  498. const ImageComparisonToleranceLevel* unfilteredTolerance = ScriptReporter::FindBestToleranceLevel(
  499. screenshotResult.m_officialComparisonResult.m_standardDiffScore, false);
  500. ImGui::Text("(Unfiltered Diff Score: %f%s)",
  501. screenshotResult.m_officialComparisonResult.m_standardDiffScore,
  502. unfilteredTolerance ? AZStd::string::format(" ~ '%s'", unfilteredTolerance->m_name.c_str()).c_str() : "");
  503. }
  504. }
  505. resetTextHighlight();
  506. ImGui::PushID("Official");
  507. ShowDiffButton("View Diff", screenshotResult.m_officialBaselineScreenshotFilePath, screenshotResult.m_screenshotFilePath);
  508. ImGui::PopID();
  509. if (!screenshotPassed && ImGui::Button("Update"))
  510. {
  511. if (screenshotResult.m_localComparisonResult.m_resultCode == ImageComparisonResult::ResultCode::FileNotFound)
  512. {
  513. UpdateSourceBaselineImage(screenshotResult, true);
  514. }
  515. else
  516. {
  517. m_messageBox.OpenPopupConfirmation(
  518. "Update Official Baseline Image",
  519. "This will replace the official baseline image \n"
  520. "with the image captured during this test run. \n"
  521. "Are you sure?",
  522. // It's important to bind screenshotResult by reference because UpdateOfficialBaselineImage will update it
  523. [this, &screenshotResult]() {
  524. UpdateSourceBaselineImage(screenshotResult, true);
  525. });
  526. }
  527. }
  528. }
  529. ImGui::Unindent();
  530. ImGui::Spacing();
  531. highlightTextIf(localBaselineWarning, HighlightWarning);
  532. ImGui::Text(("Local Baseline: " + screenshotResult.m_localBaselineScreenshotFilePath).c_str());
  533. // Local Baseline Result
  534. ImGui::Indent();
  535. {
  536. ImGui::Text(screenshotResult.m_localComparisonResult.GetSummaryString().c_str());
  537. resetTextHighlight();
  538. ImGui::PushID("Local");
  539. ShowDiffButton("View Diff", screenshotResult.m_localBaselineScreenshotFilePath, screenshotResult.m_screenshotFilePath);
  540. ImGui::PopID();
  541. if (localBaselineWarning && ImGui::Button("Update"))
  542. {
  543. if (screenshotResult.m_localComparisonResult.m_resultCode == ImageComparisonResult::ResultCode::FileNotFound)
  544. {
  545. UpdateLocalBaselineImage(screenshotResult, true);
  546. }
  547. else
  548. {
  549. m_messageBox.OpenPopupConfirmation(
  550. "Update Local Baseline Image",
  551. "This will replace the local baseline image \n"
  552. "with the image captured during this test run. \n"
  553. "Are you sure?",
  554. // It's important to bind screenshotResult by reference because UpdateLocalBaselineImage will update it
  555. [this, &screenshotResult]() {
  556. UpdateLocalBaselineImage(screenshotResult, true);
  557. });
  558. }
  559. }
  560. }
  561. ImGui::Unindent();
  562. ImGui::Spacing();
  563. resetTextHighlight();
  564. ImGui::TreePop();
  565. }
  566. }
  567. ImGui::TreePop();
  568. }
  569. resetTextHighlight();
  570. }
  571. resetTextHighlight();
  572. // Repeat the m_invalidationMessage at the bottom as well, to make sure the user doesn't miss it.
  573. if (!m_invalidationMessage.empty())
  574. {
  575. ImGui::Separator();
  576. ImGui::PushStyleColor(ImGuiCol_Text, HighlightFailed);
  577. ImGui::Text("(%s)", m_invalidationMessage.c_str());
  578. ImGui::PopStyleColor();
  579. }
  580. }
  581. m_messageBox.TickPopup();
  582. ImGui::End();
  583. }
  584. void ScriptReporter::OpenReportDialog()
  585. {
  586. m_showReportDialog = true;
  587. }
  588. ScriptReporter::ScriptReport* ScriptReporter::GetCurrentScriptReport()
  589. {
  590. if (!m_currentScriptIndexStack.empty())
  591. {
  592. return &m_scriptReports[m_currentScriptIndexStack.back()];
  593. }
  594. else
  595. {
  596. return nullptr;
  597. }
  598. }
  599. void ScriptReporter::ReportScriptError([[maybe_unused]] const AZStd::string& message)
  600. {
  601. AZ_Error("Automation", false, "Script: %s", message.c_str());
  602. }
  603. void ScriptReporter::ReportScriptWarning([[maybe_unused]] const AZStd::string& message)
  604. {
  605. AZ_Warning("Automation", false, "Script: %s", message.c_str());
  606. }
  607. void ScriptReporter::ReportScriptIssue(const AZStd::string& message, TraceLevel traceLevel)
  608. {
  609. switch (traceLevel)
  610. {
  611. case TraceLevel::Error:
  612. ReportScriptError(message);
  613. break;
  614. case TraceLevel::Warning:
  615. ReportScriptWarning(message);
  616. break;
  617. default:
  618. AZ_Assert(false, "Unhandled TraceLevel");
  619. }
  620. }
  621. void ScriptReporter::ReportScreenshotComparisonIssue(const AZStd::string& message, const AZStd::string& expectedImageFilePath, const AZStd::string& actualImageFilePath, TraceLevel traceLevel)
  622. {
  623. AZStd::string fullMessage = AZStd::string::format("%s\n Expected: '%s'\n Actual: '%s'",
  624. message.c_str(),
  625. expectedImageFilePath.c_str(),
  626. actualImageFilePath.c_str());
  627. ReportScriptIssue(fullMessage, traceLevel);
  628. }
  629. bool ScriptReporter::LoadPpmData(ImageComparisonResult& imageComparisonResult, const AZStd::string& path, AZStd::vector<uint8_t>& buffer, AZ::RHI::Size& size, AZ::RHI::Format& format, TraceLevel traceLevel)
  630. {
  631. const size_t maxFileSize = 1024 * 1024 * 25;
  632. auto readScreenshotFileResult = AZ::Utils::ReadFile<AZStd::vector<uint8_t>>(path, maxFileSize);
  633. if (!readScreenshotFileResult.IsSuccess())
  634. {
  635. ReportScriptIssue(AZStd::string::format("Screenshot check failed. %s", readScreenshotFileResult.GetError().c_str()), traceLevel);
  636. imageComparisonResult.m_resultCode = ImageComparisonResult::ResultCode::FileNotFound;
  637. return false;
  638. }
  639. if (!AZ::Utils::PpmFile::CreateImageBufferFromPpm(readScreenshotFileResult.GetValue(), buffer, size, format))
  640. {
  641. ReportScriptIssue(AZStd::string::format("Screenshot check failed. Failed to read file '%s'", path.c_str()), traceLevel);
  642. imageComparisonResult.m_resultCode = ImageComparisonResult::ResultCode::FileNotLoaded;
  643. return false;
  644. }
  645. return true;
  646. }
  647. bool ScriptReporter::DiffImages(ImageComparisonResult& imageComparisonResult, const AZStd::string& expectedImageFilePath, const AZStd::string& actualImageFilePath, TraceLevel traceLevel)
  648. {
  649. using namespace AZ::Utils;
  650. AZStd::vector<uint8_t> actualImageBuffer;
  651. AZ::RHI::Size actualImageSize;
  652. AZ::RHI::Format actualImageFormat;
  653. if (!LoadPpmData(imageComparisonResult, actualImageFilePath, actualImageBuffer, actualImageSize, actualImageFormat, traceLevel))
  654. {
  655. return false;
  656. }
  657. AZStd::vector<uint8_t> expectedImageBuffer;
  658. AZ::RHI::Size expectedImageSize;
  659. AZ::RHI::Format expectedImageFormat;
  660. if (!LoadPpmData(imageComparisonResult, expectedImageFilePath, expectedImageBuffer, expectedImageSize, expectedImageFormat, traceLevel))
  661. {
  662. return false;
  663. }
  664. float diffScore = 0.0f;
  665. float filteredDiffScore = 0.0f;
  666. static constexpr float ImperceptibleDiffFilter = 0.01;
  667. ImageDiffResultCode rmsResult = AZ::Utils::CalcImageDiffRms(
  668. actualImageBuffer, actualImageSize, actualImageFormat,
  669. expectedImageBuffer, expectedImageSize, expectedImageFormat,
  670. &diffScore,
  671. &filteredDiffScore,
  672. ImperceptibleDiffFilter);
  673. if (rmsResult != ImageDiffResultCode::Success)
  674. {
  675. if(rmsResult == ImageDiffResultCode::SizeMismatch)
  676. {
  677. ReportScreenshotComparisonIssue(AZStd::string::format("Screenshot check failed. Sizes don't match. Expected %u x %u but was %u x %u.",
  678. expectedImageSize.m_width, expectedImageSize.m_height,
  679. actualImageSize.m_width, actualImageSize.m_height),
  680. expectedImageFilePath,
  681. actualImageFilePath,
  682. traceLevel);
  683. imageComparisonResult.m_resultCode = ImageComparisonResult::ResultCode::WrongSize;
  684. return false;
  685. }
  686. else if (rmsResult == ImageDiffResultCode::FormatMismatch || rmsResult == ImageDiffResultCode::UnsupportedFormat)
  687. {
  688. ReportScreenshotComparisonIssue(AZStd::string::format("Screenshot check failed. Could not compare screenshots due to a format issue."),
  689. expectedImageFilePath,
  690. actualImageFilePath,
  691. traceLevel);
  692. imageComparisonResult.m_resultCode = ImageComparisonResult::ResultCode::WrongFormat;
  693. return false;
  694. }
  695. }
  696. imageComparisonResult.m_standardDiffScore = diffScore;
  697. imageComparisonResult.m_filteredDiffScore = filteredDiffScore;
  698. imageComparisonResult.m_finalDiffScore = diffScore; // Set the final score to the standard score just in case the filtered one is ignored
  699. return true;
  700. }
  701. void ScriptReporter::UpdateAllLocalBaselineImages()
  702. {
  703. int failureCount = 0;
  704. int successCount = 0;
  705. for (ScriptReport& report : m_scriptReports)
  706. {
  707. for (ScreenshotTestInfo& screenshotTest : report.m_screenshotTests)
  708. {
  709. if (UpdateLocalBaselineImage(screenshotTest, false))
  710. {
  711. successCount++;
  712. }
  713. else
  714. {
  715. failureCount++;
  716. }
  717. }
  718. }
  719. ShowUpdateLocalBaselineResult(successCount, failureCount);
  720. }
  721. bool ScriptReporter::UpdateLocalBaselineImage(ScreenshotTestInfo& screenshotTest, bool showResultDialog)
  722. {
  723. const AZStd::string destinationFile = ScreenshotPaths::GetLocalBaseline(screenshotTest.m_screenshotFilePath);
  724. AZStd::string destinationFolder = destinationFile;
  725. AzFramework::StringFunc::Path::StripFullName(destinationFolder);
  726. m_fileIoErrorHandler.BusConnect();
  727. bool failed = false;
  728. if (!AZ::IO::LocalFileIO::GetInstance()->CreatePath(destinationFolder.c_str()))
  729. {
  730. failed = true;
  731. m_fileIoErrorHandler.ReportLatestIOError(AZStd::string::format("Failed to create folder '%s'.", destinationFolder.c_str()));
  732. }
  733. if (!AZ::IO::LocalFileIO::GetInstance()->Copy(screenshotTest.m_screenshotFilePath.c_str(), destinationFile.c_str()))
  734. {
  735. failed = true;
  736. m_fileIoErrorHandler.ReportLatestIOError(AZStd::string::format("Failed to copy '%s' to '%s'.", screenshotTest.m_screenshotFilePath.c_str(), destinationFile.c_str()));
  737. }
  738. m_fileIoErrorHandler.BusDisconnect();
  739. if (!failed)
  740. {
  741. // Since we just replaced the baseline image, we can update this screenshot test result as an exact match.
  742. // This will update the ImGui report dialog by the next frame.
  743. ClearImageComparisonResult(screenshotTest.m_localComparisonResult);
  744. }
  745. if (showResultDialog)
  746. {
  747. int successCount = !failed;
  748. int failureCount = failed;
  749. ShowUpdateLocalBaselineResult(successCount, failureCount);
  750. }
  751. return !failed;
  752. }
  753. bool ScriptReporter::UpdateSourceBaselineImage(ScreenshotTestInfo& screenshotTest, bool showResultDialog)
  754. {
  755. bool success = true;
  756. auto io = AZ::IO::LocalFileIO::GetInstance();
  757. // Get source folder
  758. if (m_officialBaselineSourceFolder.empty())
  759. {
  760. AZStd::string projectPath = AZ::Utils::GetProjectPath();
  761. AzFramework::StringFunc::Path::Join(projectPath.c_str(), "Scripts/ExpectedScreenshots", m_officialBaselineSourceFolder);
  762. if (!io->Exists(m_officialBaselineSourceFolder.c_str()))
  763. {
  764. AZ_Error("Automation", false, "Could not find source folder '%s'. Copying to source baseline can only be used on dev platforms.", m_officialBaselineSourceFolder.c_str());
  765. m_officialBaselineSourceFolder.clear();
  766. success = false;
  767. }
  768. }
  769. // Get official cache baseline file
  770. const AZStd::string cacheFilePath = ScreenshotPaths::GetOfficialBaseline(screenshotTest.m_screenshotFilePath);
  771. // Divide cache file path into components to we can access the file name and the parent folder
  772. AZStd::fixed_vector<AZ::IO::FixedMaxPathString, 16> reversePathComponents;
  773. auto GatherPathSegments = [&reversePathComponents](AZStd::string_view token)
  774. {
  775. reversePathComponents.emplace_back(token);
  776. };
  777. AzFramework::StringFunc::TokenizeVisitorReverse(cacheFilePath, GatherPathSegments, "/\\");
  778. // Source folder path
  779. // ".../AtomSampleViewer/Scripts/ExpectedScreenshots/" + "MyTestFolder/"
  780. AZStd::string sourceFolderPath = AZStd::string::format("%s\\%s", m_officialBaselineSourceFolder.c_str(), reversePathComponents[1].c_str());
  781. // Source file path
  782. // ".../AtomSampleViewer/Scripts/ExpectedScreenshots/MyTestFolder/" + "MyTest.ppm"
  783. AZStd::string sourceFilePath = AZStd::string::format("%s\\%s", sourceFolderPath.c_str(), reversePathComponents[0].c_str());
  784. m_fileIoErrorHandler.BusConnect();
  785. // Create parent folder if it doesn't exist
  786. if (success && !io->CreatePath(sourceFolderPath.c_str()))
  787. {
  788. success = false;
  789. m_fileIoErrorHandler.ReportLatestIOError(AZStd::string::format("Failed to create folder '%s'.", sourceFolderPath.c_str()));
  790. }
  791. // Replace source screenshot with new result
  792. if (success && !io->Copy(screenshotTest.m_screenshotFilePath.c_str(), sourceFilePath.c_str()))
  793. {
  794. success = false;
  795. m_fileIoErrorHandler.ReportLatestIOError(AZStd::string::format("Failed to copy '%s' to '%s'.", screenshotTest.m_screenshotFilePath.c_str(), sourceFilePath.c_str()));
  796. }
  797. m_fileIoErrorHandler.BusDisconnect();
  798. if (success)
  799. {
  800. // Since we just replaced the baseline image, we can update this screenshot test result as an exact match.
  801. // This will update the ImGui report dialog by the next frame.
  802. ClearImageComparisonResult(screenshotTest.m_officialComparisonResult);
  803. }
  804. if (showResultDialog)
  805. {
  806. AZStd::string message = "Destination: " + sourceFilePath + "\n";
  807. message += success
  808. ? AZStd::string::format("Copy successful!.\n")
  809. : AZStd::string::format("Copy failed!\n");
  810. m_messageBox.OpenPopupMessage("Update Baseline Image(s) Result", message);
  811. }
  812. return success;
  813. }
  814. void ScriptReporter::ClearImageComparisonResult(ImageComparisonResult& comparisonResult)
  815. {
  816. comparisonResult.m_resultCode = ImageComparisonResult::ResultCode::Pass;
  817. comparisonResult.m_standardDiffScore = 0.0f;
  818. comparisonResult.m_filteredDiffScore = 0.0f;
  819. comparisonResult.m_finalDiffScore = 0.0f;
  820. }
  821. void ScriptReporter::ShowUpdateLocalBaselineResult(int successCount, int failureCount)
  822. {
  823. AZStd::string message;
  824. if (failureCount == 0 && successCount == 0)
  825. {
  826. message = "No screenshots found.";
  827. }
  828. else
  829. {
  830. message = "Destination: " + ScreenshotPaths::GetLocalBaselineFolder(true) + "\n";
  831. if (successCount > 0)
  832. {
  833. message += AZStd::string::format("Successfully copied %d files.\n", successCount);
  834. }
  835. if (failureCount > 0)
  836. {
  837. message += AZStd::string::format("Failed to copy %d files.\n", failureCount);
  838. }
  839. }
  840. m_messageBox.OpenPopupMessage("Update Baseline Image(s) Result", message);
  841. }
  842. void ScriptReporter::CheckLatestScreenshot(const ImageComparisonToleranceLevel* toleranceLevel)
  843. {
  844. AZ_Assert(GetCurrentScriptReport(), "There is no active script");
  845. if (GetCurrentScriptReport() == nullptr || GetCurrentScriptReport()->m_screenshotTests.empty())
  846. {
  847. ReportScriptError("CheckLatestScreenshot() did not find any screenshots to check.");
  848. return;
  849. }
  850. ScreenshotTestInfo& screenshotTestInfo = GetCurrentScriptReport()->m_screenshotTests.back();
  851. if (toleranceLevel == nullptr)
  852. {
  853. screenshotTestInfo.m_officialComparisonResult.m_resultCode = ImageComparisonResult::ResultCode::NullImageComparisonToleranceLevel;
  854. ReportScriptError("Screenshot check failed. No ImageComparisonToleranceLevel provided.");
  855. return;
  856. }
  857. screenshotTestInfo.m_toleranceLevel = *toleranceLevel;
  858. screenshotTestInfo.m_officialBaselineScreenshotFilePath = ScreenshotPaths::GetOfficialBaseline(screenshotTestInfo.m_screenshotFilePath);
  859. if (screenshotTestInfo.m_officialBaselineScreenshotFilePath.empty())
  860. {
  861. ReportScriptError(AZStd::string::format("Screenshot check failed. Could not determine expected screenshot path for '%s'", screenshotTestInfo.m_screenshotFilePath.c_str()));
  862. screenshotTestInfo.m_officialComparisonResult.m_resultCode = ImageComparisonResult::ResultCode::FileNotFound;
  863. }
  864. else
  865. {
  866. bool imagesWereCompared = DiffImages(
  867. screenshotTestInfo.m_officialComparisonResult,
  868. screenshotTestInfo.m_officialBaselineScreenshotFilePath,
  869. screenshotTestInfo.m_screenshotFilePath,
  870. TraceLevel::Error);
  871. if (imagesWereCompared)
  872. {
  873. screenshotTestInfo.m_officialComparisonResult.m_finalDiffScore = toleranceLevel->m_filterImperceptibleDiffs ?
  874. screenshotTestInfo.m_officialComparisonResult.m_filteredDiffScore :
  875. screenshotTestInfo.m_officialComparisonResult.m_standardDiffScore;
  876. if (screenshotTestInfo.m_officialComparisonResult.m_finalDiffScore <= toleranceLevel->m_threshold)
  877. {
  878. screenshotTestInfo.m_officialComparisonResult.m_resultCode = ImageComparisonResult::ResultCode::Pass;
  879. }
  880. else
  881. {
  882. // Be aware there is an automation test script that looks for the "Screenshot check failed. Diff score" string text to report failures.
  883. // If you change this message, be sure to update the associated tests as well located here: "C:/path/to/Lumberyard/AtomSampleViewer/Standalone/PythonTests"
  884. ReportScreenshotComparisonIssue(
  885. AZStd::string::format("Screenshot check failed. Diff score %f exceeds threshold of %f ('%s').",
  886. screenshotTestInfo.m_officialComparisonResult.m_finalDiffScore, toleranceLevel->m_threshold, toleranceLevel->m_name.c_str()),
  887. screenshotTestInfo.m_officialBaselineScreenshotFilePath,
  888. screenshotTestInfo.m_screenshotFilePath,
  889. TraceLevel::Error);
  890. screenshotTestInfo.m_officialComparisonResult.m_resultCode = ImageComparisonResult::ResultCode::ThresholdExceeded;
  891. }
  892. }
  893. }
  894. screenshotTestInfo.m_localBaselineScreenshotFilePath = ScreenshotPaths::GetLocalBaseline(screenshotTestInfo.m_screenshotFilePath);
  895. if (screenshotTestInfo.m_localBaselineScreenshotFilePath.empty())
  896. {
  897. ReportScriptWarning(AZStd::string::format("Screenshot check failed. Could not determine local baseline screenshot path for '%s'", screenshotTestInfo.m_screenshotFilePath.c_str()));
  898. screenshotTestInfo.m_localComparisonResult.m_resultCode = ImageComparisonResult::ResultCode::FileNotFound;
  899. }
  900. else
  901. {
  902. // Local screenshots should be expected match 100% every time, otherwise warnings are reported. This will help developers track and investigate changes,
  903. // for example if they make local changes that impact some unrelated AtomSampleViewer sample in an unexpected way, they will see a warning about this.
  904. bool imagesWereCompared = DiffImages(
  905. screenshotTestInfo.m_localComparisonResult,
  906. screenshotTestInfo.m_localBaselineScreenshotFilePath,
  907. screenshotTestInfo.m_screenshotFilePath,
  908. TraceLevel::Warning);
  909. if (imagesWereCompared)
  910. {
  911. if(screenshotTestInfo.m_localComparisonResult.m_standardDiffScore == 0.0f)
  912. {
  913. screenshotTestInfo.m_localComparisonResult.m_resultCode = ImageComparisonResult::ResultCode::Pass;
  914. }
  915. else
  916. {
  917. ReportScreenshotComparisonIssue(
  918. AZStd::string::format("Screenshot check failed. Screenshot does not match the local baseline; something has changed. Diff score is %f.", screenshotTestInfo.m_localComparisonResult.m_standardDiffScore),
  919. screenshotTestInfo.m_localBaselineScreenshotFilePath,
  920. screenshotTestInfo.m_screenshotFilePath,
  921. TraceLevel::Warning);
  922. screenshotTestInfo.m_localComparisonResult.m_resultCode = ImageComparisonResult::ResultCode::ThresholdExceeded;
  923. }
  924. }
  925. }
  926. }
  927. } // namespace AtomSampleViewer