Slide 1

Slide 1 text

2025/09/19 Ryuta Kibe 半自動E2Eで手っ取り早く リグレッションテストを効率化しよう @beryu

Slide 2

Slide 2 text

2

Slide 3

Slide 3 text

3

Slide 4

Slide 4 text

目次 1. 効率化する前 2. 自動E2Eテストの構築に挑戦 3. いろいろあって再設計 4. 半自動E2Eテストの実装 5. 付録(時間があれば話す)

Slide 5

Slide 5 text

1 弊社が効率化する前の リグレッションテスト

Slide 6

Slide 6 text

リリース前のテスト(頻度:週1) 新しいバージョンを申請する前に iOSエンジニアが手動でリグレッションテストを実施 リグレッションテストを してから 申請

Slide 7

Slide 7 text

スクラム活動中のテスト(頻度:週1) スクラムのスプリント中にも同様のテストを手動で実施 プランニ ング 開発 レビュー レトロ 開発とスプリントレビューの間で リグレッションテストを実施

Slide 8

Slide 8 text

毎週2回、手動でリグレッションテストをしていた

Slide 9

Slide 9 text

去年末まで使用していたテスト項目書の記述 “新規ユーザー登録からの本人確認が完了できる”  →どの本人確認書類で手続きすることを指してるの? “マッチングができる”  →どんな経路でマッチングすることを指してるの?

Slide 10

Slide 10 text

作業者によってテストシナリオがブレる どの経路がテストされるかは運任せ

Slide 11

Slide 11 text

リグレッションテストを 機械化・自動化すれば解決するはず!

Slide 12

Slide 12 text

2 自動E2Eテストの構築に 着手

Slide 13

Slide 13 text

自動E2Eテストのアーキテクチャ GitHub CI / CD環境 アプリバイナリ テスト自動化 SaaS E2Eテストを定期 実行 手動操作が不要 クラウド上でE2Eテストを実行

Slide 14

Slide 14 text

私達には難しかった…

Slide 15

Slide 15 text

UI要素の検出に失敗(成功することもある) - タップ、スワイプ、といった操作の対象のUI部品が検出できない - 部品が現れるまで待機するコマンドを実行していても失敗する

Slide 16

Slide 16 text

シミュレータのパフォーマンス問題 - クラウド環境のiPhoneシミュレータの動作が稀に遅くなる - ローカル環境で同じシナリオを実行すると成功する箇所が、クラウド 環境で実行するとタイムアウト

Slide 17

Slide 17 text

- クラウドにアプリバイナリをアップロードする工程で失敗する - UI要素の検出に成功したり失敗したりする - クラウドで動作するSimulatorのパフォーマンスが悪く、タイムアウトして しまう - 位置情報やプッシュ通知のパーミッションの許可を求めるダイアログが表 示されたりされなかったりする(それが原因でシナリオが失敗する) - シナリオ実行失敗時、その失敗の直接的な原因を自力では読み取れず、頻 繁にSaaSサポートに問い合わせ - 画面遷移をスワイプ操作で行うような特殊なUIの画面で、遷移が成功した り失敗したりする - SaaSの仕様上の理由でシナリオの部分的なデバッグが難しいケースがあ り、try & errorで時間を浪費してしまう etc… つまずき一覧 他にも細かいつまずきを たくさんしました…

Slide 18

Slide 18 text

- 私達の知識が足りていないことが主要因 - 挙動を想像出来ていない箇所に問題が集中していた - テスト自動化SaaSは多数の導入実績があり、私達に課題があると考 えるのが自然 - 弊社のAndroidアプリでも、SaaSを用いた自動E2Eテストが既に実現されている 補足:SaaSは悪くない …トラブルの解消に時間がかかっていた箇所 GitHub CI / CD環境 アプリバイナリ テスト自動化 SaaS E2Eテストを定期 実行

Slide 19

Slide 19 text

サービス仕様との相性問題も - 一部のシナリオが全自動では実現できない - SMS認証によるサインアップ - SMSが届く電話番号が必要 - QRコードによる出退勤 - カメラ(シミュレータでは動作しない)が必要

Slide 20

Slide 20 text

- 2023年10月:調査に着手 - 2024年11月:まだ調査中… 気付けば、着手から1年以上が経過していた

Slide 21

Slide 21 text

自動E2Eテストの構築・検証に14か月かけた

Slide 22

Slide 22 text

自動E2Eテストの構築・検証に14か月かけた →それだけチームが実現を望んでいるということ

Slide 23

Slide 23 text

Done is better than perfect

Slide 24

Slide 24 text

まずは60〜70点くらいの成果を早く出したい

Slide 25

Slide 25 text

3 リグレッションテストの 自動化再設計

Slide 26

Slide 26 text

新アーキテクチャはローカル環境を中心に据えた設計へ ローカルPC app binary $ xcodebuild -scheme … シナリオ テスト自動化 ツール xcodebuild インストール、 UI操作 UI取得 UI取得 インストール、 UI操作

Slide 27

Slide 27 text

