Esse projeto é para vc QA que já faz automação na sua empresa e ainda não integrou ao seu ambiente de CI/CD, no caso aqui utilizaremos o Jenkins. O intuito desse post e mostrar como integrar os testes no Jenkins e não como configurar o ambiente com Jenkins e Docker.
- Jenkins: Jenkins Info
- Docker: Docker info
- Cucumber Reports Plugin for Jenkins Cucumber Reports
- PowerShell Plugin for Jenkins PowerShell for Jenkins
Primeiramente vou mostrar como integrar os teste no Jenkins usando scripts feito em PowerShell.
Vamos começar executando os testes localmente:
cucumber
Agora vamos executar o mesmo teste só que vamos utilizar uma variável de ambiente chamada BROWSER:
cucumber BROWSER=remote
Opa parece que aconteceu algo estranho aqui:
invalid byte sequence in UTF-8 (ArgumentError)
Vamos entender melhor isso abrindo o arquivo env.rb .
Como podemos ver, ao passar a variável BROWSER=remote ele cai no if BROWSER.eql?('remote')
Capybara.register_driver :selenium_chrome do |app|
Capybara::Selenium::Driver.new(app,
:browser => :remote,
:desired_capabilities => :chrome,
:url => "http://selenium-hub:4444/wd/hub"
)
end
Onde eu registro o driver utilizado e passo alguns parâmetros, o mais diferente deles é o url.
Da onde surgiu essa url?
Daqui a pouco descobriremos.
Agora vamos começar a montar a nossa Arquitetura:
Primeiro vamos montar um Dockerfile:
O Dockerfile é a nossa "receita de bolo" para criar um container de acordo com a nossa necessidade.
FROM ruby:2.3
MAINTAINER Felipe Rodrigues <[email protected]>
ENV app_path /opt/jenkins/
WORKDIR ${app_path}
COPY Gemfile* ${app_path}
RUN bundle install
COPY . ${app_path}
ENTRYPOINT ["bundle", "exec", "cucumber -p ${BROWSER} -p ${TAG} --format json -o /opt/jenkins/cucumber.json"]
FROM é nossa imagem base no caso ruby:2.3
MAINTAINER é mera formalidade.
ENV é utilizado para criar variáveis de ambiente, no caso foi criada a variável app_path com o path /opt/jenkins/
WORKDIR é o diretório onde quando o container for executado ele vai executar o comando do ENTRYPOINT nesse diretório (WORKING DIRECTORY).
COPY é para copiar o nosso Gemfile para a pasta que definimos, no caso app_path que representa /opt/jenkins/
RUN é para colocar comandos na hora de fazer o build da imagem, nesse caso usamos o bundle install
COPY para compiar tudo do diretória atual (.ponto) para a pasta o path app_path
ENTRYPOINT é o comando que eu quero executar quando eu rodar o container
Agora que temos o Dockerfile com Ruby, vamos começar o nosso script de setup:
Para executar nossos teste dentro de containers, nós vamos precisar de dois containers, correto?
- Primeiro, um com o Ruby. (Opa, esse já criamos a receita dele com o Dockerfile)
- Segundo, um que tenha o chrome, chromedriver e tudo que for necessário (Iiii, e agora?)
Como não vamos inventar a roda novamente, vamos fazer o seguinte:
Vamos visitar o Docker Hub e pesquisar por selenium:
Docker Hub Selenium Docker Selenium Link
GitHub Selenium Git Link
Veremos uma infinidade de opções e vamos utilizar a imagem selenium/standalone-chrome
Fica como exercício dar uma lida nesses três links.
Vamos agora criar o nosso arquivo setup.ps1, esse arquivo foi criado na pasta script
docker rm -f container-ruby
docker rm -f selenium-hub
docker pull selenium/standalone-chrome
docker run -d -p 4444:4444 --name selenium-hub selenium/standalone-chrome
docker build -t cucumber/cucumber .
Aqui é basicamente o seguinte:
- As duas primeiras linhas removem os containers com os nomes container-ruby e selenium-hub caso ele exista.
- A terceira faz download ou atualiza a imagem selenium/standalone-chrome.
- A quarta linha inicializa o container e o nomeia como selenium-hub na porta 4444
- A quinta linha faz o build do nosso Dockerfile criando uma imagem o nome de cucumber/cucumber
Sim, lembra da nossa url (http://selenium-hub:4444/wd/hub) no nosso arquivo env.rb?
Então, ela é o nome do nosso container mais a porta que ele roda.
Como exercício execute o comando no seu ambiente local (se vc tiver o docker instalado na sua máquina):
docker run -d -p 4444:4444 --name selenium-hub selenium/standalone-chrome
Agora vá em localhost:4444 e clique no link console.
Depois que aprendemos um pouco sobre o script de setup, vamos agora criar o script que dispara os testes chamado test.ps1:
Param(
[string]$BROWSER,
[string]$TAG
)
docker run -v "$(pwd):/opt/jenkins" -e BROWSER=$BROWSER -e TAG=$TAG -P --name container-ruby --link selenium-hub:selenium-hub cucumber/cucumber
docker rm -f container-ruby
docker rm -f selenium-hub
Aqui basicamente é o seguinte:
- Recebemos dois parâmetros $BROWSER e $TAG
- Depois executamos o container com o nome de container-ruby a partir da nossa imagem cucumber/cucumber, criamos um volume, fazemos um link com o container selenium-hub, esse link é o que faz o dois containers conversarem entre eles.
- Depois removemos os dois containers
Feito isso temos os dois scripts configurados.
Até agora muito legal neh? Mas agora vamos ver se isso funciona de fato?
Então vamos validar se o script realmente funciona, primeiro abra o powershell:
Vamos rodar primeiro o script que faz o setup:
powershell -File .\script\setup.ps1
O setup como podemos ver funcionou, sem problemas.
Agora vamos rodar o script que realmente faz os teste, faz o link do meu container ruby com o selenium-hub:
powershell -File .\script\test.ps1
Opa, parece que deu algo errado! ELe não encontrou nenhum profile.
Pois é, lembra que o nosso script de test.ps1 recebia dois parâmetros? $BROWSER e $TAG, então temos que passar esses parâmetros para que o script funcione corretamente, como o script remove os containers container-ruby e selenium-hub, teremos que fazer o setup novamente.
powershell -File .\script\setup.ps1
Setup feito novamente, agora vamos criar esses parâmetros.
$BROWSER = 'remote'
$TAG = 'all'
Execute esse dois comandos no seu powershell
Agora vamos executar o script test.ps1 e validar se funciona realmente, só que faremos uma pequena alteração que é passar os dois parâmetros que acabamos de definir:
powershell -File .\script\test.ps1 $BROWSER $TAG
Opa, aparentemente tudo ok!
Agora vamos recapitular novamente e entender como tudo aconteceu.
Aqui são todos os comando existentes em nosso dois script:
#setup.ps1
docker rm -f container-ruby
docker rm -f selenium-hub
docker pull selenium/standalone-chrome
docker run -d -p 4444:4444 --name selenium-hub selenium/standalone-chrome
docker build -t cucumber/cucumber .
#Params
$BROWSER = 'remote'
$TAG = 'all'
#test.ps1
Param(
[string]$BROWSER,
[string]$TAG
)
docker run -v "$(pwd):/opt/jenkins" -e BROWSER=$BROWSER -e TAG=$TAG -P --name container-ruby --link selenium-hub:selenium-hub cucumber/cucumber
docker rm -f container-ruby
docker rm -f selenium-hub
Vamos ligar cada comando a sua ação agora:
- docker rm -f container-ruby => remove o container com o nome "container-ruby" caso ele exista
- docker rm -f selenium-hub => remove o container com o nome "selenium-hub" caso ele exista
- docker pull selenium/standalone-chrome => faz o download da imagem selenium/standalone-chrome ou atualiza caso esteja desatualizada
- docker run -d -p 4444:4444 --name selenium-hub selenium/standalone-chrome => esse comando é responsável por subir o container com o nome selenium-hub na porta 4444
- docker build -t cucumber/cucumber . => esse comando é responsável por fazer o build da nossa imagem cucumber/cucumber a partir do nosso Dockerfile o '.(ponto)', significa que o Dockerfile está no diretório atual, ou seja! Na raiz do projeto.
- Param => são aqueles parâmetros que criamos no terminal e depois passamos $BROWSER e $TAG par o script test.ps1
- docker run -v "$(pwd):/opt/jenkins" -e BROWSER=$BROWSER -e TAG=$TAG -P --name container-ruby --link selenium-hub:selenium-hub cucumber/cucumber => Esse comando faz bastante coisa, vamos quebrar ele por partes:
-v "$(pwd):/opt/jenkins"
Mapeia um volume dentro do container /opt/jenkins para o nosso host $(pwd), ou seja se o meu container criar qualquer arquivo nessa pasta, como cucumber.json ou os prints dos testes esses arquivos serão disponibilizados no meu host.
-e BROWSER=$BROWSER -e TAG=$TAG
Aqui são os exports de variavéis de ambiente, ou seja, quando setamos $BROWSER = 'remote' e $TAG = 'all', automaticamente esse esse trecho do comando fica assim:
-e BROWSER=remote -e TAG=all => Agora vou mostrar onde isso é utilizado, no nosso Dockerfile existe um comando chamado ENTRYPOINT:
ENTRYPOINT ["bundle", "exec", "cucumber -p ${BROWSER} -p ${TAG} --format json -o /opt/jenkins/cucumber.json"] => quando fazemos esse export esse comando dentro do nosso container é executado assim:
ENTRYPOINT ["bundle", "exec", "cucumber -p remote -p all --format json -o /opt/jenkins/cucumber.json"] ou seja, passamos como variavéis de ambiente
E como podemos ver existe uma saída chamada cucumber.json para o path /opt/jenkins/ ou seja a pasta que mapeamos um volume, então no final da execução teremos esse arquivo salvo no nosso host.
Olhando nosso arquivo cucumber.yml veremos que tanto remote e all são profiles definidos lá! Por isso quando executamos o script sem os parâmetros os profiles não foram encontrados:
--name container-ruby => é somente para nomear o container
--link selenium-hub:selenium-hub => esse trecho é responsável por fazer o link entre o container container-ruby e selenium-hub, ele faz os dois containers conversarem entre si
cucumber/cucumber é a partir dessa imagem que estamos executando o docker run -v "$(pwd):/opt/jenkins" -e BROWSER=$BROWSER -e TAG=$TAG -P --name container-ruby --link selenium-hub:selenium-hub, essa imagem é aquela que contruimos com o build do Dockerfile
- docker rm -f container-ruby => remove o container com o nome "container-ruby" caso ele exista
- docker rm -f selenium-hub => remove o container com o nome "selenium-hub" caso ele exista
OBS: Vc não precisa criar um script PowerShell ou Shell Script para integrar seus teste no Jenkins, vc pode colocar comando a comando no build do Jenkins que o resultado é o mesmo!
Ou seja, executar esses comandos de forma sequencial:
docker rm -f container-ruby
docker rm -f selenium-hub
docker pull selenium/standalone-chrome
docker run -d -p 4444:4444 --name selenium-hub selenium/standalone-chrome
docker build -t cucumber/cucumber .
$BROWSER = 'remote'
$TAG = 'all'
docker run -v "$(pwd):/opt/jenkins" -e BROWSER=$BROWSER -e TAG=$TAG -P --name container-ruby --link selenium-hub:selenium-hub cucumber/cucumber
docker rm -f container-ruby
docker rm -f selenium-hub
O resultado é o mesmo que os scripts!
Agora que entendemos um pouco sobre cada comando vamos listar os comando executados:
Script setup:
powershell -File .\script\setup.ps1
Variavéis de Ambiente:
$BROWSER = 'remote'
$TAG = 'all'
Script test:
powershell -File .\script\test.ps1 $BROWSER $TAG
Sabendo que isso é o suficiente para os nossos testes localmente usando docker, vamos simplesmente passar esses comando para o Jenkins:
Primeiramente vamos acessar o Jenkins:
Agora clicar em Novo Job:
Vamos chamar de jenkins-ci e selecionar a opção Construir um projeto de software free-style e clicar em ok.
Agora vamos fazer a primeira configuração, vamos só fazer com que o job faça um clone e atualize do github:
Vamos salvar esse job:
Agora vamos clicar em Construir Agora e ver o que acontece:
Opa, parece que não aconteceu muita coisa, vamos olhar no detalhe:
- Clicar no job 1.
Agora vamos olhar o log, clicar em saída do console:
Até agora tudo bem, vamos ver o que temos de arquivo na nossa worspace, clicar em jenkins-ci no header da página:
Agora clicar em Workspace:
Legal, todos os meus arquivos dos projeto estão aqui. Agora vamos configurar o que o nosso job deve fazer:
- Vamos até a área de build e adicionar um passo no build do tipo Windows PowerShell:
Agora vamos adicionar aqueles comando que validamos quando executamos localmente:
Script setup:
powershell -File .\script\setup.ps1
Variavéis de Ambiente:
$BROWSER = 'remote'
$TAG = 'all'
Script test:
powershell -File .\script\test.ps1 $BROWSER $TAG
Quando adicionamos o step Windows PowerShell, estamos de fato montando um script que será executado como PowerShell pelo próprio Jenkins:
Agora vamos configurar um pós build, ou seja, depois que o Jenkins executar esse bloco de PowerShell ele vai executar esse pós build, e esse pós build é sensacional, estou falando do cucumber reports!
Lembra que quando eu rodo o segundo container eu também mapeio um volume? E também disse que após o final do teste ele cria um arquivo chamado cucumber.json e uma pasta com os prints dos testes. Então, vamos configurar o cucumber reports para ver isso acontecer.
Vamos apenas alterar o Build Status para Failure caso aconteça qualquer erro!
Agora vamos salvar e executar novamente (clicar em Construir Agora) e dar uma olhada no log de saída:
Opa, parece que deu certo!
Vamos clicar no job e ver se criou com sucesso o cucumber reports?
- Vamos clicar no Job 5.
Agora vamos clicar no Cucumber reports.
MINHA NOSSA! QUE BRUXARIA É ESSA!!!!!!
Aqui o print do teste de frontend:
MINHA NOSSA! QUE BRUXARIA É ESSA!!!!!!
O Cucumber reports oferece muitos reports:
Agora vamos olhar o nosso Workspace:
Como podemos ver foi criado um arquivo chamado cucumber.json e também uma pasta chamada prints com o print do teste.
Agora vamos fazer uma pequena alteração, alterar o parâmetro $TAG do nosso job e usar outro profile, agora vamos usar o profile frontend.
Clicar em configurar e alterar $TAG = 'all' para $TAG = 'frontend' :
Clicar em salvar.
E agora vamos executar o job novamente:
Como podemos ver sucesso.
Vamos ver o cucumber reports?
Como podemos ver alteramos a tag e executamos apenas o teste de frontend.
Então galera é isso!
Essa é apenas a primeira parte do post!
Vou fazer a segunda parte utilizando shell script e também a criar um build parametrizado. Se vc olhar a pasta script do projeto, vc pode ver que os scripts são bem parecidos.
A ideia principal desse post é que se vc consegue executar seus testes por linha de comando localmente utilizando docker então vc consegue integrar isso ao seu CI/CD.
Espero que essa ideia possa ajudar vcs no dia-a-dia!
Até o próximo!!!!!
Estou aberto a sugestões, elogios, críticas ou qualquer outro tipo de comentário.
- E-mail: [email protected]
- Linkedin: https://www.linkedin.com/in/luis-felipe-rodrigues-de-oliveira-2b056b5a/
Esse código é livre para ser usado dentro dos termos da licença MIT license.