Skip to content

Commit ad247aa

Browse files
committed
Preserve full-resolution screenshot previews
1 parent 91e5b63 commit ad247aa

File tree

2 files changed

+63
-44
lines changed

2 files changed

+63
-44
lines changed

scripts/android/tests/process_screenshots.py

Lines changed: 36 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from dataclasses import dataclass
1414
from typing import Dict, Iterable, List, Tuple
1515

16-
MAX_COMMENT_BASE64 = 40_000
16+
MAX_COMMENT_BASE64 = 60_000
1717

1818
PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n"
1919

@@ -221,44 +221,19 @@ def chunk(tag: bytes, payload: bytes) -> bytes:
221221
)
222222

223223

224-
def _downscale_half(width: int, height: int, bpp: int, pixels: bytes) -> Tuple[int, int, bytes]:
225-
new_width = max(1, (width + 1) // 2)
226-
new_height = max(1, (height + 1) // 2)
227-
new_pixels = bytearray(new_width * new_height * bpp)
228-
229-
for ny in range(new_height):
230-
for nx in range(new_width):
231-
accum = [0] * bpp
232-
samples = 0
233-
for dy in (0, 1):
234-
sy = min(height - 1, ny * 2 + dy)
235-
for dx in (0, 1):
236-
sx = min(width - 1, nx * 2 + dx)
237-
src_index = (sy * width + sx) * bpp
238-
for channel in range(bpp):
239-
accum[channel] += pixels[src_index + channel]
240-
samples += 1
241-
dst_index = (ny * new_width + nx) * bpp
242-
for channel in range(bpp):
243-
new_pixels[dst_index + channel] = accum[channel] // samples
244-
245-
return new_width, new_height, bytes(new_pixels)
246-
247-
248-
def build_preview_base64(image: PNGImage, max_length: int = MAX_COMMENT_BASE64) -> str:
249-
width = image.width
250-
height = image.height
251-
bpp = image.bytes_per_pixel
252-
pixels = image.pixels
253-
254-
while True:
255-
png_bytes = _encode_png(width, height, image.bit_depth, image.color_type, bpp, pixels)
256-
encoded = base64.b64encode(png_bytes).decode("ascii")
257-
if len(encoded) <= max_length or width <= 1 or height <= 1:
258-
return encoded
259-
if image.color_type not in {0, 2, 4, 6}:
260-
return encoded
261-
width, height, pixels = _downscale_half(width, height, bpp, pixels)
224+
def build_base64_payload(image: PNGImage, max_length: int = MAX_COMMENT_BASE64) -> Tuple[str | None, int]:
225+
png_bytes = _encode_png(
226+
image.width,
227+
image.height,
228+
image.bit_depth,
229+
image.color_type,
230+
image.bytes_per_pixel,
231+
image.pixels,
232+
)
233+
encoded = base64.b64encode(png_bytes).decode("ascii")
234+
if len(encoded) <= max_length:
235+
return encoded, len(encoded)
236+
return None, len(encoded)
262237

263238

264239
def build_results(reference_dir: pathlib.Path, actual_entries: List[Tuple[str, pathlib.Path]], emit_base64: bool) -> Dict[str, List[Dict[str, object]]]:
@@ -275,10 +250,19 @@ def build_results(reference_dir: pathlib.Path, actual_entries: List[Tuple[str, p
275250
elif not expected_path.exists():
276251
record.update({"status": "missing_expected"})
277252
if emit_base64:
253+
payload = None
254+
length = 0
278255
try:
279-
record["base64"] = build_preview_base64(load_png(actual_path))
256+
payload, length = build_base64_payload(load_png(actual_path))
280257
except Exception:
281-
record["base64"] = base64.b64encode(actual_path.read_bytes()).decode("ascii")
258+
raw = base64.b64encode(actual_path.read_bytes()).decode("ascii")
259+
length = len(raw)
260+
if length <= MAX_COMMENT_BASE64:
261+
payload = raw
262+
if payload is not None:
263+
record["base64"] = payload
264+
elif length:
265+
record.update({"base64_omitted": "too_large", "base64_length": length})
282266
else:
283267
try:
284268
actual_img = load_png(actual_path)
@@ -292,10 +276,19 @@ def build_results(reference_dir: pathlib.Path, actual_entries: List[Tuple[str, p
292276
else:
293277
record.update({"status": "different", "details": outcome})
294278
if emit_base64:
279+
payload = None
280+
length = 0
295281
try:
296-
record["base64"] = build_preview_base64(actual_img)
282+
payload, length = build_base64_payload(actual_img)
297283
except Exception:
298-
record["base64"] = base64.b64encode(actual_path.read_bytes()).decode("ascii")
284+
raw = base64.b64encode(actual_path.read_bytes()).decode("ascii")
285+
length = len(raw)
286+
if length <= MAX_COMMENT_BASE64:
287+
payload = raw
288+
if payload is not None:
289+
record["base64"] = payload
290+
elif length:
291+
record.update({"base64_omitted": "too_large", "base64_length": length})
299292
results.append(record)
300293
return {"results": results}
301294

scripts/run-android-instrumentation-tests.sh

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,8 @@ for result in data.get("results", []):
427427
actual_path = result.get("actual_path", "")
428428
details = result.get("details") or {}
429429
base64_data = result.get("base64")
430+
base64_omitted = result.get("base64_omitted")
431+
base64_length = result.get("base64_length")
430432
message = ""
431433
copy_flag = "0"
432434
@@ -440,6 +442,9 @@ for result in data.get("results", []):
440442
"status": "missing reference",
441443
"message": message,
442444
"base64": base64_data,
445+
"base64_omitted": base64_omitted,
446+
"base64_length": base64_length,
447+
"artifact_name": f"{test}.png",
443448
})
444449
elif status == "different":
445450
dims = ""
@@ -452,6 +457,9 @@ for result in data.get("results", []):
452457
"status": "updated screenshot",
453458
"message": message,
454459
"base64": base64_data,
460+
"base64_omitted": base64_omitted,
461+
"base64_length": base64_length,
462+
"artifact_name": f"{test}.png",
455463
})
456464
elif status == "error":
457465
message = f"Comparison error: {result.get('message', 'unknown error')}"
@@ -461,6 +469,9 @@ for result in data.get("results", []):
461469
"status": "comparison error",
462470
"message": message,
463471
"base64": None,
472+
"base64_omitted": base64_omitted,
473+
"base64_length": base64_length,
474+
"artifact_name": f"{test}.png",
464475
})
465476
elif status == "missing_actual":
466477
message = "Actual screenshot missing (test did not produce output)."
@@ -470,6 +481,9 @@ for result in data.get("results", []):
470481
"status": "missing actual screenshot",
471482
"message": message,
472483
"base64": None,
484+
"base64_omitted": base64_omitted,
485+
"base64_length": base64_length,
486+
"artifact_name": None,
473487
})
474488
else:
475489
message = f"Status: {status}."
@@ -485,7 +499,19 @@ if comment_entries:
485499
if entry.get("base64"):
486500
lines.append("")
487501
lines.append(f" ![{entry['test']}](data:image/png;base64,{entry['base64']})")
488-
lines.append(" _(Preview image scaled for comment delivery.)_")
502+
artifact_name = entry.get("artifact_name")
503+
if artifact_name:
504+
lines.append(f" _Full-resolution PNG saved as `{artifact_name}` in workflow artifacts._")
505+
lines.append("")
506+
elif entry.get("base64_omitted") == "too_large":
507+
artifact_name = entry.get("artifact_name")
508+
size_note = ""
509+
if entry.get("base64_length"):
510+
size_note = f" (base64 length ≈ {entry['base64_length']:,} chars)"
511+
lines.append("")
512+
lines.append(" _Screenshot omitted from comment because the encoded payload exceeded GitHub's size limits" + size_note + "._")
513+
if artifact_name:
514+
lines.append(f" _Full-resolution PNG saved as `{artifact_name}` in workflow artifacts._")
489515
lines.append("")
490516
comment_path.write_text("\n".join(lines).rstrip() + "\n", encoding="utf-8")
491517
else:

0 commit comments

Comments
 (0)