自分たちが最速で成果を出すための技術選定 - Appium - テスト自動化ツール - 歴史と実績が豊富(私も過去に使ったことがある) - XCUITestも考慮したが、有識者が身近にいなかった - シナリオ記述言語にはRubyを採用 - 弊社のバックエンドがRuby on Rails製 - 稀に私もバックエンドAPIを実装していた - ライブラリが多くリリースされていて、目的が早く達成できそう The Ruby logo is used under the Creative Commons Attribution-ShareAlike 2.5 License. © 2006-2025 Ruby Association, Ruby Developers

Slide 28

Slide 28 text

方針変更からどれくらいの期間で 運用を開始できたか?

Slide 29

Slide 29 text

1か月 \ ドヤァ /

Slide 30

Slide 30 text

4 実装と運用状況

Slide 31

Slide 31 text

ツールのインストール 後述します

Slide 32

Slide 32 text

シナリオ開発 基本的には以下の1〜2を繰り返すだけ 1. Appium Inspector(※)の Recording機能で操作を記録 2. 生成されたRubyコードを rubyファイル(.rb)にコピペ (※)https://github.com/appium/appium-inspector

Slide 33

Slide 33 text

その作業で出来上がるRubyコード el1 = driver.find_element :xpath, "(//XCUIElementTypeNavigationBar[@name=\"AuthScene.LoginView\"]) [2]/XCUIElementTypeButton" # UI部品を検索 el1.click # 見つかったUI部品をクリック

Slide 34

Slide 34 text

その作業で出来上がるRubyコード el1 = driver.find_element :xpath, "(//XCUIElementTypeNavigationBar[@name=\"AuthScene.LoginView\"]) [2]/XCUIElementTypeButton" # UI部品を検索 el1.click # 見つかったUI部品をクリック ちょっと読みにくい…

Slide 35

Slide 35 text

アプリ実装を改善すれば、こう出来る el1 = driver.find_element :accessibility_id, "login" # UI部品を検索 el1.click # 見つかったUI部品をクリック

Slide 36

Slide 36 text

アプリ実装を改善すれば、こう出来る el1 = driver.find_element :accessibility_id, "login" # UI部品を検索 el1.click # 見つかったUI部品をクリック ※画像はイメージです

Slide 37

Slide 37 text

アプリ実装を改善すれば、こう出来る el1 = driver.find_element :accessibility_id, "login" # UI部品を検索 el1.click # 見つかったUI部品をクリック スッキリした!

Slide 38

Slide 38 text

この改善を全体に適用しま…  

Slide 39

Slide 39 text

この改善を全体に適用しません!

Slide 40

Slide 40 text

Done is better than perfect

Slide 41

Slide 41 text

サービス仕様と目的達成の バランスを取りながら工夫を繰り返した

Slide 42

Slide 42 text

ハードル:(前述)サービス仕様との相性問題 - SMS認証によるサインアップ - SMSが届く電話番号が必要 - QRコードによる出退勤 - カメラ(シミュレータでは動作しない)が必要

Slide 43

Slide 43 text

工夫: 実機を使うことで出来ない操作をなくす - 手動でSMS認証 - SIMカードを挿した実機を用意 - 手動でQRコードによる出退勤 - 実機のカメラで読み取る - 手動で操作をする間、シナリオには と書いて待機させておく - 実機でしか利用できない機能を これからもシナリオに追加できる - (例)NFCを用いた出退勤 sleep 30

Slide 44

Slide 44 text

ハードル: UI部品が検出できない事がある - Appium Inspectorを使ってもOS標準ダイア ログの選択肢のUI部品を検出できなかった - 稀にアクセスできることもある - (SaaSでも同じ問題が起きていた)

Slide 45

Slide 45 text

UI部品を検出する試行錯誤をしま…  

Slide 46

Slide 46 text

UI部品を検出する試行錯誤をしません!

Slide 47

Slide 47 text

- OCRを使って画面内から検出した文字列の座標を タップする仕組みをVibe Codingで作った 工夫: UI部品を探さず、特定の文字列を探す OCR.click_default_modal( driver, "アプリにトラッキングしないように要求" ) ※上記コードにあるOCRモジュールは自作

Slide 48

Slide 48 text

- スクリーンショットを加工して精度を上げている - モーダルダイアログの周囲の文字が邪魔 - スクリーンショットを2/3の幅にcrop - 文字色が青いと文字だと認識されにくい - グレースケールに変換 - Rubyライブラリのおかげで短時間で実装できた - rmagick/rmagick(画像加工) - dannnylo/rtesseract(OCR) 工夫: UI部品を探さず、特定の文字列を探す

Slide 49

Slide 49 text

ハードル: なぜかOCRが失敗するケースがある

Slide 50

Slide 50 text

- 背景画像がうっすら透けているのが原因 - 私の環境では、たまたま背景との相性が良かったから成功していた ハードル: なぜかOCRが失敗するケースがある 曇りガラス効果により、 ノイズになり得る形が 文字に重なっている

Slide 51

Slide 51 text

- 手っ取り早く問題を解決したい - rmagickにenhance()というノイズ除去用のメソッドがある - 問題の画像にenhance()を実行すれば問題が解決するのでは…? 工夫: 画像編集ライブラリのノイズ除去メソッドを使う https://rmagick.github.io/image2.html#enhance

