Пример #1
0
class LoginPage(View):
    navbar = View.nested(Navbar)
    username = TextInput(id="username")
    password = TextInput(id="password")
    remember_me = Checkbox(id="remember_me")
    login_button = GenericLocatorWidget('.//input[@name="submit"]')
    register = GenericLocatorWidget(".//a[@href='/auth/register']")

    @property
    def is_displayed(self):
        return False
Пример #2
0
def edit_credential(view, original_name, options):
    """Edit a credential through the UI and verify it was edited.

    :param view: The view context (should be the browser view)
    :param original_name: The original name of the credential.
    :param options: The options to be edited within the credential.
    """
    view.refresh()
    dash = DashboardView(view)
    dash.nav.select("Credentials")
    view.wait_for_element(locator=Locator(xpath=(edit_xpath(original_name))))
    GenericLocatorWidget(
        view, locator=Locator(xpath=edit_xpath(original_name))).click()
    modal = CredentialModalView(view, locator=Locator(css=".modal-content"))
    wait_for_animation(1)
    fill_credential_info(view, options)
    # Hack to deal with the fact that the GET refresh isn't
    # implemented when the save button is clicked.
    # https://github.com/quipucords/quipucords/issues/1399
    # https://github.com/quipucords/camayoc/issues/280
    wait_for_animation()
    modal.save_button.click()
    wait_for_animation()
    view.refresh()
    dash.nav.select("Credentials")

    # Assert the row with the credential name exists.
    # If the name was updated, use the new name.
    current_name = original_name
    if "name" in options:
        current_name = options["name"]
    view.wait_for_element(locator=Locator(xpath=row_xpath(current_name)),
                          delay=0.5,
                          timeout=10)
    GenericLocatorWidget(
        view, locator=Locator(xpath=edit_xpath(current_name))).click()
    modal = CredentialModalView(view, locator=Locator(css=".modal-content"))

    # Assert that the changed variables were in fact changed.
    # Passwords are skipped because they aren't accessible.
    for option, data in options.items():
        if ((option == "password") or (option == "become_pass")
                or (option == "source_type")):
            continue
        browser_data = get_field_value(view, CREDENTIAL_FIELD_LABELS[option])
        if option == "sshkeyfile":
            # tmp files are resolved with alias prefixes in some cases.
            # the characters afer the final '/' remain consistent.
            assert browser_data.rpartition("/")[2] == data.rpartition("/")[2]
        else:
            assert browser_data == data
Пример #3
0
class ProfileDetialsView(View):
    title = Text(".//h1")
    edit = GenericLocatorWidget(".//a[normalize-space(.)='Edit your profile']")

    @property
    def is_displayed(self):
        return self.title.text == "User: {}".format(
            self.context["object"].username)
Пример #4
0
class ProfileEditView(View):
    title = Text(".//h1")
    username = TextInput(name="username")
    about = TextInput(name="about_me")
    submit = GenericLocatorWidget(".//input[@name='submit']")

    @property
    def is_displayed(self):
        return self.title.text == "Edit Profile"
Пример #5
0
class AllPostsView(BaseLoggedInView):
    greeting = Text(".//h1")
    text_area = TextInput(name="post")
    submit = GenericLocatorWidget('.//input[@name="submit"]')
    posts = ParametrizedView.nested(PostView)

    @property
    def is_displayed(self):
        return (self.logged_in and self.greeting.text == "Hi, {}!".format(
            self.context["object"].username))
Пример #6
0
class OperatingSystemView(BaseLoggedInView, SearchableViewMixin):
    title = Text("//h1[text()='Operating systems']")
    new = Text("//a[contains(@href, '/operatingsystems/new')]")
    delete = GenericLocatorWidget(
        "//span[contains(@class, 'btn')]/a[@data-method='delete']")

    @property
    def is_displayed(self):
        return self.browser.wait_for_element(self.title,
                                             exception=False) is not None
