В преддверии старта курса "Python QA Engineer" традиционно публикуем перевод полезного материала.

Также приглашаем присоединиться к открытому вебинару на тему
"Автоматизация тестирования API".


В этой статье описываются операции по тестированию клиентской части приложения с помощью TestProject и pytest, а также способы выполнения тестов через GitHub Actions. Если у вас общедоступный репозиторий GitHub, все это будет совершенно бесплатно. Эта возможность хорошо подходит для изучения TestProject и выполнения интеграционного тестирования в ваших проектах. Если вы хотите выполнять эти операции из закрытого репозитория, то GitHub предлагает очень большое количество бесплатных минут, см. https://github.com/features/actions#pricing-details.

Карточки репозиториев GitHub

Сперва я решил изучить возможности, доступные в репозиториях GitHub, поскольку они показались мне довольно простыми для понимания, а к тому же я давно не пользовался Selenium.

Я закрепил свои любимые репозитории в верхней части своего сайта waylonwalker.com. Информация для заполнения этих карточек динамически подтягивается на сторону клиента через API-интерфейс GitHub. Это означает, что по мере загрузки страниц JavaScript выполняет скрипты для получения информации от API-интерфейса GitHub, а затем преобразует эти данные в DOM и визуализирует их на странице. Вот как выглядят карточки репозиториев GitHub:

Получение ключей

Прежде всего, вам понадобятся TPDEVTOKEN и TPAPIKEY. Эти ключи позволят TestProject получить доступ к вашей учетной записи, чтобы автоматически размещать результаты на вашей панели отчетности.

В своем репозитории GitHub перейдите в раздел settings >Secrets (или добавьте settings/secrets к URL-адресу своего репозитория) и добавьте секретные токены. GitHub получит безопасный доступ к токенам, при этом они не будут доступны широкой публике (включая участников проекта со статусом contributor), не будут отображаться в файлах журналов и т. д.

Настройка среды разработки

Чтобы ускорить работу, я настроил среду разработки в Digital Ocean. Делать это необязательно: все может работать и с вашего локального компьютера или полностью из GitHub Actions. Просто я решил, что, настроив Droplet с Ubuntu в Digital Ocean, я получу условия, близкие к продакшн-среде, а значит, смогу быстрее разработать свои тесты. Это также позволило мне немного ускорить работу всех моих тестов по сравнению с их выполнением через GitHub. При этом процесс был практически таким же, как при использовании GitHub. Таким образом, я смог детально изучить принципы настройки TestProject без необходимости выполнять полную установку при каждом запуске GitHub Actions.

Я не буду здесь подробно описывать настройку машины для разработки. Мои заметки по ее настройке можно прочитать здесь: https://waylonwalker.com/notes/new-machine-tpio.

Pytest

Все тесты, выполняющиеся с помощью Pytest, приведены на github.

Я решил воспользоваться Pytest. Мне понравилась идея применять фикстуры, автоматически выполнять мои тестовые функции и использовать кое-какие возможности Pytest для формирования отчетов по ходу разработки (при этом платформа TestProject будет работать и без фреймворка для тестирования вроде Pytest).

ПРИМЕЧАНИЕ. Следуя стандартным рекомендациям по работе с Pytest, я назвал каталог с тестами именем tests. В целом все работает, но платформа TestProject.io использует этот каталог в качестве имени проекта по умолчанию. Если бы можно было вернуться назад, я либо переименовал бы каталог, задав имя, которое я хочу видеть на TestProject.io, либо задал бы имя проекта в конфигурации.

conftest.py

Вы можете посмотреть файл conftest.py на GitHub.

В файле conftest.py обычно размещаются фикстуры, которые используются несколькими модулями. Pytest автоматически импортирует все модули conftest.py из каталога, в котором вы работаете. Это отличное место для размещения фикстур с драйверами TestProject. Обратите внимание: когда вы используете фикстуру в качестве аргумента в другой функции, фикстура выполнит настройку, передаст все из оператора yield в тестовую функцию, выполнит тестовую функцию, а затем освободит ресурсы.

conftest.py хранит фикстуры для всех модулей, находящихся в каталоге.

# tests/conftest.py
import time
import pytest
from src.TestProject.sdk.drivers import web driver
@pytest.fixture
def driver():
    "creates a webdriver and loads the homepage"
    driver = webdriver.Chrome()
    driver.get("https://waylonwalker.com/")
    yield driver
    driver.quit()

Приведенный выше пример является немного упрощенным. В реальной версии я столкнулся с некоторыми несоответствиями и обнаружил, что процент прохождения некоторых тестов был выше при добавлении оператора time.sleep. В полном проекте я остановился на фикстурах driver и slowdriver. Таким образом, у меня еще есть драйвер, который ждет выполнения JavaScript немного дольше.

testrepos.py

Полная версия testrepos.py есть на GitHub.

Изначально я настроил три разных теста для карточек репозиториев. Я сформировал список репозиториев, которые должны были отображаться в карточках. Эти тесты довольно легко выполнить с помощью платформы TestProject.io, поскольку она использует Selenium и безголовый браузер для выполнения JavaScript. Область REPOS создана здесь как глобальный список. Ее можно легко переделать в файл конфигурации, если возникнет такая необходимость.

Для тех, кто не знает, скажу, что безголовый (headless) браузер работает как обычный браузер, только без графического интерфейса пользователя. JavaScript полностью загружается и парсится, а все взаимодействие с DOM осуществляется программно.

Читайте строки документации к каждой функции. В них описано то, что происходит на каждом шаге.

"""
Test that GitHub repo data dynamically loads the client-side.
"""
REPOS = [
    "find-kedro",
    "kedro-static-viz",
    "kedro-action",
    "steel-toes",
]
def test_repos_loaded(slow_driver):
    """
    Test that each repo-name exists as a title in one of the repo cards.
    On waylonwalker.com repo cards have a title with a class of "repo-name"
    """
    repos = slow_driver.find_elements_by_class_name("repo-name")
    # get innertext from elements
    header_text = [
        header.text for header in repos
    ]
    for repo in REPOS:
        assert repo in header_text
def test_repo_description_loaded(slow_driver):
    """
    Test that each repo has a description longer than 10 characters
    On waylonwalker.com repo cards have a descriptiion with a class of "repo-description"
    """
    repo_elements = slow_driver.find_elements_by_class_name("repo")
    for el in repo_elements:
        desc = el.find_element_by_class_name("repo-description")
        assert len(desc.text) > 10
def test_repo_stars_loaded(slow_driver):
    """
    Ensure that stars are correctly parsed from the API and loaded client-side
    On waylonwalker.com repo cards have a stars element with a class of "repo-stars" and
    is displayed as "n stars"
    """
    repo_elements = slow_driver.find_elements_by_class_name("repo")
    for el in repo_elements:
        stars = el.find_element_by_class_name("repo-stars")
        num_stars, label = stars.text.split()
        assert int(num_stars) > 0
        assert label == 'stars'

Форум

Я немного запутался с настройкой TestProject.io в Actions. На форуме TestProject я быстро нашел ответ со ссылкой именно на тот пример, который был мне нужен. Пример был написан на Java, но в нем были нужные мне шаги настройки docker-compose.

GitHub Actions

[test-waylonwalker-com.yml]

Итак, репозиторий GitHub настроен, а мои тесты успешно работают в Pytest. Теперь давайте сделаем так, чтобы они автоматически выполнялись в GitHub Actions.

Сервис Actions — это решение GitHub для CI/CD. Он позволяет выполнять код на виртуальной машине, управляемой GitHub, которая может получать дополнительную информацию из вашего репозитория, например секретные токены, которые мы настроили в самом начале. Что, как и когда выполняется — все это настраивается в файле yaml.

# .github/workflows/test-waylonwalker-com.yml
name: Test WaylonWalker.com
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
  schedule:
    - cron: '*/10 * * * *'

Как видно в приведенном выше куске кода, я задал выполнение действия при отправке изменений в ветку main (команда push) или при создании pull-запроса, затрагивающего эту ветку. Я также задал достаточно агрессивный график выполнения тестов — каждые 10 минут. Это было необходимо, чтобы убедиться в эффективности тестов и получить больше данных для анализа в отчетах. Позднее я, скорее всего, увеличу этот интервал.

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@main
    - uses: actions/setup-python@v2
      with:
        python-version: '3.8'
    - run: pip install -r requirements.txt
    - name: Run TestProject Agent
      env:
        TP_API_KEY: ${{ secrets.TP_API_KEY }} # < Let Secrets handle your keys
      run: |
        envsubst < .github/ci/docker-compose.yml > docker-compose.yml
        cat docker-compose.yml
        docker-compose -f docker-compose.yml up -d
    - name: Wait for Agent to Register
      run: bash .github/ci/wait_for_agent.sh
    - run: pytest
      env:
        TP_DEV_TOKEN: ${{ secrets.TP_DEV_TOKEN }} # < Let Secrets handle your tokens
        TP_AGENT_URL: http://localhost:8585

В коде задания test видно, что я решил выполнять его на ubuntu-latest. Первые три шага — это достаточно шаблонные действия: переключение на ветку репозитория, установка Python 3.8 и установка зависимостей из файла requirements.txt через pip. Затем ключ TPAPIKEY подставляется в docker-compose.yml с помощью envsubst, далее запускается docker-compose и ожидает готовности агента. Я также предоставил pytest наш токен TPDEVTOKEN и запустил pytest.

docker-compose.yml

Следующий файл docker-compose.yml был предоставлен Виталием Буховским (одним из сооснователей и директором по безопасности TestProject) в репозитории testproject-io/java-sdk. Здесь настраивается шаблон с ключом TPAPIKEY в качестве переменной для envsubst, безголовыми браузерами Chrome и Firefox и агентом TestProject.io.

version: "3.1"
services:
  testproject-agent:
    image: testproject/agent:latest
    container_name: testproject-agent
    depends_on:
      - chrome
      - firefox
    environment:
      TP_API_KEY: "${TP_API_KEY}"
      TP_AGENT_TEMP: "true"
      TP_SDK_PORT: "8686"
      CHROME: "chrome:4444"
      CHROME_EXT: "localhost:5555"
      FIREFOX: "firefox:4444"
      FIREFOX_EXT: "localhost:6666"
    ports:
    - "8585:8585"
    - "8686:8686"
  chrome:
    image: selenium/standalone-chrome
    volumes:
      - /dev/shm:/dev/shm
    ports:
    - "5555:4444"
  firefox:
    image: selenium/standalone-firefox
    volumes:
      - /dev/shm:/dev/shm
    ports:
    - "6666:4444"

Ожидание регистрации агента

waitforagent.sh

На мой взгляд, самая интересная часть приведенного выше потока операций — это то, как мы ожидаем регистрации агента. Скрипт оболочки довольно лаконичный. Он обнаруживает превышение допустимого количества попыток запуска (max_attempts) или наличие запущенного агента путем проверки статуса через REST API (адрес: /api/status). Это позволяет нам избежать потери времени, вызванной заданием слишком большого времени ожидания, или слишком раннего начала процесса, при котором pytest запускается, когда агент еще не работает.

trap 'kill $(jobs -p)' EXIT
attempt_counter=0
max_attempts=100
mkdir -p build/reports/agent
docker-compose -f docker-compose.yml logs -f | tee build/reports/agent/log.txt&
until curl -s http://localhost:8585/api/status | jq '.registered' | grep true; do
    if [ ${attempt_counter} -eq ${max_attempts} ]; then
    echo "Agent failed to register. Terminating..."
    exit 1
    fi
    attempt_counter=$(($attempt_counter+1))
    echo
    sleep 1
done

Панель отчетности TestProject

После запуска тестов они отображаются на панели TestProject. На ранней стадии разработки тестов возникло несколько сбоев, но после устранения неполадок тесты прогоняются стабильно.

Панель: прохождение одного теста

Панель отчетности позволяет находить отдельные тесты, которые были выполнены, выбирать их и просматривать отчеты по каждому тесту. Она автоматически преобразует шаги, выполненные драйвером, в понятную человеку схему процесса, при этом каждый шаг можно открыть и посмотреть значения, которые были получены драйвером от сайта.

Подробнее о панелях отчетности TestProject можно прочитать по следующим ссылкам:

Это пособие создал Уэйлон Уокер (Waylon Walker) на основе своей статьи «Интеграционное тестирование с помощью Python, TestProject.io и GitHub Actions».

Удачного тестирования!


- Узнать подробнее о курсе "Python QA Engineer".


-
Записаться на открытый вебинар "Автоматизация тестирования API".