ui diff: better diff report on mobile (#37255)

* Add HTML template for UI diff report

* update .gitignore

* empty line

* use proper html tags

* remove paragraph tags

* simplify paths

* use h3

* use simpler replace instead; dynamically generate videos

* update diff html styling

* remove unnecessary

* fix

* use h4 instead

* padding on h4

* adjust heading margin

* revert

* use h4 again

* remove viewport

* Revert "remove viewport"

This reverts commit 7636920e556fc06bbd65cff7ecb4c3d31b11024d.
This commit is contained in:
David
2026-02-18 17:20:25 -06:00
committed by GitHub
parent d80cde6e41
commit b80d3e113b
4 changed files with 93 additions and 62 deletions
-5
View File
@@ -1,7 +1,2 @@
test
test_translations
test_ui/report_1
diff/**/*.mp4
diff/**/*.html
diff/.coverage
+2
View File
@@ -0,0 +1,2 @@
report
.coverage
+11 -57
View File
@@ -8,6 +8,7 @@ from pathlib import Path
from openpilot.common.basedir import BASEDIR
DIFF_OUT_DIR = Path(BASEDIR) / "selfdrive" / "ui" / "tests" / "diff" / "report"
HTML_TEMPLATE_PATH = Path(__file__).with_name("diff_template.html")
def extract_framehashes(video_path):
@@ -71,64 +72,17 @@ def generate_html_report(videos: tuple[str, str], basedir: str, different_frames
+ (f" Video {'2' if frame_delta > 0 else '1'} is longer by {abs(frame_delta)} frames." if frame_delta != 0 else "")
)
def render_video_cell(video_id: str, title: str, path: str, is_diff=False):
return f"""
<td width='33%'>
<p><strong>{title}</strong></p>
<video id='{video_id}' width='100%' autoplay muted {'' if is_diff else "onplay='syncVideos()'"}>
<source src='{os.path.join(basedir, os.path.basename(path))}' type='video/mp4'>
Your browser does not support the video tag.
</video>
</td>
"""
# Load HTML template and replace placeholders
html = HTML_TEMPLATE_PATH.read_text()
placeholders = {
"VIDEO1_SRC": os.path.join(basedir, os.path.basename(videos[0])),
"VIDEO2_SRC": os.path.join(basedir, os.path.basename(videos[1])),
"DIFF_SRC": os.path.join(basedir, diff_video_name),
"RESULT_TEXT": result_text,
}
for key, value in placeholders.items():
html = html.replace(f"${key}", value)
html = f"""<h2>UI Diff</h2>
<table>
<tr>
{render_video_cell("video1", "Video 1", videos[0])}
{render_video_cell("video2", "Video 2", videos[1])}
{render_video_cell("diffVideo", "Pixel Diff", diff_video_name, is_diff=True)}
</tr>
</table>
<script>
const videos = [
document.getElementById('video1'),
document.getElementById('video2'),
document.getElementById('diffVideo'),
];
const isEnded = (v) => v.ended || (Number.isFinite(v.duration) && v.currentTime >= (v.duration - 0.05));
const playAll = () => videos.forEach((v) => v.play());
function syncVideos() {{
const t = Math.min(...videos.map((v) => v.currentTime));
videos.forEach((v) => {{ v.currentTime = t; }});
playAll();
}}
function handleEnded(endedVideo) {{
endedVideo.pause();
if (videos.every(isEnded)) {{
videos.forEach((v) => {{ v.currentTime = 0; }});
playAll();
}}
}}
videos.forEach((v) => {{
v.addEventListener('timeupdate', () => {{
videos.forEach((other) => {{
if (other !== v && !isEnded(other) && Math.abs(v.currentTime - other.currentTime) > 0.1) {{
other.currentTime = v.currentTime;
if (other.paused) other.play();
}}
}});
}});
v.addEventListener('ended', () => handleEnded(v));
}});
</script>
<hr>
<p><strong>Results:</strong> {result_text}</p>
"""
return html
@@ -0,0 +1,80 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>UI Diff Report</title>
<style>
h1, h2, h3, h4 {
margin: 0.5em 0 0.25em;
}
#videoTable {
width: 100%;
}
@media (max-width: 800px) {
#videoTable tr {
display: flex;
flex-direction: column;
}
#videoTable td {
width: 100% !important;
}
}
</style>
</head>
<body>
<h2>UI Diff</h2>
<table id='videoTable'></table>
<hr>
<p><strong>Results:</strong> $RESULT_TEXT</p>
<script>
const videoDefs = [
{ id: 'video1', title: 'Video 1', src: '$VIDEO1_SRC', sync: true },
{ id: 'video2', title: 'Video 2', src: '$VIDEO2_SRC', sync: true },
{ id: 'diffVideo', title: 'Pixel Diff', src: '$DIFF_SRC', sync: false },
];
const row = document.getElementById('videoTable').insertRow();
videoDefs.forEach(({ id, title, src, sync }) => {
const td = row.insertCell();
td.width = '33%';
td.innerHTML = `<h4>${title}</h4>
<video id='${id}' width='100%' autoplay muted ${sync ? "onplay='syncVideos()'" : ''}>
<source src='${src}' type='video/mp4'>
Your browser does not support the video tag.
</video>`;
});
const videos = videoDefs.map(({ id }) => document.getElementById(id));
const isEnded = (v) => v.ended || (Number.isFinite(v.duration) && v.currentTime >= (v.duration - 0.05));
const playAll = () => videos.forEach((v) => v.play());
function syncVideos() {
const t = Math.min(...videos.map((v) => v.currentTime));
videos.forEach((v) => { v.currentTime = t; });
playAll();
}
function handleEnded(endedVideo) {
endedVideo.pause();
if (videos.every(isEnded)) {
videos.forEach((v) => { v.currentTime = 0; });
playAll();
}
}
videos.forEach((v) => {
v.addEventListener('timeupdate', () => {
videos.forEach((other) => {
if (other !== v && !isEnded(other) && Math.abs(v.currentTime - other.currentTime) > 0.1) {
other.currentTime = v.currentTime;
if (other.paused) other.play();
}
});
});
v.addEventListener('ended', () => handleEnded(v));
});
</script>
</body>
</html>