0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Playwrightでネイティブアプリの操作を自動化してみる【初歩中の初歩】

Last updated at Posted at 2025-06-13

スマホのネイティブアプリのテスト自動化ってできないの?という相談を最近よく頂くのと、Playwrightのお勉強したいなあという個人的な願望もあり、導入だけやってみた備忘録を残します。

なお、Playwrightのネイティブモバイル機能は現在AndroidのADBプロトコルを利用したものに限定されているため、Androidのみの話になります。iOSユーザーの皆さんごめんなさい;;

Step 0: 準備編 - テストを始める前に

そもそもPCとAndroidスマホをどうやって繋ぐの?という部分のセットアップです。

1. Android SDK (adb) のセットアップ

adbは、PCからスマホに命令を送るための必須ツールです。

  • 入手方法: Android Studioの公式サイトから「Command line tools only」をダウンロードするのが一番手軽です。(Android Studio本体を全部インストールする必要はありません)
  • 設定:
    1. ダウンロードしたzipファイルを解凍し、適当な場所(例: C:\android-sdk)に置きます。
    2. 中にある platform-tools というフォルダのパスを、Windowsの環境変数 Path に追加します。
    3. ターミナルを再起動し、adb --version と打ってバージョン情報が出れば成功です。

2. Androidデバイスの開発者向けオプションを有効化

次にスマホ側の設定。PCからのデバッグ命令を受け付けられるようにします。

  1. スマホの「設定」→「デバイス情報」(または「タブレット情報」)を開きます。
  2. 「ビルド番号」という項目を、7回連続でタップします。(「デベロッパーになるまであと〇ステップです」と表示が変わっていきます)
  3. 「これでデベロッパーになりました!」と表示されたら成功です。
  4. 「設定」メニューに戻ると、新しく「開発者向けオプション」という項目が出現しているので、それを開きます。
  5. 中にある「USBデバッグ」と「ワイヤレスデバッグ」の両方をONにしておきましょう。

3. Playwrightプロジェクトの作成

最後に、今回の主役であるPlaywrightのプロジェクトを準備します。

# 適当な作業フォルダを作り、そこで以下を実行
npm init playwright@latest

いくつか質問されますが、基本はEnter連打でOKです。
これで、playwright.config.tstests フォルダなどが自動で作成されます。

Step 1: 接続確認と初テスト

準備が整ったので、いよいよPlaywrightとデバイスを繋いでみます。

1. 接続確立 (adb) - USBよりワイヤレスがおすすめ

ここでいきなり最初の壁にぶつかりました。当初、USBケーブルでPCとスマホを繋いで「USBデバッグ」を試したのですが、何度やってもadb devicesでデバイスを認識してくれませんでした(どなたか有識者いらっしゃればご教授賜りたい.....)

