diff --git a/src/widgetastic/browser.py b/src/widgetastic/browser.py index 2d2fb7c8..69d651c7 100644 --- a/src/widgetastic/browser.py +++ b/src/widgetastic/browser.py @@ -20,7 +20,7 @@ from .exceptions import ( NoSuchElementException, UnexpectedAlertPresentException, MoveTargetOutOfBoundsException, - StaleElementReferenceException, NoAlertPresentException, LocatorNotImplemented) + StaleElementReferenceException, NoAlertPresentException, LocatorNotImplemented, WidgetNotFound) from .log import create_widget_logger, null_logger from .xpath import normalize_space from .utils import crop_string_middle @@ -417,7 +417,7 @@ def is_displayed(self, locator, *args, **kwargs): retry = False try: return self.move_to_element(locator, *args, **kwargs).is_displayed() - except (NoSuchElementException, MoveTargetOutOfBoundsException): + except (NoSuchElementException, MoveTargetOutOfBoundsException, WidgetNotFound): return False except StaleElementReferenceException: if isinstance(locator, WebElement) or tries <= 0: diff --git a/src/widgetastic/exceptions.py b/src/widgetastic/exceptions.py index 4e47423e..26124ebc 100644 --- a/src/widgetastic/exceptions.py +++ b/src/widgetastic/exceptions.py @@ -6,17 +6,35 @@ NoAlertPresentException, UnexpectedAlertPresentException) # NOQA -class LocatorNotImplemented(NotImplementedError): +class WidgetasticException(Exception): pass -class WidgetOperationFailed(Exception): +class LocatorNotImplemented(NotImplementedError, WidgetasticException): pass -class DoNotReadThisWidget(Exception): +class WidgetOperationFailed(WidgetasticException): pass -class RowNotFound(IndexError): +class DoNotReadThisWidget(WidgetasticException): pass + + +class RowNotFound(IndexError, WidgetasticException): + pass + + +class WidgetNotFound(WidgetasticException): + """Raised when a widget was not found""" + def __init__(self, widget, original_exception, widget_path): + self.widget = widget + self.original_exception = original_exception + self.widget_path = widget_path + + def get_message(self): + return 'Widget {} not found'.format('/'.join(self.widget_path)) + + def __str__(self): + return self.get_message() diff --git a/src/widgetastic/widget.py b/src/widgetastic/widget.py index b419c376..b69bfeb4 100644 --- a/src/widgetastic/widget.py +++ b/src/widgetastic/widget.py @@ -18,7 +18,7 @@ from .browser import Browser, BrowserParentWrapper from .exceptions import ( NoSuchElementException, LocatorNotImplemented, WidgetOperationFailed, DoNotReadThisWidget, - RowNotFound) + RowNotFound, WidgetNotFound) from .log import ( PrependParentsAdapter, create_widget_logger, logged, call_sig, create_child_logger, create_item_logger) @@ -282,16 +282,24 @@ def __init__(self, parent, arg1, arg2, logger=None): self._initialized_included_widgets = {} def __element__(self): + """Looks up the element in parent browser based on what ``__locator__`` returns. + + Raises: + :py:class:`widgetastic.exceptions.WidgetNotFound` + """ try: locator = self.__locator__() except AttributeError: raise AttributeError( '__locator__() is not defined on {} class'.format(type(self).__name__)) - else: - if isinstance(locator, WebElement): - return locator - else: - return self.parent_browser.element(locator) + + if isinstance(locator, WebElement): + return locator + + try: + return self.parent_browser.element(locator) + except NoSuchElementException as e: + raise WidgetNotFound(self, e, self.widget_names_path) def _get_included_widget(self, includer_id, widget_name, use_parent): if includer_id not in self._initialized_included_widgets: @@ -570,6 +578,22 @@ def __iter__(self): for widget_attr in self.widget_names: yield getattr(self, widget_attr) + @property + def widget_name(self): + """Returns this widget's name as defined on parent widget.""" + try: + return self.parent._desc_name_mapping[self.parent_descriptor] + except AttributeError: + return type(self).__name__ + + @property + def widget_names_path(self): + """Returns a list of the widget hierarchy, referenced by names.""" + if not isinstance(self.parent, Widget): + return [self.widget_name] + else: + return self.parent.widget_names_path + [self.widget_name] + def _gen_locator_meth(loc): def __locator__(self): # noqa @@ -730,7 +754,7 @@ def read(self): widget = getattr(self, widget_name) try: value = widget.read() - except (NotImplementedError, NoSuchElementException, DoNotReadThisWidget): + except (NotImplementedError, WidgetNotFound, DoNotReadThisWidget): continue result[widget_name] = value @@ -2169,7 +2193,7 @@ def __get__(self, o, t): raise TypeError( 'Wrong widget name specified as reference=: {}'.format( self.reference)) - except NoSuchElementException: + except WidgetNotFound: if self.ignore_bad_reference: # reference is not displayed? We are probably aware of this so skip. continue diff --git a/testing/test_view.py b/testing/test_view.py index 55611ce0..0105f7c7 100644 --- a/testing/test_view.py +++ b/testing/test_view.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals import pytest -from widgetastic.exceptions import NoSuchElementException +from widgetastic.exceptions import WidgetNotFound from widgetastic.utils import ParametrizedLocator, ParametrizedString, Parameter, Ignore from widgetastic.widget import ( ParametrizedView, ParametrizedViewRequest, Text, View, Widget, do_not_read_this_widget, @@ -411,7 +411,7 @@ class BarView(View): view = MyView(browser) - with pytest.raises(NoSuchElementException): + with pytest.raises(WidgetNotFound): view.the_switchable_view.widget.read() diff --git a/testing/test_widget.py b/testing/test_widget.py index d1667c9c..03c50ac3 100644 --- a/testing/test_widget.py +++ b/testing/test_widget.py @@ -2,7 +2,8 @@ from __future__ import absolute_import import pytest -from widgetastic.widget import View, Widget, WidgetDescriptor +from widgetastic.exceptions import WidgetNotFound, NoSuchElementException +from widgetastic.widget import View, Widget, WidgetDescriptor, Text def test_widget_correctly_collapses_to_descriptor(browser): @@ -93,3 +94,38 @@ class MyClass4(Widget): assert testw.beef.id == 'beef' assert isinstance(testw.bob, MyWidget) assert testw.bob.id == 'bob' + + +def test_widget_name(browser): + widget_without_parent = Widget(browser) + + assert widget_without_parent.widget_name == 'Widget' + assert widget_without_parent.widget_names_path == ['Widget'] + + class AHostView1(View): + named_widget = Widget() + + view = AHostView1(browser) + assert view.named_widget.widget_name == 'named_widget' + assert view.named_widget.widget_names_path == ['AHostView1', 'named_widget'] + + class ANestedView1(View): + class something_else(View): # NOQA + another_widget = Text('#doesnotexist') + + view = ANestedView1(browser) + assert view.something_else.widget_name == 'something_else' + assert view.something_else.widget_names_path == ['ANestedView1', 'something_else'] + assert view.something_else.another_widget.widget_name == 'another_widget' + assert view.something_else.another_widget.widget_names_path == [ + 'ANestedView1', 'something_else', 'another_widget'] + + try: + view.something_else.another_widget.read() + except WidgetNotFound as e: + assert e.widget is view.something_else.another_widget + assert e.widget_path == ['ANestedView1', 'something_else', 'another_widget'] + assert isinstance(e.original_exception, NoSuchElementException) + assert e.get_message() == 'Widget ANestedView1/something_else/another_widget not found' + else: + pytest.fail('Exception WidgetNotFound was not raised')