pytest-visionspec 0.2.0__tar.gz → 0.2.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytest-visionspec
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Pytest plugin that auto-reports test results with screenshots to VisionSpec
5
5
  Requires-Python: >=3.9
6
6
  Description-Content-Type: text/markdown
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pytest-visionspec"
7
- version = "0.2.0"
7
+ version = "0.2.1"
8
8
  description = "Pytest plugin that auto-reports test results with screenshots to VisionSpec"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -3,13 +3,13 @@
3
3
  Usage:
4
4
  pip install pytest-visionspec
5
5
  Set env vars: VS_API_KEY, VS_API_URL
6
- Tag tests: @pytest.mark.vs("journey-id")
6
+ Tag tests: @pytest.mark.vs("journey-id", revision="slug")
7
7
 
8
8
  The plugin:
9
- 1. Detects @pytest.mark.vs("journey-id") tags on tests
9
+ 1. Detects @pytest.mark.vs("journey-id", revision="slug") tags on tests
10
10
  2. After each test, captures a Playwright screenshot (if a page fixture exists)
11
11
  3. Uploads screenshots to VisionSpec storage
12
- 4. Posts the result (pass/fail + screenshot_urls + screenshot_labels) to VisionSpec
12
+ 4. Posts the result (pass/fail + screenshot_urls + screenshot_metadata) to VisionSpec
13
13
 
14
14
  Labeled screenshots:
15
15
  Use the vs_screenshot fixture to capture labeled screenshots mid-test:
@@ -20,7 +20,9 @@ Labeled screenshots:
20
20
  vs_screenshot("dark mode") # captures + labels
21
21
  """
22
22
 
23
+ import hashlib
23
24
  import os
25
+ import struct
24
26
  import sys
25
27
  import glob
26
28
  from pathlib import Path
@@ -124,9 +126,10 @@ def pytest_runtest_makereport(item, call):
124
126
  # Upload any captured screenshots/videos
125
127
  screenshot_urls = []
126
128
  screenshot_labels = []
129
+ screenshot_metadata = []
127
130
  video_url = None
128
131
  try:
129
- screenshot_urls, screenshot_labels, video_url = _collect_and_upload_artifacts(item, api_key, api_url)
132
+ screenshot_urls, screenshot_labels, screenshot_metadata, video_url = _collect_and_upload_artifacts(item, api_key, api_url)
130
133
  except Exception:
131
134
  pass
132
135
 
@@ -158,6 +161,7 @@ def pytest_runtest_makereport(item, call):
158
161
  "versions": _get_versions(),
159
162
  "screenshot_urls": screenshot_urls,
160
163
  "screenshot_labels": screenshot_labels,
164
+ "screenshot_metadata": screenshot_metadata,
161
165
  "video_url": video_url,
162
166
  "revision_slug": revision,
163
167
  })
@@ -184,22 +188,39 @@ def _safe_filename(name: str) -> str:
184
188
  return name.replace("[", "-").replace("]", "").replace("/", "-").replace(":", "-")
185
189
 
186
190
 
191
+ def _png_dimensions(data: bytes) -> tuple:
192
+ """Extract width and height from PNG header without Pillow."""
193
+ if len(data) < 24 or data[:8] != b'\x89PNG\r\n\x1a\n':
194
+ return (0, 0)
195
+ # IHDR chunk starts at byte 8: 4 bytes length + 4 bytes "IHDR" + 4 bytes width + 4 bytes height
196
+ width, height = struct.unpack('>II', data[16:24])
197
+ return (width, height)
198
+
199
+
200
+ def _file_metadata(data: bytes, url: str) -> dict:
201
+ """Build screenshot_metadata entry from file bytes and uploaded URL."""
202
+ width, height = _png_dimensions(data)
203
+ return {
204
+ "url": url,
205
+ "width": width,
206
+ "height": height,
207
+ "size_bytes": len(data),
208
+ "sha256": hashlib.sha256(data).hexdigest(),
209
+ }
210
+
211
+
187
212
  def _collect_and_upload_artifacts(item, api_key, api_url):
188
213
  """Find screenshots/videos in test-results/ and upload them.
189
214
 
190
- Returns (screenshot_urls, screenshot_labels, video_url).
191
- Labels are extracted from filenames: 'labeled-N-some-label.png' -> 'some label'.
192
- The final end-of-test screenshot gets label 'end'.
215
+ Returns (screenshot_urls, screenshot_labels, screenshot_metadata, video_url).
193
216
  """
194
217
  screenshot_urls = []
195
218
  screenshot_labels = []
219
+ screenshot_metadata = []
196
220
  video_url = None
197
221
 
198
222
  safe_name = _safe_filename(item.name)
199
223
 
200
- # Get labeled captures from the vs_screenshot fixture (if used)
201
- vs_labels = getattr(item, "_vs_screenshot_labels", [])
202
-
203
224
  patterns = [
204
225
  f"test-results/{safe_name}/*.png",
205
226
  f"test-results/{safe_name}/*.jpg",
@@ -228,9 +249,10 @@ def _collect_and_upload_artifacts(item, api_key, api_url):
228
249
  "webm": "video/webm", "mp4": "video/mp4",
229
250
  }.get(ext, "application/octet-stream")
230
251
 
252
+ file_bytes = path.read_bytes()
231
253
  upload_r = httpx.post(f"{api_url}/api/upload",
232
254
  headers={"X-API-Key": api_key, "Content-Type": mime, "X-Filename": path.name},
233
- content=path.read_bytes(),
255
+ content=file_bytes,
234
256
  timeout=30)
235
257
 
236
258
  if upload_r.status_code == 200:
@@ -238,21 +260,20 @@ def _collect_and_upload_artifacts(item, api_key, api_url):
238
260
  if mime.startswith("video/"):
239
261
  video_url = data["url"]
240
262
  else:
241
- screenshot_urls.append(data["url"])
263
+ url = data["url"]
264
+ screenshot_urls.append(url)
265
+ screenshot_metadata.append(_file_metadata(file_bytes, url))
242
266
  # Derive label from filename
243
267
  stem = path.stem
244
268
  if stem.startswith("labeled-") and len(stem.split("-", 2)) >= 3:
245
- # labeled-0-some-label -> some label
246
269
  label = stem.split("-", 2)[2].replace("-", " ")
247
- elif stem == "end":
270
+ elif stem in ("end", "after"):
248
271
  label = "end"
249
- elif stem == "after":
250
- label = "end" # legacy
251
272
  else:
252
273
  label = ""
253
274
  screenshot_labels.append(label)
254
275
 
255
- return screenshot_urls, screenshot_labels, video_url
276
+ return screenshot_urls, screenshot_labels, screenshot_metadata, video_url
256
277
 
257
278
 
258
279
  def _get_versions():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytest-visionspec
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Pytest plugin that auto-reports test results with screenshots to VisionSpec
5
5
  Requires-Python: >=3.9
6
6
  Description-Content-Type: text/markdown