Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
606beb8
test(maestro): 프로필과 설정 E2E 플로우 추가 #615
edv-Shin Oct 12, 2026
cab94ec
chore(maestro): 프로필 브랜치에 최신 dev 반영
edv-Shin Oct 15, 2026
90a3e77
fix(maestro): 프로필 CI와 원본 상태 복구 연결
edv-Shin Oct 15, 2026
0d0b538
chore(maestro): 프로필에 공통 CI 기반 통합
edv-Shin Oct 15, 2026
ecaff42
style(maestro): 프로필 정책 줄바꿈 정리
edv-Shin Oct 16, 2026
110e456
chore(maestro): 공통 입력 설정 통합
edv-Shin Oct 16, 2026
474ed0e
style(profile): 알림 테스트 표현식 배치 정리
edv-Shin Oct 16, 2026
2a7b738
chore(maestro): 프로필 CI 준비 진단 통합
edv-Shin Oct 16, 2026
5b5d936
fix(maestro): 프로필 입력기 ID 호환 수정 통합
edv-Shin Oct 16, 2026
d1db23b
fix(maestro): 프로필 입력기 XML variant 수정 통합
edv-Shin Oct 16, 2026
dcbdc77
fix(maestro): 프로필 API 34 선택과 ktlint 정리
edv-Shin Oct 16, 2026
ede010c
test(profile): 준비 실패 고정 코드 진단
edv-Shin Oct 16, 2026
a7cd245
fix(ci): 공유 계정 대기 실행 취소 방지
edv-Shin Oct 16, 2026
f708bec
fix(maestro): 프로필 기준 텍스트 검사를 앱 규칙에 맞춤
edv-Shin Oct 17, 2026
2a4d055
fix(maestro): 프로필 준비 실패 조건 구분
edv-Shin Oct 17, 2026
67cea31
style(maestro): 프로필 회귀 테스트 들여쓰기 수정
edv-Shin Oct 17, 2026
bc819f8
fix(profile): 빈 소개의 UI 왕복과 원문 복구 허용
edv-Shin Oct 17, 2026
719619a
fix(profile): 입력 전체 삭제와 저장 전 원문 검증
edv-Shin Oct 17, 2026
4ca316a
fix(profile): 선택 상태와 알림 권한 거부 검증 수정
edv-Shin Oct 17, 2026
dc8f19b
test(profile): 시스템 UI 중단 진단 수집
edv-Shin Oct 17, 2026
f231ea4
fix(maestro): 프로필 E2E 병합 충돌 해결
edv-Shin Oct 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions .maestro/README-profile.md

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion .maestro/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,10 @@ peer/matching 비밀은 선택된 descriptor가 요구할 때만 필수입니다
- `report.xml`: 실제 Maestro 종료 코드/JUnit과 준비·정리 결과를 합친 플로우별 JUnit. 빈 실행/skip/리포트 부재는 실패입니다.
- `<flow>/console.log`, `debug-*.log`, `debug-*.json`: 인증 값/JWT/인증 관련 줄을 제거한 Maestro 출력과 debug 정보. 원본 subprocess 예외/traceback을 출력하지 않습니다.
- `<flow>/screenshot-*.png`: **Maestro 실행 중** 생성된 PNG만 보관합니다. 준비/내보내기 화면은 캡처하지 않습니다. 원본 텍스트에서 민감 정보가 감지되면 이미지도 보관하지 않습니다.
- `<flow>/device-log.log`: Maestro 비정상 종료 시 logcat 최근 500줄 중 `[MEASURE]`/`WebSocket`/`Connectivity`/`AndroidRuntime`/`FATAL EXCEPTION`/`OkHttp`가 있는 줄만 선별하고 인증 값/JWT/인증 관련 줄 제거 규칙을 적용한 로그입니다.
- `<flow>/last-anr-summary.json`: 이어서 조회한 마지막 ANR의 고정 process/reason 분류와 load average/전체 CPU 수치만 보관합니다. 이전 사건일 수 있으며 원문·PID·창 제목·Intent는 공개하지 않습니다.

Maestro의 HOME/Java user.home은 실행별 임시 디렉터리로 격리하며 analytics, 업데이트 확인, AI 알림을 끕니다. 원본 CLI 임시 출력은 finally에서 제거하고 업로드하지 않습니다. authState는 호스트 메모리/앱 private stdin 경로에만 전달됩니다. 앱은 e2e에서 자격 증명을 포함할 수 있는 Timber 로깅을 사용하지 않습니다. raw logcat을 수집하지 않습니다.
Maestro의 HOME/Java user.home은 실행별 임시 디렉터리로 격리하며 analytics, 업데이트 확인, AI 알림을 끕니다. 원본 CLI 임시 출력은 finally에서 제거하고 업로드하지 않습니다. authState는 호스트 메모리/앱 private stdin 경로에만 전달됩니다. 앱은 e2e에서 자격 증명을 포함할 수 있는 Timber 로깅을 사용하지 않습니다. raw logcat과 dumpsys 원문은 파일로 저장하거나 업로드하지 않습니다.

CI는 실패해도 `always()`로 상태 파일과 안전한 XML/log/JSON/PNG만 7일 보관합니다. `workflow-status.log`만 있으면 빌드/설정/에뮬레이터 단계에서 UI 실행 전 실패한 상태일 수 있습니다. 이 파일은 테스트 통과 증거가 아닙니다. stdout의 고정 실패 코드와 JUnit을 먼저 보고 안전한 hierarchy/스크린샷을 함께 확인하세요.

Expand Down
116 changes: 116 additions & 0 deletions .maestro/flows/profile-edit-validation.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
appId: com.project200.undabang
name: 프로필 편집 - 저장 방지 경로
tags:
- regression
- requires-login
---
# 세 가지 차단 경로 모두 저장하지 않아야 합니다. finally가 실제 원본과 대조하고 복구합니다.
- runFlow: ../subflows/launch-logged-in.yaml

- tapOn:
id: profile_nav_graph
- extendedWaitUntil:
visible: "최근 30일 운동 횟수"
timeout: 30000

- tapOn:
id: mypage_edit_btn
- extendedWaitUntil:
visible:
id: profile_edit_loaded
timeout: 30000

- scrollUntilVisible:
element:
id: profile_edit_nickname_field
direction: DOWN
- assertVisible:
id: profile_edit_nickname_field
text: ${E2E_PROFILE_ORIGINAL_NICKNAME}
- evalScript: ${output.nickA = 'e2ea' + E2E_RUN_ID}
- evalScript: ${output.nickB = 'e2eb' + E2E_RUN_ID}

# 1) 중복 확인 없이 완료 -> 저장이 막힙니다
- runFlow:
file: ../subflows/profile-clear-text.yaml
env:
FIELD_ID: profile_edit_nickname_field
- inputText: ${output.nickA}
- hideKeyboard
- runFlow:
file: ../subflows/profile-assert-inputs.yaml
env:
EXPECTED_NICKNAME: ${output.nickA}
EXPECTED_INTRODUCTION: ${E2E_PROFILE_ORIGINAL_BIO}
- tapOn: "완료"
- extendedWaitUntil:
visible: "닉네임 중복 확인을 해주세요."
timeout: 10000

# 2) 유효하지 않은 값 -> 규칙 위반 문구가 뜨고, 중복 확인 API는 호출되지 않습니다
# ValidateNicknameUseCase 정규식(한글/영문/숫자만)에 맞춰 '!'로 위반시킵니다
- runFlow:
file: ../subflows/profile-clear-text.yaml
env:
FIELD_ID: profile_edit_nickname_field
- inputText: "e2e615!invalid"
- hideKeyboard
- runFlow:
file: ../subflows/profile-assert-inputs.yaml
env:
EXPECTED_NICKNAME: e2e615!invalid
EXPECTED_INTRODUCTION: ${E2E_PROFILE_ORIGINAL_BIO}
- tapOn: "중복 확인"
- assertVisible: "닉네임 조건을 확인해주세요."
- runFlow:
file: ../subflows/profile-assert-inputs.yaml
env:
EXPECTED_NICKNAME: e2e615!invalid
EXPECTED_INTRODUCTION: ${E2E_PROFILE_ORIGINAL_BIO}
- tapOn: "완료"
- extendedWaitUntil:
visible: "닉네임 중복 확인을 해주세요."
timeout: 10000

# 3) 닉네임 A로 중복 확인을 통과한 뒤 B로 다시 바꾸면 확인 상태가 풀립니다(요구 완료 기준)
- runFlow:
file: ../subflows/profile-clear-text.yaml
env:
FIELD_ID: profile_edit_nickname_field
- inputText: ${output.nickA}
- hideKeyboard
- runFlow:
file: ../subflows/profile-assert-inputs.yaml
env:
EXPECTED_NICKNAME: ${output.nickA}
EXPECTED_INTRODUCTION: ${E2E_PROFILE_ORIGINAL_BIO}
- tapOn: "중복 확인"
- extendedWaitUntil:
visible: "사용 가능한 닉네임입니다."
timeout: 15000