そこで「ワイヤレスデバッグ」に切り替えたところ一瞬で繋がりました。。
(重要:PCとスマホが同じWi-Fiなど、同一のネットワーク内にいる必要があります)

  1. スマホの「開発者向けオプション」→「ワイヤレスデバッグ」を開きます。

  2. 「ペア設定コードによるデバイスのペア設定」をタップすると、IPアドレスとポート番号が表示されます。(例: 192.168.1.10:45678

  3. PCのターミナルで、表示されたIPとポートを使って接続コマンドを打ちます。

    adb connect 192.168.1.10:45678 # ←スマホに表示されたIPとポートに置き換える
    
  4. 接続が成功したら、adb devicesで確認します。

    adb devices
    # List of devices attached
    # 192.168.1.10:45678   device  <- ワイヤレスデバッグだとIPアドレスで表示される
    

2. Playwrightから接続(初回テスト)

adbで認識できれば、次はPlaywrightから接続できるか試します。
いきなりテストを実行しても、中身が空だと何も起きないので、まずはおまじないとして、tests/example.spec.tsに以下の最小限のテストコードをコピペして保存します。

// tests/example.spec.ts
import { test, _android } from '@playwright/test';

test('デバイス接続テスト', async () => {
  // デバイスに接続する
  const [device] = await _android.devices();
  console.log(`接続成功! モデル名: ${device.model()}`);
  
  // デバイスとの接続を閉じる
  await device.close();
});

この状態でテストを実行し、ターミナルにモデル名が表示されれば成功です!

npx playwright test
# 出力例:
# 接続成功! モデル名: Pixel 5

【ここでの学び】

  • ADB接続で躓いたら、USB接続に固執せず、ワイヤレスデバッグを試してみるとあっさり解決することがある。
  • adb connectコマンドを忘れずに!
  • Playwrightのテストは、まず「デバイスに接続して情報を表示するだけ」の最小構成で動かし、接続自体に問題がないことを確認するのが確実。

Step 2: 本命のネイティブアプリを起動する

ブラウザの自動化はよくあるので、本題のネイティブアプリに進みます。

1. パッケージ名を知る

アプリを操作する前に、そもそもアプリってどうやって識別するん?と思ったら、どうやら「パッケージ名」という固有IDで識別しているらしい。

これもadbコマンドで調べられるらしいので調べてみます。

# インストールされてるアプリのパッケージ名を全部出す
adb shell pm list packages

# 多すぎるので、アプリ名で絞り込むのがおすすめ(PowerShellの場合)
adb shell pm list packages | Select-String "your-app-name"
# -> package:com.example.app ←これ

2. アプリの起動と「待機」

tests/example.spec.tsの中身を、今度はアプリを起動するコードに書き換えていきます。

// tests/example.spec.ts
import { test, _android } from '@playwright/test';

test('ネイティブアプリ起動テスト', async () => {
  const [device] = await _android.devices();
  console.log(`モデル名: ${device.model()}`);
  
  const pkg = 'com.example.app'; // ←さっき調べたアプリのパッケージ名に書き換える

  // テスト前に一旦アプリを強制終了させて、まっさらな状態から始める
  await device.shell(`am force-stop ${pkg}`);

  // いざ起動!
  await device.shell(`monkey -p ${pkg} -c android.intent.category.LAUNCHER 1`);

  // ★超重要:待つこと★
  // これがないと、アプリが立ち上がる前に次の処理に進んでしまう
  await device.shell('sleep 5');

  console.log('アプリを起動しました。');
  await device.close();
});

ここでも見事にハマりました。起動コマンドの直後にスクリーンショットを撮ったら、まだホーム画面のままでした。コンピュータの処理は人間が思うより遥かに速いので、UIが絡む部分は意図的に待ちの処理を入れてあげる必要があります。

【ここでの学び】
UIの起動や画面遷移など、見た目が変わる操作の直後には、必ずsleepや後述の「要素待機」を入れる。

Step 3: テストの証拠を残す

「動いた!」「動かなかった!」だけだと、後から見返したときに何が起きたか分からないので、エビデンスとしてスクリーンショットや録画は超重要です。

1. スクリーンショットを撮ってレポートに貼る

Playwrightのテストレポートに画像を添付すると、ブラウザで結果を見ながら画像も確認できて良きです。
tests/example.spec.tsにさらに処理を加えていきましょう。

// tests/example.spec.ts
import { test, _android } from '@playwright/test';
import * as fs from 'fs';

test('ネイティブアプリ起動テスト', async ({}, testInfo) => { // ← testInfo を追加
  // スクリーンショット保存用のフォルダを作成
  fs.mkdirSync('screenshots', { recursive: true });

  const [device] = await _android.devices();
  console.log(`モデル名: ${device.model()}`);
  
  const pkg = 'com.example.app';

  await device.shell(`am force-stop ${pkg}`);
  await device.shell(`monkey -p ${pkg} -c android.intent.category.LAUNCHER 1`);
  await device.shell('sleep 5');

  // スクショを撮る
  const screenshotPath = 'screenshots/app-startup.png';
  await device.screenshot({ path: screenshotPath });

  // レポートに「起動直後」という名前で画像を添付
  await testInfo.attach('起動直後', {
    path: screenshotPath,
    contentType: 'image/png'
  });

  console.log('アプリを起動し、スクリーンショットを撮りました。');
  await device.close();
});

【ここでの学び】
テストの証拠は必ず残す。成功・失敗に関わらず、実行前後のスクリーンショットがあれば、問題解決のスピードが段違いになる。

Step 4: 最大の壁:ボタンが押せない問題との戦い

ここからが本番。アプリのダイアログの「次へ」ボタンを押そうとして、地獄のトラブルシューティングが始まりました。

試行1:ボタンのテキストでタップ (玉砕)

まず最初に試す、一番簡単な方法。普通に目に見えてるラベル名で押させてみる。

await device.tap({ text: '次へ' });

結果は「見つかりませんでした」。まあ、そんなもんだろうと思った。

試行2:リソースIDでタップ (推奨だが…玉砕)

テキストがダメなら、次はresource-id。これは各UI要素に振られた固有のIDで、テキストより遥かに信頼性が高いです。

IDの調べ方 (コマンドライン)

  1. adb shell uiautomator dump を実行。
  2. adb pull /sdcard/window_dump.xml . でPCにファイルを持ってくる。
  3. window_dump.xmlを開いて、text="次へ"の近くにあるresource-idを探す。
    com.example.app:id/next_button を発見!

これを使って再挑戦。

await device.tap({ res: 'com.example.app:id/next_button' });

しかし、これも結果は「見つかりませんでした」。どうやらdevice.tapとこのアプリのボタンの相性が悪いようです(ダイアログ上のボタンだから?)

試行3:uiautomatorコマンドを直接実行 (惜しい!)

Playwrightのtapがダメなら、Androidのテストツールuiautomatorを直接コマンドで呼び出します。

const resourceId = 'com.example.app:id/next_button';
await device.shell(`uiautomator click --selector resourceId ${resourceId}`);

すると、テストログ上は「成功」しました!しかし、実際のデバイス画面ではボタンが押されていません。テスト自動化で一番厄介な「偽りの成功(False Positive)」ってやつです。

試行4:最終手段、座標でタップ (勝利!)

もう要素がどうとか言ってられません。最後の手段は、画面の特定の座標を物理的にタップすることです。
window_dump.xmlには、ボタンの表示領域boundsも書かれています。

bounds="[125,1662][955,1777]"

この対角線の中心を計算すれば、ボタンのど真ん中の座標が分かります。

  • X座標: (125 + 955) / 2 = 540
  • Y座標: (1662 + 1777) / 2 = 1719.5 (まあ1719でいいでしょう)

これをinput tapコマンドで実行します。

// まずは念のため、ボタンが存在することだけ確認
const resourceId = 'com.example.app:id/next_button';
await device.shell(`uiautomator dump && cat /sdcard/window_dump.xml | grep 'resource-id="${resourceId}"'`);

// 存在したら、座標を直接叩く!
await device.shell('input tap 540 1719');

これで、ついに「次へ」ボタンをタップすることに成功しました!

【学び(?)】
UI操作の自動化には「優先順位」がある。

  1. まずtapなど高レベルなAPIを試す。
  2. ダメならresource-idを使う。
  3. それでもダメならuiautomatorコマンドを直接叩く。
  4. 最後の手段は座標指定タップ
    この引き出しの多さが、一筋縄ではいかないアプリの自動化を成功させる鍵となる。

まとめと今後の展望

というわけで、紆余曲折ありましたが、なんとかPlaywrightでAndroidネイティブアプリの基本的な操作(起動→ボタンクリック)までを自動化することができました。

正直、Webブラウザの自動化に比べると、UI要素の特定でハマる部分が多く、一筋縄ではいかない印象です。ただ、一度resource-idや最終手段の座標タップまで経験してしまえば、大抵のことは何とかなるんじゃないか、という自信もつきました。

今後は、

  • 今回作ったテストをベースに、ログインフォームへのテキスト入力(device.fill())やアサーション(期待値のチェック)を追加して、もっと「テストらしい」テストに育てていく。
  • 複数のテストケースを並列実行させてみる。
  • CI/CDツール(GitHub Actionsなど)と連携させて、夜間に自動でテストが走る環境を構築する。

あたりに挑戦してみたいですね。
この備忘録が、これから同じ道を通る誰かの助けになれば幸いです。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?