diff --git a/tests/ui_tests/reporting/html.py b/tests/ui_tests/reporting/html.py
index 119970513..1c933b671 100644
--- a/tests/ui_tests/reporting/html.py
+++ b/tests/ui_tests/reporting/html.py
@@ -56,6 +56,12 @@ def image_column(hash: str | None, cur_dir: Path, img_id: str | None = None) ->
i("missing")
+def diff_column() -> None:
+ """Put diff image into table as one cell."""
+ with td(bgcolor="white"):
+ a("Click to show")
+
+
def _relative_path(cur_dir: Path, path_to: Path) -> str:
"""Find best relative path to refer to path_to from cur_dir."""
cur_dir = cur_dir.resolve()
@@ -91,6 +97,7 @@ def diff_table(diff: Iterable[tuple[str | None, str | None]], cur_dir: Path) ->
background = "white"
else:
background = "red"
- with tr(bgcolor=background):
+ with tr(bgcolor=background, onclick="createDiff(this)"):
image_column(left, cur_dir)
image_column(right, cur_dir)
+ diff_column()
diff --git a/tests/ui_tests/reporting/testreport.css b/tests/ui_tests/reporting/testreport.css
index 24b53ce50..262e1e433 100644
--- a/tests/ui_tests/reporting/testreport.css
+++ b/tests/ui_tests/reporting/testreport.css
@@ -69,6 +69,12 @@ tr.bad a:visited {
width: 256px;
}
+.model-TR canvas {
+ image-rendering: pixelated;
+ width: 256px;
+}
+
+
/* GIF styling */
/* Style the input field */
diff --git a/tests/ui_tests/reporting/testreport.js b/tests/ui_tests/reporting/testreport.js
index bf192774e..7dbdbd319 100644
--- a/tests/ui_tests/reporting/testreport.js
+++ b/tests/ui_tests/reporting/testreport.js
@@ -162,5 +162,67 @@ function onLoad() {
}
}
+var module = {};
+
+function getImageData(image) {
+ // Get original image size
+ const width = image.naturalWidth;
+ const height = image.naturalHeight;
+
+ // Create 2D canvas
+ let canvas = document.createElement('canvas');
+ canvas.width = width;
+ canvas.height = height;
+ let context = canvas.getContext("2d");
+
+ // Draw the image
+ context.drawImage(image, 0, 0);
+
+ // Return image raw data
+ return context.getImageData(0, 0, width, height);
+}
+
+
+function createTableDiff(table) {
+ // Process all rows in the table\
+ // (if the row doesn't contain two images, it's skipped)
+ table.querySelectorAll("tr").forEach((row) => {
+ createRowDiff(row);
+ });
+}
+
+function createRowDiff(row) {
+ // Find an element with recorded image
+ recImg = row.querySelector("td:nth-child(1) > img");
+ // Find an element with the current image
+ curImg = row.querySelector("td:nth-child(2) > img");
+ // Skip if we haven't found two images
+ if (recImg == null || curImg == null) {
+ return;
+ }
+
+ // Get images's raw data
+ recData = getImageData(recImg);
+ curData = getImageData(curImg);
+
+ const width = recImg.naturalWidth;
+ const height = recImg.naturalHeight;
+
+ // Create canvas for diff result
+ let difImg = document.createElement('canvas')
+ difImg.width = width;
+ difImg.height = height;
+ let difCtx = difImg.getContext("2d")
+
+ // Process differences
+ const difData = difCtx.createImageData(width, height);
+ options = {threshold: 0.0, includeAA: true};
+ pixelmatch(recData.data, curData.data, difData.data, width, height, options);
+ difCtx.putImageData(difData, 0, 0);
+
+ // Put the result into the 3rd column
+ row.querySelector("td:nth-child(3)").replaceChildren(difImg)
+}
+
window.onload = onLoad
diff --git a/tests/ui_tests/reporting/testreport.py b/tests/ui_tests/reporting/testreport.py
index 76d2282cf..969589d1c 100644
--- a/tests/ui_tests/reporting/testreport.py
+++ b/tests/ui_tests/reporting/testreport.py
@@ -7,7 +7,22 @@ from pathlib import Path
import dominate
import dominate.tags as t
-from dominate.tags import a, div, h1, h2, hr, i, p, span, strong, table, td, th, tr
+from dominate.tags import (
+ a,
+ div,
+ h1,
+ h2,
+ hr,
+ i,
+ p,
+ script,
+ span,
+ strong,
+ table,
+ td,
+ th,
+ tr,
+)
from dominate.util import text
from ..common import FixturesType, TestCase, TestResult
@@ -338,7 +353,13 @@ def failed(result: TestResult) -> Path:
doc = document(
title=result.test.id, actual_hash=result.actual_hash, model=result.test.model
)
+ with doc.head:
+ script(
+ type="text/javascript", src="https://cdn.jsdelivr.net/npm/pixelmatch@5.3.0"
+ )
+
with doc:
+
_header(result.test.id, result.expected_hash, result.actual_hash)
with div(id="markbox", _class="script-hidden"):
@@ -353,10 +374,11 @@ def failed(result: TestResult) -> Path:
strong("WARNING:")
text(" failed to download recorded fixtures. Is this a new test case?")
- with table(border=1, width=600):
+ with table(border=1, width=600, onclick="createTableDiff(this)"):
with tr():
th("Expected")
th("Actual")
+ th("Diff")
html.diff_table(result.diff_lines(), TESTREPORT_PATH / "failed")