[Администрирование баз данных, Распределённые системы, Микросервисы] Как e2e автотесты на Selenide помогают QA-команде при частых релизах

Автор Сообщение
news_bot ®

Стаж: 6 лет 7 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
11-Мар-2021 14:30


Всем привет! Я Иван, старший инженер-тестировщик в КРОК. Уже 6 лет занимаюсь тестированием ПО. Из них 3 года внедряю автоматизацию тестирования на различных проектах — люблю всё автоматизировать. На рабочей машине много разных “батников” и bash-скриптов, которые призваны упрощать жизнь. Недавно у нас стартовал проект по модернизации и импортозамещению системы электронного документооборота (СЭД) в одной крупной организации. Система состоит из основного приложения и двух десятков микросервисов, в основном — для построения отчётов и интеграции с другими подсистемами. Сейчас в проекте уже настроено больше 100  автотестов, и они сильно помогают при частых релизах, когда времени на регресс почти нет. Весь набор автотестов выполняется примерно за 25 минут, в среднем экономим до 3,5 часов ручной работы при каждом запуске. А запускаем мы их каждый день.Дальше будет про то, как мы выбирали технологии и инструменты, какой  каркас и подход к организации автотестов в итоге получился. И почему мы в КРОК решили тиражировать этот подход в других проектах, реализация которых основана на Content Management Framework (CMF) под СЭД. На базе CMF у нас есть комплексное решение для автоматизации процессов документооборота КСЭД 3.0. Конечно, отдельные решения по автотестам можно применять под любую СЭД. Ещё расскажу про проблемы, и как мы их решали. Пост будет интересен и полезен, если в ваших автотестах необходимо подписывать документ электронной подписью (ЭП) в докер-образе браузера, проверять содержимое pdf файла, выполнять сравнение скриншотов или интегрироваться с одной из популярных Test Management System.Что имели на входеМного лет у заказчика была СЭД, которую мы же в КРОК делали на базе экосистемы Microsoft (.NET Framework, MS SQL Server, IIS, Active Directory). Но закон об импортозамещении ПО в госсекторе, ФЗ «О безопасности критической информационной инфраструктуры», плюс моральное устаревание существующей системы стали причиной решения о создании новой, уже на open source решениях.Основной функционал новой СЭД реализуется в едином приложении. Для масштабирования запускается несколько экземпляров приложения, которые находятся за балансировщиком нагрузки. Из приложения выделено несколько подсистем, развёртываемых в качестве отдельных сервисов, — это даёт более гибкое масштабирование.UI реализуется с использованием фреймворка Vue.JS. Бэкенд, как писал ранее, разрабатывается на собственном Content Management Framework (CMF) с реализованными процессами под СЭД. Основа для него — JXFW (CROC Java Extendable FrameWork). Зарегистрирован в Едином реестре российских программ для ЭВМ и баз данных (от 29 Марта 2018, регистрационный номер ПО: 4309). Про JXFW мой коллега писал отдельно вот здесь. Данные хранятся в БД PostgreSQL. Автоматизация бизнес-процессов выполнена при помощи Camunda. В качестве брокера сообщений используется RabbitMQ. Для наглядности, укрупненная физическая архитектура системы выглядит примерно так:
Почему решили делать автотестыПроект нужно было сдать в короткие сроки, с очень частыми релизами. Мне было очевидно, что автоматизация тестирования должна нам помочь. Оставалось убедить в этом руководство.     Я сел и посчитал, сколько времени уйдёт на ручное тестирование базового функционала при каждом релизе, и сколько времени нужно для написания и поддержки автотестов. В итоге получилось, что в среднем на двадцатый прогон тесты окупятся и далее будут приносить экономическую выгоду. Учитывая, что в неделю мы выпускаем несколько релизов, то приблизительно уже через 1,5-2 месяца мы должны получить профит. Изначально таблица расчётов выглядела так:Время написания автотестовРучное выполнение
набора тестов (за 1 раз)Частота выполненияПоддержка тестов(в неделю)          70 ЧЕЛ-Ч3,5-4 ЧЕЛ-Ч3 раза в неделю3 ЧЕЛ-ЧНа практике получилось даже лучше — тесты сейчас запускаются каждый день. Но немного увеличилось общее время написание тестов — на 15 человеко-часов. Остальные показатели из таблицы не изменились.В общем, на применение автоматизированных e2e тестов получили добро быстро. А я в глубине души понимал, что мы спасаем себя от постоянной рутинной работы и сокращаем время на регрессионное тестирование.      Чем будем работать: выбор технологий и инструментовКаркас автотестов нужен был надёжный и универсальный, потому заморочились подбором инструментов.С языком программирования никаких проблем не возникло — бэкенд был написан на Java, с которым в команде инженеров-тестировщиков почти все были знакомы. Дополнительно изучать не потребовалось. Всё удачно совпало, не пришлось разводить зоопарк языков в проекте, что тоже очень хорошо.Чистый Selenium мне использовать не хотелось, так как существует прекрасный инструмент Selenide (про него много написано, например, здесь), который делает большое количество скрытой работы за нас и инкапсулирует в себе много сложной логики самого Selenium. Зачем изобретать велосипед, если ребята из “Codeborne” его уже сделали. Я раньше работал с этим фреймворком, и он мне понравился. Если коротко — это фреймворк для автоматизированного тестирования веб-приложений на основе Selenium WebDriver. Его основные преимущества:
  • Изящный API,
  • Поддержка Ajax для стабильных тестов,
  • Мощные селекторы,
  • Простая конфигурация.
