diff --git a/CHANGELOG.md b/CHANGELOG.md index aa9b0430..20521a63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -141,6 +141,17 @@ seems like currently we do raise, but cover with tests ### TODO: falsify WebDriverException when thrown inside by predicate? +### Deprecated conditions + +- `be.present` in favor of `be.present_in_dom` +- `be.not_.present` in favor of `be.not_.present_in_dom` +- `be.absent` in favor of `be.absent_in_dom` +- `be.not_.absent` in favor of `be.not_.absent_in_dom` + +### Added be.hidden_in_dom in addition to be.hidden + +Consider `be.hidden` as "hidden somewhere, maybe in DOM with "display:none", or even on frontend/backend, i.e. totally absent from the page". Then `be.hidden_in_dom` is stricter, and means "hidden in DOM, i.e. available in the page DOM, but not visible". + ### Text related conditions now accepts int and floats as text item `.have.exact_texts(1, 2.0, '3')` is now possible, and will be treated as `['1', '2.0', '3']` diff --git a/selene/core/entity.py b/selene/core/entity.py index e98863b8..9a47adff 100644 --- a/selene/core/entity.py +++ b/selene/core/entity.py @@ -153,11 +153,11 @@ def _log_webelement_outer_html_for( ) -> Callable[[TimeoutException], Exception]: def log_webelement_outer_html(error: TimeoutException) -> Exception: from selene.core import query - from selene.core.match import present + from selene.core.match import present_in_dom cached = element.cached - if cached.matching(present): + if cached.matching(present_in_dom): return TimeoutException( f'{error.msg}\n' f'Actual webelement: {query.outer_html(element)}' # type: ignore diff --git a/selene/core/match.py b/selene/core/match.py index 69ea416a..4c8b62aa 100644 --- a/selene/core/match.py +++ b/selene/core/match.py @@ -56,15 +56,47 @@ from selene.core.entity import Collection, Element from selene.core._browser import Browser -# TODO: consider renaming to present_in_dom -present: Condition[Element] = Match( +present_in_dom: Condition[Element] = Match( 'is present in DOM', actual=lambda element: element.locate(), by=lambda webelement: webelement is not None, ) -# TODO: consider renaming to absent_in_dom +absent_in_dom: Condition[Element] = Condition.as_not(present_in_dom, 'is absent in DOM') + + +def __deprecated_is_present(element: Element) -> bool: + warnings.warn( + 'be.present is deprecated, use be.present_in_dom instead', + DeprecationWarning, + ) + return element.locate() is not None + + +present: Condition[Element] = Match( + 'is present in DOM', + by=__deprecated_is_present, # noqa +) +"""Deprecated 'is present' condition. Use present_in_dom instead. """ + + absent: Condition[Element] = Condition.as_not(present, 'is absent in DOM') +"""Deprecated 'is absent' condition. Use absent_in_dom instead.""" + + +def __deprecated_is_existing(element: Element) -> bool: + warnings.warn( + 'be.existing is deprecated, use be.present_in_dom instead', + DeprecationWarning, + ) + return element.locate() is not None + + +existing: Condition[Element] = Match( + 'is present in DOM', + by=__deprecated_is_existing, # noqa +) +"""Deprecated 'is existing' condition. Use present_in_dom instead.""" visible: Condition[Element] = Match( @@ -75,7 +107,7 @@ hidden: Condition[Element] = Condition.as_not(visible, 'is hidden') -hidden_in_dom: Condition[Element] = present.and_(visible.not_) +hidden_in_dom: Condition[Element] = present_in_dom.and_(visible.not_) element_is_enabled: Condition[Element] = ElementCondition.raise_if_not( diff --git a/selene/support/conditions/be.py b/selene/support/conditions/be.py index f8927b62..9c53f707 100644 --- a/selene/support/conditions/be.py +++ b/selene/support/conditions/be.py @@ -19,22 +19,19 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. - from selene.core import match from selene.support.conditions import not_ as _not_ not_ = _not_ -visible = match.visible -hidden = match.hidden +present_in_dom = match.present_in_dom +in_dom = match.present_in_dom # TODO: do we need both present_in_dom and in_dom? +absent_in_dom = match.absent_in_dom hidden_in_dom = match.hidden_in_dom -selected = match.element_is_selected - -present = match.present -in_dom = match.present # TODO: do we need both present and in_dom? -existing = match.present # TODO: consider deprecating +hidden = match.hidden +visible = match.visible -absent = match.absent +selected = match.element_is_selected enabled = match.element_is_enabled disabled = match.element_is_disabled @@ -47,3 +44,19 @@ # --- Deprecated --- # empty = match.collection_is_empty + + +present = match.present +"""Deprecated 'is present' condition. Use +[present_in_dom][selene.support.conditions.be.present_in_dom] instead. +""" + +absent = match.absent +"""Deprecated 'is absent' condition. Use +[absent_in_dom][selene.support.conditions.not_.absent_in_dom] instead. +""" + +existing = match.existing +"""Deprecated 'is existing' condition. Use +[present_in_dom][selene.support.conditions.be.present_in_dom] instead. +""" diff --git a/selene/support/conditions/not_.py b/selene/support/conditions/not_.py index ea138d39..6bd20516 100644 --- a/selene/support/conditions/not_.py +++ b/selene/support/conditions/not_.py @@ -23,11 +23,9 @@ import warnings from typing import Any, Iterable -from typing_extensions import cast - from selene.core import match as _match -# --- be.* conditions --- # +# --- be.not_.* conditions --- # from selene.core.condition import Condition from selene.core.entity import Element, Collection from selene.core._browser import Browser @@ -39,19 +37,27 @@ hidden: Condition[Element] = _match.hidden.not_ hidden_in_dom: Condition[Element] = _match.hidden_in_dom.not_ -present: Condition[Element] = _match.present.not_ -in_dom: Condition[Element] = _match.present.not_ -# TODO: do we need both present and in_dom? -# TODO: consider deprecating existing -existing: Condition[Element] = _match.present.not_ +present_in_dom: Condition[Element] = _match.present_in_dom.not_ +absent_in_dom: Condition[Element] = _match.absent_in_dom.not_ +in_dom: Condition[Element] = _match.present_in_dom.not_ -absent: Condition[Element] = _match.absent.not_ enabled: Condition[Element] = _match.element_is_enabled.not_ disabled: Condition[Element] = _match.element_is_disabled.not_ blank: Condition[Element] = _match.element_is_blank.not_ +# --- be.not_.* DEPRECATED conditions --- # + +present: Condition[Element] = _match.present_in_dom.not_ +"""Deprecated 'is not present' condition. Use not_.present_in_dom instead.""" + +existing: Condition[Element] = _match.present_in_dom.not_ +"""Deprecated 'is not existing' condition. Use not_.present_in_dom instead.""" + +absent: Condition[Element] = _match.absent_in_dom.not_ +"""Deprecated 'is not absent' condition. Use not_.absent_in_dom instead.""" + # --- have.* conditions --- # diff --git a/tests/integration/condition__element__present_visible__plus_inversions__compared_test.py b/tests/integration/condition__element__present_visible__plus_inversions__compared_test.py index 2edaed6d..16e73d0c 100644 --- a/tests/integration/condition__element__present_visible__plus_inversions__compared_test.py +++ b/tests/integration/condition__element__present_visible__plus_inversions__compared_test.py @@ -26,6 +26,9 @@ from tests.integration.helpers.givenpage import GivenPage +# TODO: consider refactoring to parametrized test such type of tests + + def test_should_be_hidden__passed_and_failed__compared_to_be_visible(session_browser): browser = session_browser.with_(timeout=0.1) GivenPage(session_browser.driver).opened_with_body( @@ -42,19 +45,19 @@ def test_should_be_hidden__passed_and_failed__compared_to_be_visible(session_bro # THEN - absent.should(match.present.not_) - absent.should(match.present.not_.not_.not_) - absent.should(be.not_.present) + absent.should(match.present_in_dom.not_) + absent.should(match.present_in_dom.not_.not_.not_) + absent.should(be.not_.present_in_dom) - absent.should(match.absent) - absent.should(match.absent.not_.not_) - absent.should(be.absent) - hidden.should(match.present) - hidden.should(be.present) + absent.should(match.absent_in_dom) + absent.should(match.absent_in_dom.not_.not_) + absent.should(be.absent_in_dom) + hidden.should(match.present_in_dom) + hidden.should(be.present_in_dom) hidden.should(be.hidden_in_dom) # same ↙️ - hidden.should(be.present.and_(be.not_.visible)) + hidden.should(be.present_in_dom.and_(be.not_.visible)) hidden.should(be.not_.visible) - hidden.should(be.not_.absent) # TODO: rename to be.not_.absent_in_dom? + hidden.should(be.not_.absent_in_dom) # TODO: rename to be.not_.absent_in_dom? absent.should(match.visible.not_) absent.should(be.not_.visible) @@ -66,8 +69,8 @@ def test_should_be_hidden__passed_and_failed__compared_to_be_visible(session_bro visible.should(be.visible) visible.should(be.not_.hidden) visible.should(be.not_.hidden_in_dom) - visible.should(be.present) - visible.should(be.not_.absent) + visible.should(be.present_in_dom) + visible.should(be.not_.absent_in_dom) # TODO: review and extend/finalize coverage below @@ -135,6 +138,50 @@ def test_should_be_hidden__passed_and_failed__compared_to_be_visible(session_bro ) in str(error) +def x_test_deprecated_should_be_absent__passed_and_failed__compared_(session_browser): + browser = session_browser.with_(timeout=0.1) + GivenPage(session_browser.driver).opened_with_body( + ''' + + + + ''' + ) + + absent = browser.element("#absent") + hidden = browser.element("#hidden") + visible = browser.element("#visible") + + # THEN + + absent.should(match.present.not_) + absent.should(match.present.not_.not_.not_) + absent.should(be.not_.present) + + absent.should(match.absent) + absent.should(match.absent.not_.not_) + absent.should(be.absent) + hidden.should(match.present) + hidden.should(be.present) + hidden.should(be.hidden_in_dom) # same ↙️ + hidden.should(be.present.and_(be.not_.visible)) + hidden.should(be.not_.visible) + hidden.should(be.not_.absent) # TODO: rename to be.not_.absent_in_dom? + + absent.should(match.visible.not_) + absent.should(be.not_.visible) + absent.should(be.hidden) # TODO: should it fail? + absent.should(be.not_.hidden_in_dom) + absent.should(match.hidden_in_dom.not_) + + visible.should(match.visible) + visible.should(be.visible) + visible.should(be.not_.hidden) + visible.should(be.not_.hidden_in_dom) + visible.should(be.present) + visible.should(be.not_.absent) + + def test_action_on_element_found_relatively_from_hidden_element(session_browser): browser = session_browser.with_(timeout=0.1) GivenPage(session_browser.driver).opened_with_body(