Ejemplo n.º 1
0
    def __init__(self, parent, logger=None, **kwargs):
        Widget.__init__(self, parent, logger=logger)
        self.context = kwargs.pop('additional_context', {})
        self.last_fill_data = None

        if not self.fill_strategy:
            if getattr(getattr(self.parent, 'fill_strategy', None), 'respect_parent', False):
                self.fill_strategy = self.parent.fill_strategy
            else:
                self.fill_strategy = DefaultFillViewStrategy()
Ejemplo n.º 2
0
    def __init__(self, parent, logger=None, **kwargs):
        super().__init__(parent, logger=logger, **kwargs)
        self.context = kwargs.pop("additional_context", {})
        self.last_fill_data = None

        if not self.fill_strategy:
            if getattr(getattr(self.parent, "fill_strategy", None), "respect_parent", False):
                self.fill_strategy = self.parent.fill_strategy
            else:
                self.fill_strategy = DefaultFillViewStrategy()
Ejemplo n.º 3
0
class View(Widget):
    """View is a kind of abstract widget that can hold another widgets. Remembers the order,
    so therefore it can function like a form with defined filling order.

    It looks like this:

    .. code-block:: python

        class Login(View):
            user = SomeInputWidget('user')
            password = SomeInputWidget('pass')
            login = SomeButtonWidget('Log In')

            def a_method(self):
                do_something()

    The view is usually instantiated with an instance of
    :py:class:`widgetastic.browser.Browser`, which will then enable resolving of all of the
    widgets defined.

    Args:
        parent: A parent :py:class:`View` or :py:class:`widgetastic.browser.Browser`
        additional_context: If the view needs some context, for example - you want to check that
            you are on the page of user XYZ but you can also be on the page for user FOO, then
            you shall use the ``additional_context`` to pass in required variables that will allow
            you to detect this.
    """
    #: Skip this view in the element lookup hierarchy
    INDIRECT = False
    fill_strategy = None

    def __init__(self, parent, logger=None, **kwargs):
        Widget.__init__(self, parent, logger=logger)
        self.context = kwargs.pop('additional_context', {})
        self.last_fill_data = None

        if not self.fill_strategy:
            if getattr(getattr(self.parent, 'fill_strategy', None),
                       'respect_parent', False):
                self.fill_strategy = self.parent.fill_strategy
            else:
                self.fill_strategy = DefaultFillViewStrategy()

    @staticmethod
    def nested(view_class):
        """Shortcut for :py:class:`WidgetDescriptor`

        Usage:

        .. code-block:: python

            class SomeView(View):
                some_widget = Widget()

                @View.nested
                class another_view(View):
                    pass

        Why? The problem is counting things. When you are placing widgets themselves on a view, they
        handle counting themselves and just work. But when you are creating a nested view, that is a
        bit of a problem. The widgets are instantiated, whereas the views are placed in a class and
        wait for the :py:class:`ViewMetaclass` to pick them up, but that happens after all other
        widgets have been instantiated into the :py:class:`WidgetDescriptor`s, which has the
        consequence of things being out of order. By wrapping the class into the descriptor we do
        the job of :py:meth:`Widget.__new__` which creates the :py:class:`WidgetDescriptor` if not
        called with a :py:class:`widgetastic.browser.Browser` or :py:class:`Widget` instance as the
        first argument.

        Args:
            view_class: A subclass of :py:class:`View`
        """
        return WidgetDescriptor(view_class)

    @property
    def is_displayed(self):
        """Overrides the :py:meth:`Widget.is_displayed`. The difference is that if the view does
        not have the root locator, it assumes it is displayed.

        Returns:
            :py:class:`bool`
        """
        try:
            return super(View, self).is_displayed
        except (LocatorNotImplemented, AttributeError):
            return True

    def move_to(self):
        """Overrides the :py:meth:`Widget.move_to`. The difference is that if the view does
        not have the root locator, it returns None.

        Returns:
            :py:class:`selenium.webdriver.remote.webelement.WebElement` instance or ``None``.
        """
        try:
            return super(View, self).move_to()
        except LocatorNotImplemented:
            return None

    def fill(self, values):
        """Implementation of form filling.

        This method goes through all widgets defined on this view one by one and calls their
        ``fill`` methods appropriately.

        ``None`` values will be ignored.

        It will log any skipped fill items.
        It will log a warning if you pass any extra values for filling.

        It will store the fill value in :py:attr:`last_fill_data`. The data will be "deflattened" to
        ensure uniformity.

        Args:
            values: A dictionary of ``widget_name: value_to_fill``.

        Returns:
            :py:class:`bool` if the fill changed any value.
        """
        changed = []
        values = deflatten_dict(values)
        self.last_fill_data = values
        changed.append(self.before_fill(values))

        extra_keys = set(values.keys()) - set(self.widget_names)
        if extra_keys:
            self.logger.warning(
                'Extra values that have no corresponding fill fields passed: %s',
                ', '.join(extra_keys))
        to_fill = [(getattr(self, n), values[n]) for n in self.widget_names
                   if n in values and values[n] is not None]
        changed.append(self.fill_strategy.do_fill(to_fill))

        a_fill = self.after_fill(any(changed))
        return a_fill if isinstance(a_fill, bool) else any(changed)

    def read(self):
        """Reads the contents of the view and presents them as a dictionary.

        Returns:
            A :py:class:`dict` of ``widget_name: widget_read_value`` where the values are retrieved
            using the :py:meth:`Widget.read`.
        """
        result = {}
        for widget_name in self.widget_names:
            widget = getattr(self, widget_name)
            try:
                value = widget.read()
            except (NotImplementedError, NoSuchElementException,
                    DoNotReadThisWidget):
                continue

            result[widget_name] = value

        return result

    def before_fill(self, values):
        """A hook invoked before the loop of filling is invoked.

        If it returns None, the ``was_changed`` in :py:meth:`fill` does not change. If it returns a
        boolean, then on ``True`` it modifies the ``was_changed`` to True as well.

        Args:
            values: The same values that are passed to :py:meth:`fill`
        """
        pass

    def after_fill(self, was_change):
        """A hook invoked after all the widgets were filled.

        If it returns None, the ``was_changed`` in :py:meth:`fill` does not change. If it returns a
        boolean, that boolean will be returned as ``was_changed``.

        Args:
            was_change: :py:class:`bool` signalizing whether the :py:meth:`fill` changed anything,
        """
        pass