Извлекаем данные с помощью языка запросов JsonPath библиотеки Rest Assured

logo

Традиционно начну с небольшого вступления: речь в статье пойдет о том, как достать нужные данные из JSON, используя библиотеку JsonPath, которая входит в состав Rest Assured – наиболее популярного решения для тестирования API. Есть и другие одноименные библиотеки (JsonPath), которые используют другой синтаксис, это даже специально подчеркнули в доках Rest Assured во избежание путаницы.

JsonPath основан на Groovy Gpath и использует его для построения своих запросов, условно можно сказать, что это xpath для json – язык запросов для получения нужных данных. К сожалению не все из нас владеют Груви (я совсем не знаю), а вот нормальной документации по использованию языка запросов JsonPath в более-менее развернутом виде я не нашел. Вот доки GPath в груви, на котором все работает, вот доки самого класса с парой примеров, и конечно документация Rest-Assured

Итак, мы не рассматриваем, что такое JSON, как пользоваться Rest-Assured, какие методы есть в JsonPath для парсинга, вытягивания Листа или Мапы, начинаем сразу с того, что у нас есть некий json, из которого мы хотим получить различные данные. При этом нам не хочется или нет надобности создавать простые Java-классы и маппить данные из json на них, мы просто хотим вытащить необходимое просто и быстро, без написания лишнего кода. Библиотека JsonPath хоть и поставляется в составе RA, но может быть использована отдельно. Для тренировки или проверки, можете подтянуть зависимость

<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>json-path</artifactId>
    <version>3.3.0</version>
</dependency>

Ну а сам код для простоты будет вот таким

String expr = ""; // тут будет запрос
System.out.println(JsonPath.from(json).getString(expr)); //выводим результат

Еще раз подчеркну, что мы не рассматриваем методы типа setRoot, getList, getMap и так далее, сосредоточимся на языке запросов. Вот json с которым мы будем работать:

{
  "class": [
    { "name": "Ivan",
      "surname": "Ivanov",
      "age": "13",
      "marks": [2,3,4,4,5],
      "address": {
        "street": "Lenin",
        "house": 12
      }
    },
    { "name": "Petr",
      "surname": "Petrov",
      "age": "14",
      "marks": [5,5,3,5],
      "address": {
        "street": "Marx",
        "house": 7
      }
    },
    { "name": "Ivan",
      "surname": "Sidorov",
      "age": "14",
      "marks": [3,4,2,2,3],
      "address": {
        "street": "Pobedy",
        "house": 9
      },
      "some.value": 42
    }]
}

Он составлен в учебных целях, для примера некоторых особенностей, обратите внимание, что возраст возвращается строкой («age»: «14»), а не числом, и, кроме того, у третьего ученика в классе есть уникальное поле «some.value»: 42 , которое тоже добавлено специально.

1.Начнем с самого простого – корень, индексы, точки

$  — корень нашего json, просто выведет его целиком (то, что внутри главных скобок{}), как и при использовании пустой строки

Значение любого поля можно получить указав его ключ, но поиск начинается от корня, потому если поле вложенное, то нужно указать путь до него с использованием точки корень.поле1.поле2

class – вернет список из трех объектов-учеников, это единственное поле в корне нашего json, все остальные данные уже вложены в него, поэтому путь к ним нужно будет начинать с указания “class”. Если, например написать “name” то нам вернет null, так как в корне нет такого ключа. Вообще такой подход аналогичен работе с XPath – начинаем от корня и двигаемся к искомой информации вглубь документа.

Работа со списком (массивом) аналогично Java с некоторыми замечаниями – первый индекс привычно 0, выходить за границы списка нельзя, но можно использовать отрицательные индексы и слайсинг как в питоне.

securityservВажно! Все эти операции (индекс, слайсинг) применимы и к строке!

class[0] – вернет нам первый объект-ученик, а именно Иванова Ваню

class[-1] – вернет нам последний элемент списка то есть объект Сидорова Вани, аналог “class[2]

class[-2]  — вернет предпоследний элемент списка (и так далее до выхода за пределы массива), то есть Петрова. Такой же результат даст вызов “class[1]

class[0..1] – вырежет из списка и вернет элементы ОТ нулевого (по индексу) ДО первого включительно, то есть вернет 2 первых объекта списка  — Иванова и Петрова

class[1..2] – вернет последние два (для конкретно этого списка) объекта

Продолжаем погружение :

class[0].surname – получаем значение поля surname у самого первого объекта в списке, то есть получим “Ivanov”

class.name – вернет ВСЕ имена ([Ivan, Petr, Ivan]), то есть поля name у всех объектов списка. Обращаю внимание, что мы не указали никаких индексов, поэтому берется весь список и у каждого элемента ищется поле name. Если у какого-то из объектов такого поля нет — вмеcто него будет null, но в любом случае вернется список – это важно понимать.

class[0].address.house – вернет 12, то есть номер дома Вани Иванова. И так далее, используя индексы и точку можно двигаться вглубь структуры к любому из полей

securityservВажно! Попробуем получить уникальное поле some.value из последнего объекта, навскидку это

class[-1].some.value – ожидали 42, но получаем нулл! Дело в том, что ключ содержит точку и при стандартной записи GPath считает, что должно быть поле some, а в него вложено поле value, не находит его и возвращает нулл. Вот как нужно поступать в таких случаях:

class[-1].’some.value’ – использование апострофа экранирует ключ и подсказывает, что точка в данном случае –часть названия, возвращает искомые 42

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

2. Операция {it.поле условие}

class.find {it.name == ‘Ivan’} – вернет нам первый объект в котором есть поле name со значением Ivan, обратите внимание, что имя указано в апострофах, так как это строка, а не число.

securityservВажно! it – это внутренняя переменная, которую генерирует Gpath, как итератор, она поочередно получает значения всех объектов, которые есть на данном уровне. То есть, для примера выше, будут перебираться все объекты-ученики, вложенные в class и у них будет проверяться поле name

class.findAll {it.name == ‘Ivan’} – почти то же самое, но вернется список ВСЕХ объектов, удовлетворяющих условию. Думаю вы легко проведете аналогию с некоторыми методами вебдрайвера – возвращает первый элемент, возвращает все. Условия конечно могут быть не только равенство, но и неравенство «!=» и больше, меньше, все как в Java.

securityservВажно! Строка в Груви – это сравниваемый объект, к ней применимы операторы «>», «<», а также некоторые функции, которые в Java ассоциируются с числами и к строке не применимы!

class.findAll {it.name > ‘Ivan’} — вернет не ошибку, а объект Петрова Пети, потому что имя Petr>Ivan, буква P стоит дальше по алфавиту, чем I.

Если условие в фигурных скобках не указать, оставив лишь поле, то вернутся все элементы, где есть данное поле

class.findAll {it.name} — вернет все объекты, так как условия нет, а поле name есть у каждого

class.findAll {it.’some.value’}  -вернет только Сидорова, так как поле some.value есть только у него

Теперь рассмотрим более интересные функции и запросы.

3. Стандартные математические функции min(), max(), sum()

Применимы только к спискам, содержащим числа или строки, причем сумма для строк вернет конкатенацию

class[0].marks.min() – получаем минимальную оценку первого объекта в списке (Иванов), к сожалению это 2.

class.address.house.max() — получаем максимальный номер дома среди всех учеников, вернет 12 – дом Иванова

securityservК функциям min/max можно также применить выражение {it.поле} для нахождения элемента.

class.max {it.address.house} — вернет объект Иванова, так как у него самый большой номер дома. Важнейшая разница тут в том, что возвращается не само максимальное значение, а объект, который его содержит, что нужно чаще всего.

class.address.street.sum() -вернет конкатенацию названий улиц всех учеников то есть LeninMarxPobedy, так как улицы — это строки

class[-1].marks.sum() — вернет сумму оценок последнего ученика, то есть 14

Попробуем получить сумму всех оценок всех учеников:

class.marks.sum() — вместо ожидаемого числа, получаем список всех оценок [2, 3, 4, 4, 5, 5, 5, 3, 5, 3, 4, 2, 2, 3]! Это происходит из-за того, что class.marks вернет нам список списков или двумерный массив [[2, 3, 4, 4, 5], [5, 5, 3, 5], [3, 4, 2, 2, 3]] и операция суммирования в таком случае просто соберет все элементы в один список. Выходом будет или еще раз применить суммирование или сделать структуру данных плоской, используя flatten() функцию

class.marks.flatten().sum() — вернет ожидаемое число 50 и , на мой взгляд, читаемей, чем аналог «class.marks.sum().sum()»

4. Общие функции

size() – функция, возвращающая размер (количество полей, элементов, букв), применима к строке, списку, объекту. При этом для строки вернет количество букв, для списка количество элементов, для объекта количество полей. Не применимо к числу!

class.size() — ожидаемо вернет 3, то есть количество учеников в списке

class[0].name.size()  — вернет 4, то есть количество букв в слове Ivan(поле name первого элемента)

class[0].address.size() — вернет 2, то есть количество полей в объекте адрес (улица, дом) у первого ученика

unique() – функция, которая возвращает только уникальные элементы, применима только к спискам.

class.name.unique() — вернет список из двух уникальных имен, так как у нас два Ивана [Ivan, Petr]

class.unique() — вернет все 3 наших ученика, так как все они уникальны

class.marks.flatten().unique() — вернет список уникальных оценок [2, 3, 4, 5], например, чтобы убедиться, что никто из ребят не получал 1

5. Всем известные функции работы со строками endsWith, startsWith, contains

class.findAll {it.surname.endsWith(‘rov’)} — получим список всех учеников, у которых фамилия заканчивается на «ров», то есть Петрова и Сидорова

class.findAll {it.name.startsWith(‘I’)} — список всех, у кого имя начинается на I, то есть Сидорова и Иванова (у обоих имя Ivan)

class.findAll {it.name.contains(‘e’)} — вернет того, у кого в имени есть «е», то есть Петра Петрова

securityservВажно! Функция contains применима и к спискам:

class.findAll {it.marks.contains(2)} — вернет нам список двоечников, обратите внимание, что двойка без апострофов, так как оценки -это числа, а не строки

! – в значении НЕ применим к условиям, скажем если мы хотим увидеть кто у нас в классе еще не получал двоек

class.findAll {! it.marks.contains(2)} — вернет только Петрова, обратите внимание на восклицательный знак!

6. Функции преобразования.

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

reverse() – разворот, применимо к спискам и строкам

class[0].marks.reverse() — вернет [5, 4, 4, 3, 2] то есть перевернутый список оценок первого ученика

class[1].name.reverse() — вернет rteP, то есть перевернутое имя второго ученика

toInteger() – применимо к строкам, возвращает число из строки

class[0].age.toInteger() -вернет 13, то есть возраст первого ученика, но не строкой, а числом. Ниже будет более полезный пример использования данной функции

