Maestro 로 하는 React Native E2E 테스트

eeennsu·1일 전

React Native

목록 보기
99/100

개요

Maestro 는 mobile.dev 가 만든 모바일 UI 자동화, E2E 테스트 도구다. 사용자 흐름을 YAML 파일로 적어두면 에뮬레이터나 시뮬레이터에서 그대로 재생해준다. 앱 코드나 네이티브 빌드 설정을 바꾸지 않고, 빌드된 앱만 있으면 바로 돌릴 수 있다.

React Native 프로젝트에 Maestro 를 붙여서 실제로 운영할 때 필요한 설정과 명령, 자주 걸리는 문제를 정리한다.




설치

curl -fsSL "https://get.maestro.mobile.dev" | bash

# 또는 Homebrew
brew tap mobile-dev-inc/tap
brew install mobile-dev-inc/tap/maestro

Java 17 이상이 필요하고, JAVA_HOME 이 해당 버전을 가리키고 있어야 한다. 설치 후 maestro --help 로 확인하고, maestro list-devices 로 연결된 에뮬레이터와 시뮬레이터 목록을 볼 수 있다.



첫 flow 작성

testID 붙이기

Maestro 는 React Native 의 testID 를 id 선택자로 읽는다. 테스트에서 다룰 요소에 먼저 testID 를 붙인다.

<TextInput testID="login_email_input" placeholder="이메일" />
<TextInput testID="login_password_input" placeholder="비밀번호" secureTextEntry />
<Pressable testID="login_submit_button" onPress={handleLogin}>
  <Text>로그인</Text>
</Pressable>

flow 파일

# .maestro/login.yaml
appId: com.example.app
---
- launchApp:
    clearState: true
- tapOn:
    id: "login_email_input"
- inputText: "test@example.com"
- tapOn:
    id: "login_password_input"
- inputText: "password1234"
- hideKeyboard
- tapOn:
    id: "login_submit_button"
- assertVisible:
    id: "home_screen"
  • --- 위는 flow 설정, 아래는 실행할 명령 목록이다.
  • appId 에는 Android 패키지 이름이나 iOS 번들 ID 를 적는다.
  • clearState: true 로 매번 앱 데이터를 지운 상태에서 시작해야 이전 실행의 로그인 상태 때문에 결과가 달라지지 않는다.
  • 입력 후 키보드가 버튼을 가리는 화면이 많아서 hideKeyboard 를 넣어두는 편이 안전하다.

실행

maestro test .maestro/login.yaml

# 기기가 여러 대 연결돼 있으면 대상 지정 (test 앞에 위치)
maestro --device emulator-5554 test .maestro/login.yaml

# flow 를 작성하는 동안 저장할 때마다 다시 실행
maestro test -c .maestro/login.yaml

-c 는 파일을 저장할 때마다 flow 를 다시 돌려주는 모드라서, 새 flow 를 만들 때 켜두면 작업 속도가 많이 빨라진다.



자주 쓰는 명령

탭과 검증

- tapOn:
    id: "product_item_42"
- tapOn: "장바구니 담기"          # 화면에 보이는 텍스트로 찾기
- assertVisible:
    id: "cart_badge"
- assertNotVisible: "품절"

대기

Maestro 는 요소가 나타날 때까지 기본적으로 기다리기 때문에 sleep 을 넣을 필요가 없다. 기본 대기 시간으로 부족한 화면만 따로 처리한다.

# 로딩이 긴 화면
- extendedWaitUntil:
    visible:
      id: "home_screen"
    timeout: 10000

# 전환 애니메이션이 끝난 뒤에 진행
- waitForAnimationToEnd

스크롤

목록 아래쪽 요소는 scrollUntilVisible 로 찾을 때까지 스크롤한다. 기본 방향은 아래, 기본 제한 시간은 20초다.

- scrollUntilVisible:
    element:
      id: "product_item_42"
    direction: DOWN

조건 실행

팝업처럼 뜰 때도 있고 안 뜰 때도 있는 화면은 when 조건으로 처리한다. 조건이 맞지 않으면 실패하지 않고 건너뛴다.

- runFlow:
    when:
      visible: "나중에 하기"
    commands:
      - tapOn: "나중에 하기"

- runFlow:
    when:
      platform: Android
    file: subflows/android-permissions.yaml

권한

권한 팝업은 launchApp 에서 미리 정해두면 flow 에서 따로 처리하지 않아도 된다. Android 는 android.permission.* 전체 이름, iOS 는 notifications 같은 짧은 이름을 쓴다.

- launchApp:
    clearState: true
    permissions:
      all: deny
      notifications: allow

스크린샷

- takeScreenshot: checkout_done

중요한 단계마다 찍어두면 CI 에서 실패했을 때 어느 화면에서 멈췄는지 바로 확인할 수 있다.



flow 구성

공통 단계 분리

