From 105d7d7f2efa6368ca7f7ff9a219540f4e008ea7 Mon Sep 17 00:00:00 2001 From: Varun Mylaraiah Date: Thu, 5 Apr 2018 13:21:51 +0530 Subject: [PATCH] WebUI tests: Extend user group tests with more scenarios 1) Extended webui group automation test with below scenarios Scenarios *Add user group with invalid names *Add multiple groups records at one shot *Select and delete multiple records *Find and delete records etc... 2) Improved add_record method to support additional use cases: *confirm by additional buttons: 'Add', 'Add and add another', 'Add and Edit,' 'Cancel' *add multiple records in one call (uses 'Add and add another' behavior) https://pagure.io/freeipa/issue/7485 Signed-off-by: Varun Mylaraiah Reviewed-By: Petr Vobornik Reviewed-By: Michal Reznik --- ipatests/test_webui/data_group.py | 38 ++++++ ipatests/test_webui/test_group.py | 185 +++++++++++++++++++++++++----- ipatests/test_webui/ui_driver.py | 119 ++++++++++++------- 3 files changed, 274 insertions(+), 68 deletions(-) diff --git a/ipatests/test_webui/data_group.py b/ipatests/test_webui/data_group.py index 517f98f04..7ef4edd39 100644 --- a/ipatests/test_webui/data_group.py +++ b/ipatests/test_webui/data_group.py @@ -78,3 +78,41 @@ DATA6 = { ('textbox', 'gidnumber', '77777'), ] } + +PKEY7 = '' +DATA7 = { + 'pkey': PKEY7, + 'add': [ + ('textbox', 'cn', PKEY7), + ('textarea', 'description', 'Empty Group name'), + ] +} + +PKEY8 = ';test-gr@up' +DATA8 = { + 'pkey': PKEY8, + 'add': [ + ('textbox', 'cn', PKEY8), + ('textarea', 'description', 'Invalid Group name'), + ] +} + +PKEY9 = 'itest-group9' +DATA9 = { + 'pkey': PKEY9, + 'add': [ + ('textbox', 'cn', PKEY9), + ('textarea', 'description', 'test-group9 desc'), + ('radio', 'type', 'nonposix'), + ] +} + +PKEY10 = 'itest-group10' +DATA10 = { + 'pkey': PKEY10, + 'add': [ + ('textbox', 'cn', PKEY10), + ('textarea', 'description', 'test-group10 desc'), + ('radio', 'type', 'nonposix'), + ] +} diff --git a/ipatests/test_webui/test_group.py b/ipatests/test_webui/test_group.py index db9acef34..fc1a3a2d9 100644 --- a/ipatests/test_webui/test_group.py +++ b/ipatests/test_webui/test_group.py @@ -31,6 +31,12 @@ import ipatests.test_webui.test_rbac as rbac import ipatests.test_webui.data_sudo as sudo import pytest +try: + from selenium.webdriver.common.keys import Keys + from selenium.webdriver.common.action_chains import ActionChains +except ImportError: + pass + @pytest.mark.tier1 class test_group(UI_driver): @@ -75,6 +81,118 @@ class test_group(UI_driver): def check_posix_enabled(self, enabled): self.assert_disabled("[name=gidnumber]", negative=enabled) + @screenshot + def test_add_group_negative(self): + """ + Negative test for adding groups + """ + self.init_app() + + self.empty_group_name() + self.invalid_group_name() + self.duplicate_group_name() + self.tailing_spaces_in_group_description() + self.leading_spaces_in_group_description() + + def empty_group_name(self): + self.navigate_to_entity(group.ENTITY) + self.facet_button_click('add') + self.dialog_button_click('add') + elem = self.find(".widget[name='cn']") + self.assert_field_validation_required(elem) + self.dialog_button_click('cancel') + + def invalid_group_name(self): + expected_error = 'may only include letters, numbers, _, -, . and $' + pkey = ';test-gr@up' + self.navigate_to_entity(group.ENTITY) + self.facet_button_click('add') + self.fill_input('cn', pkey) + elem = self.find(".widget[name='cn']") + self.assert_field_validation(expected_error, parent=elem) + self.dialog_button_click('cancel') + + def duplicate_group_name(self): + pkey = 'editors' + expected_error = 'group with name "editors" already exists' + self.navigate_to_entity(group.ENTITY) + self.facet_button_click('add') + self.fill_input('cn', pkey) + self.cancel_retry_dialog(expected_error) + + def tailing_spaces_in_group_description(self): + pkey = 'itest_group0' + desc = 'with_trailing_space ' + expected_error = 'invalid \'desc\': Leading and trailing ' \ + 'spaces are not allowed' + self.navigate_to_entity(group.ENTITY) + self.facet_button_click('add') + self.fill_input('cn', pkey) + self.fill_textarea('description', desc) + self.cancel_retry_dialog(expected_error) + + def leading_spaces_in_group_description(self): + pkey = 'itest_group0' + desc = ' with_leading_space' + expected_error = 'invalid \'desc\': Leading and trailing' \ + ' spaces are not allowed' + self.navigate_to_entity(group.ENTITY) + self.facet_button_click('add') + self.fill_input('cn', pkey) + self.fill_textarea('description', desc) + self.cancel_retry_dialog(expected_error) + + def cancel_retry_dialog(self, expected_error): + self.dialog_button_click('add') + dialog = self.get_last_error_dialog() + assert (expected_error in dialog.text) + self.wait_for_request() + # Key press for Retry + actions = ActionChains(self.driver) + actions.send_keys(Keys.ENTER).perform() + self.wait_for_request(n=2) + self.dialog_button_click('cancel') + self.wait_for_request(n=2) + self.dialog_button_click('cancel') + + @screenshot + def test_add_multiple_group(self): + """ + Use 'add and add another' button to create multiple groups at one shot + """ + self.init_app() + # adding a POSIX and a Non-POSIX group + self.add_record(group.ENTITY, [group.DATA, group.DATA2]) + + # adding Two Non-POSIX groups + self.add_record(group.ENTITY, [group.DATA9, group.DATA10]) + + # adding Two POSIX groups + self.add_record(group.ENTITY, [group.DATA5, group.DATA6]) + + # delete multiple records + records = [group.DATA, group.DATA2, group.DATA5, group.DATA6, + group.DATA9, group.DATA10] + self.select_multiple_records(records) + self.facet_button_click('remove') + self.dialog_button_click('ok') + + @screenshot + def test_add_and_edit_group(self): + """ + 1. add and switch to edit mode + 2. add and cancel + """ + self.init_app() + + # add and edit record + self.add_record(group.ENTITY, group.DATA, dialog_btn='add_and_edit') + self.switch_to_facet('details') + self.delete_action() + + # add then cancel + self.add_record(group.ENTITY, group.DATA, dialog_btn='cancel') + @screenshot def test_actions(self): """ @@ -124,11 +242,9 @@ class test_group(UI_driver): # prepare # ------- - self.add_record(group.ENTITY, group.DATA) - self.add_record(group.ENTITY, group.DATA2, navigate=False) - self.add_record(group.ENTITY, group.DATA3, navigate=False) - self.add_record(user.ENTITY, user.DATA) - self.add_record(netgroup.ENTITY, netgroup.DATA) + self.add_record(group.ENTITY, [group.DATA, group.DATA2, group.DATA3]) + self.add_record(user.ENTITY, [user.DATA, user.DATA2]) + self.add_record(netgroup.ENTITY, [netgroup.DATA, netgroup.DATA2]) self.add_record(rbac.ROLE_ENTITY, rbac.ROLE_DATA) self.add_record(hbac.RULE_ENTITY, hbac.RULE_DATA) self.add_record(sudo.RULE_ENTITY, sudo.RULE_DATA) @@ -137,24 +253,32 @@ class test_group(UI_driver): # ------------------------- self.navigate_to_record(group.PKEY, entity=group.ENTITY) - # members - self.add_associations([group.PKEY2], facet='member_group', delete=True) - self.add_associations([user.PKEY], facet='member_user', delete=True) + # "members" add with multiple select + self.add_associations([group.PKEY2, group.PKEY3], facet='member_group', + delete=True) + self.add_associations([user.PKEY, user.PKEY2], facet='member_user', + delete=True) # TODO: external - # member of - self.add_associations([group.PKEY3], facet='memberof_group', delete=True) - self.add_associations([netgroup.PKEY], facet='memberof_netgroup', delete=True) - self.add_associations([rbac.ROLE_PKEY], facet='memberof_role', delete=True) - self.add_associations([hbac.RULE_PKEY], facet='memberof_hbacrule', delete=True) + # "member of": add with search + self.add_associations([group.PKEY3, group.PKEY2], + facet='memberof_group', delete=True, search=True) + self.add_associations([netgroup.PKEY, netgroup.PKEY2], + facet='memberof_netgroup', + delete=True, search=True) + self.add_associations([rbac.ROLE_PKEY], facet='memberof_role', + delete=True) + self.add_associations([hbac.RULE_PKEY], facet='memberof_hbacrule', + delete=True) self.navigate_to_record(group.PKEY, entity=group.ENTITY) - self.add_associations([sudo.RULE_PKEY], facet='memberof_sudorule', delete=True) + self.add_associations([sudo.RULE_PKEY], facet='memberof_sudorule', + delete=True, search=True) # cleanup # ------- self.delete(group.ENTITY, [group.DATA, group.DATA2, group.DATA3]) - self.delete(user.ENTITY, [user.DATA]) - self.delete(netgroup.ENTITY, [netgroup.DATA]) + self.delete(user.ENTITY, [user.DATA, user.DATA2]) + self.delete(netgroup.ENTITY, [netgroup.DATA, netgroup.DATA2]) self.delete(rbac.ROLE_ENTITY, [rbac.ROLE_DATA]) self.delete(hbac.RULE_ENTITY, [hbac.RULE_DATA]) self.delete(sudo.RULE_ENTITY, [sudo.RULE_DATA]) @@ -168,11 +292,8 @@ class test_group(UI_driver): # add # --- - self.add_record(group.ENTITY, group.DATA) - self.add_record(group.ENTITY, group.DATA2, navigate=False) - self.add_record(group.ENTITY, group.DATA3, navigate=False) - self.add_record(group.ENTITY, group.DATA4, navigate=False) - self.add_record(group.ENTITY, group.DATA5, navigate=False) + self.add_record(group.ENTITY, [group.DATA, group.DATA2, group.DATA3, + group.DATA4, group.DATA5]) self.add_record(user.ENTITY, user.DATA) # prepare indirect member @@ -215,15 +336,21 @@ class test_group(UI_driver): self.assert_indirect_record(user.PKEY, group.ENTITY, 'member_user') self.assert_indirect_record(group.PKEY3, group.ENTITY, 'member_group') - self.assert_indirect_record(group.PKEY5, group.ENTITY, 'memberof_group') - self.assert_indirect_record(netgroup.PKEY, group.ENTITY, 'memberof_netgroup') - self.assert_indirect_record(rbac.ROLE_PKEY, group.ENTITY, 'memberof_role') - self.assert_indirect_record(hbac.RULE_PKEY, group.ENTITY, 'memberof_hbacrule') - self.assert_indirect_record(sudo.RULE_PKEY, group.ENTITY, 'memberof_sudorule') + self.assert_indirect_record(group.PKEY5, group.ENTITY, + 'memberof_group') + self.assert_indirect_record(netgroup.PKEY, group.ENTITY, + 'memberof_netgroup') + self.assert_indirect_record(rbac.ROLE_PKEY, group.ENTITY, + 'memberof_role') + self.assert_indirect_record(hbac.RULE_PKEY, group.ENTITY, + 'memberof_hbacrule') + self.assert_indirect_record(sudo.RULE_PKEY, group.ENTITY, + 'memberof_sudorule') - ## cleanup - ## ------- - self.delete(group.ENTITY, [group.DATA, group.DATA2, group.DATA3, group.DATA4, group.DATA5]) + # cleanup + # ------- + self.delete(group.ENTITY, [group.DATA, group.DATA2, group.DATA3, + group.DATA4, group.DATA5]) self.delete(user.ENTITY, [user.DATA]) self.delete(netgroup.ENTITY, [netgroup.DATA]) self.delete(rbac.ROLE_ENTITY, [rbac.ROLE_DATA]) diff --git a/ipatests/test_webui/ui_driver.py b/ipatests/test_webui/ui_driver.py index d769f6f14..65cd66afe 100644 --- a/ipatests/test_webui/ui_driver.py +++ b/ipatests/test_webui/ui_driver.py @@ -1268,9 +1268,9 @@ class UI_driver(object): self.wait_for_request(n=2) def add_record(self, entity, data, facet='search', facet_btn='add', - dialog_btn='add', delete=False, pre_delete=True, - dialog_name='add', navigate=True, combobox_input=None, - negative=False): + dialog_btn='add', add_another_btn='add_and_add_another', + delete=False, pre_delete=True, dialog_name='add', + navigate=True, combobox_input=None, negative=False): """ Add records. @@ -1285,8 +1285,15 @@ class UI_driver(object): ], } """ - pkey = data['pkey'] + if type(data) is not list: + data = [data] + last_element = data[len(data) - 1] + + pkeys = [] + + for record in data: + pkeys.append(record['pkey']) if navigate: self.navigate_to_entity(entity, facet) @@ -1294,8 +1301,9 @@ class UI_driver(object): self.assert_facet(entity, facet) # delete if exists, ie. from previous test fail + if pre_delete: - self.delete_record(pkey, data.get('del')) + self.delete(entity, data, navigate=False) # current row count self.wait_for_request(0.5) @@ -1306,40 +1314,58 @@ class UI_driver(object): self.facet_button_click(facet_btn) self.assert_dialog(dialog_name) - # fill dialog - self.fill_fields(data['add'], combobox_input=combobox_input) + for record in data: - # confirm dialog - self.dialog_button_click(dialog_btn) - self.wait_for_request() - self.wait_for_request() + # fill dialog + self.fill_fields(record['add'], combobox_input=combobox_input) - # check expected error/warning/info - expected = ['error_4304_info'] - dialog_info = self.get_dialog_info() - if dialog_info and dialog_info['name'] in expected: - self.dialog_button_click('ok') + btn = dialog_btn + + if record != last_element: + btn = add_another_btn + + self.dialog_button_click(btn) + self.wait_for_request() self.wait_for_request() - if negative: + # check expected error/warning/info + expected = ['error_4304_info'] + dialog_info = self.get_dialog_info() + if dialog_info and dialog_info['name'] in expected: + self.dialog_button_click('ok') + self.wait_for_request() + + if negative: + return + + # check for error + self.assert_no_error_dialog() + self.wait_for_request() + self.wait_for_request(0.4) + + if dialog_btn == 'add_and_edit': + page_pkey = self.get_text('.facet-pkey') + assert record['pkey'] in page_pkey + # we cannot delete because we are on different page return - - # check for error - self.assert_no_error_dialog() - self.wait_for_request() - self.wait_for_request(0.4) - - # check if table has more rows + elif dialog_btn == add_another_btn: + # dialog is still open, we cannot check for records on search page + # or delete the records + return + elif dialog_btn == 'cancel': + return + # when standard 'add' was used then it will land on search page + # and we can check if new item was added - table has more rows new_count = len(self.get_rows()) # adjust because of paging - expected = count + 1 + expected = count + len(data) if count == 20: expected = 20 self.assert_row_count(expected, new_count) # delete record if delete: - self.delete_record(pkey) + self.delete(entity, data, navigate=False) new_count = len(self.get_rows()) self.assert_row_count(count, new_count) @@ -1403,10 +1429,9 @@ class UI_driver(object): self.wait_for_request() # 2. Add record - self.add_record(parent_entity, data, facet=search_facet, navigate=False, - facet_btn=add_facet_btn, dialog_name=add_dialog_name, - dialog_btn=add_dialog_btn - ) + self.add_record(parent_entity, data, facet=search_facet, + navigate=False, facet_btn=add_facet_btn, + dialog_name=add_dialog_name, dialog_btn=add_dialog_btn) self.close_notifications() @@ -1458,7 +1483,7 @@ class UI_driver(object): def prepare_associations( self, pkeys, facet=None, facet_btn='add', member_pkeys=None, - confirm_btn='add'): + confirm_btn='add', search=False): """ Helper function for add_associations and delete_associations """ @@ -1469,8 +1494,18 @@ class UI_driver(object): self.wait() self.wait_for_request() - for key in pkeys: - self.select_record(key, table_name='available') + if search is True: + for key in pkeys: + search_field_s = '.adder-dialog-top input[name="filter"]' + self.fill_text(search_field_s, key) + self._button_click(selector="button[name='find'].btn-default", + parent=None) + self.wait_for_request() + self.select_record(key, table_name='available') + self.button_click('add') + else: + for key in pkeys: + self.select_record(key, table_name='available') self.button_click('add') self.dialog_button_click(confirm_btn) @@ -1485,18 +1520,19 @@ class UI_driver(object): def add_associations( self, pkeys, facet=None, delete=False, facet_btn='add', - member_pkeys=None, confirm_btn='add'): + member_pkeys=None, confirm_btn='add', search=False): """ Add associations """ check_pkeys = self.prepare_associations( - pkeys, facet, facet_btn, member_pkeys, confirm_btn=confirm_btn) + pkeys, facet, facet_btn, member_pkeys, confirm_btn, search) # we need to return if we want to "cancel" to avoid assert record fail if confirm_btn == 'cancel': return for key in check_pkeys: + self.assert_record(key) if delete: self.delete_record(key) @@ -1513,7 +1549,8 @@ class UI_driver(object): for key in check_pkeys: self.assert_record(key, negative=True) - def add_table_associations(self, table_name, pkeys, parent=False, delete=False): + def add_table_associations(self, table_name, pkeys, parent=False, + delete=False): """ Add value to table (association|rule|...) """ @@ -1987,9 +2024,9 @@ class UI_driver(object): assert is_enabled == enabled, ('Invalid enabled state of action item %s. ' 'Expected: %s') % (action, str(visible)) - def assert_field_validation_required(self, parent=None): + def assert_field_validation(self, expect_error, parent=None): """ - Assert we got 'Required field' error message in field validation + Assert for error in field validation """ if not parent: @@ -1998,7 +2035,11 @@ class UI_driver(object): req_field_css = '.help-block[name="error_link"]' res = self.find(req_field_css, By.CSS_SELECTOR, context=parent) - assert 'Required field' in res.text, 'No "Required field" error found' + assert expect_error in res.text, \ + 'Expected error: {} not found'.format(expect_error) + + def assert_field_validation_required(self, parent=None): + self.assert_field_validation('Required field', parent) def assert_notification(self, type='success', assert_text=None): """