App Store 스크린샷을 16개 로케일 × 여러 탭으로 자동 캡처하는 파이프라인이 있습니다. 시뮬레이터를 로케일별로 띄우고 탭을 순회하며 찍죠. 그 결과에 페이월이 섞여 있었습니다. today 탭 슬롯에도, insights 탭 슬롯에도. 로케일마다 오염되는 탭이 달라 재현 조건이 안 잡혔습니다.
원인 넷이 겹쳐 있었다
하나씩 고칠 때마다 증상이 줄었다가 다른 형태로 돌아왔습니다.
① value-first 페이월 자동 표시 × entitlement 해석의 레이스. "가치를 먼저 보여주고 페이월을 띄우는" 패턴이 있는데, 캡처 모드에서 forceEntitlement(.pro)로 Pro를 강제해도 entitlement 해석이 비동기라 그 사이 .free인 순간이 존재합니다. 그 순간 페이월이 뜹니다. TabView가 인접 탭을 미리 렌더하니 옆 탭까지 오염됩니다.
② sheet stickiness. 캡처용 페이월을 .sheet로 띄우면, simctl launch가 이미 뜬 앱을 종료 없이 포그라운드로만 올릴 때 이전 페이월이 남아 다음 탭 캡처에 찍힙니다.
③ 중복 DerivedData. 앱 경로를 find ... | head -1로 골라서 stale 디렉터리의 옛 바이너리를 설치하던 시간이 있었습니다. 코드를 고쳐도 반영이 안 됩니다.
④ stale 프레임. 콜드부팅 직후 첫 launch가 렌더되기 전에 캡처하면 이전 프레임 — 이전 로케일의 페이월 — 이 찍힙니다.
근본 해결은 모든 자동 페이월 표시 경로를 !isScreenshotMode로 무조건 가드하는 것이었습니다. forceEntitlement에 의존하면 안 됩니다 — 그건 레이스에 지는 쪽입니다. 상태를 강제하는 대신 경로 자체를 막아야 합니다. 그리고 캡처용 페이월은 sheet가 아니라 루트 뷰로 렌더하도록 바꿨습니다. launch 인자가 루트를 결정하면 오염될 방법이 없습니다.
가장 중요한 발견은 검증 쪽이었다
캡처 파이프라인에 파일 크기 게이트가 있었는데, 이건 검정 화면이나 홈 스프링보드만 거릅니다. "잘못된 화면"은 못 잡습니다. 페이월은 정상적인 앱 화면이고 크기도 정상 범위라 자동 검증을 그냥 통과합니다.
그래서 화면 유형별로 16로케일을 한 장에 붙인 몽타주를 만들어 눈으로 전수 확인하는 단계를 넣었습니다. 이 시각 QA가 유일한 안전망입니다. 자동화된 파이프라인에도 자동으로 검증할 수 없는 지점이 남는다는 걸 인정하는 편이 낫습니다.
같은 결론이 자매 글에도 이어집니다 → 시뮬레이터가 언어 인자를 무시했다. 그쪽은 폴더명은 ja인데 안은 영어인 경우인데, 역시 크기 게이트로는 못 잡고 시각 QA만 잡습니다.
코드
// ❌ forceEntitlement 에 의존 — 비동기 해석과 레이스
if entitlement == .free { presentValueFirstPaywall() }
// ✅ 경로 자체를 막는다
if !AppLaunchArgs.isScreenshotMode && entitlement == .free {
presentValueFirstPaywall()
}# 매 launch 콜드스타트 강제
xcrun simctl launch --terminate-running-process "$UDID" "$BID" ...
# 중복 DerivedData 대비 — head -1 이 아니라 최신 mtime
find ... -name "*.app" | xargs stat -f "%m %N" | sort -rn | head -1검증: size-gate는 검정 화면·스프링보드만 거르고, PIL 몽타주가 화면 유형별 16로케일 1장으로 전수 시각 QA를 합니다. 등장 앱: Throne.
정직하게
- 원인 넷을 하나씩 발견하는 동안 매번 "이번엔 고쳤다"고 판단했다가 다른 로케일에서 다시 나왔습니다. 재현이 확률적이면 수정 검증도 확률적이 됩니다. 전수 몽타주를 먼저 만들었으면 훨씬 빨랐을 겁니다.
- 크기 게이트를 신뢰한 게 오래 갔습니다. 게이트가 통과시켰으니 정상이라 읽었는데, 게이트가 검사하는 건 "화면이 있느냐"였습니다. 검증 도구의 검사 범위를 확인하지 않고 쓴 대가입니다.
- 백그라운드 job이 약 7분에 kill되는 문제로 캡처를 4개씩 청크로 쪼갰습니다. 근본 원인은 안 팠고 청크로 우회했습니다.
당신의 자동 캡처 파이프라인이 마지막에 통과시키는 그 검증, "맞는 화면"을 보고 있습니까, 아니면 "화면이 있음"만 보고 있습니까? 페이월 한 장이 그 사이로 조용히 빠져나갑니다.