This is the multi-page printable view of this section. Click here to print.
Características avançadas
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
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
2 - Suporte a buscas em GraphQL
GraphQL é uma linguagem de consulta para APIs e um runtime para atender a essas consultas com seus dados existentes. Ele dá aos usuários o poder de pedir exatamente o que precisam e nada mais.
Enums
Enums representam possíveis conjuntos de valores para um campo.
Por exemplo, o objeto Node
possui um campo chamado status
. O estado é um enum (especificamente, do tipo Status
) porque pode ser UP
, DRAINING
ou UNAVAILABLE
.
Escalares
Escalares são valores primitivos: Int
, Float
, String
, Boolean
ou ID
.
Ao chamar a API GraphQL, você deve especificar o subcampo aninhado até retornar apenas escalares.
Estrutura do Schema
A estrutura do esquema de grade é a seguinte:
{
session(id: "<session-id>") : {
id,
capabilities,
startTime,
uri,
nodeId,
nodeUri,
sessionDurationMillis
slot : {
id,
stereotype,
lastStarted
}
}
grid: {
uri,
totalSlots,
nodeCount,
maxSession,
sessionCount,
version,
sessionQueueSize
}
sessionsInfo: {
sessionQueueRequests,
sessions: [
{
id,
capabilities,
startTime,
uri,
nodeId,
nodeUri,
sessionDurationMillis
slot : {
id,
stereotype,
lastStarted
}
}
]
}
nodesInfo: {
nodes : [
{
id,
uri,
status,
maxSession,
slotCount,
sessions: [
{
id,
capabilities,
startTime,
uri,
nodeId,
nodeUri,
sessionDurationMillis
slot : {
id,
stereotype,
lastStarted
}
}
],
sessionCount,
stereotypes,
version,
osInfo: {
arch,
name,
version
}
}
]
}
}
Consultando GraphQL
O melhor jeito de consultar GraphQL é utilizando requisições curl
. GraphQL permite que você busque apenas os dados que você quer, nada mais, anda menos.
Alguns exemplos de buscas em GraphQL estão abaixo. Você pode montar as queries como quiser.
Buscando o número total de slots (maxSession
) e slots usados (sessionCount
) na Grid:
curl -X POST -H "Content-Type: application/json" --data '{"query": "{ grid { maxSession, sessionCount } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
Geralmente na máquina local o <LINK_TO_GRAPHQL_ENDPOINT>
será http://localhost:4444/graphql
Buscando todos os detalhes da Sessão, Nó e Grid:
curl -X POST -H "Content-Type: application/json" --data '{"query":"{ grid { uri, maxSession, sessionCount }, nodesInfo { nodes { id, uri, status, sessions { id, capabilities, startTime, uri, nodeId, nodeUri, sessionDurationMillis, slot { id, stereotype, lastStarted } }, slotCount, sessionCount }} }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
Buscando o número de sessões atual na Grid:
curl -X POST -H "Content-Type: application/json" --data '{"query":"{ grid { sessionCount } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
Buscando a contagem máxima de sessões na Grid:
curl -X POST -H "Content-Type: application/json" --data '{"query":"{ grid { maxSession } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
Buscando todos os detalhes de todas as sessões de todos os nós na Grid:
curl -X POST -H "Content-Type: application/json" --data '{"query":"{ sessionsInfo { sessions { id, capabilities, startTime, uri, nodeId, nodeId, sessionDurationMillis } } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
Buscando informações dos slots de todas as sessões de cada Nó na Grid:
curl -X POST -H "Content-Type: application/json" --data '{"query":"{ sessionsInfo { sessions { id, slot { id, stereotype, lastStarted } } } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
Buscando informação da sessão para uma sessão específica:
curl -X POST -H "Content-Type: application/json" --data '{"query":"{ session (id: \"<session-id>\") { id, capabilities, startTime, uri, nodeId, nodeUri, sessionDurationMillis, slot { id, stereotype, lastStarted } } } "}' -s <LINK_TO_GRAPHQL_ENDPOINT>
Buscando os recursos de cada nó na Grid:
curl -X POST -H "Content-Type: application/json" --data '{"query": "{ nodesInfo { nodes { stereotypes } } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
Buscando o status de cada Nó na Grid:
curl -X POST -H "Content-Type: application/json" --data '{"query": "{ nodesInfo { nodes { status } } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
Buscando a URI de cada Nó e da Grid:
curl -X POST -H "Content-Type: application/json" --data '{"query": "{ nodesInfo { nodes { uri } } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
Query for getting the current requests in the New Session Queue:
curl -X POST -H "Content-Type: application/json" --data '{"query":"{ sessionsInfo { sessionQueueRequests } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
Query for getting the New Session Queue size :
curl -X POST -H "Content-Type: application/json" --data '{"query":"{ grid { sessionQueueSize } }"}' -s <LINK_TO_GRAPHQL_ENDPOINT>
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 Distribuidor.
cURL --request DELETE 'http://localhost:5553/se/grid/distributor/node/<node-id>' --header 'X-REGISTRATION-SECRET: <secret>'
Se nenhum segredo de registro foi configurado durante a configuração da Grid, use
cURL --request DELETE 'http://<Distributor-URL>/se/grid/distributor/node/<node-id>' --header 'X-REGISTRATION-SECRET;'
Drenar Nó
O comando de drenagem de nó é para desligamento normal de nó. A drenagem para o Node após a conclusão de todas as sessões em andamento. No entanto, ele não aceita novas solicitações de sessão.
No modo Standalone, a URL do distribuidor é o endereço do servidor Standalone.
No modo Hub-Node, a URL do Distribuidor é o endereço do servidor Hub.
cURL --request POST 'http://localhost:4444/se/grid/distributor/node/<node-id>/drain' --header 'X-REGISTRATION-SECRET: <secret> '
No modo totalmente distribuído, a URL é o endereço do servidor Distribuidor.
cURL --request POST 'http://localhost:5553/se/grid/distributor/node/<node-id>/drain' --header 'X-REGISTRATION-SECRET: <secret>'
Se nenhum segredo de registro foi configurado durante a configuração da Grid, use
cURL --request POST 'http://<Distributor-URL>/se/grid/distributor/node/<node-id>/drain' --header 'X-REGISTRATION-SECRET;'
Nó
Os terminais nesta seção são aplicáveis ao modo Hub-Node e ao modo Grid totalmente distribuída, onde o Nó é executado de forma independente. A URL do Nó padrão é http://localhost:5555 no caso de um Nó. No caso de vários Nós, use Grid status para obter todos os detalhes do Nó e localizar o endereço do Nó.
Status
O status do Nó é essencialmente uma verificação de integridade do Nó. O distribuidor executa ping no status do Nó em intervalos regulares e atualiza o modelo de Grid de acordo. O status inclui informações sobre disponibilidade, sessões e slots.
cURL --request GET 'http://localhost:5555/status'
Drenagem
O Distribuidor passa o comando [drain](# drain-node) para o Nó apropriado identificado pelo ID do Nó. Para drenar o Nó diretamente, use o comando cuRL listado abaixo. Ambos as rotas são válidas e produzem o mesmo resultado. Drenar termina as sessões em andamento antes de interromper o Nó.
cURL --request POST 'http://localhost:5555/se/grid/node/drain' --header 'X-REGISTRATION-SECRET: <secret>'
Se nenhum segredo de registro foi configurado durante a configuração da Grid, use
cURL --request POST 'http://<node-URL>/se/grid/node/drain' --header 'X-REGISTRATION-SECRET;'
Checar dono da sessão
Para verificar se uma sessão pertence a um Nó, use o comando cURL listado abaixo.
cURL --request GET 'http://localhost:5555/se/grid/node/owner/<session-id>' --header 'X-REGISTRATION-SECRET: <secret>'
Se nenhum segredo de registro foi configurado durante a configuração da Grid, use
cURL --request GET 'http://<node-URL>/se/grid/node/owner/<session-id>' --header 'X-REGISTRATION-SECRET;'
Ele retornará true se a sessão pertencer ao Nó, caso contrário, retornará false.
Deletar sessão
A exclusão da sessão encerra a sessão do WebDriver, fecha o driver e o remove do mapa de sessões ativas. Qualquer solicitação usando o id de sessão removido ou reutilizando a instância do driver gerará um erro.
cURL --request DELETE 'http://localhost:5555/se/grid/node/session/<session-id>' --header 'X-REGISTRATION-SECRET: <secret>'
Se nenhum segredo de registro foi configurado durante a configuração da Grid, use
cURL --request DELETE 'http://<node-URL>/se/grid/node/session/<session-id>' --header 'X-REGISTRATION-SECRET;'
Fila de Sessão
Limpar a Fila de Sessão
A Fila de Sessão contém as novas solicitações de sessão. Para limpar a fila, use o comando cURL listado abaixo. Limpar a fila rejeita todas as solicitações na fila. Para cada solicitação, o servidor retorna uma resposta de erro ao respectivo cliente. O resultado do comando clear é o número total de solicitações excluídas.
No modo Standalone, a URL Queue é o endereço do servidor Standalone.
No modo Hub-Node, a URL do enfileirador é o endereço do servidor Hub.
cURL --request DELETE 'http://localhost:4444/se/grid/newsessionqueue/queue' --header 'X-REGISTRATION-SECRET: <secret>'
No modo totalmente distribuído, a URL do enfileirador é o endereço do servidor do Enfileirador de Sessões.
cURL --request DELETE 'http://localhost:5559/se/grid/newsessionqueue/queue' --header 'X-REGISTRATION-SECRET: <secret>'
If no registration secret has been configured while setting up the Grid, then use
cURL --request DELETE 'http://<URL>/se/grid/newsessionqueue/queue' --header 'X-REGISTRATION-SECRET;'
Obter novos pedidos da Fila de Sessão
Novos pedidos da Fila de Sessão contém os novos pedidos de sessão. Para obter os pedidos na Fila, utiliza o comando cURL listado abaixo. É retornado o número total de pedidos na Fila.
No modo Standalone, a URL é a do servidor, em modo Grid, a URL será a do HUB.
cURL --request GET 'http://localhost:4444/se/grid/newsessionqueue/queue'
No modo totalmente distribuido, a URL da Fila é a porta do servidor de Fila.
cURL --request GET 'http://localhost:5559/se/grid/newsessionqueue/queue'
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 tipoorg.openqa.selenium.grid.node.Node
Config
é do tipoorg.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 umnode
.- Pode ser criado chamando
LocalNodeFactory.create(config);
, onde:LocalNodeFactory
pertence aorg.openqa.selenium.grid.node.local
Config
pertence aorg.openqa.selenium.grid.config
- Pode ser criado chamando
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.
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 nameddocker-compose.yml
with its contents as below:
version: '3.8'
services:
db:
image: postgres:9.6-bullseye
restart: always
environment:
- POSTGRES_USER=seluser
- POSTGRES_PASSWORD=seluser
- POSTGRES_DB=selenium_sessions
ports:
- "5432:5432"
volumes:
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
We can now start our database container by running:
docker-compose up -d
Our database name is selenium_sessions
with its username and password set to seluser
If you are working with an already running PostGreSQL DB instance, then you just need to create a database named selenium_sessions
and the table sessions_map
using the above mentioned SQL statement.
- Create a Selenium Grid configuration file named
sessions.toml
with the below contents:
[sessions]
implementation = "org.openqa.selenium.grid.sessionmap.jdbc.JdbcBackedSessionMap"
jdbc-url = "jdbc:postgresql://localhost:5432/selenium_sessions"
jdbc-user = "seluser"
jdbc-password = "seluser"
Note: If you plan to use an existing PostGreSQL DB instance, then replace localhost:5432
with the actual host and port number of your instance.
- Below is a simple shell script (let’s call it
distributed.sh
) that we will use to bring up our distributed Grid.
SE_VERSION=<current_selenium_version>
JAR_NAME=selenium-server-${SE_VERSION}.jar
PUBLISH="--publish-events tcp://localhost:4442"
SUBSCRIBE="--subscribe-events tcp://localhost:4443"
SESSIONS="--sessions http://localhost:5556"
SESSIONS_QUEUE="--sessionqueue http://localhost:5559"
echo 'Starting Event Bus'
java -jar $JAR_NAME event-bus $PUBLISH $SUBSCRIBE --port 5557 &
echo 'Starting New Session Queue'
java -jar $JAR_NAME sessionqueue --port 5559 &
echo 'Starting Sessions Map'
java -jar $JAR_NAME \
--ext $(coursier fetch -p org.seleniumhq.selenium:selenium-session-map-jdbc:${SE_VERSION} org.postgresql:postgresql:42.3.1) \
sessions $PUBLISH $SUBSCRIBE --port 5556 --config sessions.toml &
echo 'Starting Distributor'
java -jar $JAR_NAME distributor $PUBLISH $SUBSCRIBE $SESSIONS $SESSIONS_QUEUE --port 5553 --bind-bus false &
echo 'Starting Router'
java -jar $JAR_NAME router $SESSIONS --distributor http://localhost:5553 $SESSIONS_QUEUE --port 4444 &
echo 'Starting Node'
java -jar $JAR_NAME node $PUBLISH $SUBSCRIBE &
At this point the current directory should contain the following files:
docker-compose.yml
init.sql
sessions.toml
distributed.sh
You can now spawn the Grid by running
distributed.sh
shell script and quickly run a test. You will notice that the Grid now stores session information into the PostGreSQL database.
In the line which spawns a SessionMap
on a machine:
export SE_VERSION=<current_selenium_version>
java -jar selenium-server-${SE_VERSION}.jar \
--ext $(coursier fetch -p org.seleniumhq.selenium:selenium-session-map-jdbc:${SE_VERSION} org.postgresql:postgresql:42.3.1) \
sessions --publish-events tcp://localhost:4442 \
--subscribe-events tcp://localhost:4443 \
--port 5556 --config sessions.toml
- The variable names from the above script have been replaced with their actual values for clarity.
- Remember to substitute
localhost
with the actual hostname of the machine where yourEvent-Bus
is running. - The arguments being passed to
coursier
are basically the GAV (Group Artifact Version) Maven co-ordinates of:- selenium-session-map-jdbc which is needed to help us store sessions information in database
- postgresql which is needed to help us talk PostGreSQL database.
sessions.toml
is the configuration file that we created earlier.
Redis backed Session Map
We will spin off a Redis Cache docker container using a docker compose file.
Steps
You can skip this step if you already have a Redis Cache instance available at your disposal.
- Create a file named
docker-compose.yml
with its contents as below:
version: '3.8'
services:
redis:
image: redis:bullseye
restart: always
ports:
- "6379:6379"
We can now start our Redis container by running:
docker-compose up -d
- Create a Selenium Grid configuration file named
sessions.toml
with the below contents:
[sessions]
scheme = "redis"
implementation = "org.openqa.selenium.grid.sessionmap.redis.RedisBackedSessionMap"
hostname = "localhost"
port = 6379
Note: If you plan to use an existing Redis Cache instance, then replace localhost
and 6379
with the actual host and port number of your instance.
- Below is a simple shell script (let’s call it
distributed.sh
) that we will use to bring up our distributed grid.
SE_VERSION=<current_selenium_version>
JAR_NAME=selenium-server-${SE_VERSION}.jar
PUBLISH="--publish-events tcp://localhost:4442"
SUBSCRIBE="--subscribe-events tcp://localhost:4443"
SESSIONS="--sessions http://localhost:5556"
SESSIONS_QUEUE="--sessionqueue http://localhost:5559"
echo 'Starting Event Bus'
java -jar $JAR_NAME event-bus $PUBLISH $SUBSCRIBE --port 5557 &
echo 'Starting New Session Queue'
java -jar $JAR_NAME sessionqueue --port 5559 &
echo 'Starting Session Map'
java -jar $JAR_NAME \
--ext $(coursier fetch -p org.seleniumhq.selenium:selenium-session-map-redis:${SE_VERSION}) \
sessions $PUBLISH $SUBSCRIBE --port 5556 --config sessions.toml &
echo 'Starting Distributor'
java -jar $JAR_NAME distributor $PUBLISH $SUBSCRIBE $SESSIONS $SESSIONS_QUEUE --port 5553 --bind-bus false &
echo 'Starting Router'
java -jar $JAR_NAME router $SESSIONS --distributor http://localhost:5553 $SESSIONS_QUEUE --port 4444 &
echo 'Starting Node'
java -jar $JAR_NAME node $PUBLISH $SUBSCRIBE &
At this point the current directory should contain the following files:
docker-compose.yml
sessions.toml
distributed.sh
You can now spawn the Grid by running
distributed.sh
shell script and quickly run a test. You will notice that the Grid now stores session information into the Redis instance. You can perhaps make use of a Redis GUI such as TablePlus to see them (Make sure that you have setup a debug point in your test, because the values will get deleted as soon as the test runs to completion).
In the line which spawns a SessionMap
on a machine:
export SE_VERSION=<current_selenium_version>
java -jar selenium-server-${SE_VERSION}.jar \
--ext $(coursier fetch -p org.seleniumhq.selenium:selenium-session-map-redis:${SE_VERSION}) \
sessions --publish-events tcp://localhost:4442 \
--subscribe-events tcp://localhost:4443 \
--port 5556 --config sessions.toml
- The variable names from the above script have been replaced with their actual values for clarity.
- Remember to substitute
localhost
with the actual hostname of the machine where yourEvent-Bus
is running. - The arguments being passed to
coursier
are basically the GAV (Group Artifact Version) Maven co-ordinates of:- selenium-session-map-redis which is needed to help us store sessions information in Redis Cache.
sessions.toml
is the configuration file that we created earlier.