Пример #7
0
class BaseLoggedInView(View):
    navbar = View.nested(Navbar)
    greeting = Text(".//h1")
    text_area = TextInput(name="post")
    submit = GenericLocatorWidget('.//input[@name="submit"]')
    newer_posts = Text(".//a[contains(normalize-space(.), 'Newer posts')]")
    older_posts = Text(".//a[contains(normalize-space(.), 'Older posts')]")

    @property
    def is_displayed(self):
        return self.newer_posts.is_displayed and self.older_posts.is_displayed
Пример #8
0
def delete_source(view, source_name):
    """Delete a source through the UI."""
    clear_toasts(view=view)
    dash = DashboardView(view)
    dash.nav.select("Sources")
    wait_for_animation()
    view.wait_for_element(locator=Locator(xpath=(delete_xpath(source_name))))
    GenericLocatorWidget(
        view, locator=Locator(xpath=delete_xpath(source_name))).click()
    # mitigate database lock issue quipucords/quipucords/issues/1275
    wait_for_animation()
    DeleteModalView(view).delete_button.click()
    wait_for_animation()
    clear_toasts(view=view)
    with pytest.raises(NoSuchElementException):
        view.wait_for_element(locator=Locator(xpath=delete_xpath(source_name)),
                              timeout=2)
Пример #9
0
def create_source(view, credential_name, source_type, source_name, addresses):
    """Create a source through the UI."""
    clear_toasts(view=view)
    dash = DashboardView(view)
    dash.nav.select("Sources")
    # Display varies depending on whether or not sources already exist.
    wait_for_animation(1)
    try:
        Button(view, "Add Source").click()
    except NoSuchElementException:
        Button(view, "Add").click()

    # Source creation wizard
    modal = SourceModalView(view, locator=Locator(css=".modal-content"))
    radio_label = SOURCE_TYPE_RADIO_LABELS[source_type]

    # Wait for radio button to become responsive before clicking a source type.
    wait_for_animation()
    GenericLocatorWidget(
        modal, locator=Locator(xpath=radio_xpath(radio_label))).click()
    wait_for_animation(1)
    modal.next_button.click()

    # Fill in required source information.
    fill(modal, field_xpath("Name"), source_name)
    if source_type == "Network":
        fill(modal, field_xpath("Search Addresses", textarea=True), addresses)
        fill(modal, field_xpath("Port"), "")  # default port of 22
        cred_dropdown = Dropdown(modal, "Select one or more credentials")
        cred_dropdown.item_select(credential_name)
    else:
        fill(modal, field_xpath("IP Address or Hostname"), addresses)
        cred_dropdown = Dropdown(modal, "Select a credential")
        cred_dropdown.item_select(credential_name)
    Button(modal, "Save").click()
    wait_for_animation(2)
    view.wait_for_element(locator=Locator('//button[text()="Close"]'))
    Button(modal, "Close", classes=[Button.PRIMARY]).click()

    wait_for_animation(1)
    # mitigate database lock issue quipucords/quipucords/issues/1275
    clear_toasts(view=view)
    # Verify that the new row source has been created.
    view.wait_for_element(locator=Locator(xpath=row_xpath(source_name)))
    view.element(locator=Locator(xpath=row_xpath(source_name)))
Пример #10
0
def clear_toasts(view, count=20):
    """Attempt to flush any confirmation dialogs that may have appeared.

    Use this function to clear out dialogs (toasts) that may be
    preventing buttons from being clicked properly. Sometimes it might
    need to be used in succession. By default, this tries to flush a maximum
    of 20 toasts, but will quit early if it cannot find more.
    """
    for i in range(count):
        try:
            view.wait_for_element(locator=Locator(css=".close"), timeout=0.6)
            GenericLocatorWidget(view, locator=Locator(css=".close")).click()
        except (
                MoveTargetOutOfBoundsException,
                NoSuchElementException,
                StaleElementReferenceException,
        ):
            break
Пример #11
0
class PostView(ParametrizedView):
    PARAMETERS = ("post_id", )
    ALL_POSTS = ".//span[contains(@id, 'post')]"
    delete_link = GenericLocatorWidget(
        ParametrizedLocator(".//a[@href='/delete/{post_id}']"))

    @classmethod
    def all(cls, browser):
        return [(int(e.get_attribute("id").lstrip("post")), )
                for e in browser.elements(cls.ALL_POSTS)]

    @classmethod
    def _last_post_id(cls, browser):
        try:
            return max(cls.all(browser))
        except ValueError:
            return 0,

    def delete(self):
        self.delete_link.click()

    @property
    def is_displayed(self):
        return self.delete_link.is_displayed
