Skip to content

Commit

Permalink
Either action (ScreenPyHQ#90)
Browse files Browse the repository at this point in the history
* adding TryTo action
* Added Tests for TryTo
* adding documentation
* using better descriptive variables
* renaming TryTo to Either
* fixed multi-action output and added test
* using example action that is actually in the screenpy_selenium library.
* linking to screenpy_selenium package
* fixing references to AssertionError
* fixing python code blocks to be consistent
* using better names for the two sets of actions
* adding option for tester to ignore exceptions as desired.
* Allowing user to turn on UNABRIDGED_NARRATION to see why the first action tried failed.
* using established pattern to settings mocking
  • Loading branch information
bandophahita authored Sep 21, 2023
1 parent 6d72922 commit d506f35
Show file tree
Hide file tree
Showing 8 changed files with 421 additions and 37 deletions.
18 changes: 18 additions & 0 deletions docs/api/actions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,21 @@ Silently
**Aliases**: ``Quietly``

.. autofunction:: Silently


Either
-----

**Aliases**: ``Attempt``,
``AttemptTo``,
``GoFor``,
``Try``
``TryTo``,
``Attempts``,
``AttemptsTo``,
``GoesFor``,
``Tries``,
``TriesTo``

.. autoclass:: Either
:members:
112 changes: 102 additions & 10 deletions docs/cookbook.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ Retrieving Initial Answers
Some tests may need to ensure something has changed.
You are able to retrieve
the answers to Questions
anywhere you may need to::
anywhere you may need to.

.. code-block:: python
empty_todo_list_text = Text.of_the(TODO_LIST).answered_by(Wanda)
Expand All @@ -27,7 +29,9 @@ Using MakeNote
You can also retrieve initial answers
with the :class:`~screenpy.actions.MakeNote` class,
and retrieve the value
with a :ref:`direction <directions>`::
with a :ref:`direction <directions>`.
.. code-block:: python
when(Cameron).attempts_to(
StartRecording(),
Expand All @@ -48,7 +52,9 @@ There is one limitation
to using :class:`~screenpy.actions.MakeNote`:
it is not possible
to make a note and retrieve it
in the same Actions list::
in the same Actions list.

.. code-block:: python
# CAN NOT do this:
when(Perry).attempts_to(
Expand Down Expand Up @@ -95,7 +101,9 @@ and performs it,
repeatedly,
until either it succeeds
or the timeout expires.
Here's what that looks like::
Here's what that looks like.

.. code-block:: python
Marcel.attempts_to(
Eventually(
Expand Down Expand Up @@ -166,7 +174,9 @@ your Actors may need one or more of their Abilities
to do some cleanup.
You can assign cleanup tasks to your Actor
using their :meth:`~screenpy.actor.Actor.has_ordered_cleanup_tasks`
or :meth:`~screenpy.actor.Actor.has_independent_cleanup_tasks` method::
or :meth:`~screenpy.actor.Actor.has_independent_cleanup_tasks` method.

.. code-block:: python
Perry = AnActor.named("Perry").who_can(BrowseTheWeb.using_firefox())
Perry.has_ordered_cleanup_tasks(CompleteAllTodoItems())
Expand Down Expand Up @@ -198,7 +208,9 @@ Sometimes you only need logging when things go wrong.
to only log the important things when things go right.
Everything inside of ``Silently`` is prevented from logging.

Example: The following Action::
Example: The following Action:

.. code-block:: python
class PerformChatty(Performable):
@beat("{} tries to PerformChatty")
Expand All @@ -221,19 +233,25 @@ Would generate this log::
... hoping it's equal to True.
=> <True>

But what if perhaps, we didn't need to know all the steps being taken at in ``PerformChatty`` unless they were to fail?
Wrapping ``PerformA`` in ``Silently``::
But what if we didn't need to know
all the steps being taken in ``PerformChatty``
unless they were to fail?
Wrapping ``PerformA`` in ``Silently``...

.. code-block:: python
class PerformChatty(Performable):
@beat("{} tries to PerformChatty")
def perform_as(self, actor: Actor):
actor.will(Silently(PerformA()))
Will only generate this log::
...will only generate this log::

Marcel tries to PerformChatty

Unless of course something bad happens inside of ``PerformA`` in which case the normal logging will take place::
Unless of course something bad happens
inside of ``PerformA`` in which case
the normal logging will take place::

Marcel tries to PerformChatty
Marcel tries to PerformA
Expand All @@ -249,3 +267,77 @@ Unless of course something bad happens inside of ``PerformA`` in which case the
AssertionError:
Expected: <True>
but: was <False>


Using Either
===========

Sometimes you may need to use a try/except control flow in your test,
for one reason or another.
Luckily, your Actor can perform
this flow with the :class:`~screenpy.actions.Either` Action!

.. code-block:: python
the_actor.will(Either(DoAction()).or_(DoDifferentAction())
The Actor will attempt to perform the first action (or set of actions).
If successful, the Actor moves on.
But if an ``AssertionError`` is raised
the Actor will begin performing
the second action (or set of actions)
passed into :meth:`~screenpy.actions.Either.or_`.
Note the Actor only catches ``AssertionError`` here
allowing for other exceptions to still be raised.
Other exceptions can be caught when specified.
.. code-block:: python
the_actor.will(
Either(DoAction())
.or_(DoDifferentAction())
.ignoring(ValueError, AssertionError)
)
:class:`~screenpy.actions.Either` allows users to pass in multiple actions.
This is similar Actor performing multiple actions in one call.
.. code-block:: python
the_actor.will(
Either(
DoAction1(),
DoAction2(),
DoAction3(),
).or_(
DoDifferentAction1(),
DoDifferentAction2(),
DoDifferentAction3(),
)
)
.. note::
:class:`~screenpy.actions.Either` will not describe any cleanup for the Actor
after it experiences a failure in the first routine; the Actor will proceed directly
to the second routine. Keep this in mind while defining the two branches of Actions.
To help illustrate this further here is a real-world example
using `screenpy_selenium <https://screenpy-selenium-docs.readthedocs.io/en/latest/>`__
.. code-block:: python
the_actor.will(
Either(
See(BrowserURL(), ReadsExactly(URL)),
CheckIfAuthenticated(),
).or_(
ClearCache()
Open.their_browser_on(URL())
Eventually(Enter(username).into(USERNAME_FIELD)),
Enter.the_secret(password).into(PASSWORD_FIELD),
Click.on(SIGN_IN_BUTTON)
)
)
Loading

0 comments on commit d506f35

Please sign in to comment.