Skip to content
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
11 changes: 11 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -1,2 +1,13 @@
[mypy]
show_error_codes = True
exclude = (?x)(
setup\.py
| tests/
| docs/
)

[mypy-screenpy.*]
disallow_untyped_defs = True

[mypy-tests.*]
ignore_missing_imports = True
2 changes: 1 addition & 1 deletion screenpy_selenium/actions/chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def perform_as(self, the_actor: Actor) -> None:
the_chain = ActionChains(browser)

for action in self.actions:
if "add_to_chain" not in dir(action):
if not isinstance(action, Chainable):
Copy link
Member

Choose a reason for hiding this comment

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

So much more readable.

raise UnableToAct(
f"The {action.__class__.__name__} Action cannot be chained."
)
Expand Down
4 changes: 4 additions & 0 deletions screenpy_selenium/actions/save_console_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ class SaveConsoleLog:

attach_kwargs: Optional[dict]

def describe(self) -> str:
"""Describe the Action in present tense."""
return f"Save browser console log as {self.filename}"
Comment on lines +47 to +49
Copy link
Member

Choose a reason for hiding this comment

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

Thanks again for catching and fixing these!


@staticmethod
def as_(path: str) -> "SaveConsoleLog":
"""Supply the name and/or filepath for the saved text file.
Expand Down
4 changes: 4 additions & 0 deletions screenpy_selenium/actions/save_screenshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ class SaveScreenshot:

attach_kwargs: Optional[dict]

def describe(self) -> str:
"""Describe the Action in present tense."""
return f"Save screenshot as {self.filename}"

@staticmethod
def as_(path: str) -> "SaveScreenshot":
"""Supply the name and/or filepath for the screenshot.
Expand Down
3 changes: 2 additions & 1 deletion screenpy_selenium/protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

from screenpy import Actor
from selenium.webdriver.common.action_chains import ActionChains
from typing_extensions import Protocol
from typing_extensions import Protocol, runtime_checkable


@runtime_checkable
class Chainable(Protocol):
"""Actions that can be added to a chain are Chainable."""

Expand Down
6 changes: 5 additions & 1 deletion screenpy_selenium/questions/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ class Element:
the_actor.should(See.the(Element(WELCOME_BANNER), IsVisible()))
"""

caught_exception: Optional[TargetingError]
Copy link
Member

Choose a reason for hiding this comment

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

We were talking about this on Discord, and i do really like how we can be extremely explicit here about what kind of exceptions we're expecting to handle in the actual Questions.


def describe(self) -> str:
"""Describe the Question."""
return f"The {self.target}."
Expand All @@ -32,8 +34,10 @@ def answered_by(self, the_actor: Actor) -> Optional[WebElement]:
"""Direct the Actor to find the element."""
try:
return self.target.found_by(the_actor)
except TargetingError:
except TargetingError as exc:
self.caught_exception = exc
return None

def __init__(self, target: Target) -> None:
self.target = target
self.caught_exception = None
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from setuptools import find_packages, setup

requires = [
"screenpy>=4.0.0",
"screenpy>=4.0.2",
"screenpy_pyotp>=4.0.0,<4.1",
"typing-extensions>=4.1.1,<4.2",
"selenium>=4.1.0,<4.2",
Expand Down
7 changes: 4 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@
from screenpy_pyotp.abilities import AuthenticateWith2FA

from screenpy_selenium.abilities import BrowseTheWeb
from selenium.webdriver.remote.webdriver import WebDriver


@pytest.fixture(scope="function")
def Tester() -> AnActor:
"""Provide an Actor with mocked web browsing abilities."""
AuthenticateWith2FA_Mocked = mock.Mock(spec=AuthenticateWith2FA)
AuthenticateWith2FA_Mocked = mock.create_autospec(AuthenticateWith2FA, instance=True)
AuthenticateWith2FA_Mocked.otp = mock.Mock()
BrowseTheWeb_Mocked = mock.Mock(spec=BrowseTheWeb)
BrowseTheWeb_Mocked.browser = mock.Mock()
BrowseTheWeb_Mocked = mock.create_autospec(BrowseTheWeb, instance=True)
BrowseTheWeb_Mocked.browser = mock.create_autospec(WebDriver, instance=True)

return AnActor.named("Tester").who_can(
AuthenticateWith2FA_Mocked, BrowseTheWeb_Mocked
Expand Down
24 changes: 19 additions & 5 deletions tests/test_abilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from unittest import mock

import pytest
from screenpy.protocols import Forgettable

from screenpy_selenium.abilities import BrowseTheWeb
from screenpy_selenium.exceptions import BrowsingError
Expand All @@ -13,26 +14,30 @@ def test_can_be_instantiated(self):

assert isinstance(b, BrowseTheWeb)

@mock.patch("screenpy_selenium.abilities.browse_the_web.Firefox")
def test_implements_protocol(self):
b = BrowseTheWeb(None)
assert isinstance(b, Forgettable)

@mock.patch("screenpy_selenium.abilities.browse_the_web.Firefox", autospec=True)
def test_using_firefox(self, mocked_firefox):
BrowseTheWeb.using_firefox()

mocked_firefox.assert_called_once()

@mock.patch("screenpy_selenium.abilities.browse_the_web.Chrome")
@mock.patch("screenpy_selenium.abilities.browse_the_web.Chrome", autospec=True)
def test_using_chrome(self, mocked_chrome):
BrowseTheWeb.using_chrome()

mocked_chrome.assert_called_once()

@mock.patch("screenpy_selenium.abilities.browse_the_web.Safari")
@mock.patch("screenpy_selenium.abilities.browse_the_web.Safari", autospec=True)
def test_using_safari(self, mocked_safari):
BrowseTheWeb.using_safari()

mocked_safari.assert_called_once()

@mock.patch.dict(os.environ, {"IOS_DEVICE_VERSION": "1"})
@mock.patch("screenpy_selenium.abilities.browse_the_web.Remote")
@mock.patch("screenpy_selenium.abilities.browse_the_web.Remote", autospec=True)
def test_using_ios(self, mocked_remote):
BrowseTheWeb.using_ios()

Expand All @@ -43,7 +48,7 @@ def test_using_ios_without_env_var(self):
BrowseTheWeb.using_ios()

@mock.patch.dict(os.environ, {"ANDROID_DEVICE_VERSION": "1"})
@mock.patch("screenpy_selenium.abilities.browse_the_web.Remote")
@mock.patch("screenpy_selenium.abilities.browse_the_web.Remote", autospec=True)
def test_using_android(self, mocked_android):
BrowseTheWeb.using_android()

Expand All @@ -52,3 +57,12 @@ def test_using_android(self, mocked_android):
def test_using_android_without_env_var(self):
with pytest.raises(BrowsingError):
BrowseTheWeb.using_android()

@mock.patch("screenpy_selenium.abilities.browse_the_web.Chrome", autospec=True)
def test_forget_calls_quit(self, mocked_chrome):
b = BrowseTheWeb(mocked_chrome)
b.forget()
mocked_chrome.quit.assert_called_once()

def test_repr(self):
assert repr(BrowseTheWeb(None)) == "Browse the Web"
Loading