С выбором “сборщика” тестов тоже проблем не возникло — взяли Apache Maven. Он используется во всём проекте, привносить что-то новое не хотелось. Изначально задумывались над тем, чтобы взять Gradle, так как он более гибок. Но как показало время, мы не делали ничего такого, с чем бы не справился Maven. Конфигурация для тестов в pom.xml довольно простая.А вот определиться с выбором фреймворка для запуска тестов было немного сложнее. Изначально выбрали JUnit5, но потом передумали и взяли TestNG. С ним доводилось больше работать, и лично я считаю, что TestNG удобней, чем JUnit5. Но, как говорится, каждому своё. У нас есть тесты, которые должны сохранять порядок выполнения, потому что идут по единому процессу. Например, жизненный цикл документа. TestNG легко позволяет это делать установкой приоритета (параметр priority в аннотации @Test) в тестовом классе, а порядок тестовых классов задаётся в отдельном конфигурационном файле testng.xml.Есть тесты, которые подготавливают данные для других. TestNG позволяет это легко и просто реализовать с помощью параметра dependsOnMethods. Я знаю, что использование последовательных тестов является антипаттерном. Но небольшая часть из них так организована. Нам необходимо иметь понятную структуру, каждой задаче по документу – отдельный тест. А также быть уверенным в целостности и работоспособности бизнес-процесса.Мне нравится подход и общая организация управления выполнения тестов в TestNG. О ней подробней расскажу ниже. Дальше определились с фреймворком для построения отчетов: берем всем известный и популярный Allure. Ещё рассматривался Report Portal. Но всё же Allure более легковесный, проще в установке и поддержке. Report Portal подойдёт для единого решения по хранению и отображению результатов множества проектов на уровне компании. Такой задачи перед нами не стояло.Среду для запуска автотестов хотелось иметь единую, легко масштабируемую для параллельного запуска. Опять же, рассматривались два варианта: Selenoid и Selenium Grid. Я раньше использовал Grid, и он, на мой взгляд, имеет ряд недостатков:
  • сложность в настройке,
  • нестабильность и невысокая скорость работы,
  • сложность в добавлении и поддержке новых версий браузеров.
А вот с Selenoid нужно было разобраться, понять, что он из себя представляет и подходит ли нам. Выяснилось, что это хорошая альтернатива. В двух словах — это сервер, который позволяет запускать браузеры в docker-контейнерах. Легко настраивается и устанавливается в две команды, имеет замечательный UI-интерфейс, где можно отслеживать сессии в браузерах, даёт возможность записывать видео. Также, что очень важно, просто настраивать и конфигурировать браузеры в контейнерах. Итого, имеем следующий основной технический стек:Язык программированияJava SE8Фреймворк для написания тестовSelenideФреймворк для сборки тестовApache MavenФреймворк для запуска тестовTestNGОтчетностьAllureСреда для выполнения тестовSelenoidВсё по полочкам: структура и организация тестовДумаю, никого не удивлю, если скажу, что мы используем паттерн Page Object, позволяющий разделять логику выполнения тестов от их реализации. Все представляют структуру стандартного Java проекта. У нас для тестов она выглядит примерно так:
Всё, что является тестами, располагается в папке test. Весь же код по страницам, хелперы, ресурсы, глобальные переменные — хранятся в папке main. В папке libs — драйвера под несколько браузеров (Chrome, Firefox, Opera) на случай, если необходимо запустить тесты локально. В папке screenshot_tests — эталонные скриншоты для печатных форм документа. Там мы проверяем корректность формирования штампа ЭП — размеры, месторасположение на листе и т.д. В папке src/main/java/ru/croc/environment находится класс для хранения и управления общими переменными. Сами переменные хранятся в файле configuration.properties (папка resources). Вот фрагмент из класса Environment, где отображён метод по загрузке конфигурационного файла и получению переменной адреса приложения:
public class Environment {
    static FileInputStream fis;
    static Properties property = new Properties();
    /**
     * Загрузка пропертей
     */
    private static Properties loadProperties() {
        try {
            fis = new FileInputStream("src/main/resources/configuration.properties");
            property.load(fis);
        } catch (IOException e) {
            System.err.println("ОШИБКА: Файл свойств отсутствует!");
        }
        return property;
    }
    /**
     * Адрес СЭД
     */
    public static final String URL_SEDD = getUlrSedd("SEDD");
    private static String getUlrSedd(String param) {
        return loadProperties().getProperty(param);
    }
}
Далее уже в нужном месте кода вызываем переменную Environment.URL_SEDD.В папке src/main/java/ru/croc/listeners — класс TestListener, имплементирующий слушатель ITestListener и реализующий логику работы с TestRail. В нём отслеживаются успешно выполненные и упавшие тесты в методах onStart, onTestSuccess, onTestFailure. Об интеграции с Test Management System (далее TMS) подробно скажу далее. В папке src/main/java/ru/croc/pages — классы, описывающие страницы приложения. Основные страницы находятся в корне папки. Также существуют виджеты — это общие элементы на страницах (модальные окна, панели). Их мы храним в папке src/main/java/ru/croc/pages/widgets.В папке src/main/java/ru/croc/utils хранятся классы, отвечающие за:
  • Кастомные драйвера (драйвера под Selenoid),
  • Работу с БД,
  • Интеграцию с TestRail,
  • Скриншот тестирование,
  • API взаимодействие с системой,
  • Работу с pdf файлами,
  • Работу с пользователями,
  • Утилитарные методы — работа с датами, скачивание/загрузка файлов, работа с вкладками браузера и т.д.
Все вспомогательные ресурсы — скрипты, тестовые вложения, файлы для хранения пользователей, переменных — находятся в папке  src/main/resources.Вся общая логика по тестам вынесена в базовый класс BaseTest. Каждый тестовый класс наследуется от него. В BaseTest реализованы методы — setUp с аннотацией TestNG @BeforeSuite (выполняется перед всем тестовым набором, заданным в testng.xml) и tearDown с аннотацией @AfterMethod (выполняется после каждого тестового метода). В методе setUp реализована инициализация подходящих драйверов, выполнение скриптов и API запросов, если это необходимо, настройки работы с Allure. Нужно добавить, что автотесты выполняются на тестовой среде с тестовыми данными, поэтому мы можем менять или добавлять недостающие данные скриптами и не переживать за созданные тестовые задачи в системе. В методе tearDown реализован перелогин в приложении. Сам файл testng.xml выглядит примерно так:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="Testing SEDD" >
    <listeners>
         <listener class-name="ru.croc.listeners.TestListener" />
    </listeners>
    <test name="Autotests for SEDD" parallel="none" preserve-order="true" group-by-instances="true">
        <classes>
            <class name="ru.croc.IncomingDocTest"/>
            <class name="ru.croc.OutgoingDocTest"/>
            <!-- здесь остальные тесты -->
         </classes>
    </test>
</suite
Здесь из важного:
  • Подключается слушатель TestListener, реализующий логику работы по интеграции с TestRail,
  • Настройки, которые дают возможность запускать тесты последовательно (parallel=none) и сохраняя порядок (preserve-order="true").
