Add tests for LoginScreen widget

Add some basic tests for different aspects of LoginScreen such as
'login', 'reset_and_login', 'reset' views.

Fixes: https://pagure.io/freeipa/issue/7619
Reviewed-By: Petr Vobornik <pvoborni@redhat.com>
Reviewed-By: Serhii Tsymbaliuk <stsymbal@redhat.com>
This commit is contained in:
Stanislav Levin 2018-08-24 18:00:38 +03:00 committed by Serhii Tsymbaliuk
parent 5c32ac3e59
commit b4885d3eb7
2 changed files with 562 additions and 0 deletions

View File

@ -0,0 +1,161 @@
#
# Copyright (C) 2018 FreeIPA Contributors see COPYING for license
#
ENTITY = 'user'
PKEY = 'itest-user'
PASSWD_ITEST_USER = '12345678'
PASSWD_ITEST_USER_NEW = '87654321'
# used for add/delete fixture test user
DATA_ITEST_USER = {
'pkey': PKEY,
'add': [
('textbox', 'uid', PKEY),
('textbox', 'givenname', 'itest-user-name'),
('textbox', 'sn', 'itest-user-surname'),
('password', 'userpassword', PASSWD_ITEST_USER),
('password', 'userpassword2', PASSWD_ITEST_USER),
]
}
# used for checking login form after click Cancel on 'reset' view
FILLED_LOGIN_FORM = {
# structure of rows
# label_name, label_text,
# required, editable,
# input_type, input_name,
# input_text, placeholder
'rows': [
('username', 'Username', True, True, 'text', 'username',
PKEY, 'Username'),
('password', 'Password', True, True, 'password', 'password',
PASSWD_ITEST_USER, 'Password or Password+One-Time-Password'),
],
# structure of buttons
# button_name, button_title
'buttons': [
('cert_auth', 'Log in using personal certificate'),
('sync', 'Sync OTP Token'),
('login', 'Log in'),
],
'required_msg': [
('Username: Required field',),
('Password: Required field',),
],
}
# used for checking 'reset_and_login' view
RESET_AND_LOGIN_FORM = {
# structure of rows
# label_name, label_text,
# required, editable,
# input_type, input_name,
# input_text, placeholder
'rows': [
('username_r', 'Username', False, False, None, 'username_r',
PKEY, None),
('current_password', 'Current Password', False, True, 'password',
'current_password', '', 'Current Password'),
('otp', 'OTP', False, True, 'password', 'otp', '',
'One-Time-Password'),
('new_password', 'New Password', True, True, 'password',
'new_password', '', 'New Password'),
('verify_password', 'Verify Password', True, True, 'password',
'verify_password', '', 'New Password'),
],
# structure of buttons
# button_name, button_title
'buttons': [
('cancel', 'Cancel'),
('reset_and_login', 'Reset Password and Log in'),
],
'required_msg': [
('New Password: Required field',),
('Verify Password: Required field',),
],
}
# used for checking 'reset' view
RESET_PASSWORD_FORM = {
# structure of rows
# label_name, label_text,
# required, editable,
# input_type, input_name,
# input_text, placeholder
'rows': [
('username', 'Username', True, True, 'text', 'username', '',
'Username'),
('current_password', 'Current Password', True, True, 'password',
'current_password', '', 'Current Password'),
('otp', 'OTP', False, True, 'password', 'otp', '',
'One-Time-Password'),
('new_password', 'New Password', True, True, 'password',
'new_password', '', 'New Password'),
('verify_password', 'Verify Password', True, True, 'password',
'verify_password', '', 'New Password'),
],
# structure of buttons
# button_name, button_title
'buttons': [
('reset', 'Reset Password'),
],
'required_msg': [
('Username: Required field',),
('Current Password: Required field',),
('New Password: Required field',),
('Verify Password: Required field',),
],
}
# used for checking empty 'login' view
EMPTY_LOGIN_FORM = {
# structure of rows
# label_name, label_text,
# required, editable,
# input_type, input_name,
# input_text, placeholder
'rows': [
('username', 'Username', False, True, 'text', 'username', '',
'Username'),
('password', 'Password', False, True, 'password', 'password', '',
'Password or Password+One-Time-Password'),
],
# structure of buttons
# button_name, button_title
'buttons': [
('cert_auth', 'Log in using personal certificate'),
('sync', 'Sync OTP Token'),
('login', 'Log in'),
],
'required_msg': [
('Authentication with Kerberos failed',),
],
}
# used for checking 'login' view
LOGIN_FORM = {
# structure of rows
# label_name, label_text,
# required, editable,
# input_type, input_name,
# input_text, placeholder
'rows': [
('username', 'Username', True, True, 'text', 'username', PKEY,
'Username'),
('password', 'Password', True, True, 'password', 'password', '',
'Password or Password+One-Time-Password'),
],
# structure of buttons
# button_name, button_title
'buttons': [
('cert_auth', 'Log in using personal certificate'),
('sync', 'Sync OTP Token'),
('login', 'Log in'),
],
'required_msg': [
('Password: Required field',),
],
}

View File

@ -0,0 +1,401 @@
#
# Copyright (C) 2018 FreeIPA Contributors see COPYING for license
#
"""
Test LoginScreen widget and all it's views
"""
from ipatests.test_webui.ui_driver import UI_driver
from ipatests.test_webui.ui_driver import screenshot
import ipatests.test_webui.data_loginscreen as loginscreen
try:
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.wait import WebDriverWait
except ImportError:
pass
import pytest
from six.moves import urllib
@pytest.mark.tier1
class TestLoginScreen(UI_driver):
def setup(self, *args, **kwargs):
super(TestLoginScreen, self).setup(*args, **kwargs)
self.init_app()
self.add_test_user()
self.logout()
def teardown(self, *args, **kwargs):
# log out first
if (self.logged_in()):
self.logout()
else:
self.load_url(self.get_base_url())
# log in as administrator
self.login()
self.delete_test_user()
super(TestLoginScreen, self).teardown(*args, **kwargs)
def delete_test_user(self):
"""
Delete user for tests
"""
# User is not logged in
assert self.logged_in()
self.navigate_to_entity(loginscreen.ENTITY)
self.delete_record(loginscreen.PKEY)
def add_test_user(self):
"""
Add user for tests
"""
# User is not logged in
assert self.logged_in()
self.add_record(loginscreen.ENTITY, loginscreen.DATA_ITEST_USER,
navigate=False)
def assert_notification(self, type='success', assert_text=None,
link_text=None, link_url=None):
"""
Assert whether we have a notification of particular type
"""
notification_type = 'div.validation-summary .alert-{}'.format(type)
# wait for a half sec for notification to appear
self.wait(0.5)
is_present = self.find(notification_type, By.CSS_SELECTOR)
# Notification not present
assert is_present
if assert_text:
assert assert_text in is_present.text
if link_text and link_url:
link = self.find_xelement(".//a", is_present)
# Text on link placed on validation widget
assert link_text == link.text
# URL of link placed on validation widget
assert link_url == link.get_attribute('href')
def find_xelement(self, expression, parent, strict=True):
"""
Find element by xpath related to a given parent
"""
return self.find(expression, By.XPATH, parent, many=False,
strict=strict)
def find_xelements(self, expression, parent, strict=True):
"""
Find elements by xpath related to the given parent
"""
return self.find(expression, By.XPATH, parent, many=True,
strict=strict)
def button_click_on_login_screen(self, name):
"""
Find a button with the given name on LoginScreen widget and
then click on
"""
login_screen = self.get_login_screen()
button = self.find_xelement(".//button[@name='{}']".format(name),
login_screen)
assert button.is_displayed()
button.click()
def get_input_field(self, name, parent):
"""
Find a input field with the given name and parent
"""
return self.find_xelement(
".//input[@name='{}']".format(name),
parent
)
def load_url(self, url):
"""
Navigate to Web page and wait for loading of all dependencies.
"""
self.driver.get(url)
runner = self
WebDriverWait(self.driver, 10).until(
lambda d: runner.files_loaded()
)
def relogin_with_new_password(self):
"""
Log out and then log in using a new password.
It is need to check a new password
"""
if (self.logged_in()):
self.logout()
else:
self.load_url(self.get_base_url())
self.login(loginscreen.PKEY, loginscreen.PASSWD_ITEST_USER_NEW)
# User is not logged in
assert self.logged_in()
def reset_password(self, username=None, current_password=None,
new_password=None, link_text=None, link_url=None):
"""
Reset password with the given one
"""
login_screen = self.get_login_screen()
if username is not None:
username_field = self.get_input_field('username', login_screen)
cur_pass_field = self.get_input_field('current_password', login_screen)
new_pass_field = self.get_input_field('new_password', login_screen)
verify_pass_field = self.get_input_field('verify_password',
login_screen)
if username is not None:
username_field.send_keys(username)
cur_pass_field.send_keys(current_password)
new_pass_field.send_keys(new_password)
verify_pass_field.send_keys(new_password)
verify_pass_field.send_keys(Keys.RETURN)
self.wait()
self.assert_notification(assert_text='Password change complete',
link_text=link_text, link_url=link_url)
def get_data_from_form_row(self, form_row):
"""
Parse data from the form record to a comparable structure
"""
result = []
label = self.find_xelement(".//label", form_row)
assert label.is_displayed()
result.append(label.get_attribute('name'))
result.append(label.text)
req = self.find_xelement("./../..", label)
result.append(self.has_class(req, 'required'))
field = self.find_xelement(".//input", form_row)
# not editable field
editable = field.is_displayed()
if not editable:
field = self.find_xelement(".//p", form_row)
result.append(editable)
assert field.is_displayed()
result.append(field.get_attribute('type'))
result.append(field.get_attribute('name'))
if not editable:
result.append(field.text)
else:
result.append(field.get_attribute('value'))
result.append(field.get_attribute('placeholder'))
return tuple(result)
def get_data_from_button(self, button):
"""
Parse data from the button to a comparable structure
"""
result = []
result.append(button.get_attribute('name'))
result.append(button.get_attribute('title'))
return tuple(result)
def assert_form_equals(self, actual_form, expected_form):
"""
Compare two forms
"""
assert len(actual_form) == len(expected_form)
for act_row, exp_row in zip(actual_form, expected_form):
# structure of rows
# label_name, label_text,
# required, editable,
# input_type, input_name,
# input_text, placeholder
assert self.get_data_from_form_row(act_row) == exp_row
def assert_buttons_equal(self, actual_buttons, expected_buttons):
"""
Compare button sets
"""
assert len(actual_buttons) == len(expected_buttons)
for act_button, exp_button in zip(actual_buttons, expected_buttons):
assert self.get_data_from_button(act_button) == exp_button
def assert_validations_equal(self, actual_alerts, expected_alerts):
"""
Compare validation sets
"""
assert len(actual_alerts) == len(expected_alerts)
for act_alert, exp_alert in zip(actual_alerts, expected_alerts):
assert (act_alert.text,) == exp_alert
def has_validation(self, parent):
return self.find_xelement(".//div[@name='validation']", parent,
strict=False)
def check_elements_of_form(self, form_data):
login_screen = self.get_login_screen()
form = self.find_xelement(".//div[@class='form-horizontal']",
login_screen)
# rows
form_rows = self.find_xelements(
".//div[contains(@class, 'form-group')]", form
)
form_rows = [el for el in form_rows if el.is_displayed() and not
self.has_validation(el)]
self.assert_form_equals(form_rows, form_data['rows'])
# buttons
buttons = self.find_xelements(".//button", login_screen)
buttons = [el for el in buttons if el.is_displayed()]
self.assert_buttons_equal(buttons, form_data['buttons'])
def check_alerts(self, form_data):
login_screen = self.get_login_screen()
# Push the the most rigth button to see the Required fields
# it should be either 'Reset' or 'Reset and Login' or 'Login' button
self.button_click_on_login_screen(form_data['buttons'][-1][0])
alerts = self.find_xelements(
".//*[@data-name][contains(@class, 'alert-danger')]", login_screen
)
required_msgs = form_data['required_msg']
self.assert_validations_equal(alerts, required_msgs)
def load_reset_and_login_view(self):
self.load()
assert self.login_screen_visible()
username = loginscreen.PKEY
current_password = loginscreen.PASSWD_ITEST_USER
login_screen = self.get_login_screen()
username_field = self.get_input_field('username', login_screen)
cur_pass_field = self.get_input_field('password', login_screen)
username_field.send_keys(username)
cur_pass_field.send_keys(current_password)
cur_pass_field.send_keys(Keys.RETURN)
self.wait()
self.assert_notification(
type='info',
assert_text=(
'Your password has expired. Please enter a new password.'
)
)
def check_cancel(self):
"""
Check 'login' view after a cancel of password reset
"""
self.button_click_on_login_screen('cancel')
self.check_elements_of_form(loginscreen.FILLED_LOGIN_FORM)
@screenshot
def test_reset_password_view(self):
self.load_url('/'.join((self.get_base_url(), 'reset_password.html')))
assert self.login_screen_visible()
self.check_elements_of_form(loginscreen.RESET_PASSWORD_FORM)
self.check_alerts(loginscreen.RESET_PASSWORD_FORM)
username = loginscreen.PKEY
current_password = loginscreen.PASSWD_ITEST_USER
new_password = loginscreen.PASSWD_ITEST_USER_NEW
self.reset_password(username, current_password, new_password)
self.relogin_with_new_password()
@screenshot
def test_reset_password_view_with_redirect(self):
redir_url = self.get_base_url().lower()
encoded_redir_url = urllib.parse.urlencode({'url': redir_url})
target_url = '/'.join((self.get_base_url(), 'reset_password.html?{}'))
self.load_url(target_url.format(encoded_redir_url))
assert self.login_screen_visible()
self.check_elements_of_form(loginscreen.RESET_PASSWORD_FORM)
self.check_alerts(loginscreen.RESET_PASSWORD_FORM)
username = loginscreen.PKEY
current_password = loginscreen.PASSWD_ITEST_USER
new_password = loginscreen.PASSWD_ITEST_USER_NEW
self.reset_password(username, current_password, new_password,
link_text='Continue to next page',
link_url=redir_url,
)
self.relogin_with_new_password()
@screenshot
def test_reset_password_view_with_delayed_redirect(self):
redir_url = self.get_base_url().lower() + '/'
encoded_redir_url = urllib.parse.urlencode(
{'url': redir_url, 'delay': 5}
)
target_url = '/'.join((self.get_base_url(), 'reset_password.html?{}'))
self.load_url(target_url.format(encoded_redir_url))
assert self.login_screen_visible()
self.check_elements_of_form(loginscreen.RESET_PASSWORD_FORM)
self.check_alerts(loginscreen.RESET_PASSWORD_FORM)
username = loginscreen.PKEY
current_password = loginscreen.PASSWD_ITEST_USER
new_password = loginscreen.PASSWD_ITEST_USER_NEW
self.reset_password(username, current_password, new_password,
link_text='Continue to next page',
link_url=redir_url,
)
self.assert_notification(type='info',
assert_text='You will be redirected in ')
self.wait(3)
# check url after start delay timer, but before end
assert self.driver.current_url != redir_url
self.wait(5)
assert self.driver.current_url == redir_url
self.relogin_with_new_password()
@screenshot
def test_reset_password_and_login_view(self):
self.load_reset_and_login_view()
self.check_elements_of_form(loginscreen.RESET_AND_LOGIN_FORM)
self.check_alerts(loginscreen.RESET_AND_LOGIN_FORM)
# check click on 'Cancel' button
self.check_cancel()
self.button_click_on_login_screen('login')
# check if user is not logged
assert not self.logged_in()
current_password = loginscreen.PASSWD_ITEST_USER
new_password = loginscreen.PASSWD_ITEST_USER_NEW
# username is already here, do not fill up
self.reset_password(current_password=current_password,
new_password=new_password)
# waiting for auto login process
self.wait(5)
assert self.logged_in()
# check again if new password is valid
self.relogin_with_new_password()
@screenshot
def test_login_view(self):
self.load()
# check empty 'login' view
self.check_elements_of_form(loginscreen.EMPTY_LOGIN_FORM)
self.check_alerts(loginscreen.EMPTY_LOGIN_FORM)
# check non empty 'login' view
login_screen = self.get_login_screen()
username_field = self.get_input_field('username', login_screen)
username_field.send_keys(loginscreen.PKEY)
self.wait(0.5)
self.check_elements_of_form(loginscreen.LOGIN_FORM)
self.check_alerts(loginscreen.LOGIN_FORM)