Repository navigation
test(mindrecord): screenshotTest 소스셋을 세우고 추억 공간 baseline 을 넣는다 (#1131) - #1154
Conversation
이 모듈에는 시각 회귀 baseline 이 하나도 없었다. 추억 공간은 verticalScroll·Lazy*· Pager 가 없어 세로가 모자라면 그대로 잘리는데, 실기 QA 가 돌던 화면(446x967dp)은 국내 보급형보다 넓어 잘림이 구조적으로 안 잡혔다. 상태 3종(기록 있음·0건·조회 실패)을 기본 크기와 360x800dp 양쪽에서 고정한다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
docker(linux/amd64) 에서 생성했다. 상태 3종 × 기본/360x800dp. compact 3장은 좁은 화면에서 「돌아가기」가 헤더 부제와 겹친 상태를 그대로 담고 있다 — 감시 장치를 먼저 넣고 수정은 #1153 에서 한다. 고친 뒤 이 3장을 갱신해야 한다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
컨테이너 validate 통과를 확인했습니다 — 스크린샷 CI 는 사전 확정입니다. 생성(6m 14s)과 검증(3m 57s) 모두 같은 |
1hyok
left a comment
There was a problem hiding this comment.
변경 요청
세운 것들은 좋다. 이 모듈에 baseline 이 하나도 없었다는 것, 상태 3종을 기본·좁은 화면 양쪽으로 가른 것, COMPACT_DEVICE_SPEC 의 dpi 근거를 주석으로 남긴 것, internal 승격의 이유와 선례(MasterKeyScreenContent)를 밝힌 것, 작성 화면 2개를 뺀 판단과 「이 PR 은 #1131 을 닫지 않는다」까지 — 범위 결정에 군더더기가 없다.
막는 것은 둘이다. 둘 다 baseline 이 감시 자산으로 서지 못한다는 같은 성질이다.
- [blocking] CI 가 이 모듈을 한 번도 돌지 않는다 — 초록인 이유는 통과가 아니라 부재다 →
build.gradle.kts:16인라인 코멘트. - [blocking] empty baseline 2장이 앱에 없는 문구를 담고 있다 →
MemorySpaceScreenScreenshotTest.kt:53인라인 코멘트.
1번이 풀리기 전에는 PR 본문의 「감시 장치를 먼저 넣고 수정은 #1153 에서 한다」가 성립하지 않는다 — 감시가 CI 에 연결돼 있지 않으니, 그 사이 추억 공간이 더 망가져도 아무도 모르고, #1153 을 고친 뒤 compact 3장을 갱신하지 않아도 빨개지지 않는다. 순서 자체(#1128 의 선례)는 맞다. 연결만 빠졌다.
검증
| 항목 | 결과 |
|---|---|
CI (head fc5ec9c5) |
전 항목 pass — 단, Screenshot 잡은 이 모듈을 돌지 않았다(1번) |
| Screenshot 잡 로그 실측 (run 32860511624) | :feature:mindrecord:presentation 태스크 0줄 |
.github/ 전체 mindrecord 문자열 |
0건 |
git merge-tree origin/develop |
clean |
문구 대조 (strings.xml ↔ 테스트 하드코딩) |
empty 2장 불일치, error 2장 일치 |
실기동 QA — 해당 없음. 프로덕션 동작 변경이 가시성 완화 한 줄뿐이라는 PR 본문 판단이 맞다.
시안 대조 — 해당 없음. 이 PR 은 화면을 바꾸지 않는다. 다만 #1153 에서 겹침을 고칠 때는 시안 대조가 필요하다 — 헤더 부제와 「돌아가기」의 세로 관계를 시안이 어떻게 잡고 있는지가 수정 방향(줄바꿈 허용 · 버튼 위치 · 부제 축약)을 가른다.
| testOptions.unitTests.isIncludeAndroidResources = true | ||
|
|
||
| // 스크롤이 없는 화면은 세로가 모자라면 그대로 잘린다 — 좁은 화면 회귀를 CI 가 잡게 한다 (#1131). | ||
| experimentalProperties["android.experimental.enableScreenshotTest"] = true |
There was a problem hiding this comment.
[blocking] 소스셋은 켜졌지만 CI 는 이 모듈의 validateScreenshotTest 를 돌지 않는다. .github/workflows/screenshot.yml:60-64 가 모듈을 하드코딩한다:
./gradlew :core:ui:validateScreenshotTest \
:feature:home:presentation:validateScreenshotTest \
:feature:receiver:presentation:validateScreenshotTest \
:feature:onboarding:presentation:validateScreenshotTest \
:feature:afternote:presentation:validateScreenshotTest \
--parallel --build-cache
실측 두 가지:
- 이 PR 의
Screenshot / Validate Compose Preview Screenshots잡(run32860511624) 로그에:feature:mindrecord:presentation태스크는 한 줄도 없다. 초록인 것은 통과해서가 아니라 돌지 않아서다. .github/전체에서mindrecord문자열이 0건이다.
그러니 지금 상태로 들어가면 baseline 6장은 저장소에만 있고 아무도 보지 않는 파일이 된다. 좁은 화면 잘림을 잡겠다는 이 PR 의 목적이 CI 에서 실현되지 않는다.
세 곳을 함께 고쳐야 한다:
screenshot.yml:60-64— 태스크 목록에:feature:mindrecord:presentation:validateScreenshotTest추가screenshot.yml:81-85— 실패 시 렌더 PNG 를 받는 아티팩트 업로드 경로(feature/mindrecord/presentation/build/outputs/screenshotTest-results/). 이게 빠지면 CI 가 빨개졌을 때 무엇이 어떻게 그려졌는지 볼 방법이 없다.screenshot.yml:106— 실패 안내 문구의 모듈 목록
덧붙여, 모듈이 하나 늘면 이 잡의 벽시계 시간도 늘어난다(현재 7분대). --parallel 이 걸려 있어 치명적이지는 않겠지만, 이 PR 에서 실제 증가폭을 한 번 확인해 두면 다음 모듈을 붙일 때 근거가 된다.
| onBackClick = {}, | ||
| modifier = Modifier.fillMaxSize(), | ||
| memories = emptyList(), | ||
| statusText = "아직 담긴 기록이 없어요.\n일기나 데일리 질문에 답하면 이곳에 쌓입니다.", |
There was a problem hiding this comment.
[blocking] 이 문구는 앱에 없다. feature/mindrecord/presentation/src/main/res/values/strings.xml:19 실측:
실제 : 아직 담긴 기록이 없어요.\n일기나 데일리 질문을 «남기면» 이곳에 쌓입니다.
여기 : 아직 담긴 기록이 없어요.\n일기나 데일리 질문에 «답하면» 이곳에 쌓입니다.
화면은 MemorySpaceUiState.statusText() 가 stringResource(R.string.mindrecord_memory_space_empty) 로 만든 문구를 넘긴다(MemorySpaceScreen.kt:85). 그러니 memorySpaceScreenEmptyScreenshot · memorySpaceScreenEmptyCompactScreenshot 두 장은 앱이 그린 적 없는 화면을 baseline 으로 고정한 것이다.
이 PR 의 목적에서 특히 아프다 — 잡으려는 것이 «세로가 모자라 잘리는가» 인데, 문구가 다르면 줄바꿈과 세로 점유가 달라져 감시 대상 자체가 어긋난다. 360dp 에서 두 문장의 접히는 위치가 같다는 보장이 없다.
에러 쪽(기록을 불러오지 못했습니다.)은 mindrecord_error_memory_space_failed 와 우연히 일치한다. 하드코딩인 한 다음에 또 어긋난다.
리소스를 직접 부르면 된다 — @Preview 컴포저블 안이므로 stringResource 가 그대로 동작하고(같은 모듈의 다른 화면 Preview 들이 이미 그렇게 쓴다), 문구가 바뀌면 baseline 이 빨개져 «갱신하라» 고 알려 준다. 지금은 그 신호가 없다.
statusText = stringResource(R.string.mindrecord_memory_space_empty),baseline 6장이 저장소에만 있고 아무도 보지 않았다. screenshot.yml 이 모듈 목록을 실행
태스크·아티팩트 경로·실패 안내 세 곳에 각자 하드코딩하고 있어, 이 모듈이 어디에도 없었다 —
Screenshot 잡이 초록인 이유는 통과가 아니라 부재였다.
- 모듈 목록을 job env(SCREENSHOT_MODULES) 한 벌로 모으고 세 곳이 거기서 파생하게 한다.
다음 모듈을 붙일 때 한 줄만 늘리면 되고, 셋이 갈릴 수 없다
- 아티팩트 경로를 같은 목록에서 만든다 — 여기만 빠지면 CI 가 빨개졌을 때 무엇이 어떻게
그려졌는지 볼 방법이 없다
- empty baseline 2장이 앱에 없는 문구("답하면")를 담고 있었다. 실제 리소스는 "남기면" 이다.
잡으려는 것이 «세로가 모자라 잘리는가» 인데 문구가 다르면 줄바꿈과 세로 점유가 달라져
감시 대상 자체가 어긋난다. statusText 를 stringResource 로 바꾸고 baseline 을 재생성했다.
이제 문구가 바뀌면 baseline 이 빨개져 갱신을 요구한다
CI 벽시계 증가폭: 이 모듈 validateScreenshotTest 단독 179초(같은 docker 이미지, 워밍된
gradle 캐시). 잡 전체는 --parallel 이라 이보다 적게 는다.
docker(linux/amd64) validateScreenshotTest BUILD SUCCESSFUL.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
막는 것 둘 다 고쳤습니다. 특히 1번 지적이 맞습니다 — 초록인 이유가 부재였습니다. 1. [blocking] CI 가 이 모듈을 돌지 않음말씀하신 세 곳을 각각 고치는 대신 목록을 한 벌로 모았습니다. 세 곳이 각자 하드코딩하고 있던 게 이 문제의 원인이라, 다시 갈릴 수 있는 구조를 남기고 싶지 않았습니다. env:
SCREENSHOT_MODULES: >-
:core:ui
:feature:home:presentation
...
:feature:mindrecord:presentation
다음 모듈을 붙일 때 늘릴 줄은 한 줄이고, 셋이 어긋날 수 없습니다. 2. [blocking] empty baseline 2장이 앱에 없는 문구실측 그대로였습니다 — 바뀐 baseline 은 정확히 그 2장뿐입니다 — error 2장은 우연히 일치하고 있었다는 지적도 맞았고, 리소스로 바꾼 뒤 픽셀 무변경입니다. 이제 우연이 아니라 계약입니다. 재생성한 compact baseline 이 마침 #1153 을 그대로 보여 줍니다 — 「돌아가기」가 헤더 부제를 덮고 있고, 실제 문구는 360dp 에서 두 번째 줄이 거의 좌우 끝까지 찹니다. 감시 대상이 어긋나 있었다는 지적이 이 그림에 드러납니다. 벽시계 증가폭물어보신 것 — 같은 docker 이미지·워밍된 gradle 캐시에서 이 모듈 검증
#1153 은 별도로 두겠습니다 — 말씀대로 수정 방향(줄바꿈 허용 · 버튼 위치 · 부제 축약)이 시안 대조에 달려 있어서, 감시 장치와 같은 PR 에 넣으면 그 판단이 묻힙니다. |
|
이 PR 은
|
1hyok
left a comment
There was a problem hiding this comment.
재리뷰 — blocking 둘 다 닫혔습니다. 승인합니다
1번을 «목록에 한 줄 더하기» 로 끝내지 않고 세 곳이 각자 목록을 들던 구조 자체를 없앤 것이 이 회차의 핵심입니다. 지적은 「mindrecord 가 빠졌다」 였는데, 고친 것은 「다음 모듈도 빠질 수 있다」 쪽입니다.
| # | 지적 | 처리 |
|---|---|---|
| 1 | [blocking] CI 가 이 모듈을 한 번도 돌지 않는다 | 닫힘 — 아래 로그 |
| 2 | [blocking] empty baseline 2장이 앱에 없는 문구를 담고 있다 | 닫힘 — 리소스에서 읽음 |
1번 — 이번엔 실제로 돌았습니다
지난 회차에서 「:feature:mindrecord:presentation 태스크 0줄」로 적발했던 자리를, 같은 방법으로 다시 봤습니다. head 9e590bcc 의 Screenshot 잡(run 32942258395, conclusion success) 로그입니다.
$ gh api .../actions/jobs/98095488409/logs | grep -oE "Task :[a-z:]+:validateScreenshotTest"
Task :core:ui:validateScreenshotTest
Task :feature:home:presentation:validateScreenshotTest
Task :feature:onboarding:presentation:validateScreenshotTest
Task :feature:afternote:presentation:validateScreenshotTest
Task :feature:mindrecord:presentation:validateScreenshotTest ← 07:28:52
Task :feature:receiver:presentation:validateScreenshotTest
컨테이너에 실제로 전달된 값도 로그에 찍혀 있어 «워크플로 파일만 고쳐 놓고 안 먹는» 경우가 아님이 확인됩니다.
SCREENSHOT_MODULES: :core:ui :feature:home:presentation :feature:receiver:presentation
:feature:onboarding:presentation :feature:afternote:presentation
:feature:mindrecord:presentation
목록이 실제 소스셋과 일치하는지도 전수로 맞춰 봤습니다 — find -type d -name screenshotTest 로 나온 6개 모듈과 위 6개가 정확히 같습니다. 누락도, 없는 모듈을 가리키는 줄도 없습니다.
아티팩트 경로와 실패 안내를 같은 목록에서 파생시킨 것도 맞습니다. 특히 안내 문구를 modules.map(...) 으로 만든 덕에, 다음에 모듈이 늘 때 «봇은 옛 목록을 말하는데 실제로는 더 돌고 있는» 어긋남이 생기지 않습니다.
2번 — 키가 실제 화면의 것과 같습니다
strings.xml:19 mindrecord_memory_space_empty ← MemorySpaceScreen.kt:86 이 쓰는 그 키
strings.xml:20 mindrecord_error_memory_space_failed ← MemorySpaceViewModel.kt:59 가 넣는 그 키
종전 하드코딩(「일기나 데일리 질문에 답하면」)과 실제 문구(「일기나 데일리 질문을 남기면」)가 달랐던 것이 이렇게 구조적으로 닫힙니다. 이제 문구를 고치면 baseline 이 빨개져 갱신을 요구하고, 그게 KDoc 에 적어 두신 의도 그대로입니다.
머지 전 필수 — develop 과 충돌합니다 (기계적)
mergeable=CONFLICTING 입니다. 충돌은 한 파일 한 자리뿐이고 서로 다투는 변경이 아닙니다.
CONFLICT (content): feature/mindrecord/presentation/build.gradle.kts
plugins {
…
<<<<<<< HEAD
alias(libs.plugins.compose.screenshot) // 이 PR
=======
id("afternote.kover") // develop
>>>>>>> origin/develop
}둘 다 남기면 끝입니다. 그 조합이 실제로 서는지 로컬에서 해소해 확인했습니다 — kover 가 새 screenshotTest 소스셋을 만나 configuration 이 깨지는 일은 없었습니다.
./gradlew :feature:mindrecord:presentation:ktlintCheck \
:feature:mindrecord:presentation:compileDebugKotlin \
:feature:mindrecord:presentation:compileDebugScreenshotTestKotlin \
:feature:mindrecord:presentation:compileDebugUnitTestKotlin
→ BUILD SUCCESSFUL
다만 픽셀 비교(validateScreenshotTest)는 로컬 arm64 로 판정할 수 없어 돌리지 않았습니다. 충돌이 있는 동안은 CI 도 돌지 않으므로(merge ref 미갱신), 해소해 올리시면 그때 CI 가 이 조합에서 baseline 6장을 다시 봐 줍니다. 위 07:28 run 이 이미 통과한 조합이라 새로 깨질 이유는 보이지 않습니다.
[normal] 네 번째 자리가 남았습니다 — README.md:257-263
이 PR 이 없앤 「목록이 여러 곳에 있다」 가 문서에 하나 더 있습니다. 실행 태스크·아티팩트 경로·실패 안내 셋은 통합됐는데, 로컬 검증 절차의 모듈 목록은 5개인 채입니다 — mindrecord 가 빠져 있습니다.
# README.md — 로컬 baseline 검증 (CI 실패 재현)
docker run … ./gradlew :core:ui:validateScreenshotTest \
:feature:home:presentation:validateScreenshotTest \
:feature:receiver:presentation:validateScreenshotTest \
:feature:onboarding:presentation:validateScreenshotTest \
:feature:afternote:presentation:validateScreenshotTest
# :feature:mindrecord:presentation 없음같은 문서가 세 줄 위에서 이렇게 적고 있어 더 그렇습니다.
대상 모듈 목록의 정본은
.github/workflows/screenshot.yml이다. 모듈을 추가·이전했다면 워크플로와 이 문서를 함께 갱신한다.
여기가 어긋나면 결과는 CI 때와 같은 종류입니다 — 사람이 로컬에서 재현할 때 mindrecord 만 조용히 빠져, 「로컬에선 통과했는데 CI 가 빨갛다」 가 됩니다. 워크플로처럼 파생시킬 수 없는 자리라 손으로 한 줄 더하거나, 문서를 «정본은 워크플로이니 그 목록을 그대로 쓰라» 는 안내로 바꾸는 편이 낫겠습니다.
승인을 막지는 않습니다 — 충돌 해소하실 때 같이 얹으면 됩니다.
검증
| 항목 | 결과 |
|---|---|
CI (head 9e590bcc) |
전 항목 pass — 이번엔 mindrecord validate 를 포함해서 |
| Screenshot 잡 로그 실측 (run 32942258395) | :feature:mindrecord:presentation:validateScreenshotTest 실행 확인 |
모듈 목록 ↔ 실제 screenshotTest 소스셋 |
6 ↔ 6 일치 |
문구 대조 (strings.xml ↔ 테스트) |
리소스 참조로 전환, 화면과 동일 키 |
git merge-tree origin/develop |
CONFLICT 1건 (위 참조, 해소본 컴파일 확인) |
.github 내 모듈 하드코딩 잔재 |
0건 |
| 문서 내 잔재 | 1건 (README.md, 위 normal) |
실기동 QA — 해당 없음. 이번 증분은 워크플로·테스트·baseline 뿐이고 프로덕션 코드 변경이 없습니다.
시안 대조 — 해당 없음. 화면을 바꾸지 않습니다. 지난 회차에 적어 둔 대로, #1153 에서 겹침을 고칠 때는 헤더 부제와 「돌아가기」의 세로 관계를 시안에서 확인하셔야 합니다.
…rd-screenshot # Conflicts: # feature/mindrecord/presentation/build.gradle.kts
#1131 의 인프라 + 추억 공간을 담는다. 작성 화면 2개는 뺐다 — 아래 「왜 나눴나」.
무엇을
feature/mindrecord/presentation에screenshotTest소스셋을 켰다(enableScreenshotTest+screenshotTestImplementation2줄). 이 모듈에는 시각 회귀 baseline 이 하나도 없었다.COMPACT_DEVICE_SPEC은feature:onboarding·feature:receiver의 같은 상수와 값을 맞췄다(test(onboarding·receiver): 스크롤 없는 화면 9개에 360×800dp 스크린샷 baseline 추가 — 시각 회귀가 한 화면 크기로만 돈다 #1128).렌더를 위해
MemorySpaceContent를private→internal로 열었다. 이 모듈의 상태 없는 본문 컴포저블이 화면당 하나뿐이라 다른 선택지가 없었고,feature:receiver의MasterKeyScreenContent가 같은 형태다.첫 사이클에 결함이 하나 잡혔다 — #1153
360dp 에서 「돌아가기」 버튼이 헤더 부제(「화면을 드래그해 공간을 둘러보세요」)와 겹친다. 기본 크기에서는 여백이 남는다.
memorySpaceScreenErrorCompactScreenshot을 비롯한 compact 3장이 그 겹친 상태를 담고 있다. 감시 장치를 먼저 넣고 수정은 #1153 에서 한다(#1128 이OnboardingTermsScreen에서 택한 것과 같은 순서). 고친 뒤 이 3장을 갱신해야 한다.왜 나눴나 — 작성 화면 2개는 후속
이슈가 든
DiaryWriteScreen·DailyQuestionWriteScreen은 상태 없는 본문 컴포저블이 없다. 화면 함수가viewModel = hiltViewModel()을 직접 받고 피커·시트 상태를 안에 들고 있어, baseline 을 붙이려면 먼저 content 를 뽑아내는 리팩터가 필요하다(그 화면들의 기존@Preview가 «ViewModel 의존이 있어 Preview 는 비워둠» 인 이유이기도 하다).그 두 파일은 지금 제 열린 PR 둘(#958 · #1121)이 건드리는 중이라, 지금 뽑아내면 셋이 같은 자리에서 충돌한다. 그 PR 들이 들어간 뒤 후속으로 올리겠다 — 이 PR 은 #1131 을 닫지 않는다.
검증
baseline 생성·검증 모두
screenshot-baseline절차(dockerlinux/amd64)를 따랐다.compileDebugScreenshotTestKotlintestDebugUnitTest실기동 QA: 해당 없음. 프로덕션 동작 변경이 없다 — 가시성 완화 한 줄과 테스트 소스셋뿐이다.