Существует достаточное количество подходов для написания UI-тестов для Selenium RC на python’е. Давайте рассмотрим некоторые из них.
Данный код получить проще и легче всего. Для этого достаточно установить дополнение “Selenium IDE” к браузеру Firefox. Запустить его, записать какие-то шаги, а потом с помощью команд Selenium IDE (format -> python) получить готовый код.
Пример кода, полученного из Selenium IDE:
Рассмотрим плюсы данного подхода:
Теперь минусы этого подхода:
Этот подход, фактически идентичен первому подходу, с одной лишь разницей — мы избавляемся от двух последних недостатков.
Для этого будем хранить локаторы элементов для каждой страницы в отдельном файле. для Существует файл “mappings.xml”, в котором хранится описание ключевых объектов/элементов страниц.
Примерная структура mapping-файла:
Дополнительное изменение, которое было сделано в mapping-файле, это использование xpath выражения для определения локатора файла.
Таким образом код из первого подхода будет выглядеть так:
Оценим преимущества данного подхода:
Недостатки:
Для уменьшения количества повторяемого кода, а также для улучшения читабельности создадим модели для страниц сайта.
Для этого для каждой страницы опишем отдельный класс со свойствами( объект с локаторами страницы и объектом Selenium-а) и методами (возможными действиями на странице).
Каждый класс-страница наследуется от базового класса — CommonPage. Данный класс содержит набор общих методов для всех страниц
Пример базового класса:
Пример класса для стартовой страницы:
Тогда код из пункта 1 будет выглядеть таким образов:
При использовании данного подхода появляется простота понимания кода: есть страница “LoginPage” и на ней выполняется действие login.
Но и у данного подхода есть свои недостатки:
Для того, чтобы уменьшить повторяемость кода, создадим для каждого типа элементов страницы отдельный класс.
Кроме того, можно создать базовый класс CommonElem и от него наследовать все элементы. Этот класс содержит базовые (общие) методы для всех элементов страницы. Таким образом мы разгрузим наследуемые классы. А так же базовый класс можно использовать для обработки специфических элементов.
Приведем пример базового класса:
Таким образом наследуемые элементы будут иметь вид (пример):
Каждая страница описывается отдельным классом, который наследуется все от базового класса-страницы также как это было описано в пункте 3. Но теперь для работы непосредственно с элементами таблицы мы используем описанные объекты элементов страницы.
Пример базового класса-страницы:
Код из пункта №1 будет иметь такой же вид, как и в пункте №3, так как поменялся вид внутри методов страницы:
Дополнительные преимущества данного подхода:
Проблема возникающая при использовании данного подхода — это сложная, вложенная структура из-за чего увеличивается время на доработку и поддержку системы.
1 Код, автоматически генерируется с помощью Selenium IDE
Данный код получить проще и легче всего. Для этого достаточно установить дополнение “Selenium IDE” к браузеру Firefox. Запустить его, записать какие-то шаги, а потом с помощью команд Selenium IDE (format -> python) получить готовый код.
Пример кода, полученного из Selenium IDE:
sel = self.selenium
sel.open(‘/portal/index/login/’)
sel.type(‘Username', ‘admin’)
sel.type('Password’, ‘admin’)
sel.click('submit')
Рассмотрим плюсы данного подхода:
- Простота написания тестов (фактически их и писать то не надо — Selenium IDE сгенерирует готовый код, который надо будет чуть подправить);
- Максимальная быстрота написания теста.
Теперь минусы этого подхода:
- Относительная сложность прочтения теста;
- Много повторяющегося кода;
- Сложность изменения кода, после обновления объекта тестирования;
- Кроме того, в большинстве случаев Selenium IDE записывает локаторы элементов так, что потом сам не может их найти — требуется корректировать локаторы.
2 Локаторы элементов странице хранятся в отдельном файле
Этот подход, фактически идентичен первому подходу, с одной лишь разницей — мы избавляемся от двух последних недостатков.
Для этого будем хранить локаторы элементов для каждой страницы в отдельном файле. для Существует файл “mappings.xml”, в котором хранится описание ключевых объектов/элементов страниц.
Примерная структура mapping-файла:
<login_page>
<username_field>//input[@id='Username']</username_field>
<password_field>//input[@id='Password']</password_field>
<submit_button>//button[@type='submit']</submit_button>
</login_page>
...
Дополнительное изменение, которое было сделано в mapping-файле, это использование xpath выражения для определения локатора файла.
Таким образом код из первого подхода будет выглядеть так:
sel = self.selenium
sel.open("/portal/index/login/")
xml_data = load_page_info("mapping.xml", "login_page")
sel.type(xml_data["username_field"], "admin")
sel.type(xml_data["password_field"], "admin")
sel.click(xml_data["submit_button"])
Оценим преимущества данного подхода:
- при изменении локатора обновлять надо только mapping-файл.
- в mapping-файле хранятся “расширенные” локаторы
Недостатки:
- относительная сложность прочтения кода
- много повторяющегося кода.
3 Создание объекта/класса для каждой страницы
Для уменьшения количества повторяемого кода, а также для улучшения читабельности создадим модели для страниц сайта.
Для этого для каждой страницы опишем отдельный класс со свойствами( объект с локаторами страницы и объектом Selenium-а) и методами (возможными действиями на странице).
Каждый класс-страница наследуется от базового класса — CommonPage. Данный класс содержит набор общих методов для всех страниц
Пример базового класса:
class CommonPage(object):
def __init__(self, selenium_obj):
self._browser = selenium_obj
self._locators = load_page_info(“mapping.xml”, self.__class__.__name__)
def set_field(self, field_name, field_value):
field_locator = self._locators[field_name]
self._browser.type(field_locator, field_value)
def push_button(self, btn_name):
btn_locator = self._locators[btn_name]
self._browser.click(btn_locator)
Пример класса для стартовой страницы:
class LoginPage(CommonPage):
def __init__(self, selenium_obj):
CommonPage.__init__(self, selenium_obj)
def login(self, username, password)
self.set_field(‘username_field’, username)
self.set_field(‘password_field’, password)
self.push_button(‘submit_button’)
Тогда код из пункта 1 будет выглядеть таким образов:
sel = self.selenium
sel.open("/portal/index/login/")
login_page = LoginPage(sel)
login_page.login("admin", "admin_pwd")
При использовании данного подхода появляется простота понимания кода: есть страница “LoginPage” и на ней выполняется действие login.
Но и у данного подхода есть свои недостатки:
- при необходимости использовать специфический элемент, который нигде потом не используется все еще требуется писать в тесте код так, как это делает Selenium IDE;
- все еще есть большие участки повторяемого кода(одни и те же действия для элементов — ввод текста в text_field, проверка введенного значения, нажатие кнопок, проверка существования элемента и т.д.).
4 Создание объекта/класса для каждого элемента страницы, объекта для страницы
Для того, чтобы уменьшить повторяемость кода, создадим для каждого типа элементов страницы отдельный класс.
Кроме того, можно создать базовый класс CommonElem и от него наследовать все элементы. Этот класс содержит базовые (общие) методы для всех элементов страницы. Таким образом мы разгрузим наследуемые классы. А так же базовый класс можно использовать для обработки специфических элементов.
Приведем пример базового класса:
class CommonElem(object):
def __init__(self, selenium_obj, locator):
self._sel = selenium_obj
self._locator = locator
def is_exists(self):
return self._sel.is_element_present(self._locator)
def focus(self):
self._sel.focus(self._locator)
Таким образом наследуемые элементы будут иметь вид (пример):
class TextField(CommonElem):
def __init__(self, selenium_obj, locator):
ComonElem.__init__(self, selenium_obj, locator)
def set_text(self, text, verify_text = True):
res = True
try:
if (self.is_exists()):
self._sel.type(self._locator, text)
if (verify_text == True):
res = self.verify_text(text)
else:
raise Exception(“element doesn’t exists”)
except Exception, exc:
res = False
print “Exception happened: %s” % (exc.message)
return res
def verify_text(self, valid_text)
return valid_text == self._sel.get_text(self._locator)
class Button(CommonElem):
def __init__(self, selenium_obj, locator):
ComonElem.__init__(self, selenium_obj, locator)
def click(self):
res = True
if (self.is_exists()):
self._sel.click(self._locator)
else:
res = False
return res
def click_and_wait(self, timeout = “10000”):
res = True
if (self.is_exists()):
try:
self._sel.click_and_wait(self.locator, timeout)
except Exception,exc:
res = False
print “Exception happened: %s” % (exc.message)
else:
res = False
print “Element does not exists”
return res
Каждая страница описывается отдельным классом, который наследуется все от базового класса-страницы также как это было описано в пункте 3. Но теперь для работы непосредственно с элементами таблицы мы используем описанные объекты элементов страницы.
Пример базового класса-страницы:
class CommonPage(object):
def __init__(self, selenium_obj, url, load = True):
self._url = url
self._browser = selenium_obj
self._locators = load_page_info(“mapping.xml”, self.__class__.__name__)
def set_field(self, field_name, field_value, verify = True):
field = TextField(self._browser, self._locators[field_name])
return field.set_text(field_value, verify)
def push_button(self, btn_name):
btn = Button(self._browser, self._locators[btn_name])
return btn.click()
def load(self, timeout = “10000”):
res = True
try:
self._browser.open(self._url)
self._browser.wait_for_page_to_load(timeout)
except Exception, exc:
res = False
print “Exception happened: %s” % (exc.message)
return res
def is_text_on_page(self, text):
return self._browser.is_text_present(text)
Код из пункта №1 будет иметь такой же вид, как и в пункте №3, так как поменялся вид внутри методов страницы:
sel = self.selenium
sel.open("/portal/index/login/")
login_page = LoginPage(sel)
login_page.login("admin", "admin")
Дополнительные преимущества данного подхода:
- простота понимания кода. Код, который работает на прямую с библиотекой Selenium-a максимально разделен логически между объектами страницы и объектами элементов страницы;
- при необходимости использовать спецефический элемент, который нигде потом не используется, можно использовать базовый обьект элемента страницы, а не код типа Selenium IDE.
Проблема возникающая при использовании данного подхода — это сложная, вложенная структура из-за чего увеличивается время на доработку и поддержку системы.