モバイルエンジニアインターン生の筒井です。 本記事では、私が入社して行ってきたReact Nativeプロジェクトの、GitHub Actions上でのスクリーンショットテスト実装についてお話しします。 これによりGithub Actions上でReact Nativeアプリが動き、そのスクリーンショットが保存され、確認できるようになります。
背景
アプリ開発において、自動テストを行うことでコード内のエラーなどに気づくことができます。 しかし、Natureでは自動テストでロジックの確認は行なっていますが、現状、画面のレイアウト崩れは検知することができません。 そのため、実際に実装画面を見てみないと分からないデザインの崩れや画面遷移の確認などは、手元で動かして確認しています。 手元での確認は、 ただ、この確認作業はビルド→起動→遷移の確認...と手順が多く手間な作業です。 そこで、GitHub Actions上で実際にアプリを動作させ、その際のスクリーンショットを保存して一度に画面遷移を確認を行いたいというのが今回のモチベーションになります。
なお、数あるテストフレームワークの中で今回はDetoxと呼ばれるフレームワークを利用して実装を行いました。 (本記事では、テストフレームワークの選定などの話には言及せず、実装方法について詳しくお話しします。)
Detox設定・使い方
Detoxを利用するためには、まず package.json に必要な設定を追加し、detox.config.js などで環境を定義します。 本章では、Detoxの基本的な使い方として、テスト対象の要素を操作する方法や、スクリーンショットの取得方法を紹介します。
1. Detoxのインストール
Detox をプロジェクトに追加します。 その後、必要なパッケージなどを全てインストールします。
# このページを参考にプロジェクトの設定を行う(https://wix.github.io/Detox/docs/next/introduction/environment-setup) npm install detox-cli --global brew tap wix/brew brew install applesimutils npm install "jest@^29" --save-dev npm install detox --save-dev
2. detox.config.js の設定
Detoxを実行するために、<PROJECT_ROOT>/detox.config.js
でデバイスの設定を行います。
module.exports = { testRunner: "jest", runnerConfig: "e2e/jest.config.js", apps: { "ios.sim.debug": { # バイナリファイルが生成されるパスを指定 # 本プロジェクトでは、xcodebuildコマンドのderivedDataPathオプション利用することで、バイナリファイルのパスを指定 binaryPath: "ios/build/Build/Products/Debug-iphonesimulator/Nature.app", build: "xcodebuild -workspace ios/Nature.xcworkspace -scheme Nature -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build", }, }, devices: { "ios.sim.debug": { type: "ios.simulator", # 使いたいデバイス名を指定 device: { type: "iPhone 16 Pro", }, }, }, configurations: { "ios.sim.debug": { device: "ios.sim.debug", app: "ios.sim.debug", }, }, };
ここで
- binaryPath にアプリのビルド後の .app のパスを指定
- build に xcodebuild のビルドコマンドを設定
- device.type に使用する シミュレーター を指定
これにより、detox test --configuration ios.sim.debug を実行すると、指定したシミュレーターでテストが動作するようになります。
3. テスト画面の作成
Detoxでは、React Native アプリをプログラム上で自動操作 することができます。 ただし、動かしたい画面には事前にUI要素を識別するために testIDを設定する必要があります。
例: testIDの設定
想定画面
コード内でのtestIDの割り当て
return ( <SafeAreaView style={styles.container}> <Text style={styles.text}>ログイン画面</Text> <TextInput testID="input-id" # ID入力フォームにtestIDを指定 style={styles.input} placeholder="ID" value={id} onChangeText={setId} autoCapitalize="none" /> <TextInput testID="input-password" # PASSWORD入力フォームにtestIDを指定 style={styles.input} placeholder="PASSWORD" value={password} onChangeText={setPassword} secureTextEntry /> <Button title="ログイン" onPress={handleLogin} testID={"login-button"} # ログインボタンにtestIDを指定 /> </SafeAreaView> ); }
テストの作成
testIDの割り振り後、テストを動かすためのテストコードを作成します。 割り当てた要素ごとに、タップ、スワイプ、テキスト入力などを行うことが可能です。
it('screenshot test', async () => { # 初期画面をスクリーンショット await device.takeScreenshot('first screen'); # input-idの要素にIDを入力 # ID入力後の画面をスクリーンショット await element(by.id('input-id')).tap(); await element(by.id('input-id')).typeText('admin'); await device.takeScreenshot('input_id'); # input-passwordの要素にPWを入力 # PW入力後の画面をスクリーンショット await element(by.id('input-password')).tap(); await element(by.id('input-password')).typeText('admin'); await device.takeScreenshot('input_pass'); # login-buttonの要素をタップ # 画面遷移後の画面をスクリーンショット await element(by.id('login-button')).tap(); await device.takeScreenshot('After_login'); });
4. Detoxを自分のマシンで実行
# アプリをビルド detox build --configuration ios.sim.debug # テストを実行 detox test --configuration ios.sim.debug
このコマンドでシミュレーターを起動し、テストを実行できます。

① 初期画面
await device.takeScreenshot('first screen');

② ID入力
await element(by.id('input-id')).tap();
await element(by.id('input-id')).typeText('admin');
await device.takeScreenshot('input_id');
input-idの要素をタップしてIDを入力
その後スクリーンショットを取得

③ PW入力
await element(by.id('input-password')).tap();
await element(by.id('input-password')).typeText('admin');
await device.takeScreenshot('input_pass');
input-passwordの要素をタップしてPWを入力
その後スクリーンショットを取得

④ 画面遷移後
await element(by.id('login-button')).tap();
await device.takeScreenshot('After_login');
login-buttonの要素をタップして画面遷移
その後スクリーンショットを取得
保存された画像は、<PROJECT_ROOT>/artifacts
以下に保存されます。
以上で、 Detoxの使い方に関する解説は以上になります。 次の章では、github actions上での実装方法について述べていきます。
github actionsの環境構築
続いて、自動テストを動かすために必要な環境構築についてです。
GitHub Actionsでテストを動かすためには、ワークフロー (Workflow)と呼ばれるYAMLファイルを定義する必要があります。
YAMLファイルは、<PROJECT_ROOT>/.git/workflows
以下に作ります。
このワークフローに従い、環境構築や行いたい自動テストを実行します。
本テストで実装したワークフローは以下のようなコードになっております。
name: Detox Test # 任意のタイミングでテストを実行 on: workflow_dispatch: jobs: build: runs-on: macOS-15 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: recursive - uses: actions/setup-node@cdca7365b2dadb8aad0a33bc7601856ffabcc48e # v4.3.0 with: node-version: 18 - name: Initialize and update submodules run: | git submodule sync git submodule update --init --recursive - uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0 with: xcode-version: '16.0' - uses: ruby/setup-ruby@1a615958ad9d422dd932dc1d5823942ee002799f # v1.227.0 with: ruby-version: '3.3' bundler-cache: true - name: Config run: | gem install bundler brew install cocoapods npm install --legacy-peer-deps cd ios rm -rf .xcode.env.local rm -rf Podfile.lock bundle exec pod install --repo-update # バイナリファイル生成 - name: build run: xcodebuild -workspace ios/Nature.xcworkspace -configuration Debug -scheme Nature -destination "platform=iOS Simulator,name=iPhone 16,OS=18.0" -derivedDataPath ios/build - name: Install macOS dependencies run: | npm install detox-cli --global brew tap wix/brew brew install applesimutils brew install tmux - name: Test run: | # devサーバ起動 tmux new-session -d -s metro 'npm start --port 8081 --reset-cache' # シミュレータ起動 xcrun simctl boot "iPhone 16" xcrun simctl install booted /Users/runner/work/nature-app/nature-app/ios/build/Build/Products/Debug-iphonesimulator/Nature.app xcrun simctl launch booted global.nature.Remo # スクリーンショットテスト実行 detox test --configuration ios.sim.debug --take-screenshots all # 生成した画像をアップロード - name: Upload artifacts uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: detox-artifacts path: ./artifacts
ワークフローは基本的に下記の手順で動くようになっております。
- 基本設定(OSのバージョン指定など)
- xcodeビルドによるバイナリファイル生成
- Detoxのインストール
- devサーバを起動させ、Detoxのテストを実行
- 保存した画像をアップロード
スクリーンショット画像はgithub actionsでテストを動かしたページの下記からダウンロードできるようになっています。
まとめ
今回は、React NativeプロジェクトのGitHub Actions上でのスクリーンショットテスト実装を行いました。 今回のテスト実装を通じて、Github Actionsの実装の難しいところや利便性について体験することができました。 今後の展望としては、スクリーンショットした画像を前回と比較して出力できるようにしたいと思います。 この記事を参考に、GIthub Actions上でのスクリーンショットテストをぜひ試してみて下さい!
インターンの感想&すゝめ
Natureでは、約1年間インターン生として働かせていただき、とても貴重な経験を積むことができました。 これまでは短期的な開発経験しかなかったのですが、Natureのインターンでは1年間プロダクト開発に携わることができ、開発の進め方やチームでの連携について多くを学ぶことができました。 また、「これをやってみたい」と自分から発信したことに対して、積極的に任せてもらえる風土があり、主体的に学ぶ姿勢を後押ししてくれる環境でした。 その一方で、エンジニアとしての足りない部分を的確にフィードバックしてもらえる機会も多く、自分の課題としっかり向き合うことができました。 インターンは基本的にリモートでの勤務でしたが、わからないことがあればすぐにハドルなどで相談できる体制が整っており、距離を感じることなく安心して働けました。
私の感想がこれからNatureでインターンを考えている方々の参考になれば幸いです!