Collect – функция преобразования данных и сбора их в список,

class.collect {it.name.toUpperCase()} — просто как пример преобразования полученных результатов, вернет все имена из заглавных букв [IVAN, PETR, IVAN]

class.collect {it.age.toInteger()} — вернет список всех возрастов [13, 14, 14], причем это список именно чисел, а не строк! Скажем, если мы хотим найти максимальный возраст

class.collect {it.age.toInteger()}.max() — вернет 14, верный ответ

securityservПочему в данном случае нельзя использовать «class.age.max()»? Потому что в нашем случае это строки, а они сравниваются по алфавиту, а не как числа и если скажем поправить возраст одного из учеников на «4», то максимум вернет именно его, так как 4 стоит в алфавите дальше «1».

Если мы хотим найти самого младшего в классе, с учетом того, что возраст – строка

class.min {it.age.toInteger()} -вернет нам Иванова, которому 13

Еще обращаю внимание, что операции не терминальные, после поиска или преобразования мы можем еще применить функцию или перейти к полю

class.find {it.name.endsWith(‘an’)}.marks[-1] — получаем последнюю оценку первого найденного ученика с именем, заканчивающимся на «an»


Software-Testing.Ru

Реклама

XPATH, вверх и вниз по DOMу

xpath

Итак, мы посмотрели как писать локаторы XPATH, как писать CSS, договорились не копировать локаторы слепо и по возможности писать кратко и понятно.

Напоминаю алгоритм написания любого локатора:
1) Если есть, то используем уникальный id или имя класса
2) Если не выполняется пункт 1, то идем к разработчикам и просим его обеспечить
3) Если не выполняется пункт 2, то пытаемся использовать CSS, если можно написать коротко
4) Keep calm and use XPATH

Сегодня я покажу написание локаторов на небольшом и конечно искусственном примере, который показывает, что не всегда можно использовать CSS или другие локаторы. Перед нами сайт дорого во всех смыслах Ростелеком, с предложениями по интернет: https://spb.rt.ru/packages/tariffs

В тестах нам нужно нажать кнопку «Подключить» у тарифа с ценой 1010 рублей. Но мы при этом знаем, что тарифы часто меняются и данный тариф с его кнопкой может быть расположен в любом месте страницы, с любым id. (На самом деле задача реальная, но любые совпадения случайны)
1
Исходя из условий, мы не можем просто получить список кнопок с надписью «Подключить» и кликнуть вторую, в любой момент она может стать третьей. Мы также не можем быть уверены, что id тарифа не изменится, кроме того, было бы круто иметь один общий локатор, который бы нашел кнопку «Подключить» у тарифа по его цене. Класс также не уникален и возвращает список элементов.

2

Что же есть уникальное, к чему можно прицепиться? Цена тарифа! У всех тарифов разные цены, причем в данном случае мы как раз ищем цену 1010 рублей.3

У элемента цены есть уникальный атрибут data-fee=’1010′ по которому мы можем точно найти его, причем только его одного.

//*[@data-fee=’1010′]  — мы точно находим нужную цену. Теперь нужно двигаться к кнопке Подключить по дереву DOM и тут есть два варианта:

1) подняться от цены к ее родителю (classtariff-desc__cost_m-cell«), от него получить следующий сестринский элемент (classtariff-desc__cost_l-cell«) и уже в нем получить кнопку. Получается один шаг вверх и два вниз

4

//*[@data-fee=’1010′]/.. — переходим к родителю

//*[@data-fee=’1010′]/../following-sibling::div — уходим вниз к сестринскому элементу

//*[@data-fee=’1010′]/../following-sibling::div/a — получаем нашу кнопку

2) так как кнопка Подключить и цена входят в состав одного блока, то гораздо лучше от цены найти ее предка (не непосредственного родителя!), который является этим блоком (classtariff-desc__cost tariff-desc__cost_reset js-price-blocks«) у него просто взять вложенный элемент кнопки. Получается всего два шага -вверх и вниз

5

 

//*[@data-fee=’1010′]/ancestor::div[contains(@class,’js-price’)] — получаем предка(ancestor), с классом содержащим js-price

//*[@data-fee=’1010′]/ancestor::div[contains(@class,’js-price’)]//a — у нашего элемента просто ищем вложенную ссылку (нашу кнопку).

Можете убедиться, что оба локатора являются уникальными, и меняя цену, можно получать локаторы кнопки у всех остальных тарифов по их цене.

Я чаще всего работаю с элементами во вкладке Элементы инструментов разработчика браузера, чтобы видеть соседние элементы, предков и вообще дерево DOM. Но иногда удобнее проверять локаторы во вкладке Консоль, набрав команду вида $x(«выражение») , там видно только конкретно найденные элементы

6

 

Надеюсь вам никогда не придется идти в алгоритме поиска элемента дальше 1 пункта.

 Software-Testing.Ru

Автоматизация без Selenium

se2

Нет, я не собираюсь ниспровергать Selenium или предлагать вам абсолютно новый удобнейший инструмент, которым можно Selenium заменить, речь пойдет о трагичной привычке очень многих сводить всю автоматизацию к умению работать с WebDriver.

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

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

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

Казалось бы, в чем проблема? Молодые (и не очень) ребята проходят некие курсы по Selenium или занимаются самостоятельно по книгам, блогам, видео и выкладывают свои резюме с гордыми записями в стиле Автоматизатор, QA Automation или даже Software Developer in Test (в этом месте слышен шлепок facepalm’а разработчиков). Но потом не могут найти работу, не проходят собеседования или, что хуже, приходят на работу, но не могут с ней справиться.

