aboutsummaryrefslogtreecommitdiff
path: root/tools/tests/test_sim_screenshot.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/tests/test_sim_screenshot.py')
-rw-r--r--tools/tests/test_sim_screenshot.py94
1 files changed, 94 insertions, 0 deletions
diff --git a/tools/tests/test_sim_screenshot.py b/tools/tests/test_sim_screenshot.py
new file mode 100644
index 0000000..7a1a087
--- /dev/null
+++ b/tools/tests/test_sim_screenshot.py
@@ -0,0 +1,94 @@
+# Author: cirakg & claude ai
+# Summary: Tests for tools/sim_screenshot.py (finding, cropping and comparing simulator screens).
+"""Tests for tools/sim_screenshot.py, using synthetic simulator windows."""
+
+from __future__ import annotations
+
+import json
+from pathlib import Path
+
+import sim_screenshot as ss
+from PIL import Image, ImageDraw
+
+DISPLAY = {"x": 20, "y": 30, "width": 60, "height": 60}
+
+
+def make_device(tmp_path: Path, shape: str = "round") -> Path:
+ """A fake device folder: a striped bezel drawing and its simulator.json."""
+ device_dir = tmp_path / "fakewatch"
+ device_dir.mkdir()
+ bezel = Image.new("RGBA", (100, 120), (0, 0, 0, 0))
+ draw = ImageDraw.Draw(bezel)
+ for y in range(0, 120, 6): # stripes make the offset unambiguous
+ draw.rectangle((0, y, 99, y + 2), fill=(120 + y, 60, 200 - y, 255))
+ draw.rectangle((DISPLAY["x"], DISPLAY["y"], DISPLAY["x"] + 59, DISPLAY["y"] + 59), fill=(0, 0, 0, 255))
+ bezel.save(device_dir / "fake.png")
+ simulator = {"image": "fake.png", "display": {"location": DISPLAY, "shape": shape}}
+ (device_dir / "simulator.json").write_text(json.dumps(simulator))
+ return device_dir
+
+
+def make_window(device_dir: Path, offset: tuple[int, int], screen: Image.Image) -> Image.Image:
+ """A simulator window: grey background, the drawing at offset, the app's screen on top."""
+ window = Image.new("RGB", (100 + offset[0], 150), (230, 230, 230))
+ bezel = ss.open_image(device_dir / "fake.png").convert("RGBA")
+ window.paste(bezel, offset, bezel)
+ window.paste(screen, (offset[0] + DISPLAY["x"], offset[1] + DISPLAY["y"]))
+ return window
+
+
+def app_screen(text_colour: tuple[int, int, int] = (255, 255, 255)) -> Image.Image:
+ screen = Image.new("RGB", (60, 60), (0, 0, 0))
+ ImageDraw.Draw(screen).rectangle((20, 25, 40, 35), fill=text_colour)
+ return screen
+
+
+def test_finds_offset_and_crops_screen(tmp_path: Path) -> None:
+ device_dir = make_device(tmp_path)
+ display = ss.load_display(device_dir)
+ window = make_window(device_dir, (1, 26), app_screen())
+ bezel = ss.bezel_only(ss.open_image(device_dir / display.image), display)
+ dx, dy, score = ss.find_offset(window, bezel)
+ assert (dx, dy) == (1, 26)
+ assert score < 1.0
+ screen = ss.crop_screen(window, (dx, dy), display)
+ assert screen.size == (60, 60)
+ assert screen.getpixel((30, 30)) == (255, 255, 255)
+ assert screen.getpixel((0, 0)) == (0, 0, 0) # outside the round display
+
+
+def test_blank_screen_is_detected() -> None:
+ assert ss.is_blank(Image.new("RGB", (60, 60), (0, 0, 0)))
+ assert not ss.is_blank(app_screen())
+
+
+def test_compare_counts_differing_pixels() -> None:
+ fraction, _ = ss.compare(app_screen(), app_screen())
+ assert fraction == 0
+ fraction, overlay = ss.compare(app_screen((255, 255, 255)), app_screen((0, 0, 255)))
+ assert fraction == (21 * 11) / (60 * 60)
+ assert overlay.getpixel((30, 30)) == (255, 0, 0)
+ assert ss.compare(app_screen(), Image.new("RGB", (10, 10)))[0] == 1.0
+
+
+def test_main_flow_approve_then_compare(tmp_path: Path) -> None:
+ device_dir = make_device(tmp_path)
+ raw, out, golden = tmp_path / "raw.png", tmp_path / "out.png", tmp_path / "golden" / "fakewatch.png"
+ make_window(device_dir, (1, 26), app_screen()).save(raw)
+ args = ["--raw", str(raw), "--device-dir", str(device_dir), "--out", str(out), "--golden", str(golden)]
+ assert ss.main(args) == ss.EXIT_NO_GOLDEN
+ assert ss.main([*args, "--approve"]) == ss.EXIT_OK
+ assert ss.open_image(golden).info["Author"] == "cirakg & claude ai"
+ assert ss.main(args) == ss.EXIT_OK
+
+ make_window(device_dir, (1, 26), app_screen((0, 255, 0))).save(raw)
+ assert ss.main(args) == ss.EXIT_DIFFERENT
+ assert (tmp_path / "out-diff.png").exists()
+
+
+def test_missing_device_files_are_an_error(tmp_path: Path) -> None:
+ device_dir = make_device(tmp_path)
+ (device_dir / "fake.png").unlink()
+ raw = tmp_path / "raw.png"
+ Image.new("RGB", (100, 150)).save(raw)
+ assert ss.main(["--raw", str(raw), "--device-dir", str(device_dir), "--out", str(tmp_path / "o.png")]) == 2