Последовательность тестов в самом классе реализуем указанием параметров в аннотации @Test. Пример:
@Test(description="Рассмотрение начальником управления", priority=1)
public void headReview(){}
//...............
@Test(description="Рассмотрение начальником отдела", priority=2)
public void chiefReview(){}
Что ещё умеем: дополнительный функционалИнтеграция с TestRailПосле каждого прогона автотестов в TestRail нам хотелось иметь новый Test Run с результатами выполнения. К счастью, есть хорошо задокументированный API и реализованный клиент, позволяющий без особых проблем интегрироваться с TestRail. На его основе можно разрабатывать функционал под свои нужды.Мы создали класс TestRailUtil, в котором реализовали основные методы по работе с TMS. Пример:
/**
* Инициализация клиента для работы с API
*/
private static APIClient getClient() {
    APIClient client = new APIClient(MavenParametrs.getTestRailUrl());
    client.setUser(MavenParametrs.getTestRailUser());
    client.setPassword(MavenParametrs.getTestRailAPIKey());
    return client;
}
/**
* Добавление результата в TestRail
*/
public static String addResult4Case(String runId, String caseId, Object data) {
    try {
        JSONObject js = (JSONObject) getClient().sendPost("add_result_for_case/" + runId + "/" + caseId, data);
        return js.get("test_id").toString();
    } catch (IOException | APIException e) {
        e.printStackTrace();
        return null;
    }
}
/**
* Метод добавления результата при успешном выполнении тестов
*/
public static String testPassed(String runId, String caseId) {
    Map data = new HashMap();
    data.put("status_id", 1);
    data.put("comment", "This test worked fine!");
    return addResult4Case(runId, caseId, data);
}
/**
* Метод добавления результата при не успешном выполнении тестов
*/
public static String testFailed(String runId, String caseId) {
    Map data = new HashMap();
    data.put("status_id", 5);
    data.put("comment", "ERROR");
    return addResult4Case(runId, caseId, data);
}
/**
* Добавление нового тестрана
*/
public static String[] addTestRun(String suiteId, String projectId, String nameRun) {
    Map data = new HashMap();
    data.put("suite_id", new Integer(suiteId));
    data.put("name", nameRun + "_" + TestUtils.getDateTime());
    try {
        JSONObject js = (JSONObject) getClient().sendPost("add_run/" + projectId, data);
        return new String[] {
            js.get("id").toString(), js.get("url").toString()
        };
    } catch (IOException | APIException e) {
        e.printStackTrace();
        return null;
    }
}
/**
* Удаление тестранов
*/
public static void deleteTestRuns(List < String > list_id) {
    try {
        for (String item: list_id) {
            getClient().sendPost("delete_run/" + item, "");
        }
    } catch (IOException | APIException e) {
        e.printStackTrace();
    }
}
Думаю, здесь всё понятно по комментариям, подробно разбирать каждый метод не буду.Всю логику перенесли, как и писал ранее, в класс TestListener имплементирующий ITestListener. Используем всего 3 переопределенных метода — onStart, onTestSuccess, onTestFailure. Пример метода onTestSuccess:
/**
* Выполняется после каждого успешно выполненного теста
*/
@Override
public void onTestSuccess(ITestResult iTestResult) {
    testId = "";
    testMethods = iTestResult.getMethod().getMethodName();
    tm = FileReaderUtil.getTestMethod();
    for (TestMethodsName item: tm) {
        if (testMethods.equals(item.getMethodName())) {
            for (TestRun tr: testRuns) {
                if (tr.getTestRunName().contains(item.getTestRunName())) {
                    testId = TestRailUtil.testPassed(tr.getTestRunId(), item.getCaseId());
                    break;
                }
            }
        }
    }
}
Метод onTestFailure, который выполняется после проваленного теста, реализован аналогично, только вместо testPassed вызывается testFailed.Здесь надо пояснить, что есть ещё отдельный csv файл, в котором хранятся имена методов вместе с id тест-кейса в TestRail, то есть идет сопоставление методов в коде с тест-кейсами в TMS. Фрагмент этого файла выглядит так:
#Тест по входящему документу,DemoAutoTest
registrationIncoming,98162
headReview,98166
chiefReview,98168
execute,100003
Первая строка в файле нужна, чтобы обозначить набор кейсов в Test Run. Как раз в методе onStart мы создаем новый Test Run, например, с именем DemoAutoTest. Остальные же строки нам говорят, что тест-кейсу, например, с id 98162, соответствует метод в коде registrationIncoming. На строке:
tm = FileReaderUtil.getTestMethod();
мы получаем список объектов с именем метода, id тест-кейса и именем тестрана, к которому эти кейсы относятся. А далее выполняется проверка на соответствие текущего выполняемого метода с тест-кейсом в TMS. Также добавлена дополнительная проверка в цикле на идентификацию необходимого Test Run:
if(tr.getTestRunName().contains(item.getTestRunName()))
Если бы Test Run был один, то данную проверку выполнять было бы не нужно, а так непонятно, в каком из них  искать необходимый тест-кейс.После запуска тестов в TestRail создаётся новый Test Run с датой и временем:
А по запущенным тестам проставляются результаты:
Вот так, не очень сложно, можно реализовать интеграцию с TestRail для отслеживания результатов выполненных автотестов.Скриншот-тестирование печатных форм по документуВ системе документы подписываются ЭП, поэтому возникла задача выполнять проверку на корректность формирования штампа и шапки печатной формы после подписания. Сам штамп выглядит так:
Часто его содержимое заполнялось не корректными данными, также он мог менять свои размеры и местоположение. Поэтому приняли решение проверять его с помощью сравнения скриншотов. Для этих целей была выбрана библиотека Yandex aShot. Сравнение выполняется на основе ранее подготовленных эталонных скриншотов и текущих, которые формируются при каждом выполнении тестов.Для хранения наборов скриншотов, как писал ранее, необходимо было создать папку screenshot_tests. В ней: 
  • папка для хранения наложенных друг на друга снимков с отмеченными различиями между ними — screenshot_tests/diff_screens,
  • папка для хранения эталонных скриншотов — screenshot_tests/etalon_screens,
  • папка для хранения скриншотов, сделанных в процессе выполнения тестов — screenshot_tests/test_screens.
Скриншот можно создавать как с помощью библиотеки aShot, так и с помощью методов Selenide. Так как мы делаем снимок превью печатной формы, то необходимо было делать скриншот непосредственно самого веб-элемента, отвечающего за предварительный просмотр, а не всей страницы целиком. Мы воспользовались методом Selenide.Полностью метод снятия скриншота выглядит так:
/**
   * Метод создания скриншота элемента
   */
  public static BufferedImage takeScreenOfElement(String selector) throws IOException {
      File screen = $(selector).screenshot();
      return ImageIO.read(screen);
  }
Для сохранения используем метод:
/**
* Метод сохранения скриншота
*/
public static void saveScreenShot(BufferedImage image, String path) throws IOException {
    File file = new File(path);
    file.getParentFile().mkdirs();
    ImageIO.write(image, "png", new File(path));
}
И основной метод по сравнению снимков:
/**
     * Метод сравнения 2-х скриншотов
     */
    public static void checkImageDiff(Screenshot etalon_scr, Screenshot test_scr, String typeScreen) throws IOException {
        ImageDiff diff = new ImageDiffer().makeDiff(etalon_scr, test_scr).withDiffSizeTrigger(0);
        if (diff.hasDiff())
            saveScreenShot(diff.getMarkedImage(), pathToDiffScreen + "diff_image_" + typeScreen + ".png");
        Assert.assertFalse(diff.hasDiff(), "Screenshot has difference");
    }
Далее в тесте вызываем методы по получению, сохранению скриншотов и потом выполняем их сравнение:
BufferedImage test_scr_bottom= ScreenShotUtils.takeScreenOfElement(contentSelector);
ScreenShotUtils.saveTestScreen(test_scr_bottom,"test_image_bottom.png");
Screenshot expected_bottom = new Screenshot(test_scr_bottom);
Screenshot actual_bottom = ScreenShotUtils.getEtalonScreen("etalon_image_bottom.png");
ScreenShotUtils.checkImageDiff(actual_bottom,expected_bottom,"bottom");
Если скриншоты совпадают, то тест выполняется успешно и папка screenshot_tests/diff_screens остается пустая, иначе срабатывает проверка:
if (diff.hasDiff())
            saveScreenShot(diff.getMarkedImage(), pathToDiffScreen + "diff_image_" + typeScreen + ".png");
        Assert.assertFalse(diff.hasDiff(), "Screenshot has difference");
и тест падает. Создаётся изображение diff_image_bottom.png с разницей, которая подсвечивается:
По нему сразу становится понятно, в чем проблема.Проверка содержимого файлаПри прохождении исходящего документа по процессу, где выполняется согласование и подписание, на определённом этапе формируется “Лист согласования” в формате *.pdf. Он содержит основные данные по документу (номер, краткое содержание, тип документа и тд), а также список всех согласующих. Выглядит он так:
Стояла задача проверить корректное заполнение данного листа. Глобально ее можно декомпозировать на блоки:
  • Скачать лист согласования,
  • Выполнить его синтаксический разбор,
  • Сделать проверку содержимого.
Загрузка листа согласования осуществляется нажатием на одну кнопку. Но есть небольшая проблема при получении этого файла из Selenoid: файл доступен во время запущенной текущей сессии браузера по специальной ссылке  <selenoid-host>:4444/download/<SESSION_ID>/<FILE_NAME>. При этом имя файла — рандомное, в виде id. Мы не могли знать его заранее.Поэтому сразу пришлось по ссылке <selenoid-host>:4444/download/<SESSION_ID>/ получать имя вложения, а потом уже формировать полную ссылку с именем и класть вложение на машину, откуда запускаются тесты. Метод этот выглядит так:
/**
      * Получить Pdf файл от Selenoid
      */
     public static File getPdfFile(SessionId id) throws IOException {
         if (MavenParametrs.getName().contains("_selenoid")) {
             String pathName = DOWNLOAD_FILE_PATH + DOWNLOAD_FILE_NAME;
             File file = new File(pathName);
             file.getParentFile().mkdirs();
             String download_url = MavenParametrs.getRemoteURL().split("wd")[0] + "download/";
             InputStream inputStream1 = new URL(download_url + id).openStream();
             String text = IOUtils.toString(inputStream1, StandardCharsets.UTF_8.name());
             String fileName = StringUtils.substringBetween(text, """, """);
             InputStream inputStream2 = new URL(download_url + id + "/" + fileName).openStream();
             Files.copy(inputStream2, Paths.get(pathName), StandardCopyOption.REPLACE_EXISTING);
         }
         File dir = new File(DOWNLOAD_FILE_PATH);
         File[] files = dir.listFiles((d, name) - > name.endsWith(".pdf"));
         return files[0];
     }
