Repository: bmihelac/pytest-image-snapshot (language: python)

Issue:
Fail on missing snapshot
I'm running this plugin in CI, and it's fantastic!

A little problem that I have is that I'd like the tests to fail if a screenshot is missing (but only in CI, locally I really like the fact that's just creating the missing ones).
What about adding some `--image-snapshot-fail-if-missing`?

I'd prepare a PR if you think that fits. (I mean otherwise I can continue with `git diff --exit-code` as well...)


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 04fdf27..cdaaaf4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.3.0 (2023-12-01)
+
+* feat: improve error messages ([5d622bc](https://github.com/bmihelac/pytest-image-snapshot/commit/5d622bc))
+
 ## 0.2.2 (2023-12-01)
 
 * fix: default threshold should not be None ([470c4b0](https://github.com/bmihelac/pytest-image-snapshot/commit/470c4b0))
diff --git a/README.md b/README.md
index 6b6fd44..644a59d 100644
--- a/README.md
+++ b/README.md
@@ -87,6 +87,14 @@ Use the `--image-snapshot-update` flag to update or create new reference snapsho
 pytest --image-snapshot-update
 ```
 
+### Failing when snapshots are missing (`--image-snapshot-fail-if-missing`)
+
+Use the `--image-snapshot-fail-if-missing` flag to fail the test when the snapshot is missing. This is particularly useful in CI check to ensure that all snapshots are present and up-to-date.
+
+```bash
+pytest --image-snapshot-fail-if-missing
+```
+
 ## Example
 
 Visual regression test for [Django](https://www.djangoproject.com/) application home page with [playwright](https://playwright.dev/python/docs/intro):
diff --git a/setup.py b/setup.py
index 661a0fc..750d2d7 100644
--- a/setup.py
+++ b/setup.py
@@ -13,7 +13,7 @@ def read(fname):
 
 setup(
     name='pytest-image-snapshot',
-    version='0.2.2',
+    version='0.3.0',
     author='Bojan Mihelac',
     author_email='bojan@informatikamihelac.com',
     maintainer='Bojan Mihelac',


Test patch:
diff --git a/pytest_image_snapshot.py b/pytest_image_snapshot.py
index 852636f..372c59e 100644
--- a/pytest_image_snapshot.py
+++ b/pytest_image_snapshot.py
@@ -9,10 +9,19 @@ class ImageMismatchError(AssertionError):
     """Exception raised when images do not match."""
 
 
+class ImageNotFoundError(AssertionError):
+    """Exception raised when snapshot is missing."""
+
+
 def pytest_addoption(parser):
     parser.addoption(
         "--image-snapshot-update", action="store_true", help="Update image snapshots"
     )
+    parser.addoption(
+        "--image-snapshot-fail-if-missing",
+        action="store_true",
+        help="Fail if snapshot is missing, useful in CI",
+    )
 
 
 def extend_to_match_size(img1, img2):
@@ -50,6 +59,7 @@ def image_snapshot(request):
     def _image_snapshot(img, img_path, threshold=None):
         config = request.config
         update_snapshots = config.getoption("--image-snapshot-update")
+        fail_if_missing = config.getoption("--image-snapshot-fail-if-missing")
 
         img_path = Path(img_path)
         if not update_snapshots and img_path.exists():
@@ -79,12 +89,20 @@ def _image_snapshot(img, img_path, threshold=None):
                     if config.option.verbose > 1:
                         src_image.show(title="original")
                         img.show(title="new")
+                verbose_msg = (
+                    " Use -v or -vv to display diff."
+                    if not config.option.verbose
+                    else ""
+                )
+                snapshot_update_msg = " Use --image-snapshot-update to update snapshot."
                 raise ImageMismatchError(
-                    f"Image does not match the snapshot stored in {img_path}"
+                    f"Image does not match the snapshot stored in {img_path}."
+                    f"{verbose_msg}{snapshot_update_msg}"
                 )
             else:
                 return
+        elif fail_if_missing and not img_path.exists():
+            raise ImageNotFoundError(f"Snapshot {img_path} not found.")
         img.save(img_path)
-        return
 
     return _image_snapshot
diff --git a/tests/test_image_snapshot.py b/tests/test_image_snapshot.py
index 0e67641..31cef34 100644
--- a/tests/test_image_snapshot.py
+++ b/tests/test_image_snapshot.py
@@ -14,6 +14,7 @@ def test_image(pytester):
 def test_image_snapshot_fixture(pytester, test_image):
     pytester.makepyfile(
         """
+        from pathlib import Path
         from PIL import Image
         import pytest
 
@@ -25,6 +26,12 @@ def test_different_image(image_snapshot):
             image = Image.new('RGB', (150, 150), 'white')
             with pytest.raises(AssertionError):
                 image_snapshot(image, "white.png")
+
+        def test_create_image_if_missing(image_snapshot):
+            assert not Path("red.png").exists()
+            image = Image.new('RGB', (100, 100), 'red')
+            image_snapshot(image, "red.png")
+            assert Path("red.png").exists()
     """
     )
 
@@ -37,3 +44,22 @@ def test_different_image(image_snapshot):
     )
 
     assert result.ret == 0
+
+
+def test_image_snapshot_fixture_fail_on_missing(pytester):
+    pytester.makepyfile(
+        """
+        from pathlib import Path
+        from PIL import Image
+        import pytest
+
+        def test_fail_if_image_missing(image_snapshot):
+            assert not Path("red.png").exists()
+            image = Image.new('RGB', (100, 100), 'red')
+            image_snapshot(image, "red.png")
+    """
+    )
+
+    result = pytester.runpytest("--image-snapshot-fail-if-missing")
+
+    result.assert_outcomes(failed=1)