Skip to content

TestFlight 릴리스 노트 자동 회전과 E2E 플로우 안정화 - #97

Merged
Roy-wonji merged 8 commits into
developfrom
feature/release-notes-rotation
Sep 8, 2026
Merged

TestFlight 릴리스 노트 자동 회전과 E2E 플로우 안정화#97
Roy-wonji merged 8 commits into
developfrom
feature/release-notes-rotation

Conversation

@Roy-wonji

Copy link
Copy Markdown
Collaborator

요약

배포 문구가 매번 커밋 제목 그대로 나가던 것을 고치고, Maestro E2E 플로우가 단독 실행돼도 돌도록 정리했다. 작업 중 발견한 런타임 크래시 하나와 UI 여백도 같이 수정했다.

변경 내용

TestFlight 릴리스 노트 자동 회전

기존에는 RELEASE_NOTES="$(git log -1 --pretty=%s)" 를 그대로 changelog 로 넘겨서 fix: 배포 오류 수정 같은 내부 작업 로그가 테스터에게 나갔다. 9월 7일 하루에만 같은 문구가 8번 반복됐다.

  • fastlane/release_notes.yml — 카테고리(design/feature/performance/fix/refactor/default)별 문구 풀
  • 커밋 제목 키워드로 성격을 판별하고, 빌드번호(YYMMDDHHMM)를 문구 개수로 나눈 나머지로 문장을 골라 별도 상태 없이 순환
  • conventional commit 접두사와 끝의 PR 번호를 벗겨 부제로만 붙임
안정화 했어요!

이번 변경: 배포 오류 수정
빌드 2609071714

E2E 플로우 선행조건

Maestro Studio 는 워크스페이스 config 의 flows 패턴과 무관하게 모든 플로우 파일을 목록에 띄운다. 그래서 로그인 상태를 전제하던 하위 플로우들이 단독 실행되면서 member.home.root / management.attendance.list 를 기다리다 30초 타임아웃으로 죽었다.

  • _shared/ensure_member_home.yaml, _shared/ensure_management_home.yaml 추가 — 홈이 아닐 때만 로그인
  • 피처 플로우 13개가 이를 먼저 호출
  • 이미 로그인돼 있으면 건너뛰므로 full_test 에서 추가 비용 거의 없음

Google 로그인 대기

실패 녹화 30.7초 지점에 accounts.google.com 이 아직 로딩 스피너를 띄우고 있었고 32초에 타임아웃으로 웹뷰가 닫혔다. 세션 문제가 아니라 콜드 로딩이 제한 시간을 넘긴 것이었다.

  • 로딩 문구가 사라질 때까지 대기 후 계정 화면 확인, 타임아웃 60초로 상향
  • 계정 선택 화면 제목은 Google 이 여러 요소로 쪼개 렌더할 수 있어 불안정하므로, 제목 대신 실제로 탭할 계정 행을 기다림
  • 실패 시 어느 화면에서 막혔는지 남기도록 스크린샷 추가

TCA destination 스코프 런타임 크래시

presentStaff 처리 중 _CaseScope witness 디맹글 실패로 크래시했다.

$store.scope(\.$destination, action: \.destination).qrcode 형태는 TCA 내부의 언더스코어 프로토콜 _OptionalStoreWithCaseScope 를 타는데, Management/Profile 이 별도 동적 링크 프레임워크라 associated type 의 witness 메타데이터가 런타임에 깨진다. 이 문법을 쓰던 곳은 StaffViewProfileView 두 곳뿐이었고 나머지는 이미 표준 scope(state:action:) 를 쓰고 있었다. 두 곳을 표준 형태로 맞췄다.

출석 목록 하단 여백

AttendanceCheckView.padding(.bottom, 20) 제거.

검증

  • xcodebuild build -scheme DDDAttendance-Stage -configuration StageBUILD SUCCEEDED (에러 0건)
  • 전체 Maestro 플로우 YAML 파싱 OK
  • ruby -c fastlane/Fastfile Syntax OK, 릴리스 노트 헬퍼 6개 케이스 실행 검증

남은 작업

  • profile/*, onboarding/invite_code.yaml 은 홈보다 깊은 선행조건(프로필 화면 진입)이라 이번에 포함하지 않았다. 같은 브랜치에서 이어서 정리한다.

Maestro Studio 는 워크스페이스 config 의 flows 패턴과 무관하게 모든 플로우 파일을
목록에 띄운다. 그래서 로그인 상태를 전제하던 하위 플로우들(clearState: false)이
단독 실행되면서 member.home.root / management.attendance.list 를 기다리다
30초 타임아웃으로 죽었다.

_shared/ensure_member_home.yaml 과 ensure_management_home.yaml 을 추가해
홈이 아닐 때만 로그인하도록 하고, 피처 플로우 13개가 이를 먼저 부르게 했다.
이미 로그인돼 있으면 건너뛰므로 full_test 에서는 추가 비용이 거의 없다.

워크스페이스 실행 대상도 엔트리포인트 두 개로 좁혔다.
실패 녹화 30.7초 지점에 accounts.google.com 이 아직 "잠시만 기다려 주세요..."
스피너를 띄우고 있었고, 32초에 30초 타임아웃으로 웹뷰가 닫혔다.
계정 세션 문제가 아니라 콜드 로딩이 제한 시간을 넘긴 것이었다.

로딩 문구가 사라질 때까지 먼저 기다리고 대기 시간을 60초로 늘렸다.
계정 선택 화면 제목은 Google 이 여러 요소로 쪼개 렌더할 수 있어 매칭이 불안정하므로,
제목 대신 실제로 탭할 계정 행이 뜨는 것을 기다린다.
어느 화면에서 막혔는지 남기도록 스크린샷도 추가했다.
지금까지는 커밋 제목을 그대로 changelog 로 넘겨서 "fix: 배포 오류 수정" 같은
내부 작업 로그가 테스터에게 나갔고, 9월 7일 하루에만 같은 문구가 8번 반복됐다.

release_notes.yml 에 카테고리별 문구를 두고 커밋 제목의 키워드로 성격을 판별한다.
같은 성격이라도 빌드번호(YYMMDDHHMM)를 문구 개수로 나눈 나머지로 골라
별도 상태 없이 매번 다른 문장이 나간다.
커밋 제목은 conventional commit 접두사와 끝의 PR 번호를 벗겨 부제로만 붙인다.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 7, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-08T02:47:50.751413Z d5030d2 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 734528c2a3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

# id 는 정규식으로 매칭되므로 둘 중 먼저 뜨는 쪽에서 풀린다.
- extendedWaitUntil:
visible:
id: "member.home.root|auth.login.root"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle the opposite role before waiting for member home

When this flow runs after an operations flow, clearState: false preserves the staff login and the app launches at management.staff.root. This wait accepts only the member home or login screen, so it times out before the conditional login_to_home flow can clear state and switch accounts; the newly added standalone guarantee therefore fails in a normal cross-role Studio sequence. Include the opposite-role root in the readiness condition; the management helper has the mirrored issue when a member is logged in.

Useful? React with 👍 / 👎.

Comment thread fastlane/Fastfile
haystack = subject.to_s.downcase

matched = categories.find do |category|
Array(category["keywords"]).any? { |keyword| haystack.include?(keyword.to_s.downcase) }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Match short release-note keywords on token boundaries

For English build/configuration subjects such as build: update dependencies or chore: rebuild pipeline, substring matching finds the design keyword ui inside build before reaching any later category. Those builds are consequently uploaded with a note claiming the UI changed even though no UI work occurred; match short Latin keywords on token boundaries or classify the conventional type separately.

Useful? React with 👍 / 👎.

Comment thread fastlane/Fastfile
return nil if subject.empty?

cleaned = subject
.sub(/\A(feat|fix|chore|refactor|docs|test|ci|build|perf|style)(\([^)]*\))?!?:\s*/i, "")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Strip the repository's bracketed commit prefixes

