Selenium Grid启动过程——版本3.141.59

版本

3.141.59

入口类

org.openqa.grid.selenium.GridLauncherV3

相关启动代码:

public static void main(String[] args) {
   new GridLauncherV3().launch(args);
}

public Stoppable launch(String[] args) {
    return Optional.ofNullable(buildLauncher(args))
        .map(l -> l.launch(args))
        .orElse(()->{});
}

可见,实际上的启动过程分为两步:1.用main函数的外部参数创建启动器;2.用创建好的启动器运行launch方法进行启动。

创建启动器

 /**
   * From the {@code args}, builds a new {@link GridItemLauncher} and populates it properly.
   *
   * @return null if no role is found, or a properly populated {@link GridItemLauncher}.
   */
  private GridItemLauncher buildLauncher(String[] args) {
    if (Arrays.asList(args).contains("-htmlSuite")) {
      out.println(Joiner.on("\n").join(
          "Download the Selenium HTML Runner from http://www.seleniumhq.org/download/ and",
          "use that to run your HTML suite."));
      return null;
    }

    String role = "standalone";

    for (int i = 0; i < args.length; i++) {
      if (args[i].startsWith("-role=")) {
        role = args[i].substring("-role=".length());
      } else if (args[i].equals("-role")) {
        i++;  // Increment, because we're going to need this.
        if (i < args.length) {
          role = args[i];
        } else {
          role = null;  // Will cause us to print the usage information.
        }
      }
    }

    GridRole gridRole = GridRole.get(role);
    if (gridRole == null || LAUNCHERS.get(gridRole) == null) {
      printInfoAboutRoles(role);
      return null;
    }

    return LAUNCHERS.get(gridRole);
  }

这里解释下这段代码。
首先检查下-htmlSuite参数,如果有,那么会提示你去下载Selenium HTML Runner,然后退出启动过程。
如果没有-htmlSuite参数,接着就获取-role参数,通过判断字符串来转为枚举中的一种,默认判断为null,会退出启动过程。简单看下能实例化成哪几种类型:

public enum GridRole {
  NOT_GRID, HUB, NODE;

这三种分别代表了三种模式,即注册中心和节点合一模式、注册中心模式、节点模式。注册中心需要配合节点使用。
在返回时,返回了LAUNCHERS.get(gridRole)LAUNCHERS是由org.openqa.grid.selenium.GridLauncherV3#buildLaunchers初始化的,是一个ImmutableMap类型的Map。Size为3,key是GridRole的三个枚举成员,value是每个枚举对应的初始化方法。

启动

.put(GridRole.HUB, (args) -> {
          GridHubCliOptions options = new GridHubCliOptions();
          if (!parse(args, options, options.getCommonGridOptions().getCommonOptions())) {
            return ()->{};
          }

          GridHubConfiguration configuration = new GridHubConfiguration(options);
          configuration.setRawArgs(args); // for grid console

          log.info(String.format(
              "Launching Selenium Grid hub on port %s", configuration.port));
          Hub hub = new Hub(configuration);
          hub.start();
          return hub;
        })

这里介绍Hub的启动,第一个if检查命令中是否有version或者help,有的话就执行相应的打印提示操作后退出启动过程,否则继续执行。
继续执行的是初始化配置。这里涉及到一个配置类GridHubConfiguration。这个类会默认利用内部的DEFAULT_CONFIG_FROM_JSON静态成员变量初始化自己其他的成员变量。但如果在启动时通过参数-hubConfig传入配置文件,则利用传入的配置文件路径初始化某些成员变量。从该类中可知,默认配置文件在为org/openqa/grid/common/defaults/DefaultHub.json,在jar包的相关位置可以找到。自定义配置文件可以参考文件里的写法。
接着就通过给Hub传入配置参数,来进行启动工作:

 Hub hub = new Hub(configuration);
 hub.start();

new Hub

 public Hub(GridHubConfiguration gridHubConfiguration) {
    config = gridHubConfiguration == null ? new GridHubConfiguration() : gridHubConfiguration;

    try {
      registry = (GridRegistry) Class.forName(config.registry).newInstance();
      registry.setHub(this);
      registry.setThrowOnCapabilityNotPresent(config.throwOnCapabilityNotPresent);
    } catch (Throwable e) {
      throw new GridConfigurationException("Error creating class with " + config.registry +
                                           " : " + e.getMessage(), e);
    }

    if (config.host == null) {
      config.host = "0.0.0.0"; //default to all adapters
    }

    if (config.port == null) {
      config.port = 4444;
    }

    if (config.servlets != null) {
      for (String s : config.servlets) {
        Class<? extends Servlet> servletClass = ExtraServletUtil.createServlet(s);
        if (servletClass != null) {
          String path = "/grid/admin/" + servletClass.getSimpleName() + "/*";
          log.info("binding " + servletClass.getCanonicalName() + " to " + path);
          addServlet(path, servletClass);
        }
      }
    }

    // start the registry, now that 'config' is all setup
    registry.start();

    new JMXHelper().register(this);
  }

这里用配置中的registry反射实例化一个GridRegistry实例,并简单设置下必备参数。值得注意的是config.servlets的相关操作,这里相当于提示我们,如果还想给Hub节点拓展更多的Api接口,是如何进行初始化相关Servlet。注意,Selenium实际上是一个由内嵌Jetty管理的Server。

hub.start()