Пример #12
0
class Pagination(View):
    """Represents the Patternfly pagination.

    https://www.patternfly.org/v4/documentation/react/components/pagination
    """

    ROOT = ParametrizedLocator("{@locator}")
    DEFAULT_LOCATOR = (
        ".//div[contains(@class, 'pf-c-pagination') and not(contains(@class, 'pf-m-compact'))]"
    )

    _first = GenericLocatorWidget(".//button[contains(@data-action, 'first')]")
    _previous = GenericLocatorWidget(
        ".//button[contains(@data-action, 'previous')]")
    _next = GenericLocatorWidget(".//button[contains(@data-action, 'next')]")
    _last = GenericLocatorWidget(".//button[contains(@data-action, 'last')]")
    _options = OptionsMenu()
    _items = Text(".//span[@class='pf-c-options-menu__toggle-text']")
    _current_page = TextInput(locator=".//input[@aria-label='Current page']")
    _total_pages = Text(
        ".//div[@class='pf-c-pagination__nav-page-select']/span")

    def __init__(self, parent, locator=None, logger=None):
        View.__init__(self, parent=parent, logger=logger)
        if not locator:
            locator = self.DEFAULT_LOCATOR
        self.locator = locator
        self._cached_per_page_value = None

    @property
    def is_first_disabled(self):
        """Returns boolean detailing if the first page button is disabled."""
        return not self.browser.element(self._first).is_enabled()

    def first_page(self):
        """Clicks on the first page button."""
        if self.no_items or self.is_first_disabled:
            raise PaginationNavDisabled("first")
        self._first.click()

    @property
    def is_previous_disabled(self):
        """Returns boolean detailing if the previous page button is disabled."""
        return not self.browser.element(self._previous).is_enabled()

    def previous_page(self):
        """Clicks the previous page button."""
        if self.no_items or self.is_previous_disabled:
            raise PaginationNavDisabled("previous")
        self._previous.click()

    @property
    def is_next_disabled(self):
        """Returns boolean detailing if the next page button is disabled."""
        return not self.browser.element(self._next).is_enabled()

    def next_page(self):
        """Clicks the next page button."""
        if self.is_next_disabled:
            raise PaginationNavDisabled("next")
        self._next.click()

    @property
    def is_last_disabled(self):
        """Returns boolean detailing if the last page button is disabled."""
        return not self.browser.element(self._last).is_enabled()

    def last_page(self):
        """Clicks the last page button."""
        if self.is_last_disabled:
            raise PaginationNavDisabled("last")
        self._last.click()

    @property
    def current_page(self):
        """Returns an int of the current page number."""
        return int(self._current_page.value)

    @property
    def total_pages(self):
        """Returns int detailing the total number of pages."""
        return int(self._total_pages.text.strip().split()[1])

    @property
    def displayed_items(self):
        """Returns a string detailing the number of displayed items information.

        example "1 - 20 of 523 items"
        """
        items_string = self._items.text
        first_num, last_num = items_string.split("of")[0].split("-")
        return int(first_num.strip()), int(last_num.strip())

    @property
    def total_items(self):
        """Returns a string detailing the number of displayed items"""
        items_string = self._items.text
        return int(items_string.split("of")[1].split()[0])

    @property
    def per_page_options(self):
        """Returns an iterable of the available pagination options."""
        return self._options.items

    @property
    def no_items(self):
        """Returns wether the pagination object has elements or not"""
        return not self.total_items

    @property
    def current_per_page(self):
        """Returns an integer detailing how many items are cshown per page."""
        if self._cached_per_page_value:
            return self._cached_per_page_value

        if self.no_items:
            return 0
        else:
            return int(self._options.selected_items[0].split()[0])

    @contextmanager
    def cache_per_page_value(self):
        """
        A context manager that can be used to prevent looking up the 'current page' value.

        This adds some efficiencies when iterating over pages or in cases where it is safe to
        assume that the "per page" setting is not going to change and it's not necessary to
        re-read it from the browser repeatedly.
        """
        self._cached_per_page_value = None
        self._cached_per_page_value = self.current_per_page
        yield
        self._cached_per_page_value = None

    def set_per_page(self, count):
        """Sets the number of items per page. (Will cast to str)"""
        value = str(count)
        value_per_page = "{} per page".format(value)
        items = self._options.items
        if value_per_page in items:
            self._options.item_select(value_per_page)
        elif value in items:
            self._options.item_select(value)
        else:
            raise ValueError(
                "count '{}' is not a valid option in the pagination dropdown".
                format(count))

    def go_to_page(self, value):
        """Navigate to custom page number."""
        self._current_page.fill(value)
        self.browser.send_keys(Keys.RETURN, self._current_page)

    def __iter__(self):
        if self.current_page > 1:
            self.first_page()
        self._page_counter = 0
        return self

    def __next__(self):
        if self._page_counter < self.total_pages:
            self._page_counter += 1
            if self._page_counter > 1:
                self.next_page()
            return self._page_counter
        else:
            raise StopIteration
