Add form regions to integration tests
I have implemented some new form regions and used them in currently existing pageobjects. I also created method for switching on/off web driver implicit wait. * Form has automatic field discovery and form's field properties are initialized on the form object initialization. One must submit tuple of field names that must be submited in the same order as in the page html code. * All newly created field regions are added automaticaly to FieldFactory field list. * Forms can be initialized without specifying the source element, as a result it will be assumed that source element shoud be found according to default locator (as it is quite common that forms are located under this locator) Partially implements blueprint: selenium-integration-testing Change-Id: Ic954ea1829135efdc5dd6a34840de5045b4a7a56
This commit is contained in:
committed by
Tomáš Nováčik
parent
b4b0e9c677
commit
3d6cb82646
@@ -52,3 +52,9 @@ class BaseWebObject(object):
|
||||
def _select_dropdown_by_value(self, value, element):
|
||||
select = Support.Select(element)
|
||||
select.select_by_value(value)
|
||||
|
||||
def _turn_off_implicit_wait(self):
|
||||
self.driver.implicitly_wait(0)
|
||||
|
||||
def _turn_on_implicit_wait(self):
|
||||
self.driver.implicitly_wait(self.conf.dashboard.page_timeout)
|
||||
|
||||
@@ -16,62 +16,54 @@
|
||||
from selenium.webdriver.common import by
|
||||
|
||||
from openstack_dashboard.test.integration_tests.pages import basepage
|
||||
from openstack_dashboard.test.integration_tests.regions import forms
|
||||
from openstack_dashboard.test.integration_tests.regions import tables
|
||||
|
||||
|
||||
class KeypairPage(basepage.BasePage):
|
||||
|
||||
_keypair_create_button_locator = (by.By.CSS_SELECTOR,
|
||||
'#keypairs__action_create')
|
||||
_keypair_name_field_locator = (by.By.CSS_SELECTOR, '#id_name')
|
||||
_keypair_submit_button_locator = (by.By.CSS_SELECTOR,
|
||||
'.btn.btn-primary.pull-right')
|
||||
_keypair_delete_cnf_button_locator = (by.By.CSS_SELECTOR,
|
||||
'.btn.btn-primary')
|
||||
_key_pairs_table_locator = (by.By.CSS_SELECTOR, 'table#keypairs')
|
||||
|
||||
KEY_PAIRS_TABLE_ACTIONS = ("create_key_pair", "import_key_pair",
|
||||
"delete_key_pair")
|
||||
KEY_PAIRS_TABLE_ROW_ACTION = "delete_key_pair"
|
||||
KEY_PAIRS_TABLE_NAME_COLUMN_INDEX = 0
|
||||
|
||||
CREATE_KEY_PAIR_FORM_FIELDS = ('name',)
|
||||
|
||||
def __init__(self, driver, conf):
|
||||
super(KeypairPage, self).__init__(driver, conf)
|
||||
self._page_title = "Access & Security"
|
||||
|
||||
@property
|
||||
def keypair_create(self):
|
||||
return self._get_element(*self._keypair_create_button_locator)
|
||||
def _get_row_with_keypair_name(self, name):
|
||||
return self.keypairs_table.get_row(
|
||||
self.KEY_PAIRS_TABLE_NAME_COLUMN_INDEX, name)
|
||||
|
||||
@property
|
||||
def keypair_name_field(self):
|
||||
return self._get_element(*self._keypair_name_field_locator)
|
||||
def keypairs_table(self):
|
||||
src_elem = self._get_element(*self._key_pairs_table_locator)
|
||||
return tables.SimpleActionsTableRegion(self.driver, self.conf,
|
||||
src_elem,
|
||||
self.KEY_PAIRS_TABLE_ACTIONS,
|
||||
self.KEY_PAIRS_TABLE_ROW_ACTION)
|
||||
|
||||
@property
|
||||
def keypair_submit_button(self):
|
||||
return self._get_element(*self._keypair_submit_button_locator)
|
||||
def create_keypair_form(self):
|
||||
return forms.FormRegion(self.driver, self.conf, None,
|
||||
self.CREATE_KEY_PAIR_FORM_FIELDS)
|
||||
|
||||
@property
|
||||
def keypair_delete_cnf_button(self):
|
||||
return self._get_element(*self._keypair_delete_cnf_button_locator)
|
||||
def delete_keypair_form(self):
|
||||
return forms.BaseFormRegion(self.driver, self.conf, None)
|
||||
|
||||
def _click_on_keypair_create(self):
|
||||
self.keypair_create.click()
|
||||
|
||||
def _click_on_keypair_submit_button(self):
|
||||
self.keypair_submit_button.click()
|
||||
|
||||
def _click_on_keypair_delete_cnf_button(self):
|
||||
self.keypair_delete_cnf_button.click()
|
||||
|
||||
def get_keypair_status(self, keypair_name):
|
||||
keypair_locator = (by.By.CSS_SELECTOR,
|
||||
'#keypairs__row__%s' % keypair_name)
|
||||
keypair_status = self._is_element_present(*keypair_locator)
|
||||
return keypair_status
|
||||
def is_keypair_present(self, name):
|
||||
return bool(self._get_row_with_keypair_name(name))
|
||||
|
||||
def create_keypair(self, keypair_name):
|
||||
self._click_on_keypair_create()
|
||||
self.keypair_name_field.send_keys(keypair_name)
|
||||
self._click_on_keypair_submit_button()
|
||||
self.keypairs_table.create_key_pair.click()
|
||||
self.create_keypair_form.name.text = keypair_name
|
||||
self.create_keypair_form.submit.click()
|
||||
|
||||
def delete_keypair(self, keypair_name):
|
||||
keypair_delete_check_locator = (
|
||||
by.By.CSS_SELECTOR,
|
||||
"#keypairs__row_%s__action_delete" % keypair_name)
|
||||
self.driver.find_element(
|
||||
*keypair_delete_check_locator).click()
|
||||
self._click_on_keypair_delete_cnf_button()
|
||||
def delete_keypair(self, name):
|
||||
self._get_row_with_keypair_name(name).delete_key_pair.click()
|
||||
self.delete_keypair_form.submit.click()
|
||||
|
||||
@@ -13,24 +13,28 @@
|
||||
from selenium.webdriver.common import by
|
||||
|
||||
from openstack_dashboard.test.integration_tests.pages import basepage
|
||||
from openstack_dashboard.test.integration_tests.pages import pageobject
|
||||
from openstack_dashboard.test.integration_tests.regions import forms
|
||||
|
||||
|
||||
class ChangePasswordPage(basepage.BasePage):
|
||||
|
||||
_password_form_locator = (by.By.CSS_SELECTOR,
|
||||
'div#change_password_modal')
|
||||
|
||||
CHANGE_PASSWORD_FORM_FIELDS = ("current_password", "new_password",
|
||||
"confirm_new_password")
|
||||
|
||||
@property
|
||||
def modal(self):
|
||||
return ChangePasswordPage.ChangePasswordModal(self.driver,
|
||||
self.conf)
|
||||
def password_form(self):
|
||||
src_elem = self._get_element(*self._password_form_locator)
|
||||
return forms.FormRegion(self.driver, self.conf, src_elem,
|
||||
self.CHANGE_PASSWORD_FORM_FIELDS)
|
||||
|
||||
def change_password(self, current, new):
|
||||
self._fill_field_element(
|
||||
current, self.modal.current_password)
|
||||
self._fill_field_element(
|
||||
new, self.modal.new_password)
|
||||
self._fill_field_element(
|
||||
new, self.modal.confirm_new_password)
|
||||
self.modal.click_on_change_button()
|
||||
self.password_form.current_password.text = current
|
||||
self.password_form.new_password.text = new
|
||||
self.password_form.confirm_new_password.text = new
|
||||
self.password_form.submit.click()
|
||||
|
||||
def reset_to_default_password(self, current):
|
||||
if self.topbar.user.text == self.conf.identity.admin_username:
|
||||
@@ -39,32 +43,3 @@ class ChangePasswordPage(basepage.BasePage):
|
||||
else:
|
||||
return self.change_password(current,
|
||||
self.conf.identity.password)
|
||||
|
||||
class ChangePasswordModal(pageobject.PageObject):
|
||||
_current_password_locator = (by.By.CSS_SELECTOR,
|
||||
'input#id_current_password')
|
||||
_new_password_locator = (by.By.CSS_SELECTOR,
|
||||
'input#id_new_password')
|
||||
_confirm_new_password_locator = (by.By.CSS_SELECTOR,
|
||||
'input#id_confirm_password')
|
||||
_change_submit_button_locator = (by.By.CSS_SELECTOR,
|
||||
'div.modal-footer button.btn')
|
||||
|
||||
@property
|
||||
def current_password(self):
|
||||
return self._get_element(*self._current_password_locator)
|
||||
|
||||
@property
|
||||
def new_password(self):
|
||||
return self._get_element(*self._new_password_locator)
|
||||
|
||||
@property
|
||||
def confirm_new_password(self):
|
||||
return self._get_element(*self._confirm_new_password_locator)
|
||||
|
||||
@property
|
||||
def change_button(self):
|
||||
return self._get_element(*self._change_submit_button_locator)
|
||||
|
||||
def click_on_change_button(self):
|
||||
self.change_button.click()
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
from selenium.webdriver.common import by
|
||||
|
||||
from openstack_dashboard.test.integration_tests.pages import basepage
|
||||
from openstack_dashboard.test.integration_tests.pages import pageobject
|
||||
from openstack_dashboard.test.integration_tests.pages.settings import \
|
||||
changepasswordpage
|
||||
from openstack_dashboard.test.integration_tests.regions import forms
|
||||
|
||||
|
||||
class SettingsPage(basepage.BasePage):
|
||||
@@ -28,6 +28,9 @@ class SettingsPage(basepage.BasePage):
|
||||
"pagesize": DEFAULT_PAGESIZE
|
||||
}
|
||||
|
||||
SETTINGS_FORM_FIELDS = ("language", "timezone", "pagesize")
|
||||
|
||||
_settings_form_locator = (by.By.CSS_SELECTOR, 'div#user_settings_modal')
|
||||
_change_password_tab_locator = (by.By.CSS_SELECTOR,
|
||||
'a[href*="/settings/password/"]')
|
||||
|
||||
@@ -36,8 +39,10 @@ class SettingsPage(basepage.BasePage):
|
||||
self._page_title = "User Settings"
|
||||
|
||||
@property
|
||||
def modal(self):
|
||||
return SettingsPage.UserSettingsModal(self.driver, self.conf)
|
||||
def settings_form(self):
|
||||
src_elem = self._get_element(*self._settings_form_locator)
|
||||
return forms.FormRegion(self.driver, self.conf, src_elem,
|
||||
self.SETTINGS_FORM_FIELDS)
|
||||
|
||||
@property
|
||||
def changepassword(self):
|
||||
@@ -48,18 +53,16 @@ class SettingsPage(basepage.BasePage):
|
||||
return self._get_element(*self._change_password_tab_locator)
|
||||
|
||||
def change_language(self, lang=DEFAULT_LANGUAGE):
|
||||
self._select_dropdown_by_value(lang,
|
||||
self.modal.language_selection)
|
||||
self.modal.click_on_save_button()
|
||||
self.settings_form.language.value = lang
|
||||
self.settings_form.submit.click()
|
||||
|
||||
def change_timezone(self, timezone=DEFAULT_TIMEZONE):
|
||||
self._select_dropdown_by_value(timezone,
|
||||
self.modal.timezone_selection)
|
||||
self.modal.click_on_save_button()
|
||||
self.settings_form.timezone.value = timezone
|
||||
self.settings_form.submit.click()
|
||||
|
||||
def change_pagesize(self, size=DEFAULT_PAGESIZE):
|
||||
self._fill_field_element(size, self.modal.pagesize)
|
||||
self.modal.click_on_save_button()
|
||||
self.settings_form.pagesize.value = size
|
||||
self.settings_form.submit.click()
|
||||
|
||||
def return_to_default_settings(self):
|
||||
self.change_language()
|
||||
@@ -69,32 +72,3 @@ class SettingsPage(basepage.BasePage):
|
||||
def go_to_change_password_page(self):
|
||||
self.change_password_tab.click()
|
||||
return changepasswordpage.ChangePasswordPage(self.driver, self.conf)
|
||||
|
||||
class UserSettingsModal(pageobject.PageObject):
|
||||
_language_selection_locator = (by.By.CSS_SELECTOR,
|
||||
'select#id_language')
|
||||
_timezone_selection_locator = (by.By.CSS_SELECTOR,
|
||||
'select#id_timezone')
|
||||
_items_per_page_input_locator = (by.By.CSS_SELECTOR,
|
||||
'input#id_pagesize')
|
||||
_save_submit_button_locator = (by.By.CSS_SELECTOR,
|
||||
'div.modal-footer button.btn')
|
||||
|
||||
@property
|
||||
def language_selection(self):
|
||||
return self._get_element(*self._language_selection_locator)
|
||||
|
||||
@property
|
||||
def timezone_selection(self):
|
||||
return self._get_element(*self._timezone_selection_locator)
|
||||
|
||||
@property
|
||||
def pagesize(self):
|
||||
return self._get_element(*self._items_per_page_input_locator)
|
||||
|
||||
@property
|
||||
def save_button(self):
|
||||
return self._get_element(*self._save_submit_button_locator)
|
||||
|
||||
def click_on_save_button(self):
|
||||
self.save_button.click()
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
class BaseRegionException(Exception):
|
||||
"""Base exception class for region module."""
|
||||
pass
|
||||
|
||||
|
||||
class UnknownFormFieldTypeException(BaseRegionException):
|
||||
|
||||
def __str__(self):
|
||||
return "No FormField class matched the scope of web content."
|
||||
@@ -9,13 +9,46 @@
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import six
|
||||
|
||||
from selenium.webdriver.common import by
|
||||
import selenium.webdriver.support.ui as Support
|
||||
|
||||
from openstack_dashboard.test.integration_tests.regions import baseregion
|
||||
from openstack_dashboard.test.integration_tests.regions import exceptions
|
||||
|
||||
|
||||
class FieldFactory(baseregion.BaseRegion):
|
||||
"""Factory for creating form field objects."""
|
||||
|
||||
FORM_FIELDS_TYPES = set()
|
||||
|
||||
def make_form_field(self):
|
||||
for form_type in self.FORM_FIELDS_TYPES:
|
||||
if self._is_element_present(*form_type._element_locator):
|
||||
return form_type(self.driver, self.conf, self.src_elem)
|
||||
raise exceptions.UnknownFormFieldTypeException()
|
||||
|
||||
@classmethod
|
||||
def register_field_cls(cls, field_class, base_classes=None):
|
||||
"""Register new field class.
|
||||
|
||||
Add new field class and remove all base classes from the set of
|
||||
registered classes as they should not be in.
|
||||
"""
|
||||
cls.FORM_FIELDS_TYPES.add(field_class)
|
||||
cls.FORM_FIELDS_TYPES -= set(base_classes)
|
||||
|
||||
|
||||
class MetaBaseFormFieldRegion(type):
|
||||
"""Register form field class in FieldFactory."""
|
||||
|
||||
def __init__(cls, name, bases, dct):
|
||||
FieldFactory.register_field_cls(cls, bases)
|
||||
super(MetaBaseFormFieldRegion, cls).__init__(name, bases, dct)
|
||||
|
||||
|
||||
@six.add_metaclass(MetaBaseFormFieldRegion)
|
||||
class BaseFormFieldRegion(baseregion.BaseRegion):
|
||||
"""Base class for form fields classes."""
|
||||
|
||||
@@ -35,14 +68,150 @@ class BaseFormFieldRegion(baseregion.BaseRegion):
|
||||
return 'required' in classes
|
||||
|
||||
|
||||
class BaseTextFormFieldRegion(BaseFormFieldRegion):
|
||||
|
||||
_element_locator = None
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return self.element.text
|
||||
|
||||
@text.setter
|
||||
def text(self, text):
|
||||
self._fill_field_element(text, self.element)
|
||||
|
||||
|
||||
class TextInputFormFieldRegion(BaseTextFormFieldRegion):
|
||||
"""Text input box."""
|
||||
|
||||
_element_locator = (by.By.CSS_SELECTOR, 'div > input[type=text]')
|
||||
|
||||
|
||||
class PasswordInputFormFieldRegion(BaseTextFormFieldRegion):
|
||||
"""Password text input box."""
|
||||
|
||||
_element_locator = (by.By.CSS_SELECTOR, 'div > input[type=password]')
|
||||
|
||||
|
||||
class IntegerFormFieldRegion(BaseFormFieldRegion):
|
||||
"""Integer input box."""
|
||||
|
||||
_element_locator = (by.By.CSS_SELECTOR, 'div > input[type=number]')
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self.element.get_attribute("value")
|
||||
|
||||
@value.setter
|
||||
def value(self, value):
|
||||
self._fill_field_element(value, self.element)
|
||||
|
||||
|
||||
class SelectFormFieldRegion(BaseFormFieldRegion):
|
||||
"""Select box field."""
|
||||
|
||||
_element_locator = (by.By.CSS_SELECTOR, 'div > select')
|
||||
|
||||
@property
|
||||
def element(self):
|
||||
select = self._get_element(*self._element_locator)
|
||||
return Support.Select(select)
|
||||
|
||||
@property
|
||||
def values(self):
|
||||
results = []
|
||||
for option in self.element.all_selected_options:
|
||||
results.append(option.get_attribute('value'))
|
||||
return results
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
return self.element.first_selected_option.text
|
||||
|
||||
@text.setter
|
||||
def text(self, text):
|
||||
self.element.select_by_visible_text(text)
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self.element.first_selected_option.get_attribute('value')
|
||||
|
||||
@value.setter
|
||||
def value(self, value):
|
||||
self.element.select_by_value(value)
|
||||
|
||||
|
||||
class BaseFormRegion(baseregion.BaseRegion):
|
||||
"""Base class for forms."""
|
||||
|
||||
_submit_locator = (by.By.CSS_SELECTOR, 'button.btn.btn-primary,'
|
||||
' a.btn.btn-primary')
|
||||
_submit_locator = (by.By.CSS_SELECTOR, '*.btn.btn-primary')
|
||||
_cancel_locator = (by.By.CSS_SELECTOR, '*.btn.cancel')
|
||||
_default_form_locator = (by.By.CSS_SELECTOR, 'div.modal-dialog')
|
||||
|
||||
def __init__(self, driver, conf, src_elem=None):
|
||||
"""In most cases forms can be located through _default_form_locator,
|
||||
so specifying source element can be skipped.
|
||||
"""
|
||||
if src_elem is None:
|
||||
# fake self.src_elem must be set up in order self._get_element work
|
||||
self.src_elem = driver
|
||||
src_elem = self._get_element(*self._default_form_locator)
|
||||
super(BaseFormRegion, self).__init__(driver, conf, src_elem)
|
||||
|
||||
@property
|
||||
def submit(self):
|
||||
self._get_element(*self._submit_locator).click()
|
||||
return self._get_element(*self._submit_locator)
|
||||
|
||||
@property
|
||||
def cancel(self):
|
||||
return self._get_element(*self._cancel_locator)
|
||||
|
||||
|
||||
class FormRegion(BaseFormRegion):
|
||||
"""Standard form."""
|
||||
|
||||
_header_locator = (by.By.CSS_SELECTOR, 'div.modal-header > h3')
|
||||
_side_info_locator = (by.By.CSS_SELECTOR, 'div.right')
|
||||
_fields_locator = (by.By.CSS_SELECTOR, 'fieldset > div.form-group')
|
||||
|
||||
# private methods
|
||||
def __init__(self, driver, conf, src_elem, form_field_names):
|
||||
super(self.__class__, self).__init__(driver, conf, src_elem)
|
||||
self.form_field_names = form_field_names
|
||||
self._init_form_fields()
|
||||
|
||||
# protected methods
|
||||
def _init_form_fields(self):
|
||||
self._init_dynamic_properties(self.form_field_names,
|
||||
self._get_form_fields)
|
||||
|
||||
def _get_form_fields(self):
|
||||
fields_els = self._get_elements(*self._fields_locator)
|
||||
form_fields = []
|
||||
try:
|
||||
self._turn_off_implicit_wait()
|
||||
for elem in fields_els:
|
||||
field_factory = FieldFactory(self.driver, self.conf, elem)
|
||||
form_fields.append(field_factory.make_form_field())
|
||||
finally:
|
||||
self._turn_on_implicit_wait()
|
||||
return form_fields
|
||||
|
||||
# properties
|
||||
@property
|
||||
def header(self):
|
||||
"""Form header."""
|
||||
return self._get_element(*self._header_locator)
|
||||
|
||||
@property
|
||||
def sideinfo(self):
|
||||
"""Right part of form, usually contains description."""
|
||||
return self._get_element(*self._side_info_locator)
|
||||
|
||||
@property
|
||||
def fields(self):
|
||||
"""List of all fields that form contains."""
|
||||
return self._get_form_fields()
|
||||
|
||||
|
||||
class DateFormRegion(BaseFormRegion):
|
||||
@@ -64,7 +233,7 @@ class DateFormRegion(BaseFormRegion):
|
||||
def query(self, start, end):
|
||||
self._set_from_field(start)
|
||||
self._set_to_field(end)
|
||||
self.submit()
|
||||
self.submit.click()
|
||||
|
||||
def _set_from_field(self, value):
|
||||
self._fill_field_element(value, self.from_date)
|
||||
|
||||
@@ -29,7 +29,7 @@ class TestKeypair(helpers.TestCase):
|
||||
keypair_page.create_keypair(self.KEYPAIR_NAME)
|
||||
accesssecurity_page = self.home_pg.go_to_accesssecurity_page()
|
||||
keypair_page = accesssecurity_page.go_to_keypair_page()
|
||||
self.assertTrue(keypair_page.get_keypair_status(self.KEYPAIR_NAME))
|
||||
self.assertTrue(keypair_page.is_keypair_present(self.KEYPAIR_NAME))
|
||||
|
||||
keypair_page.delete_keypair(self.KEYPAIR_NAME)
|
||||
self.assertFalse(keypair_page.get_keypair_status(self.KEYPAIR_NAME))
|
||||
self.assertFalse(keypair_page.is_keypair_present(self.KEYPAIR_NAME))
|
||||
|
||||
@@ -21,7 +21,6 @@ class TestPasswordChange(helpers.TestCase):
|
||||
"""Changes the password, verifies it was indeed changed and resets to
|
||||
default password.
|
||||
"""
|
||||
|
||||
settings_page = self.home_pg.go_to_settings_page()
|
||||
passwordchange_page = settings_page.go_to_change_password_page()
|
||||
|
||||
|
||||
@@ -16,12 +16,9 @@ from openstack_dashboard.test.integration_tests import helpers
|
||||
class TestUserSettings(helpers.TestCase):
|
||||
|
||||
def verify_user_settings_change(self, changed_settings):
|
||||
language = self.settings_page.modal.language_selection.\
|
||||
get_attribute("value")
|
||||
timezone = self.settings_page.modal.timezone_selection.\
|
||||
get_attribute("value")
|
||||
pagesize = self.settings_page.modal.pagesize.\
|
||||
get_attribute("value")
|
||||
language = self.settings_page.settings_form.language.value
|
||||
timezone = self.settings_page.settings_form.timezone.value
|
||||
pagesize = self.settings_page.settings_form.pagesize.value
|
||||
|
||||
user_settings = (("Language", changed_settings["language"], language),
|
||||
("Timezone", changed_settings["timezone"], timezone),
|
||||
|
||||
Reference in New Issue
Block a user