Java
Selenium
テスト
SeleniumGrid

Selenium Gridを拡張する(Hub側)

はじめに

色々あってSelenium GridにRESTする前に、Hubに接続されているNodeの状態をプログラムで見ようとしました。
Web画面のコンソールでは見れるのですが、できればJsonで返してほしいがそのような機能はない・・・

本家のCustomizing the Gridを参考にしようにもコードも見つからないし、起動の方法も古いようでした:innocent:
最新の情報がどこかにある?:thinking:

今回の用途ではHub側のみですが、拡張方法を忘れないよう記録します。

利用したソフトウェア

ソフト バージョン 用途
java 1.8.0_191
selenium-server-standalone.jar 3.14.0

環境構築

Windowsで開発環境を構築しています。
Mavenで設定するのみです。
selenium-serverとレスポンスJsonを生成するためにgsonを使用します。
aWS050498.JPG

Maven設定
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>selenium-grid-extend</groupId>
  <artifactId>selenium-grid-extend</artifactId>
  <version>0.0.1</version>
  <build>
    <sourceDirectory>src</sourceDirectory>
    <testSourceDirectory>src</testSourceDirectory>
    <resources>
      <resource>
        <directory>resource</directory>
      </resource>
    </resources>
    <testResources>
      <testResource>
        <directory>resource</directory>
      </testResource>
    </testResources>
  </build>
  <dependencies>
    <dependency>
      <groupId>org.seleniumhq.selenium</groupId>
      <artifactId>selenium-server</artifactId>
      <version>3.141.5</version>
    </dependency>
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.8.5</version>
    </dependency>
  </dependencies>
  <properties>
    <java.version>1.8</java.version>
    <file.encoding>UTF-8</file.encoding>
    <project.build.sourceEncoding>${file.encoding}</project.build.sourceEncoding>
    <project.reporting.outputEncoding>${file.encoding}</project.reporting.outputEncoding>
    <maven.compiler.encoding>${file.encoding}</maven.compiler.encoding>
    <maven.compiler.source>${java.version}</maven.compiler.source>
    <maven.compiler.target>${java.version}</maven.compiler.target>
  </properties>
</project>

プログラム

Customizing the GridによるとHubの内部にアクセスする場合はRegistryBasedServletを継承、アクセスしなければHttpServletを継承すれば良いみたいです。

今回はHubに接続されているNodeの状態を見たいので、RegistryBasedServletを継承します。
試しに作成したのは2つのプログラム。

全てのNodeをそのままJsonで返す拡張クラス
AllNodes.java
package selenium.extend.hub.servlet;

import java.io.IOException;
import java.util.Iterator;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.openqa.grid.common.exception.GridException;
import org.openqa.grid.internal.GridRegistry;
import org.openqa.grid.internal.ProxySet;
import org.openqa.grid.internal.RemoteProxy;
import org.openqa.grid.web.servlet.RegistryBasedServlet;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;

public class AllNodes extends RegistryBasedServlet {

    public AllNodes() {
        this(null);
    }

    public AllNodes(GridRegistry registry) {
        super(registry);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        process(request, response);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        process(request, response);
    }

    protected void process(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        response.setStatus(200);

        try {
            JsonObject res = getResponse(request);
            response.getWriter().print(res);
            response.getWriter().close();
        } catch (JsonSyntaxException e) {
            throw new GridException(e.getMessage());
        }
    }

    private JsonObject getResponse(HttpServletRequest request) {
        JsonObject json = new JsonObject();
        ProxySet proxies = super.getRegistry().getAllProxies();
        json.add("Nodes", getNodes(proxies));
        return json;
    }

    private JsonArray getNodes(ProxySet proxies) {
        JsonArray array = new JsonArray();
        Iterator<RemoteProxy> itr = proxies.iterator();
        Gson gson = new Gson();

        while (itr.hasNext()) {
            RemoteProxy proxy = itr.next();
            JsonObject proxyJson = new JsonObject();
            proxyJson.add("Node", gson.toJsonTree(proxy.getOriginalRegistrationRequest()));
            array.add(proxyJson);
        }
        return array;
    }

}
全てのNodeを未使用/使用中にわけてJsonで返す拡張クラス
AllNodesState.java
package selenium.extend.hub.servlet;

import java.io.IOException;
import java.util.Iterator;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.openqa.grid.common.exception.GridException;
import org.openqa.grid.internal.GridRegistry;
import org.openqa.grid.internal.ProxySet;
import org.openqa.grid.internal.RemoteProxy;
import org.openqa.grid.web.servlet.RegistryBasedServlet;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;

public class AllNodesState extends RegistryBasedServlet {

    public AllNodesState() {
        this(null);
    }

    public AllNodesState(GridRegistry registry) {
        super(registry);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        process(request, response);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        process(request, response);
    }

    protected void process(HttpServletRequest request, HttpServletResponse response) throws IOException {
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        response.setStatus(200);

        try {
            JsonObject res = getResponse(request);
            response.getWriter().print(res);
            response.getWriter().close();
        } catch (JsonSyntaxException e) {
            throw new GridException(e.getMessage());
        }
    }

    private JsonObject getResponse(HttpServletRequest request) {
        JsonObject json = new JsonObject();
        ProxySet proxies = super.getRegistry().getAllProxies();
        json.add("Nodes", getNodes(proxies));
        return json;
    }

    private JsonArray getNodes(ProxySet proxies) {
        JsonArray array = new JsonArray();
        Iterator<RemoteProxy> itr = proxies.iterator();
        Gson gson = new Gson();

        JsonArray freeProxies = new JsonArray();
        JsonArray busyProxies = new JsonArray();

        while (itr.hasNext()) {
            RemoteProxy proxy = itr.next();
            JsonObject proxyJson = new JsonObject();
            proxyJson.add("Node", gson.toJsonTree(proxy.getOriginalRegistrationRequest()));

            if (!proxy.isBusy()) {
                freeProxies.add(proxyJson);
            } else {
                busyProxies.add(proxyJson);
            }
        }

        JsonObject freeJson = new JsonObject();
        freeJson.add("FreeNodes", freeProxies);
        array.add(freeJson);

        JsonObject busyJson = new JsonObject();
        busyJson.add("BusyNodes", busyProxies);
        array.add(busyJson);

        return array;
    }

}

Jarにする

作成したクラスだけぶっこんでます。
今回はextend.jarという名前で作成。
aWS050500.JPG

動作確認

ディレクトリ構成

依存関係にあるjarを全てlibに配置しておきます。

C:\GRID
├─start-hub-extend.bat
│
└─lib
     ├ extend.jar
     ├ gson-2.8.5.jar
     └ selenium-server-standalone-3.14.0.jar
Hub起動コマンド

あとはクラスパスなどを指定して起動するだけです。
-servletsで追加するクラスを指定するのですが、今回は2つあるのでカンマで区切っています。

start-hub-extend.bat
java -cp lib/* org.openqa.grid.selenium.GridLauncherV3 -role hub -servlets "selenium.extend.hub.servlet.AllNodes,selenium.extend.hub.servlet.AllNodesState"
Selenium Grid のログ

バッチ実行してみると、3行目4行目に普段はでていない『[Hub.<init>]』の文字がでています。
簡単にいうと『/grid/admin/AllNodes/』とかでアクセスしてねってことですね。

14:43:51.090 INFO [GridLauncherV3.launch] - Selenium build info: version: '3.14.0', revision: 'aacccce0'
14:43:51.106 INFO [GridLauncherV3$2.launch] - Launching Selenium Grid hub on port 4444
14:43:51.137 INFO [Hub.<init>] - binding selenium.extend.hub.servlet.AllNodes to /grid/admin/AllNodes/*
14:43:51.137 INFO [Hub.<init>] - binding selenium.extend.hub.servlet.AllNodesState to /grid/admin/AllNodesState/*
2018-11-09 14:43:51.480:INFO::main: Logging initialized @780ms to org.seleniumhq.jetty9.util.log.StdErrLog
14:43:51.777 INFO [Hub.start] - Selenium Grid hub is up and running
14:43:51.777 INFO [Hub.start] - Nodes should register to http://XXX.XXX.XXX.XXX:4444/grid/register/
14:43:51.777 INFO [Hub.start] - Clients should connect to http://XXX.XXX.XXX.XXX:4444/wd/hub
アクセスしてみる

※Jsonは記事貼り付け前に整形しています

/grid/console は以下のような状態
aWS050502.JPG

/grid/admin/AllNodes だと・・・

{
  "Nodes": [
    {
      "Node": {
        "configuration": {
          "remoteHost": "http://XXX.XXX.XXX.XXX:37803",
          "id": "http://XXX.XXX.XXX.XXX:37803",
          "capabilities": [
            {
              "caps": {
                "browserName": "chrome",
                "maxInstances": 1,
                "platform": "WINDOWS",
                "platformName": "WINDOWS",
                "seleniumProtocol": "WebDriver",
                "server:CONFIG_UUID": "8405f2b4-e375-4251-a2e8-50e432eb24b8"
              }
            }
          ],
          "downPollingLimit": 2,
          "hub": "http://XXX.XXX.XXX.XXX:4444/grid/register",
          "nodePolling": 5000,
          "nodeStatusCheckTimeout": 5000,
          "proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy",
          "register": true,
          "registerCycle": 5000,
          "unregisterIfStillDownAfter": 60000,
          "enablePlatformVerification": true,
          "custom": {},
          "maxSession": 5,
          "servlets": [],
          "withoutServlets": [],
          "avoidProxy": false,
          "browserSideLog": false,
          "captureLogsOnQuit": false,
          "browserTimeout": 0,
          "debug": false,
          "host": "172.16.11.134",
          "port": 37803,
          "role": "node",
          "timeout": 1800
        }
      }
    },
    {
      "Node": {
        "configuration": {
          "remoteHost": "http://XXX.XXX.XXX.XXX:3753",
          "id": "http://XXX.XXX.XXX.XXX:3753",
          "capabilities": [
            {
              "caps": {
                "browserName": "firefox",
                "maxInstances": 2,
                "platform": "WINDOWS",
                "platformName": "WINDOWS",
                "seleniumProtocol": "WebDriver",
                "server:CONFIG_UUID": "61acf94e-1db8-4274-9587-1e32a0ebb378"
              }
            },
            {
              "caps": {
                "browserName": "internet explorer",
                "maxInstances": 1,
                "platform": "WINDOWS",
                "platformName": "WINDOWS",
                "seleniumProtocol": "WebDriver",
                "server:CONFIG_UUID": "4adb7e6b-784e-46c3-896f-a2fcb7413bfb"
              }
            }
          ],
          "downPollingLimit": 2,
          "hub": "http://XXX.XXX.XXX.XXX:4444/grid/register",
          "nodePolling": 5000,
          "nodeStatusCheckTimeout": 5000,
          "proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy",
          "register": true,
          "registerCycle": 5000,
          "unregisterIfStillDownAfter": 60000,
          "enablePlatformVerification": true,
          "custom": {},
          "maxSession": 5,
          "servlets": [],
          "withoutServlets": [],
          "avoidProxy": false,
          "browserSideLog": false,
          "captureLogsOnQuit": false,
          "browserTimeout": 0,
          "debug": false,
          "host": "172.16.8.84",
          "port": 3753,
          "role": "node",
          "timeout": 1800
        }
      }
    }
  ]
}

/grid/admin/AllNodesState だと・・・

{
  "Nodes": [
    {
      "FreeNodes": [
        {
          "Node": {
            "configuration": {
              "remoteHost": "http://XXX.XXX.XXX.XXX:37803",
              "id": "http://XXX.XXX.XXX.XXX:37803",
              "capabilities": [
                {
                  "caps": {
                    "browserName": "chrome",
                    "maxInstances": 1,
                    "platform": "WINDOWS",
                    "platformName": "WINDOWS",
                    "seleniumProtocol": "WebDriver",
                    "server:CONFIG_UUID": "8405f2b4-e375-4251-a2e8-50e432eb24b8"
                  }
                }
              ],
              "downPollingLimit": 2,
              "hub": "http://XXX.XXX.XXX.XXX:4444/grid/register",
              "nodePolling": 5000,
              "nodeStatusCheckTimeout": 5000,
              "proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy",
              "register": true,
              "registerCycle": 5000,
              "unregisterIfStillDownAfter": 60000,
              "enablePlatformVerification": true,
              "custom": {},
              "maxSession": 5,
              "servlets": [],
              "withoutServlets": [],
              "avoidProxy": false,
              "browserSideLog": false,
              "captureLogsOnQuit": false,
              "browserTimeout": 0,
              "debug": false,
              "host": "172.16.11.134",
              "port": 37803,
              "role": "node",
              "timeout": 1800
            }
          }
        },
        {
          "Node": {
            "configuration": {
              "remoteHost": "http://XXX.XXX.XXX.XXX:3753",
              "id": "http://XXX.XXX.XXX.XXX:3753",
              "capabilities": [
                {
                  "caps": {
                    "browserName": "firefox",
                    "maxInstances": 2,
                    "platform": "WINDOWS",
                    "platformName": "WINDOWS",
                    "seleniumProtocol": "WebDriver",
                    "server:CONFIG_UUID": "61acf94e-1db8-4274-9587-1e32a0ebb378"
                  }
                },
                {
                  "caps": {
                    "browserName": "internet explorer",
                    "maxInstances": 1,
                    "platform": "WINDOWS",
                    "platformName": "WINDOWS",
                    "seleniumProtocol": "WebDriver",
                    "server:CONFIG_UUID": "4adb7e6b-784e-46c3-896f-a2fcb7413bfb"
                  }
                }
              ],
              "downPollingLimit": 2,
              "hub": "http://XXX.XXX.XXX.XXX:4444/grid/register",
              "nodePolling": 5000,
              "nodeStatusCheckTimeout": 5000,
              "proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy",
              "register": true,
              "registerCycle": 5000,
              "unregisterIfStillDownAfter": 60000,
              "enablePlatformVerification": true,
              "custom": {},
              "maxSession": 5,
              "servlets": [],
              "withoutServlets": [],
              "avoidProxy": false,
              "browserSideLog": false,
              "captureLogsOnQuit": false,
              "browserTimeout": 0,
              "debug": false,
              "host": "172.16.8.84",
              "port": 3753,
              "role": "node",
              "timeout": 1800
            }
          }
        }
      ]
    },
    {
      "BusyNodes": []
    }
  ]
}
busy状態について

予想外だったんですが、いくつかブラウザを動かせる設定でも、1つでもブラウザが動いていればそのNodeはbusyになるようです。
今回使用しているAPIをほとんど調べていないので、そもそも使い方間違えているのかもしれません:thinking:

例えばFirefoxが1つだけ動いている状態でも・・・
aWS050503.JPG

/grid/admin/AllNodesState を見るとBusyNodesに判定されています。

{
  "Nodes": [
    {
      "FreeNodes": [
        {
          "Node": {
            "configuration": {
              "remoteHost": "http://172.16.11.134:37803",
              "id": "http://172.16.11.134:37803",
              "capabilities": [
                {
                  "caps": {
                    "browserName": "chrome",
                    "maxInstances": 1,
                    "platform": "WINDOWS",
                    "platformName": "WINDOWS",
                    "seleniumProtocol": "WebDriver",
                    "server:CONFIG_UUID": "8405f2b4-e375-4251-a2e8-50e432eb24b8"
                  }
                }
              ],
              "downPollingLimit": 2,
              "hub": "http://goto-main:4444/grid/register",
              "nodePolling": 5000,
              "nodeStatusCheckTimeout": 5000,
              "proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy",
              "register": true,
              "registerCycle": 5000,
              "unregisterIfStillDownAfter": 60000,
              "enablePlatformVerification": true,
              "custom": {},
              "maxSession": 5,
              "servlets": [],
              "withoutServlets": [],
              "avoidProxy": false,
              "browserSideLog": false,
              "captureLogsOnQuit": false,
              "browserTimeout": 0,
              "debug": false,
              "host": "172.16.11.134",
              "port": 37803,
              "role": "node",
              "timeout": 1800
            }
          }
        }
      ]
    },
    {
      "BusyNodes": [
        {
          "Node": {
            "configuration": {
              "remoteHost": "http://172.16.8.84:3753",
              "id": "http://172.16.8.84:3753",
              "capabilities": [
                {
                  "caps": {
                    "browserName": "firefox",
                    "maxInstances": 2,
                    "platform": "WINDOWS",
                    "platformName": "WINDOWS",
                    "seleniumProtocol": "WebDriver",
                    "server:CONFIG_UUID": "61acf94e-1db8-4274-9587-1e32a0ebb378"
                  }
                },
                {
                  "caps": {
                    "browserName": "internet explorer",
                    "maxInstances": 1,
                    "platform": "WINDOWS",
                    "platformName": "WINDOWS",
                    "seleniumProtocol": "WebDriver",
                    "server:CONFIG_UUID": "4adb7e6b-784e-46c3-896f-a2fcb7413bfb"
                  }
                }
              ],
              "downPollingLimit": 2,
              "hub": "http://localhost:4444/grid/register",
              "nodePolling": 5000,
              "nodeStatusCheckTimeout": 5000,
              "proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy",
              "register": true,
              "registerCycle": 5000,
              "unregisterIfStillDownAfter": 60000,
              "enablePlatformVerification": true,
              "custom": {},
              "maxSession": 5,
              "servlets": [],
              "withoutServlets": [],
              "avoidProxy": false,
              "browserSideLog": false,
              "captureLogsOnQuit": false,
              "browserTimeout": 0,
              "debug": false,
              "host": "172.16.8.84",
              "port": 3753,
              "role": "node",
              "timeout": 1800
            }
          }
        }
      ]
    }
  ]
}