로그인처럼 여러 flow 에서 반복되는 단계는 subflow 로 빼고 runFlow 로 불러온다.

# .maestro/checkout.yaml
appId: com.example.app
---
- runFlow: subflows/login.yaml
- tapOn:
    id: "tab_cart"

환경 변수

계정 정보처럼 flow 에 직접 적으면 안 되는 값은 실행할 때 넘긴다.

- inputText: ${EMAIL}
maestro test -e EMAIL=test@example.com -e PASSWORD=secret .maestro/login.yaml

폴더와 태그

.maestro/
├── config.yaml
├── login.yaml
├── checkout.yaml
└── subflows/
    ├── login.yaml
    └── android-permissions.yaml
# .maestro/config.yaml
flows:
  - '*'

* 는 최상위 파일만 대상으로 하기 때문에 subflows 안의 파일이 단독으로 실행되지 않는다. 폴더를 넘기면 대상 flow 를 한 번에 실행한다.

flow 상단에 tags 를 달아두면 PR 마다 돌리는 스모크 테스트와 배포 전 전체 테스트를 나눠 실행할 수 있다.

appId: com.example.app
tags:
  - smoke
---
maestro test --include-tags=smoke .maestro



React Native 에서 주의할 점

텍스트보다 testID

텍스트 선택자는 처음 쓰기엔 편하지만 문구가 바뀌거나 다국어를 적용하면 바로 깨진다. 오래 유지할 flow 는 testID 기준으로 작성하고, 텍스트 선택자는 시스템 팝업처럼 testID 를 붙일 수 없는 곳에만 쓴다.

중첩된 터치 영역

iOS 에서 터치 가능한 컴포넌트가 여러 겹으로 감싸져 있으면, 바깥 컴포넌트가 하나의 접근성 요소로 묶이면서 안쪽 요소를 찾지 못하는 경우가 있다. 바깥 컴포넌트에 accessible={false} 를 주면 안쪽 요소가 따로 잡힌다.

디버그 빌드의 오버레이

디버그 빌드에서는 LogBox 경고 배너나 에러 화면이 버튼을 가려서 테스트가 실패하기도 하고, Metro 가 떠 있어야 앱이 동작한다. 로컬에서 flow 를 만들 때는 디버그 빌드로 충분하지만, CI 에서는 릴리즈 빌드로 돌린다.

Android 의 한글 입력

inputText 는 Android 에서 유니코드 문자를 지원하지 않는다. 한글 검색어나 이름을 입력하는 단계는 Android 에서 그대로 동작하지 않으니, 테스트 데이터는 영문과 숫자로 맞춘다. 한글 입력이 꼭 필요한 화면은 딥링크나 launchApp 의 arguments 로 값을 미리 넣어두는 방식으로 우회한다.



실패 원인 확인

  • Maestro Studio: 실행 중인 앱 화면에서 요소를 눌러 선택자를 확인하고, 명령을 바로 실행해 보면서 flow 를 만들 수 있다. "요소를 못 찾는다" 류의 실패는 여기서 실제 id 가 어떻게 잡히는지부터 확인한다.
  • --debug-output: 실패 시점의 스크린샷과 로그를 지정한 폴더에 남긴다.
maestro test --debug-output ./maestro-debug .maestro



CI 실행

CI 에서는 에뮬레이터를 띄우고 릴리즈 빌드를 설치한 뒤 실행한다. 결과는 JUnit 형식으로 남겨두면 CI 의 테스트 리포트 화면에서 바로 볼 수 있다.

maestro test \
  --include-tags=smoke \
  --format=JUNIT \
  --output=maestro-report.xml \
  --debug-output=./maestro-debug \
  .maestro

기기를 여러 대 띄울 수 있다면 --shards 로 flow 를 나눠 병렬로 돌릴 수 있다. 에뮬레이터 관리가 부담스럽다면 mobile.dev 의 클라우드 실행 서비스를 쓰는 방법도 있다.




정리

  • testID 가 id 선택자로 매핑되니, 다룰 요소에 testID 를 먼저 붙이고 텍스트 선택자는 최소한으로 쓴다.
  • clearState 로 매번 같은 상태에서 시작하고, 권한은 launchApp 에서 미리 정해둔다.
  • 요소가 나타날 때까지 자동으로 기다리니 sleep 대신 extendedWaitUntil 과 waitForAnimationToEnd 를 쓴다.
  • 공통 단계는 subflow 로, 계정 정보는 -e 환경 변수로 분리하고, tags 로 스모크 테스트를 나눈다.
  • CI 에서는 릴리즈 빌드로 돌리고, JUnit 리포트와 --debug-output 을 남겨둔다. Android 에서는 한글 입력이 안 된다는 점을 감안해 테스트 데이터를 준비한다.
profile
이력서 https://resume.eunsu.pro

0개의 댓글