fix(insta-render): fonts.ready 대기 + PNG 비어있음 검증 (렌더 known-issue 해결)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-06 12:53:07 +09:00
parent 8bfc8e153f
commit 9241b5cd90
2 changed files with 28 additions and 0 deletions

View File

@@ -151,8 +151,11 @@ async def _render_slate_locked(slate: dict, slate_id: int, template: str) -> Lis
html_path = f.name html_path = f.name
try: try:
await page.goto(f"file://{html_path}", wait_until="networkidle") await page.goto(f"file://{html_path}", wait_until="networkidle")
await page.evaluate("document.fonts.ready") # 웹폰트 로딩 완료까지 대기
out_path = os.path.join(out_dir, f"{spec['page_no']:02d}.png") out_path = os.path.join(out_dir, f"{spec['page_no']:02d}.png")
await page.screenshot(path=out_path, full_page=False, omit_background=False) await page.screenshot(path=out_path, full_page=False, omit_background=False)
if os.path.getsize(out_path) < 1000: # 빈/깨진 PNG 방어
raise RuntimeError(f"rendered PNG too small: {out_path}")
paths.append(out_path) paths.append(out_path)
finally: finally:
try: try:

View File

@@ -1,10 +1,13 @@
"""worker.py — Redis BLPOP + webhook 단위 테스트.""" """worker.py — Redis BLPOP + webhook 단위 테스트."""
import json import json
import os
from pathlib import Path
import pytest import pytest
import httpx import httpx
from unittest.mock import AsyncMock, patch from unittest.mock import AsyncMock, patch
import worker import worker
from card_renderer import render_slate, init_browser, shutdown_browser
@pytest.fixture @pytest.fixture
@@ -179,6 +182,28 @@ async def test_poll_once_calls_fail_on_exception(monkeypatch):
fake_queue.ack.assert_not_awaited() fake_queue.ack.assert_not_awaited()
@pytest.mark.asyncio
async def test_render_produces_nonempty_1080x1350(tmp_path, monkeypatch):
"""Phase 2 — fonts.ready 대기 + PNG 비어있음 검증: 10장 모두 > 1000 bytes."""
import card_renderer as _cr
templates_dir = str(Path(__file__).resolve().parent.parent / "templates")
monkeypatch.setattr(_cr, "CARD_TEMPLATE_DIR", templates_dir)
monkeypatch.setattr(_cr, "INSTA_MEDIA_ROOT", str(tmp_path))
await init_browser()
try:
slate = {
"cover_copy": {"headline": "헤드라인", "body": "서브", "accent_color": "#0F62FE"},
"body_copies": [{"headline": f"포인트{i}", "body": "본문"} for i in range(8)],
"cta_copy": {"headline": "요약", "body": "마무리", "cta": "팔로우"},
}
paths = await render_slate(slate, slate_id=99999)
assert len(paths) == 10
for p in paths:
assert os.path.getsize(p) > 1000 # 비어있지 않음
finally:
await shutdown_browser()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_poll_once_returns_false_on_timeout(monkeypatch): async def test_poll_once_returns_false_on_timeout(monkeypatch):
"""F6 — dequeue가 None 반환(타임아웃)이면 False 리턴, ack/fail 안 부름.""" """F6 — dequeue가 None 반환(타임아웃)이면 False 리턴, ack/fail 안 부름."""