From 07864693e692aecacd243e09f34d12b1efa4269d Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Tue, 16 Jun 2026 15:09:36 -0500 Subject: [PATCH 01/20] adding parent locator support --- screenpy_selenium/target.py | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/screenpy_selenium/target.py b/screenpy_selenium/target.py index 9b374c7..42d107e 100644 --- a/screenpy_selenium/target.py +++ b/screenpy_selenium/target.py @@ -20,9 +20,11 @@ from collections.abc import Iterator from screenpy.actor import Actor - from selenium.webdriver.remote.webdriver import WebElement + from selenium.webdriver.remote.webdriver import WebDriver, WebElement from typing_extensions import Self + WebDriverOrWebElement = WebDriver | WebElement + class Target: """Describe an element with a human-readable string and a locator. @@ -41,6 +43,7 @@ class Target: _description: str | None = None locator: tuple[str, str] | None = None + parent_target: Target | None = None @property def target_name(self) -> str | None: @@ -112,22 +115,45 @@ def get_locator(self) -> tuple[str, str]: def found_by(self, the_actor: Actor) -> WebElement: """Retrieve the |WebElement| as viewed by the Actor.""" - browser = the_actor.ability_to(BrowseTheWeb).browser + driver_or_element: WebDriverOrWebElement + if self.parent_target: + driver_or_element = self.parent_target.found_by(the_actor) + else: + driver_or_element = the_actor.ability_to(BrowseTheWeb).browser + try: - return browser.find_element(*self) + return driver_or_element.find_element(*self) except WebDriverException as e: msg = f"{e} raised while trying to find {self}." raise TargetingError(msg) from e 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 + driver_or_element: WebDriverOrWebElement + if self.parent_target: + driver_or_element = self.parent_target.found_by(the_actor) + else: + driver_or_element = the_actor.ability_to(BrowseTheWeb).browser + try: - return browser.find_elements(*self) + return driver_or_element.find_elements(*self) except WebDriverException as e: msg = f"{e} raised while trying to find {self}." raise TargetingError(msg) from e + def inside(self, parent_target: Target) -> Self: + """Set the parent locator of the element where this Target should search.""" + self.parent_target = parent_target + return self + + def inside_of(self, parent_target: Target) -> Self: + """Alias for :meth:`~screenpy_selenium.Target.inside`.""" + return self.inside(parent_target) + + def within(self, parent_target: Target) -> Self: + """Alias for :meth:`~screenpy_selenium.Target.inside`.""" + return self.inside(parent_target) + def __repr__(self) -> str: """A Target is represented by its name.""" return f"{self.target_name}" From 75c176149237d560ddf9b136bcfb8dcb1953aeb5 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Tue, 16 Jun 2026 16:06:21 -0500 Subject: [PATCH 02/20] tests for target parents --- tests/test_target.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/test_target.py b/tests/test_target.py index 6449dc2..ca337f2 100644 --- a/tests/test_target.py +++ b/tests/test_target.py @@ -8,7 +8,7 @@ from screenpy_selenium import Target, TargetingError -from .useful_mocks import get_mocked_browser +from .useful_mocks import get_mocked_browser, get_mocked_target_and_element if TYPE_CHECKING: from screenpy import Actor @@ -121,6 +121,15 @@ def test_found_by_raises(Tester: Actor) -> None: assert test_name in str(excinfo.value) +def test_found_by_parent(Tester: Actor) -> None: + parent, mocked_element = get_mocked_target_and_element() + test_locator = (By.ID, "child") + + Target.the("test").located(test_locator).inside(parent).found_by(Tester) + mocked_element.find_element.assert_called_once_with(*test_locator) + parent.found_by.assert_called_once_with(Tester) + + def test_all_found_by(Tester: Actor) -> None: test_locator = (By.ID, "baked beans") Target.the("test").located(test_locator).all_found_by(Tester) @@ -139,6 +148,15 @@ def test_all_found_by_raises(Tester: Actor) -> None: assert test_name in str(excinfo.value) +def test_all_found_by_parent(Tester: Actor) -> None: + parent, mocked_element = get_mocked_target_and_element() + test_locator = (By.ID, "children") + + Target.the("test").located(test_locator).inside_of(parent).all_found_by(Tester) + mocked_element.find_elements.assert_called_once_with(*test_locator) + parent.found_by.assert_called_once_with(Tester) + + def test_iterator() -> None: locator = (By.ID, "eggs") target = Target.the("test").located(locator) From 11cb245089039b2a936cf495509fa4c576198867 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Tue, 16 Jun 2026 16:15:22 -0500 Subject: [PATCH 03/20] still have to support 3.9 unions --- screenpy_selenium/target.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/screenpy_selenium/target.py b/screenpy_selenium/target.py index 42d107e..8645375 100644 --- a/screenpy_selenium/target.py +++ b/screenpy_selenium/target.py @@ -8,7 +8,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Union from selenium.common.exceptions import WebDriverException from selenium.webdriver.common.by import By @@ -23,7 +23,7 @@ from selenium.webdriver.remote.webdriver import WebDriver, WebElement from typing_extensions import Self - WebDriverOrWebElement = WebDriver | WebElement + WebDriverOrWebElement = Union[WebDriver, WebElement] class Target: From ac997905be77e2865545b9926fbfd42cb03af71d Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Wed, 17 Jun 2026 12:50:31 -0500 Subject: [PATCH 04/20] repr needs to include parentage --- screenpy_selenium/target.py | 2 ++ tests/test_target.py | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/screenpy_selenium/target.py b/screenpy_selenium/target.py index 8645375..3c937a9 100644 --- a/screenpy_selenium/target.py +++ b/screenpy_selenium/target.py @@ -156,6 +156,8 @@ def within(self, parent_target: Target) -> Self: def __repr__(self) -> str: """A Target is represented by its name.""" + if self.parent_target: + return f"{self.target_name} in {self.parent_target}" return f"{self.target_name}" __str__ = __repr__ diff --git a/tests/test_target.py b/tests/test_target.py index ca337f2..8f2c5b8 100644 --- a/tests/test_target.py +++ b/tests/test_target.py @@ -178,14 +178,26 @@ def test_empty_target_iterator() -> None: def test_repr() -> None: t1 = Target() t2 = Target("foo") + t3 = Target("bar").inside(Target("baz")) + t4 = Target("abc").inside(Target("def").inside(Target("ghi"))) + t5 = Target().located((By.ID, "bla")).inside(Target().located((By.XPATH, "//div"))) assert repr(t1) == "None" assert repr(t2) == "foo" + assert repr(t3) == "bar in baz" + assert repr(t4) == "abc in def in ghi" + assert repr(t5) == "bla in //div" def test_str() -> None: t1 = Target() t2 = Target("foo") + t3 = Target("bar").inside(Target("baz")) + t4 = Target("abc").inside(Target("def").inside(Target("ghi"))) + t5 = Target().located((By.ID, "bla")).inside(Target().located((By.XPATH, "//div"))) assert str(t1) == "None" assert str(t2) == "foo" + assert str(t3) == "bar in baz" + assert str(t4) == "abc in def in ghi" + assert str(t5) == "bla in //div" From 39c1c0785fdd23e48ffcae7f0ae4d9d8c0863f45 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Wed, 17 Jun 2026 13:23:27 -0500 Subject: [PATCH 05/20] importing WebElement directly from it's own module. --- screenpy_selenium/target.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/screenpy_selenium/target.py b/screenpy_selenium/target.py index 3c937a9..635700a 100644 --- a/screenpy_selenium/target.py +++ b/screenpy_selenium/target.py @@ -20,7 +20,8 @@ from collections.abc import Iterator from screenpy.actor import Actor - from selenium.webdriver.remote.webdriver import WebDriver, WebElement + from selenium.webdriver.remote.webdriver import WebDriver + from selenium.webdriver.remote.webelement import WebElement from typing_extensions import Self WebDriverOrWebElement = Union[WebDriver, WebElement] From 6211d82c0a8290dd5b6c3b92e4065df8af176e56 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Wed, 17 Jun 2026 14:29:19 -0500 Subject: [PATCH 06/20] adding documentation for Target.inside --- docs/targets.rst | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/docs/targets.rst b/docs/targets.rst index 574cb51..35e1a5f 100644 --- a/docs/targets.rst +++ b/docs/targets.rst @@ -3,12 +3,13 @@ Targets ======= The blocking of the screenplay! -The Target tells the Actors + +The :ref:`Target` tells the :external+screenpy:ref:`Actor` what part of the website they are to interact with. Stripping away the metaphor, -the :ref:`target` combines a locator +the :ref:`Target` combines a locator with a human-readable string. The human-readable part is what gets read out @@ -60,7 +61,7 @@ The resulting log: | Webster enters "[CENSORED]" into the password field. | Websert clicks on the "Sign In" button. -By default the :ref:`target` will use the locator string as a human-readable +By default the :ref:`Target` will use the locator string as a human-readable ``target_name`` in the absence of providing one. This can be convenient if your locators are self-describing:: @@ -79,7 +80,31 @@ locators are self-describing:: The resulting log: - | Webster enters "foo" into the username-field. - | Webster enters "[CENSORED]" into the password-field. - | Websert clicks on the sign-in-button. + | Webster enters "foo" into the username field. + | Webster enters "[CENSORED]" into the password field. + | Websert clicks on the "Sign In" button. + + +Targets in Targets +------------------ + +A :ref:`Target` (as seen above) will typically have selenium doing a search +for the locator starting at the root of the DOM:: + + # These are equivalent + web_element = USERNAME_FIELD.found_by(Webster) + + web_element = driver.find_element(*USERNAME_FIELD) + +Selenium also has the ability to search from a found WebElement; as such +so can :ref:`Target`:: + + # These are equivalent + + elem = USERNAME_FIELD.inside(LOGIN_FORM).found_by(Webster) + + form_elem = driver.find_element(*LOGIN_FORM) + elem = form_elem.find_element(*USERNAME_FIELD) + + elem = driver.find_element(*LOGIN_FORM).find_element(*USERNAME_FIELD) \ No newline at end of file From f035e431beb388c5f39c372d803406afa373c76a Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Wed, 17 Jun 2026 14:30:28 -0500 Subject: [PATCH 07/20] adding documentation for Target.inside --- docs/targets.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/targets.rst b/docs/targets.rst index 35e1a5f..6b5df66 100644 --- a/docs/targets.rst +++ b/docs/targets.rst @@ -107,4 +107,5 @@ so can :ref:`Target`:: form_elem = driver.find_element(*LOGIN_FORM) elem = form_elem.find_element(*USERNAME_FIELD) - elem = driver.find_element(*LOGIN_FORM).find_element(*USERNAME_FIELD) \ No newline at end of file + elem = driver.find_element(*LOGIN_FORM).find_element(*USERNAME_FIELD) + From f4fc7e49143d7c5db27262461959445e935ebce9 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Wed, 17 Jun 2026 15:54:41 -0500 Subject: [PATCH 08/20] making inside not mutate the Target --- screenpy_selenium/target.py | 5 +++-- tests/test_target.py | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/screenpy_selenium/target.py b/screenpy_selenium/target.py index 635700a..1e1cef4 100644 --- a/screenpy_selenium/target.py +++ b/screenpy_selenium/target.py @@ -144,8 +144,9 @@ def all_found_by(self, the_actor: Actor) -> list[WebElement]: def inside(self, parent_target: Target) -> Self: """Set the parent locator of the element where this Target should search.""" - self.parent_target = parent_target - return self + new_target = type(self)(desc=self._description, locator=self.locator) + new_target.parent_target = parent_target + return new_target def inside_of(self, parent_target: Target) -> Self: """Alias for :meth:`~screenpy_selenium.Target.inside`.""" diff --git a/tests/test_target.py b/tests/test_target.py index 8f2c5b8..b10f9da 100644 --- a/tests/test_target.py +++ b/tests/test_target.py @@ -103,6 +103,20 @@ def test_locator_tuple_size() -> None: Target("test").located_by([By.ID, "foo"]) # type: ignore[arg-type] +def test_inside() -> None: + target1 = Target.the("one").located((By.ID, "one")) + target2 = Target.the("two").located((By.ID, "two")) + target3 = target1.inside(target2) + + assert target1 is not target3 + assert target2 is not target3 + assert target1.parent_target is None + assert target2.parent_target is None + assert target3.parent_target is target2 + + assert target1 != target3 + + def test_found_by(Tester: Actor) -> None: test_locator = (By.ID, "eggs") Target.the("test").located(test_locator).found_by(Tester) From 36cbb0e23cab5ed190cfcdbe46b16ee0261149dd Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Wed, 17 Jun 2026 16:35:07 -0500 Subject: [PATCH 09/20] fixing import example. --- docs/targets.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/targets.rst b/docs/targets.rst index 6b5df66..4857c97 100644 --- a/docs/targets.rst +++ b/docs/targets.rst @@ -42,7 +42,7 @@ by passing them to Actions:: from example_test.ui.login_page import ( PASSWORD_FIELD, - SIGN_IN_BUTTON + SIGN_IN_BUTTON, USERNAME_FIELD, ) From f88283436ecf4a5451fdabff0b6a329ea7f3c594 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Wed, 17 Jun 2026 16:55:42 -0500 Subject: [PATCH 10/20] adding alias variation tests --- tests/test_target.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/tests/test_target.py b/tests/test_target.py index b10f9da..7f1bc4a 100644 --- a/tests/test_target.py +++ b/tests/test_target.py @@ -104,17 +104,23 @@ def test_locator_tuple_size() -> None: def test_inside() -> None: - target1 = Target.the("one").located((By.ID, "one")) - target2 = Target.the("two").located((By.ID, "two")) - target3 = target1.inside(target2) - - assert target1 is not target3 - assert target2 is not target3 - assert target1.parent_target is None - assert target2.parent_target is None - assert target3.parent_target is target2 - - assert target1 != target3 + t1 = Target.the("one").located((By.ID, "one")) + t2 = Target.the("two").located((By.ID, "two")) + t3 = t1.inside(t2) + t4 = t1.inside_of(t2) + t5 = t1.within(t2) + + assert t1 is not t3 + assert t1 is not t4 + assert t1 is not t5 + assert t2 is not t3 + assert t2 is not t4 + assert t2 is not t5 + assert t1.parent_target is None + assert t2.parent_target is None + assert t3.parent_target is t2 + assert t4.parent_target is t2 + assert t5.parent_target is t2 def test_found_by(Tester: Actor) -> None: From f8774d7edaa609707404ad54f7abc0dcbf82ea6c Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Thu, 18 Jun 2026 10:34:50 -0500 Subject: [PATCH 11/20] making `inside` work with subclassed Target --- screenpy_selenium/target.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/screenpy_selenium/target.py b/screenpy_selenium/target.py index 1e1cef4..01ed856 100644 --- a/screenpy_selenium/target.py +++ b/screenpy_selenium/target.py @@ -8,6 +8,7 @@ from __future__ import annotations +import copy from typing import TYPE_CHECKING, Union from selenium.common.exceptions import WebDriverException @@ -143,8 +144,11 @@ def all_found_by(self, the_actor: Actor) -> list[WebElement]: raise TargetingError(msg) from e def inside(self, parent_target: Target) -> Self: - """Set the parent locator of the element where this Target should search.""" - new_target = type(self)(desc=self._description, locator=self.locator) + """Create a new Target and set the parent target for where search should start. + + This purposefully avoids mutating the original Target for re-usability. + """ + new_target = copy.copy(self) new_target.parent_target = parent_target return new_target From dd04ed2437e440172f0554493e09c27e2660cc50 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Thu, 18 Jun 2026 10:40:05 -0500 Subject: [PATCH 12/20] grammar --- docs/targets.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/targets.rst b/docs/targets.rst index 4857c97..07d1a1e 100644 --- a/docs/targets.rst +++ b/docs/targets.rst @@ -85,10 +85,10 @@ The resulting log: | Websert clicks on the "Sign In" button. -Targets in Targets ------------------- +Target in Target +---------------- -A :ref:`Target` (as seen above) will typically have selenium doing a search +A :ref:`Target` (as seen above) will typically have selenium do a search for the locator starting at the root of the DOM:: # These are equivalent @@ -97,8 +97,8 @@ for the locator starting at the root of the DOM:: web_element = driver.find_element(*USERNAME_FIELD) -Selenium also has the ability to search from a found WebElement; as such -so can :ref:`Target`:: +But, selenium also has the ability to search starting from a found WebElement; +:ref:`Target`:: can do the same by utilizing the method `inside`:: # These are equivalent From 3fa59b0bbdb8d6506ed38d4b10dce9f26a318e22 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Thu, 18 Jun 2026 10:49:00 -0500 Subject: [PATCH 13/20] stylizing method for better clarity. --- docs/targets.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/targets.rst b/docs/targets.rst index 07d1a1e..63cbe4b 100644 --- a/docs/targets.rst +++ b/docs/targets.rst @@ -98,7 +98,7 @@ for the locator starting at the root of the DOM:: web_element = driver.find_element(*USERNAME_FIELD) But, selenium also has the ability to search starting from a found WebElement; -:ref:`Target`:: can do the same by utilizing the method `inside`:: +:ref:`Target`:: can do the same by utilizing the method ``Target.inside()``:: # These are equivalent From 3ebd357a40479841d65b21803423403b9125fea1 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Thu, 18 Jun 2026 11:08:16 -0500 Subject: [PATCH 14/20] better links --- docs/targets.rst | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/targets.rst b/docs/targets.rst index 63cbe4b..bee53f2 100644 --- a/docs/targets.rst +++ b/docs/targets.rst @@ -4,12 +4,13 @@ Targets The blocking of the screenplay! -The :ref:`Target` tells the :external+screenpy:ref:`Actor` +The :class:`~screenpy_selenium.Target` tells the +:external+screenpy:class:`~screenpy.Actor` what part of the website they are to interact with. Stripping away the metaphor, -the :ref:`Target` combines a locator +the :class:`~screenpy_selenium.Target` combines a locator with a human-readable string. The human-readable part is what gets read out @@ -61,9 +62,9 @@ The resulting log: | Webster enters "[CENSORED]" into the password field. | Websert clicks on the "Sign In" button. -By default the :ref:`Target` will use the locator string as a human-readable -``target_name`` in the absence of providing one. This can be convenient if your -locators are self-describing:: +By default the :class:`~screenpy_selenium.Target` will use the locator string +as a human-readable ``target_name`` in the absence of providing one. +This can be convenient if your locators are self-describing:: from screenpy_selenium import Target from selenium.webdriver.common.by import By @@ -88,8 +89,8 @@ The resulting log: Target in Target ---------------- -A :ref:`Target` (as seen above) will typically have selenium do a search -for the locator starting at the root of the DOM:: +A :class:`~screenpy_selenium.Target` (as seen above) will typically have +selenium do a search for the locator starting at the root of the DOM:: # These are equivalent @@ -98,7 +99,8 @@ for the locator starting at the root of the DOM:: web_element = driver.find_element(*USERNAME_FIELD) But, selenium also has the ability to search starting from a found WebElement; -:ref:`Target`:: can do the same by utilizing the method ``Target.inside()``:: +:class:`~screenpy_selenium.Target` can do the same by utilizing the +method ``Target.inside()``:: # These are equivalent @@ -109,3 +111,4 @@ But, selenium also has the ability to search starting from a found WebElement; elem = driver.find_element(*LOGIN_FORM).find_element(*USERNAME_FIELD) + From 979320fc4762714b486d0e8ad8f663dc353f50b0 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Thu, 18 Jun 2026 11:10:23 -0500 Subject: [PATCH 15/20] linking directly to the method --- docs/targets.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/targets.rst b/docs/targets.rst index bee53f2..e39d6d9 100644 --- a/docs/targets.rst +++ b/docs/targets.rst @@ -100,7 +100,7 @@ selenium do a search for the locator starting at the root of the DOM:: But, selenium also has the ability to search starting from a found WebElement; :class:`~screenpy_selenium.Target` can do the same by utilizing the -method ``Target.inside()``:: +method :meth:`~screenpy.target.Target.inside`:: # These are equivalent From 78b42e5cf186e7fb458c2acded4919833cc976b9 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Thu, 18 Jun 2026 11:26:56 -0500 Subject: [PATCH 16/20] adding note about inside not mutating Target --- docs/targets.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/targets.rst b/docs/targets.rst index e39d6d9..f2bb21d 100644 --- a/docs/targets.rst +++ b/docs/targets.rst @@ -112,3 +112,8 @@ method :meth:`~screenpy.target.Target.inside`:: elem = driver.find_element(*LOGIN_FORM).find_element(*USERNAME_FIELD) +.. note:: + + :meth:`~screenpy.target.Target.inside` does not mutate :class:`~screenpy_selenium.Target`. + This is done purposefully so the existing `Target` can be reused. + From 0d156e381484d5fd3605103e7c67eba1becaac03 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Fri, 26 Jun 2026 11:02:46 -0500 Subject: [PATCH 17/20] fixing documentation based on feedback --- docs/targets.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/targets.rst b/docs/targets.rst index f2bb21d..90b91c9 100644 --- a/docs/targets.rst +++ b/docs/targets.rst @@ -81,9 +81,9 @@ This can be convenient if your locators are self-describing:: The resulting log: - | Webster enters "foo" into the username field. - | Webster enters "[CENSORED]" into the password field. - | Websert clicks on the "Sign In" button. + | Webster enters "foo" into the username-field. + | Webster enters "[CENSORED]" into the password-field. + | Webster clicks on the sign-in-button. Target in Target From f72fa718f9adc8726579ba8b4fe9865dc02a44e3 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Fri, 26 Jun 2026 11:09:59 -0500 Subject: [PATCH 18/20] trying to used the semantic linebreak method --- docs/targets.rst | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/targets.rst b/docs/targets.rst index 90b91c9..fe89831 100644 --- a/docs/targets.rst +++ b/docs/targets.rst @@ -89,8 +89,9 @@ The resulting log: Target in Target ---------------- -A :class:`~screenpy_selenium.Target` (as seen above) will typically have -selenium do a search for the locator starting at the root of the DOM:: +A :class:`~screenpy_selenium.Target` (as seen above) +will typically have Selenium do a search +for the locator starting at the root of the DOM:: # These are equivalent @@ -98,9 +99,10 @@ selenium do a search for the locator starting at the root of the DOM:: web_element = driver.find_element(*USERNAME_FIELD) -But, selenium also has the ability to search starting from a found WebElement; -:class:`~screenpy_selenium.Target` can do the same by utilizing the -method :meth:`~screenpy.target.Target.inside`:: +But, Selenium also has the ability +to search starting from a found WebElement; +:class:`~screenpy_selenium.Target` can do the same +by utilizing the method :meth:`~screenpy.target.Target.inside`:: # These are equivalent From bd3c3618b0b54cb3f19280d9f0ab239280e64d1b Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Fri, 26 Jun 2026 11:12:27 -0500 Subject: [PATCH 19/20] documentation feedback --- docs/targets.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/targets.rst b/docs/targets.rst index fe89831..9cfe214 100644 --- a/docs/targets.rst +++ b/docs/targets.rst @@ -99,19 +99,20 @@ for the locator starting at the root of the DOM:: web_element = driver.find_element(*USERNAME_FIELD) -But, Selenium also has the ability -to search starting from a found WebElement; +Selenium also has the ability +to search for a child WebElement +starting from an already-found parent WebElement. :class:`~screenpy_selenium.Target` can do the same by utilizing the method :meth:`~screenpy.target.Target.inside`:: - # These are equivalent + # These three are equivalent - elem = USERNAME_FIELD.inside(LOGIN_FORM).found_by(Webster) + elem1 = USERNAME_FIELD.inside(LOGIN_FORM).found_by(Webster) form_elem = driver.find_element(*LOGIN_FORM) - elem = form_elem.find_element(*USERNAME_FIELD) + elem2 = form_elem.find_element(*USERNAME_FIELD) - elem = driver.find_element(*LOGIN_FORM).find_element(*USERNAME_FIELD) + elem3 = driver.find_element(*LOGIN_FORM).find_element(*USERNAME_FIELD) .. note:: From c2a5cb0a3a4031aa632cc131ba7457706d07c5e7 Mon Sep 17 00:00:00 2001 From: Marcel Wilson Date: Fri, 26 Jun 2026 14:22:26 -0500 Subject: [PATCH 20/20] better wording for `inside` --- screenpy_selenium/target.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/screenpy_selenium/target.py b/screenpy_selenium/target.py index 01ed856..53acc17 100644 --- a/screenpy_selenium/target.py +++ b/screenpy_selenium/target.py @@ -144,9 +144,9 @@ def all_found_by(self, the_actor: Actor) -> list[WebElement]: raise TargetingError(msg) from e def inside(self, parent_target: Target) -> Self: - """Create a new Target and set the parent target for where search should start. + """Set the containing parent element for this Target. - This purposefully avoids mutating the original Target for re-usability. + Creates a new Target instance. """ new_target = copy.copy(self) new_target.parent_target = parent_target