From 00f7f170d976850ae1be200faa402881dcf28c9a Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Mon, 19 Jan 2026 11:22:26 -0500 Subject: [PATCH 01/12] Adding the type annotation for the cascading locators to keywords --- src/SeleniumLibrary/keywords/element.py | 96 +++++++++---------- src/SeleniumLibrary/keywords/formelement.py | 44 ++++----- src/SeleniumLibrary/keywords/frames.py | 8 +- src/SeleniumLibrary/keywords/screenshot.py | 4 +- src/SeleniumLibrary/keywords/selectelement.py | 40 ++++---- src/SeleniumLibrary/keywords/tableelement.py | 16 ++-- src/SeleniumLibrary/keywords/waiting.py | 16 ++-- 7 files changed, 112 insertions(+), 112 deletions(-) diff --git a/src/SeleniumLibrary/keywords/element.py b/src/SeleniumLibrary/keywords/element.py index 831ebfaf2..350f625a4 100644 --- a/src/SeleniumLibrary/keywords/element.py +++ b/src/SeleniumLibrary/keywords/element.py @@ -53,7 +53,7 @@ def get_webelements(self, locator: Union[WebElement, str]) -> List[WebElement]: @keyword def element_should_contain( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], expected: Union[None, str], message: Optional[str] = None, ignore_case: bool = False, @@ -91,7 +91,7 @@ def element_should_contain( @keyword def element_should_not_contain( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], expected: Union[None, str], message: Optional[str] = None, ignore_case: bool = False, @@ -209,7 +209,7 @@ def page_should_not_contain(self, text: str, loglevel: str = "TRACE"): @keyword def page_should_not_contain_element( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -224,7 +224,7 @@ def page_should_not_contain_element( self.assert_page_not_contains(locator, message=message, loglevel=loglevel) @keyword - def assign_id_to_element(self, locator: Union[WebElement, str], id: str): + def assign_id_to_element(self, locator: Union[WebElement, str, List[Union[WebElement,str]]], id: str): """Assigns a temporary ``id`` to the element specified by ``locator``. This is mainly useful if the locator is complicated and/or slow XPath @@ -243,7 +243,7 @@ def assign_id_to_element(self, locator: Union[WebElement, str], id: str): self.driver.execute_script(f"arguments[0].id = '{id}';", element) @keyword - def element_should_be_disabled(self, locator: Union[WebElement, str]): + def element_should_be_disabled(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): """Verifies that element identified by ``locator`` is disabled. This keyword considers also elements that are read-only to be @@ -256,7 +256,7 @@ def element_should_be_disabled(self, locator: Union[WebElement, str]): raise AssertionError(f"Element '{locator}' is enabled.") @keyword - def element_should_be_enabled(self, locator: Union[WebElement, str]): + def element_should_be_enabled(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): """Verifies that element identified by ``locator`` is enabled. This keyword considers also elements that are read-only to be @@ -269,7 +269,7 @@ def element_should_be_enabled(self, locator: Union[WebElement, str]): raise AssertionError(f"Element '{locator}' is disabled.") @keyword - def element_should_be_focused(self, locator: Union[WebElement, str]): + def element_should_be_focused(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): """Verifies that element identified by ``locator`` is focused. See the `Locating elements` section for details about the locator @@ -287,7 +287,7 @@ def element_should_be_focused(self, locator: Union[WebElement, str]): @keyword def element_should_be_visible( - self, locator: Union[WebElement, str], message: Optional[str] = None + self, locator: Union[WebElement, str, List[Union[WebElement,str]]], message: Optional[str] = None ): """Verifies that the element identified by ``locator`` is visible. @@ -310,7 +310,7 @@ def element_should_be_visible( @keyword def element_should_not_be_visible( - self, locator: Union[WebElement, str], message: Optional[str] = None + self, locator: Union[WebElement, str, List[Union[WebElement,str]]], message: Optional[str] = None ): """Verifies that the element identified by ``locator`` is NOT visible. @@ -330,7 +330,7 @@ def element_should_not_be_visible( @keyword def element_text_should_be( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], expected: Union[None, str], message: Optional[str] = None, ignore_case: bool = False, @@ -366,7 +366,7 @@ def element_text_should_be( @keyword def element_text_should_not_be( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], not_expected: Union[None, str], message: Optional[str] = None, ignore_case: bool = False, @@ -399,7 +399,7 @@ def element_text_should_not_be( @keyword def get_element_attribute( - self, locator: Union[WebElement, str], attribute: str + self, locator: Union[WebElement, str, List[Union[WebElement,str]]], attribute: str ) -> str: """Returns the value of ``attribute`` from the element ``locator``. @@ -417,7 +417,7 @@ def get_element_attribute( @keyword def get_dom_attribute( - self, locator: Union[WebElement, str], attribute: str + self, locator: Union[WebElement, str, List[Union[WebElement,str]]], attribute: str ) -> str: """Returns the value of ``attribute`` from the element ``locator``. `Get DOM Attribute` keyword only returns attributes declared within the element's HTML markup. If the requested attribute @@ -434,7 +434,7 @@ def get_dom_attribute( @keyword def get_property( - self, locator: Union[WebElement, str], property: str + self, locator: Union[WebElement, str, List[Union[WebElement,str]]], property: str ) -> str: """Returns the value of ``property`` from the element ``locator``. @@ -450,7 +450,7 @@ def get_property( @keyword def element_attribute_value_should_be( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], attribute: str, expected: Union[None, str], message: Optional[str] = None, @@ -479,7 +479,7 @@ def element_attribute_value_should_be( ) @keyword - def get_horizontal_position(self, locator: Union[WebElement, str]) -> int: + def get_horizontal_position(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]) -> int: """Returns the horizontal position of the element identified by ``locator``. See the `Locating elements` section for details about the locator @@ -493,7 +493,7 @@ def get_horizontal_position(self, locator: Union[WebElement, str]) -> int: return self.find_element(locator).location["x"] @keyword - def get_element_size(self, locator: Union[WebElement, str]) -> Tuple[int, int]: + def get_element_size(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]) -> Tuple[int, int]: """Returns width and height of the element identified by ``locator``. See the `Locating elements` section for details about the locator @@ -508,7 +508,7 @@ def get_element_size(self, locator: Union[WebElement, str]) -> Tuple[int, int]: return element.size["width"], element.size["height"] @keyword - def cover_element(self, locator: Union[WebElement, str]): + def cover_element(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): """Will cover elements identified by ``locator`` with a blue div without breaking page layout. See the `Locating elements` section for details about the locator @@ -540,7 +540,7 @@ def cover_element(self, locator: Union[WebElement, str]): self.driver.execute_script(script, element) @keyword - def get_value(self, locator: Union[WebElement, str]) -> str: + def get_value(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]) -> str: """Returns the value attribute of the element identified by ``locator``. See the `Locating elements` section for details about the locator @@ -549,7 +549,7 @@ def get_value(self, locator: Union[WebElement, str]) -> str: return self.get_element_attribute(locator, "value") @keyword - def get_text(self, locator: Union[WebElement, str]) -> str: + def get_text(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]) -> str: """Returns the text value of the element identified by ``locator``. See the `Locating elements` section for details about the locator @@ -558,7 +558,7 @@ def get_text(self, locator: Union[WebElement, str]) -> str: return self.find_element(locator).text @keyword - def clear_element_text(self, locator: Union[WebElement, str]): + def clear_element_text(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): """Clears the value of the text-input-element identified by ``locator``. See the `Locating elements` section for details about the locator @@ -567,7 +567,7 @@ def clear_element_text(self, locator: Union[WebElement, str]): self.find_element(locator).clear() @keyword - def get_vertical_position(self, locator: Union[WebElement, str]) -> int: + def get_vertical_position(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]) -> int: """Returns the vertical position of the element identified by ``locator``. See the `Locating elements` section for details about the locator @@ -582,7 +582,7 @@ def get_vertical_position(self, locator: Union[WebElement, str]) -> int: @keyword def click_button( - self, locator: Union[WebElement, str], modifier: Union[bool, str] = False + self, locator: Union[WebElement, str], modifier: Union[bool, str, List[Union[WebElement,str]]] = False ): """Clicks the button identified by ``locator``. @@ -606,7 +606,7 @@ def click_button( @keyword def click_image( - self, locator: Union[WebElement, str], modifier: Union[bool, str] = False + self, locator: Union[WebElement, str], modifier: Union[bool, str, List[Union[WebElement,str]]] = False ): """Clicks an image identified by ``locator``. @@ -631,7 +631,7 @@ def click_image( @keyword def click_link( - self, locator: Union[WebElement, str], modifier: Union[bool, str] = False + self, locator: Union[WebElement, str, List[Union[WebElement,str]]], modifier: Union[bool, str] = False ): """Clicks a link identified by ``locator``. @@ -653,7 +653,7 @@ def click_link( @keyword def click_element( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], modifier: Union[bool, str] = False, action_chain: bool = False, ): @@ -694,7 +694,7 @@ def click_element( self.info(f"Clicking element '{locator}'.") self.find_element(locator).click() - def _click_with_action_chain(self, locator: Union[WebElement, str]): + def _click_with_action_chain(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): self.info(f"Clicking '{locator}' using an action chain.") action = ActionChains(self.driver, duration=self.ctx.action_chain_delay) element = self.find_element(locator) @@ -720,7 +720,7 @@ def _click_with_modifier(self, locator, tag, modifier): @keyword def click_element_at_coordinates( - self, locator: Union[WebElement, str], xoffset: int, yoffset: int + self, locator: Union[WebElement, str, List[Union[WebElement,str]]], xoffset: int, yoffset: int ): """Click the element ``locator`` at ``xoffset/yoffset``. @@ -741,7 +741,7 @@ def click_element_at_coordinates( action.perform() @keyword - def double_click_element(self, locator: Union[WebElement, str]): + def double_click_element(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): """Double clicks the element identified by ``locator``. See the `Locating elements` section for details about the locator @@ -753,7 +753,7 @@ def double_click_element(self, locator: Union[WebElement, str]): action.double_click(element).perform() @keyword - def set_focus_to_element(self, locator: Union[WebElement, str]): + def set_focus_to_element(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): """Sets the focus to the element identified by ``locator``. See the `Locating elements` section for details about the locator @@ -765,7 +765,7 @@ def set_focus_to_element(self, locator: Union[WebElement, str]): self.driver.execute_script("arguments[0].focus();", element) @keyword - def scroll_element_into_view(self, locator: Union[WebElement, str]): + def scroll_element_into_view(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): """Scrolls the element identified by ``locator`` into view. See the `Locating elements` section for details about the locator @@ -778,7 +778,7 @@ def scroll_element_into_view(self, locator: Union[WebElement, str]): @keyword def drag_and_drop( - self, locator: Union[WebElement, str], target: Union[WebElement, str] + self, locator: Union[WebElement, str, List[Union[WebElement,str]]], target: Union[WebElement, str, List[Union[WebElement,str]]] ): """Drags the element identified by ``locator`` into the ``target`` element. @@ -796,7 +796,7 @@ def drag_and_drop( @keyword def drag_and_drop_by_offset( - self, locator: Union[WebElement, str], xoffset: int, yoffset: int + self, locator: Union[WebElement, str, List[Union[WebElement,str]]], xoffset: int, yoffset: int ): """Drags the element identified with ``locator`` by ``xoffset/yoffset``. @@ -815,7 +815,7 @@ def drag_and_drop_by_offset( action.perform() @keyword - def mouse_down(self, locator: Union[WebElement, str]): + def mouse_down(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): """Simulates pressing the left mouse button on the element ``locator``. See the `Locating elements` section for details about the locator @@ -832,7 +832,7 @@ def mouse_down(self, locator: Union[WebElement, str]): action.click_and_hold(element).perform() @keyword - def mouse_out(self, locator: Union[WebElement, str]): + def mouse_out(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): """Simulates moving the mouse away from the element ``locator``. See the `Locating elements` section for details about the locator @@ -849,7 +849,7 @@ def mouse_out(self, locator: Union[WebElement, str]): action.perform() @keyword - def mouse_over(self, locator: Union[WebElement, str]): + def mouse_over(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): """Simulates hovering the mouse over the element ``locator``. See the `Locating elements` section for details about the locator @@ -861,7 +861,7 @@ def mouse_over(self, locator: Union[WebElement, str]): action.move_to_element(element).perform() @keyword - def mouse_up(self, locator: Union[WebElement, str]): + def mouse_up(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): """Simulates releasing the left mouse button on the element ``locator``. See the `Locating elements` section for details about the locator @@ -872,14 +872,14 @@ def mouse_up(self, locator: Union[WebElement, str]): ActionChains(self.driver, duration=self.ctx.action_chain_delay).release(element).perform() @keyword - def open_context_menu(self, locator: Union[WebElement, str]): + def open_context_menu(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): """Opens the context menu on the element identified by ``locator``.""" element = self.find_element(locator) action = ActionChains(self.driver, duration=self.ctx.action_chain_delay) action.context_click(element).perform() @keyword - def simulate_event(self, locator: Union[WebElement, str], event: str): + def simulate_event(self, locator: Union[WebElement, str, List[Union[WebElement,str]]], event: str): """Simulates ``event`` on the element identified by ``locator``. This keyword is useful if element has ``OnEvent`` handler that @@ -904,7 +904,7 @@ def simulate_event(self, locator: Union[WebElement, str], event: str): self.driver.execute_script(script, element, event) @keyword - def press_key(self, locator: Union[WebElement, str], key: str): + def press_key(self, locator: Union[WebElement, str, List[Union[WebElement,str]]], key: str): """Simulates user pressing key on element identified by ``locator``. See the `Locating elements` section for details about the locator @@ -930,7 +930,7 @@ def press_key(self, locator: Union[WebElement, str], key: str): element.send_keys(key) @keyword - def press_keys(self, locator: Union[WebElement, None, str] = None, *keys: str): + def press_keys(self, locator: Union[WebElement, None, str, List[Union[WebElement,str]]] = None, *keys: str): """Simulates the user pressing key(s) to an element or on the active browser. If ``locator`` evaluates as false, see `Boolean arguments` for more @@ -1033,7 +1033,7 @@ def get_all_links(self) -> List[str]: return [link.get_attribute("id") for link in links] @keyword - def mouse_down_on_link(self, locator: Union[WebElement, str]): + def mouse_down_on_link(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): """Simulates a mouse down event on a link identified by ``locator``. See the `Locating elements` section for details about the locator @@ -1047,7 +1047,7 @@ def mouse_down_on_link(self, locator: Union[WebElement, str]): @keyword def page_should_contain_link( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -1065,7 +1065,7 @@ def page_should_contain_link( @keyword def page_should_not_contain_link( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -1081,7 +1081,7 @@ def page_should_not_contain_link( self.assert_page_not_contains(locator, "link", message, loglevel) @keyword - def mouse_down_on_image(self, locator: Union[WebElement, str]): + def mouse_down_on_image(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): """Simulates a mouse down event on an image identified by ``locator``. See the `Locating elements` section for details about the locator @@ -1095,7 +1095,7 @@ def mouse_down_on_image(self, locator: Union[WebElement, str]): @keyword def page_should_contain_image( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -1113,7 +1113,7 @@ def page_should_contain_image( @keyword def page_should_not_contain_image( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -1129,7 +1129,7 @@ def page_should_not_contain_image( self.assert_page_not_contains(locator, "image", message, loglevel) @keyword - def get_element_count(self, locator: Union[WebElement, str]) -> int: + def get_element_count(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]) -> int: """Returns the number of elements matching ``locator``. If you wish to assert the number of matching elements, use diff --git a/src/SeleniumLibrary/keywords/formelement.py b/src/SeleniumLibrary/keywords/formelement.py index 15ef61256..a98ded910 100644 --- a/src/SeleniumLibrary/keywords/formelement.py +++ b/src/SeleniumLibrary/keywords/formelement.py @@ -15,7 +15,7 @@ # limitations under the License. import os -from typing import Optional, Union +from typing import Optional, Union, List from robot.libraries.BuiltIn import BuiltIn from selenium.webdriver.remote.webelement import WebElement @@ -26,7 +26,7 @@ class FormElementKeywords(LibraryComponent): @keyword - def submit_form(self, locator: Union[WebElement, None, str] = None): + def submit_form(self, locator: Union[WebElement, None, str, List[Union[WebElement,str]]] = None): """Submits a form identified by ``locator``. If ``locator`` is not given, first form on the page is submitted. @@ -41,7 +41,7 @@ def submit_form(self, locator: Union[WebElement, None, str] = None): element.submit() @keyword - def checkbox_should_be_selected(self, locator: Union[WebElement, str]): + def checkbox_should_be_selected(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): """Verifies checkbox ``locator`` is selected/checked. See the `Locating elements` section for details about the locator @@ -55,7 +55,7 @@ def checkbox_should_be_selected(self, locator: Union[WebElement, str]): ) @keyword - def checkbox_should_not_be_selected(self, locator: Union[WebElement, str]): + def checkbox_should_not_be_selected(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): """Verifies checkbox ``locator`` is not selected/checked. See the `Locating elements` section for details about the locator @@ -69,7 +69,7 @@ def checkbox_should_not_be_selected(self, locator: Union[WebElement, str]): @keyword def page_should_contain_checkbox( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -86,7 +86,7 @@ def page_should_contain_checkbox( @keyword def page_should_not_contain_checkbox( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -101,7 +101,7 @@ def page_should_not_contain_checkbox( self.assert_page_not_contains(locator, "checkbox", message, loglevel) @keyword - def select_checkbox(self, locator: Union[WebElement, str]): + def select_checkbox(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): """Selects the checkbox identified by ``locator``. Does nothing if checkbox is already selected. @@ -115,7 +115,7 @@ def select_checkbox(self, locator: Union[WebElement, str]): element.click() @keyword - def unselect_checkbox(self, locator: Union[WebElement, str]): + def unselect_checkbox(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): """Removes the selection of checkbox identified by ``locator``. Does nothing if the checkbox is not selected. @@ -131,7 +131,7 @@ def unselect_checkbox(self, locator: Union[WebElement, str]): @keyword def page_should_contain_radio_button( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -149,7 +149,7 @@ def page_should_contain_radio_button( @keyword def page_should_not_contain_radio_button( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -213,7 +213,7 @@ def select_radio_button(self, group_name: str, value: str): element.click() @keyword - def choose_file(self, locator: Union[WebElement, str], file_path: str): + def choose_file(self, locator: Union[WebElement, str, List[Union[WebElement,str]]], file_path: str): """Inputs the ``file_path`` into the file input field ``locator``. This keyword is most often used to input files into upload forms. @@ -240,7 +240,7 @@ def choose_file(self, locator: Union[WebElement, str], file_path: str): @keyword def input_password( - self, locator: Union[WebElement, str], password: str, clear: bool = True + self, locator: Union[WebElement, str, List[Union[WebElement,str]]], password: str, clear: bool = True ): """Types the given password into the text field identified by ``locator``. @@ -270,7 +270,7 @@ def input_password( @keyword def input_text( - self, locator: Union[WebElement, str], text: str, clear: bool = True + self, locator: Union[WebElement, str, List[Union[WebElement,str]]], text: str, clear: bool = True ): """Types the given ``text`` into the text field identified by ``locator``. @@ -299,7 +299,7 @@ def input_text( @keyword def page_should_contain_textfield( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -316,7 +316,7 @@ def page_should_contain_textfield( @keyword def page_should_not_contain_textfield( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -333,7 +333,7 @@ def page_should_not_contain_textfield( @keyword def textfield_should_contain( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], expected: str, message: Optional[str] = None, ): @@ -357,7 +357,7 @@ def textfield_should_contain( @keyword def textfield_value_should_be( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], expected: str, message: Optional[str] = None, ): @@ -381,7 +381,7 @@ def textfield_value_should_be( @keyword def textarea_should_contain( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], expected: str, message: Optional[str] = None, ): @@ -405,7 +405,7 @@ def textarea_should_contain( @keyword def textarea_value_should_be( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], expected: str, message: Optional[str] = None, ): @@ -429,7 +429,7 @@ def textarea_value_should_be( @keyword def page_should_contain_button( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -450,7 +450,7 @@ def page_should_contain_button( @keyword def page_should_not_contain_button( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -469,7 +469,7 @@ def page_should_not_contain_button( def _get_value(self, locator, tag): return self.find_element(locator, tag).get_attribute("value") - def _get_checkbox(self, locator: Union[WebElement, str]): + def _get_checkbox(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): return self.find_element(locator, tag="checkbox") def _get_radio_buttons(self, group_name): diff --git a/src/SeleniumLibrary/keywords/frames.py b/src/SeleniumLibrary/keywords/frames.py index 296b1ee6b..1967286ec 100644 --- a/src/SeleniumLibrary/keywords/frames.py +++ b/src/SeleniumLibrary/keywords/frames.py @@ -13,7 +13,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from typing import Union +from typing import Union, List from selenium.webdriver.remote.webelement import WebElement @@ -22,7 +22,7 @@ class FrameKeywords(LibraryComponent): @keyword - def select_frame(self, locator: Union[WebElement, str]): + def select_frame(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): """Sets frame identified by ``locator`` as the current frame. See the `Locating elements` section for details about the locator @@ -82,7 +82,7 @@ def current_frame_should_not_contain(self, text: str, loglevel: str = "TRACE"): @keyword def frame_should_contain( - self, locator: Union[WebElement, str], text: str, loglevel: str = "TRACE" + self, locator: Union[WebElement, str, List[Union[WebElement,str]]], text: str, loglevel: str = "TRACE" ): """Verifies that frame identified by ``locator`` contains ``text``. @@ -99,7 +99,7 @@ def frame_should_contain( ) self.info(f"Frame '{locator}' contains text '{text}'.") - def _frame_contains(self, locator: Union[WebElement, str], text: str): + def _frame_contains(self, locator: Union[WebElement, str, List[Union[WebElement,str]]], text: str): element = self.find_element(locator) self.driver.switch_to.frame(element) self.info(f"Searching for text from frame '{locator}'.") diff --git a/src/SeleniumLibrary/keywords/screenshot.py b/src/SeleniumLibrary/keywords/screenshot.py index 11308af85..2adc2df98 100644 --- a/src/SeleniumLibrary/keywords/screenshot.py +++ b/src/SeleniumLibrary/keywords/screenshot.py @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. import os -from typing import Optional, Union +from typing import Optional, Union, List from base64 import b64decode from robot.utils import get_link_path @@ -146,7 +146,7 @@ def _capture_page_screen_to_log(self, return_val): @keyword def capture_element_screenshot( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], filename: str = DEFAULT_FILENAME_ELEMENT, ) -> str: """Captures a screenshot from the element identified by ``locator`` and embeds it into log file. diff --git a/src/SeleniumLibrary/keywords/selectelement.py b/src/SeleniumLibrary/keywords/selectelement.py index 910fb081e..a4c8ce32b 100644 --- a/src/SeleniumLibrary/keywords/selectelement.py +++ b/src/SeleniumLibrary/keywords/selectelement.py @@ -25,7 +25,7 @@ class SelectElementKeywords(LibraryComponent): @keyword def get_list_items( - self, locator: Union[WebElement, str], values: bool = False + self, locator: Union[WebElement, str, List[Union[WebElement,str]]], values: bool = False ) -> List[str]: """Returns all labels or values of selection list ``locator``. @@ -49,7 +49,7 @@ def get_list_items( return self._get_labels(options) @keyword - def get_selected_list_label(self, locator: Union[WebElement, str]) -> str: + def get_selected_list_label(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]) -> str: """Returns the label of selected option from selection list ``locator``. If there are multiple selected options, the label of the first option @@ -62,7 +62,7 @@ def get_selected_list_label(self, locator: Union[WebElement, str]) -> str: return select.first_selected_option.text @keyword - def get_selected_list_labels(self, locator: Union[WebElement, str]) -> List[str]: + def get_selected_list_labels(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]) -> List[str]: """Returns labels of selected options from selection list ``locator``. Starting from SeleniumLibrary 3.0, returns an empty list if there @@ -75,7 +75,7 @@ def get_selected_list_labels(self, locator: Union[WebElement, str]) -> List[str] return self._get_labels(options) @keyword - def get_selected_list_value(self, locator: Union[WebElement, str]) -> str: + def get_selected_list_value(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]) -> str: """Returns the value of selected option from selection list ``locator``. If there are multiple selected options, the value of the first option @@ -88,7 +88,7 @@ def get_selected_list_value(self, locator: Union[WebElement, str]) -> str: return select.first_selected_option.get_attribute("value") @keyword - def get_selected_list_values(self, locator: Union[WebElement, str]) -> List[str]: + def get_selected_list_values(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]) -> List[str]: """Returns values of selected options from selection list ``locator``. Starting from SeleniumLibrary 3.0, returns an empty list if there @@ -101,7 +101,7 @@ def get_selected_list_values(self, locator: Union[WebElement, str]) -> List[str] return self._get_values(options) @keyword - def list_selection_should_be(self, locator: Union[WebElement, str], *expected: str): + def list_selection_should_be(self, locator: Union[WebElement, str, List[Union[WebElement,str]]], *expected: str): """Verifies selection list ``locator`` has ``expected`` options selected. It is possible to give expected options both as visible labels and @@ -138,7 +138,7 @@ def _format_selection(self, labels, values): return " | ".join(f"{label} ({value})" for label, value in zip(labels, values)) @keyword - def list_should_have_no_selections(self, locator: Union[WebElement, str]): + def list_should_have_no_selections(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): """Verifies selection list ``locator`` has no options selected. See the `Locating elements` section for details about the locator @@ -158,7 +158,7 @@ def list_should_have_no_selections(self, locator: Union[WebElement, str]): @keyword def page_should_contain_list( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -175,7 +175,7 @@ def page_should_contain_list( @keyword def page_should_not_contain_list( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -190,7 +190,7 @@ def page_should_not_contain_list( self.assert_page_not_contains(locator, "list", message, loglevel) @keyword - def select_all_from_list(self, locator: Union[WebElement, str]): + def select_all_from_list(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): """Selects all options from multi-selection list ``locator``. See the `Locating elements` section for details about the locator @@ -206,7 +206,7 @@ def select_all_from_list(self, locator: Union[WebElement, str]): select.select_by_index(index) @keyword - def select_from_list_by_index(self, locator: Union[WebElement, str], *indexes: str): + def select_from_list_by_index(self, locator: Union[WebElement, str, List[Union[WebElement,str]]], *indexes: str): """Selects options from selection list ``locator`` by ``indexes``. Indexes of list options start from 0. @@ -231,7 +231,7 @@ def select_from_list_by_index(self, locator: Union[WebElement, str], *indexes: s select.select_by_index(int(index)) @keyword - def select_from_list_by_value(self, locator: Union[WebElement, str], *values: str): + def select_from_list_by_value(self, locator: Union[WebElement, str, List[Union[WebElement,str]]], *values: str): """Selects options from selection list ``locator`` by ``values``. If more than one option is given for a single-selection list, @@ -253,7 +253,7 @@ def select_from_list_by_value(self, locator: Union[WebElement, str], *values: st select.select_by_value(value) @keyword - def select_from_list_by_label(self, locator: Union[WebElement, str], *labels: str): + def select_from_list_by_label(self, locator: Union[WebElement, str, List[Union[WebElement,str]]], *labels: str): """Selects options from selection list ``locator`` by ``labels``. If more than one option is given for a single-selection list, @@ -275,7 +275,7 @@ def select_from_list_by_label(self, locator: Union[WebElement, str], *labels: st select.select_by_visible_text(label) @keyword - def unselect_all_from_list(self, locator: Union[WebElement, str]): + def unselect_all_from_list(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): """Unselects all options from multi-selection list ``locator``. See the `Locating elements` section for details about the locator @@ -293,7 +293,7 @@ def unselect_all_from_list(self, locator: Union[WebElement, str]): @keyword def unselect_from_list_by_index( - self, locator: Union[WebElement, str], *indexes: str + self, locator: Union[WebElement, str, List[Union[WebElement,str]]], *indexes: str ): """Unselects options from selection list ``locator`` by ``indexes``. @@ -320,7 +320,7 @@ def unselect_from_list_by_index( @keyword def unselect_from_list_by_value( - self, locator: Union[WebElement, str], *values: str + self, locator: Union[WebElement, str, List[Union[WebElement,str]]], *values: str ): """Unselects options from selection list ``locator`` by ``values``. @@ -345,7 +345,7 @@ def unselect_from_list_by_value( @keyword def unselect_from_list_by_label( - self, locator: Union[WebElement, str], *labels: str + self, locator: Union[WebElement, str, List[Union[WebElement,str]]], *labels: str ): """Unselects options from selection list ``locator`` by ``labels``. @@ -368,14 +368,14 @@ def unselect_from_list_by_label( for label in labels: select.deselect_by_visible_text(label) - def _get_select_list(self, locator: Union[WebElement, str]): + def _get_select_list(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): el = self.find_element(locator, tag="list") return Select(el) - def _get_options(self, locator: Union[WebElement, str]): + def _get_options(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): return self._get_select_list(locator).options - def _get_selected_options(self, locator: Union[WebElement, str]): + def _get_selected_options(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): return self._get_select_list(locator).all_selected_options def _get_labels(self, options): diff --git a/src/SeleniumLibrary/keywords/tableelement.py b/src/SeleniumLibrary/keywords/tableelement.py index e054e9d77..47519ff42 100644 --- a/src/SeleniumLibrary/keywords/tableelement.py +++ b/src/SeleniumLibrary/keywords/tableelement.py @@ -13,7 +13,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from typing import Union +from typing import Union, List from selenium.webdriver.common.by import By from selenium.webdriver.remote.webelement import WebElement @@ -25,7 +25,7 @@ class TableElementKeywords(LibraryComponent): @keyword def get_table_cell( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], row: int, column: int, loglevel: str = "TRACE", @@ -89,7 +89,7 @@ def _get_rows(self, locator, count): @keyword def table_cell_should_contain( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], row: int, column: int, expected: str, @@ -112,7 +112,7 @@ def table_cell_should_contain( @keyword def table_column_should_contain( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], column: int, expected: str, loglevel: str = "TRACE", @@ -143,7 +143,7 @@ def table_column_should_contain( @keyword def table_footer_should_contain( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], expected: str, loglevel: str = "TRACE", ): @@ -168,7 +168,7 @@ def table_footer_should_contain( @keyword def table_header_should_contain( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], expected: str, loglevel: str = "TRACE", ): @@ -193,7 +193,7 @@ def table_header_should_contain( @keyword def table_row_should_contain( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], row: int, expected: str, loglevel: str = "TRACE", @@ -224,7 +224,7 @@ def table_row_should_contain( @keyword def table_should_contain( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], expected: str, loglevel: str = "TRACE", ): diff --git a/src/SeleniumLibrary/keywords/waiting.py b/src/SeleniumLibrary/keywords/waiting.py index eeec6756e..7dd38e0e6 100644 --- a/src/SeleniumLibrary/keywords/waiting.py +++ b/src/SeleniumLibrary/keywords/waiting.py @@ -16,7 +16,7 @@ import time from datetime import timedelta -from typing import Optional, Union +from typing import Optional, Union, List from selenium.common.exceptions import StaleElementReferenceException from selenium.webdriver.remote.webelement import WebElement @@ -222,7 +222,7 @@ def wait_until_page_does_not_contain( @keyword def wait_until_page_contains_element( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], timeout: Optional[timedelta] = None, error: Optional[str] = None, limit: Optional[int] = None, @@ -260,7 +260,7 @@ def wait_until_page_contains_element( @keyword def wait_until_page_does_not_contain_element( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], timeout: Optional[timedelta] = None, error: Optional[str] = None, limit: Optional[int] = None, @@ -298,7 +298,7 @@ def wait_until_page_does_not_contain_element( @keyword def wait_until_element_is_visible( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], timeout: Optional[timedelta] = None, error: Optional[str] = None, ): @@ -321,7 +321,7 @@ def wait_until_element_is_visible( @keyword def wait_until_element_is_not_visible( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], timeout: Optional[timedelta] = None, error: Optional[str] = None, ): @@ -344,7 +344,7 @@ def wait_until_element_is_not_visible( @keyword def wait_until_element_is_enabled( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], timeout: Optional[timedelta] = None, error: Optional[str] = None, ): @@ -372,7 +372,7 @@ def wait_until_element_is_enabled( @keyword def wait_until_element_contains( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], text: str, timeout: Optional[timedelta] = None, error: Optional[str] = None, @@ -396,7 +396,7 @@ def wait_until_element_contains( @keyword def wait_until_element_does_not_contain( self, - locator: Union[WebElement, str], + locator: Union[WebElement, str, List[Union[WebElement,str]]], text: str, timeout: Optional[timedelta] = None, error: Optional[str] = None, From 22b9dd2a33e4908293b9c9fdc791eed84c8cc7a9 Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Mon, 19 Jan 2026 12:12:30 -0500 Subject: [PATCH 02/12] Additional changes for cascading locators - Fixed a couple incorrectly modified type hints on click button and click image - Added type hints to get webelement(s) keywords --- src/SeleniumLibrary/keywords/element.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/SeleniumLibrary/keywords/element.py b/src/SeleniumLibrary/keywords/element.py index 350f625a4..d103e3c7c 100644 --- a/src/SeleniumLibrary/keywords/element.py +++ b/src/SeleniumLibrary/keywords/element.py @@ -29,7 +29,7 @@ class ElementKeywords(LibraryComponent): @keyword(name="Get WebElement") - def get_webelement(self, locator: Union[WebElement, str]) -> WebElement: + def get_webelement(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]) -> WebElement: """Returns the first WebElement matching the given ``locator``. See the `Locating elements` section for details about the locator @@ -38,7 +38,7 @@ def get_webelement(self, locator: Union[WebElement, str]) -> WebElement: return self.find_element(locator) @keyword(name="Get WebElements") - def get_webelements(self, locator: Union[WebElement, str]) -> List[WebElement]: + def get_webelements(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]) -> List[WebElement]: """Returns a list of WebElement objects matching the ``locator``. See the `Locating elements` section for details about the locator @@ -582,7 +582,7 @@ def get_vertical_position(self, locator: Union[WebElement, str, List[Union[WebEl @keyword def click_button( - self, locator: Union[WebElement, str], modifier: Union[bool, str, List[Union[WebElement,str]]] = False + self, locator: Union[WebElement, str, List[Union[WebElement,str]]], modifier: Union[bool, str] = False ): """Clicks the button identified by ``locator``. @@ -606,7 +606,7 @@ def click_button( @keyword def click_image( - self, locator: Union[WebElement, str], modifier: Union[bool, str, List[Union[WebElement,str]]] = False + self, locator: Union[WebElement, str, List[Union[WebElement,str]]], modifier: Union[bool, str] = False ): """Clicks an image identified by ``locator``. From 9f5b9c78fd1d4de6da05131d63a6e11e72ff8e6e Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Mon, 19 Jan 2026 13:39:18 -0500 Subject: [PATCH 03/12] Minor spelling correction --- src/SeleniumLibrary/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SeleniumLibrary/__init__.py b/src/SeleniumLibrary/__init__.py index 6945eae7f..5bdd14b12 100644 --- a/src/SeleniumLibrary/__init__.py +++ b/src/SeleniumLibrary/__init__.py @@ -202,7 +202,7 @@ class SeleniumLibrary(DynamicCore): used to specify multiple locators. This is useful, is some part of locator would match as the locator separator but it should not. Or if there is need to existing WebElement as locator. - Although all locators support chaining, some locator strategies do not abey the chaining. This is because + Although all locators support chaining, some locator strategies do not obey the chaining. This is because some locator strategies use JavaScript to find elements and JavaScript is executed for the whole browser context and not for the element found be the previous locator. Chaining is supported by locator strategies which are based on Selenium API, like `xpath` or `css`, but example chaining is not supported by `sizzle` or `jquery From 700eaa69bee111644874c4b8c31bbede396ec447 Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Sun, 5 Apr 2026 16:40:39 -0500 Subject: [PATCH 04/12] Switched locator type over to TypeAlias --- src/SeleniumLibrary/keywords/element.py | 102 +++++++++--------- src/SeleniumLibrary/keywords/formelement.py | 40 +++---- src/SeleniumLibrary/keywords/frames.py | 7 +- src/SeleniumLibrary/keywords/screenshot.py | 3 +- src/SeleniumLibrary/keywords/selectelement.py | 41 +++---- src/SeleniumLibrary/keywords/tableelement.py | 15 +-- src/SeleniumLibrary/keywords/waiting.py | 15 +-- src/SeleniumLibrary/utils/types.py | 5 +- 8 files changed, 118 insertions(+), 110 deletions(-) diff --git a/src/SeleniumLibrary/keywords/element.py b/src/SeleniumLibrary/keywords/element.py index d103e3c7c..e4e536f59 100644 --- a/src/SeleniumLibrary/keywords/element.py +++ b/src/SeleniumLibrary/keywords/element.py @@ -24,12 +24,12 @@ from SeleniumLibrary.base import LibraryComponent, keyword from SeleniumLibrary.errors import ElementNotFound -from SeleniumLibrary.utils.types import type_converter +from SeleniumLibrary.utils.types import type_converter, Locator class ElementKeywords(LibraryComponent): @keyword(name="Get WebElement") - def get_webelement(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]) -> WebElement: + def get_webelement(self, locator: Locator) -> WebElement: """Returns the first WebElement matching the given ``locator``. See the `Locating elements` section for details about the locator @@ -38,7 +38,7 @@ def get_webelement(self, locator: Union[WebElement, str, List[Union[WebElement,s return self.find_element(locator) @keyword(name="Get WebElements") - def get_webelements(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]) -> List[WebElement]: + def get_webelements(self, locator: Locator) -> List[WebElement]: """Returns a list of WebElement objects matching the ``locator``. See the `Locating elements` section for details about the locator @@ -53,7 +53,7 @@ def get_webelements(self, locator: Union[WebElement, str, List[Union[WebElement, @keyword def element_should_contain( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, expected: Union[None, str], message: Optional[str] = None, ignore_case: bool = False, @@ -91,7 +91,7 @@ def element_should_contain( @keyword def element_should_not_contain( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, expected: Union[None, str], message: Optional[str] = None, ignore_case: bool = False, @@ -149,7 +149,7 @@ def page_should_contain(self, text: str, loglevel: str = "TRACE"): @keyword def page_should_contain_element( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, message: Optional[str] = None, loglevel: str = "TRACE", limit: Optional[int] = None, @@ -209,7 +209,7 @@ def page_should_not_contain(self, text: str, loglevel: str = "TRACE"): @keyword def page_should_not_contain_element( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -224,7 +224,7 @@ def page_should_not_contain_element( self.assert_page_not_contains(locator, message=message, loglevel=loglevel) @keyword - def assign_id_to_element(self, locator: Union[WebElement, str, List[Union[WebElement,str]]], id: str): + def assign_id_to_element(self, locator: Locator, id: str): """Assigns a temporary ``id`` to the element specified by ``locator``. This is mainly useful if the locator is complicated and/or slow XPath @@ -243,7 +243,7 @@ def assign_id_to_element(self, locator: Union[WebElement, str, List[Union[WebEle self.driver.execute_script(f"arguments[0].id = '{id}';", element) @keyword - def element_should_be_disabled(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): + def element_should_be_disabled(self, locator: Locator): """Verifies that element identified by ``locator`` is disabled. This keyword considers also elements that are read-only to be @@ -256,7 +256,7 @@ def element_should_be_disabled(self, locator: Union[WebElement, str, List[Union[ raise AssertionError(f"Element '{locator}' is enabled.") @keyword - def element_should_be_enabled(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): + def element_should_be_enabled(self, locator: Locator): """Verifies that element identified by ``locator`` is enabled. This keyword considers also elements that are read-only to be @@ -269,7 +269,7 @@ def element_should_be_enabled(self, locator: Union[WebElement, str, List[Union[W raise AssertionError(f"Element '{locator}' is disabled.") @keyword - def element_should_be_focused(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): + def element_should_be_focused(self, locator: Locator): """Verifies that element identified by ``locator`` is focused. See the `Locating elements` section for details about the locator @@ -287,7 +287,7 @@ def element_should_be_focused(self, locator: Union[WebElement, str, List[Union[W @keyword def element_should_be_visible( - self, locator: Union[WebElement, str, List[Union[WebElement,str]]], message: Optional[str] = None + self, locator: Locator, message: Optional[str] = None ): """Verifies that the element identified by ``locator`` is visible. @@ -310,7 +310,7 @@ def element_should_be_visible( @keyword def element_should_not_be_visible( - self, locator: Union[WebElement, str, List[Union[WebElement,str]]], message: Optional[str] = None + self, locator: Locator, message: Optional[str] = None ): """Verifies that the element identified by ``locator`` is NOT visible. @@ -330,7 +330,7 @@ def element_should_not_be_visible( @keyword def element_text_should_be( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, expected: Union[None, str], message: Optional[str] = None, ignore_case: bool = False, @@ -366,7 +366,7 @@ def element_text_should_be( @keyword def element_text_should_not_be( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, not_expected: Union[None, str], message: Optional[str] = None, ignore_case: bool = False, @@ -399,7 +399,7 @@ def element_text_should_not_be( @keyword def get_element_attribute( - self, locator: Union[WebElement, str, List[Union[WebElement,str]]], attribute: str + self, locator: Locator, attribute: str ) -> str: """Returns the value of ``attribute`` from the element ``locator``. @@ -417,7 +417,7 @@ def get_element_attribute( @keyword def get_dom_attribute( - self, locator: Union[WebElement, str, List[Union[WebElement,str]]], attribute: str + self, locator: Locator, attribute: str ) -> str: """Returns the value of ``attribute`` from the element ``locator``. `Get DOM Attribute` keyword only returns attributes declared within the element's HTML markup. If the requested attribute @@ -434,7 +434,7 @@ def get_dom_attribute( @keyword def get_property( - self, locator: Union[WebElement, str, List[Union[WebElement,str]]], property: str + self, locator: Locator, property: str ) -> str: """Returns the value of ``property`` from the element ``locator``. @@ -450,7 +450,7 @@ def get_property( @keyword def element_attribute_value_should_be( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, attribute: str, expected: Union[None, str], message: Optional[str] = None, @@ -479,7 +479,7 @@ def element_attribute_value_should_be( ) @keyword - def get_horizontal_position(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]) -> int: + def get_horizontal_position(self, locator: Locator) -> int: """Returns the horizontal position of the element identified by ``locator``. See the `Locating elements` section for details about the locator @@ -493,7 +493,7 @@ def get_horizontal_position(self, locator: Union[WebElement, str, List[Union[Web return self.find_element(locator).location["x"] @keyword - def get_element_size(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]) -> Tuple[int, int]: + def get_element_size(self, locator: Locator) -> Tuple[int, int]: """Returns width and height of the element identified by ``locator``. See the `Locating elements` section for details about the locator @@ -508,7 +508,7 @@ def get_element_size(self, locator: Union[WebElement, str, List[Union[WebElement return element.size["width"], element.size["height"] @keyword - def cover_element(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): + def cover_element(self, locator: Locator): """Will cover elements identified by ``locator`` with a blue div without breaking page layout. See the `Locating elements` section for details about the locator @@ -540,7 +540,7 @@ def cover_element(self, locator: Union[WebElement, str, List[Union[WebElement,st self.driver.execute_script(script, element) @keyword - def get_value(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]) -> str: + def get_value(self, locator: Locator) -> str: """Returns the value attribute of the element identified by ``locator``. See the `Locating elements` section for details about the locator @@ -549,7 +549,7 @@ def get_value(self, locator: Union[WebElement, str, List[Union[WebElement,str]]] return self.get_element_attribute(locator, "value") @keyword - def get_text(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]) -> str: + def get_text(self, locator: Locator) -> str: """Returns the text value of the element identified by ``locator``. See the `Locating elements` section for details about the locator @@ -558,7 +558,7 @@ def get_text(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]) return self.find_element(locator).text @keyword - def clear_element_text(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): + def clear_element_text(self, locator: Locator): """Clears the value of the text-input-element identified by ``locator``. See the `Locating elements` section for details about the locator @@ -567,7 +567,7 @@ def clear_element_text(self, locator: Union[WebElement, str, List[Union[WebEleme self.find_element(locator).clear() @keyword - def get_vertical_position(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]) -> int: + def get_vertical_position(self, locator: Locator) -> int: """Returns the vertical position of the element identified by ``locator``. See the `Locating elements` section for details about the locator @@ -582,7 +582,7 @@ def get_vertical_position(self, locator: Union[WebElement, str, List[Union[WebEl @keyword def click_button( - self, locator: Union[WebElement, str, List[Union[WebElement,str]]], modifier: Union[bool, str] = False + self, locator: Locator, modifier: Union[bool, str] = False ): """Clicks the button identified by ``locator``. @@ -606,7 +606,7 @@ def click_button( @keyword def click_image( - self, locator: Union[WebElement, str, List[Union[WebElement,str]]], modifier: Union[bool, str] = False + self, locator: Locator, modifier: Union[bool, str] = False ): """Clicks an image identified by ``locator``. @@ -631,7 +631,7 @@ def click_image( @keyword def click_link( - self, locator: Union[WebElement, str, List[Union[WebElement,str]]], modifier: Union[bool, str] = False + self, locator: Locator, modifier: Union[bool, str] = False ): """Clicks a link identified by ``locator``. @@ -653,7 +653,7 @@ def click_link( @keyword def click_element( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, modifier: Union[bool, str] = False, action_chain: bool = False, ): @@ -694,7 +694,7 @@ def click_element( self.info(f"Clicking element '{locator}'.") self.find_element(locator).click() - def _click_with_action_chain(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): + def _click_with_action_chain(self, locator: Locator): self.info(f"Clicking '{locator}' using an action chain.") action = ActionChains(self.driver, duration=self.ctx.action_chain_delay) element = self.find_element(locator) @@ -720,7 +720,7 @@ def _click_with_modifier(self, locator, tag, modifier): @keyword def click_element_at_coordinates( - self, locator: Union[WebElement, str, List[Union[WebElement,str]]], xoffset: int, yoffset: int + self, locator: Locator, xoffset: int, yoffset: int ): """Click the element ``locator`` at ``xoffset/yoffset``. @@ -741,7 +741,7 @@ def click_element_at_coordinates( action.perform() @keyword - def double_click_element(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): + def double_click_element(self, locator: Locator): """Double clicks the element identified by ``locator``. See the `Locating elements` section for details about the locator @@ -753,7 +753,7 @@ def double_click_element(self, locator: Union[WebElement, str, List[Union[WebEle action.double_click(element).perform() @keyword - def set_focus_to_element(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): + def set_focus_to_element(self, locator: Locator): """Sets the focus to the element identified by ``locator``. See the `Locating elements` section for details about the locator @@ -765,7 +765,7 @@ def set_focus_to_element(self, locator: Union[WebElement, str, List[Union[WebEle self.driver.execute_script("arguments[0].focus();", element) @keyword - def scroll_element_into_view(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): + def scroll_element_into_view(self, locator: Locator): """Scrolls the element identified by ``locator`` into view. See the `Locating elements` section for details about the locator @@ -778,7 +778,7 @@ def scroll_element_into_view(self, locator: Union[WebElement, str, List[Union[We @keyword def drag_and_drop( - self, locator: Union[WebElement, str, List[Union[WebElement,str]]], target: Union[WebElement, str, List[Union[WebElement,str]]] + self, locator: Locator, target: Locator ): """Drags the element identified by ``locator`` into the ``target`` element. @@ -796,7 +796,7 @@ def drag_and_drop( @keyword def drag_and_drop_by_offset( - self, locator: Union[WebElement, str, List[Union[WebElement,str]]], xoffset: int, yoffset: int + self, locator: Locator, xoffset: int, yoffset: int ): """Drags the element identified with ``locator`` by ``xoffset/yoffset``. @@ -815,7 +815,7 @@ def drag_and_drop_by_offset( action.perform() @keyword - def mouse_down(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): + def mouse_down(self, locator: Locator): """Simulates pressing the left mouse button on the element ``locator``. See the `Locating elements` section for details about the locator @@ -832,7 +832,7 @@ def mouse_down(self, locator: Union[WebElement, str, List[Union[WebElement,str]] action.click_and_hold(element).perform() @keyword - def mouse_out(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): + def mouse_out(self, locator: Locator): """Simulates moving the mouse away from the element ``locator``. See the `Locating elements` section for details about the locator @@ -849,7 +849,7 @@ def mouse_out(self, locator: Union[WebElement, str, List[Union[WebElement,str]]] action.perform() @keyword - def mouse_over(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): + def mouse_over(self, locator: Locator): """Simulates hovering the mouse over the element ``locator``. See the `Locating elements` section for details about the locator @@ -861,7 +861,7 @@ def mouse_over(self, locator: Union[WebElement, str, List[Union[WebElement,str]] action.move_to_element(element).perform() @keyword - def mouse_up(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): + def mouse_up(self, locator: Locator): """Simulates releasing the left mouse button on the element ``locator``. See the `Locating elements` section for details about the locator @@ -872,14 +872,14 @@ def mouse_up(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]) ActionChains(self.driver, duration=self.ctx.action_chain_delay).release(element).perform() @keyword - def open_context_menu(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): + def open_context_menu(self, locator: Locator): """Opens the context menu on the element identified by ``locator``.""" element = self.find_element(locator) action = ActionChains(self.driver, duration=self.ctx.action_chain_delay) action.context_click(element).perform() @keyword - def simulate_event(self, locator: Union[WebElement, str, List[Union[WebElement,str]]], event: str): + def simulate_event(self, locator: Locator, event: str): """Simulates ``event`` on the element identified by ``locator``. This keyword is useful if element has ``OnEvent`` handler that @@ -904,7 +904,7 @@ def simulate_event(self, locator: Union[WebElement, str, List[Union[WebElement,s self.driver.execute_script(script, element, event) @keyword - def press_key(self, locator: Union[WebElement, str, List[Union[WebElement,str]]], key: str): + def press_key(self, locator: Locator, key: str): """Simulates user pressing key on element identified by ``locator``. See the `Locating elements` section for details about the locator @@ -1033,7 +1033,7 @@ def get_all_links(self) -> List[str]: return [link.get_attribute("id") for link in links] @keyword - def mouse_down_on_link(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): + def mouse_down_on_link(self, locator: Locator): """Simulates a mouse down event on a link identified by ``locator``. See the `Locating elements` section for details about the locator @@ -1047,7 +1047,7 @@ def mouse_down_on_link(self, locator: Union[WebElement, str, List[Union[WebEleme @keyword def page_should_contain_link( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -1065,7 +1065,7 @@ def page_should_contain_link( @keyword def page_should_not_contain_link( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -1081,7 +1081,7 @@ def page_should_not_contain_link( self.assert_page_not_contains(locator, "link", message, loglevel) @keyword - def mouse_down_on_image(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): + def mouse_down_on_image(self, locator: Locator): """Simulates a mouse down event on an image identified by ``locator``. See the `Locating elements` section for details about the locator @@ -1095,7 +1095,7 @@ def mouse_down_on_image(self, locator: Union[WebElement, str, List[Union[WebElem @keyword def page_should_contain_image( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -1113,7 +1113,7 @@ def page_should_contain_image( @keyword def page_should_not_contain_image( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -1129,7 +1129,7 @@ def page_should_not_contain_image( self.assert_page_not_contains(locator, "image", message, loglevel) @keyword - def get_element_count(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]) -> int: + def get_element_count(self, locator: Locator) -> int: """Returns the number of elements matching ``locator``. If you wish to assert the number of matching elements, use diff --git a/src/SeleniumLibrary/keywords/formelement.py b/src/SeleniumLibrary/keywords/formelement.py index a98ded910..2690cd5af 100644 --- a/src/SeleniumLibrary/keywords/formelement.py +++ b/src/SeleniumLibrary/keywords/formelement.py @@ -41,7 +41,7 @@ def submit_form(self, locator: Union[WebElement, None, str, List[Union[WebElemen element.submit() @keyword - def checkbox_should_be_selected(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): + def checkbox_should_be_selected(self, locator: Locator): """Verifies checkbox ``locator`` is selected/checked. See the `Locating elements` section for details about the locator @@ -55,7 +55,7 @@ def checkbox_should_be_selected(self, locator: Union[WebElement, str, List[Union ) @keyword - def checkbox_should_not_be_selected(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): + def checkbox_should_not_be_selected(self, locator: Locator): """Verifies checkbox ``locator`` is not selected/checked. See the `Locating elements` section for details about the locator @@ -69,7 +69,7 @@ def checkbox_should_not_be_selected(self, locator: Union[WebElement, str, List[U @keyword def page_should_contain_checkbox( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -86,7 +86,7 @@ def page_should_contain_checkbox( @keyword def page_should_not_contain_checkbox( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -101,7 +101,7 @@ def page_should_not_contain_checkbox( self.assert_page_not_contains(locator, "checkbox", message, loglevel) @keyword - def select_checkbox(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): + def select_checkbox(self, locator: Locator): """Selects the checkbox identified by ``locator``. Does nothing if checkbox is already selected. @@ -115,7 +115,7 @@ def select_checkbox(self, locator: Union[WebElement, str, List[Union[WebElement, element.click() @keyword - def unselect_checkbox(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): + def unselect_checkbox(self, locator: Locator): """Removes the selection of checkbox identified by ``locator``. Does nothing if the checkbox is not selected. @@ -131,7 +131,7 @@ def unselect_checkbox(self, locator: Union[WebElement, str, List[Union[WebElemen @keyword def page_should_contain_radio_button( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -149,7 +149,7 @@ def page_should_contain_radio_button( @keyword def page_should_not_contain_radio_button( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -213,7 +213,7 @@ def select_radio_button(self, group_name: str, value: str): element.click() @keyword - def choose_file(self, locator: Union[WebElement, str, List[Union[WebElement,str]]], file_path: str): + def choose_file(self, locator: Locator, file_path: str): """Inputs the ``file_path`` into the file input field ``locator``. This keyword is most often used to input files into upload forms. @@ -240,7 +240,7 @@ def choose_file(self, locator: Union[WebElement, str, List[Union[WebElement,str] @keyword def input_password( - self, locator: Union[WebElement, str, List[Union[WebElement,str]]], password: str, clear: bool = True + self, locator: Locator, password: str, clear: bool = True ): """Types the given password into the text field identified by ``locator``. @@ -270,7 +270,7 @@ def input_password( @keyword def input_text( - self, locator: Union[WebElement, str, List[Union[WebElement,str]]], text: str, clear: bool = True + self, locator: Locator, text: str, clear: bool = True ): """Types the given ``text`` into the text field identified by ``locator``. @@ -299,7 +299,7 @@ def input_text( @keyword def page_should_contain_textfield( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -316,7 +316,7 @@ def page_should_contain_textfield( @keyword def page_should_not_contain_textfield( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -333,7 +333,7 @@ def page_should_not_contain_textfield( @keyword def textfield_should_contain( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, expected: str, message: Optional[str] = None, ): @@ -357,7 +357,7 @@ def textfield_should_contain( @keyword def textfield_value_should_be( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, expected: str, message: Optional[str] = None, ): @@ -381,7 +381,7 @@ def textfield_value_should_be( @keyword def textarea_should_contain( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, expected: str, message: Optional[str] = None, ): @@ -405,7 +405,7 @@ def textarea_should_contain( @keyword def textarea_value_should_be( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, expected: str, message: Optional[str] = None, ): @@ -429,7 +429,7 @@ def textarea_value_should_be( @keyword def page_should_contain_button( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -450,7 +450,7 @@ def page_should_contain_button( @keyword def page_should_not_contain_button( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -469,7 +469,7 @@ def page_should_not_contain_button( def _get_value(self, locator, tag): return self.find_element(locator, tag).get_attribute("value") - def _get_checkbox(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): + def _get_checkbox(self, locator: Locator): return self.find_element(locator, tag="checkbox") def _get_radio_buttons(self, group_name): diff --git a/src/SeleniumLibrary/keywords/frames.py b/src/SeleniumLibrary/keywords/frames.py index 1967286ec..2490145f9 100644 --- a/src/SeleniumLibrary/keywords/frames.py +++ b/src/SeleniumLibrary/keywords/frames.py @@ -18,11 +18,12 @@ from selenium.webdriver.remote.webelement import WebElement from SeleniumLibrary.base import LibraryComponent, keyword +from SeleniumLibrary.utils.types import Locator class FrameKeywords(LibraryComponent): @keyword - def select_frame(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): + def select_frame(self, locator: Locator): """Sets frame identified by ``locator`` as the current frame. See the `Locating elements` section for details about the locator @@ -82,7 +83,7 @@ def current_frame_should_not_contain(self, text: str, loglevel: str = "TRACE"): @keyword def frame_should_contain( - self, locator: Union[WebElement, str, List[Union[WebElement,str]]], text: str, loglevel: str = "TRACE" + self, locator: Locator, text: str, loglevel: str = "TRACE" ): """Verifies that frame identified by ``locator`` contains ``text``. @@ -99,7 +100,7 @@ def frame_should_contain( ) self.info(f"Frame '{locator}' contains text '{text}'.") - def _frame_contains(self, locator: Union[WebElement, str, List[Union[WebElement,str]]], text: str): + def _frame_contains(self, locator: Locator, text: str): element = self.find_element(locator) self.driver.switch_to.frame(element) self.info(f"Searching for text from frame '{locator}'.") diff --git a/src/SeleniumLibrary/keywords/screenshot.py b/src/SeleniumLibrary/keywords/screenshot.py index 2adc2df98..64be59909 100644 --- a/src/SeleniumLibrary/keywords/screenshot.py +++ b/src/SeleniumLibrary/keywords/screenshot.py @@ -23,6 +23,7 @@ from SeleniumLibrary.base import LibraryComponent, keyword from SeleniumLibrary.utils.path_formatter import _format_path +from SeleniumLibrary.utils.types import Locator DEFAULT_FILENAME_PAGE = "selenium-screenshot-{index}.png" DEFAULT_FILENAME_ELEMENT = "selenium-element-screenshot-{index}.png" @@ -146,7 +147,7 @@ def _capture_page_screen_to_log(self, return_val): @keyword def capture_element_screenshot( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, filename: str = DEFAULT_FILENAME_ELEMENT, ) -> str: """Captures a screenshot from the element identified by ``locator`` and embeds it into log file. diff --git a/src/SeleniumLibrary/keywords/selectelement.py b/src/SeleniumLibrary/keywords/selectelement.py index a4c8ce32b..68290f1fd 100644 --- a/src/SeleniumLibrary/keywords/selectelement.py +++ b/src/SeleniumLibrary/keywords/selectelement.py @@ -20,12 +20,13 @@ from SeleniumLibrary.base import LibraryComponent, keyword from SeleniumLibrary.utils import is_truthy, plural_or_not +from SeleniumLibrary.utils.types import Locator class SelectElementKeywords(LibraryComponent): @keyword def get_list_items( - self, locator: Union[WebElement, str, List[Union[WebElement,str]]], values: bool = False + self, locator: Locator, values: bool = False ) -> List[str]: """Returns all labels or values of selection list ``locator``. @@ -49,7 +50,7 @@ def get_list_items( return self._get_labels(options) @keyword - def get_selected_list_label(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]) -> str: + def get_selected_list_label(self, locator: Locator) -> str: """Returns the label of selected option from selection list ``locator``. If there are multiple selected options, the label of the first option @@ -62,7 +63,7 @@ def get_selected_list_label(self, locator: Union[WebElement, str, List[Union[Web return select.first_selected_option.text @keyword - def get_selected_list_labels(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]) -> List[str]: + def get_selected_list_labels(self, locator: Locator) -> List[str]: """Returns labels of selected options from selection list ``locator``. Starting from SeleniumLibrary 3.0, returns an empty list if there @@ -75,7 +76,7 @@ def get_selected_list_labels(self, locator: Union[WebElement, str, List[Union[We return self._get_labels(options) @keyword - def get_selected_list_value(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]) -> str: + def get_selected_list_value(self, locator: Locator) -> str: """Returns the value of selected option from selection list ``locator``. If there are multiple selected options, the value of the first option @@ -88,7 +89,7 @@ def get_selected_list_value(self, locator: Union[WebElement, str, List[Union[Web return select.first_selected_option.get_attribute("value") @keyword - def get_selected_list_values(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]) -> List[str]: + def get_selected_list_values(self, locator: Locator) -> List[str]: """Returns values of selected options from selection list ``locator``. Starting from SeleniumLibrary 3.0, returns an empty list if there @@ -101,7 +102,7 @@ def get_selected_list_values(self, locator: Union[WebElement, str, List[Union[We return self._get_values(options) @keyword - def list_selection_should_be(self, locator: Union[WebElement, str, List[Union[WebElement,str]]], *expected: str): + def list_selection_should_be(self, locator: Locator, *expected: str): """Verifies selection list ``locator`` has ``expected`` options selected. It is possible to give expected options both as visible labels and @@ -138,7 +139,7 @@ def _format_selection(self, labels, values): return " | ".join(f"{label} ({value})" for label, value in zip(labels, values)) @keyword - def list_should_have_no_selections(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): + def list_should_have_no_selections(self, locator: Locator): """Verifies selection list ``locator`` has no options selected. See the `Locating elements` section for details about the locator @@ -158,7 +159,7 @@ def list_should_have_no_selections(self, locator: Union[WebElement, str, List[Un @keyword def page_should_contain_list( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -175,7 +176,7 @@ def page_should_contain_list( @keyword def page_should_not_contain_list( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, message: Optional[str] = None, loglevel: str = "TRACE", ): @@ -190,7 +191,7 @@ def page_should_not_contain_list( self.assert_page_not_contains(locator, "list", message, loglevel) @keyword - def select_all_from_list(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): + def select_all_from_list(self, locator: Locator): """Selects all options from multi-selection list ``locator``. See the `Locating elements` section for details about the locator @@ -206,7 +207,7 @@ def select_all_from_list(self, locator: Union[WebElement, str, List[Union[WebEle select.select_by_index(index) @keyword - def select_from_list_by_index(self, locator: Union[WebElement, str, List[Union[WebElement,str]]], *indexes: str): + def select_from_list_by_index(self, locator: Locator, *indexes: str): """Selects options from selection list ``locator`` by ``indexes``. Indexes of list options start from 0. @@ -231,7 +232,7 @@ def select_from_list_by_index(self, locator: Union[WebElement, str, List[Union[W select.select_by_index(int(index)) @keyword - def select_from_list_by_value(self, locator: Union[WebElement, str, List[Union[WebElement,str]]], *values: str): + def select_from_list_by_value(self, locator: Locator, *values: str): """Selects options from selection list ``locator`` by ``values``. If more than one option is given for a single-selection list, @@ -253,7 +254,7 @@ def select_from_list_by_value(self, locator: Union[WebElement, str, List[Union[W select.select_by_value(value) @keyword - def select_from_list_by_label(self, locator: Union[WebElement, str, List[Union[WebElement,str]]], *labels: str): + def select_from_list_by_label(self, locator: Locator, *labels: str): """Selects options from selection list ``locator`` by ``labels``. If more than one option is given for a single-selection list, @@ -275,7 +276,7 @@ def select_from_list_by_label(self, locator: Union[WebElement, str, List[Union[W select.select_by_visible_text(label) @keyword - def unselect_all_from_list(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): + def unselect_all_from_list(self, locator: Locator): """Unselects all options from multi-selection list ``locator``. See the `Locating elements` section for details about the locator @@ -293,7 +294,7 @@ def unselect_all_from_list(self, locator: Union[WebElement, str, List[Union[WebE @keyword def unselect_from_list_by_index( - self, locator: Union[WebElement, str, List[Union[WebElement,str]]], *indexes: str + self, locator: Locator, *indexes: str ): """Unselects options from selection list ``locator`` by ``indexes``. @@ -320,7 +321,7 @@ def unselect_from_list_by_index( @keyword def unselect_from_list_by_value( - self, locator: Union[WebElement, str, List[Union[WebElement,str]]], *values: str + self, locator: Locator, *values: str ): """Unselects options from selection list ``locator`` by ``values``. @@ -345,7 +346,7 @@ def unselect_from_list_by_value( @keyword def unselect_from_list_by_label( - self, locator: Union[WebElement, str, List[Union[WebElement,str]]], *labels: str + self, locator: Locator, *labels: str ): """Unselects options from selection list ``locator`` by ``labels``. @@ -368,14 +369,14 @@ def unselect_from_list_by_label( for label in labels: select.deselect_by_visible_text(label) - def _get_select_list(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): + def _get_select_list(self, locator: Locator): el = self.find_element(locator, tag="list") return Select(el) - def _get_options(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): + def _get_options(self, locator: Locator): return self._get_select_list(locator).options - def _get_selected_options(self, locator: Union[WebElement, str, List[Union[WebElement,str]]]): + def _get_selected_options(self, locator: Locator): return self._get_select_list(locator).all_selected_options def _get_labels(self, options): diff --git a/src/SeleniumLibrary/keywords/tableelement.py b/src/SeleniumLibrary/keywords/tableelement.py index 47519ff42..865195a34 100644 --- a/src/SeleniumLibrary/keywords/tableelement.py +++ b/src/SeleniumLibrary/keywords/tableelement.py @@ -19,13 +19,14 @@ from selenium.webdriver.remote.webelement import WebElement from SeleniumLibrary.base import LibraryComponent, keyword +from SeleniumLibrary.utils.types import Locator class TableElementKeywords(LibraryComponent): @keyword def get_table_cell( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, row: int, column: int, loglevel: str = "TRACE", @@ -89,7 +90,7 @@ def _get_rows(self, locator, count): @keyword def table_cell_should_contain( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, row: int, column: int, expected: str, @@ -112,7 +113,7 @@ def table_cell_should_contain( @keyword def table_column_should_contain( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, column: int, expected: str, loglevel: str = "TRACE", @@ -143,7 +144,7 @@ def table_column_should_contain( @keyword def table_footer_should_contain( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, expected: str, loglevel: str = "TRACE", ): @@ -168,7 +169,7 @@ def table_footer_should_contain( @keyword def table_header_should_contain( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, expected: str, loglevel: str = "TRACE", ): @@ -193,7 +194,7 @@ def table_header_should_contain( @keyword def table_row_should_contain( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, row: int, expected: str, loglevel: str = "TRACE", @@ -224,7 +225,7 @@ def table_row_should_contain( @keyword def table_should_contain( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, expected: str, loglevel: str = "TRACE", ): diff --git a/src/SeleniumLibrary/keywords/waiting.py b/src/SeleniumLibrary/keywords/waiting.py index 7dd38e0e6..0843ffcec 100644 --- a/src/SeleniumLibrary/keywords/waiting.py +++ b/src/SeleniumLibrary/keywords/waiting.py @@ -24,6 +24,7 @@ from SeleniumLibrary.base import LibraryComponent, keyword from SeleniumLibrary.errors import ElementNotFound from SeleniumLibrary.utils import secs_to_timestr +from SeleniumLibrary.utils.types import Locator class WaitingKeywords(LibraryComponent): @@ -222,7 +223,7 @@ def wait_until_page_does_not_contain( @keyword def wait_until_page_contains_element( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, timeout: Optional[timedelta] = None, error: Optional[str] = None, limit: Optional[int] = None, @@ -260,7 +261,7 @@ def wait_until_page_contains_element( @keyword def wait_until_page_does_not_contain_element( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, timeout: Optional[timedelta] = None, error: Optional[str] = None, limit: Optional[int] = None, @@ -298,7 +299,7 @@ def wait_until_page_does_not_contain_element( @keyword def wait_until_element_is_visible( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, timeout: Optional[timedelta] = None, error: Optional[str] = None, ): @@ -321,7 +322,7 @@ def wait_until_element_is_visible( @keyword def wait_until_element_is_not_visible( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, timeout: Optional[timedelta] = None, error: Optional[str] = None, ): @@ -344,7 +345,7 @@ def wait_until_element_is_not_visible( @keyword def wait_until_element_is_enabled( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, timeout: Optional[timedelta] = None, error: Optional[str] = None, ): @@ -372,7 +373,7 @@ def wait_until_element_is_enabled( @keyword def wait_until_element_contains( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, text: str, timeout: Optional[timedelta] = None, error: Optional[str] = None, @@ -396,7 +397,7 @@ def wait_until_element_contains( @keyword def wait_until_element_does_not_contain( self, - locator: Union[WebElement, str, List[Union[WebElement,str]]], + locator: Locator, text: str, timeout: Optional[timedelta] = None, error: Optional[str] = None, diff --git a/src/SeleniumLibrary/utils/types.py b/src/SeleniumLibrary/utils/types.py index 181b0bf50..04ecc8f40 100644 --- a/src/SeleniumLibrary/utils/types.py +++ b/src/SeleniumLibrary/utils/types.py @@ -15,15 +15,18 @@ # limitations under the License. import os from datetime import timedelta -from typing import Any +from typing import Any, TypeAlias from robot.utils import timestr_to_secs from robot.utils import is_truthy, is_falsy # noqa +from selenium.webdriver.remote.webelement import WebElement + # Need only for unit tests and can be removed when Approval tests fixes: # https://github.com/approvals/ApprovalTests.Python/issues/41 WINDOWS = os.name == "nt" +Locator: TypeAlias = WebElement | str | list['Locator'] def is_noney(item): return item is None or isinstance(item, str) and item.upper() == "NONE" From 87c1963332ea2e25b6911beaf8d702b88887ae37 Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Sun, 5 Apr 2026 16:42:55 -0500 Subject: [PATCH 05/12] Set minimum Python version to 3.10 With using the type union syntax we will require atleast Python 3.10. Also v3.8 and v3.9 have reached end of life. --- setup.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/setup.py b/setup.py index 0faf6569a..f24a0b8a9 100755 --- a/setup.py +++ b/setup.py @@ -13,8 +13,6 @@ Operating System :: OS Independent Programming Language :: Python Programming Language :: Python :: 3 -Programming Language :: Python :: 3.8 -Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 From d4c141dfa1ab402121b948a2ffa2fbb60ceaab05 Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Sun, 5 Apr 2026 21:15:42 -0500 Subject: [PATCH 06/12] Fixed expected output on test after recent spelling correction change --- .../PluginDocumentation.test_many_plugins.approved.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utest/test/api/approved_files/PluginDocumentation.test_many_plugins.approved.txt b/utest/test/api/approved_files/PluginDocumentation.test_many_plugins.approved.txt index 1a204d3bb..f1aecf173 100644 --- a/utest/test/api/approved_files/PluginDocumentation.test_many_plugins.approved.txt +++ b/utest/test/api/approved_files/PluginDocumentation.test_many_plugins.approved.txt @@ -142,7 +142,7 @@ space. It is also possible mix different locator strategies, example css or xpat used to specify multiple locators. This is useful, is some part of locator would match as the locator separator but it should not. Or if there is need to existing WebElement as locator. -Although all locators support chaining, some locator strategies do not abey the chaining. This is because +Although all locators support chaining, some locator strategies do not obey the chaining. This is because some locator strategies use JavaScript to find elements and JavaScript is executed for the whole browser context and not for the element found be the previous locator. Chaining is supported by locator strategies which are based on Selenium API, like `xpath` or `css`, but example chaining is not supported by `sizzle` or `jquery From f2d924a1551f0e8b21126e06c5a82621d1bfe3ac Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Sun, 5 Apr 2026 21:16:35 -0500 Subject: [PATCH 07/12] Added missing import for new Locator type --- src/SeleniumLibrary/keywords/formelement.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/SeleniumLibrary/keywords/formelement.py b/src/SeleniumLibrary/keywords/formelement.py index 2690cd5af..8d989b865 100644 --- a/src/SeleniumLibrary/keywords/formelement.py +++ b/src/SeleniumLibrary/keywords/formelement.py @@ -22,6 +22,7 @@ from SeleniumLibrary.base import LibraryComponent, keyword from SeleniumLibrary.errors import ElementNotFound +from SeleniumLibrary.utils.types import Locator class FormElementKeywords(LibraryComponent): From 5c113c68a57ebf2572c583490f7d571c98c7402e Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Mon, 6 Apr 2026 09:57:09 -0500 Subject: [PATCH 08/12] Corrected expiry time on far_future cookie --- atest/acceptance/keywords/cookies.robot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atest/acceptance/keywords/cookies.robot b/atest/acceptance/keywords/cookies.robot index 04b355e6a..fbe22bdae 100644 --- a/atest/acceptance/keywords/cookies.robot +++ b/atest/acceptance/keywords/cookies.robot @@ -136,4 +136,4 @@ Add Cookies ${tomorrow_thistime_datetime} = Convert Date ${tomorrow_thistime} datetime Set Suite Variable ${tomorrow_thistime_datetime} Add Cookie another value expiry=${tomorrow_thistime} - Add Cookie far_future timemachine expiry=1788236700 # 2026-09-01 12:25:00 + Add Cookie far_future timemachine expiry=1788240300 # 2026-09-01 12:25:00 From 822a862c4cad7af22bb131d0337a4ebc3b19530c Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Mon, 6 Apr 2026 09:57:30 -0500 Subject: [PATCH 09/12] (Temporarily) Fixed issue with Get Cookies test The `Get Cookies` test started to fail. It was written to expect just the another and test cookies but was also receiving the far_future cookie. I'm a bit unsure as to why this wasn't failing sooner. I would have expected this cookie to also appear. So not sure what was happening here. Git blame shows this has been unchanged for a while. I have updated the expected return value from get cookies. I do see some possible improvements. The previous check allowed for the order to be swapped. With three that is a bit more complicated. Instead of going into a complex check for any order I am going to leave this till we see that appear. --- atest/acceptance/keywords/cookies.robot | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atest/acceptance/keywords/cookies.robot b/atest/acceptance/keywords/cookies.robot index fbe22bdae..664c36664 100644 --- a/atest/acceptance/keywords/cookies.robot +++ b/atest/acceptance/keywords/cookies.robot @@ -9,8 +9,8 @@ Library DateTime *** Test Cases *** Get Cookies ${cookies}= Get Cookies - Should Match Regexp ${cookies} - ... ^(test=seleniumlibrary; another=value)|(another=value; test=seleniumlibrary)$ + Should Be Equal As Strings ${cookies} + ... another=value; test=seleniumlibrary; far_future=timemachine Get Cookies As Dict ${cookies}= Get Cookies as_dict=True From 3139960091f983b7ac6a0661875d7cf6e317ee6f Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Mon, 6 Apr 2026 13:27:12 -0500 Subject: [PATCH 10/12] Added regex to handle any order on cookies --- atest/acceptance/keywords/cookies.robot | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atest/acceptance/keywords/cookies.robot b/atest/acceptance/keywords/cookies.robot index 664c36664..aa965b4d1 100644 --- a/atest/acceptance/keywords/cookies.robot +++ b/atest/acceptance/keywords/cookies.robot @@ -9,8 +9,8 @@ Library DateTime *** Test Cases *** Get Cookies ${cookies}= Get Cookies - Should Be Equal As Strings ${cookies} - ... another=value; test=seleniumlibrary; far_future=timemachine + Should Match Regexp ${cookies} + ... (?=.*another=value)(?=.*test=seleniumlibrary)(?=.*far_future=timemachine) Get Cookies As Dict ${cookies}= Get Cookies as_dict=True From 8c0ae7ebaa301e08b81562ec2329f39dc70d3908 Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Mon, 6 Apr 2026 15:23:50 -0500 Subject: [PATCH 11/12] Cleaned up type for arguments where could be Locator or None --- src/SeleniumLibrary/keywords/element.py | 3 ++- src/SeleniumLibrary/keywords/formelement.py | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/SeleniumLibrary/keywords/element.py b/src/SeleniumLibrary/keywords/element.py index e4e536f59..1a8cd55e3 100644 --- a/src/SeleniumLibrary/keywords/element.py +++ b/src/SeleniumLibrary/keywords/element.py @@ -13,6 +13,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + from collections import namedtuple from typing import List, Optional, Tuple, Union @@ -930,7 +931,7 @@ def press_key(self, locator: Locator, key: str): element.send_keys(key) @keyword - def press_keys(self, locator: Union[WebElement, None, str, List[Union[WebElement,str]]] = None, *keys: str): + def press_keys(self, locator: Locator | None = None, *keys: str): """Simulates the user pressing key(s) to an element or on the active browser. If ``locator`` evaluates as false, see `Boolean arguments` for more diff --git a/src/SeleniumLibrary/keywords/formelement.py b/src/SeleniumLibrary/keywords/formelement.py index 8d989b865..43816e04f 100644 --- a/src/SeleniumLibrary/keywords/formelement.py +++ b/src/SeleniumLibrary/keywords/formelement.py @@ -15,10 +15,9 @@ # limitations under the License. import os -from typing import Optional, Union, List +from typing import Optional from robot.libraries.BuiltIn import BuiltIn -from selenium.webdriver.remote.webelement import WebElement from SeleniumLibrary.base import LibraryComponent, keyword from SeleniumLibrary.errors import ElementNotFound @@ -27,7 +26,7 @@ class FormElementKeywords(LibraryComponent): @keyword - def submit_form(self, locator: Union[WebElement, None, str, List[Union[WebElement,str]]] = None): + def submit_form(self, locator: Locator | None = None): """Submits a form identified by ``locator``. If ``locator`` is not given, first form on the page is submitted. From baba97061726997cadaf240a0faf9c7a8a787c2d Mon Sep 17 00:00:00 2001 From: Ed Manlove Date: Mon, 6 Apr 2026 15:25:28 -0500 Subject: [PATCH 12/12] Cleaned up imports removing unused List, Union, and WebElement --- src/SeleniumLibrary/keywords/frames.py | 3 --- src/SeleniumLibrary/keywords/screenshot.py | 4 ++-- src/SeleniumLibrary/keywords/tableelement.py | 2 -- src/SeleniumLibrary/keywords/waiting.py | 3 +-- 4 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/SeleniumLibrary/keywords/frames.py b/src/SeleniumLibrary/keywords/frames.py index 2490145f9..f2653fc03 100644 --- a/src/SeleniumLibrary/keywords/frames.py +++ b/src/SeleniumLibrary/keywords/frames.py @@ -13,9 +13,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from typing import Union, List - -from selenium.webdriver.remote.webelement import WebElement from SeleniumLibrary.base import LibraryComponent, keyword from SeleniumLibrary.utils.types import Locator diff --git a/src/SeleniumLibrary/keywords/screenshot.py b/src/SeleniumLibrary/keywords/screenshot.py index 64be59909..6963b4e28 100644 --- a/src/SeleniumLibrary/keywords/screenshot.py +++ b/src/SeleniumLibrary/keywords/screenshot.py @@ -13,12 +13,12 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + import os -from typing import Optional, Union, List +from typing import Optional, Union from base64 import b64decode from robot.utils import get_link_path -from selenium.webdriver.remote.webelement import WebElement from selenium.webdriver.common.print_page_options import PrintOptions, Orientation from SeleniumLibrary.base import LibraryComponent, keyword diff --git a/src/SeleniumLibrary/keywords/tableelement.py b/src/SeleniumLibrary/keywords/tableelement.py index 865195a34..a3100425f 100644 --- a/src/SeleniumLibrary/keywords/tableelement.py +++ b/src/SeleniumLibrary/keywords/tableelement.py @@ -13,10 +13,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from typing import Union, List from selenium.webdriver.common.by import By -from selenium.webdriver.remote.webelement import WebElement from SeleniumLibrary.base import LibraryComponent, keyword from SeleniumLibrary.utils.types import Locator diff --git a/src/SeleniumLibrary/keywords/waiting.py b/src/SeleniumLibrary/keywords/waiting.py index 0843ffcec..b8bb9473e 100644 --- a/src/SeleniumLibrary/keywords/waiting.py +++ b/src/SeleniumLibrary/keywords/waiting.py @@ -16,10 +16,9 @@ import time from datetime import timedelta -from typing import Optional, Union, List +from typing import Optional from selenium.common.exceptions import StaleElementReferenceException -from selenium.webdriver.remote.webelement import WebElement from SeleniumLibrary.base import LibraryComponent, keyword from SeleniumLibrary.errors import ElementNotFound