WSL2 上で起動した Selenium Webdriver や Puppeteer から Windows 側の Chrome ウィンドウを操作したかったが無理
出来たって人もいるみたいだけど、自分は無理だった。
目次
- やりたいこと
- Selenium Webdriver は出来そうで出来なかった
- Selenium Server を Windows 側に立ててみたがダメだった
- Selenium Webdriver ではなく Puppeteer を使ってみたがダメだった
- そもそも WSL と Windows 間の
localhost
はどう関係しているのか - 未検証の内容
やりたいこと
- Windows 側で常用している Chrome ブラウザのプロファイルを利用して、Selenium 的なツールでブラウザの自動操作を行いたい
- サイトへのログイン情報とかを流用したかったので、既存のプロファイルを使い回したかった
- ツールが Chrome を自動操作している様子をリアルタイムに確認したかった
- つまりヘッドレスで動くのではなく、ヘッドありで動かしたかった
それでいて、
- Windows 側には Java・Node.js などがインストールされていない
- WSL 側にのみ、Java や Node.js がインストールされている
という環境だった。最近は開発環境を WSL に移しているので、Windows 側には余計なモノをインストールしたくなかった。
こんな環境で、果たして WSL から Windows へと世界を飛び越えられるのか、試してみた次第。
Selenium Webdriver は出来そうで出来なかった
まずは過去にも使ったことのある、Ndoe.js 製の selenium-webdriver
を使ってみた。
- Node.js で selenium-webdriver と chromedriver を使って Chrome ブラウザを自動操作してみる
- WSL 側に Node.js プロジェクトを作って、selenium-webdriver をインストールする
- Windows 側で、Windows 用の
chromedriver.exe
をダウンロードして適当なところに置いておく - selenium-webdriver で
chromedriver.exe
へのパスを/mnt/c/
で指定する
…というだけで動かせるらしかったが、動かなかった。Builder のインスタンスを生成するところでタイムアウトになった。
- 参考 : notes/Windows/Windows Subsystem for Linux at master · lackovic/notes · GitHub
- この方式を説明している
- 参考 : WSL (Ubuntu16.04.4 LTS) 上の Python から、Selenium を利用して Windows側のウェブブラウザを操作する | ラボラジアン
- Python 版。
executable_path
なるオプションで/mnt/c/
配下のdriver.exe
を指定している
- Python 版。
- 参考 : WSL環境でのseleniumクローラーをブラウザを立ち上げて開発する – 株式会社ルーター
- Ruby 版。
driver_path
というオプションで同様に/mnt/c/
配下のchromedriver.exe
を指定している
- Ruby 版。
- 参考 : Selenium-webdriverでブラウザ用ドライバーを指定して動かすよ - Qiita
- Node.js 版において
chromedriver.exe
のパスを指定するには、new require('selenium-webdriver/chrome').ServiceBuilder('/PATH/TO/chromedriver.exe')
でインスタンスを生成し、Builder#setChromeService(service)
と指定する
- Node.js 版において
- 参考 : Using BashOnWindows with Selenium? · Issue #1169 · microsoft/WSL · GitHub
- 以下のような
chromedriver
シェルスクリプトを書き、selenium-webdriver を実行する Node.js スクリプトと同じ階層に置いておくと、ServiceBuilder での指定が要らなくなるっぽい
- 以下のような
#!/bin/sh
/mnt/c/PATH/TO/chromedriver.exe "$@"
- 参考 : node.js - How to use Selenium chromedriver without being forced to update? - Stack Overflow
chrome.ServiceBuilder(path)
、options.setChromeBinaryPath()
、usingServer()
などの API を知った
- 参考 : Electronで作った画面の自動操作 - Qiita
- 参考 : SeleniumでChromeのユーザープロファイルを指定しつつ同時に自分もChromeを使う方法 - Qiita
--user-data-dir
は使えた。コチラでプロファイル名のディレクトリまでのパスを指定する。--profile-directory
オプションは上手く行かなかった
あとはパスの指定方法を
'/mnt/c/Users/…'
'C:\\Users\\…'
(JS の文字列なのでバックスラッシュはエスケープする)'C:/Users/…'
などの表記パターンでそれぞれ試したが、うまく行かず。コード片残しとく。
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 をインストールし、
- 参考 : Downloads
↑ から 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
ココまでやると、
http://localhost:4445/wd/hub/static/resource/hub.html
なんかにアクセスして 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 よりもブラウザ周りが扱いやすいっぽい。
- 参考 : Puppeteer on WSL | Memorandum!
- Windows 側の
chrome.exe
へのパスを指定してうまく動いていそう
- Windows 側の
- 参考 : GitHub - dev100kg/puppeteer-sample-wsl: Puppeteer を WSL で実行するサンプル
- puppeteer-core と chrome-launcher を使用しているサンプルプロジェクト。動かず
- 参考 : Puppeteer & Carlo を Markdown スライド作成 CLI ツール (Marp CLI) で活用する - Qiita
- 上のサンプルプロジェクトとほぼ同じ
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()
メソッドが完了しない。
- 参考 : Puppeteer doesn't run under WSL (Windows subsystem for Linux) · Issue #1837 · puppeteer/puppeteer · GitHub
- Puppeteer における WSL 連携で一番盛んに議論されている Issues
- なんかもう最後は WSL に GUI 作って VcXsrv で開いたら?ってなってる
そもそも WSL と Windows 間の localhost
はどう関係しているのか
Puppeteer のエラーメッセージの中に、
ws://127.0.0.1:50887/devtools/browser/
とかいう 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
として見えるように調整されているが、その逆は透過されないようなのだ。
- 参考 : Comparing WSL 2 and WSL 1 | Microsoft Docs
- Windows 公式の説明。Windows → WSL の参照は
localhost
が透過しているが、WSL → Windows への参照は/etc/resolv.conf
に記載の IP を使う必要がある -
Accessing Windows networking apps from Linux (host IP)
- Windows 公式の説明。Windows → WSL の参照は
- 参考 : WSL 2 WSL 2 cannot access windows service via localhost:port · Issue #4619 · microsoft/WSL · GitHub
- 参考 : Unable to accessing Windows applications from WSL · Issue #4793 · microsoft/WSL · GitHub
- WSL 側から Windows 側を覗くには、
localhost
ではダメで、/etc/resolv.conf
に記載の172.xx.xx.xx
な IP を使うか、PowerShell でipconfig
で確認した192.168.xx.xx
な IP を指定する必要がある
- WSL 側から Windows 側を覗くには、
- 参考 : How to access host ip and port? · Issue #1032 · microsoft/WSL · GitHub
/etc/resolv.conf
から IP を拾ってくるワンライナー
Windows 側の localhost
に、WSL からアクセスしたい場合は、/etc/resolv.conf
に記載の IP を使ったりするワケである。
そうすると、Puppeteer のエラーメッセージ中に出てきた 127.0.0.1
は、WSL 内の localhost を見てしまっていて、Windows 側の localhost は覗けていないことになる。Windows 側の Chrome を触りたいのに、WSL 内の localhost に閉じていたら上手く動かないやろな、と…。
未検証の内容
なんだか段々諦めてきてしまって、以降の検証をやる前にこの記事を書いてしまった。WSL2 はまだまだアップデートが盛んで、仕様変更も多いので、過去のやり方が通用しなくなっているのだろうし、それがまたいつか別の方法で出来るようになっているかもしれない。
- 参考 : GitHub - microsoft/WinAppDriver: Windows Application Driver
- WinAppDriver という Selenium ライクなツールが WSL に対応しているらしいが未検証
Puppeteer に関しては、起動済みの Chrome ブラウザに後から接続しにいくという方法があるらしい。
- 参考 : WSL2 refusing to connect to chrome.exe · Issue #5957 · puppeteer/puppeteer · GitHub
--remote-debugging-address
オプションを付けて Chrome を起動する
- 参考 : javascript - Connecting Browsers in Puppeteer - Stack Overflow
--remote-debugging-port
オプションでポートを指定して Chrome を起動しておくlaunch()
ではなくconnect()
で接続できるらしい
- 参考 : puppeteer.connectを試す - hiroqn's Data.ByteString.Lazy.ByteString
--remote-debugging-port=0
とするとポートがランダムに決まり、エンドポイント URL がコンソールで確認できるらしいconnect()
メソッドのbrowserWSEndpoint
オプションでそのエンドポイント URL を指定すれば良いらしい
なんか原理的に考えるとコレが上手く行きそう感あるな…。
あとはヘッドありで動かすのを諦めて、Docker に閉じ込めてしまうとか。
- 参考 : Docker + docker-compose + puppeteer でスクレイピングしてみた - Qiita
- Docker 内にフォントを入れたりしないといけないみたいで、コレも面倒っちゃ面倒
やりたいことをとにかく実現したいなら、環境を汚しまくれば不可能ではない。
- WSL に GUI デスクトップ環境を作り、Ubuntu 版の Chrome を動かす (= 完全に WSL 内で完結させる。Windows 側の要素は一切使わない。デスクトップを見るのは VcXsrv で)
- 今まで Windows GitBash を使っていたんだし、Windows 側に Java でも Node.js でも入れて環境構築したらええ。そしたら
localhost
で Chrome を触れる (= WSL を一切使わない)
こうなるともうクソダルゲンナリマンで、「それならブラウザの自動化なんか実現しなくていい」って気持ちになってしまうので、もうやらないことにする。w