  public void start() {
    initServer();

    try {
      server.start();
    } catch (Exception e) {
      try {
        stop();
      } catch (Exception ignore) {
      }
      if (e instanceof BindException) {
        log.severe(String.format(
            "Port %s is busy, please choose a free port for the hub and specify it using -port option", config.port));
        return;
      } else {
        throw new RuntimeException(e);
      }
    }

    log.info("Selenium Grid hub is up and running");
    log.info(String.format("Nodes should register to %s", getRegistrationURL()));
    log.info(String.format("Clients should connect to %s", getWebDriverHubRequestURL()));
  }

其中org.openqa.grid.web.Hub#initServer中初始化一个QueuedThreadPool,这是Selenium自定义的一个用来管理任务线程的类。可以通过配置文件中的jettyMaxThreads参数来配置其线程池大小。利用线程池初始化一个org.seleniumhq.jetty9.server.Server实例。
这个过程中,有个比较重要的方法调用,org.openqa.grid.web.Hub#addDefaultServlets方法中将一些路径关联org.openqa.grid.web.servlet.RegistryBasedServlet的子类上。令Selenium Hub真正对外提供服务。

 private void addDefaultServlets(ServletContextHandler handler) {
    // add mandatory default servlets
    handler.addServlet(RegistrationServlet.class.getName(), "/grid/register/*");

    handler.addServlet(DriverServlet.class.getName(), "/wd/hub/*");
    handler.addServlet(DriverServlet.class.getName(), "/selenium-server/driver/*");

    handler.addServlet(ProxyStatusServlet.class.getName(), "/grid/api/proxy/*");
    handler.addServlet(NodeSessionsServlet.class.getName(), "/grid/api/sessions/*");

    handler.addServlet(HubStatusServlet.class.getName(), "/grid/api/hub/*");

    ServletHolder statusHolder = new ServletHolder(new HubW3CStatusServlet(getRegistry()));
    handler.addServlet(statusHolder, "/status");
    handler.addServlet(statusHolder, "/wd/hub/status");

    handler.addServlet(TestSessionStatusServlet.class.getName(), "/grid/api/testsession/*");

    // add optional default servlets
    if (!config.isWithOutServlet(ResourceServlet.class)) {
      handler.addServlet(ResourceServlet.class.getName(), "/grid/resources/*");
    }

    if (!config.isWithOutServlet(DisplayHelpServlet.class)) {
      handler.addServlet(DisplayHelpServlet.class.getName(), "/*");
      handler.setInitParameter(DisplayHelpServlet.HELPER_TYPE_PARAMETER, config.role);
    }

    if (!config.isWithOutServlet(ConsoleServlet.class)) {
      handler.addServlet(ConsoleServlet.class.getName(), "/grid/console/*");
      handler.setInitParameter(ConsoleServlet.CONSOLE_PATH_PARAMETER, "/grid/console");
    }

    if (!config.isWithOutServlet(LifecycleServlet.class)) {
      handler.addServlet(LifecycleServlet.class.getName(), "/lifecycle-manager/*");
    }

    if (!config.isWithOutServlet(Grid1HeartbeatServlet.class)) {
      handler.addServlet(Grid1HeartbeatServlet.class.getName(), "/heartbeat");
    }
  }

相关暴露的接口可以查询官方文档或者直接查看对应类中的处理方法。自定义接口可以参考它们的实现。

server.start()

 public final void start() throws Exception {
        synchronized(this._lock) {
            try {
                if (this._state == 2 || this._state == 1) {
                    return;
                }

                this.setStarting();
                this.doStart();
                this.setStarted();
            } catch (Throwable var4) {
                this.setFailed(var4);
                throw var4;
            }

        }
    }

这里都是Jetty的启动内容。

启动完成

当控制台看到“Selenium Grid hub is up and running”输出时,整个Hub节点就启动成功。

16:41:16.705 INFO [Hub.start] - Selenium Grid hub is up and running
16:41:16.740 INFO [Hub.start] - Nodes should register to http://******:5555/grid/register/
16:41:16.773 INFO [Hub.start] - Clients should connect to http://******:5555/wd/hub

Node应向第一条Url( http://******:5555/grid/register/)注册,使用WebDriver的客户端应向第二条Url(http://******:5555/wd/hub)连接。

总结

其他两种——Standalone和Node启动逻辑类似,跟着Hub的启动逻辑也可以看到它们的启动逻辑。
总之,Selenium Grid的启动依靠传入参数以及Json文件配置来完成启动流程。并且有默认的Json配置文件。可以自定义Sevlet添加扩展接口。
这就是Selenium启动过程能告诉我们的信息。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值