株式会社GENZが運営する技術ブログです。

  1. Playwright
  2. 89 view

ゼロから始める!PlaywrightでのAndroidテスト環境構築ガイド(Mac編)

ブラウザの自動テストが出来るツールは様々なものがあります。
が、スマホ環境でテストが出来るツールは限られています。
代表的なツールとしてはAppiumです。
Appiumは、Android・iOS双方のネイティブアプリやWebアプリの自動化に対応しています。
しかしAppiumは依存関係が多く、そのため環境構築のハードルが高めです。
特にiOSのセットアップは手間が多く、慣れていないと苦戦しがちです。

Androidアプリのテストに限っては別のアプローチとしてPlaywrightがあります。
Playwrightは依存関係が少なく比較的シンプルな環境構築でAndroidのネイティブアプリやブラウザの自動テストができるのが魅力です。
ただし、iOSのネイティブアプリには現状対応していないため注意が必要です。(iOSのブラウザテストには対応しています。)
本記事では、Mac環境でPlaywrightを使ってAndroidテストを実行するための環境構築手順をゼロから解説し、簡単なテストコードの実行例も紹介します。
なお、Playwright自体の説明は以下の記事にあります。

【テスト自動化】VScodeでPlaywrightを使って、自動でE2Eテストを行う~前編~

環境構築

今回環境構築するバージョンは以下になります。

  • OS:macOS Sequoia 15.3
  • Node.js:v23.7.0
  • Nodebrew:1.2.0
  • Playwright:Version 1.50.1
  • Android Debug Bridge(adb):Version 35.0.2-12147458

操作はMacのターミナルで行います。

Node.jsのインストール(Nodebrewでインストール)

Nodeがインストール済みの場合、先にアンインストールが必要です。

# Nodeがhomebrewでインストール済みか確認
% brew ls | grep node
# 存在していたらアンインストール
% brew uninstall --force node
# Nodeがnpmでインストール済みか確認
% npm ls -g node
# 存在していたらアンインストール
% npm uninstall -g npm

Nodebrewのインストール

% brew install nodebrew

# インストールしたバージョン確認
% nodebrew -v

Nodeのインストールで使う隠しフォルダを作成

以下のフォルダにNodeのインストーラーをダウンロードするため、存在しないとNodeのインストール時に Warning: : No such file or directory になってしまいます。

% mkdir -p ~/.nodebrew/src

インストール可能なNodeを確認し、Nodeをインストール

# インストール可能なバージョンを表示
% nodebrew ls-remote

# バージョンを指定してインストール
% nodebrew install-binary v23.7.0

# 最新版をインストール
% nodebrew install-binary latest

# 安定版をインストール
% nodebrew install-binary stable

インストールされたNodeを確認

確認結果の `current: none` はまだ使用するNodeを指定していないため。

% nodebrew list(もしくは nodebrew ls)
v23.7.0

current: none

使用するNodeを指定して、再度Nodeを確認

% nodebrew use v23.7.0

% nodebrew list(もしくは nodebrew ls)
v23.7.0

current: v23.7.0

rshrcにPATHを設定し反映

% echo 'export PATH=$HOME/.nodebrew/current/bin:$PATH' >> ~/.zshrc

# PATH設定を反映
% source ~/.zshrc

Nodeのバージョンを確認し、インストーラーを削除

% node -v
v23.7.0

# インストーラーを削除。もちろん削除せずに残しておいても大丈夫です
% rm -rf ~/.nodebrew/src/v23.7.0

Playwrightのインストール

% npx playwright --version

# インストールされていなければ以下表示されるのでインストール
Need to install the following packages:
playwright@1.50.1
Ok to proceed? (y)  # そのままReturnを押す

# バージョンが表示されたらインストール完了
Version 1.50.1

必要に応じて、VSCodeにPlaywright Test for VSCodeの拡張を入れます。
Playwright Test for VSCode – Visual Studio Marketplace

お疲れ様です。これで環境構築は完了です。
次にプロジェクトの初期化を行います。

プロジェクトの初期化

# プロジェクトを作成するフォルダに移動して
% npm init playwright
Need to install the following packages:
create-playwright@1.17.135
Ok to proceed? (y)  # そのまま return を押す

> npx
> create-playwright

Getting started with writing end-to-end tests with Playwright:
Initializing project in '.'

初期化を進めると、質問が出てくるので回答します。

# 利用する言語(TypeScript / JavaScript)を選択
? Do you want to use TypeScript or JavaScript? … 
  TypeScript
> JavaScript

# テストコードを格納するためのフォルダ名の指定(初期値はtests。それでよければそのままreturn)
? Where to put your end-to-end tests? › tests

# GitHub Actionsのワークフロー作成。利用に応じて選択
? Add a GitHub Actions workflow? (y/N) › y

# Playwrightで利用するヘッドレスブラウザのインストール。利用に応じて選択
? Install Playwright browsers (can be done manually via 'npx playwright install')? (Y/n) › true

# 回答が終わるとプロジェクトが作られ、以下が出たら完了です
Happy hacking! 🎭

Android ネイティブアプリテストの準備

playwright.config.jsへの追加

まず、プロジェクトフォルダ直下にあるplaywright.conf.jsに { name: ‘Android実機’, use: { android: true }, } を追記します。

# ファイルの後ろのprojectsに追加
{
  name: 'Android実機',
  use: { android: true },
},


# playwright.conf.js例
// @ts-check
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',
  use: {
    trace: 'on-first-retry',
  },

  projects: [
    {
      name: 'Android実機',
      use: { android: true },
    },
  ],
});

ドライバのインストール

% npx playwright install android

テストするネイティブアプリのパッケージ名とアクティビティ名の確認

パッケージ名とアクティビティ名を確認するために、以下のadbコマンドを実行します。

% adb logcat | grep 'Start proc'

adbコマンドが待機状態になるので、そのままAndroidで自動化したいアプリを起動します。
すると以下のようなログが出力されます。
出力が確認できたら、 control + c でログ出力を停止します。

02-12 21:08:23.466  1849  2359 I ActivityManager: Start proc 6403:com.twitter.android/u0a426 for next-top-activity {com.twitter.android/com.twitter.android.StartActivity}
02-12 21:08:24.185  1849  2359 I ActivityManager: Start proc 6811:com.google.android.webview:sandboxed_process0:org.chromium.content.app.SandboxedProcessService0:0/u0i10 for  {com.twitter.android/org.chromium.content.app.SandboxedProcessService0:0}
^C

出力されたログの { } で囲まれた箇所が パッケージ名/アクティビティ名 となっています。
今回のXの場合は、2つアクティビティ名が表示されていますが、中身を見ると1つ目は com.twitter.android.StartActivity と起動アクティビティっぽい名前で、2つ目は org.chromium.content.app.SandboxedProcessService0:0 となっていて、Activityとはなっておらず、また { } の前を見るとwebview:sandboxed_processと書いてあるので、webviewのための起動プロセスなのかなと想像できます。
なので今回調べたXのパッケージ名/アクティビティ名は com.twitter.android/com.twitter.android.StartActivity で良さそうです。
実際そのパッケージ名/アクティビティ名で起動できるかは以下のadbコマンドで確認できます。(画面ロックは解除しておいてください。)

% adb shell am start -n com.twitter.android/com.twitter.android.StartActivity

テストコード作成

Android ネイティブアプリテストのコーディング(前半)

プロジェクトフォルダ直下の tests フォルダ(初期化時の ? Where to put your end-to-end tests? で違う名前にした場合は指定したフォルダ名)の中の example.spec.js をVS Code等で開きます。
ここにテストを書いていきますが、example.spec.jsに最初から書かれている内容はブラウザテストの内容なので先頭の import { test, expect } from ‘@playwright/test’; 以外削除しコードを書いていきます。

以下はXを操作するシンプルなコード例です。

// @ts-check
import { test, _android} from '@playwright/test';

test('sample test', async () => {
  const [device] = await _android.devices();
  // アプリを強制終了してから起動
  await device.shell(`am force-stop com.twitter.android`);
  await device.shell(`am start -n com.twitter.android/com.twitter.android.StartActivity`);
  await device.wait({ enabled: true });

  // 自分のアイコンをタップ
  await device.tap({desc: 'スライドメニューを表示', clickable: true })

  // スライドメニューの「プロフィール」が表示されるまで待機
  await device.wait({ text: 'プロフィール', enabled: true });

  // スライドメニューの「プロフィール」をタップ
  await device.tap({ text: 'プロフィール' })

  // プロフィール画面の「プロフィールを編集」が表示されるまで待機
  await device.wait({ text: 'プロフィールを編集', enabled: true });

  // 戻るボタンをタップ
  await device.tap({ desc: '前に戻る' });

  // トップ画面の「おすすめ」が表示されるまで待機
  await device.wait({ text: 'おすすめ', enabled: true });

  // 検索ボタンをタップ
  await device.tap({ desc: '調べたいものを検索' });

  // 検索画面の「Xを検索」が表示されるまで待機
  await device.wait({ text: 'Xを検索', enabled: true });

  // 「テスト」と入力して検索
  await device.fill({ text: 'Xを検索' }, 'テスト' );
  await device.press({ res: 'com.twitter.android:id/query' }, 'Enter' );

  // 5秒待機
  await new Promise(resolve => setTimeout(resolve, 5000));

  // スクリーンショット取得
  await device.screenshot({ path: 'screenshot.png' });

  // アプリを終了
  await device.shell(`am force-stop com.twitter.android`);
});

それではコードを詳しく説明します。

// @ts-check
import { test, _android} from '@playwright/test';

test('sample test', async () => {

  (ここにテストを書く)

});

まず、Playwrightを使うためにtestとAndroidを操作するために_androidをimportし、テストケースを書くブロックを書きます。
sample test はテストケースの名前でテスト実行時にログに出力されるのでテストの目的に合った分かりやすい名前にします。
テストの目的に合わせ、複数のブロックを作ってテストを分けることも可能です。

test('sample test', async () => {
  const [device] = await _android.devices();
  // アプリを強制終了してから起動
  await device.shell(`am force-stop com.twitter.android`);
  await device.shell(`am start -n com.twitter.android/com.twitter.android.StartActivity`);
  await device.wait({ enabled: true });
});

テストケースの中で最初に以下のコマンドでdeviceオブジェクトを作ります。
const [device] = await _android.devices();
作ったオブジェクトを以降のコードで使っていきます。
まず、以下のコードでアプリを立ち上げます。
await device.shell(`am start -n com.twitter.android/com.twitter.android.StartActivity`);
これは先ほどadbコマンドでアプリの起動を確かめたコマンドをPlaywrightで呼んでいます。

Android側で既に該当アプリが起動しており、それによってテストに影響が出る場合もあります。
それを回避するために、以下のコマンドでアプリを終了してから立ち上げ直すのが無難でしょう。
await device.shell(`am force-stop com.twitter.android`);
続けて、アプリが立ち上がるまで少し時間がかかる可能性があるため、待機するコードを書きます。
await device.wait({ enabled: true });

あとは実際にアプリ画面をタップしたり入力したりする操作を書いていけばいいのですが
「あれ?アプリ操作で指定する要素はどう調べたらいいのか?」
となると思うので、操作する要素を調べましょう。

操作するアプリの要素の確認

操作するための画面要素を確認するためには、Androidで調べたい画面を開いた状態で以下のコマンドを実行します。

% adb shell uiautomator dump /sdcard/ui.xml

するとAndroid内に ui.xml というファイルが作成されるので、以下のコマンドでmacにコピーします。

% adb pull /sdcard/ui.xml macの任意のフォルダ
# 例えば、adb pull /sdcard/ui.xml ~/Downloads

