Тестирование страниц входа и согласия может быть довольно сложным: та же самая двухфакторная аутентификация (2FA), которая обеспечивает безопасность ваших клиентов, также затрудняет написание автоматизированных тестов. В этой статье я расскажу, как написал Python-тесты, которые обходят 2FA и при этом не покушаются на  безопасность клиентов. Для этого я использовал Selenium и разработал Slackbot.

Будучи QA-инженером в компании NMBRS, которая поставляет программное обеспечение для управления персоналом и расчета заработной платы, я должен был разработать автоматизированные тесты наших страниц входа и согласия, чтобы выяснить, где были проблемы в службе идентификации. Службу идентификации используют наши клиенты для входа в систему.

Поскольку мы работаем с конфиденциальной информацией, процесс входа требует службы аутентификации. Мы используем OAuth2.0, стандартный протокол авторизации, который также используется Facebook, Instagram и Whatsapp.

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

  1. OAuth2.0 управляет двухфакторной аутентификацией (2FA) и отправляет код аутентификации на электронный ящик или мобильный телефон. Поэтому автоматизация этого процесса стала трудной задачей, так как мы не могли связать его с электронным ящиком или мобильным телефоном. Автоматизированные тесты должны выполняться без каких-либо ручных действий, поэтому мы не могли связать их с учетной записью электронной почты или мобильным телефоном.

  2. NMBRS использует shadow-rooted Selenium для автоматизации тестов, что обычно хорошо подходит, поскольку скрывает веб-разделы. В данном конкретном случае shadow-rooted веб-компоненты усложнили разработку тестов, поскольку тест должен найти эти shadow-rooted элементы и указать селениуму, как вводить текстовые элементы.

Чтобы решить проблему 2FA, я создал Slackbot, который бы подходил для Selenium тестов. Затем я развернул бота на локальном сервере NGROK, который позволил Selenium тестам получить доступ к нему через эндпоинт. Это позволило мне протестировать систему локально, что было необходимо для того, чтобы перед развертыванием в Azure DevOps убедиться, что все идет так, как ожидалось. Чтобы решить проблему с Selenium, я написал команду, которую найдете дальше в статье.

Вот проблемы, с которыми мы столкнулись во втором релизе, и то, как мы их решили:

В первом релизе продукт тестировался в целом с помощью агрегированного тестирования, которое запускает все в одном потоке, поэтому мы не могли найти точное место, где продукт дает сбой. Например, если служба идентификации ломается, клиент не может войти в систему.

Во втором релизе весь тест был разделен на юнит-тесты. Используя pytest, библиотеку Python, мы разработали юнит-тесты, чтобы выяснить, где именно ломается служба идентификации. Тесты должны были:

  1. Определить, работает ли Slackbot, который помог нам обойти 2FA.

  2. Проверить, доступен ли сервер веб-приложения. Это необходимо, потому что если сервер веб-приложения выйдет из строя, вся система будет скомпрометирована, включая тесты.

  3. Проверить, правильно ли заполнена страница входа в систему, поскольку иногда команда UI/UX изменяет веб-компоненты, чтобы улучшить дизайн приложения или исправить ошибку, которая может нарушать работу службы идентификации. Например, если текстовое поле неправильно связано с именем пользователя или паролем, пользователь не сможет войти в систему.

  4. Проверить, что Slackbot получает уведомление о необходимости кода аутентификации 2FA и извлекает код после получения уведомления.

  5. Проверить, действителен ли код 2FA, полученный Slackbot.

  6. Проверить, все ли профили на странице согласия разрешены.

  7. Проверить, что после нажатия кнопки "разрешить" из url извлекается код, и подтвердить, что этот код был извлечен.

  8. Получить токен доступа (access token) и рефреш токен (refresh token) через OAuth 2.0.

  9. Сгенерировать новый токен доступа, который должен отличаться от предыдущих.

Шаги с 4 по 6 были самой трудной частью разработки тестов, их я рассмотрю более подробно ниже.

Подход к тестированию: Gherkin Reference

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

Gherkin — это строчно-ориентированный язык. Каждая строка начинается с ключевого слова и называется шагом. Основная цель строчно-ориентированного языка — помочь разработчику или QA-инженеру проследить, как тест выполняется на каждом этапе. В Gherkin сценарий состоит из одного или нескольких шагов и обычно имеет следующую структуру:

  1. Given: шаг, на котором определяется предварительное условие теста.

  2. When: шаг, на котором определяется действие, которое мы хотим протестировать.

  3. Then: шаг, когда ожидаемый результат успешен.

  4. And: дополнительные шаги, добавляемые к Given/When/Then, которые используются вместо повторения этих слов.

Ключевое слово "Scenario" является уникальным, и вы просто запускаете его один раз, в то время как "Scenario Outline" может иметь несколько примеров. Говоря более технически, он может работать как шаблон.

Например:

Scenario Outline: Check Login
 Given I access environment '<Environment>' on country '<Country>'
 When On login page, I click on Sign-In Button
 And On sign-in page, I fill in user '<User>' and password '<Password>'
 Then Check if the user is in start page '/start.aspx'

Таблица, использующая эти переменные, будет выглядеть следующим образом:

Examples:
| Environment     | Country | User   | Password |
| www.testenv.com | PT      | gui    | 1234     |
| www.testenv.com | NL      | thais  | 123      |

Теперь самый главный вопрос: как мне удалось решить две основные проблемы, с которыми я столкнулся во время разработки?

Shadow Root (теневой корень) на веб-элементах

Распространенная проблема страниц входа в систему, с которой сталкиваются разработчики UI/UX, — это инкапсуляция. Большинство компаний используют мощную технику, Shadow DOM, чтобы скрыть детали реализации. Чтобы защитить продукт NMBRS, наша команда DevOps скрывает конфигурацию веб-компонента (например, CSS или JS) определенного элемента или даже раздела HTML. NMBRS использует Shadow DOM, чтобы скрыть детали реализации и связать множество shadow hosts (теневых хостов) с другими shadow roots (теневыми корнями). Это позволяет нам маскировать некоторые веб-компоненты в объектной модели документа (DOM).

Практическими словами, Shadow Root — это способ скрыть и, следовательно, защитить служебную информацию.

В этой части возникла первая проблема. Поскольку элементы были с теневым корнем, мне было трудно указать Selenium-автоматике найти эти элементы. Это было сложно, поскольку теневой корень блокирует все классы и идентификаторы от определенного элемента, что не позволяет найти его, как, например, обычную кнопку.

После некоторых исследований и помощи других тестировщиков я использовал приведенную ниже команду для поиска различных компонентов. Нужно было только изменить класс или id компонентов. Например, вместо 'nmbrs-form' можно было использовать 'nmbrs-button'.

return document.querySelector(’nmbrs-form’).shadowRoot.querySelector(’div div.btn-container nmbrs-button’).shadowRoot.querySelector(’button#button’)

Как видите, есть два элемента Shadow Root. Это команда запроса, где querySelector() захватывает родительский компонент дочернего shadowRoot или конечного элемента. По сути, чтобы получить информацию внутри теневого хоста, в данном случае 'nmbrs-form', достаточно написать '.ShadowRoot'. Это позволило мне указать Selenium, какие компоненты нужно найти, несмотря на Shadow Root.

После настройки Selenium-автоматизации мне нужно было создать Slackbot для получения кода аутентификации 2FA. Как в реальной, так и в тестовой среде этот код обычно отправляется на электронную почту пользователя. Однако для автоматизации этого процесса в тестовой среде этот код отправляется в закрытый канал Slack. Этот канал принимает только сигналы, и никто, кроме инженеров отдела контроля качества, не имеет к нему доступа. Мы использовали Slack, потому что он является неотъемлемой частью нашего общения и рабочего процесса в NMBRS.

Обход 2FA с помощью Slackbot

Каждый сигнал, генерируемый в тестовой среде, поступает непосредственно в частный канал Slack. Из-за опасений по поводу безопасности пользовательских данных и политики NMBRS я создал Slackbot, чтобы получить код 2FA. Slackbot смог обеспечить безопасность, выполняя те же действия, что и обычный пользователь, и тестовую скорость, одновременно получая код.

Интеграция со Slack

Чтобы запустить Slackbot в Slack, мне нужно было интегрировать его с платформой. Я создал QA AUTHENTICATION — API для чтения писем с кодом 2FA на частном канале. Эта интеграция была основана на подписках на события: всякий раз, когда мы получали новое письмо от OAuth2.0, канал Slack уведомлял Slackbot.

#EVENT SUBSCRIPTION TO KNOW WHEN A NEW FILE IS SHARED
@slack_event_adapter.on('file_shared')
def data_handler(payload):
    if automated_test:
        event = payload.get('event', {})
        file_id = event.get('file_id')

        SlackR = client.files_info(file=file_id)

        SlackMessage = SlackR.data.get("file").get('plain_text')

        if SlackMessage.find(login_email)!=-1:
            code = SlackMessage.split(" ")
            global auth_code
            auth_code = int(code[4])

Одним из недостатков Slackbot является то, что его нельзя запустить на Azure Pipelines. На наших виртуальных машинах (ВМ) не установлена платформа Slack. В приложении Slack использовался NGROK, поскольку тесты проводились локально на моем компьютере.

NGROK — это фреймворк локального сервера, который позволяет вывести веб-сервер, работающий на вашей локальной машине, в интернет. Кроме того, он предоставляет веб-интерфейс в реальном времени, с помощью которого можно исследовать весь HTTP-трафик.

Slackbot также функционирует как Flask REST API, с которым автоматизированный Selenium-тест и приложение slack взаимодействуют через эндпоинты.

Важно убедиться, что Slackbot получает уведомление об отправке кода аутентификации 2FA и что Slackbot получает код после его отправки. Это доказывает, что код 2FA у нас самый новый и что он действителен.

Переходим к самому интересному. Я хочу поделиться с вами правильным движением между автоматизированным тестом (Selenium), Slackbot и приложением Slack.

Как взаимодействуют Slackbot, приложение Slack и Selenium-тесты 

  1. Когда Selenium-тест начинает выполняться, он отправляет HTTP-запрос в приложение Slack, чтобы сообщить Slackbot о начале прослушивания канала.

  2. Когда тест достигает страницы 2FA, Slackbot запрашивает код 2FA.

  3. Бот прослушивает все недавно пришедшие сообщения с условием, что в сигнале должно быть письмо с логином Selenium-теста.

  4. После того как Slackbot распознает письмо, он запрашивает тело сообщения.

  5. Приложение Slack извлекает основное сообщение, а Slackbot помещает его в строку.

  6. Используя библиотеку Python, бот подхватывает код 2FA.

  7. Slackbot отправляет код обратно в Selenium-тест.

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

После этого автоматический тест должен нажать на кнопку "разрешить". Код разрешения авторизации (authorisation grant code) будет получен в URL через определенный эндпоинт и отправлен в приложение Flask. Затем юнит-тест захватывает код, чтобы определить, действителен он или нет.

Код разрешения авторизации важен, поскольку это единственный способ получить токен нового доступа и рефреш токен. Если у вас их нет, вы не сможете получить доступ к какой-либо информации в приложении NMBRS.

Наконец, с этим кодом предоставления полномочий последним шагом будет обращение к конечной точке '/connect/token' для получения нового 'access_token' и 'refresh_token' с последним 'refresh_token'.

И последнее, но не менее важное: после генерации нового токена доступа мы хотим проверить, отличаются ли старый и новый токены. А они должны! Эта последняя часть проверяется путем совершения POST-запроса от сервиса идентификации. В заголовке мы добавляем тип токена доступа (в данном случае 'Bearer') и рефреш токен. После выполнения запроса сервер получит ответ с новым токена доступа в теле сообщения. На этом процесс тестирования завершается!

# GET ACCESS TOKEN VIA REFRESH TOKEN
def get_access_token():
    header = {'Content-Type': 'application/x-www-form-urlencoded', 'Authorization':basicAuthorization_encoded}
    payload = {'refresh_token':refresh_token}
    response = requests.post(baseIdentityURL +  + '/connect/token', data=payload, headers=header)

    if response.status_code==200:
        responseJSON = response.json()
        new_access_token = responseJSON['access_token']

        if new_access_token!=access_token:
            tests["test_message"] = 'Access Tokens are different.'

        return

    tests["test_message"] = 'Access Tokens Not Received via refresh token.'
    return

Плюсы и минусы моего подхода

Как и любой другой подход, этот подход имеет свои плюсы и минусы.

Давайте начнем с плюсов:

  • Slackbot может протестировать каждую NMBRS среду.

  • Автоматизированное тестирование быстрее, чем выполнение теста вручную: в среднем на получение письма и ввод кода человек тратит 2-3 минуты; моему боту требуется всего 10 секунд.

  • Кроме того, тест может работать вечно, а его зависимости требуют меньше поддержки.

Минусы:

  • Для полноценного запуска автоматизированного теста необходимо, чтобы одновременно было открыто несколько инструментов, и он зависит от нескольких платформ. Если одна из них выйдет из строя, бот тоже откажет.

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

Учитывая эти плюсы и минусы, как мы можем улучшить тесты? Чтобы тесты было проще обслуживать и легче выполнять, важно перейти от тестов, созданных локально на компьютере, к Azure DevOps Pipelines. Следующий релиз мы сделаем на Azure DevOps. Миграция позволит устранить зависимости от Slackbot и расширить эти тесты для живых сред. На наших виртуальных машинах не установлен Slack. По этой причине игнорирование интеграции Slack путем обхода 2FA с помощью конкретной электронной почты будет отличным решением, которое будет работать как в живых, так и в тестовых средах.

Мы внедрили автоматизированное тестирование в NMBRS впервые, и оно оказало огромное влияние:

  • Текущий выпуск автоматизированных тестов позволил команде QA сэкономить время на тестировании.

  • Это позволило нам тестировать каждый день, чего мы не могли делать раньше при ручном тестировании.

  • Благодаря этому внедрению DevOps может быть лучше информирован о происходящем и действовать до того, как произойдет сбой продукта.

Мигрировав на Azure Pipelines и устранив зависимости, мы еще больше оптимизируем этот процесс.

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


Через пару дней состоится открытый урок, посвященный автоматизации тестирования на Python: поговорим о профессии и ее перспективах. Обсудим, какие навыки необходимы для работы и какие требования предъявляют к кандидатам на собеседованиях. Также разберем современные технологии автоматизации тестирования и преимущества использования автотестов.

Комментарии (2)


  1. Andrey_Solomatin
    29.05.2023 13:51
    +1

    Во втором релизе весь тест был разделен на юнит-тесты. Используя pytest, библиотеку Python, мы разработали юнит-тесты, чтобы выяснить, где именно ломается служба идентификации. Тесты должны были:

    Определить, работает ли Slackbot, который помог нам обойти 2FA.

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

    Проверить, правильно ли заполнена страница входа в систему, поскольку иногда команда UI/UX изменяет веб-компоненты, чтобы улучшить дизайн приложения или исправить ошибку, которая может нарушать работу службы идентификации. Например, если текстовое поле неправильно связано с именем пользователя или паролем, пользователь не сможет войти в систему.

    Проверить, что Slackbot получает уведомление о необходимости кода аутентификации 2FA и извлекает код после получения уведомления.

    Проверить, действителен ли код 2FA, полученный Slackbot.

    Проверить, все ли профили на странице согласия разрешены.

    Проверить, что после нажатия кнопки "разрешить" из url извлекается код, и подтвердить, что этот код был извлечен.

    Получить токен доступа (access token) и рефреш токен (refresh token) через OAuth 2.0.

    Сгенерировать новый токен доступа, который должен отличаться от предыдущих.


    Мнение о том, что есть юнит-тесты сильно расходятся. Но в среднем для тестов которые делают проверку сетевого взаимодействия используют другие термины, например: интеграционные.

    От названий вещей не своими именами ничего хорошего не происходит.


  1. lxsmkv
    29.05.2023 13:51

    Я когда-то интересовался этим вопросом. Потому что у нас доступ к тестируемому приложению был через пароль и токен. Накопал, что есть библиотеки для OTP (ну там PyOTP, например). По смыслу она будет делать то же самое, что делает ваш Google Authenticator в телефоне. Мы в коде прописываем инициализационный шифр, который нам дает сервис (обычно это среди опций, "добавить вручную" вместо qr кода). И наш тестировочный код, будет тем же алгоритмом создавать нам коды доступа как это делает ваш Google Authenticator или кто-то еще. Реализацией не занимался, но ребята с другого проекта рассказывали, что этот подход рабочий.