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

Return to the regular view of this page.

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.

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:

  1. https://www.honeycomb.io/blog/how-are-structured-logs-different-from-events/
  2. 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:

  1. Evento normal - registra todas as informações sobre uma unidade de trabalho e marca a conclusão bem-sucedida do trabalho.
  2. 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

  1. Standalone
  2. Hub and Node
  3. Fully Distributed
  4. 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:

  1. 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
  1. 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..

CampoValor do CampoDescrição
Hora do EventoeventIdCarimbo de data/hora do registro do evento em nanossegundos desde a época.
ID de RastreamentotracedIdCada rastreamento é identificado exclusivamente por um ID de rastreamento.
ID de SegmentospanIdCada segmento dentro de um rastreamento é identificado exclusivamente por um ID de segmento.
Tipo de SegmentospanKindO 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 EventoeventNameIsso mapeia para a mensagem de registro.
Atributos do EventoeventAttributesIsso 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: :

CampoValor do CampoDescrição
Tipo de Exceçãoexception.typeO nome da classe da exceção.
Mensagem da Exceçãoexception.messageMotivo da exceção.
Rastreamento de Exceçãoexception.stacktraceImprime 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

  1. Compreendendo o Rastreamento
  2. Especificação da API de Rastreamento do OpenTelemetry
  3. Selenium Wiki
  4. Logs Estruturados vs. Eventos
  5. Framework Jaeger

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'

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

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

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

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

Distribuidor

Remover Nó

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

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

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

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

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

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

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

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

Drenar Nó

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

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

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

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

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

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

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

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

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

Status

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

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

Drenagem

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

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

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

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

Checar dono da sessão

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

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

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

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

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

Deletar sessão

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

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

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

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

Fila de Sessão

Limpar a Fila de Sessão

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

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

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

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

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

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

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

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

Obter novos pedidos da Fila de Sessão

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

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

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

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

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

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

  1. Crie um projeto de exemplo usando sua ferramenta de construção favorita. (Maven|Gradle).
  2. Adicione a seguinte dependência ao seu projeto de exemplo..
  3. Adicione o seu nó personalizado ao projeto.
  4. Construir algo. uber jar Para ser capaz de iniciar o Node usando o comando java -jar.
  5. 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

  1. Crie um projeto de exemplo usando a sua ferramenta de construção favorita (Maven|Gradle).
  2. Adicione a seguinte dependência ao seu projeto de exemplo:
  3. Adicione o seu Node personalizado ao projeto.
  4. Construa um arquivo JAR do seu projeto usando a sua ferramenta de construção.
  5. 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) {
	super(tracer, nodeId, uri, registrationSecret);
  }

  public static Node create(Config config) {
    LoggingOptions loggingOptions = new LoggingOptions(config);
    BaseServerOptions serverOptions = new BaseServerOptions(config);
    URI uri = serverOptions.getExternalUri();
    SecretOptions secretOptions = new SecretOptions(config);

    // Refer to the foot notes for additional context on this line.
    Node node = LocalNodeFactory.create(config);

    DecoratedLoggingNode wrapper = new DecoratedLoggingNode(loggingOptions.getTracer(),
				node.getId(),
				uri,
				secretOptions.getRegistrationSecret());
    wrapper.node = node;
    return wrapper;
  }

  @Override
  public Either<WebDriverException, CreateSessionResponse> newSession(
      CreateSessionRequest sessionRequest) {
    System.out.println("Before newSession()");
    try {
      return this.node.newSession(sessionRequest);
    } finally {
      System.out.println("After newSession()");
    }
  }

  @Override
  public HttpResponse executeWebDriverCommand(HttpRequest req) {
    try {
      System.out.println("Before executeWebDriverCommand(): " + req.getUri());
      return node.executeWebDriverCommand(req);
    } finally {
      System.out.println("After executeWebDriverCommand()");
    }
  }

  @Override
  public Session getSession(SessionId id) throws NoSuchSessionException {
    try {
      System.out.println("Before getSession()");
      return node.getSession(id);
    } finally {
      System.out.println("After getSession()");
    }
  }

  @Override
  public HttpResponse uploadFile(HttpRequest req, SessionId id) {
    try {
      System.out.println("Before uploadFile()");
      return node.uploadFile(req, id);
    } finally {
      System.out.println("After uploadFile()");
    }
  }

  @Override
  public void stop(SessionId id) throws NoSuchSessionException {
    try {
      System.out.println("Before stop()");
      node.stop(id);
    } finally {
      System.out.println("After stop()");
    }
  }

  @Override
  public boolean isSessionOwner(SessionId id) {
    try {
      System.out.println("Before isSessionOwner()");
      return node.isSessionOwner(id);
    } finally {
      System.out.println("After isSessionOwner()");
    }
  }

  @Override
  public boolean isSupporting(Capabilities capabilities) {
    try {
      System.out.println("Before isSupporting");
      return node.isSupporting(capabilities);
    } finally {
      System.out.println("After isSupporting()");
    }
  }

  @Override
  public NodeStatus getStatus() {
    try {
      System.out.println("Before getStatus()");
      return node.getStatus();
    } finally {
      System.out.println("After getStatus()");
    }
  }

  @Override
  public HealthCheck getHealthCheck() {
    try {
      System.out.println("Before getHealthCheck()");
      return node.getHealthCheck();
    } finally {
      System.out.println("After getHealthCheck()");
    }
  }

  @Override
  public void drain() {
    try {
      System.out.println("Before drain()");
      node.drain();
    } finally {
      System.out.println("After drain()");
    }

  }

  @Override
  public boolean isReady() {
    try {
      System.out.println("Before isReady()");
      return node.isReady();
    } finally {
      System.out.println("After isReady()");
    }
  }
}

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

5 - External datastore

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

Table of Contents

Introduction

Selenium Grid allows you to persist information related to currently running sessions into an external data store. The external data store could be backed by your favourite database (or) Redis Cache system.

Setup

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

Database backed Session Map

For the sake of this illustration, we are going to work with PostGreSQL database.

We will spin off a PostGreSQL database as a docker container using a docker compose file.

Steps

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

  • Create a sql file named init.sql with the below contents:
CREATE TABLE IF NOT EXISTS sessions_map(
    session_ids varchar(256),
    session_caps text,
    session_uri varchar(256),
    session_stereotype text,
    session_start varchar(256)
 );
  • In the same directory as the init.sql, create a file named docker-compose.yml with its contents as below:
version: '3.8'
services:
  db:
    image: postgres:9.6-bullseye
    restart: always
    environment:
      - POSTGRES_USER=seluser
      - POSTGRES_PASSWORD=seluser
      - POSTGRES_DB=selenium_sessions
    ports:
      - "5432:5432"
    volumes:
    - ./init.sql:/docker-entrypoint-initdb.d/init.sql

We can now start our database container by running:

docker-compose up -d

Our database name is selenium_sessions with its username and password set to seluser

If you are working with an already running PostGreSQL DB instance, then you just need to create a database named selenium_sessions and the table sessions_map using the above mentioned SQL statement.

  • Create a Selenium Grid configuration file named sessions.toml with the below contents:
[sessions]
implementation = "org.openqa.selenium.grid.sessionmap.jdbc.JdbcBackedSessionMap"
jdbc-url = "jdbc:postgresql://localhost:5432/selenium_sessions"
jdbc-user = "seluser"
jdbc-password = "seluser"

Note: If you plan to use an existing PostGreSQL DB instance, then replace localhost:5432 with the actual host and port number of your instance.

  • Below is a simple shell script (let’s call it distributed.sh) that we will use to bring up our distributed Grid.
SE_VERSION=<current_selenium_version>
JAR_NAME=selenium-server-${SE_VERSION}.jar
PUBLISH="--publish-events tcp://localhost:4442"
SUBSCRIBE="--subscribe-events tcp://localhost:4443"
SESSIONS="--sessions http://localhost:5556"
SESSIONS_QUEUE="--sessionqueue http://localhost:5559"
echo 'Starting Event Bus'
java -jar $JAR_NAME event-bus $PUBLISH $SUBSCRIBE --port 5557 &
echo 'Starting New Session Queue'
java -jar $JAR_NAME sessionqueue --port 5559 &
echo 'Starting Sessions Map'
java -jar $JAR_NAME \
--ext $(coursier fetch -p org.seleniumhq.selenium:selenium-session-map-jdbc:${SE_VERSION} org.postgresql:postgresql:42.3.1) \
sessions $PUBLISH $SUBSCRIBE --port 5556 --config sessions.toml &
echo 'Starting Distributor'
java -jar $JAR_NAME  distributor $PUBLISH $SUBSCRIBE $SESSIONS $SESSIONS_QUEUE --port 5553 --bind-bus false &
echo 'Starting Router'
java -jar $JAR_NAME router $SESSIONS --distributor http://localhost:5553 $SESSIONS_QUEUE --port 4444 &
echo 'Starting Node'
java -jar $JAR_NAME node $PUBLISH $SUBSCRIBE &
  • At this point the current directory should contain the following files:

    • docker-compose.yml
    • init.sql
    • sessions.toml
    • distributed.sh
  • You can now spawn the Grid by running distributed.sh shell script and quickly run a test. You will notice that the Grid now stores session information into the PostGreSQL database.

In the line which spawns a SessionMap on a machine:

export SE_VERSION=<current_selenium_version>
java -jar selenium-server-${SE_VERSION}.jar \
--ext $(coursier fetch -p org.seleniumhq.selenium:selenium-session-map-jdbc:${SE_VERSION} org.postgresql:postgresql:42.3.1) \
sessions --publish-events tcp://localhost:4442 \
--subscribe-events tcp://localhost:4443 \
--port 5556 --config sessions.toml 
  • The variable names from the above script have been replaced with their actual values for clarity.
  • Remember to substitute localhost with the actual hostname of the machine where your Event-Bus is running.
  • The arguments being passed to coursier are basically the GAV (Group Artifact Version) Maven co-ordinates of:
  • sessions.toml is the configuration file that we created earlier.

Redis backed Session Map

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

Steps

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

  • Create a file named docker-compose.yml with its contents as below:
version: '3.8'
services:
  redis:
    image: redis:bullseye
    restart: always
    ports:
      - "6379:6379"

We can now start our Redis container by running:

docker-compose up -d
  • Create a Selenium Grid configuration file named sessions.toml with the below contents:
[sessions]
scheme = "redis"
implementation = "org.openqa.selenium.grid.sessionmap.redis.RedisBackedSessionMap"
hostname = "localhost"
port = 6379

Note: If you plan to use an existing Redis Cache instance, then replace localhost and 6379 with the actual host and port number of your instance.

  • Below is a simple shell script (let’s call it distributed.sh) that we will use to bring up our distributed grid.
SE_VERSION=<current_selenium_version>
JAR_NAME=selenium-server-${SE_VERSION}.jar
PUBLISH="--publish-events tcp://localhost:4442"
SUBSCRIBE="--subscribe-events tcp://localhost:4443"
SESSIONS="--sessions http://localhost:5556"
SESSIONS_QUEUE="--sessionqueue http://localhost:5559"
echo 'Starting Event Bus'
java -jar $JAR_NAME event-bus $PUBLISH $SUBSCRIBE --port 5557 &
echo 'Starting New Session Queue'
java -jar $JAR_NAME sessionqueue --port 5559 &
echo 'Starting Session Map'
java -jar $JAR_NAME \
--ext $(coursier fetch -p org.seleniumhq.selenium:selenium-session-map-redis:${SE_VERSION}) \
sessions $PUBLISH $SUBSCRIBE --port 5556 --config sessions.toml &
echo 'Starting Distributor'
java -jar $JAR_NAME  distributor $PUBLISH $SUBSCRIBE $SESSIONS $SESSIONS_QUEUE --port 5553 --bind-bus false &
echo 'Starting Router'
java -jar $JAR_NAME router $SESSIONS --distributor http://localhost:5553 $SESSIONS_QUEUE --port 4444 &
echo 'Starting Node'
java -jar $JAR_NAME node $PUBLISH $SUBSCRIBE &
  • At this point the current directory should contain the following files:

    • docker-compose.yml
    • sessions.toml
    • distributed.sh
  • You can now spawn the Grid by running distributed.sh shell script and quickly run a test. You will notice that the Grid now stores session information into the Redis instance. You can perhaps make use of a Redis GUI such as TablePlus to see them (Make sure that you have setup a debug point in your test, because the values will get deleted as soon as the test runs to completion).

In the line which spawns a SessionMap on a machine:

export SE_VERSION=<current_selenium_version>
java -jar selenium-server-${SE_VERSION}.jar \
--ext $(coursier fetch -p org.seleniumhq.selenium:selenium-session-map-redis:${SE_VERSION}) \
sessions --publish-events tcp://localhost:4442 \
--subscribe-events tcp://localhost:4443 \
--port 5556 --config sessions.toml 
  • The variable names from the above script have been replaced with their actual values for clarity.
  • Remember to substitute localhost with the actual hostname of the machine where your Event-Bus is running.
  • The arguments being passed to coursier are basically the GAV (Group Artifact Version) Maven co-ordinates of:
  • sessions.toml is the configuration file that we created earlier.