Для преобразования pdf  в текст и выполнения его разбора использовалась библиотека Apache PDFBox. Метод по преобразованию можно увидеть ниже:
public static String PDFReader(String fileName){
        String resultStr="";
        try (PDDocument document = PDDocument.load(new File(fileName))) {
            document.getClass();
            if (!document.isEncrypted()) {
                PDFTextStripperByArea stripper = new PDFTextStripperByArea();
                stripper.setSortByPosition(true);
                PDFTextStripper tStripper = new PDFTextStripper();
                String pdfFileInText = tStripper.getText(document);
                String lines[] = pdfFileInText.split("\\r?\\n");
                for (String line : lines) {
                  resultStr+=line+System.lineSeparator();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return resultStr;
}
После того, как Лист согласования преобразован в текст, можно выполнять необходимые проверки в тесте:
//Исполнитель-Визирующий
Assert.assertTrue(pdf.contains(executorApprover));
//Визирующий
Assert.assertTrue(pdf.contains(approver));
Работа с пользователямиТак как система предполагает работу по задачам большим количеством пользователей, то необходимо было организовать их правильное хранение для быстрого, гибкого и легкого доступа. Не хотелось иметь проблем при добавлении нового или удалении старого пользователя. Поэтому возникла идея хранить логин, пароль, ФИО и роль пользователя в csv файле. Также создать отдельный класс User, который будет иметь все необходимые поля по сущности и методы быстрого поиска пользователя по ФИО или роли.Пример файла:
test\ivanov,1,Иванов Иван Иванович,Регистратор
test\petrov,1,Петров Александр Сергеевич,ПодписантАдресат
test\sidorov,1,Сидоров Иван Петрович,Подписант
test\ponomareva,1,Пономарева Светлана Викторовна,Адресат
test\guseva,1,Гусева Елена Вячеславовна,Корректор
test\pavlova,1,Павлова Екатерина Ивановна,ИсполнительВизирующий
Весь код класса User приводить не буду. А вот метод для поиска пользователя по должности, который используется в тестах, выглядит так:
public static User findByRole(String Role)
    {
        List<User> users = FileReaderUtil.getUsers();
        User findUser = null;
        for (User user:users){
            if(user.getRoleName().equals(Role)) {
                findUser = user;
                break;
            }
        }
        return findUser;
    }
Здесь выполняется формирование списка пользователей из файла, а потом поиск по заданной роли в системе. В тесте необходимый пользователь ищется по роли так:
User Executor = User.findByRole("Исполнитель");
Чтобы появился новый пользователь, необходимо его добавить в csv файл и тест. Для удаления — аналогично в обратном порядке. Больше ничего менять не нужно.Запуск автотестов на CI/CDВ качестве сервера непрерывной интеграции у нас используется TeamCity.
Код проекта и код автотестов хранится в Gitlab. Для разработки используются следующие ветки:
  • master — основная ветка.
  • release — релизная ветка. Здесь хранится код по текущему релизу приложения. В момент релиза вливается в master.
  • feature — ветка с новым функционалом. Здесь хранится код по каждой отдельной функциональности. Потом вливается в release, далее в master. Может вливаться в мастер напрямую — “дальний релиз”.
Каждая новая версия приложения помечается тегом.Тесты живут в том же репозитории, что и код приложения. Они также версионируются. Есть версия под релизную ветку, есть под ветку master.На TeamCity есть два джоба для каждой версии:
Они запускаются автоматически каждую ночь, при условии, что было успешное обновление тестовых стендов из необходимой ветки. Запускаются на удаленном сервере в Selenoid — за ними даже можно отдельно наблюдать через Selenoid UI или записать видео:
Для того, чтобы тесты запускались в определенной версии браузера, нужно создать кастомный драйвер с корректными параметрами подключения к Selenoid. У нас для собственного образа хрома он выглядит так:
public class CustomChromeDriverSelenoid implements WebDriverProvider {
    @Override
    public WebDriver createDriver(DesiredCapabilities capabilities) {
        RemoteWebDriver driver = null;
        InitSelenoidDriver selenoidDriver = new InitSelenoidDriver(BrowserType.CHROME,
            true, false, MavenParametrs.getVersionSelenoidBrowser());
        ChromeOptions options = new ChromeOptions();
        options.addExtensions(new File("libs/cades_plugin.crx"));
        options.setCapability("browserName", selenoidDriver.getBrowserName());
        options.setCapability("enableVNC", selenoidDriver.getEnableVNC());
        options.setCapability("enableVideo", selenoidDriver.getEnableVideo());
        options.setCapability("version", selenoidDriver.getBrowserVersion());
        try {
            driver = new RemoteWebDriver(
                URI.create(MavenParametrs.getRemoteURL()).toURL(),
                options
            );
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        return driver;
    }
}
Так как нужно было подписывать документы с помощью ЭП, пришлось создавать собственный докер-образ, чтобы там был КриптоПро, тестовые сертификаты и Cades plugin. Но про это чуть дальше.Описывать настройку джобов по запуску тестов не буду. Но тут важно сказать, что для формирования отчетов прямо в TeamCity мы используем отдельный плагин allure-teamcity. Он выполняет генерацию Allure отчета с сохранением истории выполнении тестов, что очень важно. Есть возможность отчет вынести на отдельную вкладку:
А можно смотреть прямо из артефактов, которые генерирует allure-teamcity plugin:
Также можно стандартными средствами TeamCity посмотреть на скоуп всех тестов. Какие выполнялись успешно, а какие провалились:
Если тест упал, выводится подробный стектрейс.Создание докер-образа Chrome для подписания документов ЭП в SelenoidТак как документы подписываются ЭП, для того, чтобы всё корректно работало,  необходимо установить дополнительное ПО. А именно:
  • CryptoPro CSP 5.0
  • Cades plugin
  • Тестовые сертификаты
Это всё без особых проблем устанавливается локально, а вот для Selenoid пришлось создавать собственный image с предустановленным и настроенным ПО.
Чтобы сделать image, для удобства нужно создать Dockerfile, на основе которого он и будет собираться. Пример:
# Готовый образ Chrome 81 версии
FROM selenoid/vnc:chrome_81.0
# Установка пользователя root
USER root
# Юзер под которым запускается контейнер
ARG USER_NAME=selenium
ADD dist/ /tmp/dist/
ADD cert/ /tmp/cert/
# Распаковать КриптоПро CSP 5
RUN tar -zxvf /tmp/dist/linux-amd64_deb.tgz -C /tmp/dist/
# Установка КриптоПро CSP 5
RUN /tmp/dist/linux-amd64_deb/install.sh
RUN dpkg -i /tmp/dist/linux-amd64_deb/cprocsp-rdr-gui-gtk-64_*
# Распаковать cades plugin
RUN tar -zxvf /tmp/dist/cades_linux_amd64.tar.gz -C /tmp/dist/
# Установить cades plugin
RUN dpkg -i /tmp/dist/cades_linux_amd64/cprocsp-pki-cades-64_2.0.14071-1_amd64.deb
RUN dpkg -i /tmp/dist/cades_linux_amd64/cprocsp-pki-plugin-64_2.0.14071-1_amd64.deb
# Проверка лицензии
RUN /opt/cprocsp/sbin/amd64/cpconfig -license -view
# переключаемся на "обычного" юзера перед установкой сертификатов
USER $USER_NAME
# установка корневого сертификата
RUN echo o | /opt/cprocsp/bin/amd64/certmgr -inst -file /tmp/cert/cert_test.cer -store uRoot
# установка личного сертификата. -pin <password> это пароль от закрытого ключа
RUN /opt/cprocsp/bin/amd64/certmgr -inst -pfx -file /tmp/cert/test.pfx -pin <password> -silent
# команда, для того чтобы убрать всплывающее окно - "лицензия истекает меньше чем через 2 месяца".
RUN /opt/cprocsp/sbin/amd64/cpconfig -ini '\local\KeyDevices' -add long LicErrorLevel 4
# переключаемся на суперюзера
USER root
# убираем alert "переход на новый алгоритм в 2019 году"
RUN sed -i 's/\[Parameters\]/[Parameters]\nwarning_time_gen_2001=ll:131907744000000000\nwarning_time_sign_2001=ll:131907744000000000/g' /etc/opt/cprocsp/config64.ini
# Добавляем адрес тестируемого приложения в доверенные.
RUN /opt/cprocsp/sbin/amd64/cpconfig -ini "\config\cades\trustedsites" -add multistring "TrustedSites" "http://<ip_sedd>/sedd"
# переключаемся на "обычного" юзера
USER $USER_NAME
В Dockerfile используются дистрибутивы и сертификаты.Необходимо создать две папки: distr и cert, — которые будут лежать вместе с Dockerfile.Структура должна выглядеть так:
cert_test.cer — корневой тестовый сертификат. test.pfx — личный тестовый сертификат. linux-amd64_deb.tgz — дистрибутив CryptoPro CSPcades_linux_amd64.tar.gz — дистрибутив Cades pluginДля сборки образа надо зайти в Root folder и выполнить команду:
docker build -t chrome_csp_81:vnc_chrome_csp .
После успешной сборки будет сообщение:
Successfully built d9d26ccae897
Successfully tagged chrome_csp_81:vnc_chrome_csp
Далее, необходимо созданный образ добавить в Selenoid. Для этого правим файл .aerokube/selenoid/browsers.json:
"chrome": {
    "versions": {
        "81.0": {
            "image": "chrome_csp_81:vnc_chrome_csp",
            "port": "4444",
            "path": "/"
        }
    }
}
То есть устанавливаем созданный image в качестве одного из браузеров. Выполняем перезапуск Selenoid, чтобы новый образ подтянулся на сервер. Если всё успешно, то добавленный образ отобразиться в Selenoid UI:
Итоги, и что будет дальше      Работая над проектом, я открыл для себя много новых технологий, о которых только слышал, но не работал с ними раньше. Сталкивался с вещами, которых не знал, но изучил. Собрал немало грабель по архитектуре или промахов в выборе технологий. Но прежде всего, я вырос как специалист.      Сейчас автотесты дают нам уверенность, что ничего из важного функционала не сломалось, а если и сломалось, то есть время исправить ошибки до релиза. Также мы экономим очень много времени на ручных проверках во время регресса. В планах — увеличение количества функциональных тестов и расширение покрытия. Правда, придется всё же ограничиться неким не очень большим объемом, так как у нас просто нет ресурсов для поддержки тысячи UI-автотестов. Стараемся расставлять приоритеты и выбирать самый важный функционал.Радует ещё то, что на поддержку уходит совсем немного времени. Сами тесты довольно стабильны, и ложноотрицательные результаты случаются редко. Отчасти это заслуга Selenide, отчасти — команды тест-инженеров, которые поддерживают тесты. Кстати, QA-команда состоит из двух QA-инженеров и одного QA automation.Важное достижение, на мой взгляд, — в том, что текущее решение очень хорошо себя зарекомендовало. Его можно тиражировать на другие проекты, в первую очередь, под СЭД. Причём не только под системы, которые разрабатываются на основе нашего CMF, но и на базе других вендорских продуктов. И сейчас мы активно работаем над этим. Делаем некоторый функционал более гибкими и универсальными.Оглядываясь назад, могу сказать, что уже по ходу работы я находил решения, которые мне казались лучше. Но в данном случае не видел целесообразности переделывать уже существующую реализацию, или просто не было времени внедрять что-то новое, если неплохо работало старое. Например, для хранения пропертей существует прекрасная библиотека Owner. На мой взгляд, она позволяет очень изящно и лаконично хранить параметры. Также есть проект Lombok, который добавляет дополнительную функциональность в Java c помощью изменения исходного кода перед компиляцией. Хорошо про него написано здесь. У нас в автотестах есть не маленькое количество POJO объектов с большим количеством кода. Lombok бы сильно уменьшил и упростил реализацию таких классов.А какие библиотеки или решения вы могли бы посоветовать для улучшения автотестов при тираже и наших вводных? Пишите в комментариях. Буду рад услышать примеры из вашего опыта и готов ответить на вопросы.
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_administrirovanie_baz_dannyh (Администрирование баз данных), #_raspredelennye_sistemy (Распределённые системы), #_mikroservisy (Микросервисы), #_sed (СЭД), #_avtotesty (автотесты), #_e2e, #_qa, #_mikroservisy (микросервисы), #_kod (код), #_java, #_selenium, #_testrail, #_blog_kompanii_krok (
Блог компании КРОК
)
, #_administrirovanie_baz_dannyh (
Администрирование баз данных
)
, #_raspredelennye_sistemy (
Распределённые системы
)
, #_mikroservisy (
Микросервисы
)
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 05-Окт 16:28
Часовой пояс: UTC + 5