When the deployed commit follows the repository's documented [FIX]: 설명 format, this expression only recognizes an unbracketed token immediately followed by :, so the uploaded changelog still contains 이번 변경: [FIX]: 설명. Variants already present in history such as fix : 설명 also survive unchanged, defeating the stated goal of hiding internal commit prefixes; accept the bracketed and whitespace-separated forms as well.

AGENTS.md reference: AGENTS.md:L120-L124

Useful? React with 👍 / 👎.

@tuist

tuist Bot commented Sep 7, 2026

Copy link
Copy Markdown

🛠️ Tuist Run Report 🛠️

Tests 🧪

Scheme Status Cache hit rate Tests Skipped Ran Commit
DDDAttendance 58 % 835 0 835 5b87f146d

Builds 🔨

Scheme Status Duration Commit
DDDAttendance-Stage 42.4s 5b87f146d

Bundles 🧰

Bundle Commit Install size Download size
DDD 출석 5b87f146d
92.6 MB
Δ -11.0 MB (-10.65%)
26.8 MB

앞선 커밋에서 member/management 플로우 13개에 로그인 선행조건을 붙였지만,
profile/* 와 onboarding/invite_code 는 홈보다 깊은 상태(프로필 화면 진입)를
전제해서 빠져 있었다. Studio 에서 단독 실행하면 profile.main.root 를 기다리다 죽는다.

_shared/ensure_profile_main.yaml 을 추가했다.
이미 프로필 화면이면 아무것도 하지 않고, 아니면 운영진 홈을 확보한 뒤
management.staff.profilebutton 으로 진입한다.

profile/home_smoke 는 "홈에서 프로필로 진입" 자체가 검증 대상이라
프로필 진입까지 대신하지 않고 운영진 홈 보장만 붙였다.

이로써 하위 플로우 18개 전부 단독 실행이 가능해졌다.
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

✅ 테스트 통과

835 passed · 6m 14s · 총 835개

iPhone 17 Pro (iOS Simulator 26.5) · Tuist 테스트 실행 · Tuist 빌드 실행 · d5030d2 · 워크플로 로그

📦 Bundle 크기

Bundle Install size Download size
DDD 출석 92.6 MB 26.8 MB

📊 커버리지 51.3% (13,231 / 25,787 라인)

🟡 ██████████░░░░░░░░░░

모듈별 커버리지
모듈 커버리지 라인
🔴 DDDThirdParty ░░░░░░░░░░░░░░░░░░░░ 0.0% 0 / 15
🔴 DomainAssembly ░░░░░░░░░░░░░░░░░░░░ 0.0% 0 / 7
🔴 FeatureSharedUI ░░░░░░░░░░░░░░░░░░░░ 0.0% 0 / 330
🔴 DDDAuthInterface ██░░░░░░░░░░░░░░░░░░ 9.1% 2 / 22
🔴 Member ████░░░░░░░░░░░░░░░░ 17.6% 636 / 3,621
🟡 Management ██████░░░░░░░░░░░░░░ 30.0% 1,619 / 5,390
🟡 OnBoarding ███████░░░░░░░░░░░░░ 36.4% 1,132 / 3,108
🟡 AuthDomain █████████░░░░░░░░░░░ 45.1% 314 / 697
🟡 DDDAttendance █████████░░░░░░░░░░░ 45.1% 667 / 1,478
🟡 Auth ██████████░░░░░░░░░░ 49.8% 253 / 508
🟡 MyPageDomainInterface ██████████░░░░░░░░░░ 50.0% 23 / 46
🟡 OnBoardingDomain ███████████░░░░░░░░░ 54.8% 212 / 387
🟡 AttendanceDomain ████████████░░░░░░░░ 58.0% 142 / 245
🟢 DDDSharedUI ████████████░░░░░░░░ 60.8% 906 / 1,490
🟢 DDDNetwork ██████████████░░░░░░ 70.3% 319 / 454
🟢 DDDNetworkInterface ██████████████░░░░░░ 71.6% 116 / 162
🟢 QRCodeDomain ██████████████░░░░░░ 71.9% 87 / 121
🟢 QRCodeDomainInterface ███████████████░░░░░ 73.1% 49 / 67
🟢 CoreAssembly ███████████████░░░░░ 75.0% 9 / 12
🟢 ProfileDomainInterface ███████████████░░░░░ 75.4% 343 / 455
🟢 ScheduleDomainInterface ███████████████░░░░░ 76.5% 62 / 81
🟢 DDDCoreUtility ███████████████░░░░░ 77.1% 121 / 157
🟢 ProfileDomain ████████████████░░░░ 78.4% 283 / 361
🟢 VoteDomainInterface ████████████████░░░░ 79.4% 231 / 291
🟢 MyPageDomain ████████████████░░░░ 79.4% 54 / 68
🟢 DDDStorage ████████████████░░░░ 81.0% 230 / 284
🟢 DDDStorageInterface ████████████████░░░░ 81.1% 86 / 106
🟢 ServiceAssembly ████████████████░░░░ 81.8% 27 / 33
🟢 AppUpdateDomain █████████████████░░░ 82.9% 136 / 164
🟢 VoteDomain █████████████████░░░ 83.2% 415 / 499
🟢 DDDAuth █████████████████░░░ 84.2% 165 / 196
🟢 AuthDomainInterface █████████████████░░░ 85.6% 869 / 1,015
🟢 AppUpdateDomainInterface █████████████████░░░ 87.1% 54 / 62
🟢 AttendanceDomainInterface ██████████████████░░ 88.3% 424 / 480
🟢 ScheduleDomain ██████████████████░░ 89.5% 179 / 200
🟢 OnBoardingDomainInterface ███████████████████░ 94.4% 761 / 806
🟢 Web ███████████████████░ 96.7% 234 / 242
🟢 Profile ███████████████████░ 97.1% 1,664 / 1,713
🟢 APIEndpoint ███████████████████░ 98.3% 407 / 414

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 32329ab00f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread fastlane/Fastfile
notes = Array(matching_category(categories, subject)&.fetch("notes", nil))
return nil if notes.empty?

notes[build_number.to_i % notes.size]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Rotate notes independently of timestamp jumps

When two deployments in the performance or refactor category occur at the same minute of different hours, their build numbers differ by 100 (for example, 2609071200 and 2609071300), so both select the same entry modulo the two-note pool. More generally, any build-number jump divisible by the note count repeats the previous message, meaning this does not actually prevent the consecutive repetition the change is intended to eliminate; use a persisted/previous selection or another sequence that advances exactly once per deployment.

Useful? React with 👍 / 👎.

PR #96 시점에는 커버리지가 정상이었다 (xccov framework 85개 / 타깃 124개, 14,553 / 29,539 라인). 이후 d716055 "fix: 배포 오류 수정" 이 App Store 업로드 거부(framework 안 중첩 Frameworks/libswift_*.dylib)를 막으려고 외부 의존성 7개를 .framework 에서 .staticFramework 로 바꿨다.

정적으로 링크되면 모듈이 소비자 바이너리에 흡수돼 독립 바이너리가 사라지고 xccov 가 개별 타깃으로 잡지 못한다. 그 결과 framework 타깃이 3개(FeatureAssembly, Management, Profile)로 줄고 리포트가 3,987 / 8,581 로 붕괴했다. 테스트는 860개 모두 정상 실행됐다. 계측 대상 바이너리가 없었을 뿐이다.

develop 배포도 Stage 스킴을 쓰므로 스킴으로는 가를 수 없다. TUIST_RELEASE_BUILD 환경변수로 갈라 ios-deploy 워크플로에서만 정적으로 링크한다. PR·develop 테스트 빌드는 동적으로 돌아가 계측이 붙는다.

되돌린 대상은 그 커밋이 바꾼 7개뿐이고 원래부터 정적이던 항목은 건드리지 않았다: AppCheckCore, IdentifiedCollections, TCAFlow, Clocks, CombineSchedulers, ConcurrencyExtras, SwiftUINavigation.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dee162c139

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (summary.failures.length > 0) lines.push(...renderFailures(summary.failures));
lines.push(...renderBundleInsights(bundleInsights));
lines.push(...renderCoverage(coverage));
lines.push(...renderCoverage(coverage, coverageStats));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Pass coverage stats into the report renderer

Whenever an xcresult summary is readable, renderReport reaches this call and throws ReferenceError: coverageStats is not defined: that variable is local to the exported action function, not visible in the top-level renderer's lexical scope. As a result, the Report to PR action fails instead of posting results for normal successful test runs; include the stats in renderReport's arguments.

Useful? React with 👍 / 👎.

Comment thread Tuist/Package.swift
Comment on lines +24 to +25
private let deploymentLinked: ProjectDescription.Product =
ProcessInfo.processInfo.environment["TUIST_RELEASE_BUILD"] == "1" ? .staticFramework : .framework

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Enable static linking inside Fastlane deployment lanes

When the documented bundle exec fastlane ios QA or bundle exec fastlane ios release command is run outside ios-deploy.yml, this environment variable is absent, so apply_build_number regenerates the workspace with these dependencies as dynamic frameworks. That bypasses the static-link safeguard and can recreate the nested Frameworks/libswift_*.dylib upload rejection this change is meant to prevent; set the release flag from the deployment lanes before generation rather than relying solely on one workflow.

Useful? React with 👍 / 👎.

renderReport 안에서 coverageStats 를 참조하는데 그 변수는 module.exports 함수의 지역 변수였다. 파라미터로 넘기도록 고쳤다.

기존 테스트는 renderReport 를 호출하지 않아 이 오류를 CI 에서야 발견했다. 렌더 경로 전체를 한 번 태우는 테스트를 추가했고, 수정 전 코드에서 실제로 실패하는 것을 확인했다.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d5030d2277

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

uses: actions/github-script@v7
env:
RESULT_BUNDLE_DIR: Artifacts/tests
COVERAGE_SHARD_IDS: ${{ join(fromJSON(needs.build-test-shards.outputs.matrix).shard, ',') }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Guard the shard matrix before parsing it

When build-test-shards fails before the Tuist command writes its matrix output—for example during setup, dependency installation, or compilation—this job still runs because its job-level condition accepts failed builds, but this expression evaluates fromJSON('') and fails before ios-test-report.js can execute. Consequently, the PR receives no diagnostic report for the build failures the reporter is intended to render; default an absent matrix to an empty shard list before parsing it.

Useful? React with 👍 / 👎.

Comment on lines +19 to +21
- extendedWaitUntil:
notVisible: ".*잠시만 기다려 주세요.*|.*Just a moment.*"
timeout: 90000

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Wait for positive Google page readiness

When SafariViewService initially displays a blank page or has not yet inserted the loading text after the optional Continue tap, this notVisible condition is already satisfied and returns immediately, so the advertised 90-second cold-load wait is bypassed. The role-specific callers then wait only 60 seconds for an account row, meaning a page that takes longer still times out; wait for a positive readiness signal or first establish that the loading indicator appeared before waiting for it to disappear.

Useful? React with 👍 / 👎.

@Roy-wonji
Roy-wonji merged commit 36f1974 into develop Sep 8, 2026
6 checks passed
@Roy-wonji
Roy-wonji deleted the feature/release-notes-rotation branch September 8, 2026 03:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant