Pruebas de Pytest con generación de informes en Allure usando Docker y Gitlab Pages y parcialmente selenium

Este texto está destinado a probadores novatos que desean comprender cómo hacer informes sobre el atractivo con el historial de pruebas, y también explicar dónde almacenarlos para que cualquier miembro de su equipo pueda consultar el informe.



Un ejemplo de un informe obtenido en allure



Repositorio de trabajo con infraestructura y código de trabajo final



Enlace a informes después de ejecutar pruebas



gitlab python, allure, docker, , . , , , . allure, . , UI , .



. , . windows, .



, python, , . pytest, , , . Dog API. gitlab, docker.



, :







  • API
  • allure
  • Gitlab Runner
  • .gitlab-ci.yml
  • UI




allure_pages .



:





  • conftest.py
  • requirements.txt — python
  • test_dog_api.py tests


, . :



python -m venv venv



PyCharm





, PyCharm . , .



  1. Add Interpreter , PyCharm ,




:



.



  • pip install requests — , Dog Api
  • pip install pytest — ,
  • pip install pytest-xdist
  • pip install allure-pytest — allure




pip freeze > requirements.txt.



requirements.txt .



, requirements.txt

allure-pytest==2.8.18
pytest==6.0.1
pytest-xdist==2.1.0
requests==2.24.0




API



, GET POST . . , . .





@pytest.fixture
def dog_api():
    return ApiClient(base_address="https://dog.ceo/api/")


. base_address, https://dog.ceo/api/. , GET POST .



class ApiClient:
    def __init__(self, base_address):
        self.base_address = base_address


GET .



    def get(self, path="/", params=None, headers=None):
        url = f"{self.base_address}{path}"
        return requests.get(url=url, params=params, headers=headers)


, requests. ApiClient path, params, headers get , requests.get(url=url, params=params, headers=headers). . , , , , requests.get.



POST. , , .



    def post(self, path="/", params=None, data=None, json=None, headers=None):
        url = f"{self.base_address}{path}"
        return requests.post(url=url, params=params, data=data, json=json, headers=headers)


conftest.py:



import pytest
import requests

class ApiClient:
    def __init__(self, base_address):
        self.base_address = base_address

    def post(self, path="/", params=None, data=None, json=None, headers=None):
        url = f"{self.base_address}{path}"
        return requests.post(url=url, params=params, data=data, json=json, headers=headers)

    def get(self, path="/", params=None, headers=None):
        url = f"{self.base_address}{path}"
        return requests.get(url=url, params=params, headers=headers)

@pytest.fixture
def dog_api():
    return ApiClient(base_address="https://dog.ceo/api/")




Dog API . , . , . , . , . test_dog_api.py



import pytest

def test_get_random_dog(dog_api):
    response = dog_api.get("breeds/image/random")

    with allure.step(" ,   "):
        assert response.status_code == 200, f"  ,  {response.status_code}"

    with allure.step(" .    json  ."):
        response = response.json()
        assert response["status"] == "success"

    with allure.step(f"   {response}"):
        with allure.step(f"      "):
            with allure.step(f"  - "):
                pass

@pytest.mark.parametrize("breed", [
    "afghan",
    "basset",
    "blood",
    "english",
    "ibizan",
    "plott",
    "walker"
])
def test_get_random_breed_image(dog_api, breed):
    response = dog_api.get(f"breed/hound/{breed}/images/random")
    response = response.json()
    assert breed in response["message"], f"      ,  {response}"

@pytest.mark.parametrize("file", ['.md', '.MD', '.exe', '.txt'])
def test_get_breed_images(dog_api, file):
    response = dog_api.get("breed/hound/images")
    response = response.json()
    result = '\n'.join(response["message"])
    assert file not in result, f"      {file}"

@pytest.mark.parametrize("breed", [
    "african",
    "boxer",
    "entlebucher",
    "elkhound",
    "shiba",
    "whippet",
    "spaniel",
    "dvornyaga"
])
def test_get_random_breed_images(dog_api, breed):
    response = dog_api.get(f"breed/{breed}/images/")
    response = response.json()
    assert response["status"] == "success", f"      {breed}"

@pytest.mark.parametrize("number_of_images", [i for i in range(1, 10)])
def test_get_few_sub_breed_random_images(dog_api, number_of_images):
    response = dog_api.get(f"breed/hound/afghan/images/random/{number_of_images}")
    response = response.json()
    final_len = len(response["message"])
    assert final_len == number_of_images, f"   {number_of_images},  {final_len}"


allure



allure. , . , .



with allure.step('step 1'): — , .

@allure.feature('Dog Api') @allure.story('Send few requests') — ,



allure, . allure,

API allure .



class ApiClient:
    def __init__(self, base_address):
        self.base_address = base_address

    def post(self, path="/", params=None, data=None, json=None, headers=None):
        url = f"{self.base_address}{path}"
        with allure.step(f'POST request to: {url}'):
            return requests.post(url=url, params=params, data=data, json=json, headers=headers)

    def get(self, path="/", params=None, headers=None):
        url = f"{self.base_address}{path}"
        with allure.step(f'GET request to: {url}'):
            return requests.get(url=url, params=params, headers=headers)


. , . . .



@allure.feature('Random dog')
@allure.story('         ')
def test_get_random_dog(dog_api):
    response = dog_api.get("breeds/image/random")

    with allure.step(" ,   "):
        assert response.status_code == 200, f"  ,  {response.status_code}"

    with allure.step(" .    json  ."):
        response = response.json()
        assert response["status"] == "success"

    with allure.step(f"   {response}"):
        with allure.step(f"      "):
            with allure.step(f"  - "):
                pass


. test_dog_api.py



import pytest
import allure

@allure.feature('Random dog')
@allure.story('         ')
def test_get_random_dog(dog_api):
    response = dog_api.get("breeds/image/random")

    with allure.step(" ,   "):
        assert response.status_code == 200, f"  ,  {response.status_code}"

    with allure.step(" .    json  ."):
        response = response.json()
        assert response["status"] == "success"

    with allure.step(f"   {response}"):
        with allure.step(f"      "):
            with allure.step(f"  - "):
                pass

@allure.feature('Random dog')
@allure.story('     ')
@pytest.mark.parametrize("breed", [
    "afghan",
    "basset",
    "blood",
    "english",
    "ibizan",
    "plott",
    "walker"
])
def test_get_random_breed_image(dog_api, breed):
    response = dog_api.get(f"breed/hound/{breed}/images/random")

    with allure.step(" .    json  ."):
        response = response.json()

    assert breed in response["message"], f"      ,  {response}"

@allure.feature('List of dog images')
@allure.story('       ')
@pytest.mark.parametrize("file", ['.md', '.MD', '.exe', '.txt'])
def test_get_breed_images(dog_api, file):
    response = dog_api.get("breed/hound/images")

    with allure.step(" .    json  ."):
        response = response.json()

    with allure.step("        "):
        result = '\n'.join(response["message"])

    assert file not in result, f"      {file}"

@allure.feature('List of dog images')
@allure.story('   ')
@pytest.mark.parametrize("breed", [
    "african",
    "boxer",
    "entlebucher",
    "elkhound",
    "shiba",
    "whippet",
    "spaniel",
    "dvornyaga"
])
def test_get_random_breed_images(dog_api, breed):
    response = dog_api.get(f"breed/{breed}/images/")

    with allure.step(" .    json  ."):
        response = response.json()

    assert response["status"] == "success", f"      {breed}"

@allure.feature('List of dog images')
@allure.story('    ')
@pytest.mark.parametrize("number_of_images", [i for i in range(1, 10)])
def test_get_few_sub_breed_random_images(dog_api, number_of_images):
    response = dog_api.get(f"breed/hound/afghan/images/random/{number_of_images}")

    with allure.step(" .    json  ."):
        response = response.json()

    with allure.step("      "):
        final_len = len(response["message"])

    assert final_len == number_of_images, f"   {number_of_images},  {final_len}"




, :



  • .gitlab-ci.yml — , yaml gitlab runner
  • gitlab-runner — , Go. . . gitlab runner, docker . "". .


devops - , . . docker desktop windows. , .gitlab-ci .



docker desktop, .




, gitlab.com. gitlab.com, . , .



Settings -> General -> Visibility, project features, permissions, Pipelines .





CI / CD. Settings -> CI / CD -> Runners





1 Gitlab Runner. .



Gitlab Runner



, . . .



  1. - , : C:\GitLab-Runner.
  2. x86 amd64 . gitlab-runner.exe.
  3. . ( powershell )
  4. . , () .


. , powershell, , . , , .



C:\gitlab_runners , gitlab-runner.exe



, :



  1. :

    ./gitlab-runner.exe register
  2. url, 2. , :

    https://gitlab.somesubdomain.com/
  3. 3. , :

    tJTUaJ7JxfL4yafEyF3k
  4. . UI . :

    Runner on windows for autotests
  5. , , .gitlab-ci.yml, . , .

    docker, windows
  6. , . docker

    docker
  7. image, . .gitlab-ci.yml

    python:3.8-alpine






, .



:

.\gitlab-runner.exe status



:

.\gitlab-runner.exe run



, Settings -> CI / CD -> Runners, - :





, . . .





, .



.gitlab-ci.yml



.gitlab-ci.yml. .



stages. . 4. stage — job, .



stages:
  - testing #  
  - history_copy #       
  - reports #  
  - deploy #    gitlab pages


. Testing



docker_job: #  job
  stage: testing #  stage,   

  tags:
    - docker #     gitlab ,    .    ,  ,     6   .
  image: python:3.8-alpine #   ,      .

  before_script:
    - pip install -r requirements.txt #         

  script:
    - pytest -n=4 --alluredir=./allure-results tests/test_dog_api.py #   (-n=4   ),       --alluredir=

  allow_failure: true #        ,   .

  artifacts: # ,   ,    .
    when: always #  
    paths:
      - ./allure-results #    
    expire_in: 1 day # ,     .        .


. history_copy



history_job: #  job
  stage: history_copy #   stage,   

  tags:
    - docker #     

  image: storytel/alpine-bash-curl #       ,         .     , ?

  script:
    - 'curl --location --output artifacts.zip "https://( ,  gitlab.example.com)/api/v4/projects/(  )/jobs/artifacts/master/download?job=pages&job_token=$CI_JOB_TOKEN"'  #   api     job,    .        .          
    - apk add unzip # ,          unzip,         
    - unzip artifacts.zip #  
    - chmod -R 777 public #      
    - cp -r ./public/history ./allure-results #       

  allow_failure: true #        ,      .       .

  artifacts: 
    paths:
      - ./allure-results #  
    expire_in: 1 day
  rules:
    - when: always #  


. reports



allure_job: #  job
  stage: reports #  stage,   

  tags:
    - docker #     

  image: frankescobar/allure-docker-service #      allure.      .

  script:
     - allure generate -c ./allure-results -o ./allure-report #    ./allure-results   ./allure-report

  artifacts:
    paths:
      - ./allure-results #            
      - ./allure-report
    expire_in: 1 day
  rules:
    - when: always


. deploy



pages: #   job  ,       pages

  stage: deploy #  stage,   

  script:
    - mkdir public #   public.      gitlab pages    public
    - mv ./allure-report/* public #    public  .

  artifacts:
    paths:
      - public
  rules:
    - when: always


.gitlab-ci.yml



stages:
  - testing #  
  - history_copy #       
  - reports #  
  - deploy #    gitlab pages

docker_job: #  job
  stage: testing #  stage,   
  tags:
    - docker #     gitlab ,    .    ,  ,     6   .
  image: python:3.8-alpine #   ,      .
  before_script:
    - pip install -r requirements.txt #         
  script:
    - pytest -n=4 --alluredir=./allure-results tests/test_dog_api.py #   (-n=4   ),       --alluredir=
  allow_failure: true #        ,   .
  artifacts: # ,   ,    .
    when: always #  
    paths:
      - ./allure-results #    
    expire_in: 1 day # ,     .        .

history_job: #  job
  stage: history_copy #   stage,   
  tags:
    - docker #     
  image: storytel/alpine-bash-curl #       ,         .     , ?
  script:
    - 'curl --location --output artifacts.zip "https://( ,  gitlab.example.com)/api/v4/projects/(  )/jobs/artifacts/master/download?job=pages&job_token=$CI_JOB_TOKEN"'  #   api     job,    .        .          
    - apk add unzip # ,          unzip,         
    - unzip artifacts.zip #  
    - chmod -R 777 public #      
    - cp -r ./public/history ./allure-results #       
  allow_failure: true #        ,      .       .
  artifacts: 
    paths:
      - ./allure-results #  
    expire_in: 1 day
  rules:
    - when: always #  

allure_job: #  job
  stage: reports #  stage,   
  tags:
    - docker #     
  image: frankescobar/allure-docker-service #      allure.      .
  script:
     - allure generate -c ./allure-results -o ./allure-report #    ./allure-results   ./allure-report
  artifacts:
    paths:
      - ./allure-results #            
      - ./allure-report
    expire_in: 1 day
  rules:
    - when: always

pages: #   job  ,       pages
  stage: deploy #  stage,   
  script:
    - mkdir public #   public.      gitlab pages    public
    - mv ./allure-report/* public #    public  .
  artifacts:
    paths:
      - public
  rules:
    - when: always




, :



  1. conftest.py
  2. tests
  3. requirements.txt (, )
  4. .gitlab-ci.yml


.



, . , . CI / CD -> Pipelines



, Ci , .gitlab-ci.yml.







, Settings -> Pages. pages 30 . .





. .





, , . .





. , stage history_job . .





, . .





UI



[services](https://docs.gitlab.com/ee/ci/services/). , script. UI job :



  services:
    - selenium/standalone-chrome:latest


, url, - url, . :



  • :
  • / __
  • / - ( Gitlab Runner v1.1.0 )


executor ( chromedriver , ) ui :



browser = webdriver.Remote(command_executor="http://selenium__standalone-chrome:4444/wd/hub")


, .gitlab-ci.yml:

selenium/standalone-chrome:latest

:

selenium__standalone-chrome



Para ejecutar mis pruebas, obtuve este trabajo. Si le agrega los siguientes tres trabajos del ejemplo con pruebas de API, entonces puede ejecutar todas las pruebas y obtener un informe.



chrome_job:
  stage: testing
  services:
    - selenium/standalone-chrome
  image: python:3.8
  tags:
    - docker
  before_script:
    - pip install -r requirements.txt
  script:
    - pytest --alluredir=./allure-results tests/
  allow_failure: true
  artifacts:
    when: always
    paths:
      - ./allure-results
    expire_in: 1 day


Enlaces útiles



Además de los enlaces del artículo, me gustaría compartir algunos más que pueden ayudar a trabajar con allure y gitlab ci.



Un ejemplo de este proyecto en gitlab

Enlace a informes después de ejecutar pruebas

Un artículo sobre allure en habr

Introducción a gitlab ci



UPD: Gracias a los comentaristas por señalar las inexactitudes y lo incompleto de esta guía. He corregido y agregado un enlace al repositorio de trabajo.




All Articles