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”. |
--newsession-threadpool-size | int | 24 | The Distributor uses a fixed-sized thread pool to create new sessions as it consumes new session requests from the queue. This allows configuring the size of the thread pool. The default value is no. of available processors * 3. Note: If the no. of threads is way greater than the available processors it will not always increase the performance. A high number of threads causes more context switching which is an expensive operation. |
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 |
--docker-host-config-keys | string[] | Dns DnsOptions DnsSearch ExtraHosts Binds | Specify which docker host configuration keys should be passed to browser containers. Keys name can be found in the Docker API documentation, or by running docker inspect the node-docker container. |
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[] | SE_START_XVFB SE_START_VNC SE_START_NO_VNC | 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. | |
--connection-limit-per-session | int | 10 | Let X be the maximum number of websocket connections per session.This will ensure one session is not able to exhaust the connection limit of the host. | |
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-protocol-version | string | HTTP/1.1 | Optional, enforce a specific protocol version in HttpClient when communicating with the endpoint service status |
--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. |
--disable-ui | boolean | true | Disable the Grid UI. |
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"
# Optional, enforce a specific protocol version in HttpClient when communicating with the endpoint service status (e.g. HTTP/1.1, HTTP/2)
protocol-version = "HTTP/1.1"
# 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.
ClientConfig clientConfig = ClientConfig.defaultConfig()
.baseUrl(new URL("http://localhost:4444"))
.authenticateAs(new UsernameAndPassword("admin", "myStrongPassword"));
HttpCommandExecutor executor = new HttpCommandExecutor(clientConfig);
RemoteWebDriver driver = new RemoteWebDriver(executor, new ChromeOptions());
In other languages, you can use the URL http://admin:myStrongPassword@localhost:4444
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 - Características avançadas
Para obter todos os detalhes dos recursos avançados, entenda como funciona e como configurar crie o seu próprio, navegue pelas seções a seguir.
6.1 - Observabilidade
Índice
Selenium Grid
O Grid auxilia na escalabilidade e distribuição de testes, executando testes em várias combinações de navegadores e sistemas operacionais.
Observabilidade
A observabilidade tem três pilares: rastreamentos, métricas e registros. Como o Selenium Grid 4 foi projetado para ser totalmente distribuído, a observabilidade tornará mais fácil entender e depurar os detalhes internos.
Rastreamento Distribuído
Uma única solicitação ou transação abrange vários serviços e componentes. O rastreamento acompanha o ciclo de vida da solicitação à medida que cada serviço a executa. Isso é útil para depurar cenários de erro.
Alguns termos-chave usados no contexto de rastreamento são:
Rastreamento
O rastreamento permite rastrear uma solicitação por meio de vários serviços, desde sua origem até seu destino final. A jornada dessa solicitação ajuda na depuração, no monitoramento do fluxo de ponta a ponta e na identificação de falhas. Um rastreamento representa o fluxo da solicitação de ponta a ponta. Cada rastreamento possui um identificador único.
Segmento
Cada rastreamento é composto por operações cronometradas chamadas segmentos. Um segmento possui um horário de início e término e representa operações realizadas por um serviço. A granularidade do segmento depende de como ele é instrumentado. Cada segmento possui um identificador único. Todos os segmentos dentro de um rastreamento têm o mesmo ID de rastreamento.
Atributos de Segmento
Atributos de segmento são pares de chave e valor que fornecem informações adicionais sobre cada segmento.
Eventos
Eventos são registros com carimbo de data/hora dentro de um segmento. Eles fornecem contexto adicional para os segmentos existentes. Os eventos também contêm pares de chave e valor como atributos de evento.
Registro de Eventos
O registro é essencial para depurar um aplicativo. O registro é frequentemente feito em um formato legível por humanos. Mas, para que as máquinas possam pesquisar e analisar os registros, é necessário ter um formato bem definido. O registro estruturado é uma prática comum de registrar logs de forma consistente em um formato fixo. Ele normalmente contém campos como:
- Carimbo de data/horas
- Nível de registro
- Classe de registro
- Mensagem de registro (isso é detalhado em campos relevantes à operação em que o registro foi feito)
Registros e eventos estão intimamente relacionados. Os eventos encapsulam todas as informações possíveis para realizar uma única unidade de trabalho. Os registros são essencialmente subconjuntos de um evento. No cerne, ambos auxiliam na depuração.
Consulte os recursos a seguir para entender em detalhes:
- https://www.honeycomb.io/blog/how-are-structured-logs-different-from-events/
- https://charity.wtf/2019/02/05/logs-vs-structured-events/
Observabilidade do Grid
O servidor Selenium é instrumentado com rastreamento usando o OpenTelemetry. Cada solicitação ao servidor é rastreada do início ao fim. Cada rastreamento consiste em uma série de segmentos à medida que uma solicitação é executada no servidor. A maioria dos segmentos no servidor Selenium consiste em dois eventos:
- Evento normal - registra todas as informações sobre uma unidade de trabalho e marca a conclusão bem-sucedida do trabalho.
- Evento de erro - registra todas as informações até que ocorra o erro e, em seguida, registra as informações do erro. Marca um evento de exceção.
Executando servidor Selenium
- Standalone
- Hub and Node
- Fully Distributed
- Docker
Visualizando Rastreamentos
Todos os segmentos, eventos e seus respectivos atributos fazem parte de um rastreamento. O rastreamento funciona enquanto o servidor é executado em todos os modos mencionados acima.
Por padrão, o rastreamento está habilitado no servidor Selenium. O servidor Selenium exporta os rastreamentos por meio de dois exportadores:
- Console - Registra todos os rastreamentos e os segmentos incluídos com nível FINE. Por padrão, o servidor Selenium imprime registros no nível INFO e acima.
A opção log-level pode ser usada para definir um nível de registro de sua escolha ao executar o arquivo JAR do Selenium Grid jar/s.
java -jar selenium-server-4.0.0-<selenium-version>.jar standalone --log-level FINE
- Jaeger UI - OpenTelemetry fornece as APIs e SDKs para instrumentar rastreamentos no código. Enquanto o Jaeger é um sistema de rastreamento de backend que auxilia na coleta de dados de telemetria de rastreamento e oferece recursos de consulta, filtragem e visualização dos dados.
Instruções detalhadas sobre como visualizar rastreamentos usando a interface do Jaeger podem ser obtidas executando o seguinte comando:
java -jar selenium-server-4.0.0-<selenium-version>.jar info tracing
Um exemplo muito bom e scripts para executar o servidor e enviar rastreamentos para o Jaeger
Explorando logs de eventos
O rastreamento deve estar habilitado para o registro de eventos, mesmo que alguém não deseje exportar rastreamentos para visualizá-los.
Por padrão, o rastreamento está habilitado. Não é necessário passar parâmetros adicionais para ver os logs no console.
Todos os eventos dentro de um segmento são registrados no nível FINE. Eventos de erro são registrados no nível WARN..
Campo | Valor do Campo | Descrição |
---|
Hora do Evento | eventId | Carimbo de data/hora do registro do evento em nanossegundos desde a época. |
ID de Rastreamento | tracedId | Cada rastreamento é identificado exclusivamente por um ID de rastreamento. |
ID de Segmento | spanId | Cada segmento dentro de um rastreamento é identificado exclusivamente por um ID de segmento. |
Tipo de Segmento | spanKind | O tipo de segmento é uma propriedade do segmento que indica o tipo de segmento. Isso ajuda a entender a natureza da unidade de trabalho realizada pelo segmento. |
Nome do Evento | eventName | Isso mapeia para a mensagem de registro. |
Atributos do Evento | eventAttributes | Isso forma a essência dos registros de eventos, com base na operação executada, ele contém pares chave-valor formatados em JSON. Isso também inclui um atributo de classe do manipulador, para mostrar a classe do registro. |
Simples 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"
}
}
Além dos campos mencionados anteriormente, com base na especificação do OpenTelemetry os registros de erro consistem nos seguintes campos: :
Campo | Valor do Campo | Descrição |
---|
Tipo de Exceção | exception.type | O nome da classe da exceção. |
Mensagem da Exceção | exception.message | Motivo da exceção. |
Rastreamento de Exceção | exception.stacktrace | Imprime a pilha de chamadas no momento em que a exceção foi lançada. Ajuda a entender a origem da exceção. |
Simples 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"
}
}
Observação: Os logs são formatados acima para facilitar a leitura. A formatação de logs está desativada no servidor Selenium.
Os passos acima devem configurá-lo para visualizar rastreamentos e logs.
Referências
- Compreendendo o Rastreamento
- Especificação da API de Rastreamento do OpenTelemetry
- Selenium Wiki
- Logs Estruturados vs. Eventos
- Framework Jaeger
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'
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:4444/session/<session-id>'
Which URL should I use?
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 Router.
cURL --request DELETE 'http://localhost:4444/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://<Router-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 Router.
cURL --request POST 'http://localhost:4444/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://<Router-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:4444/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://<Router-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:4444/se/grid/newsessionqueue/queue'
6.4 - Personalizando um Nó
Como personalizar um Nó
Há momentos em que gostaríamos de personalizar um Nó de acordo com nossas necessidades.
Por exemplo, podemos desejar fazer alguma configuração adicional antes que uma sessão comece a ser executada e executar alguma limpeza após o término de uma sessão.
Os seguintes passos podem ser seguidos para isso:
Crie uma classe que estenda org.openqa.selenium.grid.node.Node
.
Adicione um método estático (este será nosso método de fábrica) à classe recém-criada, cuja assinatura se parece com esta:
public static Node create(Config config)
. Here:
Node
é do tipo org.openqa.selenium.grid.node.Node
Config
é do tipo org.openqa.selenium.grid.config.Config
- Dentro deste método de fábrica, inclua a lógica para criar sua nova classe..
- TPara incorporar esta nova lógica personalizada no hub, inicie o nó e passe o nome da classe totalmente qualificado da classe acima como argumento.
--node-implementation
Vamos ver um exemplo de tudo isso:
Node personalizado como um uber jar
- Crie um projeto de exemplo usando sua ferramenta de construção favorita. (Maven|Gradle).
- Adicione a seguinte dependência ao seu projeto de exemplo..
- Adicione o seu nó personalizado ao projeto.
- Construir algo. uber jar Para ser capaz de iniciar o Node usando o comando
java -jar
. - Agora inicie o nó usando o comando:
java -jar custom_node-server.jar node \
--node-implementation org.seleniumhq.samples.DecoratedLoggingNode
Observação: Se estiver usando o Maven como ferramenta de construção, é preferível usar o maven-shade-plugin em vez do maven-assembly-plugin porque o plugin maven-assembly parece ter problemas para mesclar vários arquivos de Service Provider Interface (META-INF/services
).
Node personalizado como jar
- Crie um projeto de exemplo usando a sua ferramenta de construção favorita (Maven|Gradle).
- Adicione a seguinte dependência ao seu projeto de exemplo:
- Adicione o seu Node personalizado ao projeto.
- Construa um arquivo JAR do seu projeto usando a sua ferramenta de construção.
- Agora, inicie o Node usando o seguinte comando:
java -jar selenium-server-4.6.0.jar \
--ext custom_node-1.0-SNAPSHOT.jar node \
--node-implementation org.seleniumhq.samples.DecoratedLoggingNode
Aqui está um exemplo que apenas imprime algumas mensagens no console sempre que houver uma atividade de interesse (sessão criada, sessão excluída, execução de um comando do webdriver, etc.) no 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, NodeId nodeId, URI uri, Secret registrationSecret, Duration sessionTimeout) {
super(tracer, nodeId, uri, registrationSecret, sessionTimeout);
}
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);
NodeOptions nodeOptions = new NodeOptions(config);
Duration sessionTimeout = nodeOptions.getSessionTimeout();
// Refer to the foot notes for additional context on this line.
Node node = LocalNodeFactory.create(config);
DecoratedLoggingNode wrapper = new DecoratedLoggingNode(loggingOptions.getTracer(),
node.getId(),
uri,
secretOptions.getRegistrationSecret(),
sessionTimeout);
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()");
}
}
}
Notas de Rodapé:
No exemplo acima, a linha Node node = LocalNodeFactory.create(config);
cria explicitamente um LocalNode
.
Basicamente, existem 2 tipos de implementações visíveis para o usuário de org.openqa.selenium.grid.node.Node
disponíveis.
Essas classes são bons pontos de partida para aprender como criar um Node personalizado e também para compreender os detalhes internos de um Node.
org.openqa.selenium.grid.node.local.LocalNode
- Usado para representar um Node de execução contínua e é a implementação padrão que é usada quando você inicia um node
.- Pode ser criado chamando
LocalNodeFactory.create(config);
, onde:LocalNodeFactory
pertence a org.openqa.selenium.grid.node.local
Config
pertence a org.openqa.selenium.grid.config
org.openqa.selenium.grid.node.k8s.OneShotNode
- Esta é uma implementação de referência especial em que o Node encerra-se graciosamente após atender a uma sessão de teste. Esta classe atualmente não está disponível como parte de nenhum artefato Maven pré-construído.- Você pode consultar o código-fonte aqui para entender seus detalhes internos.
- Para construí-lo localmente, consulte aqui.
- Pode ser criado chamando
OneShotNode.create(config)
, onde:OneShotNode
pertence a org.openqa.selenium.grid.node.k8s
Config
pertence a 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.