Хабрахабр

Автоматизация End-2-End тестирования комплексной информационной системы. Часть 1. Организационная

Этой статьей мы открываем серию публикаций о том, как автоматизировали в одном из крупных проектов компании ЛАНИТ процесс ручного тестирования большой информационной системы и что у нас из этого вышло.

Руководители проектов, лидеры групп и владельцы сервисов функционального и автоматического тестирования, все, кого волнует вопрос «как построить экономически эффективное end-2-end тестирование своей ИТ системы», найдут здесь конкретный план и методику. Первая часть – организационно-управленческая – должна быть полезна в первую очередь тем, кто отвечает за автоматизацию тестирования и создает такие системы в целом.

Источник
Изначально на старте имелась большая и сложная информационная система  (будем называть ее «Система») с многочисленными сложными, длинными и взаимосвязанными бизнес-сценариями. Все сценарии тестировались как E2E через веб-интерфейсы исключительно в ручном режиме (таких сценариев только самого критичного приоритета было более полутора тысяч). Причем все эти сценарии необходимо было пройти как минимум один раз в течение регресса каждого нового релиза или хотфикса перед очередным деплоем обновления в продуктив.

Что и сделали через разработку отдельного сервиса на базе java+selenium+selenide+selenoid, который далее называется «фреймворк тестирования» или просто – «Автотесты». В определенный момент, когда кликать мышкой в ручном режиме стало уже совсем невыносимо, решили это все автоматизировать.

Сначала первая команда создала прототип с парой дюжин сценариев. Исторически код фреймворка тестирования разрабатывался двумя командами. Затем вторая команда за год масштабировала прототип как вширь (количество тестов), так и вглубь (введены типовые паттерны кодирования и имплементации).

Я являюсь тим- и техлидом второй команды, которая приняла прототип фреймворка для масштабирования (в мае 2018).

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

Итог

Автоматизировано порядка 1500 тестовых сценариев: в каждом тесте – от 200 до 2000 тысяч пользовательских операций. 

Общая мощность сервиса  – до 60 одновременно работающих браузеров, и это не предел (количество можно увеличить раз в 5 за счет виртуальных машин). 
Общая длительность полного регресса – не более 3 часов, а PreQA-теста – менее часа. 

Реализован обширный спектр возможностей:

  • локальное использование (real-time execution) и удаленное (через Bamboo-планы);
  • ограничение состава запускаемых тестов по фильтру;
  • детальный отчет с результатами выполнения каждого шага тестового сценария (через Allure-фреймворк);
  • загрузка и выгрузка файлов из/в браузер с последующей проверкой результатов их обработки в части формата и содержания файлов;
  • учет и контроль асинхронной природы ангуляр-интерфейса. В том числе контроль зависших запросов (pending request) между Angular-м и REST-сервисами;
  • контроль логов браузера;
  • запись видеотеста;
  • снятие снапшота страницы в точке «падения» теста;
  • передача событий в ELK;
  • многое другое по мелочи… 

На старте назначение системы было достаточно просто и понятно. 

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

Всё это превращается в полторы тысячи тестовых сценариев, и это только высшего приоритета и только положительные.

В процессе автоматизации выявлялись различные нюансы, которые потребовали применения различных решений.

А теперь еще добавим ограничение повторного использования тестовых данных: тестовые данные для успешного прохождения большинства тестовых сценариев должны быть «свежими» и ранее не использованными в аналогичных сценариях (в ходе выполнения проверок состояние данных в Системе меняется, вследствие чего они не могут быть повторно использованы для тех же проверок). Например, один сценарий мог содержать до сотни отдельных операций, включая такие интересные, как: «Загрузить EXCEL-файл с данными и проверить корректность обработки Системой каждой записи из файла» (для решения этой задачи потребовалась многоступенчатая подготовка данных и последующая проверка результата их загрузки в Систему).

Источник

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

Затем Автотесты (далее по тексту – Автотесты. Другими словами, группа функционального тестирования открывает «страницу», выбирает «группу тестов», нажимает кнопку «выполнить» (мы использовали Bamboo). д.), по завершении выводят детальный отчет по всем шагам и выполненным действиям и результаты проверки (соответствие ожидаемой реакции Системы ее фактическому поведению). Обозначают созданный продукт для тестирования в целом) автоматически через браузер эмулируют действия пользователей в Системе («жмут» необходимые кнопки, вводят значения в полях и т.

Это «внешняя» система, которая не принимает участия в процессе разработки тестируемой Системы и никак не связана с модульными или интеграционными тестами, используемыми разработчиками. Итого, назначением Автотестов является автоматизация ручного Е2Е-тестирования.

Цели

Необходимо было значительно снизить трудозатраты на проведение Еnd-2-Еnd тестирования и повысить скорость прохождения полных и сокращенных по своему объему регрессов. 

Дополнительные цели

  • обеспечить высокую скорость разработки автотестов с высоким уровнем автономности (должна быть сведена к минимуму необходимость в предварительном наполнении тестовыми данными стендов Системы / настройке Автотестов для запуска на каждом стенде);
  • оптимизировать затраты (временные и финансовые) на коммуникации между командами автоматизации, функционального тестирования и разработки Системы;
  • минимизировать риск расхождения фактически реализованных автотестов с исходными ожиданиями команды функционального тестирования (команда функционального тестирования должна безоговорочно доверять результатам выполнения Автотестов).

Задачи

Основная задача разработки была сформулирована очень просто — автоматизировать в течение следующих 6 месяцев 1000 тестовых сценариев высшего приоритета.

тест-методов по 10-20 строк кода, без учета общих и вспомогательных классов хелперов, дата-провайдеров и моделей данных. Прогнозируемое количество базовых тестовых действий составляло от 100 до 300, что давало нам около 200 тыс.

Таким образом, получалось, что с учетом временных ограничений (130 рабочих дней) необходимо было делать минимум 10 тестов в день и при этом обеспечить актуальность реализованных автотестов с учетом изменений, происходящих в Системе (Система активно развивается).

Так мы получили команду в 5 человек минимум (в реальности в пике разработки автотестов в команде было больше 10 инженеров автоматизации).  Согласно экспертной оценке, трудозатраты на разработку одного автотеста составляли 4-8 часов.

Задачи, которые надо было решить, также были понятны.

  • Сконфигурировать процессы и команду:
  • определить процесс взаимодействия с заказчиком (группа функционального тестирования), зафиксировать формат описания тест-кейса в качестве входных данных для команды автоматизации;
  • организовать процесс разработки и сопровождения;
  • сформировать команду.
  • Разработать автотесты со следующими возможностями:
  • автоматически кликать в браузере на кнопки с предварительной проверкой наличия элементов и необходимой информации на странице;
  • обеспечить работу со сложными элементами типа Яндекс.Карта;
  • обеспечить загрузку автоматически генерируемых файлов в Систему, обеспечить выгрузку файлов из Системы с проверкой их формата и содержания.
  • Обеспечить запись «с браузера» скриншотов, видео и внутренних логов.
  • Обеспечить возможность интеграции с внешними системами наподобие почтового сервера, системы отслеживания задач (JIRA) для проверки интеграционных процессов между тестируемой Системой и внешними системами.
  • Предоставлять документированный отчет по всем совершаемым действиям, включая показ вводимых и проверяемых значений, а также все необходимые вложения.
  • Выполнять тесты в необходимом объеме в параллельном режиме.
  • Развернуть автотесты в имеющейся инфраструктуре.
  • Доработать уже автоматизированные тест-сценарии согласной целевой концепции (скорость доработки -  порядка 50 тестов за недельный спринт).

Как я уже упоминал во введении, на старте мы имели рабочий MVP-прототип, реализованный другой командой, который надо было довести от 20 тестов до 1000, попутно добавляя новые фичи, и обеспечить приемлемую масштабируемость и гибкость внесения изменений.

Так как прототип нормально работал и обеспечивал необходимую базовую функциональность, решили не менять технологический стек, а сосредоточиться на развитии масштабируемости решения, повышения стабильности и разработке отсутствующих необходимых фич. Наличие рабочего прототипа дополнительно на входе дало нам технологический стек, который включал: Java SE8, JUnit4, Selenium + Selenide + Selenoid, Bamboo как «запускатель» тестов и «построитель» отчетов Allure.

Более того, мы вполне справились с поставленными задачами в заданное время. В принципе, все выглядело реализуемым и оптимистичным.

Далее описываются отдельные технологические и процессные аспекты разработки Автотестов.

Источник

Автотесты реализуют следующий набор пользовательских историй в контексте их использования группой тестирования:

  • автоматизация ручного тестирования;
  • автоматический полный регресс;
  • контроль качества сборок в CI\CD цепочке.

О деталях реализации и архитектурных решениях будет сказано в Часть 2 – Техническая. Архитектура и технический стек. Детали реализации и технические сюрпризы.

Автоматическое и ручное тестирование (User stories)

Я как тестировщик хочу выполнить целевой Е2Е-тест, который выполнится без моего прямого участия (в автоматическом режиме) и предоставит мне подробный отчет в контексте выполненных шагов, включая введенные данные и полученные результаты, а также:

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

Автоматический полный регресс

Я как группа тестирования хочу каждую ночь выполнять все тесты на определенном тест-стенде в автоматическом режиме, включая все возможности «Автоматического ручного тестирования».

Контроль качества сборок в CI\CD цепочке

Я как группа тестирования хочу проводить автоматическое тестирование развертываемых обновлений Системы на выделенном preQA-стенде до обновления целевых functional test stage стендов, которые в дальнейшем использовались для функционального тестирования.

Источник

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

Локальное и удаленное использование

Функция предлагала два варианта запуска Автотестов — локальный и удаленный.

Запуск выполнялся через «зеленый треугольник» в IntelliJ IIDEA -). В локальном режиме тестировщик запускал требуемый автотест на своем рабочем месте и при этом мог наблюдать, что происходит в браузере. Функция была очень полезна на старте проекта для отладок и демонстраций, однако сейчас используется только самими разработчиками автотестов.

В удаленном режиме тестировщик запускает автотест, используя интерфейс Bamboo-плана с указанием параметров состава запускаемых тестов, стенд и некоторые другие параметры.

Функция была реализована с использованием переменной окружения MODE = REMOTE|LOCAL, в зависимости от которой инициализировался либо локальный, либо удаленный веб браузер в облаке Selenoid.

Ограничение состава запускаемых тестов по фильтру

Функция дает возможность для удобства пользователей и сокращения времени тестирования ограничивать состав запускаемых тестов при удаленном режиме использования. Используется двухступенчатая фильтрация. Первый шаг блокирует выполнение тестов на основании переменной FILTER_BLOCK и используется в первую очередь для исключения из прогона больших групп тестов. Второй шаг «пропускает» только тесты, которые соответствуют переменной FILTER.

Значение фильтров задается как набор регулярных выражений REGEXP1, …, REGEXPN, применяемых по принципу «ИЛИ».

Для каждого теста данная аннотация является уникальной и конструируется по принципу набора тегов, разделенных знаком подчеркивания. Тестировщику предлагалось в Bamboo-плане при запуске в ручном режиме задать специальную переменную окружения как перечень регулярных выражений, применимых к специальной аннотации @ Filter(String value), которой аннотировались все тест-методы в тест-классах. Мы используем следующий минимальный шаблон ПОДСИСТЕМА_ФУНКЦИЯ_ТЕСТ-ИД_, где тэг DEFAULT предназначен для тестов, входящих в автоматический ночной регрес.

BlockJUnit4ClassRunner (детали будут приведены в Части 2-1 продолжения этой статьи) Функция реализована через кастомное расширение класса org.junit.runners.

Документирование отчета с результатами по всем шагам

Результаты выполнения теста выводятся по всем тестовым действиям (шагам) со всей требуемой информацией, которая доступна в Allure Framework. Перечислять их не имеет смысла. Информации достаточно как и на официальном сайте, так и в интернете в целом. Сюрпризов с использованием Allure Framework не было, и в целом рекомендую его к использованию.

Основными используемыми функциями являются:

  • отображение каждого тестового шага (имя шага соответствует его имени в спецификации теста — тестовом сценарии);
  • отображение параметров шага в человеко-читаемом виде (через требуемую реализацию метода toString всех передаваемых значений);
  • прикладывание к отчету скриншотов, видео и различных дополнительных файлов;
  • классификация тестов по типам и подсистемам, а также связывание автотеста с тест-спецификацией в системе управления тест-кейсам Test Link за счет использования специализированных аннотаций.

Загрузка и выгрузка файлов из/в браузер с последующей их проверкой и разбором

Работа с файлами – крайне важный аспект тестовых сценариев. Необходимо было обеспечить как upload различных файлов, так и download.

Загрузка была реализована стандартными методами, предоставляемыми средствами selenium. Загрузка файлов подразумевала, в первую очередь, загрузку в Систему динамически формируемых EXCEL-файлов в соответствии с контекстом выполнения тестовых сценариев.

Далее этот файл разбирался и анализировался с точки зрения формата и содержания. Выгрузка файлов подразумевала загрузку файлов по нажатию «кнопки» в браузере в локальную директорию с последующим «перебрасыванием» этого файла на сервер, где исполнялись Автотесты (сервер, на котором установлен удаленный Bamboo-агент). Основными типами файлов были EXCEL- и PDF-файлы.

Реализация этой функции оказалась нетривиальной задачей, в первую очередь из-за отсутствия стандартных возможностей по работе с файлами: на текущий момент функция реализована только для браузера Chrome через служебную страницу «chrome://downloads/».

О деталях реализации я подробно расскажу во второй части. 

Учет и контроль асинхронной природы Angular-интерфейса. Контроль зависших запросов (pending request) между Angular-м и REST-сервисами

Поскольку объект нашего тестирования базировался на Angular, нам пришлось научиться «бороться» с асинхронной природой фронтенда и таймаутами.

FluentWait используем специально разработанный метод ожидания, который через Javascript проверяет наличие «незавершенных» взаимодействий с REST сервисами фронтенда, и на основе этого динамического таймаута тесты получают информацию, можно ли следовать далее или еще чуть-чуть подождать. В общем случае мы дополнительно к org.openqa.selenium.support.ui.

Дополнительно это позволило определять «зависающие» REST-сервисы с проблемами в производительности. С точки зрения функциональности, мы смогли значительно сократить время прохождения тестов за счет отказа от статических ожиданий там, где нет возможности по-другому определить завершение операции. Например, так отловили REST-сервис, для которого количество выводимых на страницу записей было установлено в 10 000 элементов.

Это позволяет сразу передавать выявленные проблемы соответствующим группам разработки Системы. Информация о «зависшем» REST-сервисе со всеми параметрами его вызова, из-за которого по инфраструктурным причинам «падает» тест, добавляется к результатам упавшего теста, а также дополнительно транслируется как событие в ELK.

Контроль логов браузера

Функция контроля логов браузера была добавлена для контроля ошибок на страницах уровня SEVERE, чтобы получать дополнительную информацию для упавших тестов, например, контролировать ошибки типа «… Failed to load resource: the server responded with a status of 500 (Internal Server Error)».

Состав ошибок обработки страниц в браузере прикладывается к каждому результату теста, а также дополнительно выгружается как события в ELK.

Видеозапись теста и снятие снапшота страницы в точке «падения» теста

Функции реализованы для удобства диагностирования и разбора упавших тестов.

Видео прикладывается как вложение к результатам в Allure-отчете.
Скриншот экрана делается автоматически при падении теста, и результаты также прикладываются к Allure-отчету. Запись видео включается отдельно для выбранного удаленного режима запуска теста.

Передача событий в ELK 

Функция отправки событий в ELK реализована для возможности статистического анализа общего поведения Автотестов и стабильности объекта тестирования. На текущий момент реализована отправка событий о завершении тестов с результатами и длительностью, а также ошибки браузера уровня SEVERE и зафиксированные «зависшие» REST-сервисы.

Источник

Команда разработки

Итак, нам требовалось минимум 5 разработчиков. Прибавим ещё одного человека для компенсации незапланированных отсутствий. Получаем 6. Плюс тим-лид, который отвечает за cross-cutting функциональность и ревью кода.

Таким образом, нужно было взять 6 Java-разработчиков (в реальности на пике разработки автотестов состав команды превысил 10 инженеров автоматизации).

Фактически мы искали людей с базовым знанием Java. Учитывая общее состояние рынка и достаточно простой технологический стек, команда формировалась преимущественно из стажеров, большинство из них либо только окончили университеты, либо учились на последнем курсе. Предпочтение отдавали специалистам ручного тестирования, которые хотели бы перейти в программисты, и мотивированным кандидатам с некоторым (незначительным) опытом разработки, которые в будущем хотели стать программистами.

Архитектура и технический стек. Опора на начинающих юниоров потребовала ввода компенсационных механизмов в виде конкретного карьерного предложения, четкого регламента работы и разделения приложения на отдельные функциональные домены (Об этом подробнее в Часть 2 – Техническая. Детали реализации и технические сюрпризы).

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

Процесс разработки

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

Выполненные задачи проходят code review через процедуру merge request (используется GitLab). Задачами являются в первую очередь разработка новых тестов или доработка (актуализация) существующих. После успешного code review результаты сразу «мержатся» в основную ветку (фактически в продуктив) и становятся доступными для использования сразу же.

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

Сode review делает тимлид. Сode review обязательно проходит шаг, и, если на этом шаге результаты (код) не соответствуют код-конвенции и архитектурным требованиям, задача возвращается на доработку.

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

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

На практике это была сумма всех абзацев в тест-сценарии.  Оценка трудоемкости реализации осуществлялась в «тестовых шагах», которые фактически определялись по автоматизируемому тест-сценарию простым суммированием действий-шагов.

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

Спецификация задач на разработку

Исходные задачи на разработку в бэклог поступали как тест-спецификации (тестовые сценарии) от группы функционального тестирования, которая выступала внутренним пользователем и stakeholder для Автотестов.

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

д.), которые по своей сути являются требованиями по проверке функционала Системы. Необходимо использовать повелительное наклонение при описании шагов тест-кейсов (открыть страницу, ввести значения в поля и т. Если требуется указать дополнительную (вспомогательную) информацию, то необходимо явно отмечать ее как «дополнительная информация» / «к сведению» (это важно, чтобы у команды не было путаницы, является ли эта информация руководством к действию или приведена только для справки).

Кроме этого, не допускается описывать в рамках одного тест-кейса несколько сценариев проверок с разными предусловиями / разными шагами: здесь действует правило «Один тест-кейс описывает один линейный сценарий проверок (позитивный или негативный)». Один тест-кейс должен содержать линейную последовательность шагов проверок без возможности ветвления логики проверок на каком-то из шагов (пример ветвления: если результат выполнения какого-то шага – ошибка, то выполнить такие-то шаги, если результат — успех, то выполнить другие шаги).

Таким образом в спецификации «основного» тест-кейса допускалось указывать в качестве одного из шагов необходимость выполнения кейса-донора как «Выполнить все шаги из кейса X» (при этом, не допускается частичное выполнение шагов из кейса-донора). Чтобы однотипные группы шагов в тест-кейсах не дублировались, допускается полное переиспользование шагов тест-кейса (кейс-донор) в качестве части шагов другого тест-кейса («основной» кейс).

Организация репозитория

Организация репозитория была достаточно простой, отвечающей требованиям процесса разработки. Была только одна главная ветка master, от которой ответвлялись фича-ветки. Фича-ветки через процедуру мерж-реквеста сливались в главную ветку сразу же по завершению code review.

На старте проекта мы решили, что не будем поддерживать разные релизы автотестов – в этом не было необходимости, поэтому мы и не создавали отдельные релизные ветки. 

Особенности:

(+) простой процесс непрерывной доставки функций для малых команд;
(+) позволяет развертывать функции по мере их разработки без необходимости релизного планирования на уровне кода;
(+) позволяет «догонять» реальную разработку без внесения релизной задержки;
(-) поддерживается ТОЛЬКО ОДНА версия фреймворка (актуальная);
(-) в предыдущих версиях Автотестов поддерживается только hotfix багов.

В итоге мы пришли к следующим правилам.

Разработчик

  • Отвести ветку требуемого типа от MASTER.
  • Выполнить разработку.
  • Выполнить тестирование на требуемом стенде FEATURE ветки.
  • Если автоматизатор работал над веткой самостоятельно и она имеет линейную историю, следует выполнить «сжатие линейной истории комментов» через rebase.
  • Выложить финальный комит ветки в Gitlab и создать merge request на тимлида или ответственного. В merge request-е указать:
  • название — код задачи в системе баг трекинга «Jira»;
  • описание — ссылка на результат тестирования ветки в Bamboo.

GateKeeper (выполняется тимлидом)

  • Проконтролировать работоспособность ветки девелоп по результатам автотеста в Bamboo.
  • Провести аудит кода на соответствие конвенции и правилам архитектурного дизайна.
  • Влить (merge) FEATURE в DEVELOP, в случае конфликта задача возвращается разработчику для устранения конфликта в рабочем порядке.
  • Закрыть таск на разработку.

Статья написана в соавторстве с руководителем проекта и владельцем продукта kotalesssk.

На этом часть 1, посвященная организации процесса разработки и использования, заканчивается. Технические аспекты реализации далее будут приведены в части 2.

Здесь они найдут конкретные рецепты по архитектурной организации кода и развертывания, которая поддерживает массо-параллельную разработку больших групп тестов в условиях постоянной изменчивости тестовых спецификаций. Вторая часть  – техническая – ориентирована прежде всего на лидеров групп автоматизации UI end-2-end тестирования и ведущих тест-автоматизаторов. И перечнем сюрпризов, с которыми вы также возможно столкнетесь. Дополнительно вы сможете найти во второй части полный состав необходимых для  UI -тестов функций с некоторыми деталями реализации.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть