diff --git a/features/advance_search.feature b/features/advance_search.feature index 76e3a4624..4a1bd6d45 100644 --- a/features/advance_search.feature +++ b/features/advance_search.feature @@ -42,5 +42,10 @@ Feature: Flaw advance search testing Given I go to the advanced search page Then I am able to search flaws with osidb related models in query filter + Scenario: Sort search result by extended sortable fields + Given I go to the advanced search page + When I sort search result by extended sortable field + Then I got search result sorted by extended field + Scenario: Search flaws with both query filter and selected field Then I am able to search flaws with both query filter and selected field diff --git a/features/pages/advanced_search_page.py b/features/pages/advanced_search_page.py index 6077ed04b..41007af63 100644 --- a/features/pages/advanced_search_page.py +++ b/features/pages/advanced_search_page.py @@ -1,6 +1,13 @@ -from features.pages.base import BasePage +import time +from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys +from selenium.webdriver.common.action_chains import ActionChains +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.wait import WebDriverWait + +from features.pages.base import BasePage +from features.pages.flaw_detail_page import FlawDetailPage class AdvancedSearchPage(BasePage): @@ -32,6 +39,11 @@ def __init__(self, driver): "embargoedFlag": ("XPATH", "(//span[contains(text(), 'Embargoed')])[1]"), "queryFilterInput": ("XPATH", "(//div[@class='input-group my-1'])[1]/textarea"), "defaultFilterSavedMsg": ("XPATH", "//div[contains(text(), 'default filter saved')]"), + + "createdBtn": ("XPATH", "//thead[@class='sticky-top']/tr/th[contains(text(), 'Created')]"), + "extendSortSelect": ("XPATH", "//select[@title='When using this sorting with active table column sort," + " the table column field will be used as secondary sorting key']"), + "extendSortOrderBtn": ("XPATH", "//button[@class='sort-by-order btn btn-sm']"), } def first_flaw_exist(self): @@ -74,3 +86,55 @@ def set_query_filter(self, value): if value: self.driver.execute_script("arguments[0].value = '';", filter_input) filter_input.send_keys(value) + + def get_specified_field_search_result(self, field_name, n=5): + flaws = self.driver.find_elements(By.XPATH, "//tbody[@class='table-group-divider']/tr") + count = 0 + result = [] + + flaw_detail_page = FlawDetailPage(self.driver) + + wait = WebDriverWait(self.driver, 10) + + for flaw in flaws: + if count == n: + break + tds = flaw.find_elements(By.XPATH, './td') + if len(tds) < 6: + continue + + # Store the ID of the original window + original_window = self.driver.current_window_handle + + link = tds[0].find_element(By.XPATH, './a') + + # open link with a new tab + (ActionChains(self.driver).key_down(Keys.LEFT_CONTROL). + click(link).key_up(Keys.LEFT_CONTROL).perform()) + + # Wait for the new window or tab + wait.until(EC.number_of_windows_to_be(2)) + + # switch to new tab + self.driver.switch_to.window(self.driver.window_handles[1]) + time.sleep(5) + + # get data + value = "" + if field_name == "cvss_scores__score": + value = flaw_detail_page.get_cvssV3_score() + elif field_name == "cwe_id": + value = flaw_detail_page.get_input_field_value_using_relative_locator("cweidText") + elif field_name == "major_incident_state": + value = flaw_detail_page.get_select_value_using_relative_locator("incidentStateText") + elif field_name == "source": + value = flaw_detail_page.get_select_value_using_relative_locator("sourceText") + + # close tab and back to search page + self.driver.close() + self.driver.switch_to.window(original_window) + + result.append(value) + count += 1 + + return result diff --git a/features/pages/flaw_detail_page.py b/features/pages/flaw_detail_page.py index f0928fb06..a3b67a505 100644 --- a/features/pages/flaw_detail_page.py +++ b/features/pages/flaw_detail_page.py @@ -35,6 +35,8 @@ def __init__(self, driver): "createFlawLink": ("LINK_TEXT", "Create Flaw"), "createNewFlawBtn": ("XPATH", '//button[contains(text(), "Create New Flaw")]'), "embargeodCheckBox": ("XPATH", "//input[@class='form-check-input']"), + "muteNotificationIcon": ( + "XPATH", "//i[@class='bi notification-icon text-white osim-notification-icon bi-bell-slash-fill']"), "comment#0Text": ("XPATH", "//span[text()=' Comment#0']"), "descriptionBtn": ("XPATH", "//button[contains(text(), 'Add Description')]"), @@ -127,6 +129,7 @@ def __init__(self, driver): "referenceUpdatedMsg": ("XPATH", "//div[text()='Reference updated.']"), "rhsbReferenceLinkFormatErrorMsg": ("XPATH", '//div[contains(text(), "A flaw reference of the ARTICLE type does not begin with https://access.redhat.com")]'), "firstReferenceDescriptionValue": ("XPATH", "(//div[@class='osim-list-edit'])[1]/div/div/div/div/span"), + "referenceTypeText": ("XPATH", "//span[text()='Reference type']"), "bottomBar": ("XPATH", "//div[@class='osim-action-buttons sticky-bottom d-grid gap-2 d-flex justify-content-end']"), "bottomFooter": ("XPATH", "//footer[@class='fixed-bottom osim-status-bar']"), @@ -195,6 +198,7 @@ def __init__(self, driver): "addFlawTabButton": ("XPATH", "//button[@class='nav-link osim-add-tab']"), "trackerMangerSearchFlawInput": ("XPATH", "//input[@placeholder='Search...']"), "trackerManagerAddFlawBtn": ("XPATH", "//button[text()='Add']"), + "confirmFileLowSeverityBtn": ("XPATH", "//button[text()=' Confirm ']"), "msgClose": ("XPATH", "(//button[@class='osim-toast-close-btn btn-close'])[1]"), "unembargoBtn": ("XPATH", "//button[contains(text(), 'Unembargo')]"), @@ -507,7 +511,6 @@ def delete_all_reference(self): self.referenceDelConfirmBtn.click_button() self.wait_msg("referenceDeletedMsg") self.close_all_toast_msg() - time.sleep(2) def add_reference_select_external_type(self): try: @@ -516,21 +519,18 @@ def add_reference_select_external_type(self): except ElementNotVisibleException: pass + self.referenceTypeText.execute_script( + "arguments[0].scrollIntoView({behavior: 'auto',block: 'center',inline: 'center'});") + add_reference_select = self.driver.find_elements( locate_with(By.TAG_NAME, "select"). - below(self.referenceCountLabel))[0] + near(self.referenceTypeText))[0] select = Select(add_reference_select) WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable( (By.XPATH, "//select[@value='ARTICLE']//option[contains(.,'External')]"))) - self.driver.execute_script( - "arguments[0].scrollIntoView({behavior: 'auto',block: 'center',inline: 'center'});", - add_reference_select) - - time.sleep(2) - select.select_by_visible_text("External") def add_reference_set_link_url(self, value): @@ -766,8 +766,11 @@ def set_reject_reason(self, value): def get_select_value_using_relative_locator(self, field): element = getattr(self, field) field_select = self.driver.find_elements( - locate_with(By.XPATH, "//select[@class='form-select']").near(element))[0] - selected_item = field_select.get_list_selected_item() + locate_with(By.XPATH, "//select[@class='form-select']").near(element)) + if not field_select: + field_select = self.driver.find_elements( + locate_with(By.XPATH, "//select[@class='form-select is-invalid']").near(element)) + selected_item = field_select[0].get_list_selected_item() current_value = selected_item[0] if selected_item else None return current_value @@ -823,8 +826,12 @@ def check_owner_value_exist(self, value): return self.driver.find_element(By.XPATH, f"//span[text()='{value}']") def close_all_toast_msg(self): - for toast_msg in find_elements_in_page_factory(self, 'toastMsgCloseBtn'): + notifications = find_elements_in_page_factory(self, 'toastMsgCloseBtn') + if not notifications: + return + for toast_msg in notifications: self.click_button_with_js(toast_msg) + time.sleep(1) def set_cvss_score_explanation(self, value): cvss_comment_dropdown = self.driver.find_elements( @@ -1036,6 +1043,13 @@ def file_tracker(self, row=1): self.select_unfiled_tracker(row=row) # file tracker self.driver.find_element(By.XPATH, "//button[text()=' File 1 Trackers ']").click() + # confirm if current flaw is low impact + try: + self.driver.find_element(By.XPATH, "//*[contains(text(), ' Filing Low Severity Trackers ')]") + except NoSuchElementException: + pass + else: + self.confirmFileLowSeverityBtn.click_button() self.wait_msg("trackersFiledMsg") self.close_all_toast_msg() # close tracker manager window diff --git a/features/steps/advance_search.py b/features/steps/advance_search.py index c083204ac..e476a08c0 100644 --- a/features/steps/advance_search.py +++ b/features/steps/advance_search.py @@ -1,4 +1,5 @@ import time +from pprint import pprint from behave import * @@ -6,9 +7,8 @@ from features.pages.flaw_detail_page import FlawDetailPage from features.common_utils import get_data_from_tmp_data_file from features.steps.common_steps import go_to_specific_flaw_detail_page -from features.constants import EMBARGOED_FLAW_UUID_KEY, FLAW_ID_KEY -from features.utils import go_to_advanced_search_page - +from features.constants import EMBARGOED_FLAW_UUID_KEY +from features.utils import go_to_advanced_search_page, is_sorted EMPTY_FIELDS = [ "cve_description", @@ -220,6 +220,44 @@ def step_impl(context): flaw_page.check_text_exist(v) +@when('I sort search result by extended sortable field') +def step_impl(context): + advanced_search_page = AdvancedSearchPage(context.browser) + # click created order twice, let result not sort by table column + advanced_search_page.click_btn("createdBtn") + advanced_search_page.click_btn('createdBtn') + advanced_search_page.first_flaw_exist() + + select_value_list = ["cvss_scores__score", "cwe_id", "major_incident_state", "source"] + # select_value_list = ["cvss_scores__score", "major_incident_state", "source"] + result_dict = {} + + # ascending + for select_value in select_value_list: + advanced_search_page.extendSortSelect.select_element_by_value(select_value) + time.sleep(3) + res = advanced_search_page.get_specified_field_search_result(select_value) + result_dict[select_value] = {"asce": res} + + advanced_search_page.extendSortOrderBtn.click_button() + # descending + for select_value in select_value_list: + advanced_search_page.extendSortSelect.select_element_by_value(select_value) + time.sleep(3) + res = advanced_search_page.get_specified_field_search_result(select_value) + result_dict[select_value]["desc"] = res + + context.result_dict = result_dict + pprint(context.result_dict, indent=4) + + +@then('I got search result sorted by extended field') +def step_impl(context): + for k, v in context.result_dict.items(): + for order, values in v.items(): + assert is_sorted(values, order) is True, f"Sort by field {k} in {order} failed. Get: {values}" + + @then('I am able to search flaws with both query filter and selected field') def step_impl(context): # get flaw data for search diff --git a/features/steps/common_steps.py b/features/steps/common_steps.py index 99a511a55..a98022d66 100644 --- a/features/steps/common_steps.py +++ b/features/steps/common_steps.py @@ -19,8 +19,6 @@ def step_impl(context): @given('I set the bugzilla api key and jira api key') def step_impl(context): set_api_keys(context.browser) - # wait osim getting username from jira - time.sleep(2) @given('I am on the flaw list') diff --git a/features/steps/flaw_create.py b/features/steps/flaw_create.py index 3e8875e14..d787d0719 100644 --- a/features/steps/flaw_create.py +++ b/features/steps/flaw_create.py @@ -52,6 +52,8 @@ def create_flaw_with_valid_data(context, embargoed=False, with_optional=False): flaw_create_page.click_btn('createNewFlawBtn') flaw_create_page.wait_msg('flawCreatedMsg') + flaw_create_page.close_all_toast_msg() + def check_created_flaw_exist(context, embargoed=False): go_to_advanced_search_page(context.browser) diff --git a/features/steps/flaw_detail.py b/features/steps/flaw_detail.py index 92f03d2e0..dffeeca3f 100644 --- a/features/steps/flaw_detail.py +++ b/features/steps/flaw_detail.py @@ -2,6 +2,7 @@ from datetime import date, datetime from behave import when, then +from selenium.common import NoSuchElementException from selenium.webdriver.common.by import By from features.common_utils import get_data_from_tmp_data_file @@ -321,7 +322,6 @@ def step_impl(context): def add_a_reference_to_first_flaw(context, value, wait_msg, external=True): flaw_detail_page = FlawDetailPage(context.browser) - go_to_specific_flaw_detail_page(context.browser) flaw_detail_page.click_button_with_js("addReferenceBtn") if external: flaw_detail_page.add_reference_select_external_type() @@ -330,6 +330,7 @@ def add_a_reference_to_first_flaw(context, value, wait_msg, external=True): flaw_detail_page.click_button_with_js("saveReferenceBtn") flaw_detail_page.wait_msg(wait_msg) flaw_detail_page.click_btn("toastMsgCloseBtn") + time.sleep(2) @when("I add two external references to the flaw") @@ -344,7 +345,6 @@ def step_impl(context): @then("Two external references added") def step_impl(context): - go_to_specific_flaw_detail_page(context.browser) flaw_detail_page = FlawDetailPage(context.browser) flaw_detail_page.click_reference_dropdown_button() flaw_detail_page.check_text_exist(context.first_value) @@ -643,12 +643,10 @@ def step_impl(context): flaw_detail_page.wait_msg('flawSavedMsg') flaw_detail_page.check_text_exist(f"{current_affects_number} Affect(s) Deleted.") flaw_detail_page.close_all_toast_msg() - time.sleep(2) # add a new affect for filing tracker context.component = flaw_detail_page.add_new_affect(affectedness_value='AFFECTED') flaw_detail_page.close_all_toast_msg() - time.sleep(2) @when('I file a tracker for new added affect') @@ -847,7 +845,6 @@ def step_impl(context): flaw_page = FlawDetailPage(context.browser) flaw_page.add_new_affect(affectedness_value="NEW", component=generate_random_text()) flaw_page.close_all_toast_msg() - time.sleep(2) context.check_result = flaw_page.bulk_update_affects() @@ -890,13 +887,9 @@ def step_impl(context): if all_affects_number < 6: diff = 6 - all_affects_number for _ in range(diff): - flaw_detail_page.add_new_affect(component=generate_random_text(), affectedness_value="NEW", save=False) - - flaw_detail_page.click_btn('saveBtn') - flaw_detail_page.wait_msg('flawSavedMsg') - flaw_detail_page.wait_msg("affectCreatedMsg") - flaw_detail_page.check_element_exists( - By.XPATH, f"//div[text()='{diff} CVSS score(s) saved on {diff} affect(s).']") + flaw_detail_page.add_new_affect(component=generate_random_text(), affectedness_value="NEW") + flaw_detail_page.close_all_toast_msg() + time.sleep(2) # change affect pagination to 5 flaw_detail_page.change_affect_pagination(5) @@ -1004,9 +997,15 @@ def step_impl(context): def step_impl(context): flaw_detail_page = FlawDetailPage(context.browser) flaw_detail_page.driver.find_element(By.XPATH, "//button[text()=' File 1 Trackers ']").click() + # confirm if current flaw is low impact + try: + flaw_detail_page.driver.find_element(By.XPATH, "//*[contains(text(), ' Filing Low Severity Trackers ')]") + except NoSuchElementException: + pass + else: + flaw_detail_page.confirmFileLowSeverityBtn.click_button() flaw_detail_page.wait_msg("trackersFiledMsg") flaw_detail_page.close_all_toast_msg() - time.sleep(2) @then('Tracker filed for multiple flaw') diff --git a/features/utils.py b/features/utils.py index 40f6edd99..e462b4edd 100644 --- a/features/utils.py +++ b/features/utils.py @@ -1,6 +1,8 @@ import json import os import re +import time + import rstr import random import string @@ -8,6 +10,7 @@ from selenium import webdriver from selenium.webdriver.firefox.options import Options +from selenium.common.exceptions import NoSuchElementException from constants import ( OSIDB_URL, @@ -67,6 +70,18 @@ def set_api_keys(browser): settings_page.set_api_key('bugzilla', BUGZILLA_API_KEY) settings_page.set_api_key('jira', JIRA_API_KEY) + # wait osim getting username from jira + time.sleep(2) + + # open notification so that we can judge if operation succeed + flaw_detail_page = FlawDetailPage(browser) + try: + flaw_detail_page.muteNotificationIcon.click() + except NoSuchElementException: + pass + + flaw_detail_page.close_all_toast_msg() + def go_to_home_page(browser): home_page = HomePage(browser)