WSL2 上で起動した Selenium Webdriver や Puppeteer から Windows 側の Chrome ウィンドウを操作したかったが無理

出来たって人もいるみたいだけど、自分は無理だった。

目次

やりたいこと

  1. Windows 側で常用している Chrome ブラウザのプロファイルを利用して、Selenium 的なツールでブラウザの自動操作を行いたい
    • サイトへのログイン情報とかを流用したかったので、既存のプロファイルを使い回したかった
  2. ツールが Chrome を自動操作している様子をリアルタイムに確認したかった
    • つまりヘッドレスで動くのではなく、ヘッドありで動かしたかった

それでいて、

という環境だった。最近は開発環境を WSL に移しているので、Windows 側には余計なモノをインストールしたくなかった。

こんな環境で、果たして WSL から Windows へと世界を飛び越えられるのか、試してみた次第。

Selenium Webdriver は出来そうで出来なかった

まずは過去にも使ったことのある、Ndoe.js 製の selenium-webdriver を使ってみた。

…というだけで動かせるらしかったが、動かなかった。Builder のインスタンスを生成するところでタイムアウトになった。

#!/bin/sh
/mnt/c/PATH/TO/chromedriver.exe "$@"

あとはパスの指定方法を

などの表記パターンでそれぞれ試したが、うまく行かず。コード片残しとく。

const webdriver = require('selenium-webdriver');
const chrome = require('selenium-webdriver/chrome');

(async () => {
  const service = new chrome.ServiceBuilder(CHROME_DRIVER_PATH).build();
  chrome.setDefaultService(service);
  
  const options = new chrome.Options()
    .setChromeBinaryPath(CHROME_BROWSER_PATH)
    .addArguments([
      `--user-data-dir=${CHROME_USER_DATA_DIR}`
    ]);
  
  const capabilities = webdriver.Capabilities.chrome().set('chromeOptions', {
    args: [
      '--no-sandbox'
    ]
  });
  
  const driver = await new webdriver.Builder()
    .forBrowser('chrome')
    .setChromeOptions(options)
    .withCapabilities(capabilities)
    .build();
  // ココでエラーが出て続行不可能
})();

Selenium Server を Windows 側に立ててみたがダメだった

上の記事によると、Windows 側に Java をインストールし、

↑ から Selenium Server (Grid) の JAR ファイル selenium-server-standalone-3.141.59.jar を落としてきて、コレに接続するようにしたら動く、とか書いてあった。

# PowerShell にて。Chocolatey で JRE 8 を入れておく
PS1> choco install jreruntime

# PowerShell で Selenium Server を動かしてみる
PS1> java -jar .\selenium-server-standalone-3.141.59.jar -port 4445

ココまでやると、

なんかにアクセスして Selenium Server が動いているのを確認できる。

そしたら WSL 側に戻る。先程見ていた文献で usingServer() メソッドの存在走っていたので、Builder 部分を次のようにしてみる。

const driver = await new webdriver.Builder()
  .forBrowser('chrome')
  .usingServer('http://172.xx.xx.xx:4445/wd/hub')
  .setChromeOptions(options)
  .withCapabilities(capabilities)
  .build();

localhost ではなく 172.xx.xx.xx なる IP を指定しているのは、後で詳しく説明するが、Windows ホスト側の localhost を参照するため。/etc/resolv.conf 内に記載の nameserver の IP アドレスを書いてある。

コレを実行すると、PowerShell 上で動く Selenium Server がエラーログを出力していて、通信が届いていることは確認できた。でもそのエラーを解消しきれず断念。なんか実行パスかドライバーのパスが違うぐらいのエラーな感じがするんだけど、どうやってもダメだった。

Selenium Webdriver ではなく Puppeteer を使ってみたがダメだった

Selenium Webdriver を諦めて、Puppeteer を使ってみることにした。コイツは Selenium よりもブラウザ周りが扱いやすいっぽい。

await puppeteer.launch({
  executablePath: '/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe',
  userDataDir: USER_DATA_DIR
})

こんな感じになるのだが、結局は上手く動かなかった。

launch() メソッドが Windows 側に Chrome ウィンドウを開くところまでは行くのだが、その直後に

connect ECONNREFUSED 127.0.0.1:60081

というようなエラーメッセージで異常終了してしまい、launch() メソッドが完了しない。

そもそも WSL と Windows 間の localhost はどう関係しているのか

Puppeteer のエラーメッセージの中に、

とかいう URL が見えて、ふと「この 127.0.0.1 は、WSL Ubuntu の世界における localhost だよな…」と思った。

ネットワークの基礎知識として、localhost自機を表すホスト名なのは分かっている。で、WSL の世界でいう localhost と、Windows の世界の localhost は別物だろうな、という認識もある。例えば Docker コンテナ内の localhost は Windows ホスト側の localhost とは違うから、Windows 側から Docker コンテナ内で動く Web サーバを見たければポートフォワードが必要だったりするので、その辺が分かっていないワケではない。

でも、WSL 上で $ node server.js のように起動した http://localhost:8080/ な開発サーバなんかは、Windows 側から http://localhost:8080/ で繋げられてるよな?

調べてみると、どうやら WSL 上の localhost は、Windows 側からも localhost として見えるように調整されているが、その逆は透過されないようなのだ。

Windows 側の localhost に、WSL からアクセスしたい場合は、/etc/resolv.conf に記載の IP を使ったりするワケである。

そうすると、Puppeteer のエラーメッセージ中に出てきた 127.0.0.1 は、WSL 内の localhost を見てしまっていて、Windows 側の localhost は覗けていないことになる。Windows 側の Chrome を触りたいのに、WSL 内の localhost に閉じていたら上手く動かないやろな、と…。

未検証の内容

なんだか段々諦めてきてしまって、以降の検証をやる前にこの記事を書いてしまった。WSL2 はまだまだアップデートが盛んで、仕様変更も多いので、過去のやり方が通用しなくなっているのだろうし、それがまたいつか別の方法で出来るようになっているかもしれない。

Puppeteer に関しては、起動済みの Chrome ブラウザに後から接続しにいくという方法があるらしい。

なんか原理的に考えるとコレが上手く行きそう感あるな…。

あとはヘッドありで動かすのを諦めて、Docker に閉じ込めてしまうとか。

やりたいことをとにかく実現したいなら、環境を汚しまくれば不可能ではない。

こうなるともうクソダルゲンナリマンで、「それならブラウザの自動化なんか実現しなくていい」って気持ちになってしまうので、もうやらないことにする。w