TL;DR

Введение
Итак, руби-рельсы, браузерное тестирование, селениум, капибара и призма.
Про селениум и капибару не буду говорить, думаю, кто занимается тестированием, про это всё знают и без меня, а вот про призму хочу сказать пару слов.
SitePrism - это DSL (Domain Specific Language), который дает описание веб-страницы, удобное для проведения тестирования. SitePrism позволяет тестировщику структурно описать страницу, выделив части, подлежащие тестированию, и опустив несущественное.
Пример. Корзина магазина:

Описание для корзины, изображенной выше:
class Cart < SitePrism::Page set_url '/cart.html' element :header, 'h1' # Заголовок: "Shopping cart" sections :cart_items, 'div.cart_items' do # Массив из товаров (sections - во множественном числе) element :name, 'div.item-name' # Имя одного товара element :image, 'img.item-image' # Картинка товара element :price, 'div.item-price' # Цена за единицу element :quantity, 'div.item-quantity' # Кол-во element :total, 'div.item-total' # Стоимость выбранных товаров end section :checkout, 'div.checkout' do # Раздел внизу с кнопкой (section - в единственном числе) element :total, 'div.checkout-total' # Общая соимость заказа element :checkout_button, 'button.checkout-button' # Кнопка end end
Даже человек без подготовки, поглядев на картинку и описание, легко поймёт, что чему соответствует.
Имея подобное описание, можно написать тест вроде такого:
describe 'Cart' do it 'is correct' do page = Cart.new page.load # Проверили заголовок expect(page.header.text).to eq('Shopping Cart') # Убедились, что товара 2 expect(page.cart_items.size).to eq(2) # Проверили первый товар expect(page.cart_items[0].name.text).to match('Cup') expect(page.cart_items[0].quantity.value).to match('1') expect(page.cart_items[0].image[:src]).to match('cup.png') expect(page.cart_items[0].price.text).to match('19.00') expect(page.cart_items[0].total.text).to match('19.00') # Проверили второй товар expect(page.cart_items[1].name.text).to match('Cap') expect(page.cart_items[1].quantity.value).to match('2') expect(page.cart_items[1].image[:src]).to match('cap.png') expect(page.cart_items[1].price.text).to match('24.00') expect(page.cart_items[1].total.text).to match('48.00') # Проверили подвал корзины expect(page.checkout.total.text).to match('67.99') expect(page.checkout.checkout_button.text).to match('Checkout') end end
Скорее всего, это типичная картина проверки страницы в тесте. Очевидно, тест содержит избыточные, дублирующиеся слова, замедляющие как написание, так и чтение. Да, можно сократить, введя переменные, примерно так:
item = page.cart_items[0] expect(item.name.text).to match('Cup') expect(item.quantity.value).to match('1') expect(item.image[:src]).to match('cup.png') expect(item.price.text).to match('19.00') expect(item.total.text).to match('19.00')
Но всё равно жирно и многословно.

gem prism_checker
Я решил, что можно же просто взять призму и сказать, чего мы от нее ждём, а ждём мы соответствие такой вот структуре:
{ header: 'Shopping Cart', cart_items: [ { name: 'Cup', image: 'cup.png', price: '19.00', }, { name: 'Cap', image: 'cap.png', price: '24.00', } ], checkout: { total: '67.00', checkout_button: 'Checkout' } }
Вся необходимая информация есть, минимальное количество слов, в тесте такая приятная легкость образовалась. Никакого жира, один рельеф, осушили по-максимуму.
Эту идею я реализовал в gem-е PrismChecker, который позволяет коротко описывать, что мы ожидаем от призмы. Вот так:
# RSpec-версия expect(page).to be_like( header: 'Shopping Cart', cart_items: [ { name: 'Cup', image: 'cup.png', price: '19.00', }, { name: 'Cap', image: 'cap.png', price: '24.00', } ], checkout: { total: '67.00', checkout_button: 'Checkout' } )
# MiniTest-версия assert_page_like(page, header: 'Shopping Cart', cart_items: [ # ... ], checkout: { # ... } )
Иными словами, PrismChecker проверяет призму на соответствие некоторому хэшу.
Грубо говоря, element-у ставится в соответствие строка, section-у - хэш, а elements и sections проверяются с помощью массива.
# element assert_page_like(page, header: 'Shopping Cart') assert_page_like(page.header, 'Shopping Cart') # section assert_page_like(page, checkout: { total: '67.00', checkout_button: 'Checkout' }) assert_page_like(page.checkout, total: '67.00', checkout_button: 'Checkout' ) # elements, sections assert_page_like(page, cart_items: [ { name: 'Cup', price: '19.00', }, { name: 'Cap', price: '24.00', } ])
В случае ошибок будет выдано сообщение такого вида:

Подробности
Установка
для RSpec:
# Gemfile gem 'prism_checker_rspec'
# spec_helper.rb require 'prism_checker_rspec'
для MiniTest:
# Gemfile gem 'prism_checker_minitest'
# test_helper.rb require 'prism_checker_minitest'
Примеры
Проверка секции и двух дочерних элементов:
assert_page_like(page, checkout: { total: '67.00', checkout_button: 'Checkout' })
Если первый аргумент является классом SitePrism::Page, то при тестировании вначале будет выполнена проверка, что страница загружена. Элементы и секции сначала проверяются на видимость, потом на соответствие:
assert_page_like(page, button: 'Button') # assert(page.loaded?) # assert(page.button.visible?) # assert_match(page.button.text, 'Button')
Если элемент является изображением, то проверяемая строка будет сравниваться с атрибутом src. Для input и textarea проверяется value. Checkbox или radio можно проверить следующим образом:
assert_page_like(page, checkbox: true) assert_page_like(page, checkbox: {checked: true})
element можно сравнивать не только со строкой, но и с хэшем:
assert_page_like(page.image, src: 'logo.png', class: 'logo', alt: 'Logo')
Проверить, что элемент/секция видимы/невидимы или отсутствуют:
assert_page_like(page, header: :visible, checkout: :invisible ) assert_page_like(page, checkout: :absent)
Элементы и секции вначале проверяются на то, что содержат правильное количество записей:
assert_page_like(page, items: [ 'Item 1', 'Item 2' ]) # assert(page.loaded?) # assert_equal(page.items.size, ["Item 1", "Item 2"].size) # ...
Элементы и секции можно проверять просто на размер:
assert_page_like(page, items: 2) # assert(page.loaded?) # assert_equal(page.items.size, 2)
Со строкой можно сравнивать не только element, но и page, section, sections, elements:
assert_page_like(page, 'Shopping Cart') # assert(page.loaded?) # assert_match(page.text, 'Shopping Cart')
Вместо строки можно использовать регулярные выражения:
assert_page_like(page, /Shopping Cart/)
Если нужно проверить, что текст элемента есть пустая строка, то может возникнуть проблема. По умолчанию проверяется вхождение подстроки, а пустая строка входит в любую строку:
assert_page_like(page, header: '') # Всегда сработает, даже если header не пуст
Эту проблему можно решить с regexp или специальной проверкой:
assert_page_like(page, header: /^$/) assert_page_like(page, header: :empty)
По умолчанию строки сравниваются путем поиска подстроки, но можно настроить гем на точное соответствие:
assert_page_like(page, header: 'Cart') # будет проверено вот так: header.include?('Cart') PrismChecker.string_comparison = :exact # Точное соответствие assert_page_like(page, header: 'Cart') # будет проверено вот так: header == 'Cart'
Еще примеры можно найти на странице проекта, на гитхабе.
Итого
Gem PrismChecker упрощает написание и чтение браузерных тестов и бережет нервы тестировщиков.
Буду рад услышать отзывы, кртику, баги.
