Автоматизация Ajax с помощью Selenium, BDD и Page Object

Одна сложность, с  которой сталкиваются тестеры, когда они начинают управлять браузерами с помощью Cucumber, - как управлять сайтами, которые содержат множество Ajax вызовов.

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

В этой статье я напишу простой сценарий, который демонстрирует асинхронные действия обработки Page Object gem. Я также кратко ознакомлю Вас с новым Gem, который я использую для создания моих новых проектов. Я сделаю все это написанием  сценария, который использует один из примеров, который Google предоставила для демонстрации GWT библиотеки. Для тех из вас, которые посетили один из моих уроков, вы уже знакомы с этим примером, но, возможно есть еще несколько новых вещей для вас для изучения. Давайте начнем писать код!

Создание проекта

Первое, что мне нужно сделать, - это создать структуру моего  проекта. Я думаю, я буду использовать Page Object gem с  Selenium. Давайте использовать testgen gem для запуска. Если у вас нет установленного  gem, вы можете просто выполнить gem install testgen. Вот команда, которую я использовал для создания своего проекта:

{syntaxhighlighter brush: bash;fontsize: 100; first-line: 1; }testgen project ajax_example --pageobject-driver=selenium{/syntaxhighlighter}

Это создает всю структуру проекта. Давайте быстро проверим, все ли в порядке. Первое, что нужно сделать, это убедиться, что все gem установлены. Testgen создал Gemfile файл для нас, теперь все, что нужно нам сделать, - это изменить на вновь созданные ajax_example каталог и выполнить bundle install.

Теперь, когда мы уверены, что у нас все gem установлены,  пришло время для запуска Сucumber. Testgen создал Rakefile для нас, чтобы сделать это легко. Просто введите rake команду в ajax_example каталоге. Вы должны увидеть следующее:

{syntaxhighlighter brush: bash;fontsize: 100; first-line: 1; }Using the default profile...

0 scenarios

0 steps

0m0.000s{/syntaxhighlighter}

Теперь мы готовы к написанию сценария.

Написание сценария

Мы будем использовать пример DynaTable с сайта GWT. Вот эта страница. На этой странице выберите checkbox «день», а затем классы, предлагаемые на этот день, отображаются в строке с именем  профессора. Давайте попробуем написать сценарий.

Я начинаю с создания файла в каталоге функций. Вот содержание:

{syntaxhighlighter brush: bash;fontsize: 100; first-line: 1; }Feature: Displaying class schedules

I need to be able to display the class schedule for professors. When I
select a day the page should display all of the class taught by professors
on that day as well as the time for the classes.

Scenario: Displaying classes offered by professors
Given I am on the google dynamic table page
When I view the schedule for “Monday”
Then I should see that “Inman Mendez” offers a class at “Mon 9:45-10:35”
When I view the schedule for “Tuesday”
Then I should see that “Inman Mendez” offers a class at “Tues 2:15-3:05”
And I should see that “Teddy Gibbs” offers a class at “Tues 10:00-10:50”{/syntaxhighlighter}

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

Мой следующий шаг заключается в создании шага определений. Для этого я снова выполню команду ‘rake’. Cucumber достаточно хорош для создания шагов ожидания.

{syntaxhighlighter brush: ruby;fontsize: 100; first-line: 1; }Given /^I am on the google dynamic table page$/ do pending # express the regexp above with the code you wish you had end

When /^I view the schedule for “([^"]*)”$/ do |arg1|
pending # express the regexp above with the code you wish you had
end

Then /^I should see that “([^"])" offers a class at "([^"])”$/ do |arg1, arg2|
pending # express the regexp above with the code you wish you had
end{/syntaxhighlighter}

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

{syntaxhighlighter brush: ruby;fontsize: 100; first-line: 1; }When /^I view the schedule for "([^\"]*)"$/ do |day| page.select_schedule_for day end

Then /^I should see that “([^"])" offers a class at "([^"])”$/ do |name, expected|
page.schedule_for(name).include? expected
end{/syntaxhighlighter}

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

Создание Page Object

В целях реализации функциональности я в настоящее время нуждаюсь в объекте на моей странице, мне нужно будет предоставлять доступ к дням  checkboxes, none кнопка (для того, чтобы очистить все checkboxes), а также таблицу, которая содержит расписание. Мне также необходимо предоставить URL для страницы. Я создаю новый файл в pages   каталоге  с именем dynamic_table_page.rb. Вот первая версия этого класса:

{syntaxhighlighter brush: ruby;fontsize: 100; first-line: 1; }class DynamicTablePage include PageObject

page_url “http://gwt.google.com/samples/DynaTable/DynaTable.html
table(:dyna_table, :class => ‘table’)
checkbox(:sunday, :id => ‘gwt-uid-1’)
checkbox(:monday, :id => ‘gwt-uid-2’)
checkbox(:tuesday, :id => ‘gwt-uid-3’)
checkbox(:wednesday, :id => ‘gwt-uid-4’)
checkbox(:thursday, :id => ‘gwt-uid-5’)
checkbox(:friday, :id => ‘gwt-uid-6’)
checkbox(:saturday, :id => ‘gwt-uid-7’)
button(:none, :value => ‘None’)

end{/syntaxhighlighter}

Следующее, что мне нужно сделать,- это написать два метода, которые нужны были мне ранее. Вот они:

{syntaxhighlighter brush: ruby;fontsize: 100; first-line: 1; }def select_schedule_for(day) none self.send "check_#{day.downcase}" end

def schedule_for(name)
the_row = dyna_table_element.find { |row| row[0].text == name}
the_row[2].text
end{/syntaxhighlighter}

Я думаю, здесь есть несколько вещей, которые требуют объяснения. Давайте возьмем сначала один метод.

Отправка сообщений

В первом методе мы нажимали none  кнопку, которая очищает все checkboxes. Далее мы проверяем соответствующий checkboxes . page-object gem генерирует четыре метода при определении checkbox. Давайте рассмотрим четыре метода созданные  для: :monday checkbox описанного выше.

{syntaxhighlighter brush: ruby;fontsize: 100; first-line: 1; }def check_monday # check the monday checkbox end

def uncheck_monday

uncheck the monday checkbox

end

def monday_checked?

return whether the monday checkbox is checked

end

def monday_element

return the monday checkbox element

end{/syntaxhighlighter}

Понимание созданных методов делает понятным то, что мы хотели вызвать - это  check_ метод. При вызове метода в Ruby он на самом деле посылает сообщение объекту, для которого вызов должен быть сделан. На самом деле, Ruby предоставляет send метод, который выполняет фактический вызов. Другими словами:

{syntaxhighlighter brush: ruby;fontsize: 100; first-line: 1; }page.check_monday

is the same as

page.send “check_monday”{/syntaxhighlighter}

Обратите внимание, что мы переносим имя дня в нижний регистр, поэтому  “Monday”  в качестве аргумента приводит «check_monday».

{syntaxhighlighter brush: ruby;fontsize: 100; first-line: 1; }def select_schedule_for(day) none self.send "check_#{day.downcase}" end{/syntaxhighlighter}

Позвольте мне перечислить

Ruby  имеет модуль mixin, который называется Enumerable. Многие методы, обеспеченные этим модулем, добавляют возможность перемещать, искать и сортировать элементы в коллекции. В page-object gem Table, TableRow, OrderedList, и UnorderedList  все элементы включают Enumerable. Это дает возможность осуществлять определенные действия над этими елементами. Например, вы можете сделать цикл по всем TableRow элементам с помощью методов таблици, а затем вы можете обходить элементы TableCell, вызывая метод из TableRow.

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

{syntaxhighlighter brush: ruby;fontsize: 100; first-line: 1; }def schedule_for(name) the_row = dyna_table_element.find { |row| row[0].text == name} the_row[2].text end{/syntaxhighlighter}

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

Избавляемся от магии

Окончательное изменение, которое я хотел бы сделать на странице объекта, -это устранение магических чисел. Магические числа являются числами, которые появляются в коде, которые имеют значение, но их важность не очевидна. Например, мы используем 0 и 2 в schedule_for метод. Если я вернусь к этому кода через несколько месяцев после написания, я уверен, что не вспомню точно, что эти цифры означали. Чтобы решить это, я добавлю константы, которые обеспечивают определенную смысловую нагрузку. После внесения этого изменения вы можете увидеть весь класс.

{syntaxhighlighter brush: ruby;fontsize: 100; first-line: 1; }class DynamicTablePage include PageObject

NAME_COLUMN = 0
SCHEDULE_COLUMN = 2

page_url “http://gwt.google.com/samples/DynaTable/DynaTable.html
table(:dyna_table, :class => ‘table’)
checkbox(:sunday, :id => ‘gwt-uid-1’)
checkbox(:monday, :id => ‘gwt-uid-2’)
checkbox(:tuesday, :id => ‘gwt-uid-3’)
checkbox(:wednesday, :id => ‘gwt-uid-4’)
checkbox(:thursday, :id => ‘gwt-uid-5’)
checkbox(:friday, :id => ‘gwt-uid-6’)
checkbox(:saturday, :id => ‘gwt-uid-7’)
button(:none, :value => ‘None’)

def select_schedule_for(day)
none
self.send “check_#{day.downcase}”
end

def schedule_for(name)
the_row = dyna_table_element.find { |row| row[NAME_COLUMN].text == name}
the_row[SCHEDULE_COLUMN].text
end
end{/syntaxhighlighter}

Завершение Шага Определения

Теперь я могу закончить шаг определений. Я буду использовать PageObject::PageFactory для создания моих объектов. Давайте посмотрим на шаги, а затем мы сможем обсудить детали.

{syntaxhighlighter brush: ruby;fontsize: 100; first-line: 1; }Given /^I am on the google dynamic table page$/ do visit_page DynamicTablePage end

When /^I view the schedule for “([^"]*)”$/ do |day|
on_page(DynamicTablePage).select_schedule_for day
end

Then /^I should see that “([^"])" offers a class at "([^"])”$/ do |name, expected|
on_page(DynamicTablePage) do |page|
page.wait_until(2) do
page.schedule_for(name).include? expected
end
end
end{/syntaxhighlighter}

Третий этап возникает на 9 строчке, где появляется ajax heavy lifting. Я использую метод wait_until доступный на PageObject. Есть много методов для обработки Ajax вызовов baked  into the gem. Данный метод будет ждать, пока блок возвращает истину. По умолчанию он будет ждать 30 секунд, но я подумал, что это слишком долго, так я передаю в 2, чтобы установить время ожидания до 2 секунд. Если ожидаемый текст для графика не появляется в течение 2 секунд, - будет сгенерирована ошибка, и шаг не получится.

Подведение итогов

Пожалуйста, найдите время посмотреть на другие методы, обеспеченные page-object, которые делают тестирование  Ajax сайтов простым. Как всегда, пожалуйста, дайте мне знать, что вы думаете об этой статье.