summaryrefslogtreecommitdiff
path: root/tools/tests/test_sim_screenshot.py
blob: 7a1a087d593083243b866e3137953b359229459d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
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