# 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