// APP FACTORY · EP. 11
스토어 출시에서 API로 안 되는 구간을 콘솔 자동 운전으로 메웠습니다. 폼은 저항했고 저장은 서버에서 깨졌습니다. 그런데 그 구간을 정리해 보니 하나는 애초에 API가 있었고, 제가 도구에 옵션을 안 만든 것이었습니다.
1. 스토어까지 보내려면 손이 남아 있었습니다
지난 글에서 반복 루프를 직접 짠 과정을 말씀드렸습니다. 그런데 그 루프가 만든 앱을 스토어까지 보내려면 아직 사람 손이 남아 있었습니다. 콘솔에 들어가 앱을 만들고, 등록정보를 쓰고, 설문에 답하는 일입니다.
배포는 두 갈래입니다. 하나는 테스터에게 보내는 배포로, Firebase App Distribution을 씁니다. 스크립트 하나로 끝나고 121줄이며 앱별 하드코딩이 없습니다. 패키지 이름은 build.gradle.kts에서 뽑고 Firebase 앱 식별자는 google-services.json에서 그 패키지로 매칭합니다. 새 앱은 파일만 제자리에 있으면 추가 작업 없이 배포됩니다.
다른 하나가 스토어입니다. Android Publisher API를 쓰고 파이썬 파일 하나로 301줄입니다. 서비스 계정 키 하나로 개발자 계정의 모든 앱을 배포합니다. 그 키는 저장소에 없습니다. 무시 목록에 넣어 두고 환경 변수나 경로로만 찾습니다.
문제는 이 API가 못 하는 일이 있다는 것이었습니다.
2. API로 되는 것과 콘솔에만 있는 것
문서를 정리해 보니 경계가 갈렸습니다.
API로 됩니다. AAB 업로드, 트랙 출시, 단계적 출시 비율, 릴리즈 노트, 등록정보 텍스트, 그래픽 자산. 전부 edits.insert → 변경 → edits.commit의 편집 트랜잭션으로 원자 반영됩니다.
콘솔에만 있습니다. 신규 앱 생성, 데이터 안전 양식, 콘텐츠 등급 설문, 광고 ID 선언, 최초 심사 제출.
이 경계는 다른 도구를 써도 같습니다. 유명한 배포 도구들도 결국 같은 API를 호출하므로, API가 못 하는 것은 그 도구들도 못 합니다.
3. 콘솔 화면을 기계가 운전하게 했습니다
오른쪽을 어떻게 할 것인가. 사람이 하면 앱을 낼 때마다 같은 화면을 처음부터 다시 밟아야 합니다. 그래서 콘솔 화면을 기계가 직접 운전하게 했습니다.
여기서 중요한 결정이 하나 있었습니다. 자동화 도구가 브라우저를 새로 띄우게 하지 않았습니다. 제가 이미 로그인해 둔 크롬에 디버깅 포트로 붙어서 그 창을 그대로 운전하게 했습니다. AdMob 자동화를 다룬 앱 공장 4화에서 쓰던 접속 방식을 그대로 가져다 썼습니다.
이유는 계정입니다. 이 공장의 규약에서 최우선으로 못 박아 둔 것이 계정 보호입니다. 플레이 개발자 계정과 광고 계정은 대체 불가 자산이기 때문입니다. 자동화 도구가 새 브라우저를 띄워 로그인부터 하게 하면 로그인 흐름 자체를 기계가 만지게 됩니다. 반면 사람이 이미 열어 둔 창에 붙는 방식은 인증은 사람이 하고 클릭만 기계가 합니다. 같은 자동화처럼 보여도 계정 위험은 다릅니다.
4. 폼이 저항했습니다
앱 하나를 올리면서 앱 생성부터 등록정보, 그래픽 업로드, 스토어 설정까지 자동으로 통과했습니다. 9단계 중 8단계가 그냥 됐습니다. 그런데 콘텐츠 등급 설문에서 멈췄습니다. 질문 14개에 전부 아니오로 답했는데 다음 버튼이 계속 비활성이었습니다.
답을 안 한 문항이 남아 있나 싶어 화면을 다시 훑었는데 전부 채워져 있었습니다. 원인은 폼의 구조였습니다. 이 폼은 답을 다 해도 다음이 열리지 않습니다. 저장을 눌러야 그때 서버가 폼을 검증하고 다음이 열립니다. 저장이 저장이 아니라 검증 트리거였습니다. 다음이 안 열리는 것을 답이 모자란 것으로 읽으면 영원히 찾지 못합니다.
클릭도 문제였습니다. 화면에 보이는 “아니요”라는 글자를 눌렀는데 답이 반영되지 않았습니다. 기본 라디오 버튼이 아니라 직접 만든 컨트롤이라 글자를 누르면 변경 이벤트가 발화하지 않습니다. 게다가 접혀 있는 구역에도 같은 글자가 숨어 있어 엉뚱한 것을 집기도 했습니다. 결국 role=radio인 요소를 직접 누르고 예/아니오는 연결된 label[for]로 판별했습니다. 이 선택자 설명은 실제 관측을 줄여 적은 것이며, 저장소의 구현 코드를 그대로 옮긴 것은 아닙니다.
가장 비쌌던 함정은 이것입니다. 저장하지 않은 상태에서 화면을 새로 고치면 답이 전부 사라집니다. 콘텐츠 등급은 카테고리를 고르는 첫 단계로 돌아갑니다. 자동화가 막히면 반사적으로 새로 고침을 누르게 되는데, 그때마다 14문항을 다시 답한 셈이었습니다. 그래서 이 폼은 한 세션에 끝낸다는 규칙을 적어 뒀습니다.
5. 저장이 서버에서 깨졌습니다
여기까지는 우리가 잘못 다룬 것들이라 고치면 됐습니다. 그런데 데이터 안전 양식에서는 성격이 다른 벽을 만났습니다.
답을 정확히 채우고 임시저장을 누르면 구글이 예기치 않은 오류라며 코드를 던졌습니다. 입력 직후에는 오류가 없었고 누르는 순간에만 났습니다. 클릭 방식을 바꾸고, 시간을 두고, 다시 시도하기를 6회 했는데 전부 같은 코드였습니다.
클릭 방식과 입력값을 바꿔도 같은 오류가 반복됐습니다. 내가 바꿀 수 있는 입력·클릭 쪽에서는 원인을 찾지 못했고, 당시에는 그 앱과 세션의 저장 경로 문제로 판단했습니다. 그런데 이 항목 하나가 안 끝나면 앱 설정이 11개 중 10개에서 멈추고 출시 트랙 자체가 잠깁니다. 파일을 올릴 자리가 아예 열리지 않습니다.
그런데 같은 화면에 답을 파일로 내보내고 파일로 가져오는 기능이 따로 있었습니다. 그래서 화면을 채우는 대신 파일을 채웠습니다. 현재 답이 반영된 CSV를 내려받아 형식을 파악하고, 782줄짜리 표를 빠짐없이 채워서 다시 올렸습니다. 가져오기는 화면의 임시저장과 다른 처리 경로였고 그대로 통과했습니다. 다만 파일이 완전해야 합니다. 데이터 유형까지 다 채우지 않은 파일로는 여전히 실패했습니다.
6. 그런데 그건 API가 있었습니다
이렇게 앱 하나를 올렸습니다. 국가와 지역도 골라야 해서 콘솔 목록에서 177개를 선택했습니다. 화면 자동화까지 붙였으니 잘 돌았다고 생각했습니다.
그런데 이 과정을 보고했더니 질문이 하나 돌아왔습니다. 초기 설정만 화면 자동화로 하고 배포는 API여야 하는 것 아니냐. 국가 지정은 배포 쪽인데 왜 콘솔에서 했느냐는 지적이었습니다.
맞는 지적이었습니다. Google Play Developer API의 TrackRelease 문서를 다시 열어 보니 국가 타게팅은 API에 있었습니다. 출시 항목 안에 countryTargeting으로 국가 목록과 나머지 지역 포함 여부를 넣는 자리가 처음부터 있었습니다. API가 못 하는 게 아니라, 제가 만든 도구에 그 옵션이 없어서 콘솔로 우회한 것이었습니다. 한계라고 부른 것이 사실은 제 도구의 빈칸이었습니다.
그리고 이것은 화면을 아무리 잘 운전해도 드러나지 않습니다. 자동화가 잘 돌수록 빈칸은 빈칸인 채로 덮입니다.
그래서 도구에 국가 옵션을 넣고 규칙을 적었습니다. API로 되는 건 전부 API로 하고, 콘솔은 API가 원천적으로 없는 항목만 쓴다. 그리고 판별법을 같이 적었습니다. 배포 단계에서 화면 자동화를 쓰게 되면, 짜기 전에 “이거 API 없나”부터 의심할 것.
7. 진짜로 API가 없는 곳은 셋이었습니다
그렇게 걸러내고 나니 진짜로 API가 없는 자리는 셋만 남았습니다.
- 서비스 계정에 그 앱 권한을 주는 것
- 광고 ID·데이터 안전·콘텐츠 등급 같은 정책 선언
- 게시 이력이 없는 앱의 최초 검토 제출
셋 다 앱당 한 번뿐인 일입니다. 셋째는 실제로 부딪혀 보고 알았습니다. 서비스 계정으로 프로덕션에 올렸더니 400 오류가 나면서 “초안 상태인 앱에는 초안 상태의 릴리즈만 만들 수 있다”고 했습니다. 한 번도 게시된 적 없는 앱은 계정에서 draft로 잡혀 있고, 그 앱의 첫 릴리즈는 반드시 draft여야 합니다. 권한 문제로 오해하기 딱 좋은 오류인데, 권한이 없으면 400이 아니라 403이 납니다.
그래서 최초 출시만 이렇게 됩니다. draft 상태로 올리는 것까지는 API가 하고, 콘솔에서 사람이 검토 제출을 누릅니다. 두 번째 릴리즈부터는 명령 한 줄입니다. 트랙과 국가와 릴리즈 노트가 전부 옵션으로 들어가고, 커밋하면 심사에 자동으로 올라갑니다. 콘솔은 한 번도 열지 않습니다. 영상에 나온 명령과 출력은 이 표준 경로를 설명하기 위한 예시이며 실제 실행 로그를 그대로 복사한 것은 아닙니다.
# 2회차 이후 표준 경로 예시
play_publish.py party_games --track production --countries all
8. 되돌릴 수 있나
그럼 남은 사람 자리는 어디까지일까요. 처음에는 콘솔 셋업을 통째로 사람에게 넘겼습니다. 콘솔은 위험한 곳이라고 생각했기 때문입니다. 그런데 그게 과실이었고 지금은 규약에 재발 방지로 적혀 있습니다.
앱을 만들고 등록정보를 쓰고 설문에 답하는 것은 전부 draft이고 수정 가능하고 비공개입니다. 되돌릴 수 있는 일을 사람이 붙잡고 있으면 자동화가 거기서 멈춥니다.
그래서 판정 기준은 콘솔이냐 API냐가 아닙니다. 앱 공장 2화에서 정한 자율성 경계의 질문으로 돌아갔습니다.
- 되돌릴 수 있나 — 있으면 그냥 한다
- 대외에 공개되나 — 안 되면 그냥 한다
- 둘 다 아니오인 지점이 몇 개인가 — 그것만 게이트
감독형 수동이라는 말은 사람이 다 한다는 뜻이 아니라 그 한 지점만 지킨다는 뜻입니다. 나머지를 붙잡고 있으면 감독이 아니라 병목입니다.
9. 정리
API가 없다고 생각한 자리 중 하나는 제가 안 찾은 것이었고, 진짜 없는 셋은 앱당 한 번뿐이었습니다. 그리고 그 셋 중에서도 사람이 지키는 것은 되돌릴 수 없는 한 지점입니다. 자동화의 크기는 그 지점을 몇 개로 줄였느냐로 재는 것이 맞다고 생각합니다.
그런데 그 지점을 넘기고 나면 앱이 세상에 나갑니다. 다음 글에서는 출시가 왜 종점이 아닌지 이야기하겠습니다.


답글 남기기