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
- 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
- Iniciar a Grid
java -jar selenium-server-<version>.jar standalone
- Aponte* os seus testes WebDriver para http://localhost:4444
- (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.
É de extrema importância expor todas as portas necessárias de forma a que a comunicação flua correctamente entre todos os componentes.
- 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
- 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
- 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
- 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
- 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
- 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
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.
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.
Configurando a sua Grid
Se pretende usar todos estes componentes para criar a sua Grid,
visite a secção
“configurando a sua”
para entender como ligar as peças todas.
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
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
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
andChrome
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 com.google.common.collect.ImmutableMap;
import org.openqa.selenium.By;
import org.openqa.selenium.io.Zip;
import org.openqa.selenium.json.Json;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.remote.http.HttpClient;
import org.openqa.selenium.remote.http.HttpRequest;
import org.openqa.selenium.remote.http.HttpResponse;
import java.io.File;
import java.net.URL;
import java.nio.file.Files;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import static org.openqa.selenium.remote.http.Contents.asJson;
import static org.openqa.selenium.remote.http.Contents.string;
import static org.openqa.selenium.remote.http.HttpMethod.GET;
import static org.openqa.selenium.remote.http.HttpMethod.POST;
public class DownloadsSample {
public static void main(String[] args) throws Exception {
// Assuming the Grid is running locally.
URL gridUrl = new URL("http://localhost:4444");
ChromeOptions options = new ChromeOptions();
options.setCapability("se:downloadsEnabled", true);
RemoteWebDriver driver = new RemoteWebDriver(gridUrl, options);
try {
demoFileDownloads(driver, gridUrl);
} finally {
driver.quit();
}
}
private static void demoFileDownloads(RemoteWebDriver driver, URL gridUrl) throws Exception {
driver.get("https://www.selenium.dev/selenium/web/downloads/download.html");
// Download the two available files on the page
driver.findElement(By.id("file-1")).click();
driver.findElement(By.id("file-2")).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 downloadsEndpoint = String.format("/session/%s/se/files", driver.getSessionId());
String fileToDownload;
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(GET, downloadsEndpoint);
HttpResponse response = client.execute(request);
Map<String, Object> jsonResponse = new Json().toType(string(response), Json.MAP_TYPE);
@SuppressWarnings("unchecked")
Map<String, Object> value = (Map<String, Object>) jsonResponse.get("value");
@SuppressWarnings("unchecked")
List<String> names = (List<String>) value.get("names");
// Let's say there were "n" files downloaded for the current session, we would like
// to retrieve ONLY the first file.
fileToDownload = names.get(0);
}
// Now, let's download the file
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(POST, downloadsEndpoint);
request.setContent(asJson(ImmutableMap.of("name", fileToDownload)));
HttpResponse response = client.execute(request);
Map<String, Object> jsonResponse = new Json().toType(string(response), Json.MAP_TYPE);
@SuppressWarnings("unchecked")
Map<String, Object> value = (Map<String, Object>) jsonResponse.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 zippedContents = value.get("contents").toString();
// The file contents would always be a zip file and has to be unzipped.
File downloadDir = Zip.unzipToTempDir(zippedContents, "download", "");
// Read the file contents
File downloadedFile = Optional.ofNullable(downloadDir.listFiles()).orElse(new File[]{})[0];
String fileContent = String.join("", Files.readAllLines(downloadedFile.toPath()));
System.out.println("The file which was "
+ "downloaded in the node is now available in the directory: "
+ downloadDir.getAbsolutePath() + " and has the contents: " + fileContent);
}
}
}
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:
- Chamadas Síncronas “REST-ish” que usam JSON sobre pedidos HTTP.
- 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:
- 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.
- A Session Queue inicia de seguida
- 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.
- 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.
- 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”.
- Quando um Node inicia, vai publicar um evento “heart beat” numa
base regular. Este evento contém o estado do Node.
- 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:
- https://www.honeycomb.io/blog/how-are-structured-logs-different-from-events/
- 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:
- Normal event - records all information about a unit of work and marks successful completion of the work.
- Error event - records all information till the error occurs and then records the error information. Marks an exception event.
Running Selenium server
- Standalone
- Hub and Node
- Fully Distributed
- 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:
- 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
- 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
- Understanding Tracing
- OpenTelemetry Tracing API Specification
- Selenium Wiki
- Structured logs vs events
- 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>
curl -X POST -H "Content-Type: application/json" --data '{"query":"{ sessionsInfo { sessions { id, slot { id, stereotype, lastStarted } } } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
curl -X POST -H "Content-Type: application/json" --data '{"query":"{ session (id: \"<session-id>\") { id, capabilities, startTime, uri, nodeId, nodeUri, sessionDurationMillis, slot { id, stereotype, lastStarted } } } "}' -s <LINK_TO_GRAPHQL_ENDPOINT>
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;'
Nó
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
- Create a sample project using your favourite build tool (Maven|Gradle).
- Add the below dependency to your sample project.
- Add your customized Node to the project.
- Build an uber jar to be able to start the Node using
java -jar
command. - 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
- Create a sample project using your favourite build tool (Maven|Gradle).
- Add the below dependency to your sample project.
- Add your customized Node to the project.
- Build a jar of your project using your build tool.
- 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:
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:
- 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.