URL: https://github.com/freeipa/freeipa/pull/1869 Author: Rezney Title: #1869: [Backport][ipa-4-6] ui_tests: extend test_user suite Action: opened
PR body: """ This PR was opened automatically because PR #1838 was pushed to master and backport to ipa-4-6 is required. """ To pull the PR as Git branch: git remote add ghfreeipa https://github.com/freeipa/freeipa git fetch ghfreeipa pull/1869/head:pr1869 git checkout pr1869
From ac46e6f069f248c20e26a9a08d43661af49ab0fe 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 | 154 +++++++++++++++ ipatests/test_webui/test_user.py | 416 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 563 insertions(+), 7 deletions(-) diff --git a/ipatests/test_webui/data_user.py b/ipatests/test_webui/data_user.py index c5ed796c7b..ae62f72610 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'), ], } @@ -85,3 +136,106 @@ ('combobox', 'gidnumber', '77777'), ] } + +PKEY_SPECIAL_CHARS = '1spe.cial_us-er$' +PASSWD_SCECIAL_CHARS = '!!!@@@###$$$' +DATA_SPECIAL_CHARS = { + 'pkey': PKEY_SPECIAL_CHARS, + 'add': [ + ('textbox', 'uid', PKEY_SPECIAL_CHARS), + ('textbox', 'givenname', 'S$p|e>c--i_a%l_'), + ('textbox', 'sn', '%U&s?e+r'), + ('password', 'userpassword', PASSWD_SCECIAL_CHARS), + ('password', 'userpassword2', PASSWD_SCECIAL_CHARS), + ] +} + +PKEY_LONG_LOGIN = 'itest-user' * 5 +DATA_LONG_LOGIN = { + 'pkey': PKEY_LONG_LOGIN, + 'add': [ + ('textbox', 'uid', PKEY_LONG_LOGIN), + ('textbox', 'givenname', 'Name8'), + ('textbox', 'sn', 'Surname8'), + ] +} + +PKEY_PASSWD_LEAD_SPACE = 'itest-user-passwd-leading-space' +DATA_PASSWD_LEAD_SPACE = { + 'pkey': PKEY_PASSWD_LEAD_SPACE, + 'add': [ + ('textbox', 'uid', PKEY_PASSWD_LEAD_SPACE), + ('textbox', 'givenname', 'Name7'), + ('textbox', 'sn', 'Surname7'), + ('password', 'userpassword', ' Password123 '), + ('password', 'userpassword2', ' Password123 '), + ] +} + +PKEY_PASSWD_TRAIL_SPACE = 'itest-user-passwd-trailing-space' +DATA_PASSWD_TRAIL_SPACE = { + 'pkey': PKEY_PASSWD_LEAD_SPACE, + 'add': [ + ('textbox', 'uid', PKEY_PASSWD_LEAD_SPACE), + ('textbox', 'givenname', 'Name8'), + ('textbox', 'sn', 'Surname8'), + ('password', 'userpassword', 'Password123 '), + ('password', 'userpassword2', 'Password123 '), + ] +} + +PKEY_PASSWD_MISMATCH = 'itest-user-passwd-mismatch' +DATA_PASSWD_MISMATCH = { + 'pkey': PKEY_PASSWD_MISMATCH, + 'add': [ + ('textbox', 'uid', PKEY_PASSWD_MISMATCH), + ('textbox', 'givenname', 'Name9'), + ('textbox', 'sn', 'Surname9'), + ('password', 'userpassword', 'Password123'), + ('password', 'userpassword2', 'Password12'), + ] +} + +PKEY_NO_LOGIN = 'itest-user-no-login' +DATA_NO_LOGIN = { + 'pkey': PKEY_NO_LOGIN, + '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..00995744da 100644 --- a/ipatests/test_webui/test_user.py +++ b/ipatests/test_webui/test_user.py @@ -33,15 +33,29 @@ 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 +64,29 @@ def load_file(self, path): content = file_d.read() return content + def create_email_addr(self, pkey): + """ + Piece an email address together from hostname due possible different + DNS setup + """ + + domain = '.'.join(self.config.get('ipa_server').split('.')[1:]) + return '{}@{}'.format(pkey, domain) + + 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. + """ + mail = self.create_email_addr(user.DATA.get('pkey')) + + 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 +97,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 +414,237 @@ 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') + + 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.ENTITY) + + # 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 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.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.ENTITY) + 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.basic_crud(user.ENTITY, user.DATA_SPECIAL_CHARS) + + # Add an user with long login (should FAIL) + self.add_record(user.ENTITY, user.DATA_LONG_LOGIN, negative=True) + self.assert_last_error_dialog(expected_err=LONG_LOGIN) + self.close_all_dialogs() + + # Test password mismatch + self.add_record(user.ENTITY, user.DATA_PASSWD_MISMATCH, 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.DATA_PASSWD_MISMATCH.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.ENTITY) + self.facet_button_click('add') + self.fill_fields(user.DATA_PASSWD_LEAD_SPACE['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.ENTITY) + self.facet_button_click('add') + self.fill_fields(user.DATA_PASSWD_TRAIL_SPACE['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) + self.close_notifications() + + # 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() + + first_mail = self.create_email_addr(user.DATA.get('pkey')) + + 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(user.ENTITY, [user.DATA]) + + 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.DATA_NO_LOGIN) + self.assert_record('nsurname10') + + # try to add same user without login name again (should fail) + self.add_record(user.ENTITY, user.DATA_NO_LOGIN, 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() + + # search user / multiple users + self.navigate_to_entity(user.ENTITY) + 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.PKEY_NO_LOGIN, + 'nsurname10']) @pytest.mark.tier1 class test_user_no_private_group(UI_driver): @@ -510,7 +779,7 @@ def test_life_cycles(self): # send multiple records to preserved self.navigate_to_entity('stageuser') - self.navigate_to_entity('user') + self.navigate_to_entity(user.ENTITY) self.delete_record([user.PKEY, user.PKEY2], confirm_btn=None) self.check_option('preserve', value='true') @@ -527,7 +796,7 @@ def test_life_cycles(self): self.wait() # send multiple users to staged (through preserved) - self.navigate_to_entity('user') + self.navigate_to_entity(user.ENTITY) self.delete_record([user.PKEY, user.PKEY2], confirm_btn=None) self.check_option('preserve', value='true') @@ -588,6 +857,139 @@ def test_life_cycles(self): self.assert_record_value('Enabled', [user.PKEY, user.PKEY2], 'nsaccountlock') + # cleanup and check for ticket 4245 (select all should not remain + # checked after delete action). Two "ok" buttons at the end are needed + # for delete confirmation and acknowledging that "admin" cannot be + # deleted. + self.navigate_to_entity(user.ENTITY) + 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): + + @screenshot + def test_ssh_keys(self): + + self.init_app() + + # add and undo SSH key + self.add_sshkey_to_record(user.SSH_RSA, 'admin', save=False, + navigate=True) + 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_record(ssh_keys, 'admin', 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_record(user.SSH_RSA, 'admin', 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_record(user.SSH_RSA, 'admin', 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_record(user.SSH_RSA, 'admin', 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_record(user.SSH_RSA, 'admin', 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_record(user.SSH_RSA, 'admin', 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_record_sshkeys('admin') + + # add, save and delete RSA and DSA keys + keys = [user.SSH_RSA, user.SSH_DSA] + + self.add_sshkey_to_record(keys, 'admin') + self.assert_num_ssh_keys(2) + self.delete_record_sshkeys('admin') + 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_record(keys, 'admin') + self.assert_num_ssh_keys(2) + self.delete_record_sshkeys('admin') + self.assert_num_ssh_keys(0) + + # lets try to add empty SSH key (should fail) + self.add_sshkey_to_record('', 'admin') + 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_record('invalid_key', 'admin') + 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_record(user.SSH_RSA, 'admin') + self.add_sshkey_to_record(user.SSH_RSA, 'admin', 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 (we use DATA_SPECIAL_CHARS just for convenience) + self.add_record(user.ENTITY, [user.DATA2, user.DATA_SPECIAL_CHARS]) + self.add_sshkey_to_record(user.SSH_RSA, user.PKEY2, navigate=True) + + self.logout() + self.init_app(user.PKEY_SPECIAL_CHARS, user.PASSWD_SCECIAL_CHARS) + + 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_e = self.find('textarea', By.CSS_SELECTOR, self.get_dialog()) + + assert ssh_key_e.get_attribute('readonly') == 'true' + self.dialog_button_click('cancel') + self.logout() + self.init_app() + # cleanup - self.navigate_to_entity('user') - self.delete_record([user.PKEY, user.PKEY2]) + self.delete(user.ENTITY, [user.DATA2, user.DATA_SPECIAL_CHARS]) + self.delete_record_sshkeys('admin', navigate=True) From c969c5df2951799c5c8e5f6800e675f7bf002c3a 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 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, delete all of them and to extend their usage to hosts and id views. 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 | 171 ++++++++++++++++++++++++++++++++++----- 1 file changed, 150 insertions(+), 21 deletions(-) diff --git a/ipatests/test_webui/ui_driver.py b/ipatests/test_webui/ui_driver.py index 70fe570352..2991eb90fc 100644 --- a/ipatests/test_webui/ui_driver.py +++ b/ipatests/test_webui/ui_driver.py @@ -59,7 +59,6 @@ NO_YAML = True from ipaplatform.paths import paths - ENV_MAP = { 'MASTER': 'ipa_server', 'ADMINID': 'ipa_admin', @@ -374,9 +373,9 @@ def login(self, login=None, password=None, new_password=None): if self.logged_in(): return - if not login: + if login is None: login = self.config['ipa_admin'] - if not password: + if password is None: password = self.config['ipa_password'] if not new_password: new_password = password @@ -413,7 +412,14 @@ def logged_in(self): return logged_in def logout(self): + + runner = self + self.profile_menu_action('logout') + # it may take some time to get login screen visible + WebDriverWait(self.driver, self.request_timeout).until( + lambda d: runner.login_screen_visible()) + assert self.login_screen_visible() def get_login_screen(self): @@ -805,6 +811,50 @@ 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 + # lets try to find the undo button element again to check if + # it is not present or displayed + link = self.find(s, By.CSS_SELECTOR, w) + assert not link or not link.is_displayed(), 'Undo btn present' + + assert clicked, 'Value was not undone: %s' % value + def del_multivalued(self, name, value, parent=None): """ Mark value in multivalued textbox as deleted. @@ -1359,7 +1409,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 @@ -1374,6 +1425,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, @@ -1690,34 +1744,90 @@ 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_record(self, ssh_keys, pkey, entity='user', + navigate=False, save=True): """ - Add ssh public key to particular user + Add ssh public key to particular record - user (str): user to add the key to - ssh_key (str): public ssh key + ssh_keys (list): public ssh key(s) + pkey (str): user/host/idview to add the key to + entity (str): name of entity where to navigate if navigate=True + navigate (bool): whether we should navigate to record + save (bool): whether we should click save after adding a 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(entity) + self.navigate_to_record(pkey) + + 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') + + # 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_sshkey(self, user): + def delete_record_sshkeys(self, pkey, entity='user', navigate=False): """ - Delete ssh public key of particular user + Delete all ssh public keys of particular record + + pkey (str): user/host/idview to add the key to + entity (str): name of entity where to navigate if navigate=True + navigate (bool): whether we should navigate to record """ - self.navigate_to_entity('user') - self.navigate_to_record(user) + + if navigate: + self.navigate_to_entity(entity) + self.navigate_to_record(pkey) 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 found on current page' + + for btn in rm_btns: + btn.click() + self.facet_button_click('save') + 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) + def run_cmd_on_ui_host(self, cmd): """ Run "shell" command on the UI system using "admin" user's passwd from @@ -2070,3 +2180,22 @@ 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 value not 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 3e7f180d18e8a986cfb8f4ae61b1b3e60dc041fb Mon Sep 17 00:00:00 2001 From: Michal Reznik <mrez...@redhat.com> Date: Wed, 25 Apr 2018 10:51:19 +0200 Subject: [PATCH 3/3] ui_tests: introduce new test_misc cases file By this commit we introduce new test_misc cases file to test various miscellaneous cases that do not fit to other suites. In this cases that "version" is present in profile`s "about". https://pagure.io/freeipa/issue/7507 --- ipatests/test_webui/test_misc_cases.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 ipatests/test_webui/test_misc_cases.py diff --git a/ipatests/test_webui/test_misc_cases.py b/ipatests/test_webui/test_misc_cases.py new file mode 100644 index 0000000000..5f7ffb54ee --- /dev/null +++ b/ipatests/test_webui/test_misc_cases.py @@ -0,0 +1,28 @@ +# +# Copyright (C) 2018 FreeIPA Contributors see COPYING for license +# + +""" +Place for various miscellaneous test cases that do not fit to other suites +""" + +from ipatests.test_webui.ui_driver import UI_driver +from ipatests.test_webui.ui_driver import screenshot +import pytest +import re + + +@pytest.mark.tier1 +class TestMiscCases(UI_driver): + + @screenshot + def test_version_present(self): + + self.init_app() + + self.profile_menu_action('about') + + about_text = self.get_text('div[data-name="version_dialog"] p') + ver_re = re.compile('version: .*') + assert re.search(ver_re, about_text), 'Version not found' + self.dialog_button_click('ok')
_______________________________________________ FreeIPA-devel mailing list -- freeipa-devel@lists.fedorahosted.org To unsubscribe send an email to freeipa-devel-le...@lists.fedorahosted.org