React Native 키오스크의 Detox E2E를 iOS·Android로 분리한 과정
React Native 키오스크의 핵심 사용자 흐름을 Detox로 자동화하고 testID, 플랫폼별 실행 조건, 실패 artifacts를 정비한 과정을 정리합니다.
키오스크 앱의 핵심 흐름은 온보딩에서 끝나지 않습니다. 로그인, 지점 선택, 번호 입력, 사용자 선택, 입장 처리까지 여러 화면과 상태가 이어집니다. 기능을 수정할 때마다 이 전체 흐름을 사람이 반복해서 확인하면 시간이 오래 걸리고, 플랫폼에 따라 다른 동작을 놓치기 쉽습니다.
그래서 Detox를 이용해 실제 사용자 흐름을 재현하는 E2E 테스트를 구성했습니다. 처음에는 공통 테스트를 만들었고, 실행 과정에서 확인된 iOS와 Android의 차이를 각 플랫폼 테스트로 분리했습니다.
화면 문구 대신 안정적인 식별자를 사용했습니다
자동화 테스트가 화면의 한글 문구에 의존하면 문구가 조금만 바뀌어도 테스트가 실패합니다. 번역이 추가되거나 같은 문구가 여러 곳에 나타날 때도 대상을 정확히 찾기 어렵습니다.
버튼, 입력 필드, 목록과 카드에 testID를 추가해 테스트와 화면 사이의 계약을 만들었습니다. 반복되는 항목은 실제 데이터 식별자를 조합했습니다.
아래 코드 예시는 설명을 위해 식별자와 운영 설정값을 일반화했습니다.
<Container testID={`location-card-${location.id}`}>
<Title testID={`location-name-${location.id}`}>{location.name}</Title>
<Button testID={`location-open-${location.id}`} onPress={() => selectLocation(location)}>선택</Button>
</Container>
키패드와 사용자 카드도 같은 방식으로 식별했습니다.
testID={`keypad-key-${key.value}`}
testID={`person-card-${person.id}`}
목록 순서가 달라져도 특정 항목을 찾을 수 있고, 테스트가 화면 문구나 좌표에 덜 의존하게 됐습니다. E2E를 추가하면서 제품 코드의 테스트 가능성도 함께 높인 것입니다.
사용자 여정을 작은 검증 단계로 나눴습니다
한 개의 긴 테스트에서 모든 동작을 실행하기보다 사용자가 실제로 지나가는 상태를 단계별로 확인했습니다.
flowchart LR A[앱 실행] --> B[온보딩 건너뛰기] B --> C[로그인] C --> D[지점 선택] D --> E[사용자 확인] E --> F[입장 처리] F --> G[결과 화면 확인]
이 구조에서 중요한 점은 버튼을 누르는 것만이 아니라 다음 화면이 실제로 준비됐는지 확인한 뒤 진행하는 것입니다.
await element(by.id("onboarding-skip-button")).tap();
await waitFor(element(by.id("sign-in-screen")))
.toBeVisible()
.withTimeout(5000);
고정된 시간만큼 무조건 기다리는 대신 waitFor로 화면 상태를 기다렸습니다. 네트워크와 애니메이션 속도가 조금 달라져도 필요한 요소가 준비된 시점에 다음 단계로 넘어갈 수 있습니다.
입장 과정에는 바로 입장되는 경우, 재입장 안내가 나타나는 경우, 미납 안내를 거치는 경우처럼 여러 분기가 있었습니다. 테스트 헬퍼에서 이 분기를 나눠 정상 흐름 하나만 통과하는 테스트가 되지 않도록 구성했습니다.
iOS와 Android 테스트를 분리했습니다
처음에는 같은 테스트 파일로 두 플랫폼을 다루려 했지만 실제 실행 조건은 같지 않았습니다.
- Android와 iOS의 카메라 권한 안내 문구와 시스템 UI가 달랐습니다.
- iOS에서는 일부 요소가 존재해도 visibility threshold 때문에
toBeVisible()검증이 불안정했습니다. - 화면 전환과 요소 탐색에 필요한 대기 시간이 달랐습니다.
- 각 플랫폼의 빌드 명령과 실행 대상도 달랐습니다.
iOS에서는 필요한 구간에 toExist()를 사용하고 대기 시간을 조정했습니다.
await waitFor(element(by.id("onboarding-skip-button")))
.toExist()
.withTimeout(5000);
최종적으로 onboarding.android.test.js와 onboarding.ios.test.js를 분리했습니다. 공통 사용자 여정은 유지하면서 플랫폼별 권한 처리와 검증 조건을 각 파일에서 명확하게 읽을 수 있게 한 선택입니다. 하나의 파일에 조건문을 계속 추가하는 것보다 실패한 플랫폼과 원인을 찾기 쉬웠습니다.
실패 원인을 남기도록 artifacts를 설정했습니다
E2E 실패는 로그 한 줄만으로 원인을 파악하기 어려울 때가 많습니다. Detox 설정에 screenshot과 log plugin을 추가하고 결과를 artifacts 디렉터리에 남겼습니다.
artifacts: {
rootDir: "./e2e/artifacts",
plugins: {
screenshot: {
enabled: true,
shouldTakeAutomaticSnapshots: true,
keepOnlyFailedTestsArtifacts: false,
takeWhen: {
testDone: true,
testFailure: true,
},
},
log: {
enabled: true,
keepOnlyFailedTestsArtifacts: false,
},
},
}
이를 통해 테스트가 어느 화면에서 멈췄는지 스크린샷과 로그를 함께 확인할 수 있게 했습니다. 플랫폼별 테스트 분리와 artifacts는 각각 실행 안정성과 디버깅 시간을 다루는 장치입니다.
자동화 범위와 한계도 구분했습니다
이번 테스트는 앱 내부의 단순 렌더링만 확인하지 않고 로그인과 입장까지 연결된 흐름을 다뤘습니다. 그만큼 백엔드 상태, 테스트 계정, 네트워크 응답에 영향을 받을 수 있습니다.
따라서 테스트 실패를 곧바로 앱 결함으로 단정하기보다 다음 항목을 함께 확인해야 합니다.
- 테스트 데이터가 기대한 상태인지
- 외부 API가 정상 응답했는지
- 권한 팝업이 이미 처리된 기기인지
- 앱 데이터가 테스트 시작 전에 초기화됐는지
- 실패 화면과 로그가 같은 원인을 가리키는지
E2E 기본 설정, 안정적인 testID, 사용자 흐름 테스트, 플랫폼별 파일과 실패 artifacts를 공용 main 브랜치에 반영했습니다.
정리
Detox 도입에서 가장 중요했던 것은 테스트 파일의 개수가 아니라 사용자 흐름을 안정적으로 재현하고 실패 원인을 찾을 수 있는 구조였습니다.
- 화면 문구 대신
testID로 요소를 식별했습니다. - 온보딩부터 입장까지의 흐름을 단계별로 검증했습니다.
- iOS와 Android의 권한·가시성 차이를 별도 테스트로 분리했습니다.
- 스크린샷과 로그를 artifacts로 남겼습니다.
플랫폼 차이를 억지로 감추기보다 공통 흐름과 다른 조건을 구분하면서, 테스트 코드 자체도 제품의 실행 환경을 설명하는 문서가 되도록 정비했습니다.