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 - Opções CLI

Todas os detalhes das opções CLI de cada componente Grid.

Diferentes secções estão disponíveis para configurar uma Grid. Cada secção tem opções que podem ser configuradas através de opções CLI.

Pode ver abaixo um mapeamento entre o componente e a secção respectiva.

Note que esta documentação pode estar desactualizada se uma opção foi adicionada ou modificada, mas ainda não ter havido oportunidade de actualizar a documentação. Caso depare com esta situação, verifique a secção “ajuda de configuração” e esteja à vontade para nos enviar um pull request com alterações a esta página.

Secções

Standalone Hub Node Distributor Router Sessions SessionQueue
Distributor
Docker
Events
Logging
Network
Node
Router
Relay
Server
SessionQueue
Sessions

Distributor

Opção Tipo Valor/Exemplo Descrição
--healthcheck-interval int 120 Tempo em segundos em que se verifica o estado dos Nodes. Isto garante que o servidor consecontactar cada um dos Nodes com sucesso.
--distributor uri http://localhost:5553 Url do Distributor.
--distributor-host string localhost Host onde o Distributor está à escuta.
--distributor-implementation string org.openqa.selenium.grid.distributor.local.LocalDistributor Nome completo da class para uma implementação não padrão do Distributor.
--distributor-port int 5553 Porta onde o Distributor está à escuta.
--reject-unsupported-caps boolean false Permitir que o Distributor rejeite imediatamente um pedido de sessão se a Grid não suportar a capacidade pedida. Esta configuração é a ideal para Grid que não inicie Nodes a pedido.
--slot-matcher string org.openqa.selenium.grid.data.DefaultSlotMatcher Nome completo da class para uma implementação não padrão do comparador de slots. Isto é usado para determinar se um Node pode suportar uma sessão em particular.
--slot-selector string org.openqa.selenium.grid.distributor.selector.DefaultSlotSelector Nome completo da class para uma implementação não padrão do selector de slots. Isto é usado para selecionar um slot no Node caso tenha sido “matched”.

Docker

Opção Tipo Valor/Exemplo Descrição
--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

Events

Opção Tipo Valor/Exemplo Descrição
--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

Logging

Opção Tipo Valor/Exemplo Descrição
--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

Network

Opção Tipo Valor/Exemplo Descrição
--relax-checks boolean false Relax checks on origin header and content type of incoming requests, in contravention of strict W3C spec compliance.

Node

Opção Tipo Valor/Exemplo Descrição
--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.
--enable-managed-downloads boolean false This causes the Node to auto manage files downloaded for a given session on the Node.
--selenium-manager boolean false When drivers are not available on the current system, use Selenium Manager. False by default.

Relay

Opção Tipo Valor/Exemplo Descrição
--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.

Router

Opção Tipo Valor/Exemplo Descrição
--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.
--sub-path string my_company/selenium_grid A sub-path that should be considered for all user facing routes on the Hub/Router/Standalone.

Server

Opção Tipo Valor/Exemplo Descrição
--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.

SessionQueue

Opção Tipo Valor/Exemplo Descrição
--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.

Sessions

Opção Tipo Valor/Exemplo Descrição
--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.

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.

Enabling Managed downloads by the Node

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.

Start the Hub
java -jar selenium-server-<version>.jar hub
Start the Node with manage downloads enabled
java -jar selenium-server-<version>.jar node --enable-managed-downloads true
Set the capability at the test level

Tests that want to use this feature should set the capability "se:downloadsEnabled"to true

options.setCapability("se:downloadsEnabled", true);
How does this work
  • The Grid infrastructure will try to match a session request with "se:downloadsEnabled" against ONLY those nodes which were started with --enable-managed-downloads true
  • If a session is matched, then the Node automatically sets the required capabilities to let the browser know, as to where should a file be downloaded.
  • The Node now allows a user to:
    • List all the files that were downloaded for a specific session and
    • Retrieve a specific file from the list of files.
  • The directory into which files were downloaded for a specific session gets automatically cleaned up when the session ends (or) timesout due to inactivity.

Note: Currently this capability is ONLY supported on:

  • Edge
  • Firefox and
  • Chrome browser
Listing files that can be downloaded for current session:
  • The endpoint to GET from is /session/<sessionId>/se/files.
  • The session needs to be active in order for the command to work.
  • The raw response looks like below:
{
  "value": {
    "names": [
      "Red-blue-green-channel.jpg"
    ]
  }
}

In the response the list of file names appear under the key names.

Dowloading a file:
  • The endpoint to POST from is /session/<sessionId>/se/files with a payload of the form {"name": "fileNameGoesHere}
  • The session needs to be active in order for the command to work.
  • The raw response looks like below:
{
  "value": {
    "filename": "Red-blue-green-channel.jpg",
    "contents": "Base64EncodedStringContentsOfDownloadedFileAsZipGoesHere"
  }
}
  • The response blob contains two keys,
    • filename - The file name that was downloaded.
    • contents - Base64 encoded zipped contents of the file.
  • The file contents are Base64 encoded and they need to be unzipped.
List files that can be downloaded

The below mentioned curl example can be used to list all the files that were downloaded by the current session in the Node, and which can be retrieved locally.

curl -X GET "http://localhost:4444/session/90c0149a-2e75-424d-857a-e78734943d4c/se/files"

A sample response would look like below:

{
  "value": {
    "names": [
      "Red-blue-green-channel.jpg"
    ]
  }
}
Retrieve a downloaded file

Assuming the downloaded file is named Red-blue-green-channel.jpg, and using curl, the file could be downloaded with the following command:

curl -H "Accept: application/json" \
-H "Content-Type: application/json; charset=utf-8" \
-X POST -d '{"name":"Red-blue-green-channel.jpg"}' \
"http://localhost:4444/session/18033434-fa4f-4d11-a7df-9e6d75920e19/se/files"

A sample response would look like below:

{
  "value": {
    "filename": "Red-blue-green-channel.jpg",
    "contents": "UEsDBBQACAgIAJpagVYAAAAAAAAAAAAAAAAaAAAAUmVkLWJsAAAAAAAAAAAAUmVkLWJsdWUtZ3JlZW4tY2hhbm5lbC5qcGdQSwUGAAAAAAEAAQBIAAAAcNkAAAAA"
  }
}
Complete sample code in Java

Below is an example in Java that does the following:

  • Sets the capability to indicate that the test requires automatic managing of downloaded files.
  • Triggers a file download via a browser.
  • Lists the files that are available for retrieval from the remote node (These are essentially files that were downloaded in the current session)
  • Picks one file and downloads the file from the remote node to the local machine.
import static java.util.Collections.singletonMap;
import static org.openqa.selenium.remote.http.Contents.string;

import java.io.File;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.List;
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.json.TypeToken;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.remote.http.Contents;
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 Exception {
    // Assuming the Grid is running locally.
    URL gridUrl = new URL("http://localhost:4444");
    RemoteWebDriver driver = new RemoteWebDriver(gridUrl, firefoxOptions());
    try {
      demoFileDownloads(driver, gridUrl);
    } finally {
      driver.quit();
    }
  }

  private static void demoFileDownloads(RemoteWebDriver driver, URL gridUrl) throws Exception {
    // Make sure the following directory exists on your machine
    File dirToCopyTo = new File("/usr/downloads/file");
    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 it 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);

    //This is the endpoint which will provide us with list of files to download and also to
    //let us download a specific file.
    String uri = String.format("/session/%s/se/files", driver.getSessionId());

    String fileToDownload = null;

    try (HttpClient client = HttpClient.Factory.createDefault().createClient(gridUrl)) {
      // To list all files that are were downloaded on the remote node for the current session
      // we trigger GET request.
      HttpRequest request = new HttpRequest(HttpMethod.GET, uri);
      HttpResponse response = client.execute(request);
      String text = string(response);
      Type responseType = new TypeToken<Map<String, Map<String, List<String>>>>() {
      }.getType();
      Map<String, Map<String, List<String>>> map = new Json().toType(text, responseType);
      Map<String, List<String>> parsedResponse = map.get("value");
      for (String eachFile : parsedResponse.get("names")) {
        if (fileToDownload == null) {
          // Let's say there were "n" files downloaded for the current session, we would like
          // to retrieve ONLY the first file.
          fileToDownload = eachFile;
        }
      }
    }
    try (HttpClient client = HttpClient.Factory.createDefault().createClient(gridUrl)) {
      // To retrieve a specific file from one or more files that were downloaded by the current session
      // on a remote node, we use a POST request.

      HttpRequest request = new HttpRequest(HttpMethod.POST, uri);
      request.setContent(Contents.asJson(singletonMap("name", fileToDownload)));
      HttpResponse response = client.execute(request);
      String text = string(response);
      Type responseType = new TypeToken<Map<String, Map<String, String>>>() {
      }.getType();

      Map<String, Map<String, String>> map = new Json().toType(text, responseType);
      Map<String, String> parsedResponse = map.get("value");
      // 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 = parsedResponse.get("contents");

      //The file contents would always be a zip file and has to be unzipped.
      Zip.unzip(encodedContents, dirToCopyTo);
      System.out.println("The file which was "
          + "downloaded in the node is now available in the directory: "
          + dirToCopyTo.getAbsolutePath());
    }
  }

  private static FirefoxOptions firefoxOptions() {
    FirefoxOptions options = new FirefoxOptions();
    // This capability tells the Grid, that this test should be routed ONLY to a node that can
    // auto manage downloads.
    options.setCapability("se:downloadsEnabled", true);
    // 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;
  }
}

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

Enabling Managed downloads by the Node.

The Node can be instructed to manage downloads automatically. This will cause the Node to save all files that were downloaded for a particular session into a temp directory, which can later be retrieved from the node. To turn this capability on, use the below configuration:

[node]
enable-managed-downloads = true

Refer to the CLI section for a complete example.

5 - Arquitectura da Grid

A Grid está desenhada como um conjunto de componentes, em que cada tem o seu papel crucial em manter a Grid. Isto pode parecer um pouco complicado, mas esperamos que este documento ajude a esclarecer alguma confusão.

Os componentes chave

Os componentes principais da Grid são:

Event Bus
Usado para enviar mensagens que podem ser recebidas de forma assíncrona entre os outros componentes.
New Session Queue
Mantém uma lista de pedidos de sessão que serão assignadas a um Node pelo Distributor.
Distributor
Responsável por manter um modelo das localizações da Grid (slots) onde uma sessão pode ser lançada e também por aceitar novos pedidos de sessão e assignar a um slot livre.
Node
Executa uma sessão WebDriver. Cada sessão é assignada a um slot e cada Node tem um ou mais slots.
Session Map
Mantém um mapeamento entre um ID de sessão e o endereço do Node onde a sessão está a ser executada.
Router
Este é o ponto de entrada da Grid. É também a única parte da Grid que poderá estar exposta à Internet (embora nós não recomendemos). Este componente reencaminha novos pedidos para New Session Queue ou para o Node onde a sessão esteja a ser executada

Ao falar da Grid, há alguns conceitos úteis a ter em mente:

  • Um slot é o sítio onde uma sessão pode ser executada
  • Cada slot tem um stereotype. Isto é um conjunto mínimo de capacidades que um pedido de nova sessão terá que corresponder antes que o Distributor envie esse pedido ao Node que tenha esse slot
  • O Grid Model é como o Distributor mantém o estado actual da Grid. Como o nome sugere, este modelo pode perder o sincronismo com a realidade. Este mecanismo é preferível do que estar a questionar cada Node, e desta forma, o Distributor rapidamente consegue alocar uma nova sessão a um slot.

Chamadas Síncronas e Assíncronas

Existem duas formas de comunicação dentro da Grid:

  1. Chamadas Síncronas “REST-ish” que usam JSON sobre pedidos HTTP.
  2. Eventos Assíncronos enviados para o Event Bus.

Como é que escolhemos que tipo de mecanismo de comunicação a usar? Afinal, podiamos ter escolhido usar apenas comunicação baseada em eventos e tudo iria funcionar sem problemas.

No entanto a resposta correcta é, se a acção em curso é síncrona, por exemplo a maioria das chamadas WebDriver, ou se perder uma resposta é problemático, a Grid usa chamadas síncronas. Se quisermos propagar informação que pode ter várias partes interessadas, ou se perder a mensagem não for crítico, a Grid usará o event bus.

Um facto interessante a notar é que as chamadas assíncronas estão menos “presas” aos processos que as executam do que todas as chamadas síncronas.

Sequência de início e dependencias entre componentes