- runFlow:
file: ../subflows/profile-clear-text.yaml
env:
FIELD_ID: profile_edit_nickname_field
- inputText: ${output.nickB}
- hideKeyboard
- runFlow:
file: ../subflows/profile-assert-inputs.yaml
env:
EXPECTED_NICKNAME: ${output.nickB}
EXPECTED_INTRODUCTION: ${E2E_PROFILE_ORIGINAL_BIO}
# updateNickname이 값이 바뀔 때마다 isNicknameChecked를 초기화합니다. 중복 확인 문구가 사라졌는지 확인합니다
- assertNotVisible: "사용 가능한 닉네임입니다."
- tapOn: "완료"
- extendedWaitUntil:
visible: "닉네임 중복 확인을 해주세요."
timeout: 10000

# 세 경로 모두 저장되지 않았으므로 로컬 입력만 버리고 나갑니다
- back
- extendedWaitUntil:
visible:
id: nickname_tv
text: ${E2E_PROFILE_ORIGINAL_NICKNAME}
timeout: 15000
220 changes: 220 additions & 0 deletions .maestro/flows/profile-edit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
appId: com.project200.undabang
name: 프로필 편집 - 닉네임·소개 변경과 복구
tags:
- regression
- requires-login
---
# 준비 API가 읽은 원문과 로드된 입력 노드가 일치한 뒤에만 수정합니다.
# 실패 복구는 공통 finally의 profile fixture가 실제 API로 수행합니다.
- runFlow: ../subflows/launch-logged-in.yaml
- tapOn:
id: profile_nav_graph
- extendedWaitUntil:
visible: "최근 30일 운동 횟수"
timeout: 30000
- tapOn:
id: mypage_edit_btn
- extendedWaitUntil:
visible:
id: profile_edit_loaded
timeout: 30000
- scrollUntilVisible:
element:
id: profile_edit_nickname_field
direction: DOWN
- assertVisible:
id: profile_edit_nickname_field
text: ${E2E_PROFILE_ORIGINAL_NICKNAME}
- copyTextFrom:
id: profile_edit_nickname_field
- evalScript: ${output.originalNickname = maestro.copiedText}
- assertTrue: ${output.originalNickname === E2E_PROFILE_ORIGINAL_NICKNAME}
- scrollUntilVisible:
element:
id: profile_edit_introduction_field
direction: DOWN
# 빈 입력은 hint를 복사하지 않고, 로드된 실제 값의 상태를 확인한 뒤 API 원문을 보관합니다.
- assertVisible:
id: "${E2E_PROFILE_ORIGINAL_BIO === '' ? 'profile_edit_introduction_empty' : 'profile_edit_introduction_present'}"
- evalScript: ${output.originalIntroduction = E2E_PROFILE_ORIGINAL_BIO}
- runFlow:
when:
true: ${E2E_PROFILE_ORIGINAL_BIO !== ''}
commands:
- assertVisible:
id: profile_edit_introduction_field
text: ${E2E_PROFILE_ORIGINAL_BIO}
- copyTextFrom:
id: profile_edit_introduction_field
- evalScript: ${output.originalIntroduction = maestro.copiedText}
- assertTrue: ${output.originalIntroduction === E2E_PROFILE_ORIGINAL_BIO}
- scrollUntilVisible:
element:
id: profile_edit_nickname_field
direction: UP
- runFlow:
file: ../subflows/profile-clear-text.yaml
env:
FIELD_ID: profile_edit_nickname_field
- inputText: ${E2E_PROFILE_NEW_NICKNAME}
- hideKeyboard
- runFlow:
file: ../subflows/profile-assert-inputs.yaml
env:
EXPECTED_NICKNAME: ${E2E_PROFILE_NEW_NICKNAME}
EXPECTED_INTRODUCTION: ${output.originalIntroduction}
- tapOn: "중복 확인"
- extendedWaitUntil:
visible: "사용 가능한 닉네임입니다."
timeout: 15000
- scrollUntilVisible:
element:
id: profile_edit_introduction_field
direction: DOWN
- runFlow:
file: ../subflows/profile-clear-text.yaml
env:
FIELD_ID: profile_edit_introduction_field
- inputText: ${E2E_PROFILE_NEW_BIO}
- hideKeyboard
- runFlow:
file: ../subflows/profile-assert-inputs.yaml
env:
EXPECTED_NICKNAME: ${E2E_PROFILE_NEW_NICKNAME}
EXPECTED_INTRODUCTION: ${E2E_PROFILE_NEW_BIO}
- tapOn: "완료"
- extendedWaitUntil:
visible: "프로필 수정이 완료됐습니다."
timeout: 15000
- extendedWaitUntil:
visible:
id: nickname_tv
text: ${E2E_PROFILE_NEW_NICKNAME}
timeout: 15000
- scrollUntilVisible:
element:
id: introduction_tv
text: ${E2E_PROFILE_NEW_BIO}
direction: DOWN
- assertVisible:
id: introduction_tv
text: ${E2E_PROFILE_NEW_BIO}
- scrollUntilVisible:
element:
id: mypage_edit_btn
direction: UP
- tapOn:
id: mypage_edit_btn
- extendedWaitUntil:
visible:
id: profile_edit_loaded
timeout: 30000
- scrollUntilVisible:
element:
id: profile_edit_nickname_field
direction: DOWN
- assertVisible:
id: profile_edit_nickname_field
text: ${E2E_PROFILE_NEW_NICKNAME}
- scrollUntilVisible:
element:
id: profile_edit_introduction_field
direction: DOWN
- assertVisible:
id: profile_edit_introduction_field
text: ${E2E_PROFILE_NEW_BIO}
# UI 복구도 저장·재조회 경로를 반드시 통과합니다. API finally 복구의 대체물이 아닙니다.
- scrollUntilVisible:
element:
id: profile_edit_nickname_field
direction: UP
- runFlow:
file: ../subflows/profile-clear-text.yaml
env:
FIELD_ID: profile_edit_nickname_field
- inputText: ${output.originalNickname}
- hideKeyboard
- runFlow:
file: ../subflows/profile-assert-inputs.yaml
env:
EXPECTED_NICKNAME: ${output.originalNickname}
EXPECTED_INTRODUCTION: ${E2E_PROFILE_NEW_BIO}
- tapOn: "중복 확인"
- extendedWaitUntil:
visible: "사용 가능한 닉네임입니다."
timeout: 15000
- scrollUntilVisible:
element:
id: profile_edit_introduction_field
direction: DOWN
- runFlow:
file: ../subflows/profile-clear-text.yaml
env:
FIELD_ID: profile_edit_introduction_field
# 빈 원문은 실제 입력을 지운 상태로 저장하며 inputText에 빈 문자열을 넘기지 않습니다.
- runFlow:
when:
true: ${output.originalIntroduction !== ''}
commands:
- inputText: ${output.originalIntroduction}
- hideKeyboard
- runFlow:
file: ../subflows/profile-assert-inputs.yaml
env:
EXPECTED_NICKNAME: ${output.originalNickname}
EXPECTED_INTRODUCTION: ${output.originalIntroduction}
- tapOn: "완료"
- extendedWaitUntil:
visible: "프로필 수정이 완료됐습니다."
timeout: 15000
- extendedWaitUntil:
visible:
id: nickname_tv
text: ${output.originalNickname}
timeout: 15000
- runFlow:
when:
true: ${output.originalIntroduction !== ''}
commands:
- scrollUntilVisible:
element:
id: introduction_tv
text: ${output.originalIntroduction}
direction: DOWN
- assertVisible:
id: introduction_tv
text: ${output.originalIntroduction}
# 저장 뒤 다시 편집기를 열어 실제 서버 재조회가 끝난 입력 상태로 빈 원문까지 검증합니다.
- scrollUntilVisible:
element:
id: mypage_edit_btn
direction: UP
- tapOn:
id: mypage_edit_btn
- extendedWaitUntil:
visible:
id: profile_edit_loaded
timeout: 30000
- scrollUntilVisible:
element:
id: profile_edit_nickname_field
direction: DOWN
- assertVisible:
id: profile_edit_nickname_field
text: ${output.originalNickname}
- scrollUntilVisible:
element:
id: profile_edit_introduction_field
direction: DOWN
- assertVisible:
id: "${output.originalIntroduction === '' ? 'profile_edit_introduction_empty' : 'profile_edit_introduction_present'}"
- runFlow:
when:
true: ${output.originalIntroduction !== ''}
commands:
- assertVisible:
id: profile_edit_introduction_field
text: ${output.originalIntroduction}
- copyTextFrom:
id: profile_edit_introduction_field
- assertTrue: ${maestro.copiedText === output.originalIntroduction}
Loading
Loading