Slide 52

Slide 52 text

enhance()を実行しても解決しなかった… 工夫: 画像編集ライブラリのノイズ除去メソッドを使う image = image.enhance() # (省略: image内の文字を読み取る処理がここに入る) if scanned_text.end_with?(target_text) # (省略: 検出したテキストの座標をtapする) end

Slide 53

Slide 53 text

別アプローチの探索をしま…  

Slide 54

Slide 54 text

別アプローチの探索をしません!

Slide 55

Slide 55 text

成功するまでenhance()を実行し続ける → 成功! 工夫: 画像編集ライブラリのノイズ除去メソッドを使う 30.times do # 検出できるまで最大30回試す image = image.enhance() # (省略: image内の文字を読み取る処理がここに入る) if scanned_text.end_with?(target_text) # (省略: 検出したテキストの座標をtapする) end end

Slide 56

Slide 56 text

補足: iOS26対応 Liquid Glassは力技で乗り越えられなかったので、ディザ処理も加えました

Slide 57

Slide 57 text

ハードル: シャッターボタンが検出できない - Appium Inspectorで 検出できないUI部品 part.2 - 文字ではないのでOCRも使えない

Slide 58

Slide 58 text

ハードル: シャッターボタンが検出できない iOSのカメラUIの シャッターボタンって だいたいこの辺にあるよね

Slide 59

Slide 59 text

解決策の汎用化を求め過ぎません!

Slide 60

Slide 60 text

工夫: 座標指定でタップさせる ”画面のだいたいこの辺”という割合指定で 座標を決め打ちしてタップさせた size = driver.window_size position = [size.width * 0.5, size.height * 0.95] driver .action .move_to_location(position[0], position[1]) .pointer_down(:left) .release .perform

Slide 61

Slide 61 text

補足: iOS26対応 シャッターボタンの位置が変わったのでOSバージョン分岐を入れました

Slide 62

Slide 62 text

ハードル: エンジニアの生産性が上がっていない - 半自動E2Eテストは手動で操作しないといけないタイミングがある - つい端末をずっと見つめてしまって、並列で他の仕事にあたれない! もうすぐ 操作しなきゃ…

Slide 63

Slide 63 text

手動操作が必要なタイミングで Macに呼んでもらおう いま操作 して!

Slide 64

Slide 64 text

$ say hello world sayコマンド - 文字列を音声に変換して喋らせるためのコマンド - macOSに標準搭載されている hello world

Slide 65

Slide 65 text

# シナリオ側の実装 Say.text("操作が必要です。本人確認を完了させてください。60秒待ちます。") # 呼び出されるメソッド側の実装 def text(message) Thread.new do system(`say -v Kyoko "#{message}"`) # macOSのsayコマンドに喋らせる end end 工夫5: リグレッションテスト担当者のマルチタスク対応

Slide 66

Slide 66 text

No content

Slide 67

Slide 67 text

ラクになった!

Slide 68

Slide 68 text

- E2Eシナリオ拡充 - スクリーンショット撮影機能の追加 - iOS26対応 継続的に改善してます!

Slide 69

Slide 69 text

5 付録

Slide 70

Slide 70 text

- “決め”次第で気軽に運用開始できます - 皆様の背中を押すべく、私達が利用しているツール群と Appiumに与えているCapabilities(※)を紹介します (※)Appiumがシナリオ実行を開始する際に用いられるパラメータセットのこと まだ手動で運用している方、チャンスです!

Slide 71

Slide 71 text

1. Appium (npm install -g appium) 2. carthage (brew install carthage) 3. Appium XCUITest Driver (appium driver install xcuitest) 4. (画像加工をする場合) imagemagick (brew install imagemagick@6) 5. (OCRを実施する場合) tesseract (brew install tesseract) - 日本語対応のため、 https://github.com/tesseract-ocr/tessdata の jpn.traineddataも必要 ※赤字のみ必須、他は任意 私達が利用しているツール

Slide 72

Slide 72 text

設定類 - AppiumのCapabilities設定JSON - Appiumに渡す.appファイルを作るアーカイブコマンド { "appium:platformName": "iOS", "appium:automationName": "XCUITest", "appium:udid": "{デバイスID}", "appium:autoFillPasswords": false, "appium:language": "ja-JP", "appium:newCommandTimeout": 480000, "appium:app": "{.appファイルのパス}" } $ xcodebuild -scheme {スキーム名} \ -configuration Debug \ -sdk iphoneos \ -archivePath /path/to/app.xcarchive archive { "appium:platformName": "iOS", "appium:automationName": "XCUITest", "appium:udid": "{デバイスID}", "appium:autoFillPasswords": false, "appium:language": "ja-JP", "appium:newCommandTimeout": 480000, "appium:bundleId": "{インストール済のアプリの BundleID}" } アプリを新規インストールする場合 インストール済のアプリを利用する場合

Slide 73

Slide 73 text

岐部 龍太(きべ りゅうた) 株式会社タイミー iOSテックリード GitHub / X: @beryu 居住地: 福岡🍜