diff --git a/Pipfile b/Pipfile index 94087ec5b..a92fe2522 100644 --- a/Pipfile +++ b/Pipfile @@ -15,7 +15,7 @@ pytest-html = "4.1.1" pypom = "2.2.4" taskcluster-taskgraph = "==9.0.0" jsonpath-ng = "1.6.1" -pillow = "<10.5" +pillow = "12.0" pyfxa = "0.8.1" ruff = "0.9.6" pytest-rerunfailures = "14.0" diff --git a/SELECTOR_INFO.md b/SELECTOR_INFO.md index d6fb764de..c9abaab58 100644 --- a/SELECTOR_INFO.md +++ b/SELECTOR_INFO.md @@ -1216,6 +1216,13 @@ Location: about:preferences#general Applications subsection Path to .json: modules/data/about_prefs.components.json ``` ``` +Selector Name: clipboard-suggestion-checkbox +Selector Data: #clipboardSuggestion" +Description: The checkbox for Clipboard +Location: about:preferences#search +Path to .json: modules/data/about_prefs.components.json +``` +``` Selector Name: search-shortcuts-group Selector Data: oneClickSearchProvidersGroup Description: The Search shortcuts table @@ -1243,6 +1250,13 @@ Description: The Restore Default Search Engines button under Search shortcuts ta Location: about:preferences#search Search Shortcuts subsection Path to .json: modules/data/about_prefs.components.json ``` +``` +Selector Name: Browsing History +Selector Data: history-suggestion +Description: Browsing History / History Suggestion under Address bar section +Location: about:preferences#search +Path to .json: modules/data/about_prefs.components.json +``` #### about_profiles ``` Selector Name: profile-container @@ -1692,6 +1706,27 @@ Location: Any history item in the Hamburger history list Path to .json: modules/data/context_menu.components.json ``` ``` +Selector Name: context-move-tab-to-new-group +Selector Data: "context_moveTabToNewGroup" +Description: Tab context click option "Add Tab to New Group" +Location: In the tabbed browser +Path to .json: modules/data/context_menu.components.json +``` +``` +Selector Name: context-move-tab-to-group +Selector Data: "context_moveTabToGroup" +Description: Tab context click option "Add Tab to Group" +Location: In the tabbed browser +Path to .json: modules/data/context_menu.components.json +``` +``` +Selector Name: context-remove-tab-from-group +Selector Data: "context_ungroupTab" +Description: Tab context click option "Remove from Group" +Location: In the tabbed browser +Path to .json: modules/data/context_menu.components.json +``` +``` Selector Name: context-menu-pin-tab Selector Data: "context_pinTab" Description: Tab context click option "Pin Tab" @@ -1901,6 +1936,20 @@ Description: Context menu option to search selected text with the engine set as Location: Context menu - topsite context menu Path to .json: modules/data/context_menu.components.json ``` +``` +Selector Name: context-menu-open-link-in-new_container_tab +Selector Data: context-openlinkinusercontext-menu" +Description: Open linkin new container tab context menu item +Location: Newpage topsites tile context menu +Path to .json: modules/data/context_menu.components.json +``` +``` +Selector Name: context-menu-open-link-in-container-work +Selector Data: menuitem[data-l10n-id='user-context-work']" +Description: Open link in container "Work" context submenu item +Location: Newpage topsites tile submenu context menu, secodn option +Path to .json: modules/data/context_menu.components.json +``` #### credit_card_fill ``` Selector Name: form-field @@ -3308,6 +3357,20 @@ Description: Switch to tab from awesome bar Location: Address bar Path to .json: modules/data/navigation.components.json ``` +``` +Selector Name: switch-to-cliboard +Selector Data: #urlbar-results > div.urlbarView-row[type='clipboard']" +Description: Switch to clipboard +Location: Address bar +Path to .json: modules/data/navigation.components.json +``` +``` +Selector Name: tab-container-label +Selector Data: "userContext-label" +Description: Tab container label +Location: URL bar when a tab container is active +Path to .json: modules/data/navigation.components.json +``` #### panel_ui ``` Selector name: panel-ui-button @@ -3497,6 +3560,7 @@ Location: On the hamburger menu Path to .json: modules/data/panel_ui.components.json ``` ``` +Selector name: panel-menu-item-by-title Selector Data: toolbarbutton.bookmark-item[label*='{title}'] Description: Bookmark or history item by title. This selector works for both bookmarks and history items since Firefox uses the same CSS class "bookmark-item" for both in the hamburger menu UI. Location: On the hamburger menu > Bookmarks or History @@ -4016,7 +4080,62 @@ Description: A tab's Close (X) button Location: In the tabbed browser. Path to .json: modules/data/tab_bar.components.json ``` - +``` +Selector name: tabgroup-menu +Selector Data: "tab-group-editor" +Description: Adding tabs to a group menu +Location: Menu that opens when adding tabs to a group +Path to .json: modules/data/tab_bar.components.json +``` +``` +Selector Name: tabgroup-input +Selector Data: "[data-l10n-id='tab-group-editor-name-field']" +Description: Name field +Location: Menu that opens when adding tabs to a group +Path to .json: modules/data/tab_bar.components.json +``` +``` +Selector Name: tabgroup-create-button +Selector Data: "tab-group-editor-button-create" +Description: Create button +Location: Menu that opens when adding tabs to a group +Path to .json: modules/data/tab_bar.components.json +``` +``` +Selector Name: tabgroup-label +Selector Data: "tab-group-label" +Description: The label of tab group +Location: In the tabbed browser +Path to .json: modules/data/tab_bar.components.json +``` +``` +Selector Name: tabgroup-menuitem +Selector Data: "tab-group-icon" +Description: Icon related to tab group +Location: In the tabbed browser +Path to .json: modules/data/tab_bar.components.json +``` +``` +Selector Name: tabgroup-overflow-count +Selector Data: "tab-group-overflow-count" +Description: Numbers of tabs in tab group +Location: In the tabbed browser +Path to .json: modules/data/tab_bar.components.json +``` +``` +Selector Name: tabgroup-line +Selector Data: "tab-group-line" +Description: Line indicator of tab group +Location: In the tabbed browser +Path to .json: modules/data/tab_bar.components.json +``` +``` +Selector Name: tabgroup-ungroup-tabs +Selector Data: "tabGroupEditor_ungroupTabs" +Description: Ungroup the tab group +Location: Menu that opens when right click on the tab group label +Path to .json: modules/data/tab_bar.components.json +``` #### text_area_form_autofill ``` Selector Name: street-address-textarea diff --git a/addtests.py b/addtests.py index 6c9b98e4c..845dc08c0 100644 --- a/addtests.py +++ b/addtests.py @@ -1,4 +1,5 @@ import os +import sys from manifests import testkey @@ -9,4 +10,7 @@ if __name__ == "__main__": manifest = testkey.TestKey(TEST_KEY) - manifest.addtests() + if len(sys.argv) > 1 and sys.argv[1] == "-q": + manifest.addtests(interactive=False) + else: + manifest.addtests() diff --git a/choose_ci_set.py b/choose_ci_set.py index 3898e0e54..082aecff1 100644 --- a/choose_ci_set.py +++ b/choose_ci_set.py @@ -220,6 +220,7 @@ def dedupe(run_list: list) -> list: # Dedupe just in case if SLASH == "\\": run_list = [entry.replace("/", SLASH) for entry in run_list] + run_list = [entry for entry in run_list if os.path.exists(entry)] run_list = manifest.filter_filenames_by_pass(run_list) run_list = dedupe(run_list) run_list = [entry for entry in run_list if os.path.exists(entry.split("::")[0])] diff --git a/make_manifest.py b/make_manifest.py deleted file mode 100644 index 2495c8f21..000000000 --- a/make_manifest.py +++ /dev/null @@ -1,36 +0,0 @@ -import os -import re - -import yaml - -TESTFILE_RE = re.compile(r"test_.*\.py") -TEST_RE = re.compile(r"def (test.*)\(") - -if __name__ == "__main__": - testdict = {} - for root, _, files in os.walk("tests"): - for f in files: - if not TESTFILE_RE.match(f): - continue - pointer = testdict - for level in os.path.split(root)[1:]: - if level not in testdict: - pointer[level] = {} - pointer = pointer[level] - status = "pass" - filename = os.path.join(root, f) - with open(filename) as fh: - metatest = f.rsplit(".", 1)[0] - for line in fh.readlines(): - m = TEST_RE.search(line) - if m and m[1] != "test_case": - if metatest not in pointer: - pointer[metatest] = {m[1]: status} - else: - pointer[metatest][m[1]] = status - print(yaml.safe_dump(testdict), "\n\n\n\n===\n\n\n\n") - status = "pass" - if "mark.unstable" in line: - status = "unstable" - with open("new_manifest.yaml", "w") as fh: - yaml.safe_dump(testdict, fh) diff --git a/manifests/incident.yaml b/manifests/incident.yaml deleted file mode 100644 index ac5defe79..000000000 --- a/manifests/incident.yaml +++ /dev/null @@ -1,158 +0,0 @@ -address_bar_and_search: -- test_add_engine_address_bar -- test_addon_suggestion -- test_default_search_provider_change_awesome_bar -- test_google_search_counts_us -- test_google_withads_url_bar_us -- test_sap_google_adclick -- test_search_code_google_non_us -- test_search_code_google_us -- test_search_engine_selector -- test_search_suggestions -- test_search_term_persists -- test_server_not_found_error -- test_suggestion_engine_selection -audio_video: -- test_allow_audio_video_functionality -- test_block_audio_video_functionality -- test_users_actions_saved_on_reload -bookmarks_and_history: -- test_add_new_other_bookmark -- test_bookmark_via_bookmark_menu -- test_bookmark_website_via_star_button -- test_clear_all_history -- test_clear_recent_history_displayed -- test_delete_bookmarks_from_toolbar -- test_delete_other_bookmarks -- test_deleted_page_not_remembered -- test_open_bookmark_in_new_window_via_toolbar_context_menu -- test_open_bookmark_in_private_window_via_toolbar_context_menu -- test_open_bookmarks_from_toolbar -- test_open_websites_from_history -- test_private_window_website_not_in_history -- test_toggle_bookmarks_toolbar -- test_user_can_forget_history -downloads: -- test_add_zip_type -- test_download_pdf -- test_download_pdf_from_context_menu -drag_and_drop: -- test_copy_entire_row_column -find_toolbar: -- test_find_in_pdf -- test_find_toolbar_nav -- test_find_toolbar_search -form_autofill: -- test_address_autofill_attribute -- test_autofill_cc_cvv -- test_autofill_credit_card -- test_autofill_credit_card_doorhanger -- test_autofill_credit_card_enable -- test_autofill_credit_card_four_fields -- test_cc_clear_form -- test_clear_form -- test_create_new_cc_profile -- test_create_profile_autofill -- test_delete_cc_profile -- test_enable_disable_autofill -- test_form_autofill_suggestions -- test_name_autofill_attribute -- test_private_mode_info_not_saved -- test_telephone_autofill_attribute -- test_updating_address -geolocation: -- test_geolocation_shared_via_html5 -- test_geolocation_shared_via_w3c_api -language_packs: -- test_language_pack_install_addons -- test_language_pack_install_preferences -menus: -- test_frequently_used_context_menu -- test_hyperlink_context_menu -- test_tab_context_menu_actions -- test_tab_context_menu_close -networking: -- test_default_dns_protection -- test_http_site -notifications: -- test_audio_video_permissions_notification -- test_camera_permissions_notification -- test_deny_geolocation -- test_deny_screen_capture -- test_geolocation_prompt_presence -- test_microphone_permissions_notification -- test_notifications_displayed -- test_screen_share_permission_prompt -- test_webextension_completed_installation_successfully_displayed -password_manager: -- test_add_password_non_ascii_chars -- test_add_password_save_valid_data -- test_auto_saved_generated_password_context_menu -- test_can_view_password_when_PP_enabled -- test_changes_made_in_edit_mode_are_saved -- test_delete_login -- test_multiple_saved_logins -- test_never_save_login_via_doorhanger -- test_primary_password_triggered_on_about_logins_access -- test_save_login_via_doorhanger -- test_update_login_via_doorhanger -pdf_viewer: -- test_download_pdf_with_form_fields -- test_download_triggered_on_content_disposition_attachment -- test_open_pdf_in_FF -- test_pdf_navigation -- test_zoom_pdf_viewer -preferences: -- test_check_for_updates -- test_clear_cookie_data -- test_firefox_home_new_tabs -- test_firefox_home_on_launch -- test_lang_pack_changed_from_about_prefs -- test_never_remember_history -- test_notifications_change_set -printing_ui: -- test_print_preview -profile: -- test_set_default_profile -reader_view: -- test_improved_type_control_panel -scrolling_panning_zooming: -- test_default_zoom_persists -- test_mouse_wheel_zoom -- test_zoom_text_only -security_and_privacy: -- test_blocking_cryptominers -- test_blocking_fingerprinters -- test_blocking_social_media_trackers -- test_cookies_not_saved_private_browsing -- test_cross_site_tracking_cookies_blocked -- test_cryptominers_blocked_and_shown_in_info_panel -- test_detected_blocked_trackers_found -- test_downloads_from_private_not_leaked -- test_https_enabled_private_browsing -- test_never_remember_browsing_history -- test_no_trackers_detected -- test_open_link_in_private_window -- test_phishing_and_malware_warnings -- test_private_browser_password_doorhanger -- test_private_session_awesome_bar_exclusion -- test_private_session_history_exclusion -- test_private_window_from_panelui -- test_third_party_content_blocked_private_browsing -- test_tls_v1_2_protocol -- test_trackers_crypto_fingerprint_blocked -- test_tracking_content_custom_mode -- test_undo_close_tab_private_browsing -tabs: -- test_active_tab -- test_navigation_multiple_tabs -- test_open_bookmark_in_new_tab -- test_open_new_tab -- test_open_new_tab_keys -- test_open_new_tab_via_hyperlink -- test_pin_tab -- test_reopen_tab_through_context_menu -- test_reopen_tab_through_history_menu -- test_reopen_tabs_through_keys -theme_and_toolbar: -- test_customize_themes_and_redirect diff --git a/manifests/key.yaml b/manifests/key.yaml index bbd6a97f8..04c51e021 100644 --- a/manifests/key.yaml +++ b/manifests/key.yaml @@ -6,6 +6,10 @@ address_bar_and_search: win: pass splits: - smoke + test_adaptive_history_removal: + result: pass + splits: + - functional1 test_add_engine_address_bar: result: pass splits: @@ -20,6 +24,10 @@ address_bar_and_search: splits: - smoke - ci-extended + test_addressbar_bookmarks_when_history_disabled: + result: pass + splits: + - functional1 test_addressbar_search_engine_keywords: result: pass splits: @@ -28,6 +36,10 @@ address_bar_and_search: result: pass splits: - functional1 + test_clipboard_pref_flip: + result: pass + splits: + - functional1 test_copied_url_contains_https: result: pass splits: @@ -87,6 +99,10 @@ address_bar_and_search: result: pass splits: - functional1 + test_open_link_in_new_container_tab: + result: pass + splits: + - functional1 test_paste_and_go_opens_correct_url: result: pass splits: @@ -347,10 +363,7 @@ downloads: splits: - smoke test_add_zip_type: - result: - linux: unstable - mac: pass - win: pass + result: unstable splits: - smoke - ci-extended @@ -1143,6 +1156,10 @@ tabs: splits: - smoke - ci-extended + test_ungroup_tabs: + result: pass + splits: + - functional1 theme_and_toolbar: test_customize_themes_and_redirect: result: pass diff --git a/manifests/testkey.py b/manifests/testkey.py index 6aee1fc30..1581611a8 100644 --- a/manifests/testkey.py +++ b/manifests/testkey.py @@ -1,11 +1,14 @@ import os import platform +import re +import sys from copy import deepcopy import yaml NUM_FUNCTIONAL_SPLITS = 1 MAX_DEPTH = 5 +SUITE_TUPLE_RE = re.compile(r'\s+return \("S(\d+)", ?".*"\)') def sysname(): @@ -196,20 +199,54 @@ def gather_split(self, split_name, pass_only=True): return test_filenames - def addtests(self): + def get_valid_suites_in_split(self, split, suite_numbers=False): + filenames = self.gather_split(split) + suite_dirs = [] + for filename in filenames: + suite_dir = filename.split(os.path.sep)[1] + if suite_dir not in suite_dirs: + suite_dirs.append(suite_dir) + + if not suite_numbers: + return suite_dirs + else: + suite_nums = [] + for suite_dir in suite_dirs: + with open(os.path.join("tests", suite_dir, "conftest.py")) as fh: + lines = fh.readlines() + suite_flag = False + for line in lines: + if suite_flag: + m = SUITE_TUPLE_RE.search(line) + if m and len(m.groups()) > 0: + suite_num = m.group(1) + if suite_num not in suite_nums: + suite_nums.append(suite_num) + break + return suite_nums + + def addtests(self, interactive=True): newkey = deepcopy(self.manifest) for root, _, files in os.walk(self.test_root): for f in files: suite = root.split(os.path.sep)[1] + if not (f.startswith("test_") and f.endswith(".py")): + continue testfile = f.replace(".py", "") if suite not in newkey: + if not interactive: + sys.exit( + "Please run `python addtests.py` from your command prompt." + ) if ask_question(f"Found suite {suite}. Add it to key? "): newkey[suite] = {} resplit = False if testfile not in newkey[suite]: - if not testfile.startswith("test_"): - continue + if not interactive: + sys.exit( + "Please run `python addtests.py` from your command prompt." + ) if ask_question(f"Found test {suite}/{testfile}. Add it to key? "): newkey[suite][testfile] = {"result": "pass"} @@ -229,17 +266,17 @@ def addtests(self): resplit = True if resplit: - split = ask_open_question( - "What split is this test assigned to? (One only) " - ) - newkey[suite][testfile]["splits"] = [split] - if ask_question( "Should this test run in a Scheduled Functional split? " "(Say no if unsure.) " ): - newkey[suite][testfile]["splits"].append("functional1") + newkey[suite][testfile]["splits"] = ["functional1"] self.rebalance_functionals() + else: + split = ask_open_question( + "What split is this test assigned to? (One only) " + ) + newkey[suite][testfile]["splits"] = [split] print( f"Test will be added to {split} in {self.manifest_file}. " diff --git a/modules/browser_object_context_menu.py b/modules/browser_object_context_menu.py index 6e3b95214..4df825787 100644 --- a/modules/browser_object_context_menu.py +++ b/modules/browser_object_context_menu.py @@ -24,6 +24,13 @@ def click_context_item( self.fetch(reference, labels=labels).click() return self + @BasePage.context_chrome + def open_link_in_container(self) -> BasePage: + """Open a link from the context menu in a specific container tab.""" + self.click_context_item("context-menu-open-link-in-new_container_tab") + self.click_context_item("context-menu-open-link-in-container-work") + return self + @BasePage.context_chrome def verify_topsites_tile_context_menu_options( self, diff --git a/modules/browser_object_navigation.py b/modules/browser_object_navigation.py index 297fcf614..b96d3655f 100644 --- a/modules/browser_object_navigation.py +++ b/modules/browser_object_navigation.py @@ -151,6 +151,17 @@ def click_switch_to_tab(self) -> None: # Click the first matching row switch_items[0].click() + @BasePage.context_chrome + def click_on_clipboard_suggestion(self) -> None: + """ + Click the 'Visit from clipboard' suggestion in the URL bar. + Requires: + - browser.urlbar.clipboard.featureGate = true + - Clipboard suggestion already visible + """ + row = self.wait.until(lambda d: self.get_element("clipboard-suggestion")) + row.click() + @BasePage.context_chrome def search(self, term: str, mode=None) -> BasePage: """ @@ -716,14 +727,18 @@ def delete_bookmark_from_other_bookmarks_via_context_menu( return self @BasePage.context_chrome - def delete_bookmark_from_bookmarks_toolbar(self, bookmark_name: str) -> BasePage: + def delete_panel_menu_item_by_title(self, item_title: str) -> BasePage: """ - Delete bookmark from bookmarks toolbar via context menu + Delete a panel menu item (bookmark or history entry) via context menu. + + This method works for both bookmarks and history items in the panel menu (hamburger menu), + as Firefox uses the same UI structure for both. The caller should ensure the appropriate + panel menu is open (e.g., History menu or Bookmarks menu) before calling this method. Argument: - bookmark_name: The display name of the bookmark to delete + item_title: The display name/title of the item to delete (works for both bookmarks and history entries) """ - self.panel_ui.context_click("bookmark-by-title", labels=[bookmark_name]) + self.panel_ui.context_click("panel-menu-item-by-title", labels=[item_title]) self.context_menu.click_and_hide_menu("context-menu-delete-page") return self @@ -750,7 +765,9 @@ def verify_bookmark_exists_in_bookmarks_toolbar( """ Verify bookmark exists in the bookmarks toolbar """ - self.panel_ui.element_visible("bookmark-by-title", labels=[bookmark_name]) + self.panel_ui.element_visible( + "panel-menu-item-by-title", labels=[bookmark_name] + ) return self @BasePage.context_chrome @@ -774,7 +791,9 @@ def verify_bookmark_does_not_exist_in_bookmarks_toolbar( self, bookmark_name: str ) -> BasePage: """Verify bookmark does not exist in the bookmarks toolbar""" - self.panel_ui.element_not_visible("bookmark-by-title", labels=[bookmark_name]) + self.panel_ui.element_not_visible( + "panel-menu-item-by-title", labels=[bookmark_name] + ) return self @BasePage.context_chrome @@ -826,8 +845,10 @@ def open_bookmark_from_toolbar(self, bookmark_title: str) -> BasePage: Argument: bookmark_title: The title of the bookmark to open """ - self.panel_ui.element_clickable("bookmark-by-title", labels=[bookmark_title]) - self.panel_ui.click_on("bookmark-by-title", labels=[bookmark_title]) + self.panel_ui.element_clickable( + "panel-menu-item-by-title", labels=[bookmark_title] + ) + self.panel_ui.click_on("panel-menu-item-by-title", labels=[bookmark_title]) return self @BasePage.context_chrome @@ -841,8 +862,10 @@ def open_bookmark_in_new_tab_via_context_menu( bookmark_title: The title of the bookmark to open """ # Right-click the bookmark and open it in new tabe via context menu item - self.panel_ui.element_clickable("bookmark-by-title", labels=[bookmark_title]) - self.panel_ui.context_click("bookmark-by-title", labels=[bookmark_title]) + self.panel_ui.element_clickable( + "panel-menu-item-by-title", labels=[bookmark_title] + ) + self.panel_ui.context_click("panel-menu-item-by-title", labels=[bookmark_title]) self.context_menu.click_on("context-menu-toolbar-open-in-new-tab") return self @@ -857,8 +880,10 @@ def open_bookmark_in_new_window_via_context_menu( Argument: bookmark_title: The title of the bookmark to open """ - self.panel_ui.element_clickable("bookmark-by-title", labels=[bookmark_title]) - self.panel_ui.context_click("bookmark-by-title", labels=[bookmark_title]) + self.panel_ui.element_clickable( + "panel-menu-item-by-title", labels=[bookmark_title] + ) + self.panel_ui.context_click("panel-menu-item-by-title", labels=[bookmark_title]) self.context_menu.click_on("context-menu-toolbar-open-in-new-window") return self @@ -872,8 +897,10 @@ def open_bookmark_in_new_private_window_via_context_menu( Argument: bookmark_title: The title of the bookmark to open """ - self.panel_ui.element_clickable("bookmark-by-title", labels=[bookmark_title]) - self.panel_ui.context_click("bookmark-by-title", labels=[bookmark_title]) + self.panel_ui.element_clickable( + "panel-menu-item-by-title", labels=[bookmark_title] + ) + self.panel_ui.context_click("panel-menu-item-by-title", labels=[bookmark_title]) self.context_menu.click_on("context-menu-toolbar-open-in-new-private-window") return self @@ -1085,3 +1112,123 @@ def click_exit_button_searchmode(self) -> None: # Click the button self.get_element("exit-button-searchmode").click() + + @BasePage.context_chrome + def type_and_verify( + self, + input_text: str, + expected_text: str, + timeout: float = 5.0, + click: bool = True, # If True: click match; else: return index + ) -> int | bool: + """ + Types into the awesome bar, waits for a suggestion containing `expected_text`. + + If `click=True` (default): + - Click the matching element and return True + - Raises if not found (test fails) + + If `click=False`: + - Return the 0-based index of the matching element + - Raises if not found (test fails) + """ + + # Reset + type + self.clear_awesome_bar() + self.type_in_awesome_bar(input_text) + + def find_match(driver): + suggestions = self.get_all_children("results-dropdown") + + for index, s in enumerate(suggestions): + try: + if expected_text in s.text: + return (s, index) + except StaleElementReferenceException: + continue + + return False # keep polling + + # No try/except here — failure = real failure + element, index = self.custom_wait(timeout=timeout).until(find_match) + + if click: + element.click() + return True + + return index + + @BasePage.context_chrome + def verify_autofill_adaptive_element( + self, expected_type: str, expected_url: str + ) -> BasePage: + """ + Verify that the adaptive history autofill element has the expected type and URL text. + This method handles chrome context switching internally. + Arguments: + expected_type: Expected type attribute value + expected_url: Expected URL fragment to be contained in the element text + """ + autofill_element = self.get_element("search-result-autofill-adaptive-element") + actual_type = autofill_element.get_attribute("type") + actual_text = autofill_element.text + + assert actual_type == expected_type + assert expected_url in actual_text + + return self + + @BasePage.context_chrome + def verify_no_autofill_adaptive_elements(self) -> BasePage: + autofill_elements = self.get_elements("search-result-autofill-adaptive-element") + if autofill_elements: + logging.warning( + f"Unexpected adaptive autofill elements found: {[el.text for el in autofill_elements]}" + ) + assert len(autofill_elements) == 0, ( + "Adaptive history autofill suggestion was not removed after deletion." + ) + return self + + @BasePage.context_chrome + def verify_autofill_adaptive_element( + self, expected_type: str, expected_url: str + ) -> BasePage: + """ + Verify that the adaptive history autofill element has the expected type and URL text. + This method handles chrome context switching internally. + Arguments: + expected_type: Expected type attribute value + expected_url: Expected URL fragment to be contained in the element text + """ + autofill_element = self.get_element("search-result-autofill-adaptive-element") + actual_type = autofill_element.get_attribute("type") + actual_text = autofill_element.text + + assert actual_type == expected_type + assert expected_url in actual_text + + return self + + @BasePage.context_chrome + def verify_no_autofill_adaptive_elements(self) -> BasePage: + """Verify that no adaptive history autofill elements are present.""" + autofill_elements = self.get_elements("search-result-autofill-adaptive-element") + if autofill_elements: + logging.warning( + f"Unexpected adaptive autofill elements found: {[el.text for el in autofill_elements]}" + ) + assert len(autofill_elements) == 0, ( + "Adaptive history autofill suggestion was not removed after deletion." + ) + return self + + @BasePage.context_chrome + def expect_container_label(self, label_expected: str): + """ + Verify the container label for user context (container tabs). + Argument: + label_expected: The expected label text for the user context container. + """ + actual_label = self.get_element("tab-container-label").text + assert actual_label == label_expected diff --git a/modules/browser_object_panel_ui.py b/modules/browser_object_panel_ui.py index 0c76aa25d..15d355767 100644 --- a/modules/browser_object_panel_ui.py +++ b/modules/browser_object_panel_ui.py @@ -282,32 +282,26 @@ def confirm_history_clear(self): def verify_history_item_exists(self, item_title: str) -> BasePage: """ Verify that a history item with the specified title exists in the history menu. - Note: - This method uses the "bookmark-by-title" selector, which works for both - bookmarks and history items because Firefox uses the same CSS class "bookmark-item" - for both in the hamburger menu UI. This is intentional design in Firefox's UI. + Argument: item_title (str): The title text to look for in the history item (can be partial match) """ self.open_history_menu() self.get_all_history() - self.element_visible("bookmark-by-title", labels=[item_title]) + self.element_visible("panel-menu-item-by-title", labels=[item_title]) return self @BasePage.context_chrome def verify_history_item_does_not_exist(self, item_title: str) -> BasePage: """ - Verify that a history item with the specified title exists in the history menu. - Note: - This method uses the "bookmark-by-title" selector, which works for both - bookmarks and history items because Firefox uses the same CSS class "bookmark-item" - for both in the hamburger menu UI. This is intentional design in Firefox's UI. + Verify that a history item with the specified title does not exist in the history menu. + Argument: item_title (str): The title text to look for in the history item (can be partial match) """ self.open_history_menu() self.get_all_history() - self.element_not_visible("bookmark-by-title", labels=[item_title]) + self.element_not_visible("panel-menu-item-by-title", labels=[item_title]) return self # Bookmarks section @@ -338,7 +332,7 @@ def verify_bookmark_exists_in_hamburger_menu(self, bookmark_title: str) -> BaseP Arguments: bookmark_title (str): The title text to look for in the bookmark """ - self.element_visible("bookmark-by-title", labels=[bookmark_title]) + self.element_visible("panel-menu-item-by-title", labels=[bookmark_title]) return self @BasePage.context_chrome diff --git a/modules/browser_object_tabbar.py b/modules/browser_object_tabbar.py index 417a83f81..a2d775b3d 100644 --- a/modules/browser_object_tabbar.py +++ b/modules/browser_object_tabbar.py @@ -7,6 +7,7 @@ from selenium.webdriver.remote.webelement import WebElement from selenium.webdriver.support import expected_conditions as EC +from modules.browser_object import ContextMenu from modules.page_base import BasePage @@ -419,3 +420,35 @@ def reload_tab(self, nav, mod_key=None, extra_key=None): self.perform_key_combo(extra_key) else: raise ValueError("You must provide extra_key to perform reload.") + + @BasePage.context_chrome + def create_tab_group( + self, num_tabs: int, group_name: str, tab_context_menu: ContextMenu + ) -> BasePage: + """Create a new tab group""" + + # Open few tabs + for i in range(num_tabs): + self.new_tab_by_button() + + # Add the first tab into a New Group + first_tab = self.get_tab(1) + self.context_click(first_tab) + tab_context_menu.click_and_hide_menu("context-move-tab-to-new-group") + + # Wait for tab group menu to open + self.element_visible("tabgroup-input") + + # Enter a group Name and create group + self.fill("tabgroup-input", group_name, clear_first=False) + + # Make sure the group is created + self.element_visible("tabgroup-label") + + # Add the second tab into existing Group + second_tab = self.get_tab(2) + self.context_click(second_tab) + tab_context_menu.click_on("context-move-tab-to-group") + self.click_and_hide_menu("tabgroup-menuitem") + + return self diff --git a/modules/data/about_prefs.components.json b/modules/data/about_prefs.components.json index 645c5242f..6814c9749 100644 --- a/modules/data/about_prefs.components.json +++ b/modules/data/about_prefs.components.json @@ -605,6 +605,11 @@ "strategy": "css", "groups": [] }, + "clipboard-suggestion-checkbox": { + "selectorData": "#clipboardSuggestion", + "strategy": "css", + "groups": ["doNotCache"] + }, "unknown-content-type-dialog": { "selectorData": "unknownContentTypeWindow", @@ -612,6 +617,10 @@ "groups": [ "doNotCache" ] + }, + "history-suggestion": { + "selectorData": "historySuggestion", + "strategy": "id", + "groups": [] } - } diff --git a/modules/data/context_menu.components.json b/modules/data/context_menu.components.json index d84d6bc6f..adc42516f 100644 --- a/modules/data/context_menu.components.json +++ b/modules/data/context_menu.components.json @@ -55,6 +55,24 @@ "groups": [] }, + "context-move-tab-to-new-group": { + "selectorData": "context_moveTabToNewGroup", + "strategy": "id", + "groups": [] + }, + + "context-move-tab-to-group": { + "selectorData": "context_moveTabToGroup", + "strategy": "id", + "groups": [] + }, + + "context-remove-tab-from-group": { + "selectorData": "context_ungroupTab", + "strategy": "id", + "groups": [] + }, + "context-menu-pin-tab": { "selectorData": "context_pinTab", "strategy": "id", @@ -121,6 +139,12 @@ "groups": [] }, + "context-menu-open-link-in-new_container_tab": { + "selectorData": "context-openlinkinusercontext-menu", + "strategy": "id", + "groups": [] + }, + "context-menu-toolbar-open-in-new-tab": { "selectorData": "placesContext_open:newtab", "strategy": "id", @@ -233,6 +257,12 @@ "selectorData": "context-searchselect", "strategy": "id", "groups": [] - } + }, + + "context-menu-open-link-in-container-work": { + "selectorData": "menuitem[data-l10n-id='user-context-work']", + "strategy": "css", + "groups": [] +} } diff --git a/modules/data/navigation.components.json b/modules/data/navigation.components.json index 05a9e4007..68d7e95d6 100644 --- a/modules/data/navigation.components.json +++ b/modules/data/navigation.components.json @@ -669,6 +669,17 @@ "selectorData": "div.urlbarView-row[type='switchtab']", "strategy": "css", "groups": ["doNotCache"] - } + }, + "tab-container-label": { + "selectorData": "userContext-label", + "strategy": "id", + "groups": [] + }, + + "clipboard-suggestion": { + "selectorData": "#urlbar-results > div.urlbarView-row[type='clipboard']", + "strategy": "css", + "groups": ["doNotCache"] + } } diff --git a/modules/data/panel_ui.components.json b/modules/data/panel_ui.components.json index c4e35ef62..d08caa083 100644 --- a/modules/data/panel_ui.components.json +++ b/modules/data/panel_ui.components.json @@ -180,7 +180,7 @@ "groups": [] }, - "bookmark-by-title": { + "panel-menu-item-by-title": { "selectorData": "toolbarbutton.bookmark-item[label*='{title}']", "strategy": "css", "groups": [] diff --git a/modules/data/tab_bar.components.json b/modules/data/tab_bar.components.json index 22449696c..bb20fffb2 100644 --- a/modules/data/tab_bar.components.json +++ b/modules/data/tab_bar.components.json @@ -113,6 +113,66 @@ "groups": [ "doNotCache" ] + }, + + "tabgroup-menu": { + "selectorData": "tab-group-editor", + "strategy": "id", + "groups": [ + "doNotCache" + ] + }, + + "tabgroup-input": { + "selectorData": "[data-l10n-id='tab-group-editor-name-field']", + "strategy": "css", + "groups": [ + "doNotCache" + ] + }, + + "tabgroup-create-button": { + "selectorData": "tab-group-editor-button-create", + "strategy": "id", + "groups": [] + }, + + "tabgroup-label": { + "selectorData": "tab-group-label", + "strategy": "class", + "groups": [ + "doNotCache" + ] + }, + + "tabgroup-menuitem": { + "selectorData": "tab-group-icon", + "strategy": "class", + "groups": [ + "doNotCache" + ] + }, + + "tabgroup-overflow-count": { + "selectorData": "tab-group-overflow-count", + "strategy": "class", + "groups": [ + "doNotCache" + ] + }, + + "tabgroup-line": { + "selectorData": "tab-group-line", + "strategy": "class", + "groups": [ + "doNotCache" + ] + }, + + "tabgroup-ungroup-tabs": { + "selectorData": "tabGroupEditor_ungroupTabs", + "strategy": "id", + "groups": [] } } diff --git a/modules/page_object_prefs.py b/modules/page_object_prefs.py index cdaa4ce31..e46432aaf 100644 --- a/modules/page_object_prefs.py +++ b/modules/page_object_prefs.py @@ -160,6 +160,12 @@ def restore_default_search_engines(self) -> BasePage: self.click_on("restore-default-search-engines-button") return self + @BasePage.context_content + def verify_clipboard_suggestion_enabled(self) -> None: + checkbox = self.get_element("clipboard-suggestion-checkbox") + is_checked = checkbox.get_attribute("checked") in ("true", "checked", "") + assert is_checked, "Expected clipboardSuggestion checkbox to be checked" + def set_alternative_language(self, lang_code: str) -> BasePage: """Changes the browser language""" self.get_element("language-set-alternative-button").click() @@ -805,6 +811,14 @@ def open_manage_cookies_data_dialog(self) -> BasePage: BrowserActions(self.driver).switch_to_iframe_context(manage_data_popup) return self + def uncheck_history_suggestion(self): + """ + Uncheck the 'historySuggestion' checkbox if it's currently checked. + """ + checkbox = self.get_element("history-suggestion") + if checkbox.is_selected(): + checkbox.click() + class AboutAddons(BasePage): """ diff --git a/modules/testrail_integration.py b/modules/testrail_integration.py index a76265d1d..87dcab0db 100644 --- a/modules/testrail_integration.py +++ b/modules/testrail_integration.py @@ -5,6 +5,7 @@ import sys from choose_l10n_ci_set import select_l10n_mappings +from manifests.testkey import TestKey from modules import taskcluster as tc from modules import testrail as tr from modules.testrail import TestRail @@ -17,6 +18,7 @@ "[{channel} {major}] {plan}Automated testing {major}.{minor}b{beta}-build{build}" ) PLAN_NAME_RE = re.compile(r"\[(\w+) (\d+)\]") +TEST_KEY_LOCATION = os.path.join("manifests", "key.yaml") CONFIG_GROUP_ID = 95 TESTRAIL_FX_DESK_PRJ = 17 TC_EXECUTION_TEMPLATE = "https://firefox-ci-tc.services.mozilla.com/tasks/%TASK_ID%/runs/%RUN_ID%/logs/live/public/logs/live.log" @@ -267,24 +269,30 @@ def reportable(platform_to_test=None): # Only report when there is a new beta without a reported plan or if the selected split is not completely reported. return covered_mappings < expected_mappings else: - covered_suites = 0 + covered_suites = [] for entry in plan_entries: for run_ in entry.get("runs"): if run_.get("config") and platform in run_.get("config"): - covered_suites += 1 + covered_suites.append(str(run_.get("suite_id"))) - num_suites = 0 - for test_dir_name in os.listdir("tests"): - test_dir = os.path.join("tests", test_dir_name) - if os.path.isdir(test_dir) and not os.path.exists( - os.path.join(test_dir, "skip_reporting") - ): - num_suites += 1 - - logging.warning( - f"Potentially matching run found for {platform}, may be reportable. ({covered_suites} out of {num_suites} suites already reported.)" + if not os.environ.get("STARFOX_SPLIT"): + sys.exit("No split selected") + manifest = TestKey() + expected_suites = manifest.get_valid_suites_in_split( + os.environ["STARFOX_SPLIT"] ) - return covered_suites + 1 < num_suites + + uncovered_suites = set(expected_suites) - set(covered_suites) + if len(uncovered_suites): + suite_names = [ + s.get("name") + for s in tr_session.get_suites(TESTRAIL_FX_DESK_PRJ) + if str(s.get("id")) in uncovered_suites + ] + print("Coverage not found for the following suites:") + print("\t-" + "\n\t-".join(suite_names)) + + return not uncovered_suites def testrail_init() -> TestRail: diff --git a/pre-commit b/pre-commit index 3780dc371..2614cfe7a 100755 --- a/pre-commit +++ b/pre-commit @@ -60,7 +60,7 @@ fi # Lint and format with ruff ruff check --select I --fix ruff format . -python addtests.py +python addtests.py -q git add -u # if ruff check doesn't work, we need to fail diff --git a/taskcluster/kinds/lint/kind.yml b/taskcluster/kinds/lint/kind.yml index c6998926c..4c6df7709 100644 --- a/taskcluster/kinds/lint/kind.yml +++ b/taskcluster/kinds/lint/kind.yml @@ -13,7 +13,11 @@ tasks: using: run-task cwd: "{checkout}" command: |- - pip3 install 'ruff>=0.4.8,<0.5' + export PATH=$HOME/.local/bin:$PWD:$PATH + pip3 install 'pipenv==2023.11.15'; mv ./dev_pyproject.toml ./pyproject.toml; - python3 -m ruff format --check; - python3 -m ruff check --select I; + pipenv install; + pipenv run python3 -m ruff format --check; + export EXIT_CODE=$?; + pipenv run python3 -m ruff check --select I; + exit $(( $EXIT_CODE || $? )); diff --git a/taskcluster/kinds/new-beta-func/kind.yml b/taskcluster/kinds/new-beta-func/kind.yml index da5659383..4c1417920 100644 --- a/taskcluster/kinds/new-beta-func/kind.yml +++ b/taskcluster/kinds/new-beta-func/kind.yml @@ -36,7 +36,6 @@ tasks: export PATH=$HOME/.local/bin:$PWD:$PATH mkdir -p artifacts; pip3 install 'pipenv==2023.11.15'; - pip3 install 'ruff>=0.4.8,<0.5'; mv ./ci_pyproject.toml ./pyproject.toml; pipenv install; export FX_CHANNEL="beta"; diff --git a/taskcluster/kinds/new-beta-qa/kind.yml b/taskcluster/kinds/new-beta-qa/kind.yml index da5eba872..56a64c4ef 100644 --- a/taskcluster/kinds/new-beta-qa/kind.yml +++ b/taskcluster/kinds/new-beta-qa/kind.yml @@ -35,7 +35,6 @@ tasks: export PATH=$HOME/.local/bin:$PWD:$PATH mkdir -p artifacts; pip3 install 'pipenv==2023.11.15'; - pip3 install 'ruff>=0.4.8,<0.5'; mv ./ci_pyproject.toml ./pyproject.toml; pipenv install; export FX_CHANNEL="beta"; diff --git a/taskcluster/kinds/new-devedition-qa/kind.yml b/taskcluster/kinds/new-devedition-qa/kind.yml index cc8ef6aa0..b4c09817a 100644 --- a/taskcluster/kinds/new-devedition-qa/kind.yml +++ b/taskcluster/kinds/new-devedition-qa/kind.yml @@ -35,7 +35,6 @@ tasks: export PATH=$HOME/.local/bin:$PWD:$PATH mkdir -p artifacts; pip3 install 'pipenv==2023.11.15'; - pip3 install 'ruff>=0.4.8,<0.5'; mv ./ci_pyproject.toml ./pyproject.toml; pipenv install; export FX_CHANNEL="devedition"; diff --git a/taskcluster/kinds/run-smoke-tests/kind.yml b/taskcluster/kinds/run-smoke-tests/kind.yml index cdb77e9e0..2355bc8e0 100644 --- a/taskcluster/kinds/run-smoke-tests/kind.yml +++ b/taskcluster/kinds/run-smoke-tests/kind.yml @@ -28,7 +28,6 @@ tasks: export PATH=$HOME/.local/bin:$PWD:$PATH mkdir -p artifacts; pip3 install 'pipenv==2023.11.15'; - pip3 install 'ruff>=0.4.8,<0.5'; mv ./ci_pyproject.toml ./pyproject.toml; pipenv install; ./collect_executables.sh; diff --git a/taskcluster/requirements.txt b/taskcluster/requirements.txt index 8af27f73a..72e8543e8 100644 --- a/taskcluster/requirements.txt +++ b/taskcluster/requirements.txt @@ -280,9 +280,9 @@ text-unidecode==1.3 \ --hash=sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8 \ --hash=sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93 # via python-slugify -urllib3==2.5.0 \ - --hash=sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760 \ - --hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc +urllib3==2.6.0 \ + --hash=sha256:c90f7a39f716c572c4e3e58509581ebd83f9b59cced005b7db7ad2d22b0db99f \ + --hash=sha256:cb9bcef5a4b345d5da5d145dc3e30834f58e8018828cbc724d30b4cb7d4d49f1 # via requests voluptuous==0.13.1 \ --hash=sha256:4b838b185f5951f2d6e8752b68fcf18bd7a9c26ded8f143f92d6d28f3921a3e6 \ diff --git a/tests/address_bar_and_search/test_adaptive_history_autofill.py b/tests/address_bar_and_search/test_adaptive_history_autofill.py index 7e726e8dc..750190ddf 100644 --- a/tests/address_bar_and_search/test_adaptive_history_autofill.py +++ b/tests/address_bar_and_search/test_adaptive_history_autofill.py @@ -1,16 +1,14 @@ import pytest from selenium.webdriver import Firefox -from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.support.wait import WebDriverWait from modules.browser_object import Navigation from modules.browser_object_tabbar import TabBar -WAIT_TIMEOUT = 10 TEST_URL = "https://www.nationalgeographic.com/science/" EXPECTED_IN_TITLE = "Science" +TYPED_TEXT = "nat" EXPECTED_TYPE = "autofill_adaptive" -EXPECTED_TEXT_FRAGMENT = "nationalgeographic.com/science" +EXPECTED_URL = "nationalgeographic.com/science" @pytest.fixture() @@ -27,38 +25,26 @@ def test_add_adaptive_history_autofill(driver: Firefox): """ C1814373 - Verify adaptive history autofill triggers from address bar input. """ + # Instantiate objects nav = Navigation(driver) tabs = TabBar(driver) - # Step 1: Visit the test site and verify tab title + # Visit the test site and verify title nav.search(TEST_URL) - WebDriverWait(driver, WAIT_TIMEOUT).until( - lambda d: tabs.expect_title_contains(EXPECTED_IN_TITLE) - ) + tabs.expect_title_contains(EXPECTED_IN_TITLE) - # Step 2: Open new tab, close the original + # Open new tab, close the original tabs.new_tab_by_button() - tabs.wait_for_num_tabs(2) driver.switch_to.window(driver.window_handles[1]) tabs.close_first_tab_by_icon() - # Step 3: Type in address bar, then click adaptive suggestion - nav.type_in_awesome_bar("nat") + # Type in address bar, then click adaptive suggestion + nav.type_in_awesome_bar(TYPED_TEXT) nav.click_firefox_suggest() - nav.expect_in_content(EC.url_contains(TEST_URL)) + nav.url_contains(TEST_URL) - # Step 4: Open new tab and check for autofill suggestion + # Open new tab and check for autofill suggestion tabs.new_tab_by_button() - tabs.wait_for_num_tabs(2) driver.switch_to.window(driver.window_handles[-1]) - nav.type_in_awesome_bar("nat") - - tabs.set_chrome_context() - autofill_element = nav.get_element("search-result-autofill-adaptive-element") - - assert autofill_element.get_attribute("type") == EXPECTED_TYPE, ( - f"Expected type '{EXPECTED_TYPE}', got '{autofill_element.get_attribute('type')}'" - ) - assert EXPECTED_TEXT_FRAGMENT in autofill_element.text, ( - f"Autofill text did not contain expected URL. Got: {autofill_element.text}" - ) + nav.type_in_awesome_bar(TYPED_TEXT) + nav.verify_autofill_adaptive_element(EXPECTED_TYPE, EXPECTED_URL) diff --git a/tests/address_bar_and_search/test_adaptive_history_removal.py b/tests/address_bar_and_search/test_adaptive_history_removal.py new file mode 100644 index 000000000..725dbf5e5 --- /dev/null +++ b/tests/address_bar_and_search/test_adaptive_history_removal.py @@ -0,0 +1,64 @@ +import pytest +from selenium.webdriver import Firefox + +from modules.browser_object import Navigation +from modules.browser_object_panel_ui import PanelUi +from modules.browser_object_tabbar import TabBar + +TEST_URL = "https://www.nationalgeographic.com/science/" +TYPED_TEXT = "nat" +EXPECTED_IN_TITLE = "Science" +EXPECTED_TYPE = "autofill_adaptive" +EXPECTED_URL = "nationalgeographic.com/science" + + +@pytest.fixture() +def test_case(): + return "3029071" + + +@pytest.fixture() +def add_to_prefs_list(): + return [("browser.urlbar.autoFill.adaptiveHistory.enabled", True)] + + +def test_remove_adaptive_history_entry(driver: Firefox) -> None: + """ + C3029071 - Verify adaptive history entry is deleted from history and not suggested in address bar. + """ + # Instantiate objects + nav = Navigation(driver) + tabs = TabBar(driver) + panel = PanelUi(driver) + + # Visit the test site and verify title + nav.search(TEST_URL) + tabs.expect_title_contains(EXPECTED_IN_TITLE) + + # Open new tab, close the original + tabs.new_tab_by_button() + driver.switch_to.window(driver.window_handles[1]) + tabs.close_first_tab_by_icon() + + # Type in address bar, then click adaptive suggestion + nav.type_in_awesome_bar(TYPED_TEXT) + nav.click_firefox_suggest() + nav.url_contains(TEST_URL) + + # Open new tab and check for autofill suggestion + tabs.new_tab_by_button() + driver.switch_to.window(driver.window_handles[-1]) + nav.type_in_awesome_bar(TYPED_TEXT) + nav.verify_autofill_adaptive_element(EXPECTED_TYPE, EXPECTED_URL) + + # Delete the adaptive history entry + panel.open_history_menu() + nav.delete_panel_menu_item_by_title(EXPECTED_IN_TITLE) + panel.confirm_history_clear() + + # Open new tab and verify the adaptive suggestion is removed + tabs.new_tab_by_button() + driver.switch_to.window(driver.window_handles[1]) + tabs.close_first_tab_by_icon() + nav.type_in_awesome_bar(TYPED_TEXT) + nav.verify_no_autofill_adaptive_elements() diff --git a/tests/address_bar_and_search/test_addressbar_bookmarks_when_history_disabled.py b/tests/address_bar_and_search/test_addressbar_bookmarks_when_history_disabled.py new file mode 100644 index 000000000..c70551b59 --- /dev/null +++ b/tests/address_bar_and_search/test_addressbar_bookmarks_when_history_disabled.py @@ -0,0 +1,47 @@ +import pytest +from selenium.webdriver import Firefox + +from modules.browser_object_navigation import Navigation +from modules.browser_object_tabbar import TabBar +from modules.page_object_prefs import AboutPrefs + + +@pytest.fixture() +def test_case(): + return "3028908" + + +def test_addressbar_bookmarks_when_history_disabled(driver: Firefox): + """ + C3028908 - Most relevant bookmarks are shown in the address bar even when history is disabled + """ + + # Instantiate objects + nav = Navigation(driver) + tabs = TabBar(driver) + about_prefs = AboutPrefs(driver, category="search") + + # In addressbar press z and pick "Get Involved" page + nav.type_and_verify("z", "Get Involved", click=True) + + # Repeat step from above + tabs.new_tab_by_button() + nav.type_and_verify("z", "Get Involved", click=True) + + # On z press check "Get Involved" page is shown as the first result + tabs.new_tab_by_button() + position = nav.type_and_verify("z", "Get Involved", click=False) + assert position == 1 + + # Navigate to about:preferences and uncheck Browsing History + tabs.new_tab_by_button() + driver.switch_to.window(driver.window_handles[-1]) + about_prefs.open() + about_prefs.uncheck_history_suggestion() + + # Open a new tab and press z + tabs.new_tab_by_button() + + # Check "Get Involved" page is shown as the first result + position = nav.type_and_verify("z", "Get Involved", click=False) + assert position == 1 diff --git a/tests/address_bar_and_search/test_clipboard_pref_flip.py b/tests/address_bar_and_search/test_clipboard_pref_flip.py new file mode 100644 index 000000000..b3a049db1 --- /dev/null +++ b/tests/address_bar_and_search/test_clipboard_pref_flip.py @@ -0,0 +1,61 @@ +import pytest +from selenium.webdriver import Firefox + +from modules.browser_object_context_menu import ContextMenu +from modules.browser_object_navigation import Navigation +from modules.page_object_newtab import AboutNewtab +from modules.page_object_prefs import AboutPrefs + + +@pytest.fixture() +def test_case(): + return "3029320" + + +@pytest.fixture() +def add_to_prefs_list(): + # Required to expose "Visit from clipboard" suggestions + return [("browser.urlbar.clipboard.featureGate", True)] + + +TOPSITE_TITLE = "Wikipedia" + + +def test_clipboard_pref_flip(driver: Firefox): + """ + 3029320 – Verify clipboard suggestion appears in the URL bar + after copying a TopSite link. + """ + + nav = Navigation(driver) + context_menu = ContextMenu(driver) + newtab = AboutNewtab(driver) + prefs = AboutPrefs(driver) + + # Step 1: Open about:newtab + driver.get("about:newtab") + + # Step 2: Copy a TopSite link via context menu + topsite_el = newtab.get_topsite_element(TOPSITE_TITLE) + + newtab.hover(topsite_el) + nav.verify_status_panel_url(TOPSITE_TITLE.lower()) + copied_url = nav.get_status_panel_url() + + newtab.open_topsite_context_menu_by_title(TOPSITE_TITLE) + context_menu.click_context_item("context-menu-copy-link") + + # Step 3: Activate the Awesome Bar + nav.clear_awesome_bar() + nav.click_on("awesome-bar") + + # Step 4: Wait for clipboard suggestion (CHROME context) + nav.click_on_clipboard_suggestion() + + # Step 5: Validate navigation & URL bar text + nav.url_contains(copied_url) + assert copied_url in nav.get_awesome_bar_text() + + # Step 6: Go to about:preferences#search and check the pref is enabled + driver.get("about:preferences#search") + prefs.verify_clipboard_suggestion_enabled() diff --git a/tests/address_bar_and_search/test_open_link_in_new_container_tab.py b/tests/address_bar_and_search/test_open_link_in_new_container_tab.py new file mode 100644 index 000000000..4f43d93f5 --- /dev/null +++ b/tests/address_bar_and_search/test_open_link_in_new_container_tab.py @@ -0,0 +1,47 @@ +import pytest +from selenium.webdriver import Firefox + +from modules.browser_object_context_menu import ContextMenu +from modules.browser_object_navigation import Navigation +from modules.browser_object_tabbar import TabBar +from modules.page_object_generics import GenericPage +from modules.page_object_newtab import AboutNewtab + + +@pytest.fixture() +def test_case(): + return "3029117" + + +@pytest.fixture() +def add_to_prefs_list(): + return [("privacy.userContext.enabled", True)] + + +TOPSITE_TITLE = "Wikipedia" +TOPSITE_URL = "www.wikipedia.org" +EXPECTED_CONTAINER = "Work" + + +def test_open_link_in_new_container_tab(driver: Firefox) -> None: + """ + C3029117 - Verify that a link opened from the context menu in a new container tab opens + in the correct container and URL. + """ + tabs = TabBar(driver) + new_tab = AboutNewtab(driver) + context_menu = ContextMenu(driver) + nav = Navigation(driver) + page = GenericPage(driver, url="about:newtab") + + # Open about:newtab and right-click to open context menu + page.open() + new_tab.open_topsite_context_menu_by_title(TOPSITE_TITLE) + + # Click first option and verify link opens in new tab + context_menu.open_link_in_container() + + # Switch to new tab and verify URL and container + tabs.switch_to_new_tab() + nav.url_contains(TOPSITE_URL) + nav.expect_container_label(EXPECTED_CONTAINER) diff --git a/tests/bookmarks_and_history/test_delete_bookmarks_from_toolbar.py b/tests/bookmarks_and_history/test_delete_bookmarks_from_toolbar.py index 8a34f4517..d72d15408 100644 --- a/tests/bookmarks_and_history/test_delete_bookmarks_from_toolbar.py +++ b/tests/bookmarks_and_history/test_delete_bookmarks_from_toolbar.py @@ -30,7 +30,7 @@ def test_delete_bookmarks_from_toolbar(driver: Firefox): nav.verify_bookmark_exists_in_bookmarks_toolbar("Internet for people") # Delete the bookmark from toolbar - nav.delete_bookmark_from_bookmarks_toolbar("Internet for people") + nav.delete_panel_menu_item_by_title("Internet for people") # Verify bookmark was deleted nav.verify_bookmark_does_not_exist_in_bookmarks_toolbar("Internet for people") diff --git a/tests/bookmarks_and_history/test_deleted_page_not_remembered.py b/tests/bookmarks_and_history/test_deleted_page_not_remembered.py index ea3898ec3..f17e9c1d7 100644 --- a/tests/bookmarks_and_history/test_deleted_page_not_remembered.py +++ b/tests/bookmarks_and_history/test_deleted_page_not_remembered.py @@ -26,8 +26,7 @@ def test_deleted_page_not_remembered(driver: Firefox, sys_platform): # Open history menu and right-click on a specific history entry and delete it panel.open_history_menu() - panel.context_click("bookmark-by-title", labels=["Firefox Privacy Notice"]) - context_menu.click_and_hide_menu("context-menu-delete-page") + nav.delete_panel_menu_item_by_title("Firefox Privacy Notice") # Type the deleted page name in the URL bar and verify the deleted page is not suggested nav.type_in_awesome_bar("Firefox Privacy Notice") diff --git a/tests/downloads/test_add_zip_type.py b/tests/downloads/test_add_zip_type.py index 73de1a70f..c8d4c4e2e 100644 --- a/tests/downloads/test_add_zip_type.py +++ b/tests/downloads/test_add_zip_type.py @@ -26,8 +26,7 @@ def delete_files_regex_string(): def temp_selectors(): return { "github-code-button": { - "selectorData": "/html/body/div[1]/div[4]/div/main/turbo-frame/div/div/div/div/div[" - "1]/react-partial/div/div/div[2]/div[2]/button", + "selectorData": "//span[@class='prc-Button-Label-pTQ3x' and text()='Code']", "strategy": "xpath", "groups": [], }, diff --git a/tests/tabs/test_ungroup_tabs.py b/tests/tabs/test_ungroup_tabs.py new file mode 100644 index 000000000..f431e8e80 --- /dev/null +++ b/tests/tabs/test_ungroup_tabs.py @@ -0,0 +1,85 @@ +import pytest +from selenium.webdriver import Firefox +from selenium.webdriver.common.action_chains import ActionChains + +from modules.browser_object import ContextMenu, TabBar + +NUM_TABS = 2 +GROUP_NAME = ["group1", "group2", "group3"] + + +@pytest.fixture() +def test_case(): + return "2796550" + + +@pytest.fixture() +def add_to_prefs_list(): + """Add to list of prefs to set""" + return [ + ("browser.tabs.groups.enabled", True), + ("browser.tabs.groups.dragOverThresholdPercent", 20), + ] + + +def test_ungroup_tabs(driver: Firefox): + """ + C2796550, verify that grouped tab can be ungrouped. + Since in the step2 a tab group still remains + it is executed after step3 to avoid tests + dependency + """ + + """ + Step 1 + Action: Right click on one of the grouped Tabs and select Remove from Group. + Verification: The Selected Tab is removed from the group. + """ + + tabs = TabBar(driver) + tab_context_menu = ContextMenu(driver) + + # Create a tab group + tabs.create_tab_group(NUM_TABS, GROUP_NAME[0], tab_context_menu) + + # Remove first tab from the tab group + first_tab = tabs.get_tab(1) + tabs.context_click(first_tab) + tab_context_menu.click_and_hide_menu("context-remove-tab-from-group") + + # Verify tab is removed from the tab group + tabs.element_not_visible("tabgroup-overflow-count") + + """ + Step 3 + Action: Right-click the created Tab Group and select Ungroup Tabs. + Verification: The Tab Group name and color is no longer displayed and all + tabs are no longer part of any group. + """ + + # Create a tab group + tabs.create_tab_group(NUM_TABS, GROUP_NAME[2], tab_context_menu) + + # Right-click on the group and select Ungroup Tabs. + tabs.context_click("tabgroup-label") + tabs.click_and_hide_menu("tabgroup-ungroup-tabs") + + # Verify tab group is no longer there + tabs.element_not_visible("tabgroup-label") + + """ + Step 2 + Action: Grab another tab and move it beyond a Tab that is outside the group. + Verification: The selected tab is no longer part of that Tab group. + """ + + # Create a tab group + tabs.set_chrome_context() + tabs.create_tab_group(NUM_TABS, GROUP_NAME[1], tab_context_menu) + + # Click the first tab, hold, move by offset, and then release + first_tab = tabs.get_tab(1) + tabs.actions.click_and_hold(first_tab).move_by_offset(120, 0).release().perform() + + # Verify tab is removed from the tab group + tabs.element_not_visible("tabgroup-overflow-count")