Embora a Grid seja desenhada para permitir que os componentes possam iniciar em qualquer ordem, conceptualmente é esperado que a ordem de início seja:

  1. O Event Bus e o Session Map iniciam primeiro. Estes componentes não tem qualquer dependencia, nem mesmo entre eles e como tal, podem iniciar em paralelo.
  2. A Session Queue inicia de seguida
  3. O Distributor inicia. Irá periodicamente procurar novos pedidos de sessão na Session Queue, embora possa também receber um evento de um pedidos de sessão.
  4. O Router pode ser agora iniciado. Novos pedidos de sessão são direccionados para a Session Queue, o Distributor tentará encontrar um slot onde a sessão possa ser executada.
  5. O Node pode ser iniciado, veja mais abaixo os detalhes de como o Node se regista na Grid. Uma vez que o registo esteja concluído, a Grid estará pronta a receber pedidos.

Nesta tabela pode ser visualizada a dependencia ente os vários componentes. Um “✅” indica que a dependência é síncrona.

Event Bus Distributor Node Router Session Map Session Queue
Event Bus X
Distributor X
Node X
Router X
Session Map X
Session Queue X

Registo de Node

O processo de registar um Node na Grid é um processo “leve”.

  1. Quando um Node inicia, vai publicar um evento “heart beat” numa base regular. Este evento contém o estado do Node.
  2. O Distributor escuta os eventos “heart beat” e quando obtém um, tenta um GET ao endpoint /status do Node. A Grid é preparada com base nesta informação.

O Distributor irá usar regularmente o endpoint /status para continuar a obter o estado do Node. Por seu lado, o Node continua a publicar um evento “heart beat” mesmo depois do registo ter sido concluído com sucesso. Isto é feito para que mesmo que um Distributor não tenha um estado da Grid possa reiniciar e assim obter novamente uma visão do estado da Grid e assim ficar actualizado.

Objecto Node Status

O Node Status é um blob JSON com os seguintes campos:

Nome Tipo Descrição
availability string Uma string com up, draining, ou down. A mais importante é draining, que indica que não devem ser enviados novos pedidos de sessão para o Node e assim que a última sessão termine, o Node irá reiniciar ou concluir.
externalUrl string Uma URI que os outros componentes da Grid se devem ligar.
lastSessionCreated integer Um timestamp da última sessão que foi criada neste Node. O Distributor irá tentar enviar novos pedidos de sessão para o Node que esteja parado há mais tempo.
maxSessionCount integer Embora seja possível inferir o número máximo de sessões a partir da lista de slots disponíveis, este número é usado para determinar qual é o máximo de sessões que este Node pode executar em simultâneo antes que se considere que está “cheio”.
nodeId string Um identificador UUID para esta instância do Node.
osInfo object Um objecto contendo os campos arch, name, e version. Isto é usado pela Grid UI e pelas queries GraphQL.
slots array Um array de objectos Slot (descritos na secção seguinte)
version string A versão do Node (para Selenium, será igual à versão do Selenium)

É recomendado que todos os campos tenham valores.

O Objecto Slot

O objecto Slot representa um slot dentro de um Node. Um “slot” é onde uma sessão consegue ser executada. É possível que um Node tenha mais do que um Slot capaz de executar ao mesmo tempo. Por exemplo, um Node pode ser capaz de executar até 10 sessões em simultâneo, mas podem ser uma qualquer combinação de Chrome, Firefox ou Edge e neste caso, o Node irá indicar 10 como o número máximo de sessões, indicando que podem ser 10 Chrome, 10 Firefox e 10 Edge.

Nome Tipo Descrição
id string Um identificador UUID para este slot
lastStarted string timestamp no formato ISO-8601 contendo a data em que a última sessão iniciou
stereotype object Conjunto mínimo de capacidades que fazem match com este slot. O exemplo mínimo será por exemplo {"browserName": "firefox"}
session object O objecto Session (descrito na secção seguinte)

O Objecto Session

Representa uma sessão em execução dentro de um Slot

Nome Tipo Descrição
capabilities object A lista de capacidades fornecidas pela sessão. Irá coincidir com o valor obtido pelo comando nova sessão
startTime string timestamp no formato ISO-8601 contendo a data em que a última sessão iniciou
stereotype object Conjunto mínimo de capacidades que fazem match com este slot. O exemplo mínimo será por exemplo {"browserName": "firefox"}
uri string A URI usada pelo Node para comunicar com a sessão

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 :

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.

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.