Selenium Grid 允许通过将客户端发送的命令路由到远程浏览器实例来在远程机器上执行 WebDriver 脚本。
Grid 的目标:
- 提供一种在多台机器上并行运行测试的简单方法
- 允许在不同的浏览器版本上进行测试
- 启用跨平台测试
感兴趣? 通过以下部分了解Grid的工作原理, 以及如何设置自己的.
Selenium Grid 允许通过将客户端发送的命令路由到远程浏览器实例来在远程机器上执行 WebDriver 脚本。
Grid 的目标:
感兴趣? 通过以下部分了解Grid的工作原理, 以及如何设置自己的.
Page being translated from English to Chinese. Do you speak Chinese? Help us to translate it by sending us pull requests!
PATH
.PATH
java -jar selenium-server-<version>.jar standalone
*Wondering how to point your tests to http://localhost:4444?
Check the RemoteWebDriver
section.
To learn more about the different configuration options, go through the sections below.
Grid is composed by six different components, which gives you the option to deploy it in different ways.
Depending on your needs, you can start each one of them on its own (Distributed), group them in Hub & Node, or all in one on a single machine (Standalone).
Standalone combines all Grid components seamlessly into one. Running a Grid in Standalone mode gives you a fully functional Grid with a single command, within a single process. Standalone can only run on a single machine.
Standalone is also the easiest mode to spin up a Selenium Grid. By default, the server
will listen for RemoteWebDriver
requests on http://localhost:4444.
By default, the server will detect the available drivers that it can use from the System
PATH
.
java -jar selenium-server-<version>.jar standalone
After starting successfully the Grid in Standalone mode, point your WebDriver tests to http://localhost:4444.
Common use cases for Standalone are:
RemoteWebDriver
locallyHub and Node is the most used role because it allows to:
A Hub is composed by the following components: Router, Distributor, Session Map, New Session Queue, and Event Bus.
java -jar selenium-server-<version>.jar hub
By default, the server will listen for RemoteWebDriver
requests on http://localhost:4444.
During startup time, the Node will detect the available drivers that it can use from the System
PATH
.
The command below assumes the Node is running on the same machine where the Hub is running.
java -jar selenium-server-<version>.jar node
Node 1
java -jar selenium-server-<version>.jar node --port 5555
Node 2
java -jar selenium-server-<version>.jar node --port 6666
Hub and Nodes talk to each other via HTTP and the Event Bus (the Event Bus lives inside the Hub). A Node sends a message to the Hub via the Event Bus to start the registration process. When the Hub receives the message, reaches out to the Node via HTTP to confirm its existence.
To successfully register a Node to a Hub, it is important to expose the Event Bus ports (4442 and 4443 by default) on the Hub machine. This also applies for the Node port. With that, both Hub and Node will be able to communicate.
If the Hub is using the default ports, the --hub
flag can be used to register the Node
java -jar selenium-server-<version>.jar node --hub http://<hub-ip>:4444
When the Hub is not using the default ports, the --publish-events
and --subscribe-events
flags are needed.
For example, if the Hub uses ports 8886
, 8887
, and 8888
java -jar selenium-server-<version>.jar hub --publish-events tcp://<hub-ip>:8886 --subscribe-events tcp://<hub-ip>:8887 --port 8888
The Node needs to use those ports to register successfully
java -jar selenium-server-<version>.jar node --publish-events tcp://<hub-ip>:8886 --subscribe-events tcp://<hub-ip>:8887
When using a Distributed Grid, each component is started separately, and ideally on different machines.
Default ports are: 4442
, 4443
, and 5557
.
java -jar selenium-server-<version>.jar event-bus --publish-events tcp://<event-bus-ip>:4442 --subscribe-events tcp://<event-bus-ip>:4443 --port 5557
Default port is 5559
.
java -jar selenium-server-<version>.jar sessionqueue --port 5559
Default Session Map port is 5556
. Session Map interacts with the Event Bus.
java -jar selenium-server-<version>.jar sessions --publish-events tcp://<event-bus-ip>:4442 --subscribe-events tcp://<event-bus-ip>:4443 --port 5556
Default Distributor port is 5553
. Distributor interacts with New Session Queue, Session Map, Event Bus, and the Node(s).
java -jar selenium-server-<version>.jar distributor --publish-events tcp://<event-bus-ip>:4442 --subscribe-events tcp://<event-bus-ip>:4443 --sessions http://<sessions-ip>:5556 --sessionqueue http://<new-session-queue-ip>:5559 --port 5553 --bind-bus false
Default Router port is 4444
. Router interacts with New Session Queue, Session Map, and Distributor.
java -jar selenium-server-<version>.jar router --sessions http://<sessions-ip>:5556 --distributor http://<distributor-ip>:5553 --sessionqueue http://<new-session-queue-ip>:5559 --port 4444
Default Node port is 5555
.
java -jar selenium-server-<version>.jar node --publish-events tcp://<event-bus-ip>:4442 --subscribe-events tcp://<event-bus-ip>:4443
Add metadata to your tests and consume it via GraphQL
or visualize parts of it (like se:name
) through the Selenium Grid UI.
Metadata can be added by prefixing a capability with se:
. Here is a quick example in Java showing that.
ChromeOptions chromeOptions = new ChromeOptions();
chromeOptions.setCapability("browserVersion", "100");
chromeOptions.setCapability("platformName", "Windows");
// Showing a test name instead of the session id in the Grid UI
chromeOptions.setCapability("se:name", "My simple test");
// Other type of metadata can be seen in the Grid UI by clicking on the
// session info or via GraphQL
chromeOptions.setCapability("se:sampleMetadata", "Sample metadata value");
WebDriver driver = new RemoteWebDriver(new URL("http://gridUrl:4444"), chromeOptions);
driver.get("http://www.google.com");
driver.quit();
After starting a Grid, there are mainly two ways of querying its status, through the Grid UI or via an API call.
The Grid UI can be reached by opening your preferred browser and heading to http://localhost:4444.
API calls can be done through the http://localhost:4444/status endpoint or using GraphQL
For simplicity, all command examples shown in this page assume that components are running locally. More detailed examples and usages can be found in the Configuring Components section.
By default, Grid will use AsyncHttpClient. AsyncHttpClient is an open-source library built on top of Netty. It allows the execution of HTTP requests and responses asynchronously. Additionally it also provides WebSocket support. Hence it is a good fit.
However, AsyncHttpClient is not been actively maintained since June 2021. It coincides with the fact that Java 11+ provides a built-in HTTP and WebSocket client. Currently, Selenium has plans to upgrade the minimum version supported to Java 11. However, it is a sizeable effort. Aligning it with major releases and accompanied announcements is crucial to ensure the user experience is intact.
To do use the Java 11 client, you will need to download the selenium-http-jdk-client
jar file
and use the --ext
flag to make it available in the Grid jar’s classpath.
The jar file can be downloaded directly from repo1.maven.org and then start the Grid in the following way:
java -Dwebdriver.http.factory=jdk-http-client -jar selenium-server-<version>.jar -—ext selenium-http-jdk-client-<version>.jar standalone
An alternative to downloading the selenium-http-jdk-client
jar file is to use Coursier.
java -Dwebdriver.http.factory=jdk-http-client -jar selenium-server-<version>.jar —-ext $(coursier fetch -p org.seleniumhq.selenium:selenium-http-jdk-client:<version>) standalone
If you are using the Hub/Node(s) mode or the Distributed mode, setting the -Dwebdriver.http.factory=jdk-http-client
and —-ext
flags needs to be done for each one of the components.
Choosing a Grid role depends on what operating systems and browsers need to be supported, how many parallel sessions need to be executed, the amount of available machines, and how powerful (CPU, RAM) those machines are.
Creating sessions concurrently relies on the available processors to the Distributor. For example, if a machine has 4 CPUs, the Distributor will only be able to create up to 4 sessions concurrently.
By default, the maximum amount of concurrent sessions a Node supports is limited by the number of CPUs available. For example, if the Node machine has 8CPUs, it can run up to 8 concurrent browser sessions (with the exception of Safari, which is always one). Additionally, it is expected that each browser session should use around 1GB RAM.
In general, it is a recommended to have Nodes as small as possible. Instead of having a machine with 32CPUs and 32GB RAM to run 32 concurrent browser sessions, it is better to have 32 small Nodes in order to better isolate processes. With this, if a Node fails, it will do it in an isolated way. Docker is a good tool to achieve this approach.
Note that the default values (1CPU/1GB RAM per browser) are a recommendation and they could not apply to your context. It is recommended to use them as a reference, but measuring performance continuously will help to determine the ideal values for your environment.
Grid sizes are relative to the amount of supported concurrent sessions and amount of Nodes, and there is no “one size fits all”. Sizes mentioned below are rough estimations thay can vary between different environments. For example a Hub/Node with 120 Nodes might work well when the Hub has enough resources. Values below are not set on stone, and feedback is welcomed!
Standalone or Hub/Node with 5 or less Nodes.
Hub/Node between 6 and 60 Nodes.
Hub/Node between 60 and 100 Nodes. Distributed with over 100 Nodes.
Selenium Grid must be protected from external access using appropriate firewall permissions.
Failure to protect your Grid could result in one or more of the following occurring:
See this blog post on Detectify, which gives a good overview of how a publicly exposed Grid could be misused: Don’t Leave your Grid Wide Open
Page being translated from English to Chinese. Do you speak Chinese? Help us to translate it by sending us pull requests!
When would you use a Selenium Grid?
Selenium Grid runs test suites in parallel against multiple machines (called Nodes). For large and long-running test suites, this can save minutes, hours, or perhaps days. This shortens the turnaround time for test results as your application under test (AUT) changes.
Grid can run tests (in parallel) against multiple different browsers, and it can run against multiple instances of the same browser. As an example, let’s imagine a Grid with six Nodes. The first machine has Firefox’s latest version, the second has Firefox “latest minus one”, the third gets the latest Chrome, and the remaining three machines are Mac Minis, which allows for three tests to run in parallel on the latest version of Safari.
Execution time can be expressed as a simple formula:
Number of Tests * Average Test Time / Number of Nodes = Total Execution Time
15 * 45s / 1 = 11m 15s // Without Grid
15 * 45s / 5 = 2m 15s // Grid with 5 Nodes
15 * 45s / 15 = 45s // Grid with 15 Nodes
100 * 120s / 15 = 13m 20s // Would take over 3 hours without Grid
As the test suite is executing, the Grid allocates the tests to run against these browsers as configured in the tests.
A configuration such as this can greatly speed up the execution time of even the largest Selenium test suites.
Selenium Grid is a completely native part of the Selenium project, and is maintained in parallel by the same team of committers who work in the core Selenium development. Recognizing the importance of test execution speed, Grid has been a critical part of the Selenium project since the earliest days.
Page being translated from English to Chinese. Do you speak Chinese? Help us to translate it by sending us pull requests!
Selenium Grid 4 is a ground-up rewrite from previous versions. In addition to a comprehensive set of improvements to performance and standards compliance, the different functions of the grid were broken out to reflect a more modern age of computing and software development. Purpose-build for containerization and cloud-distributed scalability, Selenium Grid 4 is a wholly new solution for the modern era.
The Router is the entry point of the Grid, receiving all external requests, and forwards them to the correct component.
If the Router receives a new session request, it will be forwarded to the New Session Queue.
If the request belongs to an existing session, the Router will query the Session Map to get the Node ID where the session is running, and then the request will be forwarded directly to the Node.
The Router balances the load in the Grid by sending the requests to the component that is able to handle them better, without overloading any component that is not needed in the process.
The Distributor has two main responsibilities:
A Node registers to the Distributor by sending a Node registration event through the Event Bus. The Distributor reads it, and then tries to reach the Node via HTTP to confirm its existance. If the request is successfull, the Distributor registers the Node and keeps track of all Nodes capabilities through the GridModel.
When a new session request is sent to the Router, it gets forwarded to the New Session Queue, where it will wait in the queue. The Distributor will poll the New Session Queue for pending new session requests, and then finds a suitable Node where the session can be created. After the session has been created, the Distributor stores in the Session Map the relation between the session id and Node where the session is being executed.
The Session Map is a data store that keeps the relationship between the session id and the Node where the session is running. It supports the Router in the process of forwarding a request to the Node. The Router will ask the Session Map for the Node associated to a session id.
The New Session Queue holds all the new session requests in a FIFO order. It has configurable parameters for setting the request timeout and request retry interval (how often the timeout will be checked).
The Router adds the new session request to the New Session Queue and waits for the response. The New Session Queue regularly checks if any request in the queue has timed out, if so the request is rejected and removed immediately.
The Distributor regularly checks if a slot is available. If so, the Distributor polls the New Session Queue for the first matching request. The Distributor then attempts to create a new session.
Once the requested capabilities match the capabilities of any of the free Node slots, the Distributor attempts to get the available slot. If all the slots are busy, the Distributor will send the request back to the queue. If request times out while retrying or adding to the front of the queue, it will be rejected.
After a session is created successfully, the Distributor sends the session information to the New Session Queue, which then gets sent back to the Router, and finally to the client.
A Grid can contain multiple Nodes. Each Node manages the slots for the available browsers of the machine where it is running.
The Node registers itself to the Distributor through the Event Bus, and its configuration is sent as part of the registration message.
By default, the Node auto-registers all browser drivers available on the path of the machine where it runs. It also creates one slot per available CPU for Chromium based browsers and Firefox. For Safari, only one slot is created. Through a specific configuration, it can run sessions in Docker containers or relay commands.
A Node only executes the received commands, it does not evaluate, make judgments, or control anything other than the flow of commands and responses. The machines where the Node is running does not need to have the same operating system as the other components. For example, A Windows Node might have the capability of offering IE Mode on Edge as a browser option, whereas this would not be possible on Linux or Mac, and a Grid can have multiple Nodes configured with Windows, Mac, or Linux.
The Event Bus serves as a communication path between the Nodes, Distributor, New Session Queue, and Session Map. The Grid does most of its internal communication through messages, avoiding expensive HTTP calls. When starting the Grid in its fully distributed mode, the Event Bus is the first component that should be started.
Help命令显示基于当前代码实现的信息. 因此, 如果文档没有更新, 它将提供准确的信息. 这是了解任何新版本Grid4配置的最便捷方法.
Info命令提供以下主题的详细文档:
通过运行以下命令快速获取配置帮助:
java -jar selenium-server-<version>.jar info config
获取构建网格服务器的详细信息, 用于安全通信和节点注册.
java -jar selenium-server-<version>.jar info security
默认情况下, 网格使用本地会话表来存储会话信息. 网格支持额外的存储选项, 比如Redis和JDBC-SQL支持的数据库. 要设置不同的会话存储, 请使用以下命令获取设置步骤:
java -jar selenium-server-<version>.jar info sessionmap
默认情况下, 追踪是启用的. 要通过Jaeger导出追踪并将其可视化, 请使用以下命令进行说明:
java -jar selenium-server-<version>.jar info tracing
java -jar selenium-server-<version>.jar --config-help
上述命令将显示所有可用的命令及其描述.
在Selenium后面键入–help的配置选项, 以获取特定组件的配置信息.
java -jar selenium-server-<version>.jar standalone --help
java -jar selenium-server-<version>.jar hub --help
java -jar selenium-server-<version>.jar sessions --help
java -jar selenium-server-<version>.jar sessionqueue --help
java -jar selenium-server-<version>.jar distributor --help
java -jar selenium-server-<version>.jar router --help
java -jar selenium-server-<version>.jar node --help
Page being translated from English to Chinese. Do you speak Chinese? Help us to translate it by sending us pull requests!
Different sections are available to configure a Grid. Each section has options can be configured through command line arguments.
A complete description of the component to section mapping can be seen below.
Note that this documentation could be outdated if an option was modified or added but has not been documented yet. In case you bump into this situation, please check the “Config help” section and feel free to send us a pull request updating this page.
Standalone | Hub | Node | Distributor | Router | Sessions | SessionQueue | |
---|---|---|---|---|---|---|---|
Distributor | |||||||
Docker | |||||||
Events | |||||||
Logging | |||||||
Network | |||||||
Node | |||||||
Router | |||||||
Relay | |||||||
Server | |||||||
SessionQueue | |||||||
Sessions |
Option | Type | Value/Example | Description |
---|---|---|---|
--healthcheck-interval | int | 120 | How often, in seconds, will the health check run for all Nodes. This ensures the server can ping all the Nodes successfully. |
--distributor | uri | http://localhost:5553 | Url of the distributor. |
--distributor-host | string | localhost | Host on which the distributor is listening. |
--distributor-implementation | string | org.openqa.selenium.grid.distributor.local.LocalDistributor | Full class name of non-default distributor implementation |
--distributor-port | int | 5553 | Port on which the distributor is listening. |
--reject-unsupported-caps | boolean | false | Allow the Distributor to reject a request immediately if the Grid does not support the requested capability. Rejecting requests immediately is suitable for a Grid setup that does not spin up Nodes on demand. |
--slot-matcher | string | org.openqa.selenium.grid.data.DefaultSlotMatcher | Full class name of non-default slot matcher to use. This is used to determine whether a Node can support a particular session. |
--slot-selector | string | org.openqa.selenium.grid.distributor.selector.DefaultSlotSelector | Full class name of non-default slot selector. This is used to select a slot in a Node once the Node has been matched. |
Option | Type | Value/Example | Description |
---|---|---|---|
--docker-assets-path | string | /opt/selenium/assets | Absolute path where assets will be stored |
--docker- | string[] | selenium/standalone-firefox:latest '{"browserName": "firefox"}' | Docker configs which map image name to stereotype capabilities (example `-D selenium/standalone-firefox:latest ‘{“browserName”: “firefox”}’) |
--docker-devices | string[] | /dev/kvm:/dev/kvm | Exposes devices to a container. Each device mapping declaration must have at least the path of the device in both host and container separated by a colon like in this example: /device/path/in/host:/device/path/in/container |
--docker-host | string | localhost | Host name where the Docker daemon is running |
--docker-port | int | 2375 | Port where the Docker daemon is running |
--docker-url | string | http://localhost:2375 | URL for connecting to the Docker daemon |
--docker-video-image | string | selenium/video:latest | Docker image to be used when video recording is enabled |
Option | Type | Value/Example | Description |
---|---|---|---|
--bind-bus | boolean | false | Whether the connection string should be bound or connected. When true, the component will be bound to the Event Bus (as in the Event Bus will also be started by the component, typically by the Distributor and the Hub). When false, the component will connect to the Event Bus. |
--events-implementation | string | org.openqa.selenium.events.zeromq.ZeroMqEventBus | Full class name of non-default event bus implementation |
--publish-events | string | tcp://*:4442 | Connection string for publishing events to the event bus |
--subscribe-events | string | tcp://*:4443 | Connection string for subscribing to events from the event bus |
Option | Type | Value/Example | Description |
---|---|---|---|
--http-logs | boolean | false | Enable http logging. Tracing should be enabled to log http logs. |
--log-encoding | string | UTF-8 | Log encoding |
--log | string | Windows path example :'\path\to\file\gridlog.log' or 'C:\path\path\to\file\gridlog.log' Linux/Unix/MacOS path example : '/path/to/file/gridlog.log' | File to write out logs. Ensure the file path is compatible with the operating system’s file path. |
--log-level | string | “INFO” | Log level. Default logging level is INFO. Log levels are described here https://docs.oracle.com/javase/7/docs/api/java/util/logging/Level.html |
--plain-logs | boolean | true | Use plain log lines |
--structured-logs | boolean | false | Use structured logs |
--tracing | boolean | true | Enable trace collection |
--log-timestamp-format | string | HH:mm:ss.SSS | Allows the configure log timestamp format |
Option | Type | Value/Example | Description |
---|---|---|---|
--relax-checks | boolean | false | Relax checks on origin header and content type of incoming requests, in contravention of strict W3C spec compliance. |
Option | Type | Value/Example | Description | |
---|---|---|---|---|
--detect-drivers | boolean | true | Autodetect which drivers are available on the current system, and add them to the Node. | |
--driver-configuration | string[] | display-name="Firefox Nightly" max-sessions=2 webdriver-path="/usr/local/bin/geckodriver" stereotype='{"browserName": "firefox", "browserVersion": "86", "moz:firefoxOptions": {"binary":"/Applications/Firefox Nightly.app/Contents/MacOS/firefox-bin"}}' | List of configured drivers a Node supports. It is recommended to provide this type of configuration through a toml config file to improve readability | |
--driver-factory | string[] | org.openqa.selenium.example.LynxDriverFactory '{"browserName": "lynx"}' | Mapping of fully qualified class name to a browser configuration that this matches against. | |
--driver-implementation | string[] | "firefox" | Drivers that should be checked. If specified, will skip autoconfiguration. | |
--node-implementation | string | "org.openqa.selenium.grid.node.local.LocalNodeFactory" | Full classname of non-default Node implementation. This is used to manage a session’s lifecycle. | |
--grid-url | string | https://grid.example.com | Public URL of the Grid as a whole (typically the address of the Hub or the Router) | |
--heartbeat-period | int | 60 | How often, in seconds, will the Node send heartbeat events to the Distributor to inform it that the Node is up. | |
--max-sessions | int | 8 | Maximum number of concurrent sessions. Default value is the number of available processors. | |
--override-max-sessions | boolean | false | The # of available processors is the recommended max sessions value (1 browser session per processor). Setting this flag to true allows the recommended max value to be overwritten. Session stability and reliability might suffer as the host could run out of resources. | |
--register-cycle | int | 10 | How often, in seconds, the Node will try to register itself for the first time to the Distributor. | |
--register-period | int | 120 | How long, in seconds, will the Node try to register to the Distributor for the first time. After this period is completed, the Node will not attempt to register again. | |
--session-timeout | int | 300 | Let X be the session-timeout in seconds. The Node will automatically kill a session that has not had any activity in the last X seconds. This will release the slot for other tests. | |
--vnc-env-var | string | START_XVFB | Environment variable to check in order to determine if a vnc stream is available or not. | |
--no-vnc-port | int | 7900 | If VNC is available, sets the port where the local noVNC stream can be obtained | |
--drain-after-session-count | int | 1 | Drain and shutdown the Node after X sessions have been executed. Useful for environments like Kubernetes. A value higher than zero enables this feature. | |
--hub | string | http://localhost:4444 | The address of the Hub in a Hub-and-Node configuration. Can be a hostname or IP address (hostname ), in which case the Hub will be assumed to be http://hostname:4444 , the --grid-url will be the same --publish-events will be tcp://hostname:4442 and --subscribe-events will be tcp://hostname:4443 . If hostname contains a port number, that will be used for --grid-url but the URIs for the event bus will remain the same. Any of these default values may be overridden but setting the correct flags. If the hostname has a protocol (such as https ) that will be used too. | |
--enable-cdp | boolean | true | Enable CDP proxying in Grid. A Grid admin can disable CDP if the network doesnot allow websockets. True by default. | |
--downloads-path | string | /usr/downloads | The default location wherein all browser triggered file downloads would be available to be retrieved from. This is usually the directory that you configure in your browser as the default location for storing downloaded files. |
Option | Type | Value/Example | Description |
---|---|---|---|
--service-url | string | http://localhost:4723 | URL for connecting to the service that supports WebDriver commands like an Appium server or a cloud service. |
--service-host | string | localhost | Host name where the service that supports WebDriver commands is running |
--service-port | int | 4723 | Port where the service that supports WebDriver commands is running |
--service-status-endpoint | string | /status | Optional, endpoint to query the WebDriver service status, an HTTP 200 response is expected |
--service-configuration | string[] | max-sessions=2 stereotype='{"browserName": "safari", "platformName": "iOS", "appium:platformVersion": "14.5"}}' | Configuration for the service where calls will be relayed to. It is recommended to provide this type of configuration through a toml config file to improve readability. |
Option | Type | Value/Example | Description |
---|---|---|---|
--password | string | myStrongPassword | Password clients must use to connect to the server. Both this and the username need to be set in order to be used. |
--username | string | admin | User name clients must use to connect to the server. Both this and the password need to be set in order to be used. |
Option | Type | Value/Example | Description |
---|---|---|---|
--allow-cors | boolean | true | Whether the Selenium server should allow web browser connections from any host |
--host | string | localhost | Server IP or hostname: usually determined automatically. |
--bind-host | boolean | true | Whether the server should bind to the host address/name, or only use it to" report its reachable url. Helpful in complex network topologies where the server cannot report itself with the current IP/hostname but rather an external IP or hostname (e.g. inside a Docker container) |
--https-certificate | path | /path/to/cert.pem | Server certificate for https. Get more detailed information by running “java -jar selenium-server.jar info security” |
--https-private-key | path | /path/to/key.pkcs8 | Private key for https. Get more detailed information by running “java -jar selenium-server.jar info security” |
--max-threads | int | 24 | Maximum number of listener threads. Default value is: (available processors) * 3. |
--port | int | 4444 | Port to listen on. There is no default as this parameter is used by different components, for example, Router/Hub/Standalone will use 4444 and Node will use 5555. |
Option | Type | Value/Example | Description |
---|---|---|---|
--sessionqueue | uri | http://localhost:1237 | Address of the session queue server. |
-sessionqueue-host | string | localhost | Host on which the session queue server is listening. |
--sessionqueue-port | int | 1234 | Port on which the session queue server is listening. |
--session-request-timeout | int | 300 | Timeout in seconds. A new incoming session request is added to the queue. Requests sitting in the queue for longer than the configured time will timeout. |
--session-retry-interval | int | 5 | Retry interval in seconds. If all slots are busy, new session request will be retried after the given interval. |
Option | Type | Value/Example | Description |
---|---|---|---|
--sessions | uri | http://localhost:1234 | Address of the session map server. |
--sessions-host | string | localhost | Host on which the session map server is listening. |
--sessions-port | int | 1234 | Port on which the session map server is listening. |
All the options mentioned above can be used when starting the Grid components. They are a good way of exploring the Grid options, and trying out values to find a suitable configuration.
We recommend the use of Toml files to configure a Grid. Configuration files improve readability, and you can also check them in source control.
When needed, you can combine a Toml file configuration with CLI arguments.
To pass config options as command-line flags, identify the valid options for the component and follow the template below.
java -jar selenium-server-<version>.jar <component> --<option> value
java -jar selenium-server-<version>.jar standalone --max-sessions 4 --port 4444
java -jar selenium-server-<version>.jar hub --session-request-timeout 500 --port 3333 --tracing false
java -jar selenium-server-<version>.jar node --max-sessions 4 --log-level "fine" --port 7777 --driver-implementation "firefox" --driver-implementation "edge"
java -jar selenium-server-<version>.jar distributor --sessions http://localhost:5556 --sessionqueue http://localhost:5559 --bind-bus false
Important: Custom capabilities need to be set in the configuration in all Nodes. They also need to be included always in every session request.
java -jar selenium-server-<version>.jar hub
true
java -jar selenium-server-<version>.jar node --detect-drivers false --driver-configuration display-name="Chrome (custom capability true)" max-sessions=1 stereotype='{"browserName":"chrome","gsg:customcap":true}' --port 6161
false
java -jar selenium-server-<version>.jar node --detect-drivers false --driver-configuration display-name="Chrome (custom capability true)" max-sessions=1 stereotype='{"browserName":"chrome","gsg:customcap":false}' --port 6262
ChromeOptions options = new ChromeOptions();
options.setCapability("gsg:customcap", true);
WebDriver driver = new RemoteWebDriver(new URL("http://localhost:4444"), options);
driver.get("https://selenium.dev");
driver.quit();
Set the custom capability to false
in order to match the Node B.
At times a test may need to access files that were downloaded by it on the Node. To retrieve such files, following can be done.
java -jar selenium-server-<version>.jar hub
java -jar selenium-server-<version>.jar node --downloads-path /usr/downloads
GET
from is /session/<sessionId>/se/file?filename=
filename
- Same as what was specified in the request.contents
- Base64 encoded zipped contents of the file.Assuming the downloaded file is named my_file.pdf
, and using curl
, the
file could be downloaded with the following command:
curl -X GET "http://localhost:4444/session/<sessionId>/se/file?filename=my_file.pdf"
Below is an example in Java that shows how to download a file named my_file.pdf
.
import static org.openqa.selenium.remote.http.Contents.string;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.io.Zip;
import org.openqa.selenium.json.Json;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.remote.http.HttpClient;
import org.openqa.selenium.remote.http.HttpMethod;
import org.openqa.selenium.remote.http.HttpRequest;
import org.openqa.selenium.remote.http.HttpResponse;
public class DownloadsSample {
public static void main(String[] args) throws InterruptedException, IOException {
// Make sure the following directory exists on your machine
File dirToCopyTo = new File("/usr/downloads/file");
// Assuming the Grid is running locally.
URL gridUrl = new URL("http://localhost:4444");
RemoteWebDriver driver = new RemoteWebDriver(gridUrl, firefoxOptions());
// This public website resets the available files for dowload on a daily basis,
// check the name of the file that will be downloaded and replace it below.
driver.get("http://the-internet.herokuapp.com/download");
WebElement element = driver.findElement(By.cssSelector(".example a"));
element.click();
// The download happens in a remote Node, which makes difficult to know when the file
// has been completely downloaded. For demonstration purposes, this example uses a
// 10 second sleep which should be enough time for a file to be downloaded.
// We strongly recommend to avoid hardcoded sleeps, and ideally, to modify your
// application under test so it offers a way to know when the file has been completely
// downloaded.
TimeUnit.SECONDS.sleep(10);
HttpRequest request = new HttpRequest(HttpMethod.GET, String.format("/session/%s/se/file", driver.getSessionId()));
request.addQueryParameter("filename", "my_file.pdf");
try (HttpClient client = HttpClient.Factory.createDefault().createClient(gridUrl)) {
HttpResponse response = client.execute(request);
Map<String, Object> map = new Json().toType(string(response), Json.MAP_TYPE);
// The returned map would contain 2 keys,
// filename - This represents the name of the file (same as what was provided by the test)
// contents - Base64 encoded String which contains the zipped file.
String encodedContents = map.get("contents").toString();
//The file contents would always be a zip file and has to be unzipped.
Zip.unzip(encodedContents, dirToCopyTo);
} finally {
driver.quit();
}
}
private static FirefoxOptions firefoxOptions() {
FirefoxOptions options = new FirefoxOptions();
// Options specific for Firefox to avoid prompting a dialog for downloads. They might
// change in the future, so please refer to the Firefox documentation for up to date details.
options.addPreference("browser.download.manager.showWhenStarting", false);
options.addPreference("browser.helperApps.neverAsk.saveToDisk",
"images/jpeg, application/pdf, application/octet-stream");
options.addPreference("pdfjs.disabled", true);
return options;
}
}
CLI选项 中 显示的所有选项都可以通过 TOML 文件进行配置. 此页面显示不同Grid组件的配置示例.
请注意, 如果修改或添加了选项, 但尚未记录, 则此文档可能已过时. 如果您遇到这种情况, 请查看 “配置帮助” 部分, 并随时向我们发送更新此页面的请求.
Selenium Grid对配置文件使用 TOML 格式. 配置文件由多个部分组成, 每个部分都有选项及其各自的值.
有关详细的使用指南, 请参阅TOML文档 . 如果出现解析错误, 请使用 TOML linter 验证配置.
一般配置结构具有以下模式:
[section1]
option1="value"
[section2]
option2=["value1","value2"]
option3=true
下面是一些使用Toml文件配置的 Grid组件示例, 该组件可以 从下面的方式开始:
java -jar selenium-server-<version>.jar <component> --config /path/to/file/<file-name>.toml
单机服务器, 在端口4449上运行, 新会话请求超时500秒.
[server]
port = 4449
[sessionqueue]
session-request-timeout = 500
默认情况下仅启用Firefox 和Chrome的单机服务器或节点.
[node]
drivers = ["chrome", "firefox"]
max-sessions = 3
具有定制驱动程序的单机或节点服务器, 允许使用Firefox试用或者每日构建的功能, 并且有不同的浏览器版本.
[node]
detect-drivers = false
[[node.driver-configuration]]
max-sessions = 100
display-name = "Firefox Nightly"
stereotype = "{\"browserName\": \"firefox\", \"browserVersion\": \"93\", \"platformName\": \"MAC\", \"moz:firefoxOptions\": {\"binary\": \"/Applications/Firefox Nightly.app/Contents/MacOS/firefox-bin\"}}"
[[node.driver-configuration]]
display-name = "Chrome Beta"
stereotype = "{\"browserName\": \"chrome\", \"browserVersion\": \"94\", \"platformName\": \"MAC\", \"goog:chromeOptions\": {\"binary\": \"/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta\"}}"
[[node.driver-configuration]]
display-name = "Chrome Dev"
stereotype = "{\"browserName\": \"chrome\", \"browserVersion\": \"95\", \"platformName\": \"MAC\", \"goog:chromeOptions\": {\"binary\": \"/Applications/Google Chrome Dev.app/Contents/MacOS/Google Chrome Dev\"}}"
webdriver-executable = '/path/to/chromedriver/95/chromedriver'
单机或节点服务器能够在Docker容器中运行每个新会话.
禁用驱动程序检测,
则最多有2个并发会话.
原型配置需要映射一个Docker映像,
Docker的守护进程需要通过http/tcp公开.
此外,可以通过 devices
属性定义在主机上可访问的哪些设备文件将在容器中可用。
有关 docker 设备映射如何工作的更多信息,请参阅 docker 文档。
[node]
detect-drivers = false
max-sessions = 2
[docker]
configs = [
"selenium/standalone-chrome:93.0", "{\"browserName\": \"chrome\", \"browserVersion\": \"91\"}",
"selenium/standalone-firefox:92.0", "{\"browserName\": \"firefox\", \"browserVersion\": \"92\"}"
]
#Optionally define all device files that should be mapped to docker containers
#devices = [
# "/dev/kvm:/dev/kvm"
#]
url = "http://localhost:2375"
video-image = "selenium/video:latest"
连接到支持WebDriver外部服务 的Selenium Grid非常有用. 这种服务的一个例子可以是 云提供商或Appium服务器. 这样, Grid可以实现对本地不存在的平台和版本的更多覆盖.
下面是一个将Appium服务器连接到Grid的示例.
[node]
detect-drivers = false
[relay]
# Default Appium/Cloud server endpoint
url = "http://localhost:4723/wd/hub"
status-endpoint = "/status"
# Stereotypes supported by the service. The initial number is "max-sessions", and will allocate
# that many test slots to that particular configuration
configs = [
"5", "{\"browserName\": \"chrome\", \"platformName\": \"android\", \"appium:platformVersion\": \"11\"}"
]
通过配置包含用户名和密码的 路由器/集线器/单机的方式, 可以使用这样的基本身份验证保护Grid. 加载Grid UI或者开始一个新的会话时 需要此用户/密码组合.
[router]
username = "admin"
password = "myStrongPassword"
下面是一个Java示例, 演示如何使用配置的用户和密码启动会话.
URL gridUrl = new URL("http://admin:myStrongPassword@localhost:4444");
RemoteWebDriver webDriver = new RemoteWebDriver(gridUrl, new ChromeOptions());
Important: Custom capabilities need to be set in the configuration in all Nodes. They also need to be included always in every session request.
[node]
detect-drivers = false
[[node.driver-configuration]]
display-name = "firefox"
stereotype = '{"browserName": "firefox", "platformName": "macOS", "browserVersion":"96", "networkname:applicationName":"node_1", "nodename:applicationName":"app_1" }'
max-sessions = 5
Here is a Java example showing how to match that Node
FirefoxOptions options = new FirefoxOptions();
options.setCapability("networkname:applicationName", "node_1");
options.setCapability("nodename:applicationName", "app_1");
options.setBrowserVersion("96");
options.setPlatformName("macOS");
WebDriver driver = new RemoteWebDriver(new URL("http://localhost:4444"), options);
driver.get("https://selenium.dev");
driver.quit();
To be able to retrieve the files that were downloaded by a test at the Node, its location can be specified as below:
[node]
downloads-path = "/usr/downloads"
Refer to the CLI section for a complete example.
Page being translated from English to Chinese. Do you speak Chinese? Help us to translate it by sending us pull requests!
The Grid is designed as a set of components that all fulfill a role in maintaining the Grid. It can seem quite complicated, but hopefully this document can help clear up any confusion.
The main components of the Grid are:
While discussing the Grid, there are some other useful concepts to keep in mind:
There are two main communication mechanisms used within the Grid:
How do we pick which communication mechanism to use? After all, we could model the entire Grid in an event-based way, and it would work out just fine.
The answer is that if the action being performed is synchronous (eg. most WebDriver calls), or if missing the response would be problematic, the Grid uses a synchronous call. If, instead, we want to broadcast information to anyone who’s interested, or if missing the response doesn’t matter, then we prefer to use the event bus.
One interesting thing to note is that the async calls are more decoupled from their listeners than the synchronous calls are.
Although the Grid is designed to allow components to start up in any order, conceptually the order in which components starts is:
You can picture the dependencies between components this way, where a “✅” indicates that there is a synchronous dependency between the components.
Event Bus | Distributor | Node | Router | Session Map | Session Queue | |
---|---|---|---|---|---|---|
Event Bus | X | |||||
Distributor | ✅ | X | ✅ | ✅ | ||
Node | ✅ | X | ||||
Router | ✅ | X | ✅ | |||
Session Map | X | |||||
Session Queue | ✅ | X |
The process of registering a new Node to the Grid is lightweight.
GET
the /status
endpoint of the Node. It
is from this information that the Grid is set up.The Distributor will use the same /status
endpoint to check the Node
on a regular basis, but the Node should continue sending heart beat
events even after started so that a Distributor without a persistent
store of the Grid state can be restarted and will (eventually) be up
to date and correct.
The Node Status is a JSON blob with the following fields:
Name | Type | Description |
---|---|---|
availability | string | A string which is one of up , draining , or down . The important one is draining , which indicates that no new sessions should be sent to the Node, and once the last session on it closes, the Node will exit or restart. |
externalUrl | string | The URI that the other components in the Grid should connect to. |
lastSessionCreated | integer | The epoch timestamp of when the last session was created on this Node. The Distributor will attempt to send new sessions to the Node that has been idle longest if all other things are equal. |
maxSessionCount | integer | Although a session count can be inferred by counting the number of available slots, this integer value is used to determine the maximum number of sessions that should be running simultaneously on the Node before it is considered “full”. |
nodeId | string | A UUID used to identify this instance of the Node. |
osInfo | object | An object with arch , name , and version fields. This is used by the Grid UI and the GraphQL queries. |
slots | array | An array of Slot objects (described below) |
version | string | The version of the Node (for Selenium, this will match the Selenium version number) |
It is recommended to put values in all fields.
The Slot object represents a single slot within a Node. A “slot” is where a single session may be run. It is possible that a Node will have more slots than it can run concurrently. For example, a node may be able to run up 10 sessions, but they could be any combination of Chrome, Edge, or Firefox; in this case, the Node would indicate a “max session count” of 10, and then also say it has 10 slots for Chrome, 10 for Edge, and 10 for Firefox.
Name | Type | Description |
---|---|---|
id | string | UUID to refer to the slot |
lastStarted | string | When the slot last had a session started, in ISO-8601 format |
stereotype | object | The minimal set of capabilities this slot will match against. A minimal example is {"browserName": "firefox"} |
session | object | The Session object (see below) |
This represents a running session within a slot
Name | Type | Description |
---|---|---|
capabilities | object | The actual capabilities provided by the session. Will match the return value from the new session command |
startTime | string | The start time of the session in ISO-8601 format |
stereotype | object | The minimal set of capabilities this slot will match against. A minimal example is {"browserName": "firefox"} |
uri | string | The URI used by the Node to communicate with the session |
Page being translated from English to Chinese. Do you speak Chinese? Help us to translate it by sending us pull requests!
Grid aids in scaling and distributing tests by executing tests on various browser and operating system combinations.
Observability has three pillars: traces, metrics and logs. Since Selenium Grid 4 is designed to be fully distributed, observability will make it easier to understand and debug the internals.
A single request or transaction spans multiple services and components. Tracing tracks the request lifecycle as each service executes the request. It is useful in debugging in an error scenario. Some key terms used in tracing context are:
Trace Tracing allows one to trace a request through multiple services, starting from its origin to its final destination. This request’s journey helps in debugging, monitoring the end-to-end flow, and identifying failures. A trace depicts the end-to-end request flow. Each trace has a unique id as its identifier.
Span Each trace is made up of timed operations called spans. A span has a start and end time and it represents operations done by a service. The granularity of span depends on how it is instrumented. Each span has a unique identifier. All spans within a trace have the same trace id.
Span Attributes Span attributes are key-value pairs which provide additional information about each span.
Events Events are timed-stamped logs within a span. They provide additional context to the existing spans. Events also contain key-value pairs as event attributes.
Logging is essential to debug an application. Logging is often done in a human-readable format. But for machines to search and analyze the logs, it has to have a well-defined format. Structured logging is a common practice of recording logs consistently in a fixed format. It commonly contains fields like:
Logs and events are closely related. Events encapsulate all the possible information available to do a single unit of work. Logs are essentially subsets of an event. At the crux, both aid in debugging. Refer following resources for detailed understanding:
Selenium server is instrumented with tracing using OpenTelemetry. Every request to the server is traced from start to end. Each trace consists of a series of spans as a request is executed within the server. Most spans in the Selenium server consist of two events:
Running Selenium server
All spans, events and their respective attributes are part of a trace. Tracing works while running the server in all of the above-mentioned modes.
By default, tracing is enabled in the Selenium server. Selenium server exports the traces via two exporters:
java -jar selenium-server-4.0.0-<selenium-version>.jar standalone --log-level FINE
Detailed instructions of visualizing traces using Jaeger UI can be obtained by running the command :
java -jar selenium-server-4.0.0-<selenium-version>.jar info tracing
A very good example and scripts to run the server and send traces to Jaeger
Tracing has to be enabled for event logging as well, even if one does not wish to export traces to visualize them.
By default, tracing is enabled. No additional parameters need to be passed to see logs on the console.
All events within a span are logged at FINE level. Error events are logged at WARN level.
All event logs have the following fields :
Field | Field value | Description |
---|---|---|
Event time | eventId | Timestamp of the event record in epoch nanoseconds. |
Trace Id | tracedId | Each trace is uniquely identified by a trace id. |
Span Id | spanId | Each span within a trace is uniquely identified by a span id. |
Span Kind | spanKind | Span kind is a property of span indicating the type of span. It helps in understanding the nature of the unit of work done by the Span. |
Event name | eventName | This maps to the log message. |
Event attributes | eventAttributes | This forms the crux of the event logs, based on the operation executed, it has JSON formatted key-value pairs. This also includes a handler class attribute, to show the logger class. |
Sample log
FINE [LoggingOptions$1.lambda$export$1] - {
"traceId": "fc8aef1d44b3cc8bc09eb8e581c4a8eb",
"spanId": "b7d3b9865d3ddd45",
"spanKind": "INTERNAL",
"eventTime": 1597819675128886121,
"eventName": "Session request execution complete",
"attributes": {
"http.status_code": 200,
"http.handler_class": "org.openqa.selenium.grid.router.HandleSession",
"http.url": "\u002fsession\u002fdd35257f104bb43fdfb06242953f4c85",
"http.method": "DELETE",
"session.id": "dd35257f104bb43fdfb06242953f4c85"
}
}
In addition to the above fields, based on OpenTelemetry specification error logs consist of :
Field | Field value | Description |
---|---|---|
Exception type | exception.type | The class name of the exception. |
Exception message | exception.message | Reason for the exception. |
Exception stacktrace | exception.stacktrace | Prints the call stack at the point of time when the exception was thrown. Helps in understanding the origin of the exception. |
Sample error log
WARN [LoggingOptions$1.lambda$export$1] - {
"traceId": "7efa5ea57e02f89cdf8de586fe09f564",
"spanId": "914df6bc9a1f6e2b",
"spanKind": "INTERNAL",
"eventTime": 1597820253450580272,
"eventName": "exception",
"attributes": {
"exception.type": "org.openqa.selenium.ScriptTimeoutException",
"exception.message": "Unable to execute request: java.sql.SQLSyntaxErrorException: Table 'mysql.sessions_mappa' doesn't exist ..." (full message will be printed),
"exception.stacktrace": "org.openqa.selenium.ScriptTimeoutException: java.sql.SQLSyntaxErrorException: Table 'mysql.sessions_mappa' doesn't exist\nBuild info: version: '4.0.0-alpha-7', revision: 'Unknown'\nSystem info: host: 'XYZ-MacBook-Pro.local', ip: 'fe80:0:0:0:10d5:b63a:bdc6:1aff%en0', os.name: 'Mac OS X', os.arch: 'x86_64', os.version: '10.13.6', java.version: '11.0.7'\nDriver info: driver.version: unknown ...." (full stack will be printed),
"http.handler_class": "org.openqa.selenium.grid.distributor.remote.RemoteDistributor",
"http.url": "\u002fsession",
"http.method": "POST"
}
}
Note: Logs are pretty printed above for readability. Pretty printing for logs is turned off in Selenium server.
The steps above should set you up for seeing traces and logs.
GraphQL 是一种用于API的查询语言, 也是用于使用现有数据完成这些查询的运行时. 其仅仅是使用户能够准确地获取所需.
枚举是表示字段的可能值的集合.
例如, Node
对象具有一个称为status
的字段. 状态是一个枚举 (特别是Status
类型) , 因为它可能是UP
, DRAINING
或 UNAVAILABLE
.
标量是基本类型的值: Int
, Float
, String
, Boolean
, 或 ID
.
在调用GraphQL API时, 必须指定嵌套子字段, 直到只返回标量.
网格模式的结构如下:
{
session(id: "<session-id>") : {
id,
capabilities,
startTime,
uri,
nodeId,
nodeUri,
sessionDurationMillis
slot : {
id,
stereotype,
lastStarted
}
}
grid: {
uri,
totalSlots,
nodeCount,
maxSession,
sessionCount,
version,
sessionQueueSize
}
sessionsInfo: {
sessionQueueRequests,
sessions: [
{
id,
capabilities,
startTime,
uri,
nodeId,
nodeUri,
sessionDurationMillis
slot : {
id,
stereotype,
lastStarted
}
}
]
}
nodesInfo: {
nodes : [
{
id,
uri,
status,
maxSession,
slotCount,
sessions: [
{
id,
capabilities,
startTime,
uri,
nodeId,
nodeUri,
sessionDurationMillis
slot : {
id,
stereotype,
lastStarted
}
}
],
sessionCount,
stereotypes,
version,
osInfo: {
arch,
name,
version
}
}
]
}
}
查询GraphQL的最佳方法是使用curl
请求. GraphQL允许您仅获取所需的数据, 仅此而已.
下面给出了一些GraphQL查询的示例. 您可以根据需要构建自己的查询.
maxSession
和 sessionCount
的数量:curl -X POST -H "Content-Type: application/json" --data '{"query": "{ grid { maxSession, sessionCount } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
通常在本地机器上 <LINK_TO_GRAPHQL_ENDPOINT>
会是 http://localhost:4444/graphql
curl -X POST -H "Content-Type: application/json" --data '{"query":"{ grid { uri, maxSession, sessionCount }, nodesInfo { nodes { id, uri, status, sessions { id, capabilities, startTime, uri, nodeId, nodeUri, sessionDurationMillis, slot { id, stereotype, lastStarted } }, slotCount, sessionCount }} }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
curl -X POST -H "Content-Type: application/json" --data '{"query":"{ grid { sessionCount } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
curl -X POST -H "Content-Type: application/json" --data '{"query":"{ grid { maxSession } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
curl -X POST -H "Content-Type: application/json" --data '{"query":"{ sessionsInfo { sessions { id, capabilities, startTime, uri, nodeId, nodeId, sessionDurationMillis } } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
curl -X POST -H "Content-Type: application/json" --data '{"query":"{ sessionsInfo { sessions { id, slot { id, stereotype, lastStarted } } } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
curl -X POST -H "Content-Type: application/json" --data '{"query":"{ session (id: \"<session-id>\") { id, capabilities, startTime, uri, nodeId, nodeUri, sessionDurationMillis, slot { id, stereotype, lastStarted } } } "}' -s <LINK_TO_GRAPHQL_ENDPOINT>
curl -X POST -H "Content-Type: application/json" --data '{"query": "{ nodesInfo { nodes { stereotypes } } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
curl -X POST -H "Content-Type: application/json" --data '{"query": "{ nodesInfo { nodes { status } } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
curl -X POST -H "Content-Type: application/json" --data '{"query": "{ nodesInfo { nodes { uri } } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
curl -X POST -H "Content-Type: application/json" --data '{"query":"{ sessionsInfo { sessionQueueRequests } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
curl -X POST -H "Content-Type: application/json" --data '{"query":"{ grid { sessionQueueSize } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
Grid状态提供Grid的当前状态. 它包含每个注册节点的详细信息. 对于每个节点, 状态包括有关节点可用性、会话和插槽的信息.
cURL GET 'http://localhost:4444/status'
在独立模式下, Grid URL是独立服务器的地址.
在集线器节点模式下, Grid URL是集线器服务器的地址.
在完全分布式模式下, Grid URL是路由服务器的地址.
以上所有模式的默认URL皆为http://localhost:4444.
要从Grid中删除节点, 请使用下面列出的cURL命令. 它不会停止在该节点上运行的任何持续中的会话. 除非显式终止, 否则节点将继续按原样运行. 分发器不再知晓该节点, 因此任何匹配的新会话请求 也不会转发到该节点.
在独立模式下, 分发器URL是独立服务器的地址.
在集线器节点模式下, 分发器URL是集线器服务器的地址.
cURL --request DELETE 'http://localhost:4444/se/grid/distributor/node/<node-id>' --header 'X-REGISTRATION-SECRET: <secret> '
在完全分布式模式下, URL是分发器的地址.
cURL --request DELETE 'http://localhost:5553/se/grid/distributor/node/<node-id>' --header 'X-REGISTRATION-SECRET: <secret>'
如果在设置Grid时未配置注册密码, 则使用
cURL --request DELETE 'http://<Distributor-URL>/se/grid/distributor/node/<node-id>' --header 'X-REGISTRATION-SECRET;'
节点放空命令用于优雅地关闭节点. 放空节点将在所有持续中的会话完成后停止节点. 但是, 它不接受任何新的会话请求.
在独立模式下, 分发器URL是独立服务器的地址.
在集线器节点模式下, 分发器URL是集线器服务器的地址.
cURL --request POST 'http://localhost:4444/se/grid/distributor/node/<node-id>/drain' --header 'X-REGISTRATION-SECRET: <secret> '
在完全分布式模式下, URL是分发服务器的地址.
cURL --request POST 'http://localhost:5553/se/grid/distributor/node/<node-id>/drain' --header 'X-REGISTRATION-SECRET: <secret>'
如果在设置Grid时未配置注册密码, 则使用
cURL --request POST 'http://<Distributor-URL>/se/grid/distributor/node/<node-id>/drain' --header 'X-REGISTRATION-SECRET;'
本节中的端点适用于 集线器节点模式和 节点独立运行的完全分布式网格模式. 在一个节点的情况下, 默认节点的URL为 http://localhost:5555 . 如果有多个节点, 请使用 Grid 状态 获取所有节点详细信息 以及定位地址.
节点状态本质上是节点的运行状况检查. 分发器定期ping节点状态, 并相应地更新Grid模型. 状态包括相关的可用性, 会话和插槽的信息.
cURL --request GET 'http://localhost:5555/status'
分发器将 放空 命令传递给 由node-id标识的相应节点. 要直接放空节点, 请使用下面列出的cuRL命令. 两个端点都有效并产生相同的结果. 放空会等待持续中的会话完成后 才停止节点.
cURL --request POST 'http://localhost:5555/se/grid/node/drain' --header 'X-REGISTRATION-SECRET: <secret>'
如果在设置Grid时未配置注册密码, 则使用
cURL --request POST 'http://<node-URL>/se/grid/node/drain' --header 'X-REGISTRATION-SECRET;'
要检查会话是否属于某一节点, 请使用下面列出的cURL命令.
cURL --request GET 'http://localhost:5555/se/grid/node/owner/<session-id>' --header 'X-REGISTRATION-SECRET: <secret>'
如果在设置Grid时未配置注册密码, 则使用
cURL --request GET 'http://<node-URL>/se/grid/node/owner/<session-id>' --header 'X-REGISTRATION-SECRET;'
如果会话属于该节点, 则返回true, 否则返回false.
删除会话将终止WebDriver会话, 退出驱动程序 并将其从活动会话集合中删除. 任何使用删除的会话id 或重用驱动程序实例的请求 都将抛出错误.
cURL --request DELETE 'http://localhost:5555/se/grid/node/session/<session-id>' --header 'X-REGISTRATION-SECRET: <secret>'
如果在设置Grid时未配置注册密码, 则使用
cURL --request DELETE 'http://<node-URL>/se/grid/node/session/<session-id>' --header 'X-REGISTRATION-SECRET;'
新会话请求队列保存新会话请求. 要清除队列, 请使用下面列出的cURL命令. 清除队列将拒绝队列中的所有请求. 对于每个这样的请求, 服务器都会向相应的客户端返回一个错误响应. 清除命令的返回结果是 已删除请求的总数.
在独立模式下, 队列URL是独立服务器的地址. 在集线器节点模式下, 队列URL是集线器服务器的地址.
cURL --request DELETE 'http://localhost:4444/se/grid/newsessionqueue/queue' --header 'X-REGISTRATION-SECRET: <secret>'
在完全分布式模式下, 队列URL是新会话队列服务器的地址.
cURL --request DELETE 'http://localhost:5559/se/grid/newsessionqueue/queue' --header 'X-REGISTRATION-SECRET: <secret>'
如果在设置Grid时未配置注册密码, 则使用
cURL --request DELETE 'http://<URL>/se/grid/newsessionqueue/queue' --header 'X-REGISTRATION-SECRET;'
New Session Request Queue holds the new session requests. To get the current requests in the queue, use the cURL command enlisted below. The response returns the total number of requests in the queue and the request payloads. 新会话请求队列保存新会话请求. 要获取队列中的当前请求, 请使用下面列出的cURL命令. 响应会返回队列中的请求总数以及请求内容.
在独立模式下, 队列URL是独立服务器的地址. 在集线器节点模式下, 队列URL是集线器服务器的地址.
cURL --request GET 'http://localhost:4444/se/grid/newsessionqueue/queue'
在完全分布式模式下, 队列URL是新会话队列服务器的地址.
cURL --request GET 'http://localhost:5559/se/grid/newsessionqueue/queue'
Page being translated from English to Chinese. Do you speak Chinese? Help us to translate it by sending us pull requests!
There are times when we would like a Node to be customized to our needs.
For e.g., we may like to do some additional setup before a session begins execution and some clean-up after a session runs to completion.
Following steps can be followed for this:
Create a class that extends org.openqa.selenium.grid.node.Node
Add a static method (this will be our factory method) to the newly created class whose signature looks like this:
public static Node create(Config config)
. Here:
Node
is of type org.openqa.selenium.grid.node.Node
Config
is of type org.openqa.selenium.grid.config.Config
Within this factory method, include logic for creating your new Class.
To wire in this new customized logic into the hub, start the node and pass in the fully qualified class name of the above class to the argument --node-implementation
Let’s see an example of all this:
java -jar
command.java -jar custom_node-server.jar node \
--node-implementation org.seleniumhq.samples.DecoratedLoggingNode
Note: If you are using Maven as a build tool, please prefer using maven-shade-plugin instead of maven-assembly-plugin because maven-assembly plugin seems to have issues with being able to merge multiple Service Provider Interface files (META-INF/services
)
java -jar selenium-server-4.6.0.jar \
--ext custom_node-1.0-SNAPSHOT.jar node \
--node-implementation org.seleniumhq.samples.DecoratedLoggingNode
Below is a sample that just prints some messages on to the console whenever there’s an activity of interest (session created, session deleted, a webdriver command executed etc.,) on the Node.
package org.seleniumhq.samples;
import java.net.URI;
import java.util.UUID;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.NoSuchSessionException;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.grid.config.Config;
import org.openqa.selenium.grid.data.CreateSessionRequest;
import org.openqa.selenium.grid.data.CreateSessionResponse;
import org.openqa.selenium.grid.data.NodeId;
import org.openqa.selenium.grid.data.NodeStatus;
import org.openqa.selenium.grid.data.Session;
import org.openqa.selenium.grid.log.LoggingOptions;
import org.openqa.selenium.grid.node.HealthCheck;
import org.openqa.selenium.grid.node.Node;
import org.openqa.selenium.grid.node.local.LocalNodeFactory;
import org.openqa.selenium.grid.security.Secret;
import org.openqa.selenium.grid.security.SecretOptions;
import org.openqa.selenium.grid.server.BaseServerOptions;
import org.openqa.selenium.internal.Either;
import org.openqa.selenium.remote.SessionId;
import org.openqa.selenium.remote.http.HttpRequest;
import org.openqa.selenium.remote.http.HttpResponse;
import org.openqa.selenium.remote.tracing.Tracer;
public class DecoratedLoggingNode extends Node {
private Node node;
protected DecoratedLoggingNode(Tracer tracer, URI uri, Secret registrationSecret) {
super(tracer, new NodeId(UUID.randomUUID()), uri, registrationSecret);
}
public static Node create(Config config) {
LoggingOptions loggingOptions = new LoggingOptions(config);
BaseServerOptions serverOptions = new BaseServerOptions(config);
URI uri = serverOptions.getExternalUri();
SecretOptions secretOptions = new SecretOptions(config);
// Refer to the foot notes for additional context on this line.
Node node = LocalNodeFactory.create(config);
DecoratedLoggingNode wrapper = new DecoratedLoggingNode(loggingOptions.getTracer(),
uri, secretOptions.getRegistrationSecret());
wrapper.node = node;
return wrapper;
}
@Override
public Either<WebDriverException, CreateSessionResponse> newSession(
CreateSessionRequest sessionRequest) {
System.out.println("Before newSession()");
try {
return this.node.newSession(sessionRequest);
} finally {
System.out.println("After newSession()");
}
}
@Override
public HttpResponse executeWebDriverCommand(HttpRequest req) {
try {
System.out.println("Before executeWebDriverCommand(): " + req.getUri());
return node.executeWebDriverCommand(req);
} finally {
System.out.println("After executeWebDriverCommand()");
}
}
@Override
public Session getSession(SessionId id) throws NoSuchSessionException {
try {
System.out.println("Before getSession()");
return node.getSession(id);
} finally {
System.out.println("After getSession()");
}
}
@Override
public HttpResponse uploadFile(HttpRequest req, SessionId id) {
try {
System.out.println("Before uploadFile()");
return node.uploadFile(req, id);
} finally {
System.out.println("After uploadFile()");
}
}
@Override
public void stop(SessionId id) throws NoSuchSessionException {
try {
System.out.println("Before stop()");
node.stop(id);
} finally {
System.out.println("After stop()");
}
}
@Override
public boolean isSessionOwner(SessionId id) {
try {
System.out.println("Before isSessionOwner()");
return node.isSessionOwner(id);
} finally {
System.out.println("After isSessionOwner()");
}
}
@Override
public boolean isSupporting(Capabilities capabilities) {
try {
System.out.println("Before isSupporting");
return node.isSupporting(capabilities);
} finally {
System.out.println("After isSupporting()");
}
}
@Override
public NodeStatus getStatus() {
try {
System.out.println("Before getStatus()");
return node.getStatus();
} finally {
System.out.println("After getStatus()");
}
}
@Override
public HealthCheck getHealthCheck() {
try {
System.out.println("Before getHealthCheck()");
return node.getHealthCheck();
} finally {
System.out.println("After getHealthCheck()");
}
}
@Override
public void drain() {
try {
System.out.println("Before drain()");
node.drain();
} finally {
System.out.println("After drain()");
}
}
@Override
public boolean isReady() {
try {
System.out.println("Before isReady()");
return node.isReady();
} finally {
System.out.println("After isReady()");
}
}
}
Foot Notes:
In the above example, the line Node node = LocalNodeFactory.create(config);
explicitly creates a LocalNode
.
There are basically 2 types of user facing implementations of org.openqa.selenium.grid.node.Node
available.
These classes are good starting points to learn how to build a custom Node and also to learn the internals of a Node.
org.openqa.selenium.grid.node.local.LocalNode
- Used to represent a long running Node and is the default implementation that gets wired in when you start a node
.LocalNodeFactory.create(config);
, where:LocalNodeFactory
belongs to org.openqa.selenium.grid.node.local
Config
belongs to org.openqa.selenium.grid.config
org.openqa.selenium.grid.node.k8s.OneShotNode
- This is a special reference implementation wherein the Node gracefully shuts itself down after servicing one test session. This class is currently not available as part of any pre-built maven artifact.Page being translated from English to Chinese. Do you speak Chinese? Help us to translate it by sending us pull requests!
Selenium Grid allows you to persist information related to currently running sessions into an external data store. The external data store could be backed by your favourite database (or) Redis Cache system.
For the sake of this illustration, we are going to work with PostGreSQL database.
We will spin off a PostGreSQL database as a docker container using a docker compose file.
You can skip this step if you already have a PostGreSQL database instance available at your disposal.
init.sql
with the below contents:CREATE TABLE IF NOT EXISTS sessions_map(
session_ids varchar(256),
session_caps text,
session_uri varchar(256),
session_stereotype text,
session_start varchar(256)
);
init.sql
, create a file named docker-compose.yml
with its contents as below:version: '3.8'
services:
db:
image: postgres:9.6-bullseye
restart: always
environment:
- POSTGRES_USER=seluser
- POSTGRES_PASSWORD=seluser
- POSTGRES_DB=selenium_sessions
ports:
- "5432:5432"
volumes:
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
We can now start our database container by running:
docker-compose up -d
Our database name is selenium_sessions
with its username and password set to seluser
If you are working with an already running PostGreSQL DB instance, then you just need to create a database named selenium_sessions
and the table sessions_map
using the above mentioned SQL statement.
sessions.toml
with the below contents:[sessions]
implementation = "org.openqa.selenium.grid.sessionmap.jdbc.JdbcBackedSessionMap"
jdbc-url = "jdbc:postgresql://localhost:5432/selenium_sessions"
jdbc-user = "seluser"
jdbc-password = "seluser"
Note: If you plan to use an existing PostGreSQL DB instance, then replace localhost:5432
with the actual host and port number of your instance.
distributed.sh
) that we will use to bring up our distributed Grid.SE_VERSION=<current_selenium_version>
JAR_NAME=selenium-server-${SE_VERSION}.jar
PUBLISH="--publish-events tcp://localhost:4442"
SUBSCRIBE="--subscribe-events tcp://localhost:4443"
SESSIONS="--sessions http://localhost:5556"
SESSIONS_QUEUE="--sessionqueue http://localhost:5559"
echo 'Starting Event Bus'
java -jar $JAR_NAME event-bus $PUBLISH $SUBSCRIBE --port 5557 &
echo 'Starting New Session Queue'
java -jar $JAR_NAME sessionqueue --port 5559 &
echo 'Starting Sessions Map'
java -jar $JAR_NAME \
--ext $(coursier fetch -p org.seleniumhq.selenium:selenium-session-map-jdbc:${SE_VERSION} org.postgresql:postgresql:42.3.1) \
sessions $PUBLISH $SUBSCRIBE --port 5556 --config sessions.toml &
echo 'Starting Distributor'
java -jar $JAR_NAME distributor $PUBLISH $SUBSCRIBE $SESSIONS $SESSIONS_QUEUE --port 5553 --bind-bus false &
echo 'Starting Router'
java -jar $JAR_NAME router $SESSIONS --distributor http://localhost:5553 $SESSIONS_QUEUE --port 4444 &
echo 'Starting Node'
java -jar $JAR_NAME node $PUBLISH $SUBSCRIBE &
At this point the current directory should contain the following files:
docker-compose.yml
init.sql
sessions.toml
distributed.sh
You can now spawn the Grid by running distributed.sh
shell script and quickly run a test. You will notice that the Grid now stores session information into the PostGreSQL database.
In the line which spawns a SessionMap
on a machine:
export SE_VERSION=<current_selenium_version>
java -jar selenium-server-${SE_VERSION}.jar \
--ext $(coursier fetch -p org.seleniumhq.selenium:selenium-session-map-jdbc:${SE_VERSION} org.postgresql:postgresql:42.3.1) \
sessions --publish-events tcp://localhost:4442 \
--subscribe-events tcp://localhost:4443 \
--port 5556 --config sessions.toml
localhost
with the actual hostname of the machine where your Event-Bus
is running.coursier
are basically the GAV (Group Artifact Version) Maven co-ordinates of:sessions.toml
is the configuration file that we created earlier.We will spin off a Redis Cache docker container using a docker compose file.
You can skip this step if you already have a Redis Cache instance available at your disposal.
docker-compose.yml
with its contents as below:version: '3.8'
services:
redis:
image: redis:bullseye
restart: always
ports:
- "6379:6379"
We can now start our Redis container by running:
docker-compose up -d
sessions.toml
with the below contents:[sessions]
scheme = "redis"
implementation = "org.openqa.selenium.grid.sessionmap.redis.RedisBackedSessionMap"
hostname = "localhost"
port = 6379
Note: If you plan to use an existing Redis Cache instance, then replace localhost
and 6379
with the actual host and port number of your instance.
distributed.sh
) that we will use to bring up our distributed grid.SE_VERSION=<current_selenium_version>
JAR_NAME=selenium-server-${SE_VERSION}.jar
PUBLISH="--publish-events tcp://localhost:4442"
SUBSCRIBE="--subscribe-events tcp://localhost:4443"
SESSIONS="--sessions http://localhost:5556"
SESSIONS_QUEUE="--sessionqueue http://localhost:5559"
echo 'Starting Event Bus'
java -jar $JAR_NAME event-bus $PUBLISH $SUBSCRIBE --port 5557 &
echo 'Starting New Session Queue'
java -jar $JAR_NAME sessionqueue --port 5559 &
echo 'Starting Session Map'
java -jar $JAR_NAME \
--ext $(coursier fetch -p org.seleniumhq.selenium:selenium-session-map-redis:${SE_VERSION}) \
sessions $PUBLISH $SUBSCRIBE --port 5556 --config sessions.toml &
echo 'Starting Distributor'
java -jar $JAR_NAME distributor $PUBLISH $SUBSCRIBE $SESSIONS $SESSIONS_QUEUE --port 5553 --bind-bus false &
echo 'Starting Router'
java -jar $JAR_NAME router $SESSIONS --distributor http://localhost:5553 $SESSIONS_QUEUE --port 4444 &
echo 'Starting Node'
java -jar $JAR_NAME node $PUBLISH $SUBSCRIBE &
At this point the current directory should contain the following files:
docker-compose.yml
sessions.toml
distributed.sh
You can now spawn the Grid by running distributed.sh
shell script and quickly run a test. You will notice that the Grid now stores session information into the Redis instance. You can perhaps make use of a Redis GUI such as TablePlus to see them (Make sure that you have setup a debug point in your test, because the values will get deleted as soon as the test runs to completion).
In the line which spawns a SessionMap
on a machine:
export SE_VERSION=<current_selenium_version>
java -jar selenium-server-${SE_VERSION}.jar \
--ext $(coursier fetch -p org.seleniumhq.selenium:selenium-session-map-redis:${SE_VERSION}) \
sessions --publish-events tcp://localhost:4442 \
--subscribe-events tcp://localhost:4443 \
--port 5556 --config sessions.toml
localhost
with the actual hostname of the machine where your Event-Bus
is running.coursier
are basically the GAV (Group Artifact Version) Maven co-ordinates of:sessions.toml
is the configuration file that we created earlier.Want to support the Selenium project? Learn more or view the full list of sponsors.