URL: https://github.com/freeipa/freeipa/pull/1838 Author: Rezney Title: #1838: ui_tests: extend test_user suite Action: opened
PR body: """ ui_tests: extend test_user suite Extend WebUI test_user suite with the following test cases: test_add_user_special test_user_misc test_ssh_keys test_add_delete_undo_reset test_disable_delete_admin test_login_without_username https://pagure.io/freeipa/issue/7507 """ To pull the PR as Git branch: git remote add ghfreeipa https://github.com/freeipa/freeipa git fetch ghfreeipa pull/1838/head:pr1838 git checkout pr1838
From 83d11b8233db91a3d61555f4a0c111378e1d1ba4 Mon Sep 17 00:00:00 2001 From: Michal Reznik <mrez...@redhat.com> Date: Thu, 19 Apr 2018 15:19:37 +0200 Subject: [PATCH 1/3] ui_tests: extend test_user suite Extend WebUI test_user suite with the following test cases: test_add_user_special test_user_misc test_ssh_keys test_add_delete_undo_reset test_disable_delete_admin test_login_without_username https://pagure.io/freeipa/issue/7507 --- ipatests/test_webui/data_user.py | 155 ++++++++++++++ ipatests/test_webui/test_user.py | 443 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 593 insertions(+), 5 deletions(-) diff --git a/ipatests/test_webui/data_user.py b/ipatests/test_webui/data_user.py index c5ed796c7b..87048bba5b 100644 --- a/ipatests/test_webui/data_user.py +++ b/ipatests/test_webui/data_user.py @@ -17,6 +17,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. + ENTITY = 'user' PKEY = 'itest-user' @@ -35,17 +36,67 @@ 'mod': [ ('textbox', 'givenname', 'OtherName'), ('textbox', 'sn', 'OtherSurname'), + ('textbox', 'initials', 'NOS'), + ('textbox', 'loginshell', '/bin/csh'), + ('textbox', 'homedirectory', '/home/alias'), ('multivalued', 'telephonenumber', [ ('add', '123456789'), ('add', '987654321'), ]), + ('multivalued', 'mail', [ + ('add', 'o...@ipa.test'), + ('add', 't...@ipa.test'), + ('add', 'th...@ipa.test'), + ]), + ('multivalued', 'pager', [ + ('add', '1234567'), + ('add', '7654321'), + ]), + ('multivalued', 'mobile', [ + ('add', '001123456'), + ('add', '001654321'), + ]), + ('multivalued', 'facsimiletelephonenumber', [ + ('add', '1122334'), + ('add', '4332211'), + ]), + ('textbox', 'street', 'Wonderwall ave.'), + ('textbox', 'l', 'Atlantis'), + ('textbox', 'st', 'Universe'), + ('textbox', 'postalcode', '61600'), + ('multivalued', 'carlicense', [ + ('add', 'ZLA-1336'), + ]), + ('textbox', 'ou', 'QE'), ('combobox', 'manager', 'admin'), + ('textbox', 'employeenumber', '123'), + ('textbox', 'employeetype', 'contractor'), + ('textbox', 'preferredlanguage', 'Spanish'), ], 'mod_v': [ ('textbox', 'givenname', 'OtherName'), ('textbox', 'sn', 'OtherSurname'), + ('textbox', 'initials', 'NOS'), + ('textbox', 'loginshell', '/bin/csh'), + ('textbox', 'homedirectory', '/home/alias'), + ('label', 'krbmaxrenewableage', '604800'), + ('label', 'krbmaxticketlife', '86400'), ('multivalued', 'telephonenumber', ['123456789', '987654321']), + ('multivalued', 'mail', ['o...@ipa.test', 't...@ipa.test', + 'th...@ipa.test']), + ('multivalued', 'pager', ['1234567', '7654321']), + ('multivalued', 'mobile', ['001123456', '001654321']), + ('multivalued', 'facsimiletelephonenumber', ['1122334', '4332211']), + ('textbox', 'street', 'Wonderwall ave.'), + ('textbox', 'l', 'Atlantis'), + ('textbox', 'st', 'Universe'), + ('textbox', 'postalcode', '61600'), + ('multivalued', 'carlicense', ['ZLA-1336']), + ('textbox', 'ou', 'QE'), ('combobox', 'manager', 'admin'), + ('textbox', 'employeenumber', '123'), + ('textbox', 'employeetype', 'contractor'), + ('textbox', 'preferredlanguage', 'Spanish'), ], } @@ -71,6 +122,8 @@ ('textbox', 'givenname', 'Name3'), ('textbox', 'sn', 'Surname3'), ('checkbox', 'noprivate', None), + ('password', 'userpassword', 'Supersecret123'), + ('password', 'userpassword2', 'Supersecret123'), ] } @@ -85,3 +138,105 @@ ('combobox', 'gidnumber', '77777'), ] } + +PKEY5 = '1spe.cial_us-er$' +DATA5 = { + 'pkey': PKEY5, + 'add': [ + ('textbox', 'uid', PKEY5), + ('textbox', 'givenname', 'S$p|e>c--i_a%l_'), + ('textbox', 'sn', '%U&s?e+r'), + ('password', 'userpassword', '!!!@@@###$$$'), + ('password', 'userpassword2', '!!!@@@###$$$'), + ] +} + +PKEY6 = 'itest-user' * 5 +DATA6 = { + 'pkey': PKEY6, + 'add': [ + ('textbox', 'uid', PKEY6), + ('textbox', 'givenname', 'Name8'), + ('textbox', 'sn', 'Surname8'), + ] +} + +PKEY7 = 'itest-user-passwd-leading-space' +DATA7 = { + 'pkey': PKEY7, + 'add': [ + ('textbox', 'uid', PKEY7), + ('textbox', 'givenname', 'Name7'), + ('textbox', 'sn', 'Surname7'), + ('password', 'userpassword', ' Password123 '), + ('password', 'userpassword2', ' Password123 '), + ] +} + +PKEY8 = 'itest-user-passwd-trailing-space' +DATA8 = { + 'pkey': PKEY8, + 'add': [ + ('textbox', 'uid', PKEY8), + ('textbox', 'givenname', 'Name8'), + ('textbox', 'sn', 'Surname8'), + ('password', 'userpassword', 'Password123 '), + ('password', 'userpassword2', 'Password123 '), + ] +} + +PKEY9 = 'itest-user-passwd-mismatch' +DATA9 = { + 'pkey': PKEY9, + 'add': [ + ('textbox', 'uid', PKEY9), + ('textbox', 'givenname', 'Name9'), + ('textbox', 'sn', 'Surname9'), + ('password', 'userpassword', 'Password123'), + ('password', 'userpassword2', 'Password12'), + ] +} + +PKEY10 = 'itest-user-no-login' +DATA10 = { + 'pkey': PKEY10, + 'add': [ + ('textbox', 'givenname', 'Name10'), + ('textbox', 'sn', 'Surname10'), + ('password', 'userpassword', 'Password123'), + ('password', 'userpassword2', 'Password123'), + ] +} + +SSH_RSA = ( + 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDBVmLXpTDhrYkABOPlADFk' + 'GV8/QfgQqUQ0xn29hk18t/NTEQOW/Daq4EF84e9aTiopRXIk7jahBLzwWTZI' + 'WwuvegGYqs89bDhUHZEnS9TBfXkkYq9LamlEVooR5kxb/kPtCnmMMXhQUOzH' + 'xqakuZiN4AduRCzaecu0mearVjZWAChM3fYp4sMXKoRzek2F/xOUh81GxrW0' + 'kbhpbaeXd6oG8p6AC3QCrEspzX78WEOCPSTJlx/BAv77A27b5zO2cSeZNbZq' + 'XFqaQQj8AX46qoATWLhOnokoE2xeJTKikG/4nmc3D2KO6SRh66dEQWtJuVVw' + 'ZqgQRdaseDjjgR1FKbC1' +) + +SSH_RSA2 = ( + 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDBVmLXpTDhrYkABOPlADFk' + 'GV8/QfgQqUQ0xn29hk18t/NTEQOW/Daq4EF84e9aTiopRXIk7jahBLzwWTZI' + 'WwuvegGYqs89bDhUHZEnS9TBfXkkYq9LamlEVooR5kxb/kPtCnmMMXhQUOzH' + 'xqakuZiN4AduRCzaecu0mearVjZWAChM3fYp4sMXKoRzek2F/xOUh81GxrW0' + 'kbhpbaeXd6oG8p6AC3QCrEspzX78WEOCPSTJlx/BAv77A27b5zO2cSeZNbZq' + 'XFqaQQj8AX46qoATWLhOnokoE2xeJTKikG/4nmc3D2KO6SRh66dEQWtJuVVw' + 'ZqgQRdaseDjjgR1FK222' +) + +SSH_DSA = ( + 'ssh-dss AAAAB3NzaC1kc3MAAACBAKSh2gHHQ0lsPEKZU7utlx3I/M8FtSMx' + '+MtE+QjReRPIWHjwTHLC6j5Bh2A8kwwiiqiiiDbvkJPgV3+5zmrnWvTICzet' + 'zS4vOgk6ymDux2J/1JPRb6c2yjjFaYL0SndC6abdgohyUAJPzNkgEhnQll/o' + 'QeavJXzLyonaX1wcl+R1AAAAFQCuMfl69Zyrx5B1qZmUsRVqG24W7wAAAIEA' + 'pFVe4JOuhRjSufJXMV+nzoqkhIhDEOYLqcpnq3cUrvBFEkQ5tKyYephFJxq+' + 'u7xkFx4d/K5eC7NH6/o/ziBocKJ7ESXBihC2lGLsHnWqreN9vCBihspBij+n' + '/wUpgcq2dMBDC2BzqCfdashM1xHm1XahqCvV87pvjRhl1avy+K0AAACAEQKs' + '3kKhEB/WGuAQa+tojRyIwtBc4lzZuJia4qOg6R4oSviKINwEtFtH08snteGn' + 'c4qiZ6XBrfYJT2VS1yjFVj+OmGSHmrX1GdfRfco8Y1ZYC7VLwt20dutw9hKK' + 'MSHI9NrJ5oOZ/GONlaKuqzKtTNb/vOIn/8yz52Od3X2Ehh1=' +) diff --git a/ipatests/test_webui/test_user.py b/ipatests/test_webui/test_user.py index 90e74eea1e..10e5a1f8b0 100644 --- a/ipatests/test_webui/test_user.py +++ b/ipatests/test_webui/test_user.py @@ -30,18 +30,33 @@ import ipatests.test_webui.test_rbac as rbac import ipatests.test_webui.data_sudo as sudo import pytest +import re try: from selenium.webdriver.common.by import By + from selenium.webdriver.common.keys import Keys + from selenium.webdriver.common.action_chains import ActionChains except ImportError: pass - +EMPTY_MOD = 'no modifications to be performed' +USR_EXIST = 'user with name "{}" already exists' +USR_ADDED = 'User successfully added' +INVALID_SSH_KEY = "invalid 'sshpubkey': invalid SSH public key" +INV_FIRSTNAME = ("invalid 'first': Leading and trailing spaces are " + "not allowed") +FIELD_REQ = 'Required field' +ERR_INCLUDE = 'may only include letters, numbers, _, -, . and $' +ERR_MISMATCH = 'Passwords must match' +ERR_ADMIN_DEL = ('admin cannot be deleted or disabled because it is the last ' + 'member of group admins') USR_EXIST = 'user with name "{}" already exists' ENTRY_EXIST = 'This entry already exists' ACTIVE_ERR = 'active user with name "{}" already exists' DISABLED = 'This entry is already disabled' - +LONG_LOGIN = "invalid 'login': can be at most 32 characters" +INV_PASSWD = ("invalid 'password': Leading and trailing spaces are " + "not allowed") @pytest.mark.tier1 class user_tasks(UI_driver): @@ -50,6 +65,23 @@ def load_file(self, path): content = file_d.read() return content + def add_default_email_for_validation(self, data): + """ + E-mail is generated automatically and we do not know domain yet in + data_user so in order to validate all mail fields we need to get it + there. + """ + # getting domain from hostname due possible different DNS setup + domain = '.'.join(self.config.get('ipa_server').split('.')[1:]) + user = data.get('pkey') + mail = '{}@{}'.format(user, domain) + + for ele in data['mod_v']: + if 'mail' in ele: + ele[2].append(mail) + + return data + @pytest.mark.tier1 class test_user(user_tasks): @@ -60,7 +92,8 @@ def test_crud(self): Basic CRUD: user """ self.init_app() - self.basic_crud(user.ENTITY, user.DATA) + data = self.add_default_email_for_validation(user.DATA) + self.basic_crud(user.ENTITY, data) @screenshot def test_associations(self): @@ -376,6 +409,248 @@ def fill_password_dialog(self, password, current=None): self.wait_for_request(n=3) self.assert_no_error_dialog() + @screenshot + def test_login_without_username(self): + + self.init_app(login='', password='xxx123', skip_username=True) + + alert_e = self.find('.alert[data-name="username"]', + By.CSS_SELECTOR) + assert 'Username: Required field' in alert_e.text, 'Alert expected' + assert self.login_screen_visible() + + @screenshot + def test_disable_delete_admin(self): + """ + Test disabling/deleting admin is not allowed + """ + self.init_app() + self.navigate_to_entity('user') + + # try to disable admin user + self.select_record('admin') + self.facet_button_click('disable') + self.dialog_button_click('ok') + self.assert_last_error_dialog(ERR_ADMIN_DEL, details=True) + self.dialog_button_click('ok') + self.assert_record('admin') + + # try to delete admin user. Later we are + # confirming by keyboard to test also upstream ticket 4097 + self.select_record('admin') + self.facet_button_click('remove') + self.dialog_button_click('ok') + self.assert_last_error_dialog(ERR_ADMIN_DEL, details=True) + actions = ActionChains(self.driver) + actions.send_keys(Keys.TAB) + actions.send_keys(Keys.ENTER).perform() + # self.dialog_button_click('ok') + self.wait(0.5) + self.assert_record('admin') + + @screenshot + def test_add_user_special(self): + """ + Test various add user special cases + """ + + self.init_app() + + # Test invalid characters (#@*?) in login + self.navigate_to_entity('user') + self.facet_button_click('add') + self.fill_textbox('uid', 'itest-user#') + self.assert_field_validation(ERR_INCLUDE) + self.fill_textbox('uid', 'itest-user@') + self.assert_field_validation(ERR_INCLUDE) + self.fill_textbox('uid', 'itest-user*') + self.assert_field_validation(ERR_INCLUDE) + self.fill_textbox('uid', 'itest-user?') + self.assert_field_validation(ERR_INCLUDE) + self.dialog_button_click('cancel') + + # Add an user with special chars + self.init_app() + self.basic_crud(user.ENTITY, user.DATA5) + + # Add an user with long login (should FAIL) + self.init_app() + self.add_record(user.ENTITY, user.DATA6, negative=True) + self.assert_last_error_dialog(expected_err=LONG_LOGIN) + self.close_all_dialogs() + + # Test password mismatch + self.add_record(user.ENTITY, user.DATA9, negative=True) + pass_e = self.find('.widget[name="userpassword2"]', By.CSS_SELECTOR) + self.assert_field_validation(ERR_MISMATCH, parent=pass_e) + self.dialog_button_click('cancel') + self.assert_record(user.DATA9.get('pkey'), negative=True) + + # test add and edit record + self.add_record(user.ENTITY, user.DATA2, dialog_btn='add_and_edit') + self.action_list_action('delete_active_user') + + # click add and cancel + self.add_record(user.ENTITY, user.DATA, dialog_btn='cancel') + + # add leading space before password (should FAIL) + self.navigate_to_entity('user') + self.facet_button_click('add') + self.fill_fields(user.DATA7['add']) + self.dialog_button_click('add') + self.assert_last_error_dialog(INV_PASSWD) + self.close_all_dialogs() + + # add trailing space before password (should FAIL) + self.navigate_to_entity('user') + self.facet_button_click('add') + self.fill_fields(user.DATA8['add']) + self.dialog_button_click('add') + self.assert_last_error_dialog(INV_PASSWD) + self.close_all_dialogs() + + # add user using enter + self.add_record(user.ENTITY, user.DATA2, negative=True) + actions = ActionChains(self.driver) + actions.send_keys(Keys.ENTER).perform() + self.wait() + self.assert_notification(assert_text=USR_ADDED) + self.assert_record(user.PKEY2) + + # delete user using enter + self.select_record(user.PKEY2) + self.facet_button_click('remove') + actions.send_keys(Keys.ENTER).perform() + self.wait(0.5) + self.assert_notification(assert_text='1 item(s) deleted') + self.assert_record(user.PKEY2, negative=True) + + @screenshot + def test_add_delete_undo_reset_multivalue(self): + """ + Test add and delete multivalue with reset and undo + """ + self.init_app() + + # getting domain from hostname due possible different DNS setup + domain = '.'.join(self.config.get('ipa_server').split('.')[1:]) + first_mail = '{}@{}'.format(user.DATA.get('pkey'), domain) + + self.add_record(user.ENTITY, user.DATA) + self.navigate_to_record(user.DATA.get('pkey')) + + # add a new mail (without save) and reset + self.add_multivalued('mail', 't...@ipa.test') + self.assert_undo_button('mail') + self.facet_button_click('revert') + self.assert_undo_button('mail', visible=False) + + # click at delete on the first mail and reset + self.del_multivalued('mail', first_mail) + self.assert_undo_button('mail') + self.facet_button_click('revert') + self.assert_undo_button('mail', visible=False) + + # edit the first mail and reset + self.edit_multivalued('mail', first_mail, 't...@ipa.test') + self.assert_undo_button('mail') + self.facet_button_click('revert') + self.assert_undo_button('mail', visible=False) + + # add a new mail and undo + self.add_multivalued('mail', 't...@ipa.test') + self.assert_undo_button('mail') + self.undo_multivalued('mail', 't...@ipa.test') + self.assert_undo_button('mail', visible=False) + + # edit the first mail and undo + self.edit_multivalued('mail', first_mail, 't...@ipa.test') + self.assert_undo_button('mail') + self.undo_multivalued('mail', 't...@ipa.test') + self.assert_undo_button('mail', visible=False) + + # cleanup + self.delete_record(user.DATA.get('pkey')) + + def test_user_misc(self): + """ + Test various miscellaneous test cases under one roof to save init time + """ + self.init_app() + + # add already existing user (should fail) / also test ticket 4098 + self.add_record(user.ENTITY, user.DATA) + self.add_record(user.ENTITY, user.DATA, negative=True, + pre_delete=False) + self.assert_last_error_dialog(USR_EXIST.format(user.PKEY)) + actions = ActionChains(self.driver) + actions.send_keys(Keys.TAB) + actions.send_keys(Keys.ENTER).perform() + self.wait(0.5) + self.dialog_button_click('cancel') + + # add user without login name + self.add_record(user.ENTITY, user.DATA10) + self.assert_record('nsurname10') + + # try to add same user without login name again (should fail) + self.add_record(user.ENTITY, user.DATA10, negative=True, + pre_delete=False) + self.assert_last_error_dialog(USR_EXIST.format('nsurname10')) + self.close_all_dialogs() + + # try to modify user`s UID to -1 (should fail) + self.navigate_to_record(user.PKEY) + self.mod_record( + user.ENTITY, {'mod': [('textbox', 'uidnumber', '-1')]}, + negative=True) + uid_e = self.find('.widget[name="uidnumber"]', By.CSS_SELECTOR) + self.assert_field_validation('Minimum value is 1', parent=uid_e) + self.facet_button_click('revert') + + # edit user`s "First name" to value with leading space (should fail) + self.fill_input('givenname', ' leading_space') + self.facet_button_click('save') + self.assert_last_error_dialog(INV_FIRSTNAME) + self.dialog_button_click('cancel') + + # edit user`s "First name" to value with trailing space (should fail) + self.fill_input('givenname', 'trailing_space ') + self.facet_button_click('save') + self.assert_last_error_dialog(INV_FIRSTNAME) + self.dialog_button_click('cancel') + + # try with blank "First name" (should fail) + gn_input_s = "input[type='text'][name='givenname']" + gn_input_el = self.find(gn_input_s, By.CSS_SELECTOR, strict=True) + gn_input_el.clear() + gn_input_el.send_keys(Keys.BACKSPACE) + + self.facet_button_click('save') + gn_e = self.find('.widget[name="givenname"]', By.CSS_SELECTOR) + self.assert_field_validation(FIELD_REQ, parent=gn_e) + self.close_notifications() + + # check we can see version in "profile > about", ticket 4018 + self.profile_menu_action('about') + + about_text = self.get_text('div[data-name="version_dialog"] p') + ver_re = re.compile('version:\s.*') + assert re.search(ver_re, about_text), 'Version not found' + self.dialog_button_click('ok') + + # search user / multiple users + self.navigate_to_entity('user') + self.wait(0.5) + self.find_record('user', user.DATA) + self.add_record(user.ENTITY, user.DATA2) + self.find_record('user', user.DATA2) + # search for both users (just 'itest-user' will do) + self.find_record('user', user.DATA) + self.assert_record(user.PKEY2) + + # cleanup + self.delete_record([user.PKEY, user.PKEY2, user.PKEY10, 'nsurname10']) @pytest.mark.tier1 class test_user_no_private_group(UI_driver): @@ -588,6 +863,164 @@ def test_life_cycles(self): self.assert_record_value('Enabled', [user.PKEY, user.PKEY2], 'nsaccountlock') - # cleanup + # cleanup and check for ticket 4245 (select all should not remain + # checked after delete action self.navigate_to_entity('user') - self.delete_record([user.PKEY, user.PKEY2]) + select_all_btn = self.find('input[title="Select All"]', + By.CSS_SELECTOR) + select_all_btn.click() + self.facet_button_click('remove') + self.dialog_button_click('ok') + self.dialog_button_click('ok') + self.assert_value_checked('admin', 'uid', negative=True) + + +@pytest.mark.tier1 +class TestSSHkeys(UI_driver): + + def assert_num_ssh_keys(self, num): + """ + Assert number of SSH keys we have associated with the user + """ + + s_keys = 'div[name="ipasshpubkey"] .widget[name="value"]' + ssh_keys = self.find(s_keys, By.CSS_SELECTOR, many=True) + + num_ssh_keys = len(ssh_keys) if not None else 0 + + assert num_ssh_keys == num, \ + ('Number of SSH keys does not match. ' + 'Expected: {}, Got: {}'.format(num, num_ssh_keys)) + + def undo_ssh_keys(self, btn_name='undo'): + """ + Undo either one SSH key or all of them + + Possible options: + btn_name='undo' + btn_name='undo_all' + """ + + s_undo = 'div[name="ipasshpubkey"] button[name="{}"]'.format(btn_name) + undo = self.find(s_undo, By.CSS_SELECTOR, strict=True) + undo.click() + self.wait(0.6) + + @screenshot + def test_ssh_keys(self): + + self.init_app() + + # add and undo SSH key + self.add_sshkey_to_user(user.SSH_RSA, save=False) + self.assert_num_ssh_keys(1) + self.undo_ssh_keys() + self.assert_num_ssh_keys(0) + + # add and undo 2 SSH keys (using undo all) + ssh_keys = [user.SSH_RSA, user.SSH_DSA] + + self.add_sshkey_to_user(ssh_keys, save=False) + self.assert_num_ssh_keys(2) + self.undo_ssh_keys(btn_name='undo_all') + self.assert_num_ssh_keys(0) + + # add SSH key and refresh + self.add_sshkey_to_user(user.SSH_RSA, save=False) + self.assert_num_ssh_keys(1) + self.facet_button_click('refresh') + self.assert_num_ssh_keys(0) + + # add SSH key and revert + self.add_sshkey_to_user(user.SSH_RSA, save=False) + self.assert_num_ssh_keys(1) + self.facet_button_click('revert') + self.assert_num_ssh_keys(0) + + # add SSH key, move elsewhere and cancel. + self.add_sshkey_to_user(user.SSH_RSA, save=False) + self.assert_num_ssh_keys(1) + self.switch_to_facet('memberof_group') + self.dialog_button_click('cancel') + self.assert_num_ssh_keys(1) + self.undo_ssh_keys() + + # add SSH key, move elsewhere and click reset button. + self.add_sshkey_to_user(user.SSH_RSA, save=False) + self.assert_num_ssh_keys(1) + self.switch_to_facet('memberof_group') + self.wait_for_request() + self.dialog_button_click('revert') + self.wait() + self.switch_to_facet('details') + self.assert_num_ssh_keys(0) + + # add SSH key, move elsewhere and click save button. + self.add_sshkey_to_user(user.SSH_RSA, save=False) + self.assert_num_ssh_keys(1) + self.switch_to_facet('memberof_group') + self.wait() + self.dialog_button_click('save') + self.wait_for_request(n=4) + self.switch_to_facet('details') + self.assert_num_ssh_keys(1) + self.delete_user_sshkeys(navigate=False) + + # add,save and delete RSA and DSA keys + keys = [user.SSH_RSA, user.SSH_DSA] + + self.add_sshkey_to_user(keys) + self.assert_num_ssh_keys(2) + self.delete_user_sshkeys() + self.assert_num_ssh_keys(0) + + # add RSA SSH keys with trailing space and "=" sign at the end + keys = [user.SSH_RSA+" ", user.SSH_RSA2+"="] + + self.add_sshkey_to_user(keys) + self.assert_num_ssh_keys(2) + self.delete_user_sshkeys() + self.assert_num_ssh_keys(0) + + # lets try to add empty SSH key (should fail) + self.add_sshkey_to_user('') + self.assert_last_error_dialog(EMPTY_MOD) + self.dialog_button_click('cancel') + self.undo_ssh_keys() + + # try to add invalid SSH key + self.add_sshkey_to_user('invalid_key') + self.assert_last_error_dialog(INVALID_SSH_KEY) + self.dialog_button_click('cancel') + self.undo_ssh_keys() + + # add duplicate SSH keys + self.add_sshkey_to_user(user.SSH_RSA) + self.add_sshkey_to_user(user.SSH_RSA, save=False) + self.facet_button_click('save') + self.assert_last_error_dialog(EMPTY_MOD) + self.dialog_button_click('cancel') + + # test SSH key edit when user lacks write rights for related attribute + # see ticket 3800 + self.add_record(user.ENTITY, [user.DATA2, user.DATA5]) + self.add_sshkey_to_user(user.SSH_RSA, user.PKEY2) + + self.logout() + self.init_app(user.PKEY5, '!!!@@@###$$$') + + self.navigate_to_record(user.PKEY2, entity=user.ENTITY) + + show_ssh_key_btn = self.find('div.widget .btn[name="ipasshpubkey-0"]', + By.CSS_SELECTOR) + show_ssh_key_btn.click() + ssh_key_dialog = self.find('.modal-dialog textarea', By.CSS_SELECTOR) + + assert ssh_key_dialog.get_attribute('readonly') == 'true' + self.dialog_button_click('cancel') + self.logout() + self.init_app() + + # cleanup + self.delete_record([user.PKEY2, user.PKEY5]) + self.delete_user_sshkeys() From 0f1729e6576d131495bacf83b2b32a8e14a9e03a Mon Sep 17 00:00:00 2001 From: Michal Reznik <mrez...@redhat.com> Date: Thu, 19 Apr 2018 15:27:42 +0200 Subject: [PATCH 2/3] ui_driver: extension and modifications related to test_user In this patch we tune init_app() and login() in order to test login without username. Then we add edit_multivalued and undo_multivalued to test "undo" and "reset" buttons. Also there is a new boolean "negative" in mod_record() to switch button assertion. Later ssh_key methods were fine-tuned a little to add more keys and delete all of them. Lastly new method assert_value_checked() was introduced to assert whether a particular record is checked. https://pagure.io/freeipa/issue/7507 --- ipatests/test_webui/ui_driver.py | 126 +++++++++++++++++++++++++++++++++------ 1 file changed, 107 insertions(+), 19 deletions(-) diff --git a/ipatests/test_webui/ui_driver.py b/ipatests/test_webui/ui_driver.py index 65cd66afe2..0cc1b9f2ed 100644 --- a/ipatests/test_webui/ui_driver.py +++ b/ipatests/test_webui/ui_driver.py @@ -340,13 +340,13 @@ def xpath_has_val(self, attr, val): """ return "contains(concat(' ',normalize-space(@%s), ' '),' %s ')" % (attr, val) - def init_app(self, login=None, password=None): + def init_app(self, login=None, password=None, skip_username=False): """ Load and login """ self.load() self.wait(0.5) - self.login(login, password) + self.login(login, password, skip_username=skip_username) # metadata + default page self.wait_for_request(n=5) @@ -377,7 +377,8 @@ def load(self): WebDriverWait(self.driver, 10).until(lambda d: runner.files_loaded()) self.wait_for_request() - def login(self, login=None, password=None, new_password=None): + def login(self, login=None, password=None, new_password=None, + skip_username=False): """ Log in if user is not logged in. """ @@ -390,6 +391,8 @@ def login(self, login=None, password=None, new_password=None): password = self.config['ipa_password'] if not new_password: new_password = password + if skip_username: + login = '' auth = self.get_login_screen() login_tb = self.find("//input[@type='text'][@name='username']", @@ -424,6 +427,8 @@ def logged_in(self): def logout(self): self.profile_menu_action('logout') + # it may take some time to get login screen visible + self.wait(5) assert self.login_screen_visible() def get_login_screen(self): @@ -815,6 +820,46 @@ def add_multivalued(self, name, value, parent=None): last = inputs[-1] last.send_keys(value) + def edit_multivalued(self, name, value, new_value, parent=None): + """ + Edit multivalued textbox + """ + if not parent: + parent = self.get_form() + s = "div[name='%s'].multivalued-widget" % name + w = self.find(s, By.CSS_SELECTOR, parent, strict=True) + s = "div[name=value] input" + inputs = self.find(s, By.CSS_SELECTOR, w, many=True) + + for i in inputs: + val = i.get_attribute('value') + if val == value: + i.clear() + i.send_keys(new_value) + + def undo_multivalued(self, name, value, parent=None): + """ + Undo multivalued change + """ + if not parent: + parent = self.get_form() + s = "div[name='%s'].multivalued-widget" % name + w = self.find(s, By.CSS_SELECTOR, parent, strict=True) + s = "div[name=value] input" + inputs = self.find(s, By.CSS_SELECTOR, w, many=True) + clicked = False + for i in inputs: + val = i.get_attribute('value') + n = i.get_attribute('name') + if val == value: + s = "input[name='%s'] ~ .input-group-btn button[name=undo]" % n + link = self.find(s, By.CSS_SELECTOR, w, strict=True) + link.click() + self.wait() + clicked = True + + assert clicked, 'Value was not undone: %s' % value + def del_multivalued(self, name, value, parent=None): """ Mark value in multivalued textbox as deleted. @@ -1369,7 +1414,8 @@ def add_record(self, entity, data, facet='search', facet_btn='add', new_count = len(self.get_rows()) self.assert_row_count(count, new_count) - def mod_record(self, entity, data, facet='details', facet_btn='save'): + def mod_record(self, entity, data, facet='details', facet_btn='save', + negative=False): """ Mod record @@ -1384,6 +1430,9 @@ def mod_record(self, entity, data, facet='details', facet_btn='save'): self.facet_button_click(facet_btn) self.wait_for_request() self.wait_for_request() + + if negative: + return self.assert_facet_button_enabled(facet_btn, enabled=False) def basic_crud(self, entity, data, @@ -1700,32 +1749,53 @@ def get_t_vals(t): # add multiple at once and test table delete button self.add_table_associations(table, keys, delete=True) - def add_sshkey_to_user(self, user, ssh_key): + def add_sshkey_to_user(self, ssh_keys, user='admin', navigate=True, + save=True): """ Add ssh public key to particular user + ssh_keys (list): public ssh key(s) user (str): user to add the key to - ssh_key (str): public ssh key """ - self.navigate_to_entity('user') - self.navigate_to_record(user) - ssh_pub = 'div[name="ipasshpubkey"] button[name="add"]' - self.find(ssh_pub, By.CSS_SELECTOR).click() - self.wait() - self.driver.switch_to.active_element.send_keys(ssh_key) - self.dialog_button_click('update') - self.facet_button_click('save') + if type(ssh_keys) is not list: + ssh_keys = [ssh_keys] + + if navigate: + self.navigate_to_entity('user') + self.navigate_to_record(user) + + for key in ssh_keys: + s_add = 'div[name="ipasshpubkey"] button[name="add"]' + ssh_add_btn = self.find(s_add, By.CSS_SELECTOR, strict=True) + ssh_add_btn.click() + self.wait() + s_text_area = 'textarea.certificate' + text_area = self.find(s_text_area, By.CSS_SELECTOR, strict=True) + text_area.send_keys(key) + self.wait() + self.dialog_button_click('update') - def delete_user_sshkey(self, user): + # sometimes we do not want to save e.g. in order to test undo buttons + if save: + self.facet_button_click('save') + + def delete_user_sshkeys(self, user='admin', navigate=True): """ - Delete ssh public key of particular user + Delete all ssh public keys of particular user """ - self.navigate_to_entity('user') - self.navigate_to_record(user) + + if navigate: + self.navigate_to_entity('user') + self.navigate_to_record(user) ssh_pub = 'div[name="ipasshpubkey"] button[name="remove"]' - self.find(ssh_pub, By.CSS_SELECTOR).click() + rm_btns = self.find(ssh_pub, By.CSS_SELECTOR, many=True) + assert rm_btns, 'No SSH keys to be deleted' + + for btn in rm_btns: + btn.click() + self.facet_button_click('save') def run_cmd_on_ui_host(self, cmd): @@ -2080,3 +2150,21 @@ def assert_last_error_dialog(self, expected_err, details=False, else: s = '.modal-body div p' self.assert_text(s, expected_err, parent=err_dialog) + + def assert_value_checked(self, values, name, negative=False): + """ + Assert particular value is checked + """ + + if type(values) is not list: + values = [values] + + checked_values = self.get_field_checked(name) + + for value in values: + if negative: + assert not value in checked_values, ('{} checked while it ' + 'should not be'.format(value)) + else: + assert value in checked_values, ('{} NOT checked while it ' + 'should be'.format(value)) From f1fd6f76d289fabd5ff0032c36e6bbfddec53cf9 Mon Sep 17 00:00:00 2001 From: Michal Reznik <mrez...@redhat.com> Date: Thu, 19 Apr 2018 16:40:28 +0200 Subject: [PATCH 3/3] temp_commit: activate PRCI WebUI tests --- .freeipa-pr-ci.yaml | 158 +++------------------------------------------------- 1 file changed, 9 insertions(+), 149 deletions(-) diff --git a/.freeipa-pr-ci.yaml b/.freeipa-pr-ci.yaml index c95bef79e2..0a9b48719f 100644 --- a/.freeipa-pr-ci.yaml +++ b/.freeipa-pr-ci.yaml @@ -3,6 +3,10 @@ topologies: name: build cpu: 2 memory: 3800 + ipaserver: &ipaserver + name: ipaserver + cpu: 1 + memory: 2400 master_1repl: &master_1repl name: master_1repl cpu: 4 @@ -27,158 +31,14 @@ jobs: timeout: 1800 topology: *build - fedora-27/simple_replication: + fedora-27/test_webui: requires: [fedora-27/build] priority: 50 job: - class: RunPytest + class: RunWebuiTests args: build_url: '{fedora-27/build_url}' - test_suite: test_integration/test_simple_replication.py + test_suite: test_webui/ template: *ci-master-f27 - timeout: 3600 - topology: *master_1repl - - fedora-27/caless: - requires: [fedora-27/build] - priority: 50 - job: - class: RunPytest - args: - build_url: '{fedora-27/build_url}' - test_suite: test_integration/test_caless.py::TestServerReplicaCALessToCAFull - template: *ci-master-f27 - timeout: 3600 - topology: *master_1repl - - fedora-27/external_ca: - requires: [fedora-27/build] - priority: 50 - job: - class: RunPytest - args: - build_url: '{fedora-27/build_url}' - test_suite: test_integration/test_external_ca.py - template: *ci-master-f27 - timeout: 3600 - topology: *master_1repl - - fedora-27/test_topologies: - requires: [fedora-27/build] - priority: 50 - job: - class: RunPytest - args: - build_url: '{fedora-27/build_url}' - test_suite: test_integration/test_topologies.py - template: *ci-master-f27 - timeout: 3600 - topology: *master_1repl - - fedora-27/test_sudo: - requires: [fedora-27/build] - priority: 50 - job: - class: RunPytest - args: - build_url: '{fedora-27/build_url}' - test_suite: test_integration/test_sudo.py - template: *ci-master-f27 - timeout: 3600 - topology: *master_1repl_1client - - fedora-27/test_kerberos_flags: - requires: [fedora-27/build] - priority: 50 - job: - class: RunPytest - args: - build_url: '{fedora-27/build_url}' - test_suite: test_integration/test_kerberos_flags.py - template: *ci-master-f27 - timeout: 3600 - topology: *master_1repl_1client - - fedora-27/test_http_kdc_proxy: - requires: [fedora-27/build] - priority: 50 - job: - class: RunPytest - args: - build_url: '{fedora-27/build_url}' - test_suite: test_integration/test_http_kdc_proxy.py - template: *ci-master-f27 - timeout: 3600 - topology: *master_1repl_1client - - fedora-27/test_forced_client_enrolment: - requires: [fedora-27/build] - priority: 50 - job: - class: RunPytest - args: - build_url: '{fedora-27/build_url}' - test_suite: test_integration/test_forced_client_reenrollment.py - template: *ci-master-f27 - timeout: 3600 - topology: *master_1repl_1client - - fedora-27/test_advise: - requires: [fedora-27/build] - priority: 50 - job: - class: RunPytest - args: - build_url: '{fedora-27/build_url}' - test_suite: test_integration/test_advise.py - template: *ci-master-f27 - timeout: 3600 - topology: *master_1repl - - fedora-27/test_testconfig: - requires: [fedora-27/build] - priority: 50 - job: - class: RunPytest - args: - build_url: '{fedora-27/build_url}' - test_suite: test_integration/test_testconfig.py - template: *ci-master-f27 - timeout: 3600 - topology: *master_1repl - - fedora-27/test_service_permissions: - requires: [fedora-27/build] - priority: 50 - job: - class: RunPytest - args: - build_url: '{fedora-27/build_url}' - test_suite: test_integration/test_service_permissions.py - template: *ci-master-f27 - timeout: 3600 - topology: *master_1repl - - fedora-27/test_netgroup: - requires: [fedora-27/build] - priority: 50 - job: - class: RunPytest - args: - build_url: '{fedora-27/build_url}' - test_suite: test_integration/test_netgroup.py - template: *ci-master-f27 - timeout: 3600 - topology: *master_1repl - - fedora-27/test_vault: - requires: [fedora-27/build] - priority: 50 - job: - class: RunPytest - args: - build_url: '{fedora-27/build_url}' - test_suite: test_integration/test_vault.py - template: *ci-master-f27 - timeout: 3600 - topology: *master_1repl + timeout: 10000 + topology: *ipaserver
_______________________________________________ FreeIPA-devel mailing list -- freeipa-devel@lists.fedorahosted.org To unsubscribe send an email to freeipa-devel-le...@lists.fedorahosted.org