This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Grid

Pretende executar testes em paralelo em várias máquinas? Então a Grid é para si.

Selenium Grid permite a execucão de scripts WebDriver em máquinas remotas, passando os comandos recebidos pelo cliente para a instância remotas do navegador.

O objectivo da Grid é:

  • Providenciar uma forma fácil de executar testes em paralelo em multiplas máquinas
  • Permitir testes em versões diferentes de navegadores
  • Permitir testes em várias plataformas

Está interessado? Enão siga lendo as próximas secções para entender como a Grid funciona e também como montar a sua.

1 - Configurando a sua

Instruções para criar uma Selenium Grid simples

Início rápido

  1. Pré-requisitos
    • Java 11 ou superior instalado
    • Navegador(es) instalados
    • Drivers do(s) navegador(es)
    • Obter o ficheiro Selenium Server Jar a partir da última release
  2. Iniciar a Grid
    • java -jar selenium-server-<version>.jar standalone
  3. Aponte* os seus testes WebDriver para http://localhost:4444
  4. (Opcional) Verifique os testes que estão em execução abrindo o navegador em http://localhost:4444

*Se quer saber como direcionar os seus testes para http://localhost:4444, veja a secção RemoteWebDriver.

Para aprender mais sobre as diferentes opções de configuração, veja as secções seguintes.

Grid roles

A Grid é composta de seis componentes diferentes, o que permite ser instalada de várias formas.

Dependendo das necessidades, podemos iniciar cada um dos componentes (Distribuido) ou agrupar no formato Hub e Node ou ainda numa única máquina (Standalone).

Standalone

Standalone combina todos os componentes num só sitio. Executar uma Grid em modo Standalone permite uma Grid totalmente funcionar com um único comando, num único processo. Standalone só funcionará numa única máquina.

Standalone é também a forma mais simples de colocar uma Selenium Grid em funcionamento. Por omissão, o servidor irá escutar por pedidos RemoteWebDriver em http://localhost:4444. O servidor irá também detectar os drivers disponíveis no PATH.

java -jar selenium-server-<version>.jar standalone

Após iniciar a Selenium Grid no modo Standalone, aponte os seus WebDriver tests para http://localhost:4444.

Alguns casos de uso para Standalone são:

  • Desenvolver ou debugar testes usando RemoteWebDriver localmente
  • Executar baterias de testes rapidamente antes de fazer commit de código
  • Ter uma Grid simples de montar numa ferramenta CI/CD (GitHub Actions, Jenkins, etc…)

Hub e Node

Hub e Node é uma das formas mais usadas porque permite:

  • Combinar máquinas diferentes na mesma Grid
    • Máquinas com sistemas operativos diferentes e/ou versões de navegadores diferentes
  • Ter um ponto único de entrada para executar testes WebDriver em ambientes diferentes
  • Escalar a capacidade para cima ou para baixo sem ter que parar a Grid

Hub

O Hub é composto pelos seguintes componentes: Router, Distributor, Session Map, New Session Queue, e Event Bus.

java -jar selenium-server-<version>.jar hub

Por omissão, o servidor irá estar à escuta por pedidos de sessão RemoteWebDriver em http://localhost:4444.

Node

Ao iniciar, o Node irá detectar os drivers disponíveis através do PATH.

O comando exemplo seguinte assume que o Node está a executar na mesma máquina onde o Hub está em execução.

java -jar selenium-server-<version>.jar node
Mais do que um Node na mesma máquina

Node 1

java -jar selenium-server-<version>.jar node --port 5555

Node 2

java -jar selenium-server-<version>.jar node --port 6666
Node e Hub em máquinas diferentes

A comunicação entre Hub e Nodes ocorre via HTTP. Para iniciar o processo de registo, o Node envia uma mensagem para o Hub através do Event Bus (o Event Bus reside dentro do Hub). Quando o Hub recebe a mensagem, tenta comunicar com o Node para confirmar a sua existencia.

Para que um Node se consiga registar no Hub, é importante que as portas do Event Bus sejam expostas na máquina Hub. As portas por omissão são 4442 e 4443 para o Event Bus e 4444 para o Hub.

Se o Hub estiver a usar as portas por omissão, pode usar a flag --hub para registar o Node

java -jar selenium-server-<version>.jar node --hub http://<hub-ip>:4444

Quando o Hub não estiver a usar as portas por omissão, necessita usar as flags--publish-events e --subscribe-events.

Por exemplo, se o Hub usar as portas8886, 8887, e 8888

java -jar selenium-server-<version>.jar hub --publish-events tcp://<hub-ip>:8886 --subscribe-events tcp://<hub-ip>:8887 --port 8888

O Node necessita de especificar as portas para conseguir registar-se com sucesso

java -jar selenium-server-<version>.jar node --publish-events tcp://<hub-ip>:8886 --subscribe-events tcp://<hub-ip>:8887

Distribuida

Quando usar uma Grid distribuida, cada componente é iniciado separadamente e preferencialmente em máquinas diferentes.

  1. Event Bus: permite comunicação interna entre os diferentes componentes da Grid.

As portas por omissão são: 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
  1. New Session Queue: adiciona novos pedidos de sessão a uma queue, que serão consultadas pelo Distributor

A porta por omissão é 5559.

java -jar selenium-server-<version>.jar sessionqueue --port 5559
  1. Session Map: estabelece um mapa entre id de sessão e o Node onde a sessão está a executar

A porta por omissão é 5556. Session Map interage com o 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
  1. Distributor: consulta New Session Queue para novos pedidos de sessão, que entrega ao um Node quando encontra um capacidade correspondente. Nodes registam-se no Distributor da mesma forma como numa Grid do tipo Hub/Node.

A porta por omissão é 5553. Distributor interage com New Session Queue, Session Map, Event Bus, e 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
  1. Router: redirecciona novos pedidos de sessão para a queue, e redirecciona pedidos de sessões para o Node que estiver a executar a sessão.

A porta por omissão é 4444. Router interage com New Session Queue, Session Map, e 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
  1. Node(s)

A porta por omissão é 5555.

java -jar selenium-server-<version>.jar node --publish-events tcp://<event-bus-ip>:4442 --subscribe-events tcp://<event-bus-ip>:4443

Adicionar Metadata nos testes

Adicione Metadata aos testes, através de GraphQL ou visualize parcialmente (como se:name) através da Selenium Grid UI.

Metadata pode ser adicionada como uma capacidade com o prefixo se:. Eis um pequeno exemplo em Java.

ChromeOptions chromeOptions = new ChromeOptions();
chromeOptions.setCapability("browserVersion", "100");
chromeOptions.setCapability("platformName", "Windows");
// Mostrando na Grid UI o nome de um teste ao invés de uma session id
chromeOptions.setCapability("se:name", "My simple test"); 
// Outros tipos de metadara podem ser visualizados na Grid UI 
// ao clicar na informação de sessão ou via GraphQL
chromeOptions.setCapability("se:sampleMetadata", "Valor exemplo de Metadata"); 
WebDriver driver = new RemoteWebDriver(new URL("http://gridUrl:4444"), chromeOptions);
driver.get("http://www.google.com");
driver.quit();

Questionando a Selenium Grid

Após iniciar a Gris, existem duas formas de saber o seu estado, através da Grid UI ou por chamada API.

A Grid UI pode ser acedida pelo seu navegador preferido em http://localhost:4444. As chamadas API podem ser feitas para o endpoint http://localhost:4444/status ou através de GraphQL.

Para simplificar, todos os exemplos apresentados assumem que os componentes estão a ser executados localmente. Exemplos mais detalhados podem ser encontrados na secção Configurando Componentes.

Usando o cliente HTTP nativo Java 11

Selenium v4.5

Por omissão, a Grid irá usar AsyncHttpClient. AsyncHttpClient é uma biblioteca open-source library criada em cima do Netty. Isto permite a execução de pedidos e respostas HTTP de forma assíncrona. Esta biblioteca é uma boa escolha pois além de permitir pedidos assíncronos, também suporta WebSockets.

No entanto, a biblioteca AsyncHttpClient não é mantida activamente desde Junho de 2021. Isto coincide com o facto de que a partir do Java 11, a JVM tem um cliente nativo que suporta camadas assíncronas e contém um cliente WebSocket.

Atualmente, o projecto Selenium tem planos de atualizar a versão mínima suportada para Java 11. No entanto, isto é um esforço considerável. Alinhá-lo com os principais lançamentos e anúncios acompanhados é crucial para garantir que a experiência do usuário esteja intacta.

Para usar o cliente Java 11, terá que baixar o ficheiro jar selenium-http-jdk-client e usar a flag --ext para funcionar na Grid.

Este ficheiro pode ser obtido directamente de repo1.maven.org e depois pode iniciar a Grid com:

java -Dwebdriver.http.factory=jdk-http-client -jar selenium-server-<version>.jar -—ext selenium-http-jdk-client-<version>.jar standalone

Uma alternativa a baixar o ficheiro jar selenium-http-jdk-client é usar 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

Se está a usar a Grid em modo Hub/Node ou Distributed, terá que usar as flags -Dwebdriver.http.factory=jdk-http-client e —-ext em cada um dos componentes.

Dimensionar Grid

A escolha de Grid depende de quais sistemas operacionais e navegadores precisam ser suportados, quantas sessões paralelas precisam ser executadas, a quantidade de máquinas disponíveis e quão poderosas (CPU, RAM) essas máquinas são.

A criação de sessões simultaneas depende dos processadores disponíveis para o Distributor. Por exemplo, se uma máquina tiver 4 CPUs, o Distributor só poderá criar quatro sessões em simultâneo.

Por omissão, a quantidade máxima de sessões simultâneas que um Node suporta é limitada pelo número de CPUs disponíveis. Por exemplo, se a máquina Node tiver 8 CPUs, ela poderá executar até 8 sessões de navegador simultâneas (com exceção do Safari, que é sempre uma). Além disso, espera-se que cada sessão do navegador use cerca de 1 GB de RAM.

Em geral, é recomendado ter Nodes o mais pequenos possíveis. Em vez de ter uma máquina com 32CPUs e 32GB de RAM para executar 32 sessões de navegador simultâneas, é melhor tem 32 pequenos Nodes para isolar melhor os processos. Com isso, se um Node falhar, será de forma isolada. Docker é uma boa ferramenta para alcançar essa abordagem.

Note que os valores 1CPU/1GB RAM por navegador são uma recomendação e podem não ser os mais indicados para o seu contexto. Recomenda-se que use estea valores como referência, mas meça o desempenho continuamente para ajudar a determinar os valores ideais para o seu ambiente.

Os tamanhos da Grid são relativos à quantidade de sessões simultâneas suportadas e à quantidade de Nodes, não existindo um “tamanho único”. Os tamanhos mencionados abaixo são estimativas aproximadas que podem variar entre diferentes ambientes. Por exemplo, um Hub/Node com 120 Nodes pode funcionar bem quando o Hub tiver recursos suficientes. Os valores abaixo não são gravados em pedra, e comentários são bem-vindos!

Pequena

Standalone e Hub/Node com cinco Nodes ou menos.

Média

Hub/Node entre 6 e 60 Nodes.

Grande

Hub/Node entre 60 e 100 Nodes. Distributed com mais de 100 Nodes.

AVISO

Deve proteger a Selenium Grid de acesso externo, usando regras de firewall apropriadas.

Se falhar em proteger a Grid uma ou mais coisas poderão ocorrer:

  • Permite acesso aberto à sua infraestrutura da Grid
  • Permitir acesso de terceiros a aplicativos web e a ficheiros
  • Permitir execução remota de ficheiros binários por terceiros

Leia este artigo (em Inglês) em Detectify, que dá um bom resumo de como uma Grid exposta publicamente pode ser abusada: Don’t Leave your Grid Wide Open

Leituras adicionais

2 - Quando usar a Grid

Será a Grid a melhor escolha para você?

Quando deve usar a Selenium Grid?

  • Para executar testes em paralelo, com vários tipos e versões de navegador e sistemas operativos
  • Para reduzir o tempo necessário a executar uma bateria de testes

A Selenium Grid executa baterias de testes em paralelo contra várias máquinas (chamadas Nodes). Para testes longos, isto poderá poupar minutos, horas e talvez dias. Isto encurta o tempo de espera para obter resultados dos testes de cada vez que sobre a aplicação em testes é alterada.

A Grid consegue executar testes (em paralelo) contra multiplos e diferentes navegadores, também é capaz de executar contra várias instâncias do mesmo navegador. Como exemplo, vamos imaginar uma Grid com seis Nodes. A primeira máquina tem a versão mais recente do Firefox, a segunda tem a versão “mais recente -1”, a terceira tem o Chrome mais recente e as restantes máquinas são Mac Mini, que permitem três testes em paralelo com a versão mais recente to Safari.

O tempo de execução pode ser expresso como uma fórmula simples:

Número de testes * Tempo médio de cada teste / Número de Nodes = Tempo total de execução

   15      *       45s        /        1        =      11m 15s   // Sem Grid
   15      *       45s        /        5        =      2m 15s    // Grid com 5 Nodes
   15      *       45s        /        15       =      45s       // Grid com 15 Nodes
  100      *       120s       /        15       =      13m 20s   // Demoraria mais de 3 horas sem Grid

À medida que a bateria de testes executa, a Grid vai alocando os testes contra estes navegadores como está definido nos testes.

Uma configuração deste tipo pode acelerar bastante o tempo de execução mesmo no caso de baterias de testes grandes.

A Selenium Grid é uma parte integrante do projecto Selenium e é mantida em paralelo pela mesma equipa de developers que desenvolvem o resto das funcionalidades base do projecto. Dada a importância da velocidade e desempenho da execução dos testes, a Grid tem sido considerada desde o início como uma parte crítica e fundamental ao projecto.

3 - Componentes

Compreender como usar os componentes da Grid

A Selenium Grid 4 é uma re-escrita completa das versões anteriores. Além dos melhoramentos ao desempenho e aos cumprimentos dos standards, várias funcionalidades da Grid foram separadas para reflectir uma nova era de computação e desenvolvimento de software. Criada de raíz para containerização e escalabilidade cloud, Selenium Grid 4 é uma nova solução para a era moderna.

Componentes Grid

Componentes Selenium Grid 4

Router

O Router é o ponto de entrada da Grid, recebendo todos os pedidos externos, reenviando para o componente correcto.

Se o Router recebe um novo pedido de sessão, este será enviado para New Session Queue.

Se o pedido recebido pertence a uma sessão existente, o Router irá inquirir o Session Map para obter o Node ID onde esta sessão está em execução, enviando de seguida o pedido directamente para o Node.

O Router balança a carga na Grid ao enviar o pedido ao componente que está em condições de o receber, sem sobrecarregar qualquer outro componente que não faz parte do processo.

Distributor

O Distributor tem duas responsabilidades:

Resgistar e manter uma lista de todos os Nodes e as suas capacidades

Um Node regista-se no Distributor enviando um evento de registo Node através do Event Bus. O Distributor lê o pedido e tenta contactar o Node através de HTTP para confirmar a sua existência. Se obtiver resposta com sucesso, o Distributor regista o Node e mantém uma lista de todas as capacidades Nodes através do GridModel.

Processar algum pedido pendente que tenha sido criado na New Session Queue

Quando um novo pedido de sessão é enviado para o Router, ele é reenviado para a New Session Queue, onde ficará na fila em espera. O Distributor irá processar pedidos pendentes que existam na New Session Queue, encontrando um Node com as capacidades certas onde a sessão possa ser criada. Após esta nova sessão ter sido criada, o Distributor guarda na Session Map a relação entre o id da sessão e o Node onde a sessão está em execução.

Session Map

A Session Map é uma data store que mantém a relação entre o id da sessão e o Node onde a sessão está em execução. Apoia o Router no processo de envio do pedido para um Node onde a sessão está em execução. O Router irá pedir ao Session Map qual o Node associado ao id de sessão.

New Session Queue

A New Session Queue contém todos os pedidos de novas sessões em ordem FIFO. Existem parametros configuráveis para o timeout de sessão e para o número de tentativas.

O Router adiciona um novo pedido de sessão em New Session Queue e aguarda uma resposta. A New Session Queue verifica regularmente se algum pedido deu timeout e em caso afirmativo, rejeita e remove o pedido da queue.

O Distributor verifica regularmente se existe um slot disponível. Em caso afirmativo, o Distributor obtém um pedido a partir de New Session Queue e tenta criar uma nova sessão.

Assim que existam capacidades pedidas capazes de serem servidas por um dos Node que estejam livres, o Distributor tenta obter o slot disponível. Caso todos os slots estejam ocupados, o Distributor envia o pedido de volta para a queue. Se o pedido tiver timeout ao ser readicionado à queue, será rejeitado.

Após um id de sessão ser criado, o Distributor envia a informação se sessão para New Session Queue, que por sua vez irá enviar para o Router que finalmente entrega ao cliente.

Node

A Grid pode ter múltiplos Nodes. Cada Node gere os slots de disponibilidade para os navegadores existentes na máquina onde está a executar.

O Node regista-se no Distributor através do Event Bus, sendo que a sua configuração é enviada como parte da mensagem de registo.

Por omissão, o Node regista automaticamente todos os navegadores que estejam disponíveis no PATH da máquina onde executa. Cria também um slot de execução por cada CPU para os navegadores Chrome e Firefox. Para Safari, apenas é criado um slot. Usando uma configuração específica, é também possível executar sessões em containers Docker.

O Node apenas executa os comandos que recebe, não avalia, faz julgamentos ou controla mais nada que não seja o fluxo de comandos e respostas. A máquina onde o Node está a executar não necessita de ter o mesmo sistema operativo do que os restantes componentes. Por exemplo, um Windows Node pode ter a capacidade de oferecer IE Mode no Edge como opção de navegador, coisa que não é possível em Linux ou Mac. A Grid pode ter múltiplos Node configurados com Windows, Mac ou Linux.

Event Bus

O Event Bus serve de canal de comunicações entre Nodes, Distributor, New Session Queue, e Session Map. A Grid usa mensagens para a maioria das comunicações internas. evitando chamadas HTTP. Quando iniciar a Grid em modo distribuido, deverá iniciar o Event Bus antes dos restantes componentes.

4 - Configurando componentes

Leia aqui como pode configurar cada um dos componentes Grid com base em valores comuns ou específicos para o componente.

4.1 - Ajuda de configuração

Obtenha ajuda sobre todas as opções disponíveis para configurar a Grid.

Os comandos de ajuda (help) mostram informação baseada na implementação de código em vigor. Desta forma, irá obter informação potencialmente mais actual do que esta documentação. Embora possa não ser a mais detalhada, é sempre a forma mais simples de aprender sobre as configurações da Grid 4 em qualquer nova versão.

Commando info

O comando info fornece detalhes de documentação sobre os seguintes tópicos:

  • Configurar Selenium
  • Segurança
  • Setup Session Map
  • Traceamento

Ajuda sobre configuração

Ajuda rápida e resumo pode ser obtida com:

java -jar selenium-server-<version>.jar info config

Segurança

Para obter detalhes em como configurar os servidores Grid para comunicação segura e para o registo de Nodes:

java -jar selenium-server-<version>.jar info security

Configuração Session Map

Por omissão, a Grid usa um local session map para armazenar informação de sessões. A Grid permite armazenamento opcional em Redis ou bancos de dados JDBC SQL. Para obter informação de como usar, use o seguinte comando:

java -jar selenium-server-<version>.jar info sessionmap

Traceamento com OpenTelemetry e Jaeger

Por omissao, traceamento está activo. Para exportar e visualizar através de Jaeger, use o comando seguinte para instruções de como o efectuar:

java -jar selenium-server-<version>.jar info tracing

Lista de comandos Selenium Grid

Irá mostrar todos os comandos disponíveis e também a descrição de cada um.

java -jar selenium-server-<version>.jar --config-help

Comandos de ajuda para componente

Adicione --help após o Selenium role para obter informação específica de configuração do componente.

Standalone

java -jar selenium-server-<version>.jar standalone --help

Hub

java -jar selenium-server-<version>.jar hub --help

Sessions

java -jar selenium-server-<version>.jar sessions --help

New Session Queue

java -jar selenium-server-<version>.jar sessionqueue --help

Distributor

java -jar selenium-server-<version>.jar distributor --help

Router

java -jar selenium-server-<version>.jar router --help

Node

java -jar selenium-server-<version>.jar node --help

4.2 - CLI Options

All Grid components configuration CLI options in detail.

Page being translated from English to Portuguese. Do you speak Portuguese? 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.

Sections

StandaloneHubNodeDistributorRouterSessionsSessionQueue
Distributor
Docker
Events
Logging
Network
Node
Router
Relay
Server
SessionQueue
Sessions

Distributor

OptionTypeValue/ExampleDescription
--healthcheck-intervalint120How often, in seconds, will the health check run for all Nodes. This ensures the server can ping all the Nodes successfully.
--distributorurihttp://localhost:5553Url of the distributor.
--distributor-hoststringlocalhostHost on which the distributor is listening.
--distributor-implementationstringorg.openqa.selenium.grid.distributor.local.LocalDistributorFull class name of non-default distributor implementation
--distributor-portint5553Port on which the distributor is listening.
--reject-unsupported-capsbooleanfalseAllow 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-matcherstringorg.openqa.selenium.grid.data.DefaultSlotMatcherFull class name of non-default slot matcher to use. This is used to determine whether a Node can support a particular session.
--slot-selectorstringorg.openqa.selenium.grid.distributor.selector.DefaultSlotSelectorFull class name of non-default slot selector. This is used to select a slot in a Node once the Node has been matched.

Docker

OptionTypeValue/ExampleDescription
--docker-assets-pathstring/opt/selenium/assetsAbsolute 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-devicesstring[]/dev/kvm:/dev/kvmExposes 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-hoststringlocalhostHost name where the Docker daemon is running
--docker-portint2375Port where the Docker daemon is running
--docker-urlstringhttp://localhost:2375URL for connecting to the Docker daemon
--docker-video-imagestringselenium/video:latestDocker image to be used when video recording is enabled

Events

OptionTypeValue/ExampleDescription
--bind-busbooleanfalseWhether 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-implementationstringorg.openqa.selenium.events.zeromq.ZeroMqEventBusFull class name of non-default event bus implementation
--publish-eventsstringtcp://*:4442Connection string for publishing events to the event bus
--subscribe-eventsstringtcp://*:4443Connection string for subscribing to events from the event bus

Logging

OptionTypeValue/ExampleDescription
--http-logsbooleanfalseEnable http logging. Tracing should be enabled to log http logs.
--log-encodingstringUTF-8Log encoding
--logstringWindows 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-levelstring“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-logsbooleantrueUse plain log lines
--structured-logsbooleanfalseUse structured logs
--tracingbooleantrueEnable trace collection
--log-timestamp-formatstringHH:mm:ss.SSSAllows the configure log timestamp format

Network

OptionTypeValue/ExampleDescription
--relax-checksbooleanfalseRelax checks on origin header and content type of incoming requests, in contravention of strict W3C spec compliance.

Node

OptionTypeValue/ExampleDescription
--detect-driversbooleantrueAutodetect which drivers are available on the current system, and add them to the Node.
--driver-configurationstring[]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-factorystring[]org.openqa.selenium.example.LynxDriverFactory '{"browserName": "lynx"}'Mapping of fully qualified class name to a browser configuration that this matches against.
--driver-implementationstring[]"firefox"Drivers that should be checked. If specified, will skip autoconfiguration.
--node-implementationstring"org.openqa.selenium.grid.node.local.LocalNodeFactory"Full classname of non-default Node implementation. This is used to manage a session’s lifecycle.
--grid-urlstringhttps://grid.example.comPublic URL of the Grid as a whole (typically the address of the Hub or the Router)
--heartbeat-periodint60How often, in seconds, will the Node send heartbeat events to the Distributor to inform it that the Node is up.
--max-sessionsint8Maximum number of concurrent sessions. Default value is the number of available processors.
--override-max-sessionsbooleanfalseThe # 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-cycleint10How often, in seconds, the Node will try to register itself for the first time to the Distributor.
--register-periodint120How 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-timeoutint300Let 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-varstringSTART_XVFBEnvironment variable to check in order to determine if a vnc stream is available or not.
--no-vnc-portint7900If VNC is available, sets the port where the local noVNC stream can be obtained
--drain-after-session-countint1Drain and shutdown the Node after X sessions have been executed. Useful for environments like Kubernetes. A value higher than zero enables this feature.
--hubstringhttp://localhost:4444The 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-cdpbooleantrueEnable CDP proxying in Grid. A Grid admin can disable CDP if the network doesnot allow websockets. True by default.

Relay

OptionTypeValue/ExampleDescription
--service-urlstringhttp://localhost:4723URL for connecting to the service that supports WebDriver commands like an Appium server or a cloud service.
--service-hoststringlocalhostHost name where the service that supports WebDriver commands is running
--service-portint4723Port where the service that supports WebDriver commands is running
--service-status-endpointstring/statusOptional, endpoint to query the WebDriver service status, an HTTP 200 response is expected
--service-configurationstring[]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.

Router

OptionTypeValue/ExampleDescription
--passwordstringmyStrongPasswordPassword clients must use to connect to the server. Both this and the username need to be set in order to be used.
--usernamestringadminUser name clients must use to connect to the server. Both this and the password need to be set in order to be used.

Server

OptionTypeValue/ExampleDescription
--allow-corsbooleantrueWhether the Selenium server should allow web browser connections from any host
--hoststringlocalhostServer IP or hostname: usually determined automatically.
--bind-hostbooleantrueWhether 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-certificatepath/path/to/cert.pemServer certificate for https. Get more detailed information by running “java -jar selenium-server.jar info security”
--https-private-keypath/path/to/key.pkcs8Private key for https. Get more detailed information by running “java -jar selenium-server.jar info security”
--max-threadsint24Maximum number of listener threads. Default value is: (available processors) * 3.
--portint4444Port 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.

SessionQueue

OptionTypeValue/ExampleDescription
--sessionqueueurihttp://localhost:1237Address of the session queue server.
-sessionqueue-hoststringlocalhostHost on which the session queue server is listening.
--sessionqueue-portint1234Port on which the session queue server is listening.
--session-request-timeoutint300Timeout 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-intervalint5Retry interval in seconds. If all slots are busy, new session request will be retried after the given interval.

Sessions

OptionTypeValue/ExampleDescription
--sessionsurihttp://localhost:1234Address of the session map server.
--sessions-hoststringlocalhostHost on which the session map server is listening.
--sessions-portint1234Port on which the session map server is listening.

Configuration examples

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.

Command-line flags

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

Standalone, setting max sessions and main port

java -jar selenium-server-<version>.jar standalone --max-sessions 4 --port 4444

Hub, setting a new session request timeout, a main port, and disabling tracing

java -jar selenium-server-<version>.jar hub --session-request-timeout 500 --port 3333 --tracing false

Node, with 4 max sessions, with debug(fine) log, 7777 as port, and only with Firefox and Edge

java -jar selenium-server-<version>.jar node --max-sessions 4 --log-level "fine" --port 7777 --driver-implementation "firefox" --driver-implementation "edge"

Distributor, setting Session Map server url, Session Queue server url, and disabling bus

java -jar selenium-server-<version>.jar distributor --sessions http://localhost:5556 --sessionqueue http://localhost:5559 --bind-bus false

Setting custom capabilities for matching specific Nodes

Important: Custom capabilities need to be set in the configuration in all Nodes. They also need to be included always in every session request.

Start the Hub
java -jar selenium-server-<version>.jar hub
Start the Node A with custom cap set to 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
Start the Node B with custom cap set to 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
Matching Node A
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.

4.3 - Toml Options

Grid configuration examples using Toml files.

Page being translated from English to Portuguese. Do you speak Portuguese? Help us to translate it by sending us pull requests!

All the options shown in CLI options can be configured through a TOML file. This page shows configuration examples for the different Grid components.

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.

Overview

Selenium Grid uses TOML format for config files. The config file consists of sections and each section has options and its respective value(s).

Refer to the TOML documentation for detailed usage guidance. In case of parsing errors, validate the config using TOML linter.

The general configuration structure has the following pattern:

[section1]
option1="value"

[section2]
option2=["value1","value2"]
option3=true

Below are some examples of Grid components configured with a Toml file, the component can be started in the following way:

java -jar selenium-server-<version>.jar <component> --config /path/to/file/<file-name>.toml

Standalone

A Standalone server, running on port 4449, and a new session request timeout of 500 seconds.

[server]
port = 4449

[sessionqueue]
session-request-timeout = 500

Specific browsers and a limit of max sessions

A Standalone server or a Node which only has Firefox and Chrome enabled by default.

[node]
drivers = ["chrome", "firefox"]
max-sessions = 3

Configuring and customising drivers

Standalone or Node server with customised drivers, which allows things like having Firefox Beta or Nightly, and having different browser versions.

[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'

Standalone or Node with Docker

A Standalone or Node server that is able to run each new session in a Docker container. Disabling drivers detection, having maximum 2 concurrent sessions. Stereotypes configured need to be mapped to a Docker image, and the Docker daemon needs to be exposed via http/tcp. In addition, it is possible to define which device files, accessible on the host, will be available in containers through the devices property. Refer to the docker documentation for more information about how docker device mapping works.

[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"

Relaying commands to a service endpoint that supports WebDriver

It is useful to connect an external service that supports WebDriver to Selenium Grid. An example of such service could be a cloud provider or an Appium server. In this way, Grid can enable more coverage to platforms and versions not present locally.

The following is an en example of connecting an Appium server to 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\"}"
]

Basic auth enabled

It is possible to protect a Grid with basic auth by configuring the Router/Hub/Standalone with a username and password. This user/password combination will be needed when loading the Grid UI or starting a new session.

[router]
username = "admin"
password = "myStrongPassword"

Here is a Java example showing how to start a session using the configured user and password.

URL gridUrl = new URL("http://admin:myStrongPassword@localhost:4444");
RemoteWebDriver webDriver = new RemoteWebDriver(gridUrl, new ChromeOptions());

Setting custom capabilities for matching specific Nodes

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();

5 - Grid Architecture

Page being translated from English to Portuguese. Do you speak Portuguese? 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 Key Components

The main components of the Grid are:

Event Bus
Used for sending messages which may be received asynchronously between the other components.
New Session Queue
Maintains a list of incoming sessions which have yet to be assigned to a Node by the Distributor.
Distributor
Responsible for maintaining a model of the available locations in the Grid where a session may run (known as "slots") and taking any incoming new session requests and assigning them to a slot.
Node
Runs a WebDriver session. Each session is assigned to a slot, and each node has one or more slots.
Session Map
Maintains a mapping between the session ID and the address of the Node the session is running on.
Router
Acts as the front-end of the Grid. This is the only part of the Grid which may be exposed to the wider Web (though we strongly caution against it). This routes incoming requests to either the New Session Queue or the Node on which the session is running.

While discussing the Grid, there are some other useful concepts to keep in mind:

  • A slot is the place where a session can run.
  • Each slot has a stereotype. This is the minimal set of capabilities that a new session session request must match before the Distributor will send that request to the Node owning the slot.
  • The Grid Model is how the Distributor tracks the state of the Grid. As the name suggests, this may sometimes fall out of sync with reality (perhaps because the Distributor has only just started). It is used in preference to querying each Node so that the Distributor can quickly assign a slot to a New Session request.

Synchronous and Asynchronous Calls

There are two main communication mechanisms used within the Grid:

  1. Synchronous “REST-ish” JSON over HTTP requests.
  2. Asynchronous events sent to the Event Bus.

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.

Start Up Sequence and Dependencies Between Components

Although the Grid is designed to allow components to start up in any order, conceptually the order in which components starts is:

  1. The Event Bus and Session Map start first. These have no other dependencies, not even on each other, and so are safe to start in parallel.
  2. The Session Queue starts next.
  3. It is now possible to start the Distributor. This will periodically connect to the Session Queue and poll for jobs, though this polling might be initiated either by an event (that a New Session has been added to the queue) or at regular intervals.
  4. The Router(s) can be started. New Session requests will be directed to the Session Queue, and the Distributor will attempt to find a slot to run the session on.
  5. We are now able to start a Node. See below for details about how the Node is registered with the Grid. Once registration is complete, the Grid is ready to serve traffic.

You can picture the dependencies between components this way, where a “✅” indicates that there is a synchronous dependency between the components.

Event BusDistributorNodeRouterSession MapSession Queue
Event BusX
DistributorX
NodeX
RouterX
Session MapX
Session QueueX

Node Registration

The process of registering a new Node to the Grid is lightweight.

  1. When the Node starts, it should emit a “heart beat” event on a regular basis. This heartbeat contains the node status.
  2. The Distributor listens for the heart beat events. When it sees one, it attempts to 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 Object

The Node Status is a JSON blob with the following fields:

NameTypeDescription
availabilitystringA 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.
externalUrlstringThe URI that the other components in the Grid should connect to.
lastSessionCreatedintegerThe 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.
maxSessionCountintegerAlthough 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”.
nodeIdstringA UUID used to identify this instance of the Node.
osInfoobjectAn object with arch, name, and version fields. This is used by the Grid UI and the GraphQL queries.
slotsarrayAn array of Slot objects (described below)
versionstringThe 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

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.

NameTypeDescription
idstringUUID to refer to the slot
lastStartedstringWhen the slot last had a session started, in ISO-8601 format
stereotypeobjectThe minimal set of capabilities this slot will match against. A minimal example is {"browserName": "firefox"}
sessionobjectThe Session object (see below)

The Session Object

This represents a running session within a slot

NameTypeDescription
capabilitiesobjectThe actual capabilities provided by the session. Will match the return value from the new session command
startTimestringThe start time of the session in ISO-8601 format
stereotypeobjectThe minimal set of capabilities this slot will match against. A minimal example is {"browserName": "firefox"}
uristringThe URI used by the Node to communicate with the session

6 - Advanced Features

To get all the details of the advanced features, understand how it works, and how to set up your own, please browse thorough the following sections.

Page being translated from English to Portuguese. Do you speak Portuguese? Help us to translate it by sending us pull requests!

6.1 - Observability

Page being translated from English to Portuguese. Do you speak Portuguese? Help us to translate it by sending us pull requests!

Table of Contents

Selenium Grid

Grid aids in scaling and distributing tests by executing tests on various browser and operating system combinations.

Observability

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.

Distributed tracing

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.

Event logging

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:

  • Timestamp
  • Logging level
  • Logger class
  • Log message (This is further broken down into fields relevant to the operation where the log was recorded)

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:

  1. https://www.honeycomb.io/blog/how-are-structured-logs-different-from-events/
  2. https://charity.wtf/2019/02/05/logs-vs-structured-events/

Grid Observability

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:

  1. Normal event - records all information about a unit of work and marks successful completion of the work.
  2. Error event - records all information till the error occurs and then records the error information. Marks an exception event.

Running Selenium server

  1. Standalone
  2. Hub and Node
  3. Fully Distributed
  4. Docker

Visualizing Traces

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:

  1. Console - Logs all traces and their included spans at FINE level. By default, Selenium server prints logs at INFO level and above. The log-level flag can be used to pass a logging level of choice while running the Selenium Grid jar/s.
java -jar selenium-server-4.0.0-<selenium-version>.jar standalone --log-level FINE
  1. Jaeger UI - OpenTelemetry provides the APIs and SDKs to instrument traces in the code. Whereas Jaeger is a tracing backend, that aids in collecting the tracing telemetry data and providing querying, filtering and visualizing features for the data.

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

Leveraging event logs

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 :

FieldField valueDescription
Event timeeventIdTimestamp of the event record in epoch nanoseconds.
Trace IdtracedIdEach trace is uniquely identified by a trace id.
Span IdspanIdEach span within a trace is uniquely identified by a span id.
Span KindspanKindSpan 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 nameeventNameThis maps to the log message.
Event attributeseventAttributesThis 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 :

FieldField valueDescription
Exception typeexception.typeThe class name of the exception.
Exception messageexception.messageReason for the exception.
Exception stacktraceexception.stacktracePrints 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.

References

  1. Understanding Tracing
  2. OpenTelemetry Tracing API Specification
  3. Selenium Wiki
  4. Structured logs vs events
  5. Jaeger framework

6.2 - Suporte a buscas em GraphQL

GraphQL é uma linguagem de consulta para APIs e um runtime para atender a essas consultas com seus dados existentes. Ele dá aos usuários o poder de pedir exatamente o que precisam e nada mais.

Enums

Enums representam possíveis conjuntos de valores para um campo.

Por exemplo, o objeto Node possui um campo chamado status. O estado é um enum (especificamente, do tipo Status) porque pode ser UP, DRAINING ou UNAVAILABLE.

Escalares

Escalares são valores primitivos: Int, Float, String, Boolean ou ID.

Ao chamar a API GraphQL, você deve especificar o subcampo aninhado até retornar apenas escalares.

Estrutura do Schema

A estrutura do esquema de grade é a seguinte:

{
    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
                }
            }
        ]
    }
}

Consultando GraphQL

O melhor jeito de consultar GraphQL é utilizando requisições curl. GraphQL permite que você busque apenas os dados que você quer, nada mais, anda menos.

Alguns exemplos de buscas em GraphQL estão abaixo. Você pode montar as queries como quiser.

Buscando o número total de slots (maxSession) e slots usados (sessionCount) na Grid:

curl -X POST -H "Content-Type: application/json" --data '{"query": "{ grid { maxSession, sessionCount } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>

Geralmente na máquina local o <LINK_TO_GRAPHQL_ENDPOINT> será http://localhost:4444/graphql

Buscando todos os detalhes da Sessão, Nó e Grid:

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>

Buscando o número de sessões atual na Grid:

curl -X POST -H "Content-Type: application/json" --data '{"query":"{ grid { sessionCount } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>

Buscando a contagem máxima de sessões na Grid:

curl -X POST -H "Content-Type: application/json" --data '{"query":"{ grid { maxSession } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>

Buscando todos os detalhes de todas as sessões de todos os nós na Grid:

curl -X POST -H "Content-Type: application/json" --data '{"query":"{ sessionsInfo { sessions { id, capabilities, startTime, uri, nodeId, nodeId, sessionDurationMillis } } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>

Buscando informações dos slots de todas as sessões de cada Nó na Grid:

curl -X POST -H "Content-Type: application/json" --data '{"query":"{ sessionsInfo { sessions { id, slot { id, stereotype, lastStarted } } } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>

Buscando informação da sessão para uma sessão específica:

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>

Buscando os recursos de cada nó na Grid:

curl -X POST -H "Content-Type: application/json" --data '{"query": "{ nodesInfo { nodes { stereotypes } } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>

Buscando o status de cada Nó na Grid:

curl -X POST -H "Content-Type: application/json" --data '{"query": "{ nodesInfo { nodes { status } } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>

Buscando a URI de cada Nó e da Grid:

curl -X POST -H "Content-Type: application/json" --data '{"query": "{ nodesInfo { nodes { uri } } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>

Query for getting the current requests in the New Session Queue:

curl -X POST -H "Content-Type: application/json" --data '{"query":"{ sessionsInfo { sessionQueueRequests } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>

Query for getting the New Session Queue size :

curl -X POST -H "Content-Type: application/json" --data '{"query":"{ grid { sessionQueueSize } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>

6.3 - Rotas da Grid

Grid

Status da Grid

O status da Grid fornece o estado atual da grid. Consiste em detalhes sobre cada nó registrado. Para cada nó, o status inclui informações sobre a disponibilidade, sessões e slots do nó.

cURL GET 'http://localhost:4444/status'

No modo Standalone, o URL da Grid é o endereço do servidor Standalone.

No modo Hub-Node, a URL da Grid é o endereço do servidor Hub.

No modo totalmente distribuído, a URL da Grid é o endereço do servidor do roteador.

A URL padrão para todos os modos acima é http://localhost:4444.

Distribuidor

Remover Nó

Para remover o Nó da Grid, use o comando cURL listado abaixo. Ele não interrompe nenhuma sessão em andamento em execução nesse nó. O Node continua rodando como está, a menos que seja explicitamente eliminado. O Distribuidor não está mais ciente do Nó e, portanto, qualquer solicitação de nova sessão correspondente não será encaminhado para esse Nó.

No modo Standalone, a URL do distribuidor é o endereço do servidor Standalone.

No modo Hub-Node, a URL do Distribuidor é o endereço do servidor Hub.

cURL --request DELETE 'http://localhost:4444/se/grid/distributor/node/<node-id>' --header 'X-REGISTRATION-SECRET: <secret> '

No modo totalmente distribuído, a URL é o endereço do servidor Distribuidor.

cURL --request DELETE 'http://localhost:5553/se/grid/distributor/node/<node-id>' --header 'X-REGISTRATION-SECRET: <secret>'

Se nenhum segredo de registro foi configurado durante a configuração da Grid, use

cURL --request DELETE 'http://<Distributor-URL>/se/grid/distributor/node/<node-id>' --header 'X-REGISTRATION-SECRET;'

Drenar Nó

O comando de drenagem de nó é para desligamento normal de nó. A drenagem para o Node após a conclusão de todas as sessões em andamento. No entanto, ele não aceita novas solicitações de sessão.

No modo Standalone, a URL do distribuidor é o endereço do servidor Standalone.

No modo Hub-Node, a URL do Distribuidor é o endereço do servidor Hub.

cURL --request POST 'http://localhost:4444/se/grid/distributor/node/<node-id>/drain' --header 'X-REGISTRATION-SECRET: <secret> '

No modo totalmente distribuído, a URL é o endereço do servidor Distribuidor.

cURL --request POST 'http://localhost:5553/se/grid/distributor/node/<node-id>/drain' --header 'X-REGISTRATION-SECRET: <secret>'

Se nenhum segredo de registro foi configurado durante a configuração da Grid, use

cURL --request POST 'http://<Distributor-URL>/se/grid/distributor/node/<node-id>/drain' --header 'X-REGISTRATION-SECRET;'

Os terminais nesta seção são aplicáveis ao modo Hub-Node e ao modo Grid totalmente distribuída, onde o Nó é executado de forma independente. A URL do Nó padrão é http://localhost:5555 no caso de um Nó. No caso de vários Nós, use Grid status para obter todos os detalhes do Nó e localizar o endereço do Nó.

Status

O status do Nó é essencialmente uma verificação de integridade do Nó. O distribuidor executa ping no status do Nó em intervalos regulares e atualiza o modelo de Grid de acordo. O status inclui informações sobre disponibilidade, sessões e slots.

cURL --request GET 'http://localhost:5555/status'

Drenagem

O Distribuidor passa o comando [drain](# drain-node) para o Nó apropriado identificado pelo ID do Nó. Para drenar o Nó diretamente, use o comando cuRL listado abaixo. Ambos as rotas são válidas e produzem o mesmo resultado. Drenar termina as sessões em andamento antes de interromper o Nó.

cURL --request POST 'http://localhost:5555/se/grid/node/drain' --header 'X-REGISTRATION-SECRET: <secret>'

Se nenhum segredo de registro foi configurado durante a configuração da Grid, use

cURL --request POST 'http://<node-URL>/se/grid/node/drain' --header 'X-REGISTRATION-SECRET;'

Checar dono da sessão

Para verificar se uma sessão pertence a um Nó, use o comando cURL listado abaixo.

cURL --request GET 'http://localhost:5555/se/grid/node/owner/<session-id>' --header 'X-REGISTRATION-SECRET: <secret>'

Se nenhum segredo de registro foi configurado durante a configuração da Grid, use

cURL --request GET 'http://<node-URL>/se/grid/node/owner/<session-id>' --header 'X-REGISTRATION-SECRET;'

Ele retornará true se a sessão pertencer ao Nó, caso contrário, retornará false.

Deletar sessão

A exclusão da sessão encerra a sessão do WebDriver, fecha o driver e o remove do mapa de sessões ativas. Qualquer solicitação usando o id de sessão removido ou reutilizando a instância do driver gerará um erro.

cURL --request DELETE 'http://localhost:5555/se/grid/node/session/<session-id>' --header 'X-REGISTRATION-SECRET: <secret>'

Se nenhum segredo de registro foi configurado durante a configuração da Grid, use

cURL --request DELETE 'http://<node-URL>/se/grid/node/session/<session-id>' --header 'X-REGISTRATION-SECRET;'

Fila de Sessão

Limpar a Fila de Sessão

A Fila de Sessão contém as novas solicitações de sessão. Para limpar a fila, use o comando cURL listado abaixo. Limpar a fila rejeita todas as solicitações na fila. Para cada solicitação, o servidor retorna uma resposta de erro ao respectivo cliente. O resultado do comando clear é o número total de solicitações excluídas.

No modo Standalone, a URL Queue é o endereço do servidor Standalone.

No modo Hub-Node, a URL do enfileirador é o endereço do servidor Hub.

cURL --request DELETE 'http://localhost:4444/se/grid/newsessionqueue/queue' --header 'X-REGISTRATION-SECRET: <secret>'

No modo totalmente distribuído, a URL do enfileirador é o endereço do servidor do Enfileirador de Sessões.

cURL --request DELETE 'http://localhost:5559/se/grid/newsessionqueue/queue' --header 'X-REGISTRATION-SECRET: <secret>'

If no registration secret has been configured while setting up the Grid, then use

cURL --request DELETE 'http://<URL>/se/grid/newsessionqueue/queue' --header 'X-REGISTRATION-SECRET;'

Obter novos pedidos da Fila de Sessão

Novos pedidos da Fila de Sessão contém os novos pedidos de sessão. Para obter os pedidos na Fila, utiliza o comando cURL listado abaixo. É retornado o número total de pedidos na Fila.

No modo Standalone, a URL é a do servidor, em modo Grid, a URL será a do HUB.

cURL --request GET 'http://localhost:4444/se/grid/newsessionqueue/queue'

No modo totalmente distribuido, a URL da Fila é a porta do servidor de Fila.

cURL --request GET 'http://localhost:5559/se/grid/newsessionqueue/queue'

6.4 - Customizing a Node

Page being translated from English to Portugese. Do you speak Portugese? Help us to translate it by sending us pull requests!

How to customize a Node

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:

Custom Node as an uber jar

  1. Create a sample project using your favourite build tool (Maven|Gradle).
  2. Add the below dependency to your sample project.
  3. Add your customized Node to the project.
  4. Build an uber jar to be able to start the Node using java -jar command.
  5. Now start the Node using the 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)

Custom Node as a regular jar

  1. Create a sample project using your favourite build tool (Maven|Gradle).
  2. Add the below dependency to your sample project.
  3. Add your customized Node to the project.
  4. Build a jar of your project using your build tool.
  5. Now start the Node using the command:
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.

Sample customized 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.
    • It can be created by calling 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.
    • You can refer to the source code here to understand its internals.
    • To build it locally refer here.
    • It can be created by calling OneShotNode.create(config), where:
      • OneShotNode belongs to org.openqa.selenium.grid.node.k8s
      • Config belongs to org.openqa.selenium.grid.config

6.5 - External datastore

Page being translated from English to Portuguese. Do you speak Portuguese? Help us to translate it by sending us pull requests!

Table of Contents

Introduction

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.

Setup

  • Coursier - As a dependency resolver, so that we can download maven artifacts on the fly and make them available in our classpath
  • Docker - To manage our PostGreSQL/Redis docker containers.

Database backed Session Map

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.

Steps

You can skip this step if you already have a PostGreSQL database instance available at your disposal.

  • Create a sql file named 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)
 );
  • In the same directory as the 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.

  • Create a Selenium Grid configuration file named 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.

  • Below is a simple shell script (let’s call it 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 
  • The variable names from the above script have been replaced with their actual values for clarity.
  • Remember to substitute localhost with the actual hostname of the machine where your Event-Bus is running.
  • The arguments being passed to coursier are basically the GAV (Group Artifact Version) Maven co-ordinates of:
  • sessions.toml is the configuration file that we created earlier.

Redis backed Session Map

We will spin off a Redis Cache docker container using a docker compose file.

Steps

You can skip this step if you already have a Redis Cache instance available at your disposal.

  • Create a file named 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
  • Create a Selenium Grid configuration file named 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.

  • Below is a simple shell script (let’s call it 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 
  • The variable names from the above script have been replaced with their actual values for clarity.
  • Remember to substitute localhost with the actual hostname of the machine where your Event-Bus is running.
  • The arguments being passed to coursier are basically the GAV (Group Artifact Version) Maven co-ordinates of:
  • sessions.toml is the configuration file that we created earlier.