macにコピーできたら、Android内に作ったui.xmlファイルは不要だと思うので以下のコマンドで削除します。

% adb shell rm /sdcard/ui.xml

開いているAndroid画面の要素はこのui.xmlに全て詰まっているので、ファイル内の text の値や content-desc の値をヒントに狙いを定めます。

Androidネイティブアプリテストのコーディング(後半)

それではAndroidで操作する要素の狙い方も分かったので、先ほど書いたテストケースのブロック内の続きに操作を書いていきましょう。

// @ts-check
import { test, _android} from '@playwright/test';

test('sample test', async () => {

  (ここにテストを書く)

});

要素をタップしたい

// ui.xmlは以下
// <node index="0" text="" resource-id="" class="android.widget.ImageButton" package="com.twitter.android" content-desc="スライドメニューを表示" checkable="false" checked="checked" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="false" password="false" selected="selected" bounds="[0,63][147,210]" />
await device.tap({desc: 'スライドメニューを表示', clickable: true })

// ui.xmlは以下
// <node index="1" text="プロフィール" resource-id="" class="android.widget.TextView" package="com.twitter.android" content-desc="" checkable="false" checked="checked" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="selected" bounds="[210,655][809,720]" />
await device.tap({ text: 'プロフィール' })

要素に入力したい

// ui.xmlは以下
// <node index="0" text="Xを検索" resource-id="com.twitter.android:id/query" class="android.widget.EditText" package="com.twitter.android" content-desc="検索ボタン" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="true" scrollable="false" long-clickable="true" password="false" selected="false" bounds="[189,95][1096,178]" />

// 「テスト」を入力
await device.fill({ text: 'Xを検索' }, 'テスト' );
// 「Enter」を押下
await device.press({ res: 'com.twitter.android:id/query' }, 'Enter' );

待機したい

// ui.xmlは以下
// <node index="1" text="プロフィール" resource-id="" class="android.widget.TextView" package="com.twitter.android" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[210,655][809,720]" />

// 「プロフィール」が表示されるまで待機
await device.wait({ text: 'プロフィール', enabled: true });

// 5秒待機
await new Promise(resolve => setTimeout(resolve, 5000));

スクリーンショットを撮りたい

// 「screenshot.png」のファイル名でスクリーンショットをプロジェクト直下に保存
await device.screenshot({ path: 'screenshot.png' });

これだけあれば基本的な操作はひとまず出来ると思います。

テスト実行

テストは以下のコマンドで実行します。

% npx playwright test

実行すると以下のブロックが表示される場合があります。

その場合はAndroidで以下の操作を行います。

  1. AndroidのPlayストアアプリを起動
  2. 右上の自身のアイコンをタップ
  3. Playプロテクトをタップ
  4. Playプロテクト画面右上の歯車アイコンをタップ
  5. Playプロテクト設定画面のPlayプロテクトによるアプリのスキャンをオフ

まとめ

今回はPlaywrightでAndroid(実機 or エミュレータ)のネイティブアプリを操作する方法を書いてきました。
環境構築や要素探しはなかなか手間がかかるものもありますが、一度セットしてしまえば2回目以降は無視できるステップも多いので、2回目以降は割と簡単に動かせると思います。
とはいえ、PlaywrightやAppiumでの自動テストは無料で行える代わりに環境構築などが少し難しいのは事実です。
また、無料の自動テストツールで頑張るか、有料の自動ツールでサクッと構築してしまうかの判断も難しいところがあります。
そのようなお悩みがある場合、弊社にはG-SATというテスト自動化のソリューションがありますので、一度ご相談いただけたらと思います。

<過去の記事>
【テスト自動化】VScodeでPlaywrightを使って、自動でE2Eテストを行う~前編~
【テスト自動化】VScodeでPlaywrightを使って、自動でE2Eテストを行う~後編~
ゼロから始める!Appium + Python環境構築ガイド(Windows対応)

Playwrightの最近記事

  1. ゼロから始める!PlaywrightでのAndroidテスト環境構築ガイド(Mac編)

関連記事