- PVSM.RU - https://www.pvsm.ru -
Фундаментальный строительный блок автоматизации – тестирование
Род Джонсон
Я не амбассадор автоматизации тестирования веб интерфейсов, однако сей очерк скорее будет полезен камрадам, уже имеющим опыт в этой сфере.
Для совсем новичков также будет полезно, т.к. я предоставляю исходный код, где можно посмотреть, как в конечном продукте организовано взаимодействие с селениумом.
Я расскажу о том, как с нуля, имея небольшой опыт разработки, написал платформу для запуска тестов, и о самой платформе. Сам я считаю, что мой продукт получился весьма эффективным, а значит будет многим полезен и имеет место для рассмотрения.
Процесс тестирования зависит от информационной системы.
Для поминания моей концепции, необходимо понять на какие системы я ориентируюсь в первую очередь – это те системы, где обычно существуют конкретные линейные бизнес-процессы, которые и ставятся ключом при проведении регрессионных тестов.
Итак, система типа srm. Ключевая бизнес-сущность – коммерческие предложения поставщика. Ключевое внимание при проведении регрессионного тестирования – целостность бизнес-процесса.
Бизнес-процесс начинается от регистрации поставщика в системе, далее идет создание коммерческого предложения – идет на этап рассмотрения, который производится различными типами внутренних пользователей (и для каждого пользователя уникальный интерфейс) до возврата решения о рассмотрения предложения поставщику.
Получается, что мы проходим через ряд разных интерфейсов, и практически всегда работаем с разными. На деле – если просто взглянуть на это прямо – это похоже на просмотр видеокассеты – т.е. это именно процесс, имеющий начало и конец, и он абсолютно линейный – никаких ветвлений, при написании теста мы всегда знаем ожидаемый результат. Т.е. я хочу сказать, что уже глядя на это картину, можно заключить, что сделать тесты полиморфными вряд ли получится. Ввиду этого при создании платформы для запуска тестов, ключевым фактором я поставил скорость написания тестов.
Концепции, которые я перед собой поставил:
От чего я решил отказаться уже на старте:
Немного еще расскажу об уровне разработки в компании и условиях создания инструмента дабы до конца внести в ясность в некоторые детали.
В силу неких конфиденциальных обстоятельств я не раскрываю компанию, где работаю.
В нашей компании разработка существует уже далеко не первый год, и стало быть все процессы уже давно налажены. Однако, они сильно отстают от современных тенденций.
Все представители айти понимают, что надо покрывать код тестами, писать сценарии автотестов в момент согласования требований к будущей функциональности, гибкие технологии существенно экономят время и ресурсы, а CI который просто берет и упрощает жизнь. Но это все пока только медленно доходит до нас…
Так и служба контроля качества ПО – все тесты выполняются мануально, если взглянуть на процесс «сверху» – то это «горлышко бутылки» всего процесса разработки.
Платформа написана на языке Java с использогванием JDK 12
Основные инфраструктурные инструменты — Selenium Web Driver, OJDBC
Для работы приложения на ПК должен быть установлен браузер FireFox версии выше 52
С приложением в обязательном порядке идет 3 папки и 2 файла:
• папка BuildKit — содержит:
• папка Reports — в нее сохраняются отчеты после завершения выполнения приложением тест кейса. Также должна содержать папку ErrorScreens, куда сохраняется скриншот в случае возникновения ошибки
• папка TestSuite — веб пакеты, яваскрипты, набор тест кейсов (о заполнении данной папке будет подробно рассказано отдельно)
• файл config.properties — содержит конфиг для подключения к БД Oracle и значения явных ожиданий для WebDriverWait
• starter.bat — файлик для запуска приложения (возможен автоматический запуск приложения без ручного указания TestCase если в конце ввести в качестве параметра ввести имя TestCase).
Приложение может быть запущено с параметром (имя TestCase) либо без него — в этом случае необходимо ввести имя тест кейса в консоли самостоятельно.
Пример общего содержания bat файла, для запуска без параметра: start «AutoTest launcher» %cd%BuildKitjdk-12binjava.exe -Xmx768M -jar --enable-preview %cd%BuildKitSprintAutoTest.jar
При общем запуске приложения, оно просматривает xml файлы, находящиеся в директории "TestSuiteTestCase" (без просмотра содержания вложенных папок). При этом происходит первичная валидация xml файлов на корректность структуры (т.е. что все теги с точки зрения разметки xml указаны верно), и берутся названия, указанные в теге «testCaseName», после чего пользователю предлагается ввести один из возможных вариантов имен имеющихся тест кейсов. В случае ошибочного ввода система попросит ввести имя повторно.
После того как будет получено имя TestCase, выполняется построение внутренней модели, которая представляет из себя связку TestCase (тестовый сценарий) — WebPackage (хранилище элементов) в виде java объектов. После построения модели строится непосредственно TestCase (исполняемый объект программы). На этапе построения TestCase также происходит вторичная валидация — проверяется что все указанные формы в TestCase имеются в связанном WebPackage и что все элементы, указанные в action, имеются в WebPackage в рамках указанных страниц. (О структуре TestCase и WebPackage подробно описано ниже)
После того, как будет построен TestCase, происходит непосредственно запуск сценария
TestCase представляет из себя набор сущностей Action, который в свою очередь представляет из себя набор сущностей Event.
TestCase
-> List{Action}
-> List{Event}
При запуске TestCase происходит последовательный запуск Action (каждый Action возвращает логический результат)
При запуске Action происходит последовательный запуск Event (каждый Event возвращает логический результат)
По результату выполнения каждого Event сохраняется результат
Соответственно тест завершается либо, когда все действия были выполнены успешно, либо если Action вернул false.
*Механизм сбоя
Т.к. моя тестируемая система древняя и имеет отлавливаемые ошибки/глюги, которые не являются ошибками, и некоторые события не срабатывают с первого раза, в платформе реализован механизм, который может отходить от вышеописанной концепции жестко линейного теста (однако он жестко типизирован). При отлавливании таких ошибок предусмотрена возможность повторения кейсов сначала и выполнения дополнительных действий для возможности повтора действий
По окончанию работы приложения происходит формирование отчета, который сохраняется в директорию "Reports". В случае возникновение ошибки делается скриншот, который сохраняется в "ReportsErrorScreens"
Итак, описание теста. Как уже говорилось, основной параметр, необходимый для запуска – имя теста, который следует запустить. Это имя хранится в xml файле в директории “/TestSuite/TestCase”. В данной директории хранятся все тестовые сценарии. Их здесь может быть сколько угодно. Имя тест кейса берется не из имени файла, а из тега “testCaseName” внутри файла.
В TestCase задается что именно будет делаться – т.е. действия. В директории “/TestSuite/WebPackage” в xml файлах хранятся все локаторы. Т.е. все в лучших традициях – действия хранятся отдельно, локаторы веб форм отдельно.
В TestCase также хранится имя WebPackage в теге “webPackageName”.
Итого картина уже есть. Для запуска необходимо наличие 2-ух xml файлов: TestCase и WebPackage. Они составляют связку. WebPackage независим – в качестве идентификатора указывается имя в теге “webPackageName”. Соответственно вот и первое правило – имена TestCase и WebPackage должны быть уникальными. Т.е. еще раз – по сути наш тест — это связка файлов TestCase и WepPackage, которые связаны именем WebPackage, которое указывается в TestCase. На практике я автоматизирую одну систему и все свои тест кейсы вяжу к одному WebPackage в котором у меня до кучи описание всех форм.
Следующий слой логической декомпозиции основан на таком паттерне, как Page Object.
Разделение логики и реализации
Существует большая разница между логикой тестирования (что проверить) и его реализацией (как проверить). Пример тестового сценария: «Пользователь вводит неверный логин или пароль, нажимает кнопку входа, получает сообщение об ошибке». Этот сценарий описывает логику теста, в то время как реализация содержит в себе такие действия как поиск полей ввода на странице, их заполнение, проверку полученной ошибки и т.д. И если, например, измениться способ вывода сообщения об ошибке, то это никак не повлияет на сценарий теста, все также нужно будет ввести неверные данные, нажать кнопку входа и проверить ошибку. Но это напрямую затронет реализацию теста — необходимо будет изменить метод получающий и обрабатывающий сообщение об ошибке. При разделении логики теста от его реализации автотесты становятся более гибкими и их, как правило, легче поддерживать.
*! Нельзя говорить о том, что данный архитектурный подход применен полностью. Речь идет лишь о декомпозиции описания тестового сценария постранично, что помогает писать тесты быстрее и добавлять дополнительные автопроверки на всех страницах, стимулирует к правильному описанию локаторов (чтобы они не были одинаковыми на разных страницах) и строит «красивую» логическую структуру теста. Сама платформа реализована на принципах «Чистой архитектуры»
Далее я постараюсь подробно не расписывать структуру WebPackage и TestCase. Для них я создал DTD схему для WebPackage и XSD 1.1 для TestCase.
За счет ведения DTD и XSD схем осуществляется концепция быстрого написания теста.
При написании непосредственно WebPackage и TestCase необходимо использовать xml Editor со встроенными функциями валидации DTD и XSD в реальном времени с автогенерацией тегов, что сделает сам процесс написания авто теста в значительной мере автоматизированным (все обязательные теги будут подставлены автоматически, для значений атрибутов будут выданы выпадающие списки возможных значений, под тип ивента будут сгенерированы соответствующие теги).
Когда эти схемы «прикручиваются» к самому xml файлу, то можно забыть про корректность структуры xml файла, если использовать специальную среду. Мой выбор пал на oXygen XLM Editor. Еще раз – без использования подобной проги, вы не поймете суть скорости написания. Idea для этого не очень подходит т.к. в ней не обрабатывается конструкция XSD 1.1 “alternative”, которое имеет ключевое значение для TestCase.
WebPackaege — xml файл, описывающий элементы веб форм, находится в директории "TestSuiteWebPackage". (может быть сколько угодно много файлов. Название файлов может быть любым — значение имеет только содержание).
<!DOCTYPE webPackage [
<!ELEMENT webPackage (webPackageName, forms)>
<!ELEMENT webPackageName (#PCDATA)>
<!ELEMENT forms (form+)>
<!ELEMENT form (formName, elements+)>
<!ELEMENT formName (#PCDATA)>
<!ELEMENT elements (element+)>
<!ELEMENT element (name, locator)>
<!ATTLIST element type (0|1|2|3|4|5|6|7) #REQUIRED>
<!ATTLIST element alwaysVisible (0|1) #IMPLIED>
<!ELEMENT name (#PCDATA)>
<!ELEMENT locator (#PCDATA)>
<!ATTLIST locator type (1|2) #IMPLIED>
]>
<webPackage>
<webPackageName>уникальное_название</webPackageName>
<forms>
<form>
<formName>пустая_форма_авторизации_при_открытии_тестового_приложения</formName>
<elements>
<element type="2" alwaysVisible="1">
<name>наименование_элемента</name>
<locator type="2">.//div/form/div/div/form/table/tbody/tr/td[text()="Логин"]/following-sibling::td/input</locator>
</element>
<element type="2">
<name>наименование_другого_элемента</name>
<locator>.//div/form/div/div/form/table/tbody/tr/td[text()="Логин"]/following-sibling::td/input</locator>
</element>
.......
</elements>
</form>
.......
</forms>
</webPackage>
Как уже говорилось, чтобы элементы были не в кучи – все декомпозировано по веб формам
Ключевой сущностью является
<element>
Тег element имеет 2 атрибута:
Атрибут type является обязательным и задает тип элемента. В платформе задается типом byte
На текущей момент конкретно для себя в платформе реализовал следующие типы:
• 0 — не имеет функционального смысла, обычно какая-то надпись
• 1 — кнопка (button)
• 2 — поле ввода (input)
• 3 — чекбокс (checkBox)
• 4 — выпадающий список (select) – на самом деле не реализован, но место под него оставил
• 5 — для выпадающего списка srm: пишем название, дожидаемся появления значения – выбираем по конкретному xpath шаблону – тип конкретно под мою систему
• 6 — srm select — используется на типовых функциях типа поиска и т.д. – тип конкретно под мою систему
Атрибут alwaysVisible — необязательный — показывает присутствует ли элемент на форме всегда, может использоваться при начальной/конечной валидации Action (т.е. в автоматическом режиме можно проверить, что при открытии формы на ней присутствуют все элементы, которые есть на ней всегда, при закрытии формы, все эти элементы исчезли)
Возможные значения:
Дополнительный необязательный атрибут type реализован у тега locator
Возможные значения:
TestCase — xml файл, описывающих непосредственно сценарий тестирования, находится в директории "TestSuiteTestCase" (может быть сколько угодно много файлов. Название файлов может быть любым — значение имеет только содержание).
<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="1.1">
<xs:element name="testCase">
<xs:complexType>
<xs:sequence>
<xs:element name="testCaseName" type="xs:string"/>
<xs:element name="webPackageName" type="xs:string"/>
<xs:element name="actions" type="actionsType"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="actionsType">
<xs:sequence>
<xs:element name="action" type="actionType" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="actionType">
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="orderNumber" type="xs:positiveInteger"/>
<xs:element name="runConfiguration" type="runConfigurationType"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="runConfigurationType">
<xs:sequence>
<xs:element name="formName" type="xs:string"/>
<xs:element name="repeatsOnError" type="xs:positiveInteger" minOccurs="0"/>
<xs:element name="events" type="eventsType"/>
<xs:element name="exceptionBlock" type="eventsType" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="openValidation" use="required">
<xs:simpleType>
<xs:restriction base="xs:byte">
<xs:enumeration value="1"/>
<xs:enumeration value="0"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="closeValidation" use="required">
<xs:simpleType>
<xs:restriction base="xs:byte">
<xs:enumeration value="1"/>
<xs:enumeration value="0"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
<xs:complexType name="eventBaseType">
<xs:sequence>
<xs:element name="orderNumber" type="xs:positiveInteger"/>
</xs:sequence>
<xs:attribute name="type" use="required">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="goToURL"/>
<xs:enumeration value="checkElementsVisibility"/>
<xs:enumeration value="checkElementsInVisibility"/>
<xs:enumeration value="fillingFields"/>
<xs:enumeration value="clickElement"/>
<xs:enumeration value="dbUpdate"/>
<xs:enumeration value="wait"/>
<xs:enumeration value="scrollDown"/>
<xs:enumeration value="userInput"/>
<xs:enumeration value="checkInputValues"/>
<xs:enumeration value="checkQueryResultWithUtilityValue"/>
<xs:enumeration value="checkFieldsPresenceByQueryResult"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="invertResult" use="optional" default="0">
<xs:simpleType>
<xs:restriction base="xs:byte">
<xs:enumeration value="1"/>
<xs:enumeration value="0"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="hasExceptionBlock" use="optional" default="0">
<xs:simpleType>
<xs:restriction base="xs:byte">
<xs:enumeration value="1"/>
<xs:enumeration value="0"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
<xs:complexType name="eventsType">
<xs:sequence>
<xs:element name="event" type="eventBaseType" maxOccurs="unbounded">
<xs:alternative test="@type='goToURL'" type="eventGoToURL"/>
<xs:alternative test="@type='checkElementsVisibility'" type="eventCheckElementsVisibility"/>
<xs:alternative test="@type='checkElementsInVisibility'" type="eventCheckElementsVisibility"/>
<xs:alternative test="@type='fillingFields'" type="eventFillingFields"/>
<xs:alternative test="@type='checkInputValues'" type="eventFillingFields"/>
<xs:alternative test="@type='clickElement'" type="eventClickElement"/>
<xs:alternative test="@TYPE='dbUpdate'" type="eventRequest"/>
<xs:alternative test="@type='wait'" type="utilityValueInteger"/>
<xs:alternative test="@type='scrollDown'" type="eventClickElement"/>
<xs:alternative test="@type='userInput'" type="eventClickElement"/>
<xs:alternative test="@type='checkQueryResultWithUtilityValue'" type="eventRequestWithValue"/>
<xs:alternative test="@type='checkFieldsPresenceByQueryResult'" type="eventRequestWithValue"/>
</xs:element>
</xs:sequence>
</xs:complexType>
<!-- ТИПЫ ДЛЯ EVENTS -->
<xs:complexType name="eventGoToURL">
<xs:complexContent>
<xs:extension base="eventBaseType">
<xs:sequence>
<xs:element name="url" type="xs:anyURI"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="eventCheckElementsVisibility">
<xs:complexContent>
<xs:extension base="eventBaseType">
<xs:sequence>
<xs:element name="fields" type="fieldType"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="eventFillingFields">
<xs:complexContent>
<xs:extension base="eventBaseType">
<xs:sequence>
<xs:element name="fields" type="fieldTypeWithValue"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="eventClickElement">
<xs:complexContent>
<xs:extension base="eventBaseType">
<xs:sequence>
<xs:element name="elementName" type="xs:string"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="eventRequest">
<xs:complexContent>
<xs:extension base="eventBaseType">
<xs:sequence>
<xs:element name="dbRequest" type="xs:string"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="utilityValueInteger">
<xs:complexContent>
<xs:extension base="eventBaseType">
<xs:sequence>
<xs:element name="utilityValue" type="xs:positiveInteger"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="eventRequestWithValue">
<xs:complexContent>
<xs:extension base="eventBaseType">
<xs:sequence>
<xs:element name="dbRequest" type="xs:string"/>
<xs:element name="utilityValue" type="xs:string"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<!-- ТИПЫ ДЛЯ EVENTS -->
<xs:complexType name="fieldType">
<xs:sequence>
<xs:element name="field" maxOccurs="unbounded">
<xs:complexType>
<xs:choice>
<xs:element name="element" type="xs:string"/>
<xs:element name="xpath" type="xs:string"/>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="fieldTypeWithValue">
<xs:sequence>
<xs:element name="field" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="element" type="xs:string"/>
<xs:element name="value" type="valueType"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="valueType">
<xs:complexContent>
<xs:extension base="xs:anyType">
<xs:attribute name="type" use="optional" default="1">
<xs:simpleType>
<xs:restriction base="xs:byte">
<xs:enumeration value="1"/>
<xs:enumeration value="2"/>
<xs:enumeration value="3"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>
<!DOCTYPE testCase SYSTEM "./TestSuite/TestCase/entities.dtd" []>
<testCase xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="testShema.xsd">
<testCaseName>уникальное_наименование_testCase</testCaseName>
<webPackageName>название_webPackage_из_webPackageName</webPackageName>
<actions>
<action>
<name>Вход через пустую форму авторизации в интерфейс КМа для инициализации приложения</name>
<orderNumber>10</orderNumber>
<runConfiguration openValidation="1" closeValidation="1">
<formName>пустая_форма_авторизации_при_открытии_тестового_приложения</formName>
<events>
<event type="goToURL">
<orderNumber>10</orderNumber>
<url>&srmURL;</url>
</event>
.......
</events>
</runConfiguration>
</action>
.......
</actions>
</testCase>
Вот в этой строчке видно как прикрутить xsd схему чтобы XML Editor ее видел:
<testCase xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="testShema.xsd">
В TestCase я также использую сущности DTD, которые хранятся отдельно в той же директории – файлик с расширением .dtd. В нем я храню практически все данные – константы. Также я построил логику таким образом, что чтобы запустить новый тест, и по ходу всего теста создавались новые уникальные сущности, регистрировался новый КА, достаточно поменять 1 цифру в этом файле.
<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY mainNumber '040'>
<!ENTITY mail '@mail.ru'>
<!ENTITY srmURL 'https://srm-test.ru'>
В значение тега такие константы вставляются так:
<url>&srmURL;</url>
— можно комбинировать.
! Рекомендация — при написании testCase следует указывать эти сущности DTD внутри документа, и уже после того, как все стабильно заработает, переносить в отдельный файлик. У моего xml editor-а с этим сложности – он не может найти DTD и не берет во внимание XSD, поэтому рекомендую так
testCase
testCase — самый родительский тег содержит:
action
Содержит:
runConfiguration
Содержит:
event
Минимальная структурная единичка — данная сущность показывает, какие действия совершаются
Каждый event особенный, может иметь уникальные теги и атрибуты.
Базовый тип содержит:
Кейс: ввод капчи. Данный момент я не смог автоматизировать, так сказать проверка на робота пока подкашивает – не пишут мне тестовый сервис капчи (а мне самому уже проще сделать нейросеть для распознавания))) Так вот, мы можем ошибиться при вводе. На этот случай я делаю контрольный ивент, в котором проверяю, что у нас отсутствует элемент – уведомление о неверном контрольном коде, ставлю на нем атрибут hasExceptionBlock. Предварительно я задал на action, что у нас может быть несколько повторений (5) и после всего прописал exceptionBlock, в котором прописал, что надо нажать кнопку выхода из уведомления, после чего action повторяется.
Примеры из моего контекста.
Вот я как я прописал ивент:
<event type="checkElementsInVisibility" hasExceptionBlock="1">
<orderNumber>57</orderNumber>
<fields>
<field>
<element>уведомление_неверный_контрольный_код</element>
</field>
</fields>
</event>
А вот exceptionBlock после ивентов
<exceptionBlock>
<event type="clickElement">
<orderNumber>10</orderNumber>
<elementName>кнопка_ок_для_выхода_из_уведомления</elementName>
</event>
</exceptionBlock>
И да, действия на одной странице можно декомпозировать на несколько action.
+ кто заметил в конфиге 2 параметра: defaultTimeOutsForWebDriverWait, lowTimeOutsForWebDriverWait. Так вот зачем они. Т.к. весь веб драйвер у меня в синглтоне, и я не захотел создавать каждый раз новый WebDriverWait, то у меня 1 быстрый, и он на случай ошибки (ну или если вы просто поставите hasExceptionBlock=«1», то он тупо будет с меньшим количеством времени явного ожидания) – ну согласитесь, ждать минуту чтобы убедиться, что сообщение не вылезло не комильфо, как и создавать каждый раз новый WebDriverWait. Ну и эта ситуация с какой стороны не ткнись требует костылька – я решил сделать так.
Здесь я приведу минимальный набор моих ивентов, типа набора бойскаута, с помощью которых я по своей системе могу протестить практически все.
И теперь плавненько к коду для понимания что такое ивент и как он строится. В коде по сути реализован фреймворк. У меня есть 2 класса – DataBaseWrapper и SeleniumWrapper. В этих классах описано взаимодействие с инфраструктурными компонентами, а также отражаются особенности платформы. Приведу интерфейс, который реализует SeleniumWrapper
package logic.selenium;
import models.ElementWithStringValue;
import models.webpackage.Element;
import org.openqa.selenium.WebElement;
public interface SeleniumService {
void initialization(boolean webDriverWait);
void nacigateTo(String url);
void refreshPage();
boolean checkElementNotPresent(Element element);
WebElement findSingleVisibleElement(Element element);
WebElement findSingleElementInDOM(Element element);
void enterSingleValuesToWebField(ElementWithStringValue element);
void click(Element element);
String getInputValue(Element element);
Object jsReturnsValue(String jsFunction);
//Actions actions
void doubleClick(Element element);
void moveMouseToElement(Element element);
void pressKey(CharSequence charSequence);
void getScreenShot(String storage);
}
В нем описываются все возможности селениума и накладываются фишки платформы – ну собственно основная фишка – это метод «enterSingleValuesToWebField». Помните, что мы в WebPackage указываем тип элемента. Так вот, то как на этот тип реагировать при заполнении полей прописано тут. Пишем 1 раз и забываем. Вышеуказанный метод стоит поправить под себя в первую очередь. К примеру типы 5 и 6, ныне действующие – подходят только для моей системы. А если у вас есть такая штука как фильтр и надо много фильтровать, и он типовой (в вашем веб приложении), но, чтобы им воспользоваться надо сначала навести мышку на поле, дождаться появления каких-то полей, перейти на какое-то, дождаться там чего-то, затем перейти туда и ввести… Тупо прописываете механизм действия 1 раз, даете уникальный тип этому всему в конструкции switch – далее не заморачиваетесь – получаете полиморфный метод на все аналогичные фильтры приложения.
Итак, в пакете «package logic.testcase.events» есть абстрактный класс, описывающий общие действия ивента. Для того чтобы создать свой уникальный ивент, необходимо создать новый класс, унаследоваться от этого абстрактного класса, и у вас уже в комплекте есть и dataBaseService и seleniumService – а далее вы определяете какие данные вам нужны и что с ними делать. Как-то так. Ну и соответственно, после создания нового ивента надо допилить класс-фабрику TestCaseActionFactory и по возможности XSD схему. Ну и, если добавляется новый атрибут – доработать саму модель. На деле это очень легко и быстро.
Итак, набор бойскаута.
goToURL – обычно первое действие – переход по указанной ссылке
<event type="goToURL">
<orderNumber>10</orderNumber>
<url>testURL</url>
</event>
fillingFields — Заполнение указанных элементов
Специальные теги:
<event type="fillingFields">
<orderNumber>10</orderNumber>
<fields>
<field>
<element>test</element>
<value>test</value>
</field>
</fields>
</event>
checkElementsVisibility – проверяется, что указанные элементы присутствуют на форме (именно видимые, а не просто в DOM). В атрибуте field может быть указан или элемент из WebPackage или непосредственно xpath
<event type="checkElementsVisibility">
<orderNumber>10</orderNumber>
<fields>
<field>
<element>test</element>
</field>
<field>
<xpath>test</xpath>
</field>
</fields>
</event>
checkElementsInVisibility – аналогичный checkElementsVisibility, но наоборот
clickElement – клик по указанному элементу
<event type="clickElement">
<orderNumber>10</orderNumber>
<elementName>test</elementName>
</event>
checkInputValues – проверка введенных значений
<event type="checkInputValues">
<orderNumber>10</orderNumber>
<fields>
<field>
<element>test</element>
<value>test</value>
</field>
</fields>
</event>
dbUpdate – выполнить апдейт в базе (oXygen странно реагирует на 1 ивент типа dbUpdate – не знаю что с ним сделать и не понимаю почему)
<event type="dbUpdate">
<orderNumber>10</orderNumber>
<dbRequest>update что-то там</dbRequest>
</event>
CheckQueryResultWithUtilityValue — проверка введенного пользователем значения с значением из БД
<event type="checkQueryResultWithUtilityValue">
<orderNumber>10</orderNumber>
<dbRequest>select ...</dbRequest>
<utilityValue>test</utilityValue>
</event>
checkFieldsPresenceByQueryResult — проверка наличия элементов на форме по xpath по паттерну. Если желаемы паттерн не указан, то поиск произойдет по паттерну .//*[text()[contains(normalize-space(.), "$")]], где вместо «$» будет значение из БД. При описании собственного паттерна, на то место, куда следует поставить значение из БД – надо указать «$». В моей системе есть так называемые гриды в которых бывают значения, которые как правило формируются из какой-то вьюхи. Этот ивент для проверки таких гридов
<event type="checkFieldsPresenceByQueryResult">
<orderNumber>10</orderNumber>
<dbRequest>test</dbRequest>
<utilityValue></utilityValue>
</event>
Wait – все просто – ожидание указанного количества милисекунд. К сожалению, хоть это и принято считать костылем, скажу точно – иногда без него невозможно обойтись
<event type="wait">
<orderNumber>10</orderNumber>
<utilityValue>1000</utilityValue>
</event>
scrollDown – скрол вниз от указанного элемента. Делается так: кликается по указанному элементу и типа нажимается клавиша «PgDn». В моих кейсах, где мне надо было скрольнуть вниз работает отлично:
<event type="scrollDown">
<orderNumber>10</orderNumber>
<elementName>test</elementName>
</event>
userInput – ввод значения в указанный элемент. Единственный полуавтомат в моей автоматизации, используется только для капчи. Указывается элемент, в который надо ввести значение. Значение вводится в всплывшее диалоговое окошко.
<event type="userInput">
<orderNumber>10</orderNumber>
<elementName>capch_input</elementName>
</event>
Значит, я старался сделать платформу в соответствии с принципами Чистой архитектуры дядюшки Боба.
Пакеты:
application – инициализация и запуск + конфиги и отчет (не ругайте меня за класс Report – вот что максимально на скорую руку, то максимально на скорую руку)
logic – ключевая логика + сервисы Селениума и БД. Тут же ивенты.
models – POJO на XML и все вспомогательные классы-объекты
utils – синглтоны для селениума и БД
Чтобы код запустить, надо скачать jdk 12 и везде указать, чтобы включились ее фишки. В Idea это делается через Project Structure –> Modules и Project. Также не забыть про Maven runner. И при запуске в бат файле дописать --enable-preview. Пример был.
Ну и, чтобы все еще запустилось, по мимо JDK потребуется скачать ojdbc драйвер и закинуть джарник в директорию “SprintAutoTestsrclib”. Я его не предоставляю, т.к. у оракла там щас все серьезно – чтобы скачать надо зарегаться, но я уверен, все желающие так или иначе справятся (ну и проверьте, чтобы все папки были созданы, а то отчет не сохранится)
Итак, мы имеем запускалку тестов, написание тестов на которую делается действительно быстро. За рабочую неделю мне удалось автоматизировать 1,5 часа ручной работы, которая выполняется роботом за 5 – 6 минут. Это примерно 3700 строчек конкатенированного тест кейса и 830 описанных элементов (более 4800 строчек). Цифры грубые, и так оно не меряется, но кто занимается автоматизацией должен понимать, что это весьма высокий показатель, тем более для недружелюбных к роботам систем. При этом я тестирую все – бизнес-логику, по ходу выполняю какие-то негативные тесты на корректность заполненных атрибутов, и как бонус проверяю полностью каждую веб-форму, по которой не лентяйничаю, и описываю все функциональные и ключевые элементы, в независимости нужны они мне или нет (Небольшое отступление – closeValidation я применяю в основном только при написании теста. Когда он стабилен, и понятно, что локаторы не пересекаются, я его отключаю для всех action, дабы процесс шел шустрее).
На первый взгляд кажется, что много строчек xml, однако на практике они генерируются полуавтоматически, при этом ошибку можно совершить только в непосредственно вводимых параметрах (т.к. по сути мы имеем 2 уровня валидации – 1-ый это xml схемы, 2-ой проверки наличия указанных форм и элементов при старте запуска TestCase).
Из минусов – нет четких границ тестов. Как таковые они отсутствуют и можно упрекнуть меня, что это просто запускалка макроса, а не тестов. Я оговариваю это так:
В платформе тесты концептуально с моей точки зрения делятся на несколько уровней абстракции:
Если сравнивать мой подход с чем-то, то минусы, например, популярного Cucumber и самой концепции BDD для меня более существенны (именно когда мы тестируем подобные моей системы):
Из того что я хотел бы сделать, над чем подумать, но еще не дошли руки:
Ну в общем-то и все. Прилагаю ссылочку на github: исходники [1].
Буду крайне рад конструктивной критике, надеюсь этот проект действительно будет полезен.
Автор: vvpetrov91
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/java/316754
Ссылки в тексте:
[1] исходники: https://github.com/PetrovVladimir/SprintAutoTest
[2] Источник: https://habr.com/ru/post/450726/?utm_campaign=450726
Нажмите здесь для печати.