お金をかけずにサーバーの勉強をしよう

Selenium Gridによるリモートのテスト実行環境

2024年1月17日

メニューへ戻る

※2024年1月17日時点で、このやり方ですと上手くいきません。
snapのコマンド実行の仕組みに問題があるようです。

Selenium Gridどころか、ローカルでの実行でも問題があるようで、回避策として以下の情報がありますが、Selenium Gridではダメでした。
[🐛 Bug]: Firefox browser initialization fails with Snap-installed Firefox and Selenium Python version >= 4.11 #13252

要するに GeckoDriverのパスを [/snap/bin/geckodriver](これは [/snap/bin/snap]ファイルへのシンボリックリンク)ではなく、強制的に本体の [/snap/firefox/current/usr/lib/firefox/geckodriver]と指定しているのです

しかし、ローカル用の WebDriverのクラスではこのパスを引数で渡せるのですが、リモート用の WebDriverのクラスではその引数を取らないのです。

現時点で Seleniumも Mozillaもこれに対応するつもりはないようなので諦め気分です。
snapの Firefoxを棄てて、aptの Firefoxを入れれば解決するようですが、負けた気がします…


こんにちは。

Seleniumインストール」で Seleniumによる Webブラウザを介してのテストを自動化をやっていますが、Seleniumの真髄は Gridというテスト実行環境の分散にあると思っています。

Seleniumのサイトに Gridの説明がありますので、詳しくはこちらを参照して下さい。
Grid

Gridのことを簡単に書きますと、以下の要素になるかと思います。

図にするとこんな感じ。
Selenium Grid 関係図

Gridを沢山作ることによって、テストを同時並行で行うことができ、その取りまとめを Hubが担う構成です。

実際にこのような分散テスト環境を作る場合には、Hubの機能が分化されたサーバープログラムを立てる必要があるのですが、ここでは Standaloneという形式の、リモートサーバー1台でテスト実行まで行う形態の環境を作ります。

環境は以下の通り。


それでは環境を作ります。
Ubuntu Serverは以下で作ったものを前提にしています。

Ubuntu Linux Serverをインストール
Ubuntu Serverの初期設定


snap版 Firefoxをインストールします。

subro@UbuntuServer2204:-1~$ sudo snap install firefox
firefox 112.0-2 from Mozilla✓ installed

インストールできました。


必須ではないんですが、Selenium Gridのサーバープログラムを実行するための専用ユーザーを作ってそれで動かすことにしました。

subro@UbuntuServer2204-1:~$ sudo adduser selenium
ユーザー `selenium' を追加しています...
新しいグループ `selenium' (1002) を追加しています...
新しいユーザー `selenium' (1002) をグループ `selenium' に追加しています...
ホームディレクトリ `/home/selenium' を作成しています...
`/etc/skel' からファイルをコピーしています...
新しい パスワード: seleniumユーザーの新規パスワード
新しい パスワードを再入力してください: パスワードをもう一回
passwd: パスワードは正しく更新されました
selenium のユーザ情報を変更中
新しい値を入力してください。標準設定値を使うならリターンを押してください
        フルネーム []:Enterキー
        部屋番号 []:Enterキー
        職場電話番号 []:Enterキー
        自宅電話番号 []:Enterキー
        その他 []:Enterキー
以上で正しいですか? [Y/n] Y

seleniumユーザーができました。


一旦ログアウトして、seleniumユーザーでログインし直してから以降を行います。

Selenium Gridのプログラムをダウンロードします。
Downloads

ここにある「Latest stable version 4.8.3」(2023年4月13日の最新)をクリックするとダウンロードが始まります。

でも面倒くさいので、curlコマンドを使ってサーバーで直接落としますね。

selenium@UbuntuServer2204-1:~$ curl -s -k -OL https://github.com/SeleniumHQ/selenium/releases/download/selenium-4.8.0/selenium-server-4.8.3.jar

selenium@UbuntuServer2204-1:~$ ls -l selenium-server-4.8.3.jar
-rw-rw-r-- 1 selenium selenium 31238548  4月 13 11:27 selenium-server-4.8.3.jar

ダウンロードできました。

snap版 Firefoxを使う際の対応として、ホームディレクトリに「tmp」というディレクトリを作るのと、ユーザーの環境変数で「TMPDIR=$HOME/tmp」というのを有効になるようにしないといけません。

ディレクトリ作成です。

selenium@UbuntuServer2204-1:~$ mkdir tmp

ホームディレクトリ/.bashrc の最後に以下を加えました。

export TMPDIR=$HOME/tmp

