Open9

Android端末のChromeをPlaywrightで操作したい【Windows11】

masa5714masa5714

PlaywrightにはAndroid端末のChromeを操作できる機能が備わっている。

https://playwright.dev/docs/api/class-android

ADB(Android Debug Bridge)経由で操作可能になるらしい。
実際の処理としてはこの辺りは気にしなくてもいいと思うので、何か問題が起きたときに思い出せる程度に仕組みとして "ADBを経由してるんだな~" ぐらいに理解しておけば良いだろう。

masa5714masa5714

動作環境

僕は下記の環境で動作確認しています。

  • Windows 11
  • Android Version8.0.0の端末(機種名は10台ほど買い集めたいので一旦隠します。)
  • USB-C接続

準備すること(adbコマンドを使えるようにする)

https://developer.android.com/tools/releases/platform-tools?hl=ja#downloads

上記リンクから SDK Platform-Tools をダウンロードします。 SDK Platform-Tools for Windows をダウンロード リンクからダウンロードできます。ダウンロードしたzipファイルを展開すると、 platform-tools というフォルダが入っているので、これをどこでもいいので置いてください。

今回は C:\adb というフォルダを作って格納しました。パス: C:\adb\platform-tools こんな感じ。

次にコマンドで使えるようにするためにパスを通します。
環境変数の変更画面を開き、「システム環境変数」の Path に先ほどのplatform-toolsの所在パスを追加してください。今回の例では C:\adb\platform-tools を追加する形になります。

コマンドプロンプトから adb と入力して Enter するとコマンドリストが表示されれば成功です。
これでadbの準備は整いました。

masa5714masa5714

Android端末をadb接続する

adbにAndroid端末を認識させる必要があります。
Android端末側の「開発者オプション」から「USBデバッグ」を有効にしましょう。
「USBデバッグを許可しますか?」と出てくるので「このパソコンからUSBデバッグを常に許可する」にチェックを入れてOKで完了します。

パソコン側で

adb devices

を実行して、端末が認識されているか確認しましょう。

masa5714masa5714

PlaywrightのコマンドがAndroid端末で動くようにする

このままではadbによるChrome起動が出来たとしてもChrome内の操作ができないので設定をする。

Android端末のChromeのアドレスバーに chrome://flags/ と入力してアクセスする。
Enable command line on non-rooted devices という項目を探して Enabled にしてChromeを再起動して完了。

masa5714masa5714

パソコンからPlaywrightのコードを実行してAndroid端末が動くか確認する。

index.ts
import { _android as android } from "@playwright/test";

(async () => {
  const [device] = await android.devices();

  await device.shell("am force-stop com.android.chrome");
  const context = await device.launchBrowser();

  const page = await context.newPage();
  await page.goto("https://yahoo.co.jp");
})();

こんな感じのコードを実行してみる。
npx ts-node index.ts を実行すると、Android端末でChromeが立ち上がり、Yahoo! Japan のページが開くことが確認できる。

ということでPlaywrightからAndroid Chromeを操作する準備が整いました!

masa5714masa5714

.screenshot() はパソコン側に保存される

当然ながら await page.screenshot({ path: "screenshot.png" }) を実行すると、スマホ側で撮影されて、パソコン側に screenshot.png が保存される。スマホ側に画像が残る訳ではない。

masa5714masa5714

複数台で同じ処理を実行するには?

複数台を同時に動かす場合は少し手間がかかります。
下記のように書くことで同時に実行できます。

index.ts
import { _android as android, AndroidDevice } from "@playwright/test";

// Yahoo! Japanを閲覧してスクリーンショットを撮影する関数
// この関数を複数台で実行するという例です。
async function YahooBrowsing(device: AndroidDevice) {
  const deviceSerialNumber = device.serial(); // 端末のシリアル番号を取得( adb devices を実行したときと同じ値が取得できる。 )
  const context = await device.launchBrowser(); // Android Chromeを立ち上げる
  const page = await context.newPage();
  await page.goto("https://yahoo.co.jp"); // Yahoo! Japanにアクセスする

  await page.screenshot({ path: `./hoge-${deviceSerialNumber}.png` }); // スクリーンショットを撮影する
  await device.shell("am force-stop com.android.chrome"); // Chromeを閉じる
  await device.close(); // デバイス操作を終了
}

(async () => {
  const promises: Promise<any>[] = []; // 並列実行したい処理を格納するための配列
  const devices = await android.devices(); // 接続中の全てのAndroid端末を取得

  // 端末毎に処理したい内容を登録する
  for (let key in devices) {
    promises.push(YahooBrowsing(devices[key]));
  }

  // 並列実行を実行する
  await Promise.allSettled(promises);
})();

このように Promise.all() や Promise.allSettled() のような並列実行を利用することで、並列実行が可能となります。Playwrightには並列実行をする仕組みが用意されていないようなので、このように自前で実装する必要があるようでした。

masa5714masa5714

実行時にプロキシを挿すには?

await device.shell("settings put global http_proxy 【IPアドレス】:【ポート番号】");

これを先に実行しておけば端末レベルでプロキシを挿すことができます。
実行中は通知なども(恐らく)ここを通ることになるので注意してください。

スクレイピングが終わったらプロキシを解除したいかと思います。
プロキシの解除は

await device.shell("settings put global http_proxy :0");

を実行するだけでOKです。
0 ではなく、 :0 です。見間違いしないように注意してください。

masa5714masa5714

CookieやLocalStorageを削除したい

プロキシを刺す場合、プロキシリストが一斉にブロックされてしまうことを防ぐため、CookieやLocalStorageを削除して予防しておくべきです。ということで、実行前にCookieやLocalStorageを削除する方法を記載します。

await device.shell("pm clear com.android.chrome");

たったこれだけで削除できます。
しかし、これだけでは chrome://flags で設定した内容や、Chromeの初期設定画面が表示されるなど、スクレイピングに影響を与えてしまいます。これをパスするほぼ完璧なshellが落ちていましたので紹介します。

上記のような画面をスキップする方法です。

await device.shell("am set-debug-app --persistent com.android.chrome");
await device.shell('echo "chrome --disable-fre --no-default-browser-check --no-first-run" > /data/local/tmp/chrome-command-line');
await device.shell("am start -n com.android.chrome/com.google.android.apps.chrome.Main");

1行目で chrome://flags/ での設定を有効化している。
2行目で 初期設定画面をパスするような指定をしている。
3行目で これらの設定を適用している。

とのことでした。

https://stackoverflow.com/questions/60444428/android-skip-chrome-welcome-screen-using-adb

で、結局何をすればいいのか?の結論ですが、

await device.shell("pm clear com.android.chrome");
await device.shell("am set-debug-app --persistent com.android.chrome");
await device.shell('echo "chrome --disable-fre --no-default-browser-check --no-first-run" > /data/local/tmp/chrome-command-line');
await device.shell("am start -n com.android.chrome/com.google.android.apps.chrome.Main");

await device.shell("settings put global http_proxy 【IPアドレス】:【ポート番号】");

とすれば、処理前にCookieやLocalStorageを初期化しながらプロキシを適用することができます。