Пример #13
0
class Pagination(View):
    """Represents the Patternfly pagination.

    https://www.patternfly.org/v4/documentation/react/components/pagination
    """

    ROOT = ParametrizedLocator("{@locator}")
    _first = GenericLocatorWidget(".//button[contains(@data-action, 'first')]")
    _previous = GenericLocatorWidget(".//button[contains(@data-action, 'previous')]")
    _next = GenericLocatorWidget(".//button[contains(@data-action, 'next')]")
    _last = GenericLocatorWidget(".//button[contains(@data-action, 'last')]")
    _options = Dropdown()
    _items = Text(".//span[@class='pf-c-options-menu__toggle-text']")
    _current_page = TextInput(locator=".//input[@aria-label='Current page']")
    _total_pages = Text(".//div[@class='pf-c-pagination__nav-page-select']/span")

    def __init__(self, parent, locator, logger=None):
        View.__init__(self, parent=parent, logger=logger)
        self.locator = locator

    @property
    def is_first_disabled(self):
        return "pf-m-disabled" in self.browser.classes(self._first)

    def first_page(self):
        self._first.click()

    @property
    def is_previous_disabled(self):
        return "pf-m-disabled" in self.browser.classes(self._previous)

    def previous_page(self):
        self._previous.click()

    @property
    def is_next_disabled(self):
        return "pf-m-disabled" in self.browser.classes(self._next)

    def next_page(self):
        self._next.click()

    @property
    def is_last_disabled(self):
        return "pf-m-disabled" in self.browser.classes(self._last)

    def last_page(self):
        self._last.click()

    @property
    def current_page(self):
        return int(self._current_page.value)

    @property
    def total_pages(self):
        # example "of 6 pages"
        return int(self._total_pages.text.strip().split()[1])

    @property
    def displayed_items(self):
        items_string = self._items.text
        # example "1 - 20 of 523 items"
        first_num, last_num = items_string.split("of")[0].split("-")
        return int(first_num.strip()), int(last_num.strip())

    @property
    def total_items(self):
        items_string = self._items.text
        return int(items_string.split("of")[1].split()[0])

    @property
    def per_page_options(self):
        return self._options.items

    def set_per_page(self, count):
        # convert a possible int to string
        value = str(count)
        value_per_page = "{} per page".format(value)
        items = self._options.items
        if value_per_page in items:
            self._options.item_select(value_per_page)
        elif value in items:
            self._options.item_select(value)
        else:
            raise ValueError(
                "count '{}' is not a valid option in the pagination dropdown".format(count)
            )

    def __iter__(self):
        self.first_page()
        self._page_counter = 0
        return self

    def __next__(self):
        if self._page_counter < self.total_pages:
            self._page_counter += 1
            if self._page_counter > 1:
                self.next_page()
            return self._page_counter
        else:
            raise StopIteration

    def next(self):
        # For sake Python 2 compatibility
        return self.__next__()