Давайте разбираться, что же тут не так, ребята же 2 недельные курсы прошли, книжку прочитали…

no-ya-zhe_28002397_orig_

  • web-приложение — это не только и не столько графический интерфейс (далее ГУИ). По сути ГУИ — это только фасад с которым взаимодействует ваш пользователь, самое важное как всегда — это информация, ее трансформация, обработка и так далее. Крайне сложно представить приложение, где самое важное – это графика в браузере, а не стоящая за ней информация, которая этой графикой визуализируется. Даже в браузерных играх, которые я имел удовольствие тестировать, графика конечно важна, но гораздо важнее правильность обработки всего того, что происходит за кадром. Проще говоря пользователь еще может смириться с тем, что выстрел его рельсы был не того цвета, но даже не думайте начислить ему на 1 кристалл меньше или при этом выстреле нанести дамаг ниже ожидаемого. Согласитесь, что и пользователи ваших приложений скорее могут смириться с поехавшей версткой или неплавными переходами меню, чем с некорректными данными или с их отсутствием. Можно конечно сверять данные и с помощью Selenium, но для этого есть более простые и надежные способы.
  • Про критичную важность и маст-хев юнит-тестов я даже не буду говорить, это должно быть всем ясно, но в чем их особая сила? Они тестируют максимально малый участок кода, максимально близко к нему и максимально быстро, тем и хороши. В чем проблема Selenium-тестов? Между нашим кодом и приложением есть посредники, а именно API самого Selenium и собственно используемый браузер, то есть к проблемам (возможным) нашего кода и/или приложения добавляются проблемы как программные так и с временем выполнения от Selenium и браузера. В эту цепочку кроме того часто встраивают еще и обертку над Selenium или прокси. О надежности и скорости выполнения таких тестов думаю все в курсе.
  • ГУИ –самая простая и логичная вещь в плане проверки простыми тестировщиками (я не люблю терминов «ручные» тестировщики, так как мы их не приручили, не люблю и «ручники», так как ногами тоже никто не тестирует, в данном случае под «простыми тестировщиками» я имею в виду тех, кто не использует автоматизацию на языках программирования). Среди простых тестировщиков есть те, кто умеют работать с БД или скажем SOAP UI(Postman), но проверять ГУИ, согласитесь можно научить кого угодно, да и это уже все умеют, достаточно дать теорию. То есть многие вещи из наших тестов могут и должны проверять простые тестировщики, в том числе те, кто знаком с исследовательским тестированием. А с помощью кода проще и логичнее проверять те стороны продукта, которые простым тестировщикам недоступны в силу отсутствия знаний и соответствующих инструментов. То есть простые тестировщики тестируют черный ящик, а уж автоматизатор должен лезть внутрь этого ящика и проверять серый или прозрачный ящик.
  • Современный ГУИ уже редко пишется руками и на чистом javascript, все чаще его строят на основе имеющихся фреймворков. А элементы и блоки этих фреймворков уже многократно протестированы как их разработчиками, так и многими пользователями этих фреймворков по всему миру. Дополнительно проверять скроллы, календари и кнопки в них нет смысла, важны только данные, которые придут и как они будут обработаны.

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

а) Selenium тесты должны быть на вершине пирамиды тестирования и проверять совсем немного сценариев;

б) больший упор в ГУИ должен быть отдан на проверку простым тестировщикам, исследовательское тестирование rules!

в) должны быть тесты API приложения и/или его частей, которые можно проверить изолированно

г) приложение должно быть максимально покрыто юнит-тестами (да, я знаю, что их не пишут)

в итоге наш автоматизатор должен появляться в пункте «в» по максимуму и совсем немного и в последнюю очередь в пункте «а»

Что же должен уметь и знать автоматизатор по факту, а не после просмотра видео на ютубчике:

securityserv1) Главное правило автоматизации тестирования «Изучай язык программирования, а не автоматизацию на нем!» (с)

Автоматизация тестирования, в том числе с помощью Selenium – это лишь малая часть всего того, что есть в данном языке программирования. Заранее делая узкой свою область знаний (только Selenium) автоматизатор отказывается от большого числа технологий и инструментов, которые в языке есть. Кроме того, при появлении проблем и задач, не связанных с Selenium, автоматизатор впадает в ступор, так как не знает как это решать, более того начинает придумывать костыли в силу этого незнания. Владея только одним инструментом, а не всеми инструментами языка, автоматизатор и все проблемы пытается решать им, что не всегда нужно и логично.

Для  языка Java автоматизатор должен знать:

— основы (да-да!) примитивы, объекты, как работает сборщик мусора, модификаторы доступа

— ООП, не просто прочитать и выучить определения, а понимать что это, как реализуется в коде, почему ругают наследование, в чем сила полиморфизма, для чего нужна композиция

— коллекции! Без этого просто невозможно, как минимум List, Set, Map и их основные реализации

— многопоточность, особенно понимание блокировок, взаимодействия потоков, общих данных

— работа с http запросами, на чистой Java или используя библиотеки.

— работа с БД с помощью jdbc

— паттерны (шаблоны), нужно знать хоть что-то еще кроме ПейджОбджект и Синглтон, рекомендую Команда, Стратегия, Декоратор, Строитель

— логирование

— нужно знать еще один тестовый фреймворк. Если чаще работаете с TestNG то знать и Junit и т.п.

— умение писать юнит-тесты (для своего кода)

— парсинг данных и все что с этим связано

— умение работать со сборщиками maven/gradle

— Java 8 фичи – это обязательно! Стримы, лямбды, функциональные интерфейсы, методы по умолчанию

— вы будете смеяться, но нужно знать и уметь работать с Selenium: локаторы, логи, ожидания, actions, вот это вот все

Для других языков программирования нужны аналогичные знания

securityserv2)  Не помню у кого прочел (или Болтон или Виноградов) но все мы тестировщики изначально, а все остальное, в том числе автоматизация – это уже не так важно. Болтон вообще считает разделение на автоматизаторов и простых тестировщиков вредной. Это я к тому, что знание всего из пункта 1 не освобождает автоматизатора от знаний теории тестирования, цикла разработки, умения писать баги, проводить исследовательское тестирование.

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

— понимание как работает браузер, понимание самого протокола http, кодов ответа, rest.

— знание основ SQL

—  умение работать с системой контроля версий

— понимание что такое json, xml

Проблема, которую я хотел описать не только в низком уровне «автоматизаторов». Что важно, сами компании  (наши работодатели) ассоциируют автоматизацию с Selenium и не ищут автоматизатора там, где он бы пригодился, потому что не знают что он ДОЛЖЕН решать и такие проблемы, а не только кликать в браузере. То есть с одной стороны мы имеем не самых хороших кандидатов, а с другой стороны формирование мнения (частично справедливого) о невысоком уровне автоматизаторов и их ненужности на серьезных проектах, где важнее данные, а не картинка. И эту ситуацию нужно менять, в первую очередь начиная с себя и своих умений.

Надеюсь, эта статья толкнет некоторых на пересмотр своего резюме, а главное на изучение дополнительных технологий. Но мне будет достаточно и «Спасибо, Кэп!»

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

Software-Testing.Ru

Регистрация и забытый пароль с временными ящиками почты

BBrbB4i.img

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

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

В итоге ищем бесплатные сервисы, предоставляющие не только почту, но и API для нее. Платные я сразу же не рассматривал и в итоге нашел сходу post-shift.ru, а также Guerilla Mail. В обоих случаях мы имеем очень простое API (только GET запросы, с параметрами в адресной строке), позволяющее создать новый почтовый ящик, проверять список писем, получать текст письма (для того чтобы получить вожделенную ссылку для подтверждения регистрации или восстановления пароля).

Особенности post-shift:

— создает обычный ящик на 10 минут, можно продлить время, если оно уже не истекло

— можно создать ящик с заданным именем на 1 час

— позволяет получать ответы как в виде json так и простым текстом

— можно очистить ящик, но нельзя удалить конкретное письмо

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

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

— все ответы приходят только в формате json

— позволяет удалять письма из ящика, но нет возможности очистить ящик сразу

— позволяет задать имя для ящика, то есть текст который идет до знака @

— в тексте письма удаляет iframe, javascript код, апплеты

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

Лично мне понравились оба сервиса, у них есть различия из которых и можно исходить делая выбор

Для своего удобства накидал небольшие модули для использования API этих сервисов, правда на языке Python – так и быстрее и код короче, чем на Java. Можно посмотреть вот тут

Software-Testing.Ru

Сравниваем скорости поиска элементов

cross

Я уже не раз писал, как и многие до меня, что несмотря на распространенное мнение поиск с помощью XPATH  не медленнее поиска с помощью CSS локаторов. Выдалась свободная минутка и решил потестить немного скорость поиска элемента с помощью разных локаторов и в разных браузерах: Chrome, Firefox, InternetExplorer. Естественно, замеры относительны и воспроизводимы с теми же цифрами только на моей машине, однако есть интересные тенденции.

В Python есть удобная утилита timeit в стандартной библиотеке, к сожалению не смог найти что-то похожее на Java, поэтому просто накидал метод который 100 раз подряд находит элемент по заданному  локатору и выдает среднее затраченное время.

Поиск проводил на странице https://google.ru  и по идее искал я один и тот же элемент, просто задавал различные локаторы для него. Время в миллисекундах.

Браузер

XPATH

CSS

ID

CLASS

NAME

‘//form[@class=’tsf’]’

‘form.tsf’

‘tsf’

‘tsf’

‘f’

Chrome 6-7 6-7 7 6 6-7
Firefox 3-5 4-5 3-5 3-4 3-4
IE 50 (!) 78(!!) 77-80 77-79 76-79

Я конечно знал, что Chrome быстрый, в последнее время я работаю только с ним, но не ожидал, что Internet Explorer будет настолько медленнее, искренне сочувствую тем, кому приходится тестировать УИ на нем!

Второй неожиданностью стало то, что Firefox пусть и немного, но побыстрее Chrome.

Плюс достаточно интересная вещь, что поиск через XPATH в IE вполовину быстрее всех остальных видов поиска, то есть тем бедолагам, что вынуждены его использовать, рекомендую все локаторы переписать на XPATH.

Естественно, замеры я проводил не один раз и в разном порядке, как 1  локатор за сессию, так и все подряд.

Не успокоившись на этом, запустил консоль Python, который сейчас активно осваиваю, и воспользовался утилитой timeit, которая выдает среднее время за указанное количество повторов функции. Локаторы и сайт естественно остались теми же. Цифры были уже другие, но закономерности сохранились, а именно: Firefox немного быстрее Chrome, Internet Explorer на порядок медленнее обоих браузеров, но при этом поиск по XPATH у него работает гораздо бодрее всех остальных.

Итак, выводы:

1) поиск по XPATH не быстрее CSS, а для браузера Internet Explorer даже наоборот, есть смысл все локаторы писать только на XPATH!

2) Chrome не самый быстрый браузер, пусть и совсем чуть-чуть, но Firefox ищет побыстрее, видимо обновления пошли на пользу Лисе

3) Internet Explorer must die!

4) поиск по id или class имеет только преимущество в удобстве написания локатора, если есть уникальные id или имя класса. Вопреки некоторым встреченным в интернете рассказам, поиск по id или class не быстрее остальных видов поиска

Вот тут пожно почитать про xpath, а вот тут про css!

Software-Testing.Ru

Еще немного о WebDriver

item1313068_600px

Решил добавить еще немного информации, о которой не все, как мне кажется, знают и не на всяком курсе рассказывают. Кому-то может показаться любопытным:

securityservСуществует 2 способа паузы, реализуемой через Thread.sleep() непосредственно в библиотеке вебдрайвера. Кроме описанного вот тут, есть еще интерфейс Sleeper, правда все его удобство кончается на возможности задания точного периода ожидания, от необходимости обрабатывать InterruptedException вы не избавлены!

Sleeper.SYSTEM_SLEEPER.sleep(new Duration(3,TimeUnit.SECONDS));

Это чисто для ознакомления, надеюсь вы не будете это использовать в коде тестов для ожидания элементов. Лично мне не ясно для чего такое дублирование кода в библиотеке и почему в Actions при вызове pause() не вызывать Sleeper.

securityservЕсть специальный класс для безопасного использования вебдрайвера в многопоточном тестировании, который рекомендуется для любого многопоточного использования.

webDriver = ThreadGuard.protect(webDriver);

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

Внимание! Этот класс не решит проблем кривой архитектуры при многопоточности или совместного использования ресурсов, он лишь бросает исключение при любой попытке обращения к драйверу из других потоков.

То есть, это скорее способ дополнительно гарантировать себе, что драйвер используется безопасно, как только видите исключение в стиле

Exception in thread "Thread-0" org.openqa.selenium.WebDriverException: 
Thread safety error; this instance of WebDriver was constructed on thread main (id 1) and is being accessed by thread Thread-0 (id 67)
This is not permitted and *will* cause undefined behaviour

нужно принимать меры.

securityservВ библиотеке WebDriver есть свой класс для работы с файлами и директориями FileHandler. Его возможности вы можете посмотреть в документации, а я приведу лишь один пример. Скажем для создания скриншота в основном используют класс из google-guava или apache-commons, но это можно сделать и так, не используя сторонних библиотек

public void make(String fileName) {
  File scrFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE)
     try {
       new FileHandler().copy(scrFile, new File(fileName));
     } catch (IOException e) {
        //логируем и т.д.
     }
}

securityservКроме всем известных способов задания метода поиска By, есть и методы работающие сразу с набором элементов — ByAll и ByChained.

Применяя класс ByAll, мы собираем все элементы, которые соответствуют указанным локаторам. Внимание, будут найдены все элементы, удовлетворяющие каждому из локаторов, а не им всем. Это может быть удобно, если нам нужен список различных элементов: к примеру(ну так случилось!) у вас на странице много кнопок, но локаторы у них разные. С помощью этого класса, можно получить список всех кнопок, перечислив все локаторы.

driver.findElements(new ByAll(By.id("a"), By.id("b")));

— вернет список элементов, в котором будут все, соответствующие локатору id=’a’, а кроме того все, соответствующие локатору id=’b’. То есть это аналогично результату, если бы мы нашли список элементов id=’a’, нашли список id=’b’ и смержили два списка в один.

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

driver.findElements(new ByChained(By.id("a"), By.id("b")));

— вернет список элементов с локатором id=’b’ (!), которые при этом потомки (вложены) в элемент(ы) с локатором id=’a’.

Если в первом варианте (ByAll) мы можем спокойно перепутать локаторы местами – все равно вернет все элементы, то во втором случае (ByChained) нужно быть точным в порядке локаторов, иначе вернется пустой список.

securityservЛюбителю ОС Windows будет интересно узнать, что в библиотеке есть класс с многозначительным названием WindowsUtils. Как и большинство (все?) утилитных классов он содержит статичные методы, нет необходимости создавать объект класса. Умеет он не так уж много, из полезного можно определить является ли текущая ОС действительно виндой

WindowsUtils.thisIsWindows();  // возвращает true рабам Микрософт :-)

И если работаем именно в Окнах, то можно, к примеру, убить процесс по его имени или id. Думаю все в курсе, что в случае падения теста по исключению (не ассерту) или если забыли вызвать driver.quit()  — процесс драйвера продолжает висеть в работающих, даже если закрыть браузер. С помощью утилит, его можно убить, скажем перед началом тестов (чтобы убедиться, что нет в процессах никаких висящих драйверов)

WindowsUtils.killByName("chromedriver.exe"); //убивает хромдрайвер, если он есть в процессах

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

 

Вот тут можно почитать о других не самых известных фишках Selenium

Software-Testing.Ru

Чтение документации как средство от граблей и костылей.

se

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

securityservПоиск элемента по тексту при помощи CSS. У меня за время использования Selenium скопилась целая библиотека книг, интересных статей и видео о использовании WebDriver и во многих из них, причем достаточно свежих, упоминается CSS локатор в стиле a:contains(‘text’) . То есть люди переписывают этот локатор, даже не проверяя, работает ли он. Но данный локатор не работает! В спецификации W3C можно увидеть пункт с довольно зловещим номером.

