Основы описания тестов
- Описание тестов
- Селекторы
- Выражения и кванторы со скалярными типами
- Кванторы и коллекции
- Проверка инвариантов
- Использование python кода в тестах
Для описания тестов “Meta Test” использует специализированный встроенный язык. По сути это декларативный, внутренний предметно-ориентированный язык предназначенный для описания автоматизированных QA тестов пользовательского интерфейса веб приложений.
Такой подход позволяет:
- Значительно сократить время описания тестов
- Повысить стабильность тестов
- Снизить порог вхождения в автоматизированное тестирование
- Получить эффективное многопоточное выполнение тестов (за счёт гарантированной атомарности на уровне теста)
Описание тестов
TEST["Заголовок"](Последовательность команд {, атрибут=значение }) [ ({, значение } {, параметр=значение }) ]
Этот объект используется для описания теста. Обязательно должен быть определён заголовок теста и последовательность команд которые будут выполнены при запуске теста. Свойства теста определяются значениями в глобальном объекте CONFOG. Для конкретного теста они могут быть переопределены при помощи следующих атрибутов:
- tags - Множество текстовых тегов на которых определён тест. Если это множество не пересекается с множеством CONFIG.TAGS, то тест не будет запущен. Если множество пустое или аргумент явно не определён, то тест будет выполнен только в том случае если множество тегов CONFIG.TAGS не определено. Если имя тега начинается с символа **@*, то это означает что тест на этом теге не определён. Например: если атрибут теги [“@Тег1”, “Тег2”], то тест не будет запущен если CONFIG.TAGS содержит тег “Тег1”. По умолчанию - {}*
- browser - Множество идентификаторов браузеров на которых определён тест. Если это множество не пересекается с множеством CONFIG.BROWSERS, то тест не будет запущен. Если множество пустое или аргумент явно не определён, то тест будет выполнен на всех браузерах из множества CONFIG.BROWSERS. Что бы запретить запуск теста с определённым браузером нужно добавить его идентификатор во множество browser с префиксом @* По умолчанию - {}*
- test_id - Идентификатор теста (см CONFIG.TEST_ID). Уникальная строка которая однозначно идентифицирует тест. Если аргумент не определён, то идентификатор генерируется автоматически.
- size - Размер окна браузера. Может быть определён как кортеж (длина, ширина) или как строковые константы. По умолчанию - CONFIG.PAGE_SIZE
| Константа | Описание |
|---|---|
| maximize | Развернуть окно во весь экран |
| fullscreen | Полноэкранный режим |
| minimize | Свернуть окно браузера |
- fatality - Флаг, если он равен True, то тест завершится после первой ошибки. По умолчанию - CONFIG.TEST_FATALITY
- retries - Устанавливает максимальное количество перезапусков теста. По умолчанию - CONFIG.RETRIES
- retries_sleep - Устанавливает задержку в сек. перед перезапуском теста. По умолчанию - CONFIG.RETRIES_SLEEP
- repiated - Устанавливает количество запусков теста (игнорируется если установлен retries). По умолчанию - CONFIG.TEST_REPIATED
- active - Флаг, если он установлен в False то тест не выполняется. По умолчанию - True
- single_thread - Флаг указывающий что все экземпляры теста (т.е. параметризованные тесты и тесты для разных браузеров) должны выполняться последовательно. По умолчанию - CONFIG.SINGLE_THREADS
- no_vacuum - Если этот флаг установлен в True то тест будет каждый раз запускатьбся в новом браузере даже если параметр CONFIG.VACUUM установлен в True. По умолчанию - False
- negative - Если этот флаг установлен в True то тест негативный. По умолчанию - False
- trigger - Идентификатор триггера для текущего теста.
- description - Текст с описанием теста.
- severity - Строковая константа устанавливающая степень важности тестируемого дефекта. Может принимать значения: TRIVIAL | MINOR | NORMAL | CRITICAL | BLOCKER. По умолчанию - NORMAL
- flaky - Флаг, если он установлен в True то тест считается не стабильным. Может использоваться для перезапуска только нестабильных тестов см. CONFIG.RETRIES_MODE. По умолчанию False
Кроме этих атрибутов в расширениях могут быть описаны пользовательский атрибуты тестов (см. Пользовательские атрибуты тестов )
Пример:
TEST["На хабр можно зайти"](
URL("https://habr.com/")
, "habr.com" > PAGE_URL
, "Проверка наличия блока логотипа"
, ONE("scope::habr/logo_locator")
, "Проверка наличия блока поиска"
, ONE("scope::habr/search_locator")
, "Проверка наличия блока рекламы"
, ONE("scope::habr/news_block_locator")
, description = "Мы можем зайти на habr.com, нам виден логотип и некоторые блоки"
, browser = ("chrome", "firefox")
, tags = ("base", "habr")
)Параметризованные тесты
Тесты можно запускать с различными параметрами. Для каждого набора параметров будет запущен отдельный экземпляр теста. После запуска теста текущие параметры записываются в пространство имён теста и могут быть получены командой ARG.
Параметры теста могут быть именованные и не именованные. Если параметр именованный, то его имя явно задаётся при описания теста, в противном случае значение параметра можно получить по его индексу.
Пример:
from faker import Faker
test = TEST["text-box"](
URL("https://demoqa.com/text-box")
, "Заполняем"
, DATA("#userName", ARG(0))
, DATA("#userEmail", ARG(1))
, DATA("#currentAddress", ARG("address1"))
, DATA("#permanentAddress", ARG("address2"))
, CLICK("#submit")
, "Проверяем"
, ONE("#name") == "Name:" + ARG(0)
, ONE("#email") == "Email:" + ARG(1)
, ONE("p#currentAddress") == "Current Address :" + ARG("address1")
, ONE("p#permanentAddress") == "Permananet Address :" + ARG("address2")
, tags = "form"
)
# выполняем тест "text-box" 100 раз.
# каждый раз генерируется новый набор тестовых данных
fake = Faker("ru_RU")
for _ in range(100):
test(
fake.name(), fake.email() # Не именованные параметры
, address1=fake.address() # Именованные параметры
, address2=fake.address()
)Если во время выполнения параметризованного теста могут меняться данные на сайте, то возможно будет целесообразным установить атрибут single_thread в True. Это позволит гарантировать последовательное выполнение тестов при многопоточном запуске что обеспечит их независимость от изменения данных другим экземпляром теста.
Вложенные тесты
Иногда необходимо не завершая текущий тест открыть новый браузер и выполнить ряд действий на тестируемом сайте. Для этих целей используются вложенные тесты. Вложенный тест определяется в последовательности команд основного теста как тест без указания заголовка.
Пример:
TEST["Основной тест"](
URL
, ...
TEST(
URL
, ...
)
, ...
)Вложенный тест имеет общее пространство имён с основным тестом и наследует все его атрибуты.
ARG("имя" [, значение ] [, default=значение ])
Перед запуском теста создаётся специальное пространство имён доступ к которому могут получить только команды выполняемые во время тестирования. Это пространство используется для обмена данными между скриптами, вложенными тестами и основным тестом, а так же для параметризации тестов и скриптов. Непосредственный доступ к пространству внутри теста можно получить посредством команды ARG следующим образом:
- ARG("имя", значение) - Устанавливает ассоциацию имени со значением.
- ARG("имя") - Возвращает значение ассоциированное с именем. Если данные ассоциированные с именем отсутствуют, то действие в котором использован аргумент будет пропущено. В режиме отладки пропущенные действия отображаются со статусом SCIP. Если необходимо пропустить не одно, а несколько действий, то можно воспользоваться условным оператором.
- ARG("имя", default=value) - Возвращает значение ассоциированное с именем. Если с именем не было ассоциировано никаких данных, то вернётся значение определённое в атрибуте default.
Пример:
# этот блок будет выполнен только если определён аргумент user
IF(ARG("user"), (
DATA("pht::Username", ARG("user"))
, CLICK("button")
))Обратите внимание на то что аргументы не являются переменными. Одна команда должна установить определить ассоциацию с данными лишь единожды.
SCRIPT["идентификатор"]( Последовательность команд {, атрибут=значение })
Этот объект позволяет задать идентификатор последовательности команд что позволяет переиспользовать её в тестах. Для скрипта может быть переопределён атрибут fatality.
Пример:
# Создаём скрипт
SCRIPT["login"](
URL("/login")
, DATA("pht::Username", "Admin")
, DATA("pht::Password", "admin123")
, CLICK("button")
, fatality=True
)SCRIPT["идентификатор"] | SCRIPT("идентификатор" {, "идентификатор" } {, аргумент=значение })
Для того что бы выполнить ранее определённый скрипт нужно использовать команду SCRIPT[“Идентификатор”].
Пример:
SCRIPT["login"](
URL("/login")
, DATA("pht::Username", ARG("user"))
, DATA("pht::Password", ARG("passwd"))
, CLICK("button")
, ~(PAGE_URL < "/login") # URL текущей страница не должен содержать подстроки /login
, fatality=True
)
TEST["Проверка входа на сайт"](
ARG("user", "Admin")
, ARG("passwd", "admin123")
, SCRIPT["login"]
)Как видно при большом количестве аргументов не удобно использовать их явное определение. По этому для вызова параметризованного скрипта нужно использовать конструкцию SCRIPT("Идентификатор" {, "идентификатор"}, {, аргумент=значение }). Так же с её помощью возможно вызвать последовательно несколько скриптов указав их идентификаторы через запятую.
Пример:
TEST["Проверка входа на сайт"](
SCRIPT("login", user="Admin", passwd="admin123")
)
TEST["Без пароля не войдём"](
SCRIPT("login", user="Admin")
, negative=True
)
TEST["Без логина не войдём"](
SCRIPT("login", passwd="admin123")
, negative=True
)
TEST["Заходим и выходим"](
SCRIPT("login", "logout", passwd="admin123", user="Admin")
)С целью исключения взаимовлияния скриптов осуществляется изоляция их пространств имён. Это значит что аргументы определённые в блоке команд скрипта или переданные ему при вызове существуют только во время его выполнения. Если аргумент определённый в скрипте должен использоваться и дальше в тесте, то его нужно определить внутри последовательности команд теста до вызова скрипта.
Пример:
SCRIPT["arg_url"](
ARG("url", PAGE_URL)
)
TEST["Пример использования значения из скрипта"](
ARG("url", "") # Заранее определяем аргумент "url"
, SCRIPT("arg_url") # Вызываем скрипт, он переопределит аргумент
, PRINT(ARG("url")) # Печатаем значение аргумента "url". Если его заранее не определить, то это действие будет пропущено
)STEP["заголовок"](Последовательность команд, {, атрибут=значение })
Логически связанную последовательность команд можно объединить вместе при помощи объекта STEP. Это позволит добавить описание бизнес-логики, что позволит отделить смысл теста от описания действий пользователя. Для создания простейшего шага достаточно указывать только его заголовок в виде обычной строки. Все команды находящиеся после него до начала следующего шага или конца теста будут рассматриваться как тело шага.
Пример:
...
, STEP["Логин"](
DATA("pht::Username", ARG("user"))
, DATA("pht::Password", ARG("passwd"))
, CLICK("button")
)
# шаги могут быть вложенными:
...
, STEP["Логин"](
STEP["заполняем"](
DATA("pht::Username", ARG("user"))
, DATA("pht::Password", ARG("passwd"))
)
, CLICK("button")
)
# Сокращённая запись (вложенность не поддерживается)
...
, "Логин"
, DATA("pht::Username", ARG("user"))
, DATA("pht::Password", ARG("passwd"))
, CLICK("button")Для шага могут быть определены атрибуты description, step_id и fatality.
SCOPE(["идентификатор"])
Для передачи данных в тесты можно создавать пользовательские пространства имён. Это полезно для хранения данных которые используют тесты находящиеся в разных файлах.
Чтобы добавить значение в пространство имён необходимо получить на него ссылку вызвав объект SCOPE. Можно создавать неограниченное количество пространств имён, для этого при вызове объекта SCOPE нужно передать уникальный идентификатор который будет ассоциирован с конкретным пространством.
NP(идентификатор, имя [, title ])
Для получения данных из конкретного пространства имён можно в описании тестов использовать специальный объект NP (Name Pointer).
Аргументы: * идентификатор - строковый идентификатор пространства имён. * имя - строка имени ассоциированного с конкретным значением. * title - Строковое представление которое будет использовано в отчётах.
Для упрощённого доступа к значениям помимо EP существует специальные селекторы - "scope::идентификатор/имя" и "scope[индекс]::идентификатор/имя". Индекс в последнем селекторе преобразуется в префикс индекс:: если в пространстве имён находятся текстовые данные (если данные представляют селектор с уже определённым индексом, то он будет заменён на индекс scope-селектора), иначе будут возвращены данные находящиеся по соответствующему индексу.
Примеры:
scp = SCOPE("user") # Получаем пространство имён с идентификатором "user"
scp.fio = fake.name() # Генерируем данные пользователя и сохраняем их в пространстве имён "user"
scp.email = fake.email()
scp.address = fake.address()
TEST["..."](
...
, DATA("#fio", scp.fio) # Получаем имя пользователя из ссылке на скоп
, DATA("#email", "scope::user/email") # Получаем email по селектору на скоп
, DATA("#adres", NP("user", "address")) # Получаем address через объект NP
)
# помещаем в scope::form/city сразу несколько селекторов
scop = SCOPE("form")
scop.city = ("#state", "#react-select-3-option-2", "#city", "Panipat")
# передаём в команду все селекторы из scope::form/city
CLICK(*NP("form", "city", title="Штат и город"))Селекторы
Для определения пути к веб-элементу используются селекторы. Обычно селектором является строка соответствующая специальному формату - XPATH или CSS, либо строка содержащая значение уникальной части веб-элемента (например значение атрибуты или текст внутри веб-элемента). Meta Test может автоматически определить тип XPATH и CSS селектора. Правила определения типа приведены в таблице.
| Начало строки | Описание |
|---|---|
| / | Поиск по XPATH |
| ( | Поиск по XPATH |
| ./ | Поиск по XPATH |
| .. | Поиск по XPATH |
| */ | Поиск по XPATH |
| . | Поиск по CSS селектору (класс) |
| # | Поиск по CSS селектору (id) |
| * | Поиск по CSS селектору |
| [ … ] | Поиск по CSS селектору |
| htlm-тег | Поиск по CSS селектору |
Для того что бы явно указать тип селектора нужно задать его префикс через разделитель “::”.
| Префикс | Описание |
|---|---|
| css | Поиск по CSS селектору |
| xpath | Поиск по XPATH селектору |
| id | Поиск по атрибуту id |
| name | Поиск по атрибуту name |
| class | Поиск по имени класса |
| tag | Поиск по имени тега |
| link | Поиск по полному совпадению с текстом ссылки |
| plink | Поиск по частичному совпадению с текстом ссылки |
| pht | Поиск по совпадению с текстом атрибута placeholder |
| text | Поиск по полному совпадению с текстом внутри веб-элемента |
| content | Поиск по частичному совпадению с текстом внутри веб-элемента |
Если ни одно из вышеприведённых правил не подходит, то происходит поиск по частичному совпадению с текстом внутри веб-элемента.
Селекторы могут указывать не на один, а на список веб-элементов. В этом случае что бы выбрать конкретный веб-элемент можно указать его целочисловой индекс добавив его в конец префикса селектора - префикс[индекс]::селектор или индекс::селектор если префикс не задан. Отсчёт индексов начинается с 0, индекс -1 будет указывать на последний веб-элемент.
Пример:
# Кликнет на первый веб-элемент который принадлежат классу react-datepicker-wrapper
CLICK("[1]::.react-datepicker-wrapper")
# То же, но с явным указанием типа селектора.
CLICK("css[1]::.react-datepicker-wrapper")EP(селектор {, селектор } [, title ] [, timeout ])
EP это указатель на веб-элемент (Element Pointer). Он представляет из себя обёртку над селекторами и может быть использован в качестве аргумента команд которым нужны веб-элементы, а не строки указывающие на них (например с JS). Кроме того с помощью указателя EP могут быть определены составные селекторы. Если селектор EP указывает на несколько веб-элементов, то для их фильтрации можно применять срезы.
Аргументы:
- селектор - Селектор веб-элемента
- title - Строковое представление которое будет использовано в отчётах
- timeout - Время явного ожидания веб-элемента. По умолчанию - CONFIG.WAIT_TIMEOUT
Пример:
# Меняем местами веб-элементы A и B
, JS("scope::js/dnd", EP("#column-a"), EP("#column-b"))Составные селекторы
Иногда бывает проще определить путь к веб-элементу страницы как список селекторов подчинённых веб-элементов. Такие селекторы называются составными. Этот способ определения селектора удобен ещё тем что позволяет комбинировать селекторы разных типов. Что бы определит составной селектор нужно передать в указатель EP не один, а несколько селекторов.
Пример:
# На форме есть несколько полей с заполнителем "Адрес".
# По этому выбираем то которое находится в контейнере с идентификатором "adress_1"
ONE(EP("#adress_1", "pht::Адрес"))Составными селекторами не стоит злоупотреблять т.к. скорость их работы может быть значительно меньше чем у обычных селекторов.
FP(селектор_фрейма {, селектор_фрейма }, селектор [, title ] [, timeout ])
Для определения пути к веб-элементу находящемуся внутри фрейма можно использовать специальный объект FP (Frame Pointer). Сначала ему нужно передать селектор фрейма (или нескольких фреймов в порядке вложенности) в котором находится веб-элемент, а затем селектор самого веб-элемента.
Аргументы:
- селектор_фрейма - селектор указывающий на веб-элемент с тегом frame или iframe.
- селектор - селектор указывающий на веб-элемент находящийса внутри фрейма.
- title - Строковое представление которое будет использовано в отчётах.
- timeout - Время явного ожидания веб-элемента. По умолчанию - CONFIG.WAIT_TIMEOUT
Пример:
TEST["nestedframes"](
URL("https://demoqa.com/nestedframes")
, ONE(FP("#frame1", "body")) == "Parent frame"
, ONE(FP("#frame1", "iframe", "body")) == "Child Iframe"
)Этот способ удобен если нужно однократно обратится к веб-элементу в фреме, если же нужно активно работать с такими веб-элементами, то целесообразно полностью переключится на фрейм (см. описание команды FRAME)
SP(sd-селектор {, sd-селектор }, селектор [, title ] [, timeout ])
Для определения пути к веб-элементу находящемуся внутри теневого DOM можно использовать специальный объект SP (Shadow Pointer). Ему сначала передают селекторы контейнеров содержащих вложенный DOM, а затем селектор веб-элемента.
Для того что бы получить доступ в веб-элементам внутри теневого DOM он должен находится в режиме open. Внутри теневого DOM не будут работать XPATH селекторы.
Аргументы: * sd-селектор - селектор веб-элемента содержащего теневой DOM. * селектор - селектор веб-элемента находящегося внутри теневого DOM. * title - Строковое представление которое будет использовано в отчётах. * timeout - Время явного ожидания веб-элемента. По умолчанию - CONFIG.WAIT_TIMEOUT
Пример:
TEST["shadow DOM"](
URL("http://watir.com/examples/shadow_dom.html")
, DATA(SP("#shadow_host", "input[type=text]"), "file.jpeg")
, UPLOAD(SP("#shadow_host", "input[type=file]"), "file.jpeg")
, CLICK(SP("#shadow_host", "input[type=checkbox]"))
, ONE(SP("#shadow_host", "#nested_shadow_host", "#nested_shadow_content")) == "nested text"
)Выражения и кванторы со скалярными типами
Для проверки значений на страницы используются выражения. Выражения имеют смысл если являются предикатами т.е. возвращают логическое значение. Для проверки свойств веб-элементов используются объекты называемые кванторами они получили своё название из-за схожего поведения с одноимёнными абстракциями математической логики. Кроме кванторов в выражениях можно использовать константы, они подробно описаны в соответствующем разделе.
ONE(селектор [, propertie ] [, no_displayed ] [, strip ] [, lower ] [, timeout ]) { "[" фильтр "]" }
Квантор единственности. Если селектор будет указывать более чем на один веб-элемент или веб-элемента вообще не будет обнаружено, то выполнение квантора завершится с ошибкой. В противном случае квантор вернёт свойство веб-элемента на который указывает селектор. Какое именно свойство нужно вернуть указывается в необязательном атрибуте propertie. Если атрибут не задан то:
- для веб-элементов input с типом image возвращается значение свойства src
- для веб-элементов input с типом radio или checkbox возвращается значение свойства checked
- для веб-элементов input с другими типами, textarea, select, progress и param возвращается значение свойства value
- для веб-элемента option возвращается значение свойства selected
- для всех других веб-элементов значение свойства textContent
При помощи атрибута propertie можно получить значение css свойства веб-элемента или его атрибута. Для этого нужно указать его префикс css:: или attr:: соответственно. Если у веб-элемента отсутствует указанный атрибут, то квантор вернёт None. Если у атрибута веб-элемента не указано значение, то квантор вернёт True.
Кроме properties квантор может принимать следующие параметры:
- displayed - Если равен True, то ждём отображения веб-элемента на страницы. По умолчанию - False
- strip - Если равен True, удаляются все пробельные символы в начале и конце возвращаемой строки. По умолчанию - True
- lower - Если равен True, то все символы приводятся в нижний регистр. По умолчанию - False
- timeout - Время явного ожидания веб-элемента. По умолчанию - CONFIG.WAIT_TIMEOUT
В выражениях с квантором ONE могут использоваться следующие операторы:
| Оператор | Описание | Пояснение |
|---|---|---|
| == | Проверка на равенство | ONE(…) == значение |
| != | Проверка на неравенство | ONE(…) != значение |
| @ | Проверка соответствия регулярному выражению | ONE(…) @ r"выражение" |
| >> | Вхождение подстроки без учёта регистра (Правоассоциативный) | "подстрока" >> ONE(…) |
| << | Вхождение подстроки без учёта регистра (Левоассоциативный) | ONE(…) << "подстрока" |
| > | Вхождение подстроки с учётом регистра (Правоассоциативный) | "подстрока" > ONE(…) |
| < | Вхождение подстроки с учётом регистра (Левоассоциативный) | ONE(…) < "подстрока" |
| >= | Вхождение подстроки в начало значения | ONE(…) >= "подстрока" |
| <= | Вхождение подстроки в конец значения | ONE(…) <= "подстрока" |
| + | Сумма числовых или конкатенация строковых значений | ONE(…) + значение |
| - | Разность числовых значений | ONE(…) - значение |
| * | Произведение числовых значений | ONE(…) * значение |
| / | Деление числовых значений | ONE(…) / значение |
| // | Целочисленное деление числовых значений | ONE(…) // значение |
| % | Остаток от деления числовых значений | ONE(…) % значение |
| ** | Возведение в степень числовых значений | ONE(…) ** значение |
| [] | Получение значения по индексу | ONE(…)[индекс] |
| & | Логическая операция И | (ONE(…) == знач1) & (ONE(…) == знач2) |
| | | Логическая операция ИЛИ | (ONE(…) == знач1) | (ONE(…) == знач2) |
| ~ | Логическая операция НЕ | ~(ONE(…) == значение) |
Пример:
# Существует только ОДИН веб-элемент соответствующий селектору "div.passwd"
ONE("div.passwd")
# Существует только ОДИН веб-элемент соответствующий селектору "div.passwd"
# Текст которого содержит подстроку "Пароль"
"Пароль" > ONE("div.passwd")
# Существует только ОДИН веб-элемент соответствующий селектору "div.passwd"
# Цвет фона которого равен #f5f5f5
ONE("div.passwd", "css::background-color") == "#f5f5f5"
# Проверка существования класса error у веб-элемента
ONE("#password", "classList") << "error"ALL(селектор [, propertie ], [, displayed ] [, strip ] [, lower ] [, unique ] [, timeout ]) { "[" фильтр "]" }
Квантор всеобщности Этот квантор возвращает коллекцию свойств веб-элементов на которые указывает селектор. Его использование аналогично квантору ONE, за тем исключением что в выражение применяется не к одному значению, а их списку. Предикат с квантором ALL примет истину когда выражение содержащие квантор будет истенно с каждым значением которое вернул квантор.
Атрибуты:
- propertie - Идентификатор возвращаемого свойства (см. описание квантора ONE)
- displayed - Если равен True, то ждём отображения веб-элементов на страницы. По умолчанию - False
- strip - Если равен True, то удаляются все пробельные символы в начале и конце каждой возвращаемой строки. По умолчанию - True
- lower - Если равен True, то все символы приводятся в нижний регистр. По умолчанию - False
- unique - Все значения возвращаемые квантором будут уникальными (порядок значений сохраняется). По умолчанию - False
- timeout - Время явного ожидания ожидания веб-элемента. По умолчанию - CONFIG.WAIT_TIMEOUT
Семантика операторов при операциях со скалярным значением:
| Оператор | Описание | Пояснение |
|---|---|---|
| == | Проверка на равенство КАЖДОМУ значению выражения | ALL(…) == значение |
| != | Проверка на неравенство КАЖДОМУ значению выражения | ALL(…) != значение |
| @ | Проверка соответствия КАЖДОГО значения регулярному выражению | ALL(…) @ r"выражение" |
| >> | Вхождение подстроки в КАЖДОЕ значение выражения без учёта регистра | "подстрока" >> ALL(…) |
| << | Вхождение подстроки в КАЖДОЕ значение выражения без учёта регистра | ALL(…) << "подстрока" |
| > | Вхождение подстроки в КАЖДОЕ значение выражения с учётом регистра | "подстрока" > ALL(…) |
| < | Вхождение подстроки в КАЖДОЕ значение выражения с учётом регистра | ALL(…) < "подстрока" |
| >= | Вхождение подстроки в начало КАЖДОГО значения выражения | ALL(…) >= значение |
| <= | Вхождение подстроки в конец КАЖДОГО значения выражения | ALL(…) <= значение |
| + | Сумма или конкатенация значения с каждым веб-элементом выражения | ALL(…) + значение |
| - | Разность значения с каждым веб-элементом выражения | ALL(…) - значение |
| * | Произведение значения с каждым веб-элементом выражения | ALL(…) * значение |
| / | Деление значения с каждым веб-элементом выражения | ALL(…) / значение |
| // | Целочисленное деление значения с каждым веб-элементом выражения | ALL(…) // значение |
| % | Остаток от деления значения с каждым веб-элементом выражения | ALL(…) % значение |
| ** | Возведение в степень значения с каждым веб-элементом выражения | ALL(…) ** значение |
| & | Логическая операция И над КАЖДЫМ веб-элементом выражения | (ALL(…) == знач1) & (ALL(…) == знач2) |
| | | Логическая операция ИЛИ над КАЖДЫМ веб-элементом выражения | (ALL(…) == знач1) |(ALL(…) == знач2) |
| ~ | Логическая операция НЕ над КАЖДЫМ веб-элементом выражения | ~(ALL(…) == значение) |
Пример:
# Текст во ВСЕХ веб-элементов соответствующих селектору "div.passwd" содержит подстроку "Пароль"
ALL("div.passwd") < "Пароль"
# Текст во ВСЕХ свойствах веб-элементов соответствующих селектору "div.passwd" равен строке "Пароль"
ALL("div.passwd") == "Пароль"ANY(селектор [, propertie ] [, displayed ] [, strip ] [, lower ] [, unique ] [, timeout ]) { "[" фильтр "]" }
Квантор существования. Этот квантор схож с квантором ALL, но в отличии от него предикат с квантором ANY примет истину когда выражение содержащие квантор будет истинно с любым из значений которое вернул квантор.
Атрибуты:
- propertie - Идентификатор возвращаемого свойства (см. описание квантора ONE)
- displayed - Если равен True, то ждём отображения веб-элемента на страницы. По умолчанию - False
- strip - Если равен True, удаляются все пробельные символы в начале и конце каждой возвращаемой строки. По умолчанию - True
- lower - Если равен True, то все символы приводятся в нижний регистр. По умолчанию - False
- unique - Значения возвращаемые квантором будут уникальными (порядок значений сохраняется). По умолчанию - False
- timeout - Время явного ожидания ожидания веб-элемента. По умолчанию - CONFIG.WAIT_TIMEOUT
Семантика операторов при операциях со скалярным значением:
| Оператор | Описание | Пояснение |
|---|---|---|
| == | Проверка на равенство ЛЮБОМУ значению выражения | ANY(…) == значение |
| != | Проверка на неравенство ЛЮБОМУ значению выражения | ANY(…) != значение |
| @ | Проверка соответствия ЛЮБОГО из значений регулярному выражению | ANY(…) @ r"выражение" |
| >> | Вхождение подстроки в ЛЮБОЕ значение выражения без учёта регистра | "подстрока" >> ANY(…) |
| << | Вхождение подстроки в ЛЮБОЕ значение выражения без учёта регистра | ANY(…) << "подстрока" |
| > | Вхождение подстроки в ЛЮБОЕ значение выражения с учётом регистра | "подстрока" > ANY(…) |
| < | Вхождение подстроки в ЛЮБОЕ значение выражения с учётом регистра | ANY(…) < "подстрока" |
| >= | Вхождение подстроки в начало ЛЮБОГО значения выражения | ANY(…) >= значение |
| <= | Вхождение подстроки в конец ЛЮБОГО значения выражения | ANY(…) <= значение |
| + | Сумма или конкатенация значения с каждым веб-элементом выражения | ANY(…) + значение |
| - | Разность значения с каждым веб-элементом выражения | ANY(…) - значение |
| * | Произведение значения с каждым веб-элементом выражения | ANY(…) * значение |
| / | Деление значения с каждым веб-элементом выражения | ANY(…) / значение |
| // | Целочисленное деление значения с каждым веб-элементом выражения | ANY(…) // значение |
| % | Остаток от деления значения с каждым веб-элементом выражения | ANY(…) % значение |
| ** | Возведение в степень значения с каждым веб-элементом выражения | ANY(…) ** значение |
| & | Логическая операция И над ЛЮБЫМ веб-элементом выражения | (ANY(…) == знач1) & (ANY(…) == знач2) |
| | | Логическая операция ИЛИ над ЛЮБЫМ веб-элементом выражения | (ANY(…) == знач1) | (ANY(…) == знач2) |
| ~ | Логическая операция НЕ над ЛЮБЫМ веб-элементом выражения | ~(ANY(…) == значение) |
Пример:
# СУЩЕСТВУЕТ веб-элемент соответствующий селектору "div.passwd" текст которого содержит подстроку "Пароль"
ANY("div.passwd") < "Пароль"
# СУЩЕСТВУЕТ веб-элемент соответствующий селектору "div.passwd" текст которого равен строке "Пароль"
ANY("div.passwd") == "Пароль"Фильтры
К спискам значений полученные путём вычисления кванторов ALL или ANY можно применить фильтр, причём полученные данные будут сохранять семантику заданную квантором. В качестве фильтров может использоваться индекс, срез или фильтрующая функция. Если после применение фильтра останется пустая коллекция, то вычисление завершится ошибкой.
Пример:
TEST["Проверка сортировки в второй колонки"](
URL("http://the-internet.herokuapp.com/tables")
, "Сортировка First Name"
, CLICK(EP("#table1", "[2]::th"))
# В качестве фильтра используем срез который вернёт первый столбец таблицы
# т. е. каждое 6-ое значение начиная с индекса 1
, SORT_ASC(ALL("#table1 tr td")[1::6]) == True
)В качестве фильтра может быть использована унарная (т.е. принимающая 1 аргумент) функция которая должна вернуть True если значение проходит фильтрацию и False если нет.
Пример:
TEST["Печатаем имена длиннее трёх"](
URL("http://the-internet.herokuapp.com/tables")
# Получаем значение во второй колонке и оставляем только те длина которых больше 3
, PRINT(ALL("#table1 tr td")[1::6][lambda x: len(x) > 3])
)Если квантор в качестве значения возвратил список или словарь, то получить значение конкретного поля можно тоже при помощи фильтра:
Пример:
# Проверяем значение возвращаемое встроенным валидатором для input
# Свойство validity возвращает json объект который преобразуется в словарь вида: {"valid": False, "valueMissing":False, ....}
# При помощи фильтра ["valid"] мы получаем значение по ключу "valid" и сравниваем его с False
ONE("name::email", "validity")["valid"] == FalseКванторы и коллекции
Кванторы могут определять не только выражениями со скалярными значениями, но и с коллекциями т.е. с списками, кортежами и множествами. При этом действия квантора распространяется на все значения из коллекции.
Семантика квантора ONE в выражениях с коллекциями:
| Оператор | Описание | Пояснение |
|---|---|---|
| > | Вхождение значения квантора в коллекцию с учётом регистра | ONE(…) > [ значение, … ] |
| < | Вхождение значения квантора в коллекцию с учётом регистра | [ значение, … ] < ONE(…) |
| >> | Вхождение значения квантора в коллекцию без учёта регистра | ONE(…) >> [ значение, … ] |
| << | Вхождение значения квантора в коллекцию без учёта регистра | [ значение, … ] << ONE(…) |
| >= | Вхождение значения квантора в начало коллекции | ONE(…) >= [ значение, … ] |
| <= | Вхождение значения квантора в конец коллекции | ONE(…) <= [ значение, … ] |
Пример:
# Текст веб-элемента с селектором "[1]::div" находится в списке ["Строка 1", "Строка 2"]
ONE("[1]::div") > ["Строка 1", "Строка 2"]Операторы == и != сравнивают значение возвращаемое квантором ALL или ANY со значением списка, а операторы + и - позволяют объединить значение квантора и списка или получить их разность.
Семантика квантора ALL в выражениях с коллекциями:
| Оператор | Описание | Пояснение |
|---|---|---|
| == | Проверка на равенство выражения коллекции | ALL(…) == [ значение, … ] |
| != | Проверка на неравенство выражения коллекции | ALL(…) != [ значение, … ] |
| >> | Вхождение КАЖДОЙ подстроки списка в КАЖДЫМ значение выражения без учёта регистра | ALL(…) >> [ значение, … ] |
| << | Вхождение КАЖДОЙ подстроки списка в КАЖДЫМ значение выражения без учёта регистра | [ значение, … ] << ALL(…) |
| > | Вхождение КАЖДОЙ подстроки списка в КАЖДЫМ значение выражения с учётом регистра | ALL(…) > [ значение, … ] |
| < | Вхождение КАЖДОЙ подстроки списка в КАЖДЫМ значение выражения с учётом регистра | [ значение, … ] < ALL(…) |
| >= | Вхождение КАЖДОЙ подстроки списка в начало КАЖДОГО значения выражения | ALL(…) >= [ значение, … ] |
| <= | Вхождение КАЖДОЙ подстроки списка в конец КАЖДОГО значения выражения | ALL(…) <= [ значение, … ] |
| + | Конкатенация | ALL(…) + ALL(…) или ALL(…) + […] |
| - | Список различающихся веб-элементов | ALL(…) - ALL(…) или ALL(…) - […] |
Пример:
# Текст ВСЕХ веб-элементов соответствующих селектору "div.passwd" содержат подстроку "Пароль" И "Passwd"
ALL("div.passwd") < ["Пароль", "Passwd"]
# проверка заполнения таблицы. Оператор == сравнивает значение квантора со списком
ALL("tr > td") == [
"Student Name", "имя, фамилия"
, "Student Email", "scope::data/email"
, "Gender", "Male"
, "Mobile", "scope::data/mobile"
, "Date of Birth", "31 December,1999"
, "Subjects", "Physics"
, "Hobbies", "Sports"
, "Picture", "scope::data/image_name"
, "Address", "scope::data/address"
, "State and City", "Haryana Panipat"
]Семантика квантора ANY в выражениях с коллекциями:
| Оператор | Описание | Пояснение |
|---|---|---|
| == | Проверка на равенство выражения коллекции | ANY(…) == [ значение, … ] |
| != | Проверка на неравенство выражения коллекции | ANY(…) != [ значение, … ] |
| >> | Вхождение ЛЮБОЙ подстроки списка в ЛЮБОЕ значение выражения без учёта регистра | ANY(…) >> [ значение, … ] |
| << | Вхождение ЛЮБОЙ подстроки списка в ЛЮБОЕ значение выражения без учёта регистра | [ значение, … ] << ANY(…) |
| > | Вхождение ЛЮБОЙ подстроки списка в ЛЮБОЕ значение выражения с учётом регистра | ANY(…) > [ значение, … ] |
| < | Вхождение ЛЮБОЙ подстроки списка в ЛЮБОЕ значение выражения с учётом регистра | [ значение, … ] < ANY(…) |
| >= | Вхождение ЛЮБОЙ подстроки списка в начало ЛЮБОГО значения выражения | ANY(…) >= [ значение, … ] |
| <= | Вхождение ЛЮБОЙ подстроки списка в конец ЛЮБОГО значения выражения | ANY(…) <= [ значение, … ] |
| + | Конкатенация | ANY(…) + ALL(…) или ANY(…) + […] |
| - | Список различающихся веб-элементов | ANY(…) - ALL(…) или ANY(…) - […] |
Пример:
# СУЩЕСТВУЕТ веб-элементов соответствующий селектору "div.passwd"
# Текст которого содержит подстроки "Пароль" ИЛИ "Passwd"
ANY("div.passwd") < ["Пароль", "Passwd"]Обратите внимание на то что при сравнении значения кванторов ALL или ANY со списками или кортежами имеет значение порядок веб-элементов в коллекции. Если вы не хотите что бы он был важен, то сравнивать нужно с множеством. В этом случае значение которое вернёт квантор так же будет приведено к множеству.
Пример:
# На странице 2 веб-элемента. Значение первого "A", второго "B".
ALL("#columns > .column") == ["A", "B"]
# На странице есть веб-элементы со значениями "A" и "B".
ALL("#columns > .column") == {"A", "B"}ANY_LIST(значение {, значение }) и ALL_LIST(значение {, значение })
Нетрудно заметить что действие квантора распространяется и на веб-элементы коллекции. Например выражение ALL(“tr”) < [“1”, “2”, “3”] означает что текст внутри ВСЕХ веб-элементов с селектором tr должен содержать ВСЕ подстроки “1”, “2” и “3”.
Если текст хотя бы одного из веб-элементов не будет содержать одну из этих подстрок всё выражение будет ложным. Что бы ослабить это условие список подстрок нужно определить как коллекцию заданную при помощи конструктора ANY_LIST: ALL(“tr”) < ANY_LIST(1,2,3). Такое выражение будет означать что текст КАЖДОГО из веб-элементов соответствующих селектору должен содержать ЛЮБУЮ из подстрок коллекции.
Аналогичная ситуация и с выражениями образованными при помощи квантора ANY. Так выражение ANY(“tr”) < [“1”, “2”, “3”] означает что текст любого из веб-элементов с селектором tr должен содержать ЛЮБУЮ из подстрок “1”, “2” или “3”.
Если необходимо проверить вхождение ВСЕХ подстрок в текст ЛЮБОГО из веб-элементов, то выражение нужно переписать с использованием конструктора ALL_LIST: ANY(“tr”) < ALL_LIST(1,2,3).
Кроме того конструктора ANY_LIST и ALL_LIST могут использоваться вместе с квантором ONE что бы более точно определить его поведение в выражениях с коллекциями. Например выражение ONE(“[1]::tr”) > ANY_LIST(“1”, “2”, “3”) означает что значение возвращаемое квантором ONE(“[1]::tr”) должно содержать ЛЮБУЮ из подстрок “1”, “2” или “3”. А выражение ONE(“[1]::tr”) > ALL_LIST(“1”, “2”, “3”) означает что значение этого квантора должно содержать ВСЕ подстроки из ALL_LIST(“1”, “2”, “3”).
Если в выражениях с квантором ONE используются обычные коллекции (т.е. кортежи, списки или множества), то для операторов @ и != они будут интерпретироваться как ALL_LIST, а для всех остальных как ANY_LIST.
Проверка инвариантов
Вышеприведённые выражения можно использовать для проверки данных на странице и её поведения. Для этого достаточно поместить любое выражение в описании теста.
Если такое выражение окажется ложным, то тест или завершится со статусом FATAL или продолжится со статусом FAILED в зависимости от значения параметра CONFIG.TEST_FATALITY. Команды ASSERT и FATAL позволяют явно задать это поведение.
Пример:
# Тест завершится или выдаст ошибку, но продолжит работать
# если текст в веб-элементе с селектором "div.user" не будет равен "Admin"
, ONE("div.user") == "Admin"ASSERT | ASSERT(выражение [, сообщение ])
Вычисляет выражение, если его значение False, то тест продолжится со статусом FAILED. К отчёту можно добавить текст сообщения об ошибки. Если выражение не содержит аргументов, то оно сразу переводит тест в статус FAILED.
Аргументы:
- выражение - Выражение в котором произошла ошибка
- сообщение - пользовательское описание ошибки
FATAL | FATAL(выражение [, сообщение ])
Вычисляет выражение, если его значение False, то тест завершится со статусом FATAL. К отчёту можно добавить текст сообщения об ошибки. Если выражение не содержит аргументов, то оно сразу переводит тест в статус FATAL.
Аргументы:
- выражение - Выражение в котором произошла ошибка
- сообщение - пользовательское описание ошибки
Пример:
# Завершает тест если текущий URL не содержит подстроки "/news"
, FATAL("/news" > PAGE_URL, "Не тот URL")
# Если в тексте заголовка страницы отсутствует подстрока "Новости",
# то переводим тест в FAILED, но продолжаем тестировать
, ASSERT("Новости" > PAGE_TITLE, "Не верный заголовок страницы")Использование кода на языке python в тестах
Поскольку интерпретатор мета-языка работает поверх виртуальной машины python, то возможно комбинировать тесты описанные на мета-языке с программным кодом написанным на языке python. Но нужно учитывать что обычный код на python-е будет работать только во время загрузки тестов, но не во время их выполнения. Если же необходимо вызвать python-код во время выполнения теста, то можно воспользоваться одним из нижеописанных способов.
Динамическое получение данных
В предыдущих примерах данные создавались перед выполнением теста. Но иногда необходимо получать их динамически во время его выполнение. Для этой цели можно в качестве данных использовать нульарную функцию (т.е. функцию не принимающую аргументов) которая будет их возвращать.
Примеры:
# pass_manager - гипотетическая функция которая выдаёт пароль пользователя во время выполнения команды DATA из теста.
# С её помощью можно гарантировать не пересечение данных между тестами.
test = TEST["Зачисление денег на счёт"](
...
, DATA("#user", ARG("user_pass"))
...
, test_id="123"
)
test(user_pass=lambda: pass_manager(test.test_id))
test = TEST["Списание денег с счёта"](
...
, DATA("#user", ARG("user_pass"))
...
, test_id="345"
)
test(user_pass=lambda: pass_manager(test.test_id))
import random
def get_data():
return random.randint(1, 100)
TEST["Заполнение случайными числами"](
...
, DATA("#random_data", get_data)
...
)Вызов python функции из теста
При описании теста на метаязыке вы полностью абстрагированы от объекта WebDriver-а. Но не исключено что может возникнуть необходимость в низкоуровневом доступе к управлению браузером. В этом случае можно получить объект-обёртку WebBrowser и вызвав его метод get_driver который вернёт объект WebDriver-а. Что бы получить WebBrowser нужно передать в тело теста унарную функцию (т.е. функцию с 1 аргументом) которой и будет передан WebBrowser.
Пример:
def go_example(browser):
driver = browser.get_driver()
driver.get("https://example.com")
TEST["Пример перехода на URL через WebDriver"](
go_example
...
)Использовать эту возможность нужно очень аккуратно и только при крайней необходимости.
Декоратор command
Вызываемая функция из последовательности команд теста не является полноценной командой. Например результат который она возвращает нельзя использовать в выражениях. Для решения этой проблемы был создан декоратор @command. Декорируемая им функция в первом аргументе принимает объект WebBrowser представляющий обёртку надо webdriver. Кроме него она может принять произвольное количество аргументов передаваемых ей из тела теста, а возвращаемое ей значение может быть использовано напрямую в выражениях.
Пример:
@command
def SORT_ASC(browser, data_list):
return all(map(lambda x, y: x <= y, data_list[0::2], data_list[1::2]))
TEST["Проверка сортировки"](
...
, SORT_ASC(ALL(".price")) == True
....
)В команде созданной при помощи декоратора command можно добавлять вложения, предупреждения, сообщения или скриншоты. Для этих целей служат методы set_attachment, set_warning, set_message и set_screenshots. Методы set_warning и set_message принимают строковые данные. Метод set_screenshots принимает байтовую строку содержащую изображение в формате PNG (её можно получить вызвав browser.screenshot()). Для добавления вложения нужно вызвать метод set_attachment передав ему наименования вложения, его данные и тип. В качестве типа вложения на данный момент можно использовать следующие значения: TEXT, CSV, JSON, XML, YAML, HTML, PNG, MP4, PDF.
Пример:
@command
def SORT_ASC(browser, data_list):
browser.set_message("Проверка сортировки")
browser.set_attachment("data.txt", "Привет!", "TEXT")
try:
if all(map(lambda x, y: x <= y, data_list[0::2], data_list[1::2])):
return True
else:
browser.set_warning("Не отсортировано!")
return False
except:
browser.set_screenshots(browser.screenshot())Для версии 2.0 редакция от 16.09.2026
Политика конфиденциальности