環境変数が有効になるように ホームディレクトリ/.bashrcを読み込みます。

selenium@UbuntuServer2204-1:~$ . .bashrc

selenium@UbuntuServer2204-1:~$ env | grep TMPDIR
TMPDIR=/home/selenium/tmp

有効になりました。

Selenium Gridのプログラムを実行します。

selenium@UbuntuServer2204-1:~$ java -jar selenium-server-4.8.3.jar standalone
11:59:54.871 INFO [LoggingOptions.configureLogEncoding] - Using the system default encoding
11:59:54.877 INFO [OpenTelemetryTracer.createTracer] - Using OpenTelemetry for tracing
11:59:55.687 INFO [NodeOptions.getSessionFactories] - Detected 4 available processors
11:59:55.696 INFO [NodeOptions.discoverDrivers] - Driver(s) already present on the host: 1
11:59:55.720 INFO [NodeOptions.report] - Adding Firefox for {"browserName": "firefox"} 4 times (Host)
11:59:55.760 INFO [Node.] - Binding additional locator mechanisms: relative
11:59:55.791 INFO [GridModel.setAvailability] - Switching Node ac238cb9-6a7a-48ff-b186-0a22a40d45f6 (uri: http://172.17.0.1:4444) from DOWN to UP
11:59:55.791 INFO [LocalDistributor.add] - Added node ac238cb9-6a7a-48ff-b186-0a22a40d45f6 at http://172.17.0.1:4444. Health check every 120s
11:59:55.943 INFO [Standalone.execute] - Started Selenium Standalone 4.8.3 (revision b19b418e60): http://172.17.0.1:4444

立ち上がりました。
4444/tcpで待ち受けています。

これで Selenium Grid側の準備は終了です。


では、テストプログラムを作ります。
私はクライアント環境として Lubuntu 22.04で実行しますが、他でも変わらないでしょう。

テストプログラムは Python3で作りましたので、Pythonの seleniumライブラリをインストールします。

subro@Lubuntu2204:~/work/python/selenium$ python3 -m pip install selenium
Defaulting to user installation because normal site-packages is not writeable
Collecting selenium
  Using cached selenium-4.5.0-py3-none-any.whl (995 kB)
Collecting trio~=0.17
  Using cached trio-0.22.0-py3-none-any.whl (384 kB)
Collecting urllib3[socks]~=1.26
  Using cached urllib3-1.26.12-py2.py3-none-any.whl (140 kB)
Collecting certifi>=2021.10.8
  Using cached certifi-2022.9.24-py3-none-any.whl (161 kB)
Collecting trio-websocket~=0.9
  Using cached trio_websocket-0.9.2-py3-none-any.whl (16 kB)
Collecting attrs>=19.2.0
  Using cached attrs-22.1.0-py2.py3-none-any.whl (58 kB)
Requirement already satisfied: idna in /usr/local/lib/python3.8/dist-packages (from trio~=0.17->selenium) (2.10)
Collecting async-generator>=1.9
  Using cached async_generator-1.10-py3-none-any.whl (18 kB)
Collecting sniffio
  Using cached sniffio-1.3.0-py3-none-any.whl (10 kB)
Collecting sortedcontainers
  Using cached sortedcontainers-2.4.0-py2.py3-none-any.whl (29 kB)
Collecting exceptiongroup>=1.0.0rc9
  Using cached exceptiongroup-1.0.0rc9-py3-none-any.whl (12 kB)
Collecting outcome
  Using cached outcome-1.2.0-py2.py3-none-any.whl (9.7 kB)
Collecting wsproto>=0.14
  Using cached wsproto-1.2.0-py3-none-any.whl (24 kB)
Collecting PySocks!=1.5.7,<2.0,>=1.5.6
  Using cached PySocks-1.7.1-py3-none-any.whl (16 kB)
Collecting h11<1,>=0.9.0
  Using cached h11-0.14.0-py3-none-any.whl (58 kB)
Installing collected packages: sortedcontainers, urllib3, sniffio, PySocks, h11, exceptiongroup, certifi, attrs, async-generator, wsproto, outcome, trio, trio-websocket, selenium
Successfully installed PySocks-1.7.1 async-generator-1.10 attrs-22.1.0 certifi-2022.9.24 exceptiongroup-1.0.0rc9 h11-0.14.0 outcome-1.2.0 selenium-4.5.0 sniffio-1.3.0 sortedcontainers-2.4.0 trio-0.22.0 trio-websocket-0.9.2 urllib3-1.26.12 wsproto-1.2.0

2022年4月13日時点では、4.8.3がインストールされました。
環境的はこれで OKでしょう。


テストスクリプトはこのように。

subro@Lubuntu2204:~/work/python/selenium$ cat test.py
import unittest
from selenium import webdriver
from selenium.webdriver.firefox.options import Options

class OkaneKakenai(unittest.TestCase):

    def setUp(self):
        firefox_options = webdriver.FirefoxOptions()
        firefox_options.add_argument("--headless")

        self.driver = webdriver.Remote(command_executor='http://UbuntuServer2204:4444',options=firefox_options)
        print (firefox_options)

    def test_search_in_python_org(self):
        driver = self.driver
        driver.get("https://subro.mokuren.ne.jp/")
        self.assertIn("お金をかけずにサーバーの勉強をしよう", driver.title, '---理由--- タイトルが間違っています。')

    def tearDown(self):
        self.driver.close()

if __name__ == "__main__":
    unittest.main()

このサイトのトップページにアクセスして、タイトルに「お金をかけずにサーバーの勉強をしよう」と入っているかのテストをするようになっています。

では、テストしてみます。

subro@Lubuntu2204:~/work/python/selenium$ python3 test.py
<selenium.webdriver.firefox.options.Options object at 0x7f3e07c0a170>
./usr/lib/python3.10/unittest/suite.py:84: ResourceWarning: unclosed <socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=6, laddr=('192.168.1.45', 33598), raddr=('192.168.1.102', 4444)>
  return self.run(*args, **kwds)
ResourceWarning: Enable tracemalloc to get the object allocation traceback

----------------------------------------------------------------------
Ran 1 test in 3.976s

OK

実行したテストが1つで、結果は OKって出てます。
上のメッセージがよく分かりませんが…。

スクリプトを変更してわざとエラーになるようにして、もう一回テスト。

subro@Lubuntu2204:~/work/python/selenium$ cat test.py
import unittest
from selenium import webdriver
from selenium.webdriver.firefox.options import Options

class OkaneKakenai(unittest.TestCase):

    def setUp(self):
        firefox_options = webdriver.FirefoxOptions()
        firefox_options.add_argument("--headless")

        self.driver = webdriver.Remote(command_executor='http://UbuntuServer2204:4444',options=firefox_options)
        print (firefox_options)

    def test_search_in_python_org(self):
        driver = self.driver
        driver.get("https://subro.mokuren.ne.jp/")
        self.assertIn("お金をかけてサーバーの勉強をしよう", driver.title, '---理由--- タイトルが間違っています。')

    def tearDown(self):
        self.driver.close()

if __name__ == "__main__":
    unittest.main()

実行します。

subro@Lubuntu2204:~/work/python/selenium$ python3 test.py
<selenium.webdriver.firefox.options.Options object at 0x7f530fd8e170>
F
======================================================================
FAIL: test_search_in_python_org (__main__.OkaneKakenai)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/subro/work/python/selenium/test.py", line 17, in test_search_in_python_org
    self.assertIn("お金をかけてサーバーの勉強をしよう", driver.title, '---理由--- タイトルが間違っています。')
AssertionError: 'お金をかけてサーバーの勉強をしよう' not found in 'お金をかけずにサーバーの勉強をしよう' : ---理由--- タイトルが間違っています。

----------------------------------------------------------------------
Ran 1 test in 3.866s

FAILED (failures=1)

うむ、お金をかけてお勉強するのは誤りのようです。

スクリプトの作りが悪いのでしょう。
出てくるメッセージに若干の違和感を感じるものの、リモートサーバー(GUIなし)にある Firefoxをヘッドレスで動かして、Webアクセスのテストを自動化することができました。

ここではFireFoxでやっていますが、Chromeでも同じ様にすればできるはずです。

Windowsサーバーで Selenium Gridのサーバーを立てれば、Edgeでもできるでしょう。

Gridを複数のサーバーに展開していくにはもう少しお勉強が必要ですが、使う Javaのファイルは同じものですし、そんなに難しくはなさそうです。
私にとってはテストスクリプトをいっぱい書くほうが大変で…。

Jenkinsから Seleniumの環境に連携することができるようですし、クライアントPCからテストを実行するのではなく、CI/CDツールから実行させるようにすれば、仕事の時間外にテストをやらせておけますよね。


こうして Selenium Gridを使えるようになりましたが、実はこれまでのバージョンには問題があって中々上手く行かなかった経緯があり、それを「Seleniumの WebDriver周りの話」に書いています。
WebDriver(geckodriver)と Firefoxの関係について図示しているので、よろしければそちらもご覧ください。

以上で Selenium Gridのお話はおしまいです。