666

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

securityservБудем честны, хоть раз каждый из нас использовал паузу в стиле Thread.sleep(), а кто не признается, тот и сейчас пользуется 🙂 Как я уже писал – это плохой способ ждать чего-либо и не стоит этим злоупотреблять в своем коде, но если уж решили использовать, то не нужно писать развесистый куст с Thread.sleep() и перехватом InterruptedException. Библиотека WebDriver содержит возможность подождать, впрочем внутри там тот же самый sleep()

new Actions(driver).pause(1000).perform();

— так короче и не бросается в глаза использование слипов.

Стоит отметить, что при написании этой статьи, я обнаружил, что при использовании этого метода в паре со слушателем событий вебдрайвера и хромдрайвером, в глубинах вебдрайвера падает исключение UnsupportedOperation Exception, которое вверх не пробрасывается, но отлавливается упомянутым слушателем. Пауза при этом работает, видимо апи Actions еще не полностью реализовано в хромдрайвере. Но это не мешает использовать паузу.

securityservWebElement не хранит свойств элемента страницы. Работая с Java, мы привыкли, что полноценный, сформированный объект хранит в себе свои поля, предоставляет публичные геттеры и не зависит от других объектов. Скажем ArrayList со значениями, полученными откуда угодно, доступен независимо от текущей доступности источника данных. Но WebElement  для запроса тега, текста или других параметров использует запрос к вебдрайверу и если браузер закрыт или изменилась страница, был переход на другой УРЛ, то падает исключение.

Пример:

WebElement element = driver.findElement(By.id(“one”));
driver.get(“http://anotherPage.com”);
System.out.println(element.getText()); // тут падает StaleElementReferenceException так как элемента нет на странице.

Казалось бы, объект element создан и все знает о своем состоянии, но по факту это не так, любые параметры получаются запросом к драйверу. И в коде

if (element.isDisplayed()) {
    System.out.println(element.isDisplayed());
}

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

securityservУ вебдрайвера есть АПИ работы с логами браузера, что порой очень удобно – можно после каких-то действий проверить нет ли ошибок в консоли. Но работает это апи довольно неочевидным образом – при запросе логов они удаляются (для запрошенного уровня browser, driver и так далее) и заново их получить (те же самые логи) вы не можете!

То есть допустим два простых метода получения ошибок браузера и проверки наличия таких ошибок

public List consoleErrors() {
        return driver.manage().logs().get("browser").filter(Level.SEVERE);
    }

public boolean isConsoleErrorsExists(){
        return consoleErrors().size() > 0;
}

Если мы воспользуемся методом isConsoleErrorsExists() то даже если ошибки и были, мы их уже не увидим – логов больше нет, мы их запросили внутри метода, вызвав consoleErrors(). Используйте логи сразу при получении, второго шанса не будет!

securityservПри использовании поиска по локатору className будут найдены не только все элементы у которых класс точно равен искомому, но и все те, где искомое лишь один из классов элемента. То есть при поиске By.className(«link») будут найдены и элементы, где class=’link’, и все те, кто содержит link, например class = ‘link b-sethome__link i-bem link_js_inited’. Это нужно учитывать.

securityservИногда нужно проверять какие то ссылки со страницы, или новые урл на предмет доступности, я в свое время накидал простенький код для проверок с использованием средств Java. Но в библиотеке Selenium уже есть очень полезный в подобных случаях класс UrlChecker, позволяющий проверять доступность ссылок, а конкретнее он ждет в течение заданного времени, что УРЛ вернет код ответа 200, означающий доступность страницы. Это можно сделать так

 new UrlChecker().waitUntilAvailable(timeOutInSeconds, TimeUnit.SECONDS, new URL(“http://site.com”));
// останется только обработать TimeOutException

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

((Locatable) element).getCoordinates().inViewPort();

Магия происходит именно в методе inViewPort(); согласно спецификации вместо него с тем же эффектом можно вызвать и onScreen() – но он пока не реализован в драйверах, в отличие от inViewPort().

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

И всех с прошедшими праздниками!

Software-Testing.Ru

Отладка Selenium тестов в jshell, не забывая про Idea

java9

Многие уже в курсе, что с Java 9 к нам пришла и джава-консоль jshell, позволяющая быстро выполнять джава-код без лишних телодвижений. Например для извечного «Hello, world!» не нужно создавать класс, а в нем метод main, просто пишем в консоли System.out.println(«Hello, world!»).

Jshell как уже говорилось идет в составе 9 Java, при условии верно прописанных переменных окружения просто пишем в командной строке jshell  и немного ждем, запуск не мгновенный.

По умолчанию в jshell импортировано несколько пакетов, которые можно посмотреть командой /imports, все остальное при необходимости нужно импортировать самим.

imp

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

file

Но ближе к делу! Консоль можно использовать для отладки ваших тестов на  Selenium потому что она позволяет:

  • не писать излишнего кода, не создавать классов, не смотреть пока на исключения
  • в случае падения исключения продолжать выполнение, предприняв необходимые действия (изменить урл, локатор  и т.п.) при этом браузер жив и продолжает работать
  • добавлять новые переменные в код
  • в результате вывести весь ваш код командой /list  и скопировать его в ваш тест
  • демонстрировать простые действия или обучать работе с Selenium

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

jshell —class-path C:\drivers\selenium-server-standalone-3.8.1.jar

path

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

chrome

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

Завершив, выводим весь наш сценарий

list

И на сладкое! Еще удобнее работать с jshell в среде разработки Intellij Idea! Просто открываем терминал Идеи, запускаем там jshell с нужными настройками и готово. Тут есть большое удобство в том, что можно тут же код копировать из Идеи в консоль и обратно.

idea

Не забывайте почитать документацию по jshell, там много интересного.

P.S. Я не предлагаю вам отлаживать или писать тесты именно так, скорее продемонстрировал еще одну возможность, которая, на мой взгляд, может быть удобна в определенных ситуациях.


Software-Testing.Ru

Ожидания в стиле Java 8

083402_kx1b2e8athumbnail

На мысль навел просмотр видео, в котором Сергей Король справедливо напомнил, что многие из нас «упираются» собственно в средства Селениума и не используют всю силу благословенной Java. Уже вышла 9 версия, а многие еще недостаточно освоили 8, часто ли вы используете в проекте лямбды, стримы, функциональные интерфейсы?

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

Сначала пишем небольшой enum, в котором используем все наиболее часто применяемые ожидания. Вот весь код, далее обсудим:

import org.openqa.selenium.By;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.ExpectedConditions;

import java.util.function.Function;

public enum WaitConditions {
   visible   (ExpectedConditions::visibilityOfElementLocated),
   exist     (ExpectedConditions::presenceOfAllElementsLocatedBy),
   clickable (ExpectedConditions::elementToBeClickable),
   invisible (ExpectedConditions::invisibilityOfElementLocated);

   WaitConditions(Function<By, ExpectedCondition> type) {
      this.type = type;
   }

   public Function<By, ExpectedCondition> getType() {
      return type;
   }

   private final Function<By, ExpectedCondition> type;
}

Тип нашего enum  это функциональный интерфейс Function, который получает на вход By (наш локатор) и возвращает ExpectedCondition то есть те самые условия ожидания.

 visible (ExpectedConditions::visibilityOfElementLocated),
 exist (ExpectedConditions::presenceOfAllElementsLocatedBy),
 clickable (ExpectedConditions::elementToBeClickable),
 invisible (ExpectedConditions::invisibilityOfElementLocated);

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

private final Function<By, ExpectedCondition> type;

Обратите внимание на ? — это необходимо для использования invisibilityOfElementLocated, так как он в отличие от остальных методов возвращает ExpectedCondition.

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

public boolean waitFor(By locator, WaitConditions conditions) {
   WebDriverWait wait = new WebDriverWait(driver, defaulTime);
   try {
      wait.until(conditions.getType().apply(locator));
      return true;
   } catch (TimeoutException ex) {
      //делаем скриншот, логируем и  т.д.
      return false;
   }
}

defaultTime — это переменная int, сколько конкретно секунд ждать по умолчанию, при желании ее можно менять, driver это конечно же наш WebDriver. Вот тут все происходит wait.until(conditions.getType().apply(locator)); — наш локатор используется для ожидания соответствующего типа.

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

waitFor(By.id("loginButton"), visible);
или
waitFor(submitButton, clickable);

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

Software-Testing.Ru

Headless Chrome вместе с Selenium WebDriver — для нетерпеливых

google_now_in_chrome_canary_release_nemmfq

Кто еще не в курсе, начиная с 59 версии в браузер Хром будет введена возможность запуска в headless-режиме, то есть без создания визуального окна браузера. Это позволит прогонять тесты быстрее (теоретически) и с меньшими затратами ресурсов, а главное — позволит запускать тесты на системах без графической составляющей. Не беспокойтесь — возможность делать скриншоты никак не пострадает.

Естественно, кроме самого браузера необходимо дождаться и новой версии chrome driver (текущая 2.29). Но если ждать не хочется, а хочется уже сейчас посмотреть и попробовать, то вот простой рецепт (проверялось для chrome driver 2.29 на Windows 10)

Скачиваем и устанавливаем Chrome Canary, который поддерживает все новые функции будущей версии Хрома. Не беспокойтесь, установка идет в отдельную папку по умолчанию и ваш родной текущий Хром браузер никак не пострадает. Сразу запоминаем или копируем путь установки.

Стандартно указываем путь к нашему драйверу хром

System.setProperty("webdriver.chrome.driver", System.getProperty("user.dir") + "/vendors/chromedriver.exe");

в данном случае, у меня драйвер лежит в папке проекта, в директории vendors.

Далее указываем хром проперти для использования headless режима

ChromeOptions options = new ChromeOptions();
options.setBinary("C:\\Users\\admin\\AppData\\Local\\Google\\Chrome SxS\\Application\\chrome.exe");
options.addArguments("--headless");

в методе setBinary мы указываем путь к расположению нашего Chrome Canary, ну и гвоздем программы устанавливаем аргумент —headless, который говорит сам за себя.

далее, опять же по стандарту, просто создаем объект браузера

driver = new ChromeDriver(options);

Можно запускать!

Единственный момент, который я сразу обнаружил — невидимое окно браузера всего размером 800х600, видно по скриншотам. Кому то может это и не важно, а у нас приложение меняет некоторые элементы в зависимости от размеров окна. Поэтому, нужный размер окна устанавливаем вот так

options.addArguments("window-size=1800x900");

где 1800х900 это размер, нужный вам.

Родные методы driver.manage().window().maximize(); или driver.manage().window().setSize(); тут не сработают, так как chrome driver все еще 2.29  и видимо пока не может использовать эти операции с headless браузером.

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

Software-Testing.Ru