From 0623185a9790f3e89b5c47c5ac4aef23cdcabe70 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Thu, 8 Feb 2024 09:24:11 -0600 Subject: [PATCH] using `Self` now that it's available in typing_extensions --- screenpy_selenium/abilities/browse_the_web.py | 24 ++++++------ screenpy_selenium/actions/clear.py | 19 ++++------ screenpy_selenium/actions/click.py | 21 +++++------ screenpy_selenium/actions/double_click.py | 27 +++++--------- screenpy_selenium/actions/enter.py | 35 ++++++++---------- screenpy_selenium/actions/enter_2fa_token.py | 17 ++++----- screenpy_selenium/actions/hold_down.py | 19 ++++------ screenpy_selenium/actions/move_mouse.py | 37 +++++++------------ screenpy_selenium/actions/release.py | 15 ++++---- .../actions/respond_to_the_prompt.py | 13 +++---- screenpy_selenium/actions/right_click.py | 27 +++++--------- screenpy_selenium/actions/save_console_log.py | 17 ++++----- screenpy_selenium/actions/save_screenshot.py | 17 ++++----- screenpy_selenium/actions/select.py | 37 +++++++------------ screenpy_selenium/actions/switch_to.py | 15 ++++---- screenpy_selenium/actions/wait.py | 35 ++++++++---------- screenpy_selenium/questions/list.py | 19 +++++----- screenpy_selenium/questions/number.py | 13 +++---- screenpy_selenium/questions/selected.py | 23 +++++------- screenpy_selenium/questions/text.py | 19 +++++----- screenpy_selenium/target.py | 33 ++++++++--------- 21 files changed, 205 insertions(+), 277 deletions(-) diff --git a/screenpy_selenium/abilities/browse_the_web.py b/screenpy_selenium/abilities/browse_the_web.py index 7a8cd2a..a5f9237 100644 --- a/screenpy_selenium/abilities/browse_the_web.py +++ b/screenpy_selenium/abilities/browse_the_web.py @@ -3,9 +3,10 @@ from __future__ import annotations import os -from typing import TYPE_CHECKING, TypeVar +from typing import TYPE_CHECKING from selenium.webdriver import Chrome, Firefox, Remote, Safari +from typing_extensions import Self from ..exceptions import BrowsingError @@ -15,9 +16,6 @@ DEFAULT_APPIUM_HUB_URL = "http://localhost:4723/wd/hub" -SelfBrowseTheWeb = TypeVar("SelfBrowseTheWeb", bound="BrowseTheWeb") - - class BrowseTheWeb: """Use Selenium to enable browsing the web with a web browser. @@ -35,22 +33,22 @@ class BrowseTheWeb: browser: WebDriver @classmethod - def using_chrome(cls: type[SelfBrowseTheWeb]) -> SelfBrowseTheWeb: + def using_chrome(cls) -> Self: """Create and use a default Chrome Selenium webdriver instance.""" return cls.using(browser=Chrome()) @classmethod - def using_firefox(cls: type[SelfBrowseTheWeb]) -> SelfBrowseTheWeb: + def using_firefox(cls) -> Self: """Create and use a default Firefox Selenium webdriver instance.""" return cls.using(browser=Firefox()) @classmethod - def using_safari(cls: type[SelfBrowseTheWeb]) -> SelfBrowseTheWeb: + def using_safari(cls) -> Self: """Create and use a default Safari Selenium webdriver instance.""" return cls.using(browser=Safari()) @classmethod - def using_ios(cls: type[SelfBrowseTheWeb]) -> SelfBrowseTheWeb: + def using_ios(cls) -> Self: """ Create and use a default Remote driver instance. @@ -83,7 +81,7 @@ def using_ios(cls: type[SelfBrowseTheWeb]) -> SelfBrowseTheWeb: return cls.using(browser=Remote(hub_url, IOS_CAPABILITIES)) @classmethod - def using_android(cls: type[SelfBrowseTheWeb]) -> SelfBrowseTheWeb: + def using_android(cls) -> Self: """ Create and use a default Remote driver instance. @@ -116,19 +114,19 @@ def using_android(cls: type[SelfBrowseTheWeb]) -> SelfBrowseTheWeb: return cls.using(browser=Remote(hub_url, ANDROID_CAPABILITIES)) @classmethod - def using(cls: type[SelfBrowseTheWeb], browser: WebDriver) -> SelfBrowseTheWeb: + def using(cls, browser: WebDriver) -> Self: """Provide an already-set-up WebDriver to use to browse the web.""" return cls(browser=browser) - def forget(self: SelfBrowseTheWeb) -> None: + def forget(self) -> None: """Quit the attached browser.""" self.browser.quit() - def __repr__(self: SelfBrowseTheWeb) -> str: + def __repr__(self) -> str: """Repr.""" return "Browse the Web" __str__ = __repr__ - def __init__(self: SelfBrowseTheWeb, browser: WebDriver) -> None: + def __init__(self, browser: WebDriver) -> None: self.browser = browser diff --git a/screenpy_selenium/actions/clear.py b/screenpy_selenium/actions/clear.py index ea0a621..6b84012 100644 --- a/screenpy_selenium/actions/clear.py +++ b/screenpy_selenium/actions/clear.py @@ -2,19 +2,18 @@ from __future__ import annotations -from typing import TYPE_CHECKING, TypeVar +from typing import TYPE_CHECKING from screenpy.exceptions import DeliveryError from screenpy.pacing import beat from selenium.common.exceptions import WebDriverException +from typing_extensions import Self if TYPE_CHECKING: from screenpy.actor import Actor from ..target import Target -SelfClear = TypeVar("SelfClear", bound="Clear") - class Clear: """Clear the text from an input field. @@ -28,7 +27,7 @@ class Clear: """ @classmethod - def the_text_from_the(cls: type[SelfClear], target: Target) -> SelfClear: + def the_text_from_the(cls, target: Target) -> Self: """ Specify the Target from which to clear the text. @@ -39,23 +38,21 @@ def the_text_from_the(cls: type[SelfClear], target: Target) -> SelfClear: return cls(target=target) @classmethod - def the_text_from(cls: type[SelfClear], target: Target) -> SelfClear: + def the_text_from(cls, target: Target) -> Self: """Alias for :meth:`~screenpy_selenium.actions.Clear.the_text_from_the`.""" return cls.the_text_from_the(target=target) @classmethod - def the_text_from_the_first_of_the( - cls: type[SelfClear], target: Target - ) -> SelfClear: + def the_text_from_the_first_of_the(cls, target: Target) -> Self: """Alias for :meth:`~screenpy_selenium.actions.Clear.the_text_from_the`.""" return cls.the_text_from_the(target=target) - def describe(self: SelfClear) -> str: + def describe(self) -> str: """Describe the Action in present tense.""" return f"Clear the text from the {self.target}." @beat("{} clears text from the {target}.") - def perform_as(self: SelfClear, the_actor: Actor) -> None: + def perform_as(self, the_actor: Actor) -> None: """Direct the Actor to clear the text from the input field.""" element = self.target.found_by(the_actor) @@ -68,5 +65,5 @@ def perform_as(self: SelfClear, the_actor: Actor) -> None: ) raise DeliveryError(msg) from e - def __init__(self: SelfClear, target: Target) -> None: + def __init__(self, target: Target) -> None: self.target = target diff --git a/screenpy_selenium/actions/click.py b/screenpy_selenium/actions/click.py index c9093ec..d261904 100644 --- a/screenpy_selenium/actions/click.py +++ b/screenpy_selenium/actions/click.py @@ -2,11 +2,12 @@ from __future__ import annotations -from typing import TYPE_CHECKING, TypeVar +from typing import TYPE_CHECKING from screenpy.exceptions import DeliveryError, UnableToAct from screenpy.pacing import beat from selenium.common.exceptions import WebDriverException +from typing_extensions import Self if TYPE_CHECKING: from screenpy.actor import Actor @@ -14,8 +15,6 @@ from ..target import Target -SelfClick = TypeVar("SelfClick", bound="Click") - class Click: """Click on an element! @@ -33,7 +32,7 @@ class Click: """ @classmethod - def on_the(cls: type[SelfClick], target: Target) -> SelfClick: + def on_the(cls, target: Target) -> Self: """ Target the element to click on. @@ -44,21 +43,21 @@ def on_the(cls: type[SelfClick], target: Target) -> SelfClick: return cls(target=target) @classmethod - def on(cls: type[SelfClick], target: Target) -> SelfClick: + def on(cls, target: Target) -> Self: """Alias for :meth:`~screenpy_selenium.actions.Click.on_the`.""" return cls.on_the(target=target) @classmethod - def on_the_first_of_the(cls: type[SelfClick], target: Target) -> SelfClick: + def on_the_first_of_the(cls, target: Target) -> Self: """Alias for :meth:`~screenpy_selenium.actions.Click.on_the`.""" return cls.on_the(target=target) - def describe(self: SelfClick) -> str: + def describe(self) -> str: """Describe the Action in present tense.""" return f"Click on the {self.target}." @beat("{} clicks on the {target}.") - def perform_as(self: SelfClick, the_actor: Actor) -> None: + def perform_as(self, the_actor: Actor) -> None: """Direct the Actor to click on the element.""" if self.target is None: msg = ( @@ -79,9 +78,7 @@ def perform_as(self: SelfClick, the_actor: Actor) -> None: raise DeliveryError(msg) from e @beat("Click{description}!") - def add_to_chain( - self: SelfClick, the_actor: Actor, the_chain: ActionChains - ) -> None: + def add_to_chain(self, the_actor: Actor, the_chain: ActionChains) -> None: """Add the Click Action to a Chain of Actions.""" if self.target is not None: the_element = self.target.found_by(the_actor) @@ -90,6 +87,6 @@ def add_to_chain( the_chain.click(on_element=the_element) - def __init__(self: SelfClick, target: Target | None = None) -> None: + def __init__(self, target: Target | None = None) -> None: self.target = target self.description = f" on the {target}" if target is not None else "" diff --git a/screenpy_selenium/actions/double_click.py b/screenpy_selenium/actions/double_click.py index c22728a..13c80fb 100644 --- a/screenpy_selenium/actions/double_click.py +++ b/screenpy_selenium/actions/double_click.py @@ -2,10 +2,11 @@ from __future__ import annotations -from typing import TYPE_CHECKING, TypeVar +from typing import TYPE_CHECKING from screenpy.pacing import beat from selenium.webdriver.common.action_chains import ActionChains +from typing_extensions import Self from ..abilities import BrowseTheWeb @@ -14,8 +15,6 @@ from ..target import Target -SelfDoubleClick = TypeVar("SelfDoubleClick", bound="DoubleClick") - class DoubleClick: """Double-click on an element, or wherever the cursor currently is. @@ -33,7 +32,7 @@ class DoubleClick: target: Target | None @classmethod - def on_the(cls: type[SelfDoubleClick], target: Target) -> SelfDoubleClick: + def on_the(cls, target: Target) -> Self: """ Target the element to double-click on. @@ -44,20 +43,16 @@ def on_the(cls: type[SelfDoubleClick], target: Target) -> SelfDoubleClick: return cls(target=target) @classmethod - def on(cls: type[SelfDoubleClick], target: Target) -> SelfDoubleClick: + def on(cls, target: Target) -> Self: """Alias for :meth:`~screenpy_selenium.actions.DoubleClick.on_the`.""" return cls.on_the(target=target) @classmethod - def on_the_first_of_the( - cls: type[SelfDoubleClick], target: Target - ) -> SelfDoubleClick: + def on_the_first_of_the(cls, target: Target) -> Self: """Alias for :meth:`~screenpy_selenium.actions.DoubleClick.on_the`.""" return cls.on_the(target=target) - def _add_action_to_chain( - self: SelfDoubleClick, the_actor: Actor, the_chain: ActionChains - ) -> None: + def _add_action_to_chain(self, the_actor: Actor, the_chain: ActionChains) -> None: """Private method to add this Action to the chain.""" if self.target is not None: the_element = self.target.found_by(the_actor) @@ -66,12 +61,12 @@ def _add_action_to_chain( the_chain.double_click(on_element=the_element) - def describe(self: SelfDoubleClick) -> str: + def describe(self) -> str: """Describe the Action in present tense.""" return f"Double-click{self.description}." @beat("{} double-clicks{description}.") - def perform_as(self: SelfDoubleClick, the_actor: Actor) -> None: + def perform_as(self, the_actor: Actor) -> None: """Direct the Actor to double-click on the element.""" browser = the_actor.ability_to(BrowseTheWeb).browser the_chain = ActionChains(browser) # type: ignore[arg-type] @@ -79,12 +74,10 @@ def perform_as(self: SelfDoubleClick, the_actor: Actor) -> None: the_chain.perform() @beat("Double-click{description}!") - def add_to_chain( - self: SelfDoubleClick, the_actor: Actor, the_chain: ActionChains - ) -> None: + def add_to_chain(self, the_actor: Actor, the_chain: ActionChains) -> None: """Add the DoubleClick Action to a Chain of Actions.""" self._add_action_to_chain(the_actor, the_chain) - def __init__(self: SelfDoubleClick, target: Target | None = None) -> None: + def __init__(self, target: Target | None = None) -> None: self.target = target self.description = f" on the {target}" if target is not None else "" diff --git a/screenpy_selenium/actions/enter.py b/screenpy_selenium/actions/enter.py index fcdba36..e9566eb 100644 --- a/screenpy_selenium/actions/enter.py +++ b/screenpy_selenium/actions/enter.py @@ -3,11 +3,12 @@ from __future__ import annotations from functools import partial -from typing import TYPE_CHECKING, TypeVar +from typing import TYPE_CHECKING from screenpy.exceptions import DeliveryError, UnableToAct from screenpy.pacing import aside, beat from selenium.common.exceptions import WebDriverException +from typing_extensions import Self from ..speech_tools import KEY_NAMES @@ -17,8 +18,6 @@ from ..target import Target -SelfEnter = TypeVar("SelfEnter", bound="Enter") - class Enter: """Enter text into an input field, or press specific keys. @@ -39,7 +38,7 @@ class Enter: text_to_log: str @classmethod - def the_text(cls: type[SelfEnter], text: str) -> SelfEnter: + def the_text(cls, text: str) -> Self: """Provide the text to enter into the field. Aliases: @@ -48,12 +47,12 @@ def the_text(cls: type[SelfEnter], text: str) -> SelfEnter: return cls(text=text) @classmethod - def the_keys(cls: type[SelfEnter], text: str) -> SelfEnter: + def the_keys(cls, text: str) -> Self: """Alias for :meth:`~screenpy_selenium.actions.Enter.the_text`.""" return cls.the_text(text=text) @classmethod - def the_secret(cls: type[SelfEnter], text: str) -> SelfEnter: + def the_secret(cls, text: str) -> Self: """ Provide the text to enter into the field, but mask it in logging. @@ -65,11 +64,11 @@ def the_secret(cls: type[SelfEnter], text: str) -> SelfEnter: return cls(text=text, mask=True) @classmethod - def the_password(cls: type[SelfEnter], text: str) -> SelfEnter: + def the_password(cls, text: str) -> Self: """Alias for :meth:`~screenpy_selenium.actions.Enter.the_secret`.""" return cls.the_secret(text=text) - def into_the(self: SelfEnter, target: Target) -> SelfEnter: + def into_the(self, target: Target) -> Self: """Target the element to enter text into. Aliases: @@ -80,19 +79,19 @@ def into_the(self: SelfEnter, target: Target) -> SelfEnter: self.target = target return self - def into(self: SelfEnter, target: Target) -> SelfEnter: + def into(self, target: Target) -> Self: """Alias for :meth:`~screenpy_selenium.actions.Enter.into_the`.""" return self.into_the(target) - def on(self: SelfEnter, target: Target) -> SelfEnter: + def on(self, target: Target) -> Self: """Alias for :meth:`~screenpy_selenium.actions.Enter.into_the`.""" return self.into_the(target) - def into_the_first_of_the(self: SelfEnter, target: Target) -> SelfEnter: + def into_the_first_of_the(self, target: Target) -> Self: """Alias for :meth:`~screenpy_selenium.actions.Enter.into_the`.""" return self.into_the(target) - def then_hit(self: SelfEnter, *keys: str) -> SelfEnter: + def then_hit(self, *keys: str) -> Self: """Supply additional keys to hit after entering the text. Args: @@ -105,16 +104,16 @@ def then_hit(self: SelfEnter, *keys: str) -> SelfEnter: self.following_keys.extend(keys) return self - def then_press(self: SelfEnter, *keys: str) -> SelfEnter: + def then_press(self, *keys: str) -> Self: """Alias for :meth:`~screenpy_selenium.actions.Enter.then_hit`.""" return self.then_hit(*keys) - def describe(self: SelfEnter) -> str: + def describe(self) -> str: """Describe the Action in present tense.""" return f'Enter "{self.text_to_log}" into the {self.target}.' @beat('{} enters "{text_to_log}" into the {target}.') - def perform_as(self: SelfEnter, the_actor: Actor) -> None: + def perform_as(self, the_actor: Actor) -> None: """Direct the Actor to enter the text into the element.""" if self.target is None: msg = ( @@ -138,9 +137,7 @@ def perform_as(self: SelfEnter, the_actor: Actor) -> None: raise DeliveryError(msg) from e @beat(' Enter "{text_to_log}" into the {target}!') - def add_to_chain( - self: SelfEnter, the_actor: Actor, the_chain: ActionChains - ) -> None: + def add_to_chain(self, the_actor: Actor, the_chain: ActionChains) -> None: """Add the Enter Action to a Chain of Actions.""" if self.target is None: send_keys = the_chain.send_keys @@ -152,7 +149,7 @@ def add_to_chain( for key in self.following_keys: send_keys(key) - def __init__(self: SelfEnter, text: str, mask: bool = False) -> None: + def __init__(self, text: str, mask: bool = False) -> None: self.text = text self.target = None self.following_keys = [] diff --git a/screenpy_selenium/actions/enter_2fa_token.py b/screenpy_selenium/actions/enter_2fa_token.py index c490e01..e7191a0 100644 --- a/screenpy_selenium/actions/enter_2fa_token.py +++ b/screenpy_selenium/actions/enter_2fa_token.py @@ -2,10 +2,11 @@ from __future__ import annotations -from typing import TYPE_CHECKING, TypeVar +from typing import TYPE_CHECKING from screenpy.pacing import beat from screenpy_pyotp.abilities import AuthenticateWith2FA +from typing_extensions import Self from .enter import Enter @@ -15,8 +16,6 @@ from ..target import Target -SelfEnter2FAToken = TypeVar("SelfEnter2FAToken", bound="Enter2FAToken") - class Enter2FAToken: """Enter the current two-factor authentication token into an input field. @@ -31,7 +30,7 @@ class Enter2FAToken: """ @classmethod - def into_the(cls: type[SelfEnter2FAToken], target: Target) -> SelfEnter2FAToken: + def into_the(cls, target: Target) -> Self: """ Target the element into which to enter the 2FA token. @@ -41,7 +40,7 @@ def into_the(cls: type[SelfEnter2FAToken], target: Target) -> SelfEnter2FAToken: return cls(target) @classmethod - def into(cls: type[SelfEnter2FAToken], target: Target) -> SelfEnter2FAToken: + def into(cls, target: Target) -> Self: """Alias for :meth:`~screenpy_selenium.actions.Enter2FAToken.into_the`.""" return cls.into_the(target=target) @@ -50,18 +49,16 @@ def describe(self) -> str: return f"Enter a 2FA token into the {self.target}." @beat("{} enters their 2FA token into the {target}.") - def perform_as(self: SelfEnter2FAToken, the_actor: Actor) -> None: + def perform_as(self, the_actor: Actor) -> None: """Direct the Actor to enter their 2FA token into the element.""" token = the_actor.uses_ability_to(AuthenticateWith2FA).to_get_token() the_actor.attempts_to(Enter.the_text(token).into_the(self.target)) @beat("Enter their 2FA token into the {target}!") - def add_to_chain( - self: SelfEnter2FAToken, the_actor: Actor, the_chain: ActionChains - ) -> None: + def add_to_chain(self, the_actor: Actor, the_chain: ActionChains) -> None: """Add the Enter2FAToken Action to a Chain of Actions.""" token = the_actor.uses_ability_to(AuthenticateWith2FA).to_get_token() the_chain.send_keys_to_element(self.target.found_by(the_actor), token) - def __init__(self: SelfEnter2FAToken, target: Target) -> None: + def __init__(self, target: Target) -> None: self.target = target diff --git a/screenpy_selenium/actions/hold_down.py b/screenpy_selenium/actions/hold_down.py index 9acbac5..4aae06b 100644 --- a/screenpy_selenium/actions/hold_down.py +++ b/screenpy_selenium/actions/hold_down.py @@ -3,11 +3,12 @@ from __future__ import annotations import platform -from typing import TYPE_CHECKING, TypeVar +from typing import TYPE_CHECKING from screenpy.exceptions import UnableToAct from screenpy.pacing import beat from selenium.webdriver.common.keys import Keys +from typing_extensions import Self from ..speech_tools import KEY_NAMES @@ -17,8 +18,6 @@ from ..target import Target -SelfHoldDown = TypeVar("SelfHoldDown", bound="HoldDown") - class HoldDown: """Hold down the specified key or left mouse button. @@ -47,7 +46,7 @@ class HoldDown: description: str @classmethod - def command_or_control_key(cls: type[SelfHoldDown]) -> SelfHoldDown: + def command_or_control_key(cls) -> Self: """ A convenience method for supporting multiple operating systems. @@ -59,25 +58,23 @@ def command_or_control_key(cls: type[SelfHoldDown]) -> SelfHoldDown: return cls(Keys.CONTROL) @classmethod - def left_mouse_button(cls: type[SelfHoldDown]) -> SelfHoldDown: + def left_mouse_button(cls) -> Self: """Hold down the left mouse button.""" return cls(lmb=True) - def on_the(self: SelfHoldDown, target: Target) -> SelfHoldDown: + def on_the(self, target: Target) -> Self: """Target an element to hold down left click on.""" self.target = target return self on = on_the - def describe(self: SelfHoldDown) -> str: + def describe(self) -> str: """Describe the Action in present tense.""" return f"Hold down {self.description}." @beat("Hold down {description}!") - def add_to_chain( - self: SelfHoldDown, the_actor: Actor, the_chain: ActionChains - ) -> None: + def add_to_chain(self, the_actor: Actor, the_chain: ActionChains) -> None: """Add the HoldDown Action to a Chain of Actions.""" if self.lmb: element = self.target.found_by(the_actor) if self.target else None @@ -88,7 +85,7 @@ def add_to_chain( msg = "HoldDown must be told what to hold down." raise UnableToAct(msg) - def __init__(self: SelfHoldDown, key: str | None = None, lmb: bool = False) -> None: + def __init__(self, key: str | None = None, lmb: bool = False) -> None: self.key = key self.lmb = lmb self.target = None diff --git a/screenpy_selenium/actions/move_mouse.py b/screenpy_selenium/actions/move_mouse.py index 78152ed..4126760 100644 --- a/screenpy_selenium/actions/move_mouse.py +++ b/screenpy_selenium/actions/move_mouse.py @@ -2,11 +2,12 @@ from __future__ import annotations -from typing import TYPE_CHECKING, TypeVar +from typing import TYPE_CHECKING from screenpy.exceptions import UnableToAct from screenpy.pacing import beat from selenium.webdriver.common.action_chains import ActionChains +from typing_extensions import Self from ..abilities import BrowseTheWeb @@ -15,8 +16,6 @@ from ..target import Target -SelfMoveMouse = TypeVar("SelfMoveMouse", bound="MoveMouse") - class MoveMouse: """Move the mouse to a specific element or by a pixel offset. @@ -48,7 +47,7 @@ class MoveMouse: description: str @classmethod - def to_the(cls: type[SelfMoveMouse], target: Target) -> SelfMoveMouse: + def to_the(cls, target: Target) -> Self: """ Target an element to move the mouse to. @@ -61,46 +60,40 @@ def to_the(cls: type[SelfMoveMouse], target: Target) -> SelfMoveMouse: return cls(target=target, description=f"to the {target}") @classmethod - def on_the(cls: type[SelfMoveMouse], target: Target) -> SelfMoveMouse: + def on_the(cls, target: Target) -> Self: """Alias for :meth:`~screenpy_selenium.actions.MoveMouse.to_the`.""" return cls.to_the(target=target) @classmethod - def over_the(cls: type[SelfMoveMouse], target: Target) -> SelfMoveMouse: + def over_the(cls, target: Target) -> Self: """Alias for :meth:`~screenpy_selenium.actions.MoveMouse.to_the`.""" return cls.to_the(target=target) @classmethod - def over_the_first_of_the( - cls: type[SelfMoveMouse], target: Target - ) -> SelfMoveMouse: + def over_the_first_of_the(cls, target: Target) -> Self: """Alias for :meth:`~screenpy_selenium.actions.MoveMouse.to_the`.""" return cls.to_the(target=target) @classmethod - def to_the_first_of_the(cls: type[SelfMoveMouse], target: Target) -> SelfMoveMouse: + def to_the_first_of_the(cls, target: Target) -> Self: """Alias for :meth:`~screenpy_selenium.actions.MoveMouse.to_the`.""" return cls.to_the(target=target) @classmethod - def by_offset( - cls: type[SelfMoveMouse], x_offset: int, y_offset: int - ) -> SelfMoveMouse: + def by_offset(cls, x_offset: int, y_offset: int) -> Self: """Specify the offset by which to move the mouse.""" return cls( offset=(x_offset, y_offset), description=f"by an offset of ({x_offset}, {y_offset})", ) - def with_offset(self: SelfMoveMouse, x_offset: int, y_offset: int) -> SelfMoveMouse: + def with_offset(self, x_offset: int, y_offset: int) -> Self: """Specify the mouse should be moved to the element with an offset.""" self.offset = (x_offset, y_offset) self.description += f" offset by ({x_offset}, {y_offset})" return self - def _add_action_to_chain( - self: SelfMoveMouse, the_actor: Actor, the_chain: ActionChains - ) -> None: + def _add_action_to_chain(self, the_actor: Actor, the_chain: ActionChains) -> None: """Private method to add this Action to the chain.""" if self.target is not None and self.offset is not None: the_chain.move_to_element_with_offset( @@ -117,12 +110,12 @@ def _add_action_to_chain( ) raise UnableToAct(msg) - def describe(self: SelfMoveMouse) -> str: + def describe(self) -> str: """Describe the Action in present tense.""" return f"Move the mouse {self.description}." @beat("{} moves the mouse {description}.") - def perform_as(self: SelfMoveMouse, the_actor: Actor) -> None: + def perform_as(self, the_actor: Actor) -> None: """Direct the Actor to move the mouse.""" browser = the_actor.ability_to(BrowseTheWeb).browser the_chain = ActionChains(browser) # type: ignore[arg-type] @@ -130,14 +123,12 @@ def perform_as(self: SelfMoveMouse, the_actor: Actor) -> None: the_chain.perform() @beat("Move the mouse {description}!") - def add_to_chain( - self: SelfMoveMouse, the_actor: Actor, the_chain: ActionChains - ) -> None: + def add_to_chain(self, the_actor: Actor, the_chain: ActionChains) -> None: """Add the MoveMouse Action to a Chain of Actions.""" self._add_action_to_chain(the_actor, the_chain) def __init__( - self: SelfMoveMouse, + self, offset: tuple[int, int] | None = None, target: Target | None = None, description: str = "", diff --git a/screenpy_selenium/actions/release.py b/screenpy_selenium/actions/release.py index d7afc04..628ab31 100644 --- a/screenpy_selenium/actions/release.py +++ b/screenpy_selenium/actions/release.py @@ -3,11 +3,12 @@ from __future__ import annotations import platform -from typing import TYPE_CHECKING, TypeVar +from typing import TYPE_CHECKING from screenpy.exceptions import UnableToAct from screenpy.pacing import beat from selenium.webdriver.common.keys import Keys +from typing_extensions import Self from ..speech_tools import KEY_NAMES @@ -15,8 +16,6 @@ from screenpy import Actor from selenium.webdriver.common.action_chains import ActionChains -SelfRelease = TypeVar("SelfRelease", bound="Release") - class Release: """Release the specified key or left mouse button. @@ -43,7 +42,7 @@ class Release: the_kraken: str @classmethod - def command_or_control_key(cls: type[SelfRelease]) -> SelfRelease: + def command_or_control_key(cls) -> Self: """ A convenience method for supporting multiple operating systems. @@ -55,17 +54,17 @@ def command_or_control_key(cls: type[SelfRelease]) -> SelfRelease: return cls(key=Keys.CONTROL) @classmethod - def left_mouse_button(cls: type[SelfRelease]) -> SelfRelease: + def left_mouse_button(cls) -> Self: """Release the left mouse button.""" return cls(lmb=True) - def describe(self: SelfRelease) -> str: + def describe(self) -> str: """Describe the Action in present tense.""" # darn, it doesn't work quite as well here. :P return f"Release {self.the_kraken}." @beat("Release {the_kraken}!") - def add_to_chain(self: SelfRelease, _: Actor, the_chain: ActionChains) -> None: + def add_to_chain(self, _: Actor, the_chain: ActionChains) -> None: """Add the Release Action to a Chain of Actions.""" if self.lmb: the_chain.release() @@ -75,7 +74,7 @@ def add_to_chain(self: SelfRelease, _: Actor, the_chain: ActionChains) -> None: msg = "Release must be told what to release." raise UnableToAct(msg) - def __init__(self: SelfRelease, key: str | None = None, lmb: bool = False) -> None: + def __init__(self, key: str | None = None, lmb: bool = False) -> None: self.key = key self.lmb = lmb self.description = "LEFT MOUSE BUTTON" if lmb else KEY_NAMES[key] diff --git a/screenpy_selenium/actions/respond_to_the_prompt.py b/screenpy_selenium/actions/respond_to_the_prompt.py index 9d201de..bdfac53 100644 --- a/screenpy_selenium/actions/respond_to_the_prompt.py +++ b/screenpy_selenium/actions/respond_to_the_prompt.py @@ -2,17 +2,16 @@ from __future__ import annotations -from typing import TYPE_CHECKING, TypeVar +from typing import TYPE_CHECKING from screenpy.pacing import aside, beat +from typing_extensions import Self from ..abilities import BrowseTheWeb if TYPE_CHECKING: from screenpy.actor import Actor -SelfRespondToThePrompt = TypeVar("SelfRespondToThePrompt", bound="RespondToThePrompt") - class RespondToThePrompt: """Enter text into and accept a javascript prompt. @@ -28,16 +27,16 @@ class RespondToThePrompt: """ @classmethod - def with_(cls: type[SelfRespondToThePrompt], text: str) -> SelfRespondToThePrompt: + def with_(cls, text: str) -> Self: """Provide the text to enter into the prompt.""" return cls(text) - def describe(self: SelfRespondToThePrompt) -> str: + def describe(self) -> str: """Describe the Action in present tense.""" return f'Respond to the prompt with "{self.text}".' @beat('{} responds to the prompt with "{text}".') - def perform_as(self: SelfRespondToThePrompt, the_actor: Actor) -> None: + def perform_as(self, the_actor: Actor) -> None: """Direct the Actor to respond to the prompt using the given text.""" browser = the_actor.uses_ability_to(BrowseTheWeb).browser alert = browser.switch_to.alert @@ -45,5 +44,5 @@ def perform_as(self: SelfRespondToThePrompt, the_actor: Actor) -> None: alert.send_keys(self.text) alert.accept() - def __init__(self: SelfRespondToThePrompt, text: str) -> None: + def __init__(self, text: str) -> None: self.text = text diff --git a/screenpy_selenium/actions/right_click.py b/screenpy_selenium/actions/right_click.py index d915370..6da0c9a 100644 --- a/screenpy_selenium/actions/right_click.py +++ b/screenpy_selenium/actions/right_click.py @@ -2,10 +2,11 @@ from __future__ import annotations -from typing import TYPE_CHECKING, TypeVar +from typing import TYPE_CHECKING from screenpy.pacing import beat from selenium.webdriver.common.action_chains import ActionChains +from typing_extensions import Self from ..abilities import BrowseTheWeb @@ -14,8 +15,6 @@ from ..target import Target -SelfRightClick = TypeVar("SelfRightClick", bound="RightClick") - class RightClick: """Right-click on an element, or wherever the cursor currently is. @@ -38,7 +37,7 @@ class RightClick: target: Target | None @classmethod - def on_the(cls: type[SelfRightClick], target: Target) -> SelfRightClick: + def on_the(cls, target: Target) -> Self: """Target an element to right-click on. Aliases: @@ -48,20 +47,16 @@ def on_the(cls: type[SelfRightClick], target: Target) -> SelfRightClick: return cls(target=target) @classmethod - def on(cls: type[SelfRightClick], target: Target) -> SelfRightClick: + def on(cls, target: Target) -> Self: """Alias for :meth:`~screenpy_selenium.actions.RightClick.on_the`.""" return cls.on_the(target=target) @classmethod - def on_the_first_of_the( - cls: type[SelfRightClick], target: Target - ) -> SelfRightClick: + def on_the_first_of_the(cls, target: Target) -> Self: """Alias for :meth:`~screenpy_selenium.actions.RightClick.on_the`.""" return cls.on_the(target=target) - def _add_action_to_chain( - self: SelfRightClick, the_actor: Actor, the_chain: ActionChains - ) -> None: + def _add_action_to_chain(self, the_actor: Actor, the_chain: ActionChains) -> None: """Private method to add this Action to the chain.""" if self.target is not None: the_element = self.target.found_by(the_actor) @@ -70,12 +65,12 @@ def _add_action_to_chain( the_chain.context_click(on_element=the_element) - def describe(self: SelfRightClick) -> str: + def describe(self) -> str: """Describe the Action in present tense.""" return f"Right-click{self.description}." @beat("{} right-clicks{description}.") - def perform_as(self: SelfRightClick, the_actor: Actor) -> None: + def perform_as(self, the_actor: Actor) -> None: """Direct the Actor to right-click on the element.""" browser = the_actor.ability_to(BrowseTheWeb).browser the_chain = ActionChains(browser) # type: ignore[arg-type] @@ -83,12 +78,10 @@ def perform_as(self: SelfRightClick, the_actor: Actor) -> None: the_chain.perform() @beat("Right-click{description}!") - def add_to_chain( - self: SelfRightClick, the_actor: Actor, the_chain: ActionChains - ) -> None: + def add_to_chain(self, the_actor: Actor, the_chain: ActionChains) -> None: """Add the RightClick Action to a Chain of Actions.""" self._add_action_to_chain(the_actor, the_chain) - def __init__(self: SelfRightClick, target: Target | None = None) -> None: + def __init__(self, target: Target | None = None) -> None: self.target = target self.description = f" on the {target}" if target is not None else "" diff --git a/screenpy_selenium/actions/save_console_log.py b/screenpy_selenium/actions/save_console_log.py index fad9612..cdfb493 100644 --- a/screenpy_selenium/actions/save_console_log.py +++ b/screenpy_selenium/actions/save_console_log.py @@ -3,18 +3,17 @@ from __future__ import annotations import os -from typing import TYPE_CHECKING, Any, TypeVar +from typing import TYPE_CHECKING, Any from screenpy.actions import AttachTheFile from screenpy.pacing import beat +from typing_extensions import Self from ..abilities import BrowseTheWeb if TYPE_CHECKING: from screenpy import Actor -SelfSaveConsoleLog = TypeVar("SelfSaveConsoleLog", bound="SaveConsoleLog") - class SaveConsoleLog: """Save the Actor's browser's console log. @@ -53,12 +52,12 @@ class SaveConsoleLog: path: str filename: str - def describe(self: SelfSaveConsoleLog) -> str: + def describe(self) -> str: """Describe the Action in present tense.""" return f"Save browser console log as {self.filename}" @classmethod - def as_(cls: type[SelfSaveConsoleLog], path: str) -> SelfSaveConsoleLog: + def as_(cls, path: str) -> Self: """Supply the name and/or filepath for the saved text file. If only a name is supplied, the text file will appear in the current @@ -66,9 +65,7 @@ def as_(cls: type[SelfSaveConsoleLog], path: str) -> SelfSaveConsoleLog: """ return cls(path=path) - def and_attach_it( - self: SelfSaveConsoleLog, **kwargs: Any # noqa: ANN401 - ) -> SelfSaveConsoleLog: + def and_attach_it(self, **kwargs: Any) -> Self: # noqa: ANN401 """Indicate the console log file should be attached to any reports. This method accepts any additional keywords needed by any adapters @@ -80,7 +77,7 @@ def and_attach_it( and_attach_it_with = and_attach_it @beat("{} saves their browser's console log as {filename}") - def perform_as(self: SelfSaveConsoleLog, the_actor: Actor) -> None: + def perform_as(self, the_actor: Actor) -> None: """Direct the actor to save their browser's console log.""" browser = the_actor.ability_to(BrowseTheWeb).browser js_log = "\n".join([str(entry) for entry in browser.get_log("browser")]) @@ -91,7 +88,7 @@ def perform_as(self: SelfSaveConsoleLog, the_actor: Actor) -> None: if self.attach_kwargs is not None: the_actor.attempts_to(AttachTheFile(self.path, **self.attach_kwargs)) - def __init__(self: SelfSaveConsoleLog, path: str) -> None: + def __init__(self, path: str) -> None: self.path = path self.filename = path.split(os.path.sep)[-1] self.attach_kwargs = None diff --git a/screenpy_selenium/actions/save_screenshot.py b/screenpy_selenium/actions/save_screenshot.py index d582a4e..5812415 100644 --- a/screenpy_selenium/actions/save_screenshot.py +++ b/screenpy_selenium/actions/save_screenshot.py @@ -3,18 +3,17 @@ from __future__ import annotations import os -from typing import TYPE_CHECKING, Any, TypeVar +from typing import TYPE_CHECKING, Any from screenpy.actions import AttachTheFile from screenpy.pacing import beat +from typing_extensions import Self from ..abilities import BrowseTheWeb if TYPE_CHECKING: from screenpy import Actor -SelfSaveScreenshot = TypeVar("SelfSaveScreenshot", bound="SaveScreenshot") - class SaveScreenshot: """Save a screenshot from the actor's browser. @@ -49,12 +48,12 @@ class SaveScreenshot: path: str filename: str - def describe(self: SelfSaveScreenshot) -> str: + def describe(self) -> str: """Describe the Action in present tense.""" return f"Save screenshot as {self.filename}" @classmethod - def as_(cls: type[SelfSaveScreenshot], path: str) -> SelfSaveScreenshot: + def as_(cls, path: str) -> Self: """Supply the name and/or filepath for the screenshot. If only a name is supplied, the screenshot will appear in the current @@ -62,9 +61,7 @@ def as_(cls: type[SelfSaveScreenshot], path: str) -> SelfSaveScreenshot: """ return cls(path=path) - def and_attach_it( - self: SelfSaveScreenshot, **kwargs: Any # noqa: ANN401 - ) -> SelfSaveScreenshot: + def and_attach_it(self, **kwargs: Any) -> Self: # noqa: ANN401 """Indicate the screenshot should be attached to any reports. This method accepts any additional keywords needed by any adapters @@ -76,7 +73,7 @@ def and_attach_it( and_attach_it_with = and_attach_it @beat("{} saves a screenshot as {filename}") - def perform_as(self: SelfSaveScreenshot, the_actor: Actor) -> None: + def perform_as(self, the_actor: Actor) -> None: """Direct the actor to save a screenshot.""" browser = the_actor.ability_to(BrowseTheWeb).browser screenshot = browser.get_screenshot_as_png() @@ -87,7 +84,7 @@ def perform_as(self: SelfSaveScreenshot, the_actor: Actor) -> None: if self.attach_kwargs is not None: the_actor.attempts_to(AttachTheFile(self.path, **self.attach_kwargs)) - def __init__(self: SelfSaveScreenshot, path: str) -> None: + def __init__(self, path: str) -> None: self.path = path self.filename = path.split(os.path.sep)[-1] self.attach_kwargs = None diff --git a/screenpy_selenium/actions/select.py b/screenpy_selenium/actions/select.py index 2c70a0b..fb58916 100644 --- a/screenpy_selenium/actions/select.py +++ b/screenpy_selenium/actions/select.py @@ -2,22 +2,19 @@ from __future__ import annotations -from typing import TYPE_CHECKING, TypeVar +from typing import TYPE_CHECKING from screenpy.exceptions import DeliveryError, UnableToAct from screenpy.pacing import beat from selenium.common.exceptions import WebDriverException from selenium.webdriver.support.ui import Select as SeleniumSelect +from typing_extensions import Self if TYPE_CHECKING: from screenpy import Actor from ..target import Target -SelfSelectByText = TypeVar("SelfSelectByText", bound="SelectByText") -SelfSelectByIndex = TypeVar("SelfSelectByIndex", bound="SelectByIndex") -SelfSelectByValue = TypeVar("SelfSelectByValue", bound="SelectByValue") - class Select: """Select an option from a dropdown menu. @@ -72,19 +69,19 @@ class SelectByText: target: Target | None text: str - def from_the(self: SelfSelectByText, target: Target) -> SelfSelectByText: + def from_the(self, target: Target) -> Self: """Target the dropdown or multi-select field to select the option from.""" self.target = target return self from_ = from_the_first_of_the = from_the - def describe(self: SelfSelectByText) -> str: + def describe(self) -> str: """Describe the Action in present tense.""" return f'Select the option "{self.text}" from the {self.target}.' @beat('{} selects the option "{text}" from the {target}.') - def perform_as(self: SelfSelectByText, the_actor: Actor) -> None: + def perform_as(self, the_actor: Actor) -> None: """Direct the Actor to select the option by its text.""" if self.target is None: msg = ( @@ -104,9 +101,7 @@ def perform_as(self: SelfSelectByText, the_actor: Actor) -> None: ) raise DeliveryError(msg) from e - def __init__( - self: SelfSelectByText, text: str, target: Target | None = None - ) -> None: + def __init__(self, text: str, target: Target | None = None) -> None: self.target = target self.text = text @@ -124,19 +119,19 @@ class SelectByIndex: target: Target | None index: int - def from_the(self: SelfSelectByIndex, target: Target) -> SelfSelectByIndex: + def from_the(self, target: Target) -> Self: """Target the dropdown or multi-select field to select the option from.""" self.target = target return self from_ = from_the_first_of_the = from_the - def describe(self: SelfSelectByIndex) -> str: + def describe(self) -> str: """Describe the Action in present tense.""" return f"Select the option at index {self.index} from the {self.target}." @beat("{} selects the option at index {index} from the {target}.") - def perform_as(self: SelfSelectByIndex, the_actor: Actor) -> None: + def perform_as(self, the_actor: Actor) -> None: """Direct the Actor to select the option using its index.""" if self.target is None: msg = ( @@ -156,9 +151,7 @@ def perform_as(self: SelfSelectByIndex, the_actor: Actor) -> None: ) raise DeliveryError(msg) from e - def __init__( - self: SelfSelectByIndex, index: int | str, target: Target | None = None - ) -> None: + def __init__(self, index: int | str, target: Target | None = None) -> None: self.target = target self.index = int(index) @@ -176,19 +169,19 @@ class SelectByValue: target: Target | None value: str - def from_the(self: SelfSelectByValue, target: Target) -> SelfSelectByValue: + def from_the(self, target: Target) -> Self: """Target the dropdown or multi-select field to select the option from.""" self.target = target return self from_ = from_the_first_of_the = from_the - def describe(self: SelfSelectByValue) -> str: + def describe(self) -> str: """Describe the Action in present tense.""" return f'Select the option with value "{self.value}" from the {self.target}.' @beat('{} selects the option with value "{value}" from the {target}.') - def perform_as(self: SelfSelectByValue, the_actor: Actor) -> None: + def perform_as(self, the_actor: Actor) -> None: """Direct the Actor to select the option by its value.""" if self.target is None: msg = ( @@ -208,8 +201,6 @@ def perform_as(self: SelfSelectByValue, the_actor: Actor) -> None: ) raise DeliveryError(msg) from e - def __init__( - self: SelfSelectByValue, value: int | str, target: Target | None = None - ) -> None: + def __init__(self, value: int | str, target: Target | None = None) -> None: self.target = target self.value = str(value) diff --git a/screenpy_selenium/actions/switch_to.py b/screenpy_selenium/actions/switch_to.py index eecde46..8862fb7 100644 --- a/screenpy_selenium/actions/switch_to.py +++ b/screenpy_selenium/actions/switch_to.py @@ -2,9 +2,10 @@ from __future__ import annotations -from typing import TYPE_CHECKING, TypeVar +from typing import TYPE_CHECKING from screenpy.pacing import beat +from typing_extensions import Self from ..abilities import BrowseTheWeb @@ -13,8 +14,6 @@ from ..target import Target -SelfSwitchTo = TypeVar("SelfSwitchTo", bound="SwitchTo") - class SwitchTo: """Switch to an element, most likely an iframe, or back to default. @@ -32,21 +31,21 @@ class SwitchTo: """ @classmethod - def the(cls: type[SelfSwitchTo], target: Target) -> SelfSwitchTo: + def the(cls, target: Target) -> Self: """Target an element, probably an iframe, to switch to.""" return cls(target=target, frame_to_log=str(target)) @classmethod - def default(cls: type[SelfSwitchTo]) -> SelfSwitchTo: + def default(cls) -> Self: """Switch back to the default frame, the browser window.""" return cls(target=None, frame_to_log="default frame") - def describe(self: SelfSwitchTo) -> str: + def describe(self) -> str: """Describe the Action in present tense.""" return f"Switch to the {self.frame_to_log}." @beat("{} switches to the {frame_to_log}.") - def perform_as(self: SelfSwitchTo, the_actor: Actor) -> None: + def perform_as(self, the_actor: Actor) -> None: """Direct the Actor to switch to an element or back to default.""" browser = the_actor.ability_to(BrowseTheWeb).browser if self.target is None: @@ -54,6 +53,6 @@ def perform_as(self: SelfSwitchTo, the_actor: Actor) -> None: else: browser.switch_to.frame(self.target.found_by(the_actor)) - def __init__(self: SelfSwitchTo, target: Target | None, frame_to_log: str) -> None: + def __init__(self, target: Target | None, frame_to_log: str) -> None: self.target = target self.frame_to_log = frame_to_log diff --git a/screenpy_selenium/actions/wait.py b/screenpy_selenium/actions/wait.py index 94ca3a6..94fd604 100644 --- a/screenpy_selenium/actions/wait.py +++ b/screenpy_selenium/actions/wait.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, Iterable, TypeVar +from typing import TYPE_CHECKING, Any, Callable, Iterable from screenpy import Actor, settings from screenpy.exceptions import DeliveryError @@ -10,14 +10,13 @@ from selenium.common.exceptions import WebDriverException from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait +from typing_extensions import Self from ..abilities import BrowseTheWeb if TYPE_CHECKING: from ..target import Target -SelfWait = TypeVar("SelfWait", bound="Wait") - class Wait: """Wait for the application to fulfill a given condition. @@ -55,7 +54,7 @@ class Wait: log_detail: str | None @classmethod - def for_the(cls: type[SelfWait], target: Target) -> SelfWait: + def for_the(cls, target: Target) -> Self: """Set the Target to wait for. Aliases: @@ -64,11 +63,11 @@ def for_the(cls: type[SelfWait], target: Target) -> SelfWait: return cls(seconds=settings.TIMEOUT, args=[target]) @classmethod - def for_(cls: type[SelfWait], target: Target) -> SelfWait: + def for_(cls, target: Target) -> Self: """Alias for :meth:`~screenpy_selenium.actions.Wait.for_the`.""" return cls.for_the(target=target) - def seconds_for_the(self: SelfWait, target: Target) -> SelfWait: + def seconds_for_the(self, target: Target) -> Self: """Set the Target to wait for, after changing the default timeout.""" self.args = [target] return self @@ -76,8 +75,8 @@ def seconds_for_the(self: SelfWait, target: Target) -> SelfWait: second_for = second_for_the = seconds_for = seconds_for_the def using( - self: SelfWait, strategy: Callable[..., Any], log_detail: str | None = None - ) -> SelfWait: + self, strategy: Callable[..., Any], log_detail: str | None = None + ) -> Self: """Use the given strategy to wait for the Target. Args: @@ -94,45 +93,45 @@ def using( to = seconds_using = using - def with_(self: SelfWait, *args: Any) -> SelfWait: # noqa: ANN401 + def with_(self, *args: Any) -> Self: # noqa: ANN401 """Set the arguments to pass in to the wait condition.""" self.args = args return self - def to_appear(self: SelfWait) -> SelfWait: + def to_appear(self) -> Self: """Use Selenium's "visibility of element located" strategy.""" return self.using(EC.visibility_of_element_located, "for the {0} to appear...") - def to_be_clickable(self: SelfWait) -> SelfWait: + def to_be_clickable(self) -> Self: """Use Selenium's "to be clickable" strategy.""" return self.using(EC.element_to_be_clickable, "for the {0} to be clickable...") - def to_disappear(self: SelfWait) -> SelfWait: + def to_disappear(self) -> Self: """Use Selenium's "invisibility of element located" strategy.""" return self.using( EC.invisibility_of_element_located, "for the {0} to disappear..." ) - def to_contain_text(self: SelfWait, text: str) -> SelfWait: + def to_contain_text(self, text: str) -> Self: """Use Selenium's "text to be present in element" strategy.""" return self.using( EC.text_to_be_present_in_element, 'for "{1}" to appear in the {0}...' ).with_(*self.args, text) @property - def log_message(self: SelfWait) -> str: + def log_message(self) -> str: """Format the nice log message, or give back the default.""" if self.log_detail is None: return f"using {self.condition.__name__} with {self.args}" return self.log_detail.format(*self.args) - def describe(self: SelfWait) -> str: + def describe(self) -> str: """Describe the Action in present tense.""" return f"Wait {self.timeout} seconds {self.log_message}." @beat("{} waits up to {timeout} seconds {log_message}") - def perform_as(self: SelfWait, the_actor: Actor) -> None: + def perform_as(self, the_actor: Actor) -> None: """Direct the Actor to wait for the condition to be satisfied.""" browser = the_actor.ability_to(BrowseTheWeb).browser @@ -148,9 +147,7 @@ def perform_as(self: SelfWait, the_actor: Actor) -> None: raise DeliveryError(msg) from e def __init__( - self: SelfWait, - seconds: float | None = None, - args: Iterable[Any] | None = None, + self, seconds: float | None = None, args: Iterable[Any] | None = None ) -> None: self.args = args if args is not None else [] self.timeout = seconds if seconds is not None else settings.TIMEOUT diff --git a/screenpy_selenium/questions/list.py b/screenpy_selenium/questions/list.py index d6af340..a694f08 100644 --- a/screenpy_selenium/questions/list.py +++ b/screenpy_selenium/questions/list.py @@ -2,9 +2,10 @@ from __future__ import annotations -from typing import TYPE_CHECKING, TypeVar +from typing import TYPE_CHECKING from screenpy.pacing import beat +from typing_extensions import Self if TYPE_CHECKING: from screenpy import Actor @@ -12,8 +13,6 @@ from ..target import Target -SelfList = TypeVar("SelfList", bound="List") - class List: """Ask for a list of elements. @@ -27,7 +26,7 @@ class List: """ @classmethod - def of_the(cls: type[SelfList], target: Target) -> SelfList: + def of_the(cls, target: Target) -> Self: """Target the element(s) to list. Aliases: @@ -38,28 +37,28 @@ def of_the(cls: type[SelfList], target: Target) -> SelfList: return cls(target=target) @classmethod - def of_all_the(cls: type[SelfList], target: Target) -> SelfList: + def of_all_the(cls, target: Target) -> Self: """Alias for :meth:`~screenpy_selenium.actions.List.of_the`.""" return cls.of_the(target=target) @classmethod - def of_all(cls: type[SelfList], target: Target) -> SelfList: + def of_all(cls, target: Target) -> Self: """Alias for :meth:`~screenpy_selenium.actions.List.of_the`.""" return cls.of_the(target=target) @classmethod - def of(cls: type[SelfList], target: Target) -> SelfList: + def of(cls, target: Target) -> Self: """Alias for :meth:`~screenpy_selenium.actions.List.of_the`.""" return cls.of_the(target=target) - def describe(self: SelfList) -> str: + def describe(self) -> str: """Describe the Question.""" return f"The list of {self.target}." @beat("{} lists off the {target}.") - def answered_by(self: SelfList, the_actor: Actor) -> list[WebElement]: + def answered_by(self, the_actor: Actor) -> list[WebElement]: """Direct the Actor to rattle off the specified elements.""" return self.target.all_found_by(the_actor) - def __init__(self: SelfList, target: Target) -> None: + def __init__(self, target: Target) -> None: self.target = target diff --git a/screenpy_selenium/questions/number.py b/screenpy_selenium/questions/number.py index 20c3976..201c5bb 100644 --- a/screenpy_selenium/questions/number.py +++ b/screenpy_selenium/questions/number.py @@ -2,17 +2,16 @@ from __future__ import annotations -from typing import TYPE_CHECKING, TypeVar +from typing import TYPE_CHECKING from screenpy.pacing import beat +from typing_extensions import Self if TYPE_CHECKING: from screenpy import Actor from ..target import Target -SelfNumber = TypeVar("SelfNumber", bound="Number") - class Number: """Ask how many of a certain element are on the page. @@ -26,18 +25,18 @@ class Number: """ @classmethod - def of(cls: type[SelfNumber], target: Target) -> SelfNumber: + def of(cls, target: Target) -> Self: """Target the element to be counted.""" return cls(target=target) - def describe(self: SelfNumber) -> str: + def describe(self) -> str: """Describe the Question.""" return f"The number of {self.target}." @beat("{} counts the number of {target}.") - def answered_by(self: SelfNumber, the_actor: Actor) -> int: + def answered_by(self, the_actor: Actor) -> int: """Direct the Actor to count the elements.""" return len(self.target.all_found_by(the_actor)) - def __init__(self: SelfNumber, target: Target) -> None: + def __init__(self, target: Target) -> None: self.target = target diff --git a/screenpy_selenium/questions/selected.py b/screenpy_selenium/questions/selected.py index 026f36f..ecd2d29 100644 --- a/screenpy_selenium/questions/selected.py +++ b/screenpy_selenium/questions/selected.py @@ -2,18 +2,17 @@ from __future__ import annotations -from typing import TYPE_CHECKING, TypeVar +from typing import TYPE_CHECKING from screenpy.pacing import beat from selenium.webdriver.support.ui import Select as SeleniumSelect +from typing_extensions import Self if TYPE_CHECKING: from screenpy import Actor from ..target import Target -SelfSelected = TypeVar("SelfSelected", bound="Selected") - class Selected: """Ask for the text of selected option(s) in a dropdown or multi-select field. @@ -34,7 +33,7 @@ class Selected: multi: bool @classmethod - def option_from_the(cls: type[SelfSelected], target: Target) -> SelfSelected: + def option_from_the(cls, target: Target) -> Self: """ Get the option. @@ -50,14 +49,12 @@ def option_from_the(cls: type[SelfSelected], target: Target) -> SelfSelected: return cls(target=target) @classmethod - def option_from(cls: type[SelfSelected], target: Target) -> SelfSelected: + def option_from(cls, target: Target) -> Self: """Alias of :meth:`~screenpy_selenium.actions.Selected.option_from_the`.""" return cls.option_from_the(target=target) @classmethod - def options_from_the( - cls: type[SelfSelected], multiselect_target: Target - ) -> SelfSelected: + def options_from_the(cls, multiselect_target: Target) -> Self: """ Get all the options that are currently selected in a multi-select field. @@ -71,18 +68,16 @@ def options_from_the( return cls(target=multiselect_target, multi=True) @classmethod - def options_from( - cls: type[SelfSelected], multiselect_target: Target - ) -> SelfSelected: + def options_from(cls, multiselect_target: Target) -> Self: """Alias of :meth:`~screenpy_selenium.actions.Selected.options_from_the`.""" return cls.options_from_the(multiselect_target=multiselect_target) - def describe(self: SelfSelected) -> str: + def describe(self) -> str: """Describe the Question.""" return f"The selected option(s) from the {self.target}." @beat("{} checks the selected option(s) from the {target}.") - def answered_by(self: SelfSelected, the_actor: Actor) -> str | list[str]: + def answered_by(self, the_actor: Actor) -> str | list[str]: """Direct the Actor to name the selected option(s).""" select = SeleniumSelect(self.target.found_by(the_actor)) @@ -90,6 +85,6 @@ def answered_by(self: SelfSelected, the_actor: Actor) -> str | list[str]: return [e.text for e in select.all_selected_options] return select.first_selected_option.text - def __init__(self: SelfSelected, target: Target, multi: bool = False) -> None: + def __init__(self, target: Target, multi: bool = False) -> None: self.target = target self.multi = multi diff --git a/screenpy_selenium/questions/text.py b/screenpy_selenium/questions/text.py index 5e9eab5..c3d89e4 100644 --- a/screenpy_selenium/questions/text.py +++ b/screenpy_selenium/questions/text.py @@ -2,17 +2,16 @@ from __future__ import annotations -from typing import TYPE_CHECKING, TypeVar +from typing import TYPE_CHECKING from screenpy.pacing import beat +from typing_extensions import Self if TYPE_CHECKING: from screenpy import Actor from ..target import Target -SelfText = TypeVar("SelfText", bound="Text") - class Text: """Ask what text appears in an element or elements. @@ -35,7 +34,7 @@ class Text: multi: bool @classmethod - def of_the(cls: type[SelfText], target: Target) -> SelfText: + def of_the(cls, target: Target) -> Self: """Target the element to extract the text from. Aliases: @@ -45,31 +44,31 @@ def of_the(cls: type[SelfText], target: Target) -> SelfText: return cls(target=target) @classmethod - def of(cls: type[SelfText], target: Target) -> SelfText: + def of(cls, target: Target) -> Self: """Alias of :meth:`~screenpy_selenium.actions.Text.of_the`.""" return cls.of_the(target=target) @classmethod - def of_the_first_of_the(cls: type[SelfText], target: Target) -> SelfText: + def of_the_first_of_the(cls, target: Target) -> Self: """Alias of :meth:`~screenpy_selenium.actions.Text.of_the`.""" return cls.of_the(target=target) @classmethod - def of_all(cls: type[SelfText], multi_target: Target) -> SelfText: + def of_all(cls, multi_target: Target) -> Self: """Target the elements, plural, to extract the text from.""" return cls(target=multi_target, multi=True) - def describe(self: SelfText) -> str: + def describe(self) -> str: """Describe the Question.""" return f"The text from the {self.target}." @beat("{} reads the text from the {target}.") - def answered_by(self: SelfText, the_actor: Actor) -> str | list[str]: + def answered_by(self, the_actor: Actor) -> str | list[str]: """Direct the Actor to read off the text of the element(s).""" if self.multi: return [e.text for e in self.target.all_found_by(the_actor)] return self.target.found_by(the_actor).text - def __init__(self: SelfText, target: Target, multi: bool = False) -> None: + def __init__(self, target: Target, multi: bool = False) -> None: self.target = target self.multi = multi diff --git a/screenpy_selenium/target.py b/screenpy_selenium/target.py index aefe684..daa33e1 100644 --- a/screenpy_selenium/target.py +++ b/screenpy_selenium/target.py @@ -8,10 +8,11 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterator, TypeVar +from typing import TYPE_CHECKING, Iterator from selenium.common.exceptions import WebDriverException from selenium.webdriver.common.by import By +from typing_extensions import Self from .abilities.browse_the_web import BrowseTheWeb from .exceptions import TargetingError @@ -20,8 +21,6 @@ from screenpy.actor import Actor from selenium.webdriver.remote.webdriver import WebElement -SelfTarget = TypeVar("SelfTarget", bound="Target") - class Target: """Describe an element with a human-readable string and a locator. @@ -42,29 +41,29 @@ class Target: locator: tuple[str, str] | None = None @property - def target_name(self: SelfTarget) -> str | None: + def target_name(self) -> str | None: """Return the description when set or the 2nd half of the locator.""" if self._description is not None: return self._description return self.locator[1] if self.locator else None @target_name.setter - def target_name(self: SelfTarget, value: str) -> None: + def target_name(self, value: str) -> None: self._description = value @target_name.deleter - def target_name(self: SelfTarget) -> None: + def target_name(self) -> None: del self._description @classmethod - def the(cls: type[SelfTarget], desc: str) -> SelfTarget: + def the(cls, desc: str) -> Self: """Name this Target. Beginning with a lower-case letter makes the logs look the nicest. """ return cls(desc=desc) - def located_by(self: SelfTarget, locator: tuple[str, str] | str) -> SelfTarget: + def located_by(self, locator: tuple[str, str] | str) -> Self: """Set the locator for this Target. Possible values for locator: @@ -91,11 +90,11 @@ def located_by(self: SelfTarget, locator: tuple[str, str] | str) -> SelfTarget: return self - def located(self: SelfTarget, locator: tuple[str, str] | str) -> SelfTarget: + def located(self, locator: tuple[str, str] | str) -> Self: """Alias for :meth:~screenpy_selenium.Target.located_by.""" return self.located_by(locator) - def get_locator(self: SelfTarget) -> tuple[str, str]: + def get_locator(self) -> tuple[str, str]: """Return the stored locator. Raises: @@ -109,7 +108,7 @@ def get_locator(self: SelfTarget) -> tuple[str, str]: raise TargetingError(msg) return self.locator - def found_by(self: SelfTarget, the_actor: Actor) -> WebElement: + def found_by(self, the_actor: Actor) -> WebElement: """Retrieve the |WebElement| as viewed by the Actor.""" browser = the_actor.ability_to(BrowseTheWeb).browser try: @@ -118,7 +117,7 @@ def found_by(self: SelfTarget, the_actor: Actor) -> WebElement: msg = f"{e} raised while trying to find {self}." raise TargetingError(msg) from e - def all_found_by(self: SelfTarget, the_actor: Actor) -> list[WebElement]: + def all_found_by(self, the_actor: Actor) -> list[WebElement]: """Retrieve a list of |WebElement| objects as viewed by the Actor.""" browser = the_actor.ability_to(BrowseTheWeb).browser try: @@ -127,24 +126,22 @@ def all_found_by(self: SelfTarget, the_actor: Actor) -> list[WebElement]: msg = f"{e} raised while trying to find {self}." raise TargetingError(msg) from e - def __repr__(self: SelfTarget) -> str: + def __repr__(self) -> str: """A Target is represented by its name.""" return f"{self.target_name}" __str__ = __repr__ - def __iter__(self: SelfTarget) -> Iterator[str]: + def __iter__(self) -> Iterator[str]: """Allow Targets to be treated as ``(By, str)`` tuples.""" return self.get_locator().__iter__() - def __getitem__(self: SelfTarget, index: int) -> str: + def __getitem__(self, index: int) -> str: """Allow Targets to be treated as ``(By, str)`` tuples.""" return self.get_locator()[index] def __init__( - self: SelfTarget, - desc: str | None = None, - locator: tuple[str, str] | None = None, + self, desc: str | None = None, locator: tuple[str, str] | None = None ) -> None: self.target_name = desc self.locator = locator