Skip to content

Improve type hints #1666

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions src/SeleniumLibrary/base/librarycomponent.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@
# limitations under the License.

import os
from typing import Optional
from datetime import timedelta
from typing import Optional, Union

from robot.api import logger
from robot.libraries.BuiltIn import BuiltIn, RobotNotRunningError

from SeleniumLibrary.utils import is_noney, timestr_to_secs
from SeleniumLibrary.utils import is_noney

from .context import ContextAware

Expand Down Expand Up @@ -74,10 +75,12 @@ def assert_page_not_contains(
raise AssertionError(message)
logger.info(f"Current page does not contain {tag_message} '{locator}'.")

def get_timeout(self, timeout: Optional[str] = None) -> float:
if is_noney(timeout):
def get_timeout(self, timeout: Union[str, int, timedelta, None] = None) -> float:
if timeout is None:
return self.ctx.timeout
return timestr_to_secs(timeout)
if isinstance(timeout, timedelta):
return timeout.total_seconds()
return timeout

@property
def log_dir(self):
Expand Down
14 changes: 9 additions & 5 deletions src/SeleniumLibrary/keywords/alert.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +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
from datetime import timedelta
from typing import Union, Optional

from selenium.common.exceptions import TimeoutException, WebDriverException
from selenium.webdriver.support import expected_conditions as EC
Expand All @@ -31,7 +32,7 @@ class AlertKeywords(LibraryComponent):

@keyword
def input_text_into_alert(
self, text: str, action: str = ACCEPT, timeout: Union[float, str, None] = None
self, text: str, action: str = ACCEPT, timeout: Optional[timedelta] = None
):
"""Types the given ``text`` into an input field in an alert.

Expand All @@ -52,7 +53,7 @@ def alert_should_be_present(
self,
text: str = "",
action: str = ACCEPT,
timeout: Union[float, str, None] = None,
timeout: Optional[timedelta] = None,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional[] is unnecessary when there's None as a default value. Even MyPy doesn't need it unless you run it with --no-implicit-optional:
https://mypy.readthedocs.io/en/stable/command_line.html?highlight=optional#cmdoption-mypy-no-implicit-optional

Copy link
Member

@mkorpela mkorpela Oct 2, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Optional is best practice if None is used. Types should not contain None as part of the type and if they do, then that should explicitly be stated.
See for example https://en.wikipedia.org/wiki/Void_safety for more details on this subject.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And the default is also changing in mypy python/mypy#9091

):
"""Verifies that an alert is present and by default, accepts it.

Expand All @@ -68,6 +69,7 @@ def alert_should_be_present(
In earlier versions, the alert was always accepted and a timeout was
hardcoded to one second.
"""
self.info(f"{type(timeout)}::{timeout}")
message = self.handle_alert(action, timeout)
if text and text != message:
raise AssertionError(
Expand All @@ -76,7 +78,7 @@ def alert_should_be_present(

@keyword
def alert_should_not_be_present(
self, action: str = ACCEPT, timeout: Union[float, str, None] = 0
self, action: str = ACCEPT, timeout: Union[timedelta] = 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrong because an argument with type timedelta cannot really have an integer as a default. You probably also meant Optional[timedelta] not Union[timedelta] (but I'd leave Optional[] away as well).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, what would be suitable default? The easiest would be to change it to None but that would be somewhat backwards incompatible (Because it would default to library default timeout.) Would:

def alert_should_not_be_present(self, action: str = ACCEPT, timeout = timedelta(seconds=0);

be better?

):
"""Verifies that no alert is present.

Expand All @@ -101,7 +103,7 @@ def alert_should_not_be_present(

@keyword
def handle_alert(
self, action: str = ACCEPT, timeout: Union[float, str, None] = None
self, action: str = ACCEPT, timeout: Optional[timedelta] = None
):
"""Handles the current alert and returns its message.

Expand All @@ -127,6 +129,7 @@ def handle_alert(

New in SeleniumLibrary 3.0.
"""
self.info(f"HANDLE::{type(timeout)}::{timeout}")
alert = self._wait_alert(timeout)
return self._handle_alert(alert, action)

Expand All @@ -143,6 +146,7 @@ def _handle_alert(self, alert, action):

def _wait_alert(self, timeout=None):
timeout = self.get_timeout(timeout)
self.info(f"WAITING::{type(timeout)}::{timeout}")
wait = WebDriverWait(self.driver, timeout)
try:
return wait.until(EC.alert_is_present())
Expand Down
2 changes: 1 addition & 1 deletion src/SeleniumLibrary/keywords/browsermanagement.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def open_browser(
url: Optional[str] = None,
browser: str = "firefox",
alias: Optional[str] = None,
remote_url: Optional[str] = False,
remote_url: Union[str, bool] = False,
desired_capabilities: Union[str, dict, None] = None,
ff_profile_dir: Optional[str] = None,
options: Any = None,
Expand Down
12 changes: 6 additions & 6 deletions src/SeleniumLibrary/keywords/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from collections import namedtuple
from typing import List, Optional, Tuple
from typing import List, Optional, Tuple, Union

from robot.utils import plural_or_not
from selenium.webdriver.common.action_chains import ActionChains
Expand Down Expand Up @@ -144,7 +144,7 @@ def page_should_contain_element(
self,
locator: str,
message: Optional[str] = None,
loglevel: Optional[str] = "TRACE",
loglevel: str = "TRACE",
limit: Optional[int] = None,
):
"""Verifies that element ``locator`` is found on the current page.
Expand Down Expand Up @@ -530,7 +530,7 @@ def get_vertical_position(self, locator: str) -> int:
return self.find_element(locator).location["y"]

@keyword
def click_button(self, locator: str, modifier: Optional[str] = False):
def click_button(self, locator: str, modifier: Union[str, bool] = False):
"""Clicks the button identified by ``locator``.

See the `Locating elements` section for details about the locator
Expand All @@ -552,7 +552,7 @@ def click_button(self, locator: str, modifier: Optional[str] = False):
self._click_with_modifier(locator, ["button", "input"], modifier)

@keyword
def click_image(self, locator: str, modifier: Optional[str] = False):
def click_image(self, locator: str, modifier: Union[str, bool] = False):
"""Clicks an image identified by ``locator``.

See the `Locating elements` section for details about the locator
Expand All @@ -575,7 +575,7 @@ def click_image(self, locator: str, modifier: Optional[str] = False):
self._click_with_modifier(locator, ["image", "input"], modifier)

@keyword
def click_link(self, locator: str, modifier: Optional[str] = False):
def click_link(self, locator: str, modifier: Union[str, bool] = False):
"""Clicks a link identified by ``locator``.

See the `Locating elements` section for details about the locator
Expand All @@ -595,7 +595,7 @@ def click_link(self, locator: str, modifier: Optional[str] = False):

@keyword
def click_element(
self, locator: str, modifier: Optional[str] = False, action_chain: bool = False
self, locator: str, modifier: Union[str, bool] = False, action_chain: bool = False
):
"""Click the element identified by ``locator``.

Expand Down
29 changes: 15 additions & 14 deletions src/SeleniumLibrary/keywords/waiting.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
# limitations under the License.

import time
from datetime import timedelta
from typing import Optional, Union

from selenium.common.exceptions import StaleElementReferenceException
Expand All @@ -29,7 +30,7 @@ class WaitingKeywords(LibraryComponent):
def wait_for_condition(
self,
condition: str,
timeout: Union[str, float, None] = None,
timeout: Optional[timedelta] = None,
error: Optional[str] = None,
):
"""Waits until ``condition`` is true or ``timeout`` expires.
Expand Down Expand Up @@ -64,7 +65,7 @@ def wait_for_condition(
def wait_until_location_is(
self,
expected: str,
timeout: Union[str, float, None] = None,
timeout: Optional[timedelta] = None,
message: Optional[str] = None,
):
"""Waits until the current URL is ``expected``.
Expand Down Expand Up @@ -93,7 +94,7 @@ def wait_until_location_is(
def wait_until_location_is_not(
self,
location: str,
timeout: Union[str, float, None] = None,
timeout: Optional[timedelta] = None,
message: Optional[str] = None,
):
"""Waits until the current URL is not ``location``.
Expand Down Expand Up @@ -121,7 +122,7 @@ def wait_until_location_is_not(
def wait_until_location_contains(
self,
expected: str,
timeout: Union[str, float, None] = None,
timeout: Optional[timedelta] = None,
message: Optional[str] = None,
):
"""Waits until the current URL contains ``expected``.
Expand Down Expand Up @@ -149,7 +150,7 @@ def wait_until_location_contains(
def wait_until_location_does_not_contain(
self,
location: str,
timeout: Union[str, float, None] = None,
timeout: Optional[timedelta] = None,
message: Optional[str] = None,
):
"""Waits until the current URL does not contains ``location``.
Expand Down Expand Up @@ -177,7 +178,7 @@ def wait_until_location_does_not_contain(
def wait_until_page_contains(
self,
text: str,
timeout: Union[str, float, None] = None,
timeout: Optional[timedelta] = None,
error: Optional[str] = None,
):
"""Waits until ``text`` appears on the current page.
Expand All @@ -199,7 +200,7 @@ def wait_until_page_contains(
def wait_until_page_does_not_contain(
self,
text: str,
timeout: Union[str, float, None] = None,
timeout: Optional[timedelta] = None,
error: Optional[str] = None,
):
"""Waits until ``text`` disappears from the current page.
Expand All @@ -221,7 +222,7 @@ def wait_until_page_does_not_contain(
def wait_until_page_contains_element(
self,
locator: str,
timeout: Union[str, float, None] = None,
timeout: Optional[timedelta] = None,
error: Optional[str] = None,
limit: Optional[int] = None,
):
Expand Down Expand Up @@ -259,7 +260,7 @@ def wait_until_page_contains_element(
def wait_until_page_does_not_contain_element(
self,
locator: str,
timeout: Union[str, float, None] = None,
timeout: Optional[timedelta] = None,
error: Optional[str] = None,
limit: Optional[int] = None,
):
Expand Down Expand Up @@ -297,7 +298,7 @@ def wait_until_page_does_not_contain_element(
def wait_until_element_is_visible(
self,
locator: str,
timeout: Union[str, float, None] = None,
timeout: Optional[timedelta] = None,
error: Optional[str] = None,
):
"""Waits until the element ``locator`` is visible.
Expand All @@ -320,7 +321,7 @@ def wait_until_element_is_visible(
def wait_until_element_is_not_visible(
self,
locator: str,
timeout: Union[str, float, None] = None,
timeout: Optional[timedelta] = None,
error: Optional[str] = None,
):
"""Waits until the element ``locator`` is not visible.
Expand All @@ -343,7 +344,7 @@ def wait_until_element_is_not_visible(
def wait_until_element_is_enabled(
self,
locator: str,
timeout: Union[str, float, None] = None,
timeout: Optional[timedelta] = None,
error: Optional[str] = None,
):
"""Waits until the element ``locator`` is enabled.
Expand Down Expand Up @@ -372,7 +373,7 @@ def wait_until_element_contains(
self,
locator: str,
text: str,
timeout: Union[str, float, None] = None,
timeout: Optional[timedelta] = None,
error: Optional[str] = None,
):
"""Waits until the element ``locator`` contains ``text``.
Expand All @@ -396,7 +397,7 @@ def wait_until_element_does_not_contain(
self,
locator: str,
text: str,
timeout: Union[str, float, None] = None,
timeout: Optional[timedelta] = None,
error: Optional[str] = None,
):
"""Waits until the element ``locator`` does not contain ``text``.
Expand Down
29 changes: 29 additions & 0 deletions utest/test/api/test_get_timeout.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from datetime import timedelta

import pytest
from mockito import mock, unstub

from SeleniumLibrary.base import LibraryComponent


@pytest.fixture
def lib():
ctx = mock()
ctx.timeout = 5.0
return LibraryComponent(ctx)


def teardown_function():
unstub()


def test_timeout_as_none(lib: LibraryComponent):
assert lib.get_timeout(None) == 5.0


def test_timeout_as_float(lib: LibraryComponent):
assert lib.get_timeout(1.0) == 1.0


def test_timeout_as_timedelta(lib: LibraryComponent):
assert lib.get_timeout(timedelta(seconds=0.1)) == 0.1
4 changes: 2 additions & 2 deletions utest/test/keywords/test_keyword_arguments_waiting.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def test_wait_for_condition(waiting):
assert "did not become true" in str(error.value)

with pytest.raises(AssertionError) as error:
waiting.wait_for_condition(condition, "None", "foobar")
waiting.wait_for_condition(condition, None, "foobar")
assert "foobar" in str(error.value)


Expand All @@ -38,5 +38,5 @@ def test_wait_until_page_contains(waiting):
assert "Text 'text' did not" in str(error.value)

with pytest.raises(AssertionError) as error:
waiting.wait_until_page_contains(text, "None", "error")
waiting.wait_until_page_contains(text, None, "error")
assert "error" in str(error.value)