Pull to refresh

Некоторые подходы для написания UI-тестов с помощью Selenium RC на python

Существует достаточное количество подходов для написания UI-тестов для Selenium RC на python’е. Давайте рассмотрим некоторые из них.

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.

Проблема возникающая при использовании данного подхода — это сложная, вложенная структура из-за чего увеличивается время на доработку и поддержку системы.
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.