visualcheck 0.2.2__tar.gz → 0.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {visualcheck-0.2.2 → visualcheck-0.2.4}/PKG-INFO +1 -1
- {visualcheck-0.2.2 → visualcheck-0.2.4}/pyproject.toml +1 -1
- {visualcheck-0.2.2 → visualcheck-0.2.4}/src/visualcheck/baseline.py +37 -1
- {visualcheck-0.2.2 → visualcheck-0.2.4}/src/visualcheck/capture.py +23 -1
- {visualcheck-0.2.2 → visualcheck-0.2.4}/src/visualcheck/figma.py +9 -1
- {visualcheck-0.2.2 → visualcheck-0.2.4}/src/visualcheck.egg-info/PKG-INFO +1 -1
- {visualcheck-0.2.2 → visualcheck-0.2.4}/README.md +0 -0
- {visualcheck-0.2.2 → visualcheck-0.2.4}/setup.cfg +0 -0
- {visualcheck-0.2.2 → visualcheck-0.2.4}/src/visualcheck/__init__.py +0 -0
- {visualcheck-0.2.2 → visualcheck-0.2.4}/src/visualcheck/cli.py +0 -0
- {visualcheck-0.2.2 → visualcheck-0.2.4}/src/visualcheck/config.py +0 -0
- {visualcheck-0.2.2 → visualcheck-0.2.4}/src/visualcheck/diff_engine.py +0 -0
- {visualcheck-0.2.2 → visualcheck-0.2.4}/src/visualcheck/report.py +0 -0
- {visualcheck-0.2.2 → visualcheck-0.2.4}/src/visualcheck/runner.py +0 -0
- {visualcheck-0.2.2 → visualcheck-0.2.4}/src/visualcheck/schemas/visualcheck.schema.json +0 -0
- {visualcheck-0.2.2 → visualcheck-0.2.4}/src/visualcheck/templates/report.html +0 -0
- {visualcheck-0.2.2 → visualcheck-0.2.4}/src/visualcheck/utils.py +0 -0
- {visualcheck-0.2.2 → visualcheck-0.2.4}/src/visualcheck/views.py +0 -0
- {visualcheck-0.2.2 → visualcheck-0.2.4}/src/visualcheck.egg-info/SOURCES.txt +0 -0
- {visualcheck-0.2.2 → visualcheck-0.2.4}/src/visualcheck.egg-info/dependency_links.txt +0 -0
- {visualcheck-0.2.2 → visualcheck-0.2.4}/src/visualcheck.egg-info/entry_points.txt +0 -0
- {visualcheck-0.2.2 → visualcheck-0.2.4}/src/visualcheck.egg-info/requires.txt +0 -0
- {visualcheck-0.2.2 → visualcheck-0.2.4}/src/visualcheck.egg-info/top_level.txt +0 -0
|
@@ -3,8 +3,8 @@ requires = ["setuptools>=68", "wheel"]
|
|
|
3
3
|
build-backend = "setuptools.build_meta"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
|
+
version = "0.2.4"
|
|
6
7
|
name = "visualcheck"
|
|
7
|
-
version= "0.2.2"
|
|
8
8
|
description = "Visual regression checking with figma-first baselines, pixel+SSIM diffing, ignore regions, and HTML reports."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -23,11 +23,47 @@ def baseline_path(cfg: dict, kind: str, view_id: str, snapshot_id: str) -> Path:
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
def resolve_baseline(cfg: dict, view_id: str, snapshot_id: str) -> Tuple[Optional[Path], str]:
|
|
26
|
-
"""Return (path, kind). kind is figma/runtime/none.
|
|
26
|
+
"""Return (path, kind). kind is figma/runtime/none.
|
|
27
|
+
|
|
28
|
+
If a figma baseline is configured but not present on disk, attempt a sync
|
|
29
|
+
from the Figma API (if configured) and re-check. This allows workflows where
|
|
30
|
+
the authoritative baseline lives in Figma and should be used before creating
|
|
31
|
+
a runtime baseline.
|
|
32
|
+
"""
|
|
27
33
|
for kind in cfg["baseline"]["priority"]:
|
|
28
34
|
p = baseline_path(cfg, kind, view_id, snapshot_id)
|
|
29
35
|
if p.exists():
|
|
36
|
+
# If figma baseline exists, make it the authoritative runtime baseline
|
|
37
|
+
if kind == "figma":
|
|
38
|
+
try:
|
|
39
|
+
runtime_p = baseline_path(cfg, "runtime", view_id, snapshot_id)
|
|
40
|
+
ensure_dir(runtime_p.parent)
|
|
41
|
+
# overwrite runtime baseline with figma baseline so Figma is authoritative
|
|
42
|
+
shutil.copy2(p, runtime_p)
|
|
43
|
+
return runtime_p, "figma"
|
|
44
|
+
except Exception:
|
|
45
|
+
# If copying fails, return the figma path
|
|
46
|
+
return p, "figma"
|
|
30
47
|
return p, kind
|
|
48
|
+
|
|
49
|
+
# If figma is configured as a priority and missing locally, try to fetch it
|
|
50
|
+
if "figma" in (cfg["baseline"]["priority"] or []):
|
|
51
|
+
try:
|
|
52
|
+
summary = figma_sync_baselines(cfg) # best-effort
|
|
53
|
+
# after sync, re-check for figma baseline
|
|
54
|
+
p = baseline_path(cfg, "figma", view_id, snapshot_id)
|
|
55
|
+
if p.exists():
|
|
56
|
+
try:
|
|
57
|
+
runtime_p = baseline_path(cfg, "runtime", view_id, snapshot_id)
|
|
58
|
+
ensure_dir(runtime_p.parent)
|
|
59
|
+
shutil.copy2(p, runtime_p)
|
|
60
|
+
return runtime_p, "figma"
|
|
61
|
+
except Exception:
|
|
62
|
+
return p, "figma"
|
|
63
|
+
except Exception:
|
|
64
|
+
# never fail; always fall through to runtime/none
|
|
65
|
+
pass
|
|
66
|
+
|
|
31
67
|
return None, "none"
|
|
32
68
|
|
|
33
69
|
|
|
@@ -93,9 +93,31 @@ def capture_all(cfg: dict, env: str, out_dir: Path, headed: bool = False, slow_m
|
|
|
93
93
|
url = _abs_url(base_url, pg["url"])
|
|
94
94
|
print(f"[capture] view={view_id} snapshot={snap_id} url={url}")
|
|
95
95
|
page.goto(url, wait_until="domcontentloaded")
|
|
96
|
+
# Wait for the configured wait_for selector if present, then allow extra time for lazy loads.
|
|
96
97
|
if pg.get("wait_for"):
|
|
97
98
|
page.wait_for_selector(pg["wait_for"], timeout=30000)
|
|
98
|
-
|
|
99
|
+
|
|
100
|
+
# Optional additional wait and smart auto-scroll to ensure page fully renders dynamic content
|
|
101
|
+
extra_wait_ms = int(cfg.get('capture', {}).get('extra_wait_ms', 1000))
|
|
102
|
+
scroll_times = int(cfg.get('capture', {}).get('scrolls', 5))
|
|
103
|
+
scroll_delay = int(cfg.get('capture', {}).get('scroll_delay_ms', 700))
|
|
104
|
+
|
|
105
|
+
page.wait_for_timeout(extra_wait_ms)
|
|
106
|
+
|
|
107
|
+
# gentle auto-scroll to trigger lazy-loading images/content
|
|
108
|
+
try:
|
|
109
|
+
for i in range(max(1, scroll_times)):
|
|
110
|
+
page.keyboard.press('PageDown')
|
|
111
|
+
page.wait_for_timeout(scroll_delay)
|
|
112
|
+
except Exception:
|
|
113
|
+
# ignore if page doesn't accept keyboard events
|
|
114
|
+
try:
|
|
115
|
+
# fallback to mouse wheel
|
|
116
|
+
for i in range(max(1, scroll_times)):
|
|
117
|
+
page.mouse.wheel(0, 800)
|
|
118
|
+
page.wait_for_timeout(scroll_delay)
|
|
119
|
+
except Exception:
|
|
120
|
+
pass
|
|
99
121
|
|
|
100
122
|
full_page = bool(pg.get("full_page", True))
|
|
101
123
|
png_path = view_out / f"{slug(snap_id)}.png"
|
|
@@ -19,7 +19,15 @@ from decouple import config
|
|
|
19
19
|
def _get_token(cfg: dict) -> Optional[str]:
|
|
20
20
|
fig = cfg.get("figma") or {}
|
|
21
21
|
token_env = fig.get("token_env") or "FIGMA_TOKEN"
|
|
22
|
-
#
|
|
22
|
+
# Special-case: when token_env is the canonical "FIGMA_TOKEN" string we must read
|
|
23
|
+
# exclusively from the repo .env (python-decouple) and NOT fall back to system env.
|
|
24
|
+
# This avoids accidental use of a different host env variable when the YAML explicitly
|
|
25
|
+
# requested the canonical name.
|
|
26
|
+
if token_env == "FIGMA_TOKEN":
|
|
27
|
+
return config(token_env, default=None)
|
|
28
|
+
|
|
29
|
+
# For custom token_env names, prefer .env but allow OS-level fallback (useful for
|
|
30
|
+
# host-level overrides like OPENCLAW_FIGMA_TOKEN or similarly named vars).
|
|
23
31
|
return config(token_env, default=os.environ.get(token_env))
|
|
24
32
|
|
|
25
33
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|