Escalando Testes com Docker e Python


Índice

  1. Introdução
    1.1 Agradecimentos
    1.2 A Solução Docker
  1. O que é Docker
  1. Instalando Docker
    3.1 Requisitos Mínimos
    3.2 Instalando
  1. Configurando Docker
    4.1 Imagens Docker
    4.2 Docker Compose
    4.3 Arquivo Yml
    4.4 Implementação
  1. Comandos Docker Compose
    5.1 Spin Up Containers
    5.2 Checar Containers Criados
    5.3 Reiniciar Containers
    5.4 Tear Down Containers
  1. Executar Testes em um Container
    6.1 Convenções
    6.2 Implementação
    6.3 Executar os testes em um container
  1. Escalar Testes utilizando Containers
    7.1 Escalar docker containers
    7.2 Executar testes paralelos em um container
  1. Repository
  1. Bibliografia


1. Introdução


1.1 Agradecimentos


1.2 A Solução Docker

Ao desenvolver projetos de automação de testes surgem dois principais problemas:

  1. Testes UI são lentos e demoram muito tempo para executar.
  2. Variáveis externas como as de ambiente podem criar testes quebradiços.

Vamos utilizar o Docker para resolver isso.


2. O Que é Docker

“Docker é projetado para “buildar”, enviar e executar aplicações críticas de negócios em escala.”

– docs.docker.com

Com docker você pode “buildar” e compartilhar containers e automatizar o “pipeline” de desenvolvimento a partir de um único ambiente.
Cada um desses containers representa um pedaço do app ou do sistema.

Imagens (“Blueprints”):

  • Assim como as imagens para VMs, especificações de imagens docker são utilizadas para criar containers.

Containers:

  • Permite que você “empacote” as coisas que você precisa.
  • São mais rápidas e leves que as máquinas virtuais.

Nesse tutorial nós utilizaremos versões de imagens do Chrome e do Firefox e uma imagem “Selenium Grid” para subir a quantidade de containers que precisarmos.
Com alguns comandos simples nós faremos o “spin up”, “restart” e “tear down” de um grid completo em segundos.


3. Instalando Docker


3.1 Requisitos Mínimos

Antes de você começar a instalar o docker verifique se você habilitou a virtualização:
Habilitar virtualização pela BIOS no windows.


3.2 Instalando

Clique no link abaixo para escolher a versão do docker que você queira instalar, nesse tutorial irei mostrar como instalar a versão para o windows:
https://docs.docker.com/get-docker/.

Etapa 1 – Escolha a versão para o windows.

Etapa 2 – Faça o download.

Etapa 3 – Selecione pelo menos a opção “WSL 2” e clique OK.

Etapa 4 – Clique no botão “Close and restart” para reiniciar sua máquina.

Etapa 5 – Marque a opção “I accept the terms” e clique em “Accept”.

Etapa 6 – Se a mensagem da imagem abaixo for exibida para você, vá para o link abaixo e instale a atualização wsl para o windows.
Pacote de atualização do kernel do Linux do WSL2 para computadores x64.
Após instalado clique em “Restart”.

Etapa 7 – O docker engine está rodando e pronto para uso.

Etapa 8 – Para verificar se o docker foi instalado com sucesso abra o “Prompt de Comando” e execute o comando abaixo.

docker version


4. Configurando Docker

A imagem abaixo representa a estrutura do projeto.
Na configuração do nosso grid existem 3 principais peças:

  1. Docker Engine (“My Laptop”).
  2. Hub Container (Um dos nodes do selenium grid)
  3. Node containers (Os navegadores conectados ao hub).

Os testes serão executados apontando para o hub que irá fazer o balanceamento entre os nodes.
O hub e os nodes são containers, para criar containers você precisa de imagens docker.


4.1 Imagens Docker

Para baixar as imagens docker vá para docker hub, esse site é um repositório de imagens docker onde você pode salvar e/ou baixar imagens.

Para baixar as imagens é necessário que você tenha criado uma conta.

Após criar uma conta prossiga para fazer o login.
Após o login pesquise por “selenium”:

Pesquise pelos nodes disponibilizados pelo selenium, então baixe os seguintes:

  • selenium/hub
  • selenium/node-chrome
  • selenium/node-firefox

Para baixar cada uma das imagens docker clique nelas, copie o comando do campo “Docker Pull Command”, cole no seu “Prompt de Comando” e confirme.

docker pull selenium/hub

Repita o processo “docker pull” para as outras 2 imagens listadas anteriormente.

docker pull selenium/node-chrome
docker pull selenium/node-firefox

Para verificar se as imagens foram baixadas com sucesso execute o comando abaixo:

docker images


4.2 Docker Compose

Para criar um container de cada vez você precisa de utilizar o comando “docker run”.

Em nosso tutorial iremos criar todos os nossos containers de uma só vez com apenas um comando utilizando docker compose “yml”.

Docker compose é uma parte do docker que possui seus próprios comandos e utiliza um arquivo “yml” para descrever as especificações dos containers.

Docker compose define um único arquivo “yml” com os serviços (containers) que você quer, então usa o comando “docker-compose” para criá-los de uma só vez.


4.3 Arquivo Yml

Para configurar um arquivo “docker-compose.yml” nós iremos utilizar o repositório do tutorial anterior:
https://github.com/LuizGustavoR/intro-selenium-py/tree/tutorial/parallel-tests
(branch: tutorial/parallel-tests).

Clone na sua máquina e abra o projeto utilizando o VS Code.

É ok criar o arquivo “docker-compose.yml” em qualquer lugar do projeto,
mas nesse tutorial colocaremos ele dentro de uma pasta chamada “docker”.

“docker/docker-compose.yml”:


4.4 Implementação

docker/docker-compose.yml”:

version: "3"
services:
 
  selenium-hub:
    image: selenium/hub
    container_name: selenium-hub
    ports:
      - "4444:4444"
 
  chrome:
    image: selenium/node-chrome
    shm_size: 2gb
    depends_on:
      - selenium-hub
    environment:
      - SE_EVENT_BUS_HOST=selenium-hub
      - SE_EVENT_BUS_PUBLISH_PORT=4444
      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
      - SE_NODE_OVERRIDE_MAX_SESSIONS=true
      - SE_NODE_MAX_SESSIONS=3
 
  firefox:
    image: selenium/node-firefox
    shm_size: 2gb
    depends_on:
      - selenium-hub
    environment:
      - SE_EVENT_BUS_HOST=selenium-hub
      - SE_EVENT_BUS_PUBLISH_PORT=4444
      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
      - SE_NODE_OVERRIDE_MAX_SESSIONS=true
      - SE_NODE_MAX_SESSIONS=4

Para mais informações sobre como o docker-compose funciona visite:
https://github.com/SeleniumHQ/docker-selenium


5. Comandos Docker Compose

Abra o “Prompt de Comando” dentro do diretório que você salvou o arquivo “docker-compose.yml”:


5.1 Spin Up Containers

Para executar as imagens e criar os containers execute o comando abaixo:

docker-compose up -d

Para listar os containers criados execute o comando abaixo:

docker ps -a


5.2 Checar Containers Criados

Você pode verificar se os containers foram criados acessando a url abaixo:
http://localhost:4444/

Você também pode verificar os containers que você criou utilizando a versão desktop do docker que você instalou na sua máquina.


5.3 Reiniciar Containers

Para reiniciar o grid execute o comando abaixo:

docker-compose restart


5.4 Tear Down Containers

Para “derrubar” o grid execute o comando abaixo:

docker-compose down

Para verificar se os containers foram “derrubados” execute o comando abaixo:

docker ps -a

Obs.: Tear Down = Derrubar.


6. Executar Testes em um Container


6.1 Convenções

Como discutido no tutorial “Testes em Paralelo”, para executar testes em paralelo (e escalá-los) você precisará:

  • Executar testes independentemente.
  • Testes não compartilham um driver (webdriver).

Dica:

  • Lembre-se de reportar os resultados dos testes na máquina em que o “Docker Engine” esteja sendo executado (no caso deste tutorial a sua máquina).
  • O nome do arquivo de cada relatório tem que ser único para que eles não sejam sobrescritos.

Obs.: Relatório de bugs não será implementado neste tutorial.


6.2 Implementação

Para executar o projeto em um docker container é necessário editar 3 arquivos em nosso projeto:

  1. docker-compose.yml (Já foi editado no capítulo “4.4 Implementação”).
  2. config.json
  3. conftest.py

config.json”:

{
    "browser": "Headless Chrome",
    "type": "remote",
    "implicit_wait": 10,
    "url_remote": "http://127.0.0.1:4444/wd/hub"
}

conftest.py”:

"""
This module contains shared fixture.
"""
 
import json
import pytest
import selenium.webdriver
 
# scope='session' makes
# this fixture run only one time before the entire test suite
@pytest.fixture
def config(scope='session'):
 
    # Read the file
    with open('config.json') as config_file:
        config = json.load(config_file)
 
    # Accept values are acceptable
    assert config['browser'] in ['Firefox', 'Chrome', 'Headless Firefox', 'Headless Chrome']
    assert config['type'] in ['local', 'remote']
    assert isinstance(config['implicit_wait'], int)
    assert config['implicit_wait'] > 0
    assert isinstance(config['url_remote'], str)
    assert len(config['url_remote']) > 0
 
    # Return config so it can be used
    return config
 
# This fixture will run once for each test case
@pytest.fixture
def browser(config):
 
    # Initialize the local WebDriver instance
    if config['type'] == 'local':
 
        if config['browser'] == 'Firefox':
            b = selenium.webdriver.Firefox()
            opts.add_argument('--window-size=1920,1080')
            b = selenium.webdriver.Firefox(options=opts)
        elif config['browser'] == 'Chrome':
            opts = selenium.webdriver.ChromeOptions()
            opts.add_argument('--window-size=1920,1080')
            b = selenium.webdriver.Chrome(options=opts)
        else:
            raise Exception(f'Browser "{config["browser"]}" is not supported in local mode')
 
    # Initialize the remote WebDriver instance
    elif config['type'] == 'remote':
 
        if config['browser'] == 'Headless Firefox':
            opts = selenium.webdriver.FirefoxOptions()
        elif config['browser'] == 'Headless Chrome':
            opts = selenium.webdriver.ChromeOptions()
        else:
            raise Exception(f'Browser "{config["browser"]}" is not supported in remote mode')
 
        opts.add_argument('--no-sandbox')
        opts.add_argument('--headless')
        opts.add_argument('--disable-gpu')
        b = selenium.webdriver.Remote(
            command_executor = config['url_remote'],
            options=opts
        )
 
    # Make its calls wait for elements to appear
    b.implicitly_wait(config['implicit_wait'])
 
    # Return the WebDriver instance for the setup
    yield b
 
    # Quit the WebDriver instance for the instance
    b.quit


6.3 Executar os testes em um container

Antes de começar, verifique se já existem containers rodando na sua máquina executando o comando abaixo:

docker ps -a

Nenhum container sendo executado:

Obs.: Se você encontrar algum container em execução, derrube-o executando o comando abaixo dentro da pasta “docker” do projeto:

docker-compose down

“spin up” (suba) os novos containers do grid:

docker-compose up -d

Containers em execução:

Retorne da pasta “docker” para a raíz do seu projeto executando o comando “cd..”:

Quando dentro da raíz do seu projeto, para executar os testes dentro do container do docker execute o comando abaixo:

pipenv run python -m pytest

Os 3 testes irão executar cada um em um sessão do chrome container:

Perceba que após um teste é finalizado (quit), demora um tempo para o container limpar a sessão.
Então um novo teste não irá iniciar já que não existem sessões disponíveis.
Para resolver isso você tem 3 opções:

  1. Reinicie o grid ao executar o comando “docker-compose restart” dentro da pasta “docker” e então de volta à pasta raiz do projeto execute o comando dos testes novamente.
  1. Permitir que o chrome container execute mais sessões de teste.
    Faça isso ao editar o arquivo “docker-compose.yml”, por exemplo:
    Substitua “SE_NODE_MAX_SESSIONS=3” por “SE_NODE_MAX_SESSIONS=6”.
    Obs.: Não esqueça de executar dentro da pasta “folder” do projeto o comando “docker-compose up” e “docker-compose down”, o comando “docker-compose restart” não vai atualizar o limite máximo do número de sessões.
  1. A melhor e terceira opção é “Escalar docker containers”.
    Mais detalhes no capítulo abaixo.


7. Escalar Testes utilizando Containers


7.1 Escalar docker containers

Para que você escale por 10 o número de chrome containers que foram declarados no arquivo “docker-compose.yml” vá para a pasta “docker” dentro do projeto e execute o comando abaixo:

docker-compose up -d --scale chrome=10

Agora você possui 10 chrome containers, cada container possuindo um máximo de 3 sessões em execução, resultando em um total de 30 sessões:


7.2 Executar testes paralelos em um container

Caso você queira executar os testes em paralelo igual fizemos no tutorial “Executando Testes em Paralelo” utilizando o xdist, mas em um container, apenas execute o comando abaixo na pasta raíz do projeto:

pipenv run python -m pytest -n 3

Como você pode ver o selenium grid distribuiu a carga de teste, redirecionando cada teste para executar em um único container entre os 10 que escalamos acima:

Para “derrubar” tudo de uma vez execute o comando abaixo na pasta “docker” do projeto:

docker-compose down

Obs.: Você pode executar ambos os testes “não paralelos” e “paralelos” em um grid escalado, mas testes “não paralelos” serão executados um teste após o outro mesmo em um contexto distribuído, enquanto testes em “paralelo” irão iniciar todos ao mesmo tempo.


8. Repository

Criei uma branch nova para esse tutorial chamada “tutorial/scale-tests-docker”, clique no link abaixo para visualizar:
https://github.com/LuizGustavoR/intro-selenium-py/tree/tutorial/scale-tests-docker

